MarkAHershberger has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/386007 )
Change subject: add Echo support ...................................................................... add Echo support * Sprinkle debug statements throught to help with that. * Add shim to work w/o WatchedItemStore from MediaWikiServices. * Make third arg on CategoryAfterPageRemovalHook optional. * Add title-message key that older Echo requires and silently dies without. Change-Id: Id0df74ccef1f9c6ea51c22396977f5424cbe19ae --- M .gitignore D CategoryWatch.php A assets/catwatch.svg M extension.json M i18n/en.json M i18n/qqq.json A src/CategoryWatch.php A src/EchoEventPresentationModel.php A src/Hook.php 9 files changed, 905 insertions(+), 464 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/CategoryWatch refs/changes/07/386007/1 diff --git a/.gitignore b/.gitignore index 4a59931..d2d7c2e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *~ \#*\# .\#* +.tramp_history # misc PHPTAGS.sqlite diff --git a/CategoryWatch.php b/CategoryWatch.php deleted file mode 100644 index aca8f23..0000000 --- a/CategoryWatch.php +++ /dev/null @@ -1,453 +0,0 @@ -<?php -/** - * CategoryWatch extension - * - Extends watchlist functionality to include notification about membership - * changes of watched categories - * - * Copyright (C) 2008 Aran Dunkley - * Copyright (C) 2017 Sean Chen - * Copyright (C) 2017 Mark A. Hershberger - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * - * See https://www.mediawiki.org/Extension:CategoryWatch - * for installation and usage details - * See http://www.organicdesign.co.nz/Extension_talk:CategoryWatch - * for development notes and disucssion - * - * @file - * @ingroup 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 - */ - -class CategoryWatch { - // Instance - protected static $watcher; - - /** - * The extension function. - * It has to be the static function in a class now. - */ - public static function setupCategoryWatch() { - wfDebugLog( 'CategoryWatch', 'loading extension...' ); - - # Instantiate the CategoryWatch singleton now - # that the environment is prepared - self::$watcher = new CategoryWatch(); - } - - /** - * Get a list of categories before article updated Since MediaWiki - * version 1.25.x, we have to use static function for hooks. the - * hook has different signatures. - * @param WikiPage $wikiPage the page - * @param User $user who is modifying - * @param Content $content the new article content - * @param string $summary the article summary (comment) - * @param bool $isMinor minor flag - * @param bool $isWatch watch flag (not used, aka always null) - * @param int $section section number (not used, aka always null) - * @param int $flags see WikiPage::doEditContent documentation for flags' definition - * @param Status $status Status (object) - */ - public static function onPageContentSave( - $wikiPage, $user, $content, $summary, $isMinor, - $isWatch, $section, $flags, $status - ) { - global $wgCategoryWatchUseAutoCat, $wgCategoryWatchUseAutoCatRealName, - $wgCategoryWatch; - - self::$watcher->before = []; - $dbr = wfGetDB( DB_MASTER ); - $cl = $dbr->tableName( 'categorylinks' ); - $id = $wikiPage->getID(); - wfDebugLog( 'CategoryWatch', "tablename = $cl" ); - wfDebugLog( 'CategoryWatch', "page id=$id" ); - $res = $dbr->select( - $cl, 'cl_to', "cl_from = $id", __METHOD__, - [ 'ORDER BY' => 'cl_sortkey' ] - ); - $row = $dbr->fetchRow( $res ); - while ( $row ) { - self::$watcher->before[] = $row[0]; - $row = $dbr->fetchRow( $res ); - } - $dbr->freeResult( $res ); - wfDebugLog( 'CategoryWatch', 'Categories before page saved' ); - wfDebugLog( 'CategoryWatch', join( ', ', self::$watcher->before ) ); - - # 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( wfMessage( 'categorywatch-autocat', '' )->text() ) - ); - $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"; - - # Insert an entry into watchlist for each - $row = $dbr->fetchRow( $res ); - while ( $row ) { - $user = User::newFromId( $row[0] ); - $name = $wgCategoryWatchUseAutoCatRealName - ? $user->getRealName() - : $user->getName(); - $wl_title = str_replace( - ' ', '_', wfMessage( 'categorywatch-autocat', $name )->text() - ); - $dbr->insert( - $wtbl, - [ - 'wl_user' => $row[0], 'wl_namespace' => NS_CATEGORY, - 'wl_title' => $wl_title - ] - ); - $row = $dbr->fetchRow( $res ); - } - $dbr->freeResult( $res ); - } - } - - /** - * the proper hook for save page request. - * @see https://www.mediawiki.org/wiki/Manual:Hooks/PageContentSaveComplete - * @param WikiPage $article Article edited - * @param User $user who edited - * @param Content $content New article text - * @param string $summary Edit summary - * @param bool $isMinor Minor edit or not - * @param bool $isWatch Watch this article? - * @param string $section Section that was edited - * @param int $flags Edit flags - * @param Revision $revision that was created - * @param Status $status of activities - * @param int $baseRevId base revision - */ - public static function onPageContentSaveComplete( - $article, $user, $content, $summary, $isMinor, $isWatch, $section, - $flags, $revision, $status, $baseRevId - ) { - # Get cats after update - self::$watcher->after = []; - - $parseTimestamp = $revision->getTimestamp(); - $content = $revision->getContent(); - $title = $article->getTitle(); - $options = $content->getContentHandler()->makeParserOptions( 'canonical' ); - $options->setTimestamp( $parseTimestamp ); - $output = $content->getParserOutput( $title, $revision->getId(), $options ); - self::$watcher->after = array_map( - 'strval', array_keys( $output->getCategories() ) - ); - wfDebugLog( 'CategoryWatch', 'Categories after page saved' ); - wfDebugLog( 'CategoryWatch', join( ', ', self::$watcher->after ) ); - - # Get list of added and removed cats - $add = array_diff( self::$watcher->after, self::$watcher->before ); - $sub = array_diff( self::$watcher->before, self::$watcher->after ); - - # Notify watchers of each cat about the addition or removal of this article - if ( count( $add ) > 0 || count( $sub ) > 0 ) { - $page = $article->getTitle(); - $pagename = $page->getPrefixedText(); - $pageurl = $page->getFullUrl(); - $page = "$pagename ($pageurl)"; - - if ( count( $add ) == 1 && count( $sub ) == 1 ) { - $add = array_shift( $add ); - $sub = array_shift( $sub ); - - $title = Title::newFromText( $add, NS_CATEGORY ); - $message = wfMessage( - 'categorywatch-catmovein', $page, - self::$watcher->friendlyCat( $add ), - self::$watcher->friendlyCat( $sub ) - )->text(); - self::$watcher->notifyWatchers( - $title, $user, $message, $summary, $medit, $pageurl - ); - - $title = Title::newFromText( $sub, NS_CATEGORY ); - $message = wfMessage( - 'categorywatch-catmoveout', $page, - self::$watcher->friendlyCat( $sub ), - self::$watcher->friendlyCat( $add ) - )->text(); - self::$watcher->notifyWatchers( - $title, $user, $message, $summary, $medit, $pageurl - ); - } else { - - foreach ( $add as $cat ) { - $title = Title::newFromText( $cat, NS_CATEGORY ); - $message = wfMessage( - 'categorywatch-catadd', $page, - self::$watcher->friendlyCat( $cat ) - )->text(); - self::$watcher->notifyWatchers( - $title, $user, $message, $summary, $medit, $pageurl - ); - } - - foreach ( $sub as $cat ) { - $title = Title::newFromText( $cat, NS_CATEGORY ); - $message = wfMessage( - 'categorywatch-catsub', $page, - self::$watcher->friendlyCat( $cat ) - )->text(); - self::$watcher->notifyWatchers( - $title, $user, $message, $summary, $medit, $pageurl - ); - } - } - } - - global $wgCategoryWatchNotifyParentWatchers; - if ( $wgCategoryWatchNotifyParentWatchers ) { - self::notifyParentWatchers(); - } - } - - /** - * Notify the watchers of parent categories - */ - protected static function notifyParentWatchers() { - self::$watcher->allparents = []; - self::$watcher->i = 0; - self::$watcher->findCategoryParents( self::$watcher->after ); - ## For each active parent category, send the mail - if ( self::$watcher->allparents ) { - $page = $article->getTitle(); - $pageurl = $page->getFullUrl(); - foreach ( self::$watcher->allparents as $cat ) { - $title = Title::newFromText( $cat, NS_CATEGORY ); - $message = wfMessage( - 'categorywatch-catchange', $page, - self::$watcher->friendlyCat( $cat ) - ); - self::$watcher->notifyWatchers( - $title, $user, $message, $summary, $medit, $pageurl - ); - } - } - } - - /** - * Recursively find all parents of the given categories - * - * @param array $catarray the categories - */ - protected function findCategoryParents( array $catarray ) { - $this->i++; - if ( $this->i == 200 ) { - return; - } - - if ( $catarray ) { - foreach ( $catarray as $catname ) { - self::$watcher->allparents[] = $catname; - $id = self::$watcher->getCategoryArticleId( $catname ); - if ( is_numeric( $id ) ) { - $parentCat = self::$watcher->getParentCategories( $id ); - if ( $parentCat ) { - self::$watcher->allparents[] = $parentCat; - self::$watcher->findCategoryParents( [ $parentCat ] ); - } - } - } - self::$watcher->allparents = array_unique( self::$watcher->allparents ); - } - } - - /** - * Return the parent categories - * @param int $id Category Article id - * @return parents - */ - protected function getParentCategories( $id ) { - $dbr = wfGetDB( DB_SLAVE ); - $cl = $dbr->tableName( 'categorylinks' ); - $res = $dbr->select( - $cl, 'cl_to', "cl_from = $id", __METHOD__, - [ 'ORDER BY' => 'cl_sortkey' ] - ); - $row = $dbr->fetchRow( $res ); - $dbr->freeResult( $res ); - if ( empty( $row[0] ) ) { - return false; - } - return $row[0]; - } - - /** - * Load page ID of one category - * - * @param string $catname name of category - * @return int - */ - protected 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]; - } - - /** - * Return "Category:Cat (URL)" from "Cat" - * @param string $cat name of category - * @return string - */ - protected function friendlyCat( $cat ) { - $cat = Title::newFromText( $cat, NS_CATEGORY ); - $catname = $cat->getPrefixedText(); - $caturl = $cat->getFullUrl(); - return "$catname ($caturl)"; - } - - /** - * Notify any watchers - * @param Title $title of article - * @param User $editor of article - * @param string $message for user - * @param string $summary editor gave - * @param bool $medit true if minor - * @param string $pageurl of page - */ - function notifyWatchers( $title, $editor, $message, $summary, $medit, $pageurl ) { - global $wgLang, $wgNoReplyAddress, $wgCategoryWatchNotifyEditor, - $wgEnotifRevealEditorAddress, $wgEnotifUseRealName, $wgPasswordSender, - $wgEnotifFromEditor, $wgPasswordSenderName; - - # Get list of users watching this category - $dbr = wfGetDB( DB_SLAVE ); - $conds = [ - 'wl_title' => $title->getDBkey(), 'wl_namespace' => $title->getNamespace() - ]; - if ( !$wgCategoryWatchNotifyEditor ) { - $conds[] = 'wl_user <> ' . intval( $editor->getId() ); - } - $res = $dbr->select( 'watchlist', [ 'wl_user' ], $conds, __METHOD__ ); - - # Wrap message with common body and send to each watcher - $page = $title->getPrefixedText(); - $adminAddress = new MailAddress( - $wgPasswordSender, - isset( $wgPasswordSenderName ) - ? $wgPasswordSenderName - : 'WikiAdmin' - ); - $editorAddress = new MailAddress( $editor ); - $summary = $summary - ? $summary - : ' - '; - $medit = $medit - ? wfMessage( 'minoredit' )->text() - : ''; - $row = $dbr->fetchRow( $res ); - while ( $row ) { - $watchingUser = User::newFromId( $row[0] ); - $timecorrection = $watchingUser->getOption( 'timecorrection' ); - $editdate = $wgLang->timeanddate( - wfTimestampNow(), true, false, $timecorrection - ); - - if ( - $watchingUser->getOption( 'enotifwatchlistpages' ) - && $watchingUser->isEmailConfirmed() - ) { - $to = new MailAddress( $watchingUser ); - $subject = wfMessage( 'categorywatch-emailsubject', $page )->text(); - $body = wfMessage( 'enotif_body' )->inContentLanguage()->text(); - - # 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. - if ( $wgCategoryWatchNoRealName ) { - $name = $watchingUser->getName(); - } - $name = $wgEnotifUseRealName - ? $watchingUser->getRealName() - : $watchingUser->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 = [ - '$WATCHINGUSERNAME' => $name, - '$NEWPAGE' => $message, - '$PAGETITLE' => $page, - '$PAGEEDITDATE' => $editdate, - '$CHANGEDORCREATED' => wfMessage( 'changed' ) - ->inContentLanguage()->text(), - '$PAGETITLE_URL' => $title->getFullUrl(), - '$PAGEEDITOR_WIKI' => $userPage->getFullUrl(), - '$PAGESUMMARY' => $summary, - '$PAGEMINOREDIT' => $medit, - '$OLDID' => '' - ]; - if ( $editor->isIP( $name ) ) { - $utext = wfMessage( - 'enotif_anon_editor', $name - )->inContentLanguage()->text(); - $subject = str_replace( '$PAGEEDITOR', $utext, $subject ); - $keys['$PAGEEDITOR'] = $utext; - $keys['$PAGEEDITOR_EMAIL'] = wfMmessage( - 'noemailtitle' - )->inContentLanguage()->text(); - } 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 ); - $options = []; - $options['replyTo'] = $replyto; - UserMailer::send( $to, $from, $subject, $body, $options ); - } - } - - $dbr->freeResult( $res ); - } -} diff --git a/assets/catwatch.svg b/assets/catwatch.svg new file mode 100644 index 0000000..704e616 --- /dev/null +++ b/assets/catwatch.svg @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="210mm" + height="297mm" + viewBox="0 0 210 297" + version="1.1" + id="svg8" + inkscape:version="0.92.1 r15371" + sodipodi:docname="drawing.svg"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35" + inkscape:cx="405.71429" + inkscape:cy="674.28571" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="1920" + inkscape:window-height="1043" + inkscape:window-x="3120" + inkscape:window-y="576" + inkscape:window-maximized="1" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="fill:#000000;stroke-width:0.35277778" + d="m 84.994481,214.31563 c -5.949945,-1.05364 -21.213966,-2.23295 -13.352206,-10.9714 3.695507,-12.89237 4.88332,-26.42383 5.638505,-39.76417 -19.025785,1.84272 -39.922515,-1.87712 -54.534241,-14.94703 -10.376185,-8.42312 -15.0451298,-23.62666 -8.969777,-35.94804 3.623461,-8.80684 10.448942,-15.705428 17.578613,-21.776377 3.345481,-10.464225 5.068239,-21.801097 8.460262,-31.982302 4.466092,6.611636 8.174656,13.714598 12.269483,20.565238 16.66839,-1.648195 33.562264,-3.625288 50.27083,-1.585865 8.27464,4.824707 13.98201,0.431648 16.53857,-7.829803 2.33085,-3.357656 6.60157,-15.536359 7.20814,-5.420148 1.69117,8.81785 3.42432,17.627565 5.14434,26.439813 8.65544,8.252934 16.47454,18.690314 16.23665,31.261414 0.15052,19.00213 -16.39479,33.26368 -33.43058,38.20593 -6.98098,2.18615 -14.27459,3.10793 -21.550892,3.59694 6.822478,11.6862 16.162262,22.46563 18.961012,36.01002 11.7332,-1.60806 23.47262,-9.37933 27.57163,-21.00115 4.57614,-10.28331 2.7418,-21.81526 3.56869,-32.62977 3.59259,-8.36013 9.75557,-18.09339 20.08437,-17.61026 11.31681,0.60592 21.72389,8.55681 26.96142,18.37719 4.27664,7.64106 1.15388,18.96169 -8.02363,21.03954 -4.43086,1.17288 -13.59254,3.94132 -15.73306,0.24894 6.2893,-4.1808 17.38926,-5.47314 18.52184,-14.45304 -1.09128,-7.7233 -8.60287,-12.46372 -14.47271,-16.64506 -7.5555,-5.27554 -17.4013,1.51599 -20.25435,8.90236 -2.71908,12.67921 2.14599,26.86221 -5.10981,38.59235 -6.58969,10.90246 -18.14975,19.19652 -30.72988,21.4619 -6.9547,1.43192 -11.31322,10.44678 -20.201235,8.39584 -2.892543,0.0701 -5.786826,-0.14442 -8.651984,-0.53306 z M 50.548173,136.37518 c 9.126923,-2.37248 15.298303,-10.49331 20.748169,-17.70152 -5.627883,-8.11307 -11.761537,-16.94128 -21.327889,-20.587224 10.698125,7.229604 11.612395,24.495364 3.278572,33.827614 -6.151929,7.34316 -17.514316,1.94845 -19.376662,-6.47971 -3.402429,-9.5564 -1.364495,-22.65477 8.336981,-27.712969 -10.159912,2.352789 -15.430016,12.183909 -21.347193,19.873609 5.109409,10.56059 15.906937,19.04843 27.919299,18.96924 l 1.768725,-0.18904 z m 71.578187,-1.25594 c 6.9859,-3.84485 12.36892,-9.99874 16.90292,-16.44614 -5.43052,-7.84042 -11.20344,-16.34394 -20.28566,-20.21139 8.99047,7.71777 10.23142,22.34175 3.60087,31.91231 -4.0751,6.81567 -14.54106,6.61987 -18.29916,-0.39976 -6.686348,-9.84327 -5.228428,-26.2426 5.89534,-32.266383 -10.180009,2.368153 -15.346942,12.210943 -21.318868,19.857373 5.056277,10.75606 16.025078,18.8559 28.021648,19.13088 1.93444,-0.0364 3.81243,-0.61346 5.48291,-1.57689 z" + id="path168" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/extension.json b/extension.json index 2d06c58..a1f819e 100644 --- a/extension.json +++ b/extension.json @@ -1,6 +1,6 @@ { "name": "CategoryWatch", - "version": "1.2.2, 2011-12-03", + "version": "2.0, 2017-09-19", "author": [ "[http://www.organicdesign.co.nz/User:Nad User:Nad]", "Sean Chen", @@ -11,20 +11,29 @@ "license-name": "GPL-2.0+", "type": "other", "AutoloadClasses": { - "CategoryWatch": "CategoryWatch.php" + "CategoryWatch\\CategoryWatch": "src/CategoryWatch.php", + "CategoryWatch\\EchoEventPresentationModel": "src/EchoEventPresentationModel.php", + "CategoryWatch\\Hook": "src/Hook.php" }, - "ExtensionFunctions": [ - "CategoryWatch::setupCategoryWatch" - ], "MessagesDirs": { "CategoryWatch": "i18n" }, + "DefaultUserOptions": { + "echo-subscriptions-email-categorywatch": true, + "echo-subscriptions-web-categorywatch": true + }, "Hooks": { - "PageContentSave": [ - "CategoryWatch::onPageContentSave" + "BeforeCreateEchoEvent": [ + "CategoryWatch\\Hook::onBeforeCreateEchoEvent" ], - "PageContentSaveComplete": [ - "CategoryWatch::onPageContentSaveComplete" + "EchoGetBundleRules": [ + "CategoryWatch\\Hook::onEchoGetBundleRules" + ], + "CategoryAfterPageAdded": [ + "CategoryWatch\\Hook::onCategoryAfterPageAdded" + ], + "CategoryAfterPageRemoved": [ + "CategoryWatch\\Hook::onCategoryAfterPageRemoved" ] }, "config": { diff --git a/i18n/en.json b/i18n/en.json index 27ed439..0fe568f 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -10,5 +10,17 @@ "categorywatch-catmoveout": "$1 has moved out of $2 into $3", "categorywatch-catadd": "$1 has been added to $2", "categorywatch-catsub": "$1 has been removed from $2", - "categorywatch-autocat": "Automatically watched by $1" + "categorywatch-autocat": "Automatically watched by $1", + "categorywatch-notification-link": "Category: [[:$1]]", + "categorywatch-notification-categorywatch-add-header": "[[:$1|$2]] added to [[:$3|$4]]", + "categorywatch-notification-categorywatch-add-summary": "[[:$3|$4]] added to [[:$5|$6]]", + "categorywatch-notification-categorywatch-add-body": "[[User:$1|$1]] added [[:$3|$3]] to [[:$4|$4]]", + "categorywatch-notification-categorywatch-remove-header": "[[:$1|$2]] removed from [[:$3|$4]]", + "categorywatch-notification-categorywatch-remove-summary": "[[:$3|$4]]removed from [[:$5|$6]]", + "categorywatch-notification-categorywatch-remove-body": "[[User:$1|$1]] removed [[:$3|$3]] from [[:$4|$4]]", + "categorywatch-notification-bundle": "$1 changes in categorization on {{SITENAME}}", + "categorywatch-add-title": "Title added to watched category", + "categorywatch-remove-title": "Title removed from watched category", + "echo-category-title-categorywatch": "Category watch", + "echo-pref-tooltip-categorywatch": "Notify me when someone categorizes a page into or out of a category that I'm watching." } diff --git a/i18n/qqq.json b/i18n/qqq.json index abea5f9..91fa34c 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -14,5 +14,12 @@ "categorywatch-catmoveout": "Substituted as $5 in {{msg-mw|categorywatch-emailbody}}.\n* $1 is a page name\n* $2 is the source category name\n* $3 is the target category name", "categorywatch-catadd": "Substituted as $5 in {{msg-mw|categorywatch-emailbody}}.\n* $1 is a page name\n* $2 is a category name", "categorywatch-catsub": "Substituted as $5 in {{msg-mw|categorywatch-emailbody}}.\n* $1 is a page name\n* $2 is a category name", - "categorywatch-autocat": "If the \"automatically watching\" feature is enabled, this message is used as a page title in the watchlist.\n* $1 is a username (or a realname)" + "categorywatch-autocat": "If the \"automatically watching\" feature is enabled, this message is used as a page title in the watchlist.\n* $1 is a username (or a realname)", + "categorywatch-notification-add": "[[User:$1|$1]] $3 [[:$4|$4]]", + "categorywatch-notification-remove": "[[User:$1|$1]] $3 [[:$4|$4]]", + "categorywatch-notification-bundle": "$1 changes in categorization on {{SITENAME}}", + "categorywatch-add-action": "added [[:$1|$1]] to", + "categorywatch-remove-action": "removed [[:$1|$1]] from", + + "echo-category-title-categorywatch": "Membership changes in categories watched" } diff --git a/src/CategoryWatch.php b/src/CategoryWatch.php new file mode 100644 index 0000000..cdcdc7f --- /dev/null +++ b/src/CategoryWatch.php @@ -0,0 +1,403 @@ +<?php + +/** + * CategoryWatch extension + * - Extends watchlist functionality to include notification about membership + * changes of watched categories + * + * Copyright (C) 2008 Aran Dunkley + * Copyright (C) 2017 Sean Chen + * Copyright (C) 2017 Mark A. Hershberger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * See https://www.mediawiki.org/Extension:CategoryWatch + * for installation and usage details + * See http://www.organicdesign.co.nz/Extension_talk:CategoryWatch + * for development notes and disucssion + * + * @file + * @ingroup 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 + */ + +namespace CategoryWatch; + +class CategoryWatch { + public $before = []; + public $after = []; + + protected $count = 0; + protected $allParents = []; + + protected $wikiPage; + protected $editor; + protected $content; + protected $summary; + protected $minorEdit; + protected $flags; + + /** + * Construction + * @param WikiPage $wikiPage the page + * @param User $user who is modifying + * @param Content $content the new article content + * @param string $summary the article summary (comment) + * @param bool $isMinor minor flag + * @param int $flags see WikiPage::doEditContent documentation for flags' definition + */ + public function __construct( + WikiPage $wikiPage, User $user, Content $content, $summary, $isMinor, $flags + ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $this->wikiPage; + $this->editor; + $this->content; + $this->summary; + $this->minorEdit; + $this->flags; + + $this->before = $this->wikiPage->getTitle()->getParentCategories(); + $this->doAutoCat(); + } + + /** + * Notify all category watchers + * + * @param Revision $revision that was created + * @param int $baseRevId base revision + */ + public function notifyCategoryWatchers( + Revision $revision, $baseRevId + ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + # Get cats after update + $this->after = $this->wikiPage->getTitle()->getParentCategories(); + + # Get list of added and removed cats + $add = array_diff( $this->after, $this->before ); + $sub = array_diff( $this->before, $this->after ); + wfDebugLog( 'CategoryWatch', 'Categories after page saved' ); + wfDebugLog( 'CategoryWatch', join( ', ', $this->after ) ); + wfDebugLog( 'CategoryWatch', 'Categories added' ); + wfDebugLog( 'CategoryWatch', join( ', ', $add ) ); + wfDebugLog( 'CategoryWatch', 'Categories removed' ); + wfDebugLog( 'CategoryWatch', join( ', ', $sub ) ); + + # Notify watchers of each cat about the addition or removal of this article + if ( count( $add ) > 0 || count( $sub ) > 0 ) { + $page = $article->getTitle(); + $pagename = $page->getPrefixedText(); + $pageurl = $page->getFullUrl(); + $page = "$pagename ($pageurl)"; + + if ( count( $add ) == 1 && count( $sub ) == 1 ) { + $this->notifyMove( $sub[0], $add[0] ); + } else { + $this->notifyAdd( $add ); + + foreach ( $sub as $cat ) { + $title = Title::newFromText( $cat, NS_CATEGORY ); + $message = wfMessage( + 'categorywatch-catsub', $page, + $this->friendlyCat( $cat ) + )->text(); + $this->notifyWatchers( + $title, $user, $message, $summary, $medit, $pageurl + ); + } + } + } + + if ( $this->shouldNotifyParentWatchers() ) { + $this->notifyParentWatchers(); + } + } + + /** + * Should watchers of parent categories be notified? + * @return bool + */ + protected function shouldNotifyParentWatchers() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + global $wgCategoryWatchNotifyParentWatchers; + return $wgCategoryWatchNotifyParentWatchers; + } + + /** + * Should the editor be notified of his own edits? + * @return bool + */ + protected function shouldNotifyEditor() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + global $wgCategoryWatchNotifyEditor; + return $wgCategoryWatchNotifyEditor; + } + + /** + * Should CategoryWatch use the user's real name in email? + * @return bool + */ + protected function useRealName() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + global $wgCategoryWatchNoRealName; + return !$wgCategoryWatchNoRealName; + } + + /** + * Return "Category:Cat (URL)" from "Cat" + * @param string $cat name of category + * @return string + */ + protected function friendlyCat( $cat ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $cat = Title::newFromText( $cat, NS_CATEGORY ); + $catname = $cat->getPrefixedText(); + $caturl = $cat->getFullUrl(); + return "$catname ($caturl)"; + } + + /** + * Notify any watchers + * @param Title $title of article + * @param User $editor of article + * @param string $message for user + * @param string $summary editor gave + * @param bool $medit true if minor + * @param string $pageurl of page + */ + protected function notifyWatchers( + $title, $editor, $message, $summary, $medit, $pageurl + ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + global $wgLang, $wgNoReplyAddress, + $wgEnotifRevealEditorAddress, $wgEnotifUseRealName, $wgPasswordSender, + $wgEnotifFromEditor, $wgPasswordSenderName; + + # Get list of users watching this category + $dbr = wfGetDB( DB_SLAVE ); + $conds = [ + 'wl_title' => $title->getDBkey(), 'wl_namespace' => $title->getNamespace() + ]; + if ( !$this->shouldNotifyEditor() ) { + $conds[] = 'wl_user <> ' . intval( $editor->getId() ); + } + $res = $dbr->select( 'watchlist', [ 'wl_user' ], $conds, __METHOD__ ); + + # Wrap message with common body and send to each watcher + $page = $title->getPrefixedText(); + $adminAddress = new MailAddress( + $wgPasswordSender, + isset( $wgPasswordSenderName ) + ? $wgPasswordSenderName + : 'WikiAdmin' + ); + $editorAddress = new MailAddress( $editor ); + $summary = $summary + ? $summary + : ' - '; + $medit = $medit + ? wfMessage( 'minoredit' )->text() + : ''; + $row = $dbr->fetchRow( $res ); + while ( $row ) { + $watchingUser = User::newFromId( $row[0] ); + $timecorrection = $watchingUser->getOption( 'timecorrection' ); + $editdate = $wgLang->timeanddate( + wfTimestampNow(), true, false, $timecorrection + ); + + if ( + $watchingUser->getOption( 'enotifwatchlistpages' ) + && $watchingUser->isEmailConfirmed() + ) { + $to = new MailAddress( $watchingUser ); + $subject = wfMessage( 'categorywatch-emailsubject', $page )->text(); + $body = wfMessage( 'enotif_body' )->inContentLanguage()->text(); + + # 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 + ? $watchingUser->getRealName() + : $watchingUser->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 = [ + '$WATCHINGUSERNAME' => $name, + '$NEWPAGE' => $message, + '$PAGETITLE' => $page, + '$PAGEEDITDATE' => $editdate, + '$CHANGEDORCREATED' => wfMessage( 'changed' ) + ->inContentLanguage()->text(), + '$PAGETITLE_URL' => $title->getFullUrl(), + '$PAGEEDITOR_WIKI' => $userPage->getFullUrl(), + '$PAGESUMMARY' => $summary, + '$PAGEMINOREDIT' => $medit, + '$OLDID' => '' + ]; + if ( $editor->isIP( $name ) ) { + $utext = wfMessage( + 'enotif_anon_editor', $name + )->inContentLanguage()->text(); + $subject = str_replace( '$PAGEEDITOR', $utext, $subject ); + $keys['$PAGEEDITOR'] = $utext; + $keys['$PAGEEDITOR_EMAIL'] = wfMmessage( + 'noemailtitle' + )->inContentLanguage()->text(); + } 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 ); + $options = []; + $options['replyTo'] = $replyto; + UserMailer::send( $to, $from, $subject, $body, $options ); + } + } + + $dbr->freeResult( $res ); + } + + /** + * Notify the watchers of parent categories + */ + protected function notifyParentWatchers() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $this->allparents = $this->wikiPage->getTitle()->getParentCategoryTree(); + $page = $this->wikiPage->getTitle(); + $pageUrl = $page->getFullUrl(); + foreach ( (array)$this->allparents as $cat ) { + $title = Title::newFromText( $cat, NS_CATEGORY ); + $message = wfMessage( + 'categorywatch-catchange', $page, + $this->friendlyCat( $cat ) + ); + $this->notifyWatchers( + $title, $user, $message, $summary, $medit, $pageurl + ); + } + } + + /** + * Handle autocat option + */ + protected function doAutoCat() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + global $wgCategoryWatchUseAutoCat; + if ( $wgCategoryWatchUseAutoCat ) { + $dbr = wfGetDB( DB_SLAVE ); + + # Find all users not watching the autocat + $like = '%' . str_replace( + ' ', '_', trim( wfMessage( 'categorywatch-autocat', '' )->text() ) + ) . '%'; + $res = $dbr->select( [ 'user', 'watchlist' ], 'user_id', + 'wl_user IS NULL', __METHOD__, [], + [ 'watchlist' => [ 'LEFT JOIN', + [ + 'user_id=wl_user', + 'wl_tile', $dbr->buildLike( $like ) + ] ] ] ); + + # Insert an entry into watchlist for each + $row = $dbr->fetchRow( $res ); + while ( $row ) { + $user = User::newFromId( $row[0] ); + $name = $user->getName(); + $wl_title = str_replace( + ' ', '_', wfMessage( 'categorywatch-autocat', $name )->text() + ); + $dbr->insert( + 'watchlist', + [ + 'wl_user' => $row[0], 'wl_namespace' => NS_CATEGORY, + 'wl_title' => $wl_title + ] + ); + $row = $dbr->fetchRow( $res ); + } + $dbr->freeResult( $res ); + } + } + + /** + * Send a notification that the page's categorization has moved. + * @param string $from Category moving from + * @param string $to Category moving to + */ + protected function notifyMove( $from, $to ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $title = Title::newFromText( $to, NS_CATEGORY ); + $message = wfMessage( + 'categorywatch-catmovein', $page, + $this->friendlyCat( $to ), + $this->friendlyCat( $from ) + )->text(); + $this->notifyWatchers( + $title, $user, $message, $summary, $medit, $pageurl + ); + + $title = Title::newFromText( $from, NS_CATEGORY ); + $message = wfMessage( + 'categorywatch-catmoveout', $page, + $this->friendlyCat( $from ), + $this->friendlyCat( $to ) + )->text(); + $this->notifyWatchers( + $title, $user, $message, $summary, $medit, $pageurl + ); + } + + /** + * Send a notification that a page has been added to the category + * @param array $add Category being added + */ + protected function notifyAdd( $add ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + foreach ( $add as $cat ) { + $title = Title::newFromText( $cat, NS_CATEGORY ); + $message = wfMessage( + 'categorywatch-catadd', $page, + $this->friendlyCat( $cat ) + )->text(); + $this->notifyWatchers( + $title, $user, $message, $summary, $medit, $pageurl + ); + } + } +} diff --git a/src/EchoEventPresentationModel.php b/src/EchoEventPresentationModel.php new file mode 100644 index 0000000..049e928 --- /dev/null +++ b/src/EchoEventPresentationModel.php @@ -0,0 +1,180 @@ +<?php + +/** + * Category watch events + * + * Copyright (C) 2017 Mark A. Hershberger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace CategoryWatch; + +use RawMessage; +use Title; +use WikiPage; + +class EchoEventPresentationModel extends \EchoEventPresentationModel { + /** + * Tell the caller if this event can be rendered. + * + * @return bool + */ + public function canRender() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + return (bool)$this->event->getTitle(); + } + + /** + * Which of the registered icons to use. + * + * @return string + */ + public function getIconType() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + return 'categorywatch'; + } + + /** + * The header of this event's display + * + * @return Message + */ + public function getHeaderMessage() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + if ( $this->isBundled() ) { + $msg = $this->msg( 'categorywatch-notification-bundle' ); + $msg->params( $this->getBundleCount() ); + $msg->params( $this->getTruncatedTitleText( $this->event->getTitle(), true ) ); + $msg->params( $this->getViewingUserForGender() ); + } else { + $msg = $this->msg( 'categorywatch-notification-' . $this->event->getType() . '-header' ); + $msg->params( $this->getPageTitle() ); + $msg->params( $this->getTruncatedTitleText( $this->getPageTitle(), true ) ); + $msg->params( $this->event->getTitle() ); + $msg->params( $this->getTruncatedTitleText( $this->event->getTitle(), true ) ); + } + return $msg; + } + + /** + * Shorter display + * + * @return Message + */ + public function getCompactHeaderMessage() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $msg = parent::getCompactHeaderMessage(); + $msg->params( $this->getViewingUserForGender() ); + return $msg; + } + + /** + * Summary of edit + * + * @return string + */ + public function getRevisionEditSummary() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $msg = $this->getMessageWithAgent( 'categorywatch-notification-' . $this->event->getType() . '-summary' ); + $msg->params( $this->getPageTitle() ); + $msg->params( $this->getTruncatedTitleText( $this->getPageTitle(), true ) ); + $msg->params( $this->event->getTitle() ); + $msg->params( $this->getTruncatedTitleText( $this->event->getTitle(), true ) ); + return $msg; + } + + /** + * Body to display + * + * @return Message + */ + public function getBodyMessage() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $msg = $this->getMessageWithAgent( 'categorywatch-notification-' . + $this->event->getType() . '-body' ); + $msg->params( $this->getPageTitle() ); + $msg->params( $this->event->getTitle() ); + return $msg; + } + + /** + * Title of page + * + * @return Title|string + */ + public function getPageTitle() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $page = WikiPage::newFromId( $this->event->getExtraParam( "pageid" ) ); + return $page ? $page->getTitle() : new Title(); + } + + /** + * Provide the main link + * + * @return string + */ + public function getPrimaryLink() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $title = $this->event->getTitle(); + $msg = $this->msg( 'categorywatch-notification-link' ); + $msg->params( $title ); + return [ + 'url' => $title->getFullURL(), + 'label' => $title->getPrefixedText() + ]; + } + + /** + * Aux links + * + * @return array + */ + public function getSecondaryLinks() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + if ( $this->isBundled() ) { + // For the bundle, we don't need secondary actions + return []; + } else { + return [ + $this->getAgentLink(), + [ + 'url' => $this->getPageTitle()->getFullURL(), + 'label' => $this->getPageTitle()->getPrefixedText() + ] + ]; + } + } + + /** + * override parent + * @return array + * @throws TimestampException + */ + public function jsonSerialize() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $body = $this->getBodyMessage(); + + return [ + 'header' => $this->getHeaderMessage()->parse(), + 'compactHeader' => $this->getCompactHeaderMessage()->parse(), + 'body' => $body ? $body->toString() : '', + 'icon' => $this->getIconType(), + 'links' => [ + 'primary' => $this->getPrimaryLinkWithMarkAsRead() ?: [], + 'secondary' => array_values( array_filter( $this->getSecondaryLinks() ) ), + ], + ]; + } +} diff --git a/src/Hook.php b/src/Hook.php new file mode 100644 index 0000000..033f894 --- /dev/null +++ b/src/Hook.php @@ -0,0 +1,220 @@ +<?php +/** + * Hooks for CategoryWatch extension + * + * Copyright (C) 2017 Mark A. Hershberger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +namespace CategoryWatch; + +use Category; +use Content; +use EchoDiscussionParser; +use EchoEvent; +use MediaWiki\MediaWikiServices; +use WatchedItemStore; +use Status; +use Title; +use User; +use WikiPage; + +class Hook { + // Instance + protected static $watcher; + + /** + * Explain bundling + * + * @param Event $event to bundle + * @param string &$bundleString to use + */ + public static function onEchoGetBundleRules( EchoEvent $event, &$bundleString ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + switch ( $event->getType() ) { + case 'categorywatch-add': + case 'categorywatch-remove': + $bundleString = 'categorywatch'; + break; + } + } + + /** + * Define the CategoryWatch notifications + * + * @param array &$notifications assoc array of notification types + * @param array &$notificationCategories assoc array describing + * categories + * @param array &$icons assoc array of icons we define + */ + public static function onBeforeCreateEchoEvent( + array &$notifications, array &$notificationCategories, array &$icons + ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + $icons['categorywatch']['path'] = 'CategoryWatch/assets/catwatch.svg'; + + $notifications['categorywatch-add'] = [ + 'bundle' => [ + 'web' => true, + 'email' => true, + 'expandable' => true, + ], + 'title-message' => 'categorywatch-add-title', + 'category' => 'categorywatch', + 'group' => 'neutral', + 'user-locators' => [ 'CategoryWatch\\Hook::userLocater' ], + 'user-filters' => [ 'CategoryWatch\\Hook::userFilter' ], + 'presentation-model' => 'CategoryWatch\\EchoEventPresentationModel', + ]; + + $notifications['categorywatch-remove'] = [ + 'bundle' => [ + 'web' => true, + 'email' => true, + 'expandable' => true, + ], + 'title-message' => 'categorywatch-remove-title', + 'category' => 'categorywatch', + 'group' => 'neutral', + 'user-locators' => [ 'CategoryWatch\\Hook::userLocater' ], + 'user-filters' => [ 'CategoryWatch\\Hook::userFilter' ], + 'presentation-model' => 'CategoryWatch\\EchoEventPresentationModel', + ]; + + $notificationCategories['categorywatch'] = [ + 'priority' => 2, + 'tooltip' => 'echo-pref-tooltip-categorywatch' + ]; + } + + /** + * Internal compatibility function + * @return WatchedItemStore + */ + protected static function getWatchedItemStore() { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + if ( method_exists( 'WatchedItemStore', 'getDefaultInstance' ) ) { + return WatchedItemStore::getDefaultInstance(); + } else { + return MediaWikiServices::getInstance()->getWatchedItemStore(); + } + } + + /** + * Hook for page being added to a category. + * + * @param Category $cat that page is being add to + * @param WikiPage $page that is being added + */ + public static function onCategoryAfterPageAdded( + Category $cat, WikiPage $page + ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + # Is anyone watching the category? + if ( + self::getWatchedItemStore() + ->countWatchers( $cat->getTitle() ) > 0 + ) { + # Send them a notification! + $user = User::newFromId( $page->getUser() ); + + EchoEvent::create( [ + 'type' => 'categorywatch-add', + 'title' => $cat->getTitle(), + 'agent' => $user, + 'extra' => [ + 'pageid' => $page->getId(), + 'revid' => $page->getRevision()->getId(), + ], + ] ); + } + } + + /** + * Hook for page being taken out of a category. + * + * @param Category $cat that page is being removed from + * @param WikiPage $page that is being removed + * @param int $id that this happened in. (not given pre 1.27ish) + */ + public static function onCategoryAfterPageRemoved( + Category $cat, WikiPage $page, $id = 0 + ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + # Is anyone watching the category? + if ( + self::getWatchedItemStore() + ->countWatchers( $cat->getTitle() ) > 0 + ) { + # Send them a notification! + $user = User::newFromId( $page->getUser() ); + + EchoEvent::create( [ + 'type' => 'categorywatch-remove', + 'title' => $cat->getTitle(), + 'agent' => $user, + 'extra' => [ + 'pageid' => $page->getId(), + 'revid' => $page->getRevision()->getId(), + ], + ] ); + } + } + + /** + * Find the watchers for a title + * + * @param Title $target to check + * + * @return array + */ + protected static function getWatchers( Title $target ) { + $dbr = wfGetDB( DB_SLAVE ); + $return = $dbr->selectFieldValues( + 'watchlist', + 'wl_user', + [ + 'wl_namespace' => $target->getNamespace(), + 'wl_title' => $target->getDBkey(), + ], + __METHOD__ + ); + + return array_map( function ( $id ) { + return User::newFromID( $id ); + }, $return ); + } + + /** + * Get users that should be notified for this event. + * + * @param EchoEvent $event to be looked at + * @return array + */ + public static function userLocater( EchoEvent $event ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + return self::getWatchers( $event->getTitle() ); + } + + /** + * Filter out the person performing the action + * + * @param EchoEvent $event to be looked at + * @return array + */ + public static function userFilter( EchoEvent $event ) { + wfDebugLog( 'CategoryWatch', __METHOD__ ); + return [ $event->getAgent() ]; + } +} -- To view, visit https://gerrit.wikimedia.org/r/386007 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Id0df74ccef1f9c6ea51c22396977f5424cbe19ae Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/CategoryWatch Gerrit-Branch: master Gerrit-Owner: MarkAHershberger <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
