Watched Pages Extension


This is yet another extension which will allow your site to email users when there are changes to pages which they're interested in. Additionally, users can watch entire categories, and will be notified whenever the set of pages belonging to that category change.

Users can configure which pages they're interested in watching via a watchedpages action, which would typically be added to their UserSettings page.

Emails include a word-based text diff of the raw page source, which allows users to easily see what's changed.

Credit for this extension goes partly to WikkaWikiEMailNotifications, on which it is based. Most of the code is by skrap.

Rationale


Why another one of these change-emailing extensions? What's wrong with NotifyOnChange and WikkaWikiEMailNotifications?

The short answer: nothing's wrong with those other extensions. The authors of those extensions were designing for other needs. However, I think my extension has a bit more general-purpose applicability, and it's easier to use.

This plugin:
* allows users to watch pages which they don't own
* makes minimal use of the config file, and instead makes use of the DB to track watched pages and categories
* allows users to watch entire categories, and be notified when their page set changes
* allows normal users to watch the entire wiki, if they want to
* uses normal read ACLs to determine access
* encapsulates almost all of the code in a library file, leaving it minimally invasive to install

Current Status


This code is being used in one "production" website right now, with around 700 pages of content. I consider it stable for my usage, but please report any bugs you see to me at jonah at petri dot us. However, I have a fix in my local wikkawiki distribution which is needed for this extension to work. This fix has been submitted to the wikkawiki bug tracker, but as of version 1.3.3 it has not been integrated.

You'll need to apply the patch from this bug for this to work:
* https://wush.net/trac/wikka/ticket/1136
Hopefully this won't be needed for long!

Bugs:
* Sends emails to watchers of all pages on page creation, which could leak a sensitive page name before an ACL could be set up for it.
* There's no flood protection - 1000 changes will mean 1000 emails.

Installation


WARNING: I've not tried this installation on a "clean" wikkawiki install. These steps are my recollection of the correct order of installation.

See note in Current Status about a bug fix needed for this.

Add a table to your DB


To create the table for this feature (substitute [PREFIX_] to your db prefix):
     CREATE TABLE `[PREFIX_]watchedpages` (
     `name` VARCHAR(75) NOT NULL DEFAULT '',
     `allpages` enum('Y','N') NOT NULL DEFAULT 'N',
     `pageslist` text NOT NULL DEFAULT '',
     `categorieslist` text NOT NULL DEFAULT '',
     PRIMARY KEY (`name`)
     );


Install PHP/FineDiff

The code uses PHP/FineDiff to put high quality diffs into the emails. I've got a version of PHP/FineDiff which is utf8-aware, and is available here: https://github.com/skrap/PHP-FineDiff/blob/master/finediff.php

You should put that file into 3rdparty/plugins/finediff/finediff.php

Install watchedpages lib


Place the following into libs/watchedpages.class.php:
<?php
   
    /**
     * Core support code for Watched Pages
     *
     * @package Wikka
     * @subpackage Libs
     * @version $Id$
     * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
     * @filesource
     *
     * @author {@link http://wikkawiki.org/skrap skrap}
     *
     */

   
    /**
     * To create the table for this feature (substitute [PREFIX_] to your db prefix):
     CREATE TABLE `[PREFIX_]watchedpages` (
     `name` varchar(75) NOT NULL default '',
     `allpages` enum('Y','N') NOT NULL default 'N',
     `pageslist` text NOT NULL default '',
     `categorieslist` text NOT NULL default '',
     PRIMARY KEY (`name`)
     );
     *
     */

   
    /**
     * Public access methods for watched pages info.
     *
     * @name WatchedPages
     * @package Wikka
     * @subpackage Libs
     */

    class WatchedPages
    {
        var $username;
        var $pageslist;
        var $categorieslist;
        var $allpages;
        var $loaded;
       
        /**
         * Constructor
         *
         * @access  public
         * @param   object  $wakka  Provides access to the main Wakka object
         */

        function WatchedPages($wakka)
        {
            $this->wakka= $wakka;
            $user = $wakka->GetUser();
            $this->username = $user['name'];
            $this->pageslist= array();
            $this->categorieslist= array();
            $this->loaded= FALSE;
        }
       
        /**
         * Load the watched pages info from the DB
         *
         * @access  public
         * @param   none
         * @return  TRUE if load successful, FALSE otherwise
         */

        function Load()
        {
            if ($this->username) {
                $queryresult= $this->wakka->LoadSingle( 'SELECT name,allpages,pageslist,categorieslist FROM '
                                                 . $this->wakka->config['table_prefix']
                                                 . 'watchedpages WHERE name = \''
                                                 . $this->username . '\' LIMIT 1' );
               
                if (is_array($queryresult)) {
                    $this->pageslist = array_filter( explode( ',', $queryresult['pageslist'] ), array($this->wakka,"IsWikiName") );
                    $this->categorieslist = array_filter( explode( ',', $queryresult['categorieslist'] ), array($this->wakka,"IsWikiName") );
                    $this->allpages = $queryresult['allpages'] == 'Y';
                    $this->loaded = TRUE;
                }
            }
        }
       
        /**
         * Gets the stored array of watched page names, loading from DB if needed.
         *
         * @access public
         * @param none
         * @return an array, which will be empty if load failed.
         */

        function GetWatchedPages() {
            if ($this->loaded != TRUE) {
                $this->Load();
            }
            if ($this->loaded) {
                return $this->pageslist;
            } else {
                return array();
            }
        }
       
        /**
         * Sets the array of watched page names.  Does not commit to the DB.
         *
         * @access public
         * @param watchedpageslist, an array of page names to watch.
         * @return TRUE
         *
         */

        function SetWatchedPages( $watchedpageslist ) {
            if (is_array($watchedpageslist)) {
                $this->pageslist= array_filter( $watchedpageslist, array($this->wakka,"IsWikiName") );
            }
        }
       
        /**
         * Gets stored array of watched categories, loading from DB if needed
         *
         */

        function GetWatchedCategories() {
            if (!$this->loaded) $this->Load();
            if ($this->loaded) return $this->categorieslist;
            else return array();
        }
       
        /**
         * Sets watched categories, but does not commit to DB.
         */

        function SetWatchedCategories( $watchedcategories ) {
            if (is_array($watchedcategories)) {
                $this->categorieslist= array_filter( $watchedcategories, array($this->wakka,"IsWikiName") );
            }
        }
       
        /**
         * Gets whether the user prefers to watch all pages, loading from DB
         * if needed.
         *
         * @access public
         * @param none
         * @return TRUE or FALSE
         */

        function GetWatchesAllPages() {
            if ($this->loaded != TRUE) {
                $this->Load();
            }
            if ($this->loaded) {
                return $this->allpages;
            } else {
                return FALSE;
            }
        }
       
       
        /**
         * Sets the watches all pages flag.  Does not commit to the DB.
         *
         * @access public
         * @param allpages, TRUE or FALSE
         * @return TRUE
         *
         */

        function SetWatchesAllPages( $allpages ) {
            $this->allpages= $allpages == TRUE;
        }
       
        /**
         * Saves the current data to the DB
         *
         * @access  public
         * @return  TRUE on success, otherwise FALSE
         */

        function Save()
        {
            if ($this->username) {
                $updateresult = $this->wakka->Query("REPLACE INTO "
                                                    .$this->wakka->GetConfigValue('table_prefix')
                                                    ."watchedpages (name,allpages,pageslist,categorieslist) VALUES ('"
                                                    .mysql_real_escape_string($this->username)
                                                    ."','"
                                                    .mysql_real_escape_string($this->allpages==TRUE?'Y':'N')
                                                    ."','"
                                                    .mysql_real_escape_string(implode(',',$this->pageslist))
                                                    ."','"
                                                    .mysql_real_escape_string(implode(',',$this->categorieslist))
                                                    ."')");
                if ($updateresult) {
                    return TRUE;
                }
            }
            return FALSE;
        }
       
        /**
         * Renders an array of pages to a string
         *
         * @access public
         * @return a string
         */

        function WatchedPagesListToString($watchedpagesarray) {
            return implode( ', ', $watchedpagesarray );
        }
       
        /**
         * Takes a string containing a list of watched pages, and separates it into an array.
         *
         * @access public
         * @return an array
         */

        function WatchedPagesStringToArray($watchedpagesstring) {
            return preg_split( '/\s*,\s*/', $watchedpagesstring );
        }
       
    }
   
    /**
     * Get the usernames who would watch the last diff for the current page
     *
     * @param wakka instance of wakka
     * @param before_page_text
     * @param after_page_text
     * @return array of UserName => "reason"
     */

   
    function getWatchers($wakka,$before_page_text,$after_page_text) {
        $emailuserslist= array();

        if ($before_page_text != '' || $after_page_text != '') {
            if (is_array($watchers = $wakka->LoadAll('SELECT name,allpages,pageslist,categorieslist FROM '
                                                     . $wakka->GetConfigValue('table_prefix')
                                                     . 'watchedpages;'))) {
                foreach ($watchers as $watcher) {
                    if (!$wakka->HasAccess('read', $wakka->GetPageTag(), $watcher['name'])) continue;
                   
                    if ($watcher['allpages'] == 'Y') {
                        $emailuserslist[$watcher['name']] = 'you watch all changes.';
                        continue;
                    }
                   
                    if( preg_match('/(^|,)'.preg_quote($wakka->GetPageTag()).'(,|$)/', $watcher['pageslist']) ) {
                        // push onto the end of the array
                        $emailuserslist[$watcher['name']] = 'you watch the page "' . $wakka->GetPageTag() . '".';
                        continue;
                    }
                    // Look at the category list to see if any category name is present in the added or deleted text.
                    foreach (explode(',', $watcher['categorieslist']) as $categoryname) {
                        $pattern = '/\b' . preg_quote($categoryname) . '\b/';
                        $in_before_text = preg_match($pattern, $before_page_text);
                        $in_after_text = preg_match($pattern, $after_page_text);
                        if( $in_before_text != $in_after_text ) {
                            $emailuserslist[$watcher['name']] = 'you watch the category "' . $categoryname . '".';
                            continue;
                        }
                    }
                }
            }
        }
        return $emailuserslist;
    }
   
   
    /**
     * Send email to users watching the given page.  Call this during the edit handler.
     *
     *
     */

    function EmailPageWatchers($wakka) {
        $message_debug = '';
       
        $page_to_diff_a = FALSE;
        $page_to_diff_b = FALSE;
       
        // Step through and check this page id and previous revision page id
        if ($pages = $wakka->LoadRevisions($wakka->GetPageTag()))
        {
            $c = 0;
            $diff = $wakka->GetPageTag() . "/diff";
            foreach ($pages as $page)
            {
                $c++;
                if ($c <= 3)
                {
                    if ($c == 1)
                    {
                        $diff .= "&amp;a=".$page["id"];
                        $page_to_diff_a = $page["id"];
                    }
                    if ($c == 2)
                    {
                        $diff .= "&amp;b=".$page["id"];
                        $page_to_diff_b = $page["id"];
                    }
                }
            }
        }
       
        $emailuserslist= array();
        if( $page_to_diff_a != FALSE && $page_to_diff_b != FALSE ) {
           
            $pageA = $wakka->LoadPageById($page_to_diff_a);
            $pageB = $wakka->LoadPageById($page_to_diff_b);
           
            $beforeText = $pageB['body'];
            $afterText = $pageA['body'];

            $emailuserslist= getWatchers($wakka,$beforeText,$afterText);
        }
       
        if ( count($emailuserslist) && $pageA['body'] != '' && $pageB['body'] != '' )
        {
            // Produce email headers text
            $headers  = "From: " . $wakka->config["email_notifications_sender_name"];
            $headers .= " <" . $wakka->config["email_notifications_sender_email"] . ">\n";
            $headers .= "X-Mailer: PHP/".phpversion()."\n";       //mailer name
            $headers .= "X-Priority: 3\n";                        //1 = UrgentMessage, 3 =Normal
            $headers .= 'Content-Type: text/html; charset="UTF-8"'."\n";  //comment this to send text format
           
            $subject = "[" . $wakka->config["email_notifications_subject"] . "] ";
            $subject .= $wakka->GetPageTag() . " has been edited by " . $wakka->GetUserName();
           
            // html message prologue
            $message_a  = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
            <!-- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -->
            <html xmlns="http://www.w3.org/1999/xhtml">
            <head>
            <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
            <title></title></head><body>'
;
            $message_a .=  '<div style="color: #000000; font-family: \'Lucida Grande\', Verdana, Arial, Sans-Serif;';
            $message_a .= 'background-color: #E9F9E9;border: 1px solid #ACA;padding: 5px 10px;font-size: 90%;margin-bottom: 1em;">'."\n";
           
            //$message_a .= print_r($emailnotify_user_list, true); //debug
           
            $message_c  = "<p>The page <a href=\"" . $wakka->Href("",$wakka->GetPageTag(),"")."\">";
            $message_c .= $wakka->GetPageTag()."</a> has been edited by " . $wakka->GetUserName() . "</p>\n";
            $message_c .= '<div style="font-size: 70%; margin-bottom: 1.5em;">';
            $message_c .= '<a href="' . $wakka->Href("",$diff,"") . '">[Full Differences]</a>&nbsp;';
            $message_c .= '<a href="' . $wakka->Href("",$diff,"") . '&amp;fastdiff=1">[Simple Differences]</a>&nbsp;';
            $message_c .= '<a href="' . $wakka->Href('revisions') . '">[Revisions]</a>&nbsp;';
            $message_c .= '<a href="' . $wakka->Href('history') . '">[Page History]</a>&nbsp;';
            $message_c .= '<a href="' . $wakka->Href('acls') . '">[Page ACLs]</a>&nbsp;';
            $message_c .= '</div>';
           
            // message epilogue - close out the HTML
            $message_epilogue = '</div></body></html>';

           
            // Clear this in case we don't install the diff part of the code
            $message_diff = '';
           
            if ($wakka->config["email_notifications_include_diff"] == 1) // Check config file to see if we are doing diffs in email
            {
                // These should really go at the top of the page, but it would add an extra step to this mod...
                if (!defined('ERROR_BAD_PARAMETERS')) define ('ERROR_BAD_PARAMETERS', 'Sorry, no revisions to compare were specified.');
                if (!defined('CONTENT_ADDITIONS_HEADER')) define ('CONTENT_ADDITIONS_HEADER', 'Additions:');
                if (!defined('CONTENT_DELETIONS_HEADER')) define ('CONTENT_DELETIONS_HEADER', 'Deletions:');
                if (!defined('CONTENT_NO_DIFFERENCES')) define ('CONTENT_NO_DIFFERENCES', 'No Differences');
                if (!defined('WHEN_BY_WHO')) define('WHEN_BY_WHO', '%1$s by %2$s');
                if (!defined('UNREGISTERED_USER')) define('UNREGISTERED_USER', 'unregistered user');
                if (!defined('DIFF_CONTEXT_CHARS')) define('DIFF_CONTEXT_CHARS', 200);
               
                $info = '';
               
                // Do the simple diff check line by line
                $finediff_lib = '3rdparty'.DIRECTORY_SEPARATOR.'plugins'.DIRECTORY_SEPARATOR.'finediff'.DIRECTORY_SEPARATOR.'finediff.php';
                require_once($finediff_lib);
               
                $diff = new FineDiff($beforeText, $afterText, FineDiff::$wordGranularity);
                $rendered_diff = '';
               
                $in_offset = 0;    
                $last_in_written = 0;
                ob_start();
                foreach ($diff->getOps() as $edit) {
                    $n = $edit->getFromLen();
                    $first = isset($first) ? FALSE : TRUE;
                    if ( $edit instanceof FineDiffCopyOp ) {
                        // This only handles the diff epilogue - prologue is handled below.
                        if (!$first) {
                            $write_len = min($n,DIFF_CONTEXT_CHARS);
                            echo htmlspecialchars(mb_substr($beforeText,$in_offset,$write_len,'UTF-8'));
                            $last_in_written = $in_offset+$write_len;
                        }
                    }
                    else {
                        // Handle the diff prologue, if needed
                        if ( $last_in_written < $in_offset ) {
                            $write_offset = max($last_in_written,$in_offset-DIFF_CONTEXT_CHARS);
                            if( $write_offset > $last_in_written && !$first ) echo '<hr />'; // separate discontinuous sections with a HR
                            $write_len = $in_offset-$write_offset;
                            echo htmlspecialchars(mb_substr($beforeText,$write_offset,$write_len,'UTF-8'));
                            $last_in_written += $write_len;
                        }
                        if ( $edit instanceof FineDiffInsertOp || $edit instanceof FineDiffReplaceOp ) {
                            echo '<ins style="background-color: #CFC; text-decoration: none;">', htmlspecialchars(mb_substr($edit->getText(), 0, $edit->getToLen(),'UTF-8')), '</ins>';
                        }
                        if ( $edit instanceof FineDiffDeleteOp || $edit instanceof FineDiffReplaceOp ) {
                            $deletion = mb_substr($beforeText,$in_offset,$n,'UTF-8');
                            echo '<del style="color: #876; background-color: #FC9;text-decoration: line-through;">', htmlspecialchars($deletion), '</del>';
                        }
                    }
                    $in_offset += $n;
                }
                $rendered_diff = ob_get_clean();
               
                $pageA_edited_by = $pageA['user'];
                if (!$wakka->LoadUser($pageA_edited_by)) $pageA_edited_by .= ' ('.UNREGISTERED_USER.')';
                if ($pageA['note']) $noteA='['.$wakka->htmlspecialchars_ent($pageA['note']).']'; else $noteA ='';
               
                $pageB_edited_by = $pageB['user'];  
                if (!$wakka->LoadUser($pageB_edited_by)) $pageB_edited_by .= ' ('.UNREGISTERED_USER.')';
                if ($pageB['note']) $noteB='['.$wakka->htmlspecialchars_ent($pageB['note']).']'; else $noteB ='';
               
                $info =  '<div style="color: #000000;background-color: #F9F9F9;font-family: monospace;';
                $info .= 'border: 1px solid #ACA;padding: 5px 10px;margin-bottom: 1em;">'."\n";
                $info .= '<b>Comparing <a title="Display the revision list for '.$pageA['tag'].'" href="'.$wakka->Href('revisions');
                $info .= '">revisions</a> for <a title="Return to the current revision of the page" href="';
                $info .= $wakka->Href().'">'.$pageA['tag'].'</a></b>'."\n";
                $info .= '<ul style="margin: 10px 0;monospace;">'."\n";
                $info .= '  <li><a href="'.$wakka->Href('show', '', 'time='.urlencode($pageA['time'])).'">['.$pageA['id'].']</a> ';
                $info .= sprintf(WHEN_BY_WHO, '<a style="color: #666;monospace;" href="'.
                                 $wakka->Href('show','','time='.urlencode($pageA["time"])).'">'.$pageA['time'].'</a>', $pageA_edited_by);
                $info .= ' <span style="color: #888;">'.$noteA.'</span></li>'."\n";
                $info .= '  <li><a href="'.$wakka->Href('show', '', 'time='.urlencode($pageB['time'])).'">['.$pageB['id'].']</a> ';
                $info .= sprintf(WHEN_BY_WHO, '<a style="color: #666;monospace;" href="'.
                                 $wakka->Href('show','','time='.urlencode($pageB["time"])).'">'.$pageB['time'].'</a>', $pageB_edited_by);
                $info .= ' <span style="color: #888;">'.$noteB.'</span></li>'."\n";
                $info .= '</ul>'."\n";
                $info .= '<strong>'.HIGHLIGHTING_LEGEND.'</strong>&nbsp;<ins style="background-color: #CFC; text-decoration: none;">'.DIFF_SAMPLE_ADDITION.'</ins>&nbsp;<del style="color: #876; background-color: #FC9;text-decoration: line-through;">'.DIFF_SAMPLE_DELETION.'</del>';
                $info .= '</div>';
               
                $message_diff .=  $info;
                $message_diff .= '<div style="font-family: monospace;color: #666;background-color: #F9F9F9;border: 1px solid #ACA;padding: 0.5em;">';
                $message_diff .= nl2br($rendered_diff);
                $message_diff .= '</div>';
            }
           
            foreach ($emailuserslist as $emailusername => $reason) {
                if ($wakka->GetUserName() == $emailusername) continue; // don't send watch emails for a user's own edits.
                // find the user's email address
                $useremail  = $wakka->LoadSingle("select email from " .$wakka->config["table_prefix"]."users where name = '".mysql_escape_string($emailusername)."'");
               
                if( $useremail ) {
                    // make personallised part of email message
                    $message_b  = "<p>Hello " . $emailusername . ", this is an automated response from <a href=\"";
                    $message_b  .= $wakka->config["base_url"] ."\">". $wakka->config["wakka_name"] . "</a></p>";
                   
                    $message_reason = "<p>You are receiving this message because " . $reason . "</p>";
                   
                    $message = $message_prologue . $message_debug . $message_a . $message_b . $message_reason . $message_c . $message_diff . $message_epilogue;
                   
                    // Send out the email to the user
                    mail( $useremail['email'], $subject, $message, $headers, '-f' . $wakka->config["email_notifications_sender_email"] );
                }
            }
        }
    }    
   
    ?>


Install watchedpages action


This code goes in plugins/actions/watchedpages/watchedpages.php:
<?php
    /**
     * Display a form to view and modify watched pages
     *
     * @package     Actions
     * @version     $Id$
     * @license     http://www.gnu.org/copyleft/gpl.html GNU General Public License
     * @filesource
     *
     * @author      {@link http://wikkawiki.org/skrap skrap}
     *
     * @todo    complete @uses
     */

   
    /**#@+
     * Default value.
     */

    if (!defined('WATCHED_PAGES_LEGEND')) define('WATCHED_PAGES_LEGEND', 'Watched Pages');
    if (!defined('WATCHED_PAGES_LABEL')) define('WATCHED_PAGES_LABEL', 'Watched Page List');
    if (!defined('WATCHED_CATEGORIES_LABEL')) define('WATCHED_CATEGORIES_LABEL', 'Watched Category List');
    if (!defined('WATCHED_PAGES_UPDATE_BUTTON')) define('WATCHED_PAGES_UPDATE_BUTTON', 'Update');
    if (!defined('ALL_PAGES_LABEL')) define('ALL_PAGES_LABEL', 'Watch All Pages');
   
    // user is logged in
    if ($user = $this->GetUser())
    {
        // Create watchedpages object
        include_once('libs/watchedpages.class.php');
        $watchedpages = new WatchedPages($this);
       
       
        // load stored settings
        $watchedpages->Load();
       
        // is user trying to update their watched pages?
        if ($this->GetSafeVar('action', 'post') == 'watchedpagesupdate')
        {
            // get POST parameters
            $watchedpageslist = $this->GetSafeVar('watchedpageslist', 'post');
            $watchedpages->SetWatchedPages($watchedpages->WatchedPagesStringToArray($watchedpageslist));
            $watchedcategorieslist = $this->GetSafeVar('watchedcategorieslist', 'post');
            $watchedpages->SetWatchedCategories($watchedpages->WatchedPagesStringToArray($watchedcategorieslist));
            $watchedpages->SetWatchesAllPages($this->GetSafeVar('allpages', 'post') == 'Y');
            $watchedpages->Save();
        }
       
        // get the set pages list.
        $watchedpageslist= $watchedpages->WatchedPagesListToString($watchedpages->GetWatchedPages());
        $watchedcategorieslist= $watchedpages->WatchedPagesListToString($watchedpages->GetWatchedCategories());
       
        // *** BEGIN WATCHED PAGES
        echo $this->FormOpen(); // open watchedpages form
        ?>
<fieldset id="watchedpageslist" class="usersettings">
<legend><?php echo WATCHED_PAGES_LEGEND ?></legend>
<label for="watchedpageslist"><?php echo WATCHED_PAGES_LABEL ?></label>
<input type="text" name="watchedpageslist" size="40" value="<?php echo $watchedpageslist ?>" />
<br />
<label for="watchedcategorieslist"><?php echo WATCHED_CATEGORIES_LABEL ?></label>
<input type="text" name="watchedcategorieslist" size="40" value="<?php echo $watchedcategorieslist ?>" />
<br />
<label for="allpages"><?php echo ALL_PAGES_LABEL ?></label>
<input type="hidden" name="allpages" value="N" />
<input id="allpages" type="checkbox" name="allpages" value="Y" <?php echo $watchedpages->GetWatchesAllPages() == 'Y' ? 'checked="checked"' : '' ?> />
<br />
<input type="hidden" name="action" value="watchedpagesupdate" />
<input type="submit" value="<?php echo WATCHED_PAGES_UPDATE_BUTTON ?>" />
</fieldset>
<?php
    echo $this->FormClose();
    }
    ?>


Install Page Modification Handler


               // Load the watched pages lib if needed, and email this page's watchers.
               include_once('libs/watchedpages.class.php');
               if (function_exists('EmailPageWatchers')) {
                   EmailPageWatchers($this);
               }


Configure Your Emails

Add to your wikka.config.php
    'email_notifications_sender_name' => 'My Bestest Wiki',
    'email_notifications_sender_email' => 'MyWiki@MyHost.com',
    'email_notifications_subject' => 'MyWiki',
    'email_notifications_include_diff' => '1'


Update your UserSettings page

Add the following action tag to your
UserSettings page:
{{WatchedPages}}

There are no comments on this page.
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki