======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): %%(sql) 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) 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 .= "&a=".$page["id"]; $page_to_diff_a = $page["id"]; } if ($c == 2) { $diff .= "&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 = '
The page Href("",$wakka->GetPageTag(),"")."\">"; $message_c .= $wakka->GetPageTag()." has been edited by " . $wakka->GetUserName() . "
\n"; $message_c .= 'Hello " . $emailusername . ", this is an automated response from config["base_url"] ."\">". $wakka->config["wakka_name"] . "
"; $message_reason = "You are receiving this message because " . $reason . "
"; $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) 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 ?> FormClose(); } ?> %% ===Install Page Modification Handler=== %%(php) // 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** %%(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}} %%