User:Cm~mediawikiwiki/CategoryWatch
Appearance
<?php
/**
* CategoryWatch extension
* - Extends watchlist functionality to include notification about membership changes of watched categories
*
* See http://www.mediawiki.org/Extension:CategoryWatch for installation and usage details
* See http://www.organicdesign.co.nz/Extension_talk:CategoryWatch for development notes and disucssion
*
* @package MediaWiki
* @subpackage Extensions
* @author Aran Dunkley [http://www.organicdesign.co.nz/nad User:Nad]
* @copyright © 2008 Aran Dunkley
* @licence GNU General Public Licence 2.0 or later
*
*
* Extended by Cynthia Mattingly @ Marketing Factory Consulting
*
* Automatically notify users of edits to a site under a category which
* they are watching
*
* Recursive lookup of watching (subcategories of watched category)
*/
if ( !defined('MEDIAWIKI' ) ) die( 'Not an entry point.' );
define( 'CATEGORYWATCH_VERSION', '1.1.0, 2009-04-21' );
$wgCategoryWatchNotifyEditor = true;
$wgCategoryWatchUseAutoCat = false;
$wgExtensionFunctions[] = 'wfSetupCategoryWatch';
$wgExtensionCredits['other'][] = array(
'path' => __FILE__,
'name' => 'CategoryWatch',
'author' => '[http://www.organicdesign.co.nz/User:Nad User:Nad]',
'description' => 'Extends watchlist functionality to include notification about membership changes of watched categories',
'descriptionmsg' => 'categorywatch-desc',
'url' => 'http://www.mediawiki.org/wiki/Extension:CategoryWatch',
'version' => CATEGORYWATCH_VERSION,
);
$wgExtensionMessagesFiles['CategoryWatch'] = dirname(__FILE__) . '/CategoryWatch.i18n.php';
class CategoryWatch {
function __construct() {
global $wgHooks;
$wgHooks['ArticleSave'][] = $this;
$wgHooks['ArticleSaveComplete'][] = $this;
}
/**
* Recursively find all parents of the given categories
*
* @param $catarray array
* @return void
*/
function findCategoryParents ($catarray) {
$this->i++;
if ($this->i == 200) {
return;
}
$dbr = wfGetDB( DB_SLAVE );
$cl = $dbr->tableName( 'categorylinks' );
foreach ($catarray as $catname) {
$this->allparents[] = $catname;
$id = $this->getCategoryArticleId($catname);
if (is_numeric($id)) {
$res = $dbr->select( $cl, 'cl_to', "cl_from = $id", __METHOD__, array( 'ORDER BY' => 'cl_sortkey' ) );
while ( $row = $dbr->fetchRow( $res ) ) {
$this->allparents[] = $row[0];
$parents = $this->findCategoryParents(array($row[0]));
if (is_array($parents)) {
$this->allparents = array_merge($this->allparents, $parents);
}
}
$dbr->freeResult( $res );
}
}
$this->allparents = array_unique($this->allparents);
}
/**
* Load page ID of one category
*
* @param $catname string
* @return integer
*/
function getCategoryArticleId ($catname) {
$dbr = wfGetDB( DB_SLAVE );
$cl = $dbr->tableName( 'page' );
$res = $dbr->select( $cl, 'page_id', "page_title = '$catname'", __METHOD__);
$row = $dbr->fetchRow( $res );
$dbr->freeResult( $res );
return $row[0];
}
/**
* Get a list of categories before article updated
*/
function onArticleSave( &$article, &$user, &$text ) {
global $wgCategoryWatchUseAutoCat;
$this->before = array();
$dbr = wfGetDB( DB_SLAVE );
$cl = $dbr->tableName( 'categorylinks' );
$id = $article->getID();
$res = $dbr->select( $cl, 'cl_to', "cl_from = $id", __METHOD__, array( 'ORDER BY' => 'cl_sortkey' ) );
while ( $row = $dbr->fetchRow( $res ) ) $this->before[] = $row[0];
$dbr->freeResult( $res );
# If using the automatically watched category feature, ensure that all users are watching it
if ( $wgCategoryWatchUseAutoCat ) {
$dbr = wfGetDB( DB_SLAVE );
# Find all users not watching the autocat
$like = str_replace( ' ', '_', trim( wfMsg( 'categorywatch-autocat', '' ) ) );
$utbl = $dbr->tableName( 'user' );
$wtbl = $dbr->tableName( 'watchlist' );
$sql = "SELECT user_id FROM $utbl LEFT JOIN $wtbl ON user_id=wl_user AND wl_title LIKE '%$like%' WHERE wl_user IS NULL";
$res = $dbr->query( $sql );
# Insert an entry into watchlist for each
while ( $row = $dbr->fetchRow( $res ) ) {
$uname = User::newFromId( $row[0] )->getName();
$wl_title = str_replace( ' ', '_', wfMsg( 'categorywatch-autocat', $uname ) );
$dbr->insert( $wtbl, array( 'wl_user' => $row[0], 'wl_namespace' => NS_CATEGORY, 'wl_title' => $wl_title ) );
}
$dbr->freeResult( $res );
}
return true;
}
/**
* Find changes in categorisation and send messages to watching users
*/
function onArticleSaveComplete( &$article, &$user, &$text, &$summary, &$medit ) {
# Get cats after update
$this->after = array();
$this->allparents = array();
$dbr = wfGetDB( DB_SLAVE );
$cl = $dbr->tableName( 'categorylinks' );
$id = $article->getID();
$res = $dbr->select( $cl, 'cl_to', "cl_from = $id", __METHOD__, array( 'ORDER BY' => 'cl_sortkey' ) );
while ( $row = $dbr->fetchRow( $res ) ) $this->after[] = $row[0];
$dbr->freeResult( $res );
$page = $article->getTitle();
$pagename = $page->getPrefixedText();
$pageurl = $page->getFullUrl();
$page = "$pagename ($pageurl)";
$this->i = 0;
$this->findCategoryParents($this->after);
## For each active parent category, send the mail
foreach ( $this->allparents as $cat ) {
$title = Title::newFromText( $cat, NS_CATEGORY );
$message = wfMsg( 'categorywatch-catchange', $page, $this->friendlyCat( $cat ) );
$this->notifyWatchers( $title, $user, $message, $summary, $medit );
}
# Get list of removed cats
$sub = array_diff( $this->before, $this->after );
# Notify watchers of each removed cat
if ( count( $sub ) > 0 ) {
if ( count( $sub ) == 1 ) {
$sub = array_shift( $sub );
$title = Title::newFromText( $sub, NS_CATEGORY );
$message = wfMsg( 'categorywatch-catmoveout', $page, $this->friendlyCat( $sub ));
$this->notifyWatchers( $title, $user, $message, $summary, $medit );
} else {
foreach ( $sub as $cat ) {
$title = Title::newFromText( $cat, NS_CATEGORY );
$message = wfMsg( 'categorywatch-catsub', $page, $this->friendlyCat( $cat ) );
$this->notifyWatchers( $title, $user, $message, $summary, $medit );
}
}
}
return true;
}
/**
* Return "Category:Cat (URL)" from "Cat"
*/
function friendlyCat( $cat ) {
$cat = Title::newFromText( $cat, NS_CATEGORY );
$catname = $cat->getPrefixedText();
$caturl = $cat->getFullUrl();
return "$catname ($caturl)";
}
function notifyWatchers( &$title, &$editor, &$message, &$summary, &$medit ) {
global $wgLang, $wgEmergencyContact, $wgNoReplyAddress, $wgCategoryWatchNotifyEditor,
$wgEnotifRevealEditorAddress, $wgEnotifUseRealName, $wgPasswordSender, $wgEnotifFromEditor;
# Get list of users watching this category
$dbr = wfGetDB( DB_SLAVE );
$conds = array( 'wl_title' => $title->getDBkey(), 'wl_namespace' => $title->getNamespace() );
if ( !$wgCategoryWatchNotifyEditor) $conds[] = 'wl_user <> ' . intval( $editor->getId() );
$res = $dbr->select( 'watchlist', array( 'wl_user' ), $conds, __METHOD__ );
# Wrap message with common body and send to each watcher
$page = $title->getPrefixedText();
$adminAddress = new MailAddress( $wgPasswordSender, 'WikiAdmin' );
$editorAddress = new MailAddress( $editor );
$summary = $summary ? $summary : ' - ';
$medit = $medit ? wfMsg( 'minoredit' ) : '';
while ( $row = $dbr->fetchRow( $res ) ) {
$watchingUser = User::newFromId( $row[0] );
$timecorrection = $watchingUser->getOption( 'timecorrection' );
$editdate = $wgLang->timeanddate( wfTimestampNow(), true, false, $timecorrection );
if ( $watchingUser->getOption( 'enotifwatchlistpages' ) && $watchingUser->isEmailConfirmed()
&& $editor->getName() != $watchingUser->getName()) {
$to = new MailAddress( $watchingUser );
$subject = wfMsg( 'categorywatch-emailsubject', $page );
$body = wfMsgForContent( 'enotif_body' );
# Reveal the page editor's address as REPLY-TO address only if
# the user has not opted-out and the option is enabled at the
# global configuration level.
$name = $wgEnotifUseRealName ? $editor->getRealName() : $editor->getName();
if ( $wgEnotifRevealEditorAddress
&& ( $editor->getEmail() != '' )
&& $editor->getOption( 'enotifrevealaddr' ) ) {
if ( $wgEnotifFromEditor ) {
$from = $editorAddress;
} else {
$from = $adminAddress;
$replyto = $editorAddress;
}
} else {
$from = $adminAddress;
$replyto = new MailAddress( $wgNoReplyAddress );
}
# Define keys for body message
$userPage = $editor->getUserPage();
$keys = array(
'$WATCHINGUSERNAME' => $watchingUser->mName,
'$NEWPAGE' => $message,
'$PAGETITLE' => $page,
'$PAGEEDITDATE' => $editdate,
'$CHANGEDORCREATED' => wfMsgForContent( 'changed' ),
'$PAGETITLE_URL' => $title->getFullUrl(),
'$PAGEEDITOR_WIKI' => $userPage->getFullUrl(),
'$PAGESUMMARY' => $summary,
'$PAGEMINOREDIT' => $medit,
'$OLDID' => ''
);
if ( $editor->isIP( $name ) ) {
$utext = wfMsgForContent( 'enotif_anon_editor', $name );
$subject = str_replace( '$PAGEEDITOR', $utext, $subject );
$keys['$PAGEEDITOR'] = $utext;
$keys['$PAGEEDITOR_EMAIL'] = wfMsgForContent( 'noemailtitle' );
} else {
$subject = str_replace( '$PAGEEDITOR', $name, $subject );
$keys['$PAGEEDITOR'] = $name;
$emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $name );
$keys['$PAGEEDITOR_EMAIL'] = $emailPage->getFullUrl();
}
$keys['$PAGESUMMARY'] = $summary;
# Replace keys, wrap text and send
$body = strtr( $body, $keys );
$body = wordwrap( $body, 72 );
if ( function_exists( 'userMailer' ) ) userMailer( $to, $from, $subject, $body, $replyto );
else UserMailer::send( $to, $from, $subject, $body, $replyto );
}
}
$dbr->freeResult( $res );
}
/**
* Needed in some versions to prevent Special:Version from breaking
*/
function __toString() { return __CLASS__; }
}
function wfSetupCategoryWatch() {
global $wgCategoryWatch;
wfLoadExtensionMessages( 'CategoryWatch' );
# Instantiate the CategoryWatch singleton now that the environment is prepared
$wgCategoryWatch = new CategoryWatch();
}