Wiki Email Notifications

This was originally based on the Notify on Change patch from

This patch allows any user to monitor any change on any page (immediately it occurs and not using crontab). It was set up as I tend to use my wiki for collaborative work, and the users stopped using it as they would not be aware that others had made changes without regular monitoring. RSS too was not much use as most pages were restricted to certain users.

There is not a lot of code to change, and it is mostly in a couple of files.

Features (With all below code installed)
- default users listed (in main conf) who always receive all changes (e.g. admins)
- users can get styled/html formatted emails showing simple diffs of each page change
- page owners can edit the list of people who receive emails for their page (on the Edit ACLs page)
TODO: users can select whether they receive emails for any particular page (check box on edit page maybe?)


V1.0 - zorruno 2010-01-10 - original, tested on WikkaWiki 1.2-p1
V1.1 - zorruno 2010-04-02 - bug fixes to acls.php & minor fixes
V1.2 - zorruno 2010-04-05 - accidentally deleted some of the acls.php mod, now back in & more minor fixes
V1.3 - zorruno 2010-05-02 - bug fix, Null user in array causing email warnings

To Do:

- Fix the last part of my code to add tick boxes for users & upload here
- add page number to subject line?
- add comments about code checks needed
- add comment about emails to users with no acl access
- new pages generate error about no diff available?
- mode the inline css code into a template css file?
- make behave more like ACLs e.g with + and - users etc?


I've broken the code into steps to make it easier to see what is going on. You may also not want to install everything if you only want some of the functionality.
  1. additions to the wikki config file
  2. adding one column to a table in the db (you don't need this if you have a fixed list of users that need to be notified for all pages)
  3. the main code that generates the email on every page edit (with just this installed, you can either just use a fixed list of users to be notified for all pages, or manually edit the database to say which users will be notified for each page)
  4. the code that generates simple diffs that can be sent off with each email (if you don't want diffs emailed, set the config accordingly)
  5. code that allows the owner of each page to decide who will be notified of each change for that page (set similar to the acl lists)
  6. TODO: code that allows each user to individually turn notifications on or off for any page. (a tick box is set on each page for the user that they can opt in or out with)

Step 1:

Changes to wikka.config.php (in the root directory). Add these to the end (or somewhere) in the array of config items. Watch where the commas go - you don't need it with the last item in the array.
	'email_notifications_db' => '1',
	'email_notifications_all' => 'JohnSmith,DaveAdmin,MoxiMary',
	'email_notifications_include_diff' => '1',
	'email_notifications_subject' => 'Johns Wiki',
	'email_notifications_sender_name' => 'Johns Wiki Robot',
	'email_notifications_sender_email' => '',

- Set this to 1 to use a list of users for each page from the db (you must must do step 2).
- Set it to 0 to not obtain any usernames from the database (there is no need to do the database change in step 2 if this is set to 0)
- Add the users in a comma separated list that you want notified of ALL page changes e.g. admins.
- Leave this empty ( => '' ) if you just want database users.
- Set this to '1' if you want a simple diff of the most recent page revision sent to the users (you must install the diff code below)
- This is some text in [square brackets] that will be sent at the beginning the subject line of all notification emails.
- This is the email name that emails will be sent from
- This is the email address that emails will be sent from

Step 2:

SQL code to add the user list for each page. You don't need this if you plan to have a fixed set of users that will be notified on any page change (i.e. you have set 'email_notifications_db' => '0'. Run this in phpmyadmin or similar. Ensure you use the correct prefix for your database tables (e.g. mine below is wikka_ )

ALTER TABLE `wikka_acls` ADD `emailnotify_acl` TEXT NULL 

Step 3:

- This is the code you need to insert for the main part of this mod.
- OPEN the file handlers/page/edit.php
- ADD the following code, just before //forward

// --------------------------------------------------------------------------
// START: Email Notification Mod
// Mod for sending emails out to users on page modification
// --------------------------------------------------------------------------

if (function_exists('mail') && ($this->config["email_notifications_db"]== 1 || $this->config["email_notifications_all"]))
    // Get list of users who should get notifications on ALL page changes from config
    if ($this->config["email_notifications_all"])
        $users_to_notify_on_all = explode(",", $this->config["email_notifications_all"]);

    // Get list of users who should get notifications on changes to THIS PAGE from the database                      
    if ($this->config["email_notifications_db"]== 1)
        $db_users = $this->LoadSingle("select emailnotify_acl from " . $this->config["table_prefix"] . "acls where page_tag = '" . mysql_escape_string($this->GetPageTag()) . "'");
        $db_users_to_notify = explode("\n",$this->TrimACLs($db_users[emailnotify_acl]));

        // Join the array for users from the db and users from the config file...
        // and remove duplicate users so they don't get multiple emails...
        // and remove blanks
        $emailnotify_user_list = array_filter(array_unique(array_merge($users_to_notify_on_all, $db_users_to_notify)));

    // Step through and check this page id and previous revision page id
    if ($pages = $this->LoadRevisions($this->tag))
        $c = 0;
        $diff = $this->GetPageTag() . "/diff?";
        foreach ($pages as $page)
            if ($c <= 3)
                if ($c == 1)
                    $diff .= "a=".$page["id"];
                    $page_to_diff_a = $page["id"];
                if ($c == 2)
                    $diff .= "&amp;b=".$page["id"];
                    $page_to_diff_b = $page["id"];
    // Produce email headers text
    $headers  = "From: " . $this->config["email_notifications_sender_name"];
    $headers .= " <" . $this->config["email_notifications_sender_email"] . ">\r\n";
    $headers .= "X-Mailer: PHP/".phpversion()."\r\n";       //mailer name
    $headers .= "X-Priority: 3\r\n";                        //1 = UrgentMessage, 3 =Normal
    $headers .= "Content-Type: text/html; charset=iso-8859-1\r\n";  //comment this to send text format

    $subject = "[" . $this->config["email_notifications_subject"] . "] The page \"";
    $subject .= $this->GetPageTag() . "\" has been edited by " . $this->GetUserName();

    // Produce email message text
    $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=\"" . $this->Href("",$tag,"")."\">";
    $message_c .= $this->GetPageTag()."</a> has been edited by " . $this->GetUserName() . "</p>\n";
    $message_c .= '<p div style="font-size: 70%">';
    $message_c .= '<a href="' . $this->Href("",$diff,"") . '">[Full Differences]</a>&nbsp;';
    $message_c .= '<a href="' . $this->Href("",$diff,"") . '&fastdiff=1">[Simple Differences]</a>&nbsp;';
    $message_c .= '<a href="' . $this->Href('revisions') . '">[Revisions]</a>&nbsp;';
    $message_c .= '<a href="' . $this->Href('history') . '">[Page History]</a>&nbsp;';
    $message_c .= '<a href="' . $this->Href('acls') . '">[Page ACLs]</a>&nbsp;';
    $message_c .= "</p>";
    $message_c .= "</div>";
    // Clear this in case we don't install the diff part of the code
    $message_diff = '';
    // --- Install the simple diff part of the mod HERE ---

    // Send email to each user who needs to get email      
    foreach ($emailnotify_user_list as $emailnotify_user)
    //to do - better if we check if user has actual read access to this page before sending
    //to do - could also have a parameter in the config as to whether we bother sending to the page modifying user
        $MailAdr  = $this->LoadSingle("select email from " .$this->config["table_prefix"]."users where name = '".mysql_escape_string($emailnotify_user)."'");
        // make personallised part of email message
        $message_b  = "<p>Hello " . $emailnotify_user . ", this is an automated response from <a href=\"";
        $message_b  .= $this->config["base_url"] ."\">". $this->config["wakka_name"] . "</a></p>";
        $message = $message_a . $message_b . $message_c . $message_diff;

        // Send out the email to the user
        mail($MailAdr["email"], $subject, $message, $headers);  

// --------------------------------------------------------------------------
// END: Email Notification Mod
// mod for sending emails out to users on page modification
// --------------------------------------------------------------------------

Step 4:

- You need this if you want simple page diffs printed in the email.
- Most of this code was taken from the diff.php page handler
- ADD this code where it tells you to in step 3 (inside that code).

    // --------------------------------------------------------------------------
    // START: Get simple diff for Email Notification Mod
    // --------------------------------------------------------------------------
    if ($this->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');

        $output = '';
        $info = '';

        $pageA = $this->LoadPageById($page_to_diff_a);
        $pageB = $this->LoadPageById($page_to_diff_b);
        // Do the simple diff check line by line
        if ($pageA != '' && $pageB != '')  //If pages are both empty, likely it is a new page so we return nothing
            //echo '<em class="error">'.ERROR_BAD_PARAMETERS.'</em><br />';
            //echo '</div>'."\n";
            $pageA_edited_by = $pageA['user'];
            if (!$this->LoadUser($pageA_edited_by)) $pageA_edited_by .= ' ('.UNREGISTERED_USER.')';
            if ($pageA['note']) $noteA='['.$this->htmlspecialchars_ent($pageA['note']).']'; else $noteA ='';

            $pageB_edited_by = $pageB['user']
            if (!$this->LoadUser($pageB_edited_by)) $pageB_edited_by .= ' ('.UNREGISTERED_USER.')';
            if ($pageB['note']) $noteB='['.$this->htmlspecialchars_ent($pageB['note']).']'; else $noteB ='';
            $bodyA = explode("\n", $this->htmlspecialchars_ent($pageA['body']));
            $bodyB = explode("\n", $this->htmlspecialchars_ent($pageB['body']));

            $added   = array_diff($bodyA, $bodyB);
            $deleted = array_diff($bodyB, $bodyA);
            $info =  '<div style="color: #000000;background-color: #E9F9E9;font-family: \'Lucida Console\', Monaco, monospace;';
            $info .= 'border: 1px solid #ACA;padding: 5px 10px;font-size: 85%;margin-bottom: 1em;">'."\n";
            $info .= '<b>Comparing <a title="Display the revision list for '.$pageA['tag'].'" href="'.$this->Href('revisions');
            $info .= '">revisions</a> for <a title="Return to the current revision of the page" href="';
            $info .= $this->Href().'">'.$pageA['tag'].'</a></b>'."\n";
            $info .= '<ul style="margin: 10px 0;font-size: 80%;font-family: \'Lucida Console\', Monaco, monospace;">'."\n";
            $info .= '  <li><a href="'.$this->Href('show', '', 'time='.urlencode($pageA['time'])).'">['.$pageA['id'].']</a> ';
            $info .= sprintf(WHEN_BY_WHO, '<a style="color: #666;font-size: 80%;font-family: \'Lucida Console\', Monaco, monospace;" href="'.
                    $this->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="'.$this->Href('show', '', 'time='.urlencode($pageB['time'])).'">['.$pageB['id'].']</a> ';
            $info .= sprintf(WHEN_BY_WHO, '<a style="color: #666;font-size: 80%;font-family: \'Lucida Console\', Monaco, monospace;" href="'.
                    $this->Href('show','','time='.urlencode($pageB["time"])).'">'.$pageB['time'].'</a>', $pageB_edited_by);
            $info .= ' <span style="color: #888;">'.$noteB.'</span></li>'."\n";
            $info .= '</ul>'."\n";

            if ($added)
                $output .= "\n".'<b style="clear:both;">'.CONTENT_ADDITIONS_HEADER.'</b>'."\n";
                $output .= '<div style="font-family: monospace; font-size: 1.1em;color: #666;background-color: #F9F9F9;
                            border: 1px solid #CCC;padding: 1em;
                            margin-bottom:.5em;"><ins style="background-color: #CFC;
                            text-decoration: none;">'
.nl2br(implode("\n", $added)) . '</ins></div>' . "\n";
            if ($deleted)
                $output .= "\n".'<b style="clear:both;">'.CONTENT_DELETIONS_HEADER.'</b>'."\n";
                $output .= '<div style="font-family: monospace; font-size: 1.1em;color: #666;background-color: #F9F9F9;
                            border: 1px solid #CCC;padding: 1em;
                            margin-bottom:.5em;"><del style="color: #876;
                            background-color: #FC9;text-decoration: none;">'
.nl2br(implode("\n", $deleted)) . '</del></div>' . "\n";
            if (!$added && !$deleted)
                $output .= '<br />'."\n".CONTENT_NO_DIFFERENCES;
            $message_diff .=  $info.$output;
    //  --------------------------------------------------------------------------
    // END: Get simple diff for Email Notification Mod
    // --------------------------------------------------------------------------

Step 5:

- If you want the page owners to be able to update the list of who gets notification emails, install these code changes
- When installed, an additional box will show on the Edit ACLs page that page owners can add/edit a list of users who will receive email notifications.
- Note that this will produce a fourth text boxes on the ACLs page it may make your template layout ugly, especially if it is not very wide.

OPEN up the page handlers/page/acls.php

AFTER: or somewhere in the language definition stuff(around line 16)

// --------------------------------------------------------------------------
// START: Email Notification Mod
// Mod for sending emails out to users on page modification
// --------------------------------------------------------------------------
// There will be no EMAILNOTIFY_ACL_LABEL defined ...unless we also add something to the language files, so feel free to do so
if (!defined('EMAILNOTIFY_ACL_LABEL')) define('EMAILNOTIFY_ACL_LABEL', 'Email Notify:');
if (!defined('ACL_SYNTAX_HELP_EMAIL_NOTIFY')) define('ACL_SYNTAX_HELP_EMAIL_NOTIFY', '--- --- Note that the Email Notify list requires a list of users only, and will ignore ##*##, ##+## and ##!## ---(including users prefixed with !).');
// END: Email Notifications mod

AFTER: (around line 67)
$default_comment_acl = $this->GetConfigValue('default_comment_acl');

            // START: Email Notifications mod - V1.1
            $default_emailnotify_acl    = '';    //not really needed, but may use if mod is expanded
            $posted_emailnotify_acl = $_POST['emailnotify_acl'];
            // END: Email Notifications mod

FIND AND DELETE: (around line 84)
if ($page

($posted_read_acl != $default_read_acl

$posted_write_acl != $default_write_acl

$posted_comment_acl != $default_comment_acl))
$this->SaveACL($this->GetPageTag(), 'read', $this->TrimACLs($posted_read_acl));
$this->SaveACL($this->GetPageTag(), 'write', $this->TrimACLs($posted_write_acl));
$this->SaveACL($this->GetPageTag(), 'comment', $this->TrimACLs($posted_comment_acl));
$message = ACLS_UPDATED;

if ($page

($posted_read_acl != $default_read_acl

$posted_write_acl != $default_write_acl

$posted_comment_acl != $default_comment_acl

$posted_emailnotify_acl != $default_emailnotify_acl)) Added this Line: Email Notifications mod - V1.1
$this->SaveACL($this->GetPageTag(), 'read', $this->TrimACLs($posted_read_acl));
$this->SaveACL($this->GetPageTag(), 'write', $this->TrimACLs($posted_write_acl));
$this->SaveACL($this->GetPageTag(), 'comment', $this->TrimACLs($posted_comment_acl));
$this->SaveACL($this->GetPageTag(), 'emailnotify', $this->TrimACLs($posted_emailnotify_acl)); Added this Line: Email Notifications mod - V1.1
$message = ACLS_UPDATED;

<textarea name="comment_acl" rows="4" cols="20"><?php echo $this->ACLs['comment_acl'] ?></textarea>

<!-- START: Email Notifications mod -->
<strong><?php echo EMAILNOTIFY_ACL_LABEL; ?></strong><br />
<textarea name="emailnotify_acl" rows="4" cols="20"><?php echo $this->ACLs['emailnotify_acl'] ?></textarea>
<!-- END: Email Notifications mod -->

AFTER: (around line 167) - this part is optional really
<?php echo $this->Format(ACL_SYNTAX_HELP); ?>

<!-- START: Email Notifications mod -->
<?php echo $this->Format(ACL_SYNTAX_HELP_EMAIL_NOTIFY); ?>
<!-- END: Email Notifications mod -->

OPEN up the page libs/Wakka.class.php

AFTER: (around line 2021)
$default = "read_acl = , write_acl = , comment_acl = , ";

    // START: Email Notifications mod
    $default .= "emailnotify_acl = '', ";
    //END: Email Notifications mod

There are 2 comments on this page. [Show comments]