Revision history for WatchedPages


Revision [21745]

Last edited on 2012-08-28 21:00:00 by skrap
Additions:
Credit for this extension goes partly to WikkaWikiEMailNotifications, on which it is based. Most of the code is by [[skrap]].


Revision [21744]

Edited on 2012-08-28 20:54:39 by skrap
Additions:
===Add a table to your DB===
To create the table for this feature (substitute [PREFIX_] to your db prefix):
%%(sql)
===Update your UserSettings page==
Add the following action tag to your UserSettings page:
{{WatchedPages}}


Revision [21743]

Edited on 2012-08-28 20:52:08 by skrap
Additions:
You'll need to apply the patch from this bug for this to work:
Hopefully this won't be needed for long!
''See note in Current Status about a bug fix needed for 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'
Deletions:
You'll need to add the patch from this bug for this to work:
''STILL UNDER CONSTRUCTION.''


Revision [21742]

Edited on 2012-08-28 20:48:34 by skrap
Additions:
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 add the patch from this bug for this to work:
* https://wush.net/trac/wikka/ticket/1136
Deletions:
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##.


Revision [21741]

Edited on 2012-08-27 21:51:57 by skrap
Additions:
===Install watchedpages action===
This code goes in plugins/actions/watchedpages/watchedpages.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
* @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();


Revision [21740]

Edited on 2012-08-27 21:44:51 by skrap
Additions:
===Install PHP/FineDiff===
===Install watchedpages lib===
===Install Page Modification Handler===
Deletions:
==Install PHP/FineDiff==
==Install watchedpages lib==
==Install Page Modification Handler==


Revision [21739]

Edited on 2012-08-27 21:41:25 by skrap
Additions:
''WARNING: I've not tried this installation on a "clean" wikkawiki install. These steps are my recollection of the correct order of installation.''
''STILL UNDER CONSTRUCTION.''
==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)
<?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 .= "&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 = '<!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> ';
$message_c .= '<a href="' . $wakka->Href("",$diff,"") . '&fastdiff=1">[Simple Differences]</a> ';
$message_c .= '<a href="' . $wakka->Href('revisions') . '">[Revisions]</a> ';
$message_c .= '<a href="' . $wakka->Href('history') . '">[Page History]</a> ';
$message_c .= '<a href="' . $wakka->Href('acls') . '">[Page ACLs]</a> ';
$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> <ins style="background-color: #CFC; text-decoration: none;">'.DIFF_SAMPLE_ADDITION.'</ins> <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 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);
}
%%
Deletions:
(coming soon)


Revision [21738]

Edited on 2012-08-27 20:56:29 by skrap
Additions:
* There's no flood protection - 1000 changes will mean 1000 emails.
(coming soon)


Revision [21737]

Edited on 2012-08-27 20:54:55 by skrap
Additions:
Why another one of these change-emailing extensions? What's wrong with NotifyOnChange and WikkaWikiEMailNotifications?
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##.
Deletions:
Why another one of these change-emailing extension? What's wrong with NotifyOnChange and WikkaWikiEMailNotifications?
This code is being used in one "production" website right now, with around 700 pages of content.


Revision [21736]

The oldest known version of this page was created on 2012-08-27 20:23:31 by skrap
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki