Introduce CategoryMembershipChange
authoraddshore <addshorewiki@gmail.com>
Thu, 17 Sep 2015 09:51:25 +0000 (10:51 +0100)
committeraddshore <addshorewiki@gmail.com>
Tue, 29 Sep 2015 22:59:34 +0000 (23:59 +0100)
This is split from:
I03516bb34144d95e5f25c46ae98ab70ce699b31b

Change-Id: I27539d25ef3e81cf991657dffc0a62b9719d21d8

autoload.php
includes/DefaultSettings.php
includes/Defines.php
includes/changes/CategoryMembershipChange.php [new file with mode: 0644]
includes/changes/RecentChange.php
languages/i18n/en.json
languages/i18n/qqq.json
tests/phpunit/includes/changes/CategoryMembershipChangeTest.php [new file with mode: 0644]
tests/phpunit/includes/changes/RecentChangeTest.php

index f1b0a6c..29dccd4 100644 (file)
@@ -194,6 +194,7 @@ $wgAutoloadLocalClasses = array(
        'CapsCleanup' => __DIR__ . '/maintenance/cleanupCaps.php',
        'Category' => __DIR__ . '/includes/Category.php',
        'CategoryFinder' => __DIR__ . '/includes/CategoryFinder.php',
+       'CategoryMembershipChange' => __DIR__ . '/includes/changes/CategoryMembershipChange.php',
        'CategoryPage' => __DIR__ . '/includes/page/CategoryPage.php',
        'CategoryPager' => __DIR__ . '/includes/specials/SpecialCategories.php',
        'CategoryViewer' => __DIR__ . '/includes/CategoryViewer.php',
index 5fd8574..f3e2639 100644 (file)
@@ -4462,6 +4462,7 @@ $wgReservedUsernames = array(
        'msg:usermessage-editor', // Default user for leaving user messages
        'msg:proxyblocker', // For $wgProxyList and Special:Blockme (removed in 1.22)
        'msg:spambot_username', // Used by cleanupSpam.php
+       'msg:autochange-username', // Used by anon category RC entries (parser functions, Lua & purges)
 );
 
 /**
index 1c06ad0..b8a0ec0 100644 (file)
@@ -170,6 +170,7 @@ define( 'RC_EDIT', 0 );
 define( 'RC_NEW', 1 );
 define( 'RC_LOG', 3 );
 define( 'RC_EXTERNAL', 5 );
+define( 'RC_CATEGORIZE', 6 );
 /**@}*/
 
 /**@{
diff --git a/includes/changes/CategoryMembershipChange.php b/includes/changes/CategoryMembershipChange.php
new file mode 100644 (file)
index 0000000..2533a5b
--- /dev/null
@@ -0,0 +1,273 @@
+<?php
+/**
+ * Helper class for category membership changes
+ *
+ * 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.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Kai Nissen
+ * @author Adam Shorland
+ * @since 1.26
+ */
+
+use Wikimedia\Assert\Assert;
+
+class CategoryMembershipChange {
+
+       const CATEGORY_ADDITION = 1;
+       const CATEGORY_REMOVAL = -1;
+
+       /**
+        * @var string Current timestamp, set during CategoryMembershipChange::__construct()
+        */
+       private $timestamp;
+
+       /**
+        * @var Title Title instance of the categorized page
+        */
+       private $pageTitle;
+
+       /**
+        * @var Revision|null Latest Revision instance of the categorized page
+        */
+       private $revision;
+
+       /**
+        * @var int
+        * Number of pages this WikiPage is embedded by; set by CategoryMembershipChange::setRecursive()
+        */
+       private $numTemplateLinks = 0;
+
+       /**
+        * @var callable|null
+        */
+       private $newForCategorizationCallback = null;
+
+       /**
+        * @param Title $pageTitle Title instance of the categorized page
+        * @param Revision $revision Latest Revision instance of the categorized page
+        *
+        * @throws MWException
+        */
+       public function __construct( Title $pageTitle, Revision $revision = null ) {
+               $this->pageTitle = $pageTitle;
+               $this->timestamp = wfTimestampNow();
+               $this->revision = $revision;
+               $this->newForCategorizationCallback = array( 'RecentChange', 'newForCategorization' );
+       }
+
+       /**
+        * Overrides the default new for categorization callback
+        * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
+        *
+        * @param callable $callback
+        * @see RecentChange::newForCategorization for callback signiture
+        *
+        * @throws MWException
+        */
+       public function overrideNewForCategorizationCallback( $callback ) {
+               if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+                       throw new MWException( 'Cannot override newForCategorization callback in operation.' );
+               }
+               Assert::parameterType( 'callable', $callback, '$callback' );
+               $this->newForCategorizationCallback = $callback;
+       }
+
+       /**
+        * Determines the number of template links for recursive link updates
+        */
+       public function checkTemplateLinks() {
+               $this->numTemplateLinks = $this->pageTitle->getBacklinkCache()->getNumLinks( 'templatelinks' );
+       }
+
+       /**
+        * Create a recentchanges entry for category additions
+        *
+        * @param Title $categoryTitle
+        */
+       public function triggerCategoryAddedNotification( Title $categoryTitle ) {
+               $this->createRecentChangesEntry( $categoryTitle, self::CATEGORY_ADDITION );
+       }
+
+       /**
+        * Create a recentchanges entry for category removals
+        *
+        * @param Title $categoryTitle
+        */
+       public function triggerCategoryRemovedNotification( Title $categoryTitle ) {
+               $this->createRecentChangesEntry( $categoryTitle, self::CATEGORY_REMOVAL );
+       }
+
+       /**
+        * Create a recentchanges entry using RecentChange::notifyCategorization()
+        *
+        * @param Title $categoryTitle
+        * @param int $type
+        */
+       private function createRecentChangesEntry( Title $categoryTitle, $type ) {
+               $this->notifyCategorization(
+                       $this->timestamp,
+                       $categoryTitle,
+                       $this->getUser(),
+                       $this->getChangeMessageText( $type, array(
+                               'prefixedText' => $this->pageTitle->getPrefixedText(),
+                               'numTemplateLinks' => $this->numTemplateLinks
+                       ) ),
+                       $this->pageTitle,
+                       $this->getPreviousRevisionTimestamp(),
+                       $this->revision
+               );
+       }
+
+       /**
+        * @param string $timestamp Timestamp of the recent change to occur in TS_MW format
+        * @param Title $categoryTitle Title of the category a page is being added to or removed from
+        * @param User $user User object of the user that made the change
+        * @param string $comment Change summary
+        * @param Title $pageTitle Title of the page that is being added or removed
+        * @param string $lastTimestamp Parent revision timestamp of this change in TS_MW format
+        * @param Revision|null $revision
+        *
+        * @throws MWException
+        */
+       private function notifyCategorization(
+               $timestamp,
+               Title $categoryTitle,
+               User $user = null,
+               $comment,
+               Title $pageTitle,
+               $lastTimestamp,
+               $revision
+       ) {
+               $deleted = $revision ? $revision->getVisibility() & Revision::SUPPRESSED_USER : 0;
+               $newRevId = $revision ? $revision->getId() : 0;
+
+               /**
+                * T109700 - Default bot flag to true when there is no corresponding RC entry
+                * This means all changes caused by parser functions & Lua on reparse are marked as bot
+                * Also in the case no RC entry could be found due to slave lag
+                */
+               $bot = 1;
+               $lastRevId = 0;
+               $ip = '';
+
+               # If no revision is given, the change was probably triggered by parser functions
+               if ( $revision !== null ) {
+                       // TODO if no RC try again from the master DB?
+                       $correspondingRc = $this->revision->getRecentChange();
+                       if ( $correspondingRc !== null ) {
+                               $bot = $correspondingRc->getAttribute( 'rc_bot' ) ?: 0;
+                               $ip = $correspondingRc->getAttribute( 'rc_ip' ) ?: '';
+                               $lastRevId = $correspondingRc->getAttribute( 'rc_last_oldid' ) ?: 0;
+                       }
+               }
+
+               $rc = call_user_func_array(
+                       $this->newForCategorizationCallback,
+                       array(
+                               $timestamp,
+                               $categoryTitle,
+                               $user,
+                               $comment,
+                               $pageTitle,
+                               $lastRevId,
+                               $newRevId,
+                               $lastTimestamp,
+                               $bot,
+                               $ip,
+                               $deleted
+                       )
+               );
+               $rc->save();
+       }
+
+       /**
+        * Get the user associated with this change.
+        *
+        * If there is no revision associated with the change and thus no editing user
+        * fallback to a default.
+        *
+        * False will be returned if the user name specified in the
+        * 'autochange-username' message is invalid.
+        *
+        * @return User|bool
+        */
+       private function getUser() {
+               if ( $this->revision ) {
+                       $userId = $this->revision->getUser( Revision::RAW );
+                       if ( $userId === 0 ) {
+                               return User::newFromName( $this->revision->getUserText( Revision::RAW ), false );
+                       } else {
+                               return User::newFromId( $userId );
+                       }
+               }
+
+               $username = wfMessage( 'autochange-username' )->inContentLanguage()->text();
+               $user = User::newFromName( $username );
+               # User::newFromName() can return false on a badly configured wiki.
+               if ( $user && !$user->isLoggedIn() ) {
+                       $user->addToDatabase();
+               }
+
+               return $user;
+       }
+
+       /**
+        * Returns the change message according to the type of category membership change
+        *
+        * The message keys created in this method may be one of:
+        * - recentchanges-page-added-to-category
+        * - recentchanges-page-added-to-category-bundled
+        * - recentchanges-page-removed-from-category
+        * - recentchanges-page-removed-from-category-bundled
+        *
+        * @param int $type may be CategoryMembershipChange::CATEGORY_ADDITION
+        * or CategoryMembershipChange::CATEGORY_REMOVAL
+        * @param array $params
+        * - prefixedUrl: result of Title::->getPrefixedURL()
+        *
+        * @return string
+        */
+       private function getChangeMessageText( $type, array $params ) {
+               $array = array(
+                       self::CATEGORY_ADDITION => 'recentchanges-page-added-to-category',
+                       self::CATEGORY_REMOVAL => 'recentchanges-page-removed-from-category',
+               );
+
+               $msgKey = $array[$type];
+
+               if ( intval( $params['numTemplateLinks'] ) > 0 ) {
+                       $msgKey .= '-bundled';
+               }
+
+               return wfMessage( $msgKey, $params )->inContentLanguage()->text();
+       }
+
+       /**
+        * Returns the timestamp of the page's previous revision or null if the latest revision
+        * does not refer to a parent revision
+        *
+        * @return null|string
+        */
+       private function getPreviousRevisionTimestamp() {
+               $previousRev = Revision::newFromId(
+                               $this->pageTitle->getPreviousRevisionID( $this->pageTitle->getLatestRevID() )
+                       );
+
+               return $previousRev ? $previousRev->getTimestamp() : null;
+       }
+
+}
index 5c884a5..7cf2138 100644 (file)
@@ -67,6 +67,7 @@ class RecentChange {
        const SRC_NEW = 'mw.new';
        const SRC_LOG = 'mw.log';
        const SRC_EXTERNAL = 'mw.external'; // obsolete
+       const SRC_CATEGORIZE = 'mw.categorize';
 
        public $mAttribs = array();
        public $mExtra = array();
@@ -97,6 +98,7 @@ class RecentChange {
                'new' => RC_NEW,
                'log' => RC_LOG,
                'external' => RC_EXTERNAL,
+               'categorize' => RC_CATEGORIZE,
        );
 
        # Factory methods
@@ -749,6 +751,80 @@ class RecentChange {
                return $rc;
        }
 
+       /**
+        * Constructs a RecentChange object for the given categorization
+        * This does not call save() on the object and thus does not write to the db
+        *
+        * @since 1.26
+        *
+        * @param string $timestamp Timestamp of the recent change to occur
+        * @param Title $categoryTitle Title of the category a page is being added to or removed from
+        * @param User $user User object of the user that made the change
+        * @param string $comment Change summary
+        * @param Title $pageTitle Title of the page that is being added or removed
+        * @param int $oldRevId Parent revision ID of this change
+        * @param int $newRevId Revision ID of this change
+        * @param string $lastTimestamp Parent revision timestamp of this change
+        * @param bool $bot true, if the change was made by a bot
+        * @param string $ip IP address of the user, if the change was made anonymously
+        * @param int $deleted Indicates whether the change has been deleted
+        *
+        * @return RecentChange
+        */
+       public static function newForCategorization(
+               $timestamp,
+               Title $categoryTitle,
+               User $user = null,
+               $comment,
+               Title $pageTitle,
+               $oldRevId,
+               $newRevId,
+               $lastTimestamp,
+               $bot,
+               $ip = '',
+               $deleted = 0
+       ) {
+
+               $rc = new RecentChange;
+               $rc->mTitle = $categoryTitle;
+               $rc->mPerformer = $user;
+               $rc->mAttribs = array(
+                       'rc_timestamp' => $timestamp,
+                       'rc_namespace' => $categoryTitle->getNamespace(),
+                       'rc_title' => $categoryTitle->getDBkey(),
+                       'rc_type' => RC_CATEGORIZE,
+                       'rc_source' => self::SRC_CATEGORIZE,
+                       'rc_minor' => 0,
+                       'rc_cur_id' => $pageTitle->getArticleID(),
+                       'rc_user' => $user ? $user->getId() : 0,
+                       'rc_user_text' => $user ? $user->getName() : '',
+                       'rc_comment' => $comment,
+                       'rc_this_oldid' => $newRevId,
+                       'rc_last_oldid' => $oldRevId,
+                       'rc_bot' => $bot ? 1 : 0,
+                       'rc_ip' => self::checkIPAddress( $ip ),
+                       'rc_patrolled' => 1, // Always patrolled, just like log entries
+                       'rc_new' => 0, # obsolete
+                       'rc_old_len' => 0,
+                       'rc_new_len' => 0,
+                       'rc_deleted' => $deleted,
+                       'rc_logid' => 0,
+                       'rc_log_type' => null,
+                       'rc_log_action' => '',
+                       'rc_params' => ''
+               );
+
+               $rc->mExtra = array(
+                       'prefixedDBkey' => $categoryTitle->getPrefixedDBkey(),
+                       'lastTimestamp' => $lastTimestamp,
+                       'oldSize' => 0,
+                       'newSize' => 0,
+                       'pageStatus' => 'changed'
+               );
+
+               return $rc;
+       }
+
        /**
         * Initialises the members of this object from a mysql row object
         *
index 0b17c78..0bc1920 100644 (file)
        "recentchangeslinked-summary": "This is a list of changes made recently to pages linked from a specified page (or to members of a specified category).\nPages on [[Special:Watchlist|your watchlist]] are <strong>bold</strong>.",
        "recentchangeslinked-page": "Page name:",
        "recentchangeslinked-to": "Show changes to pages linked to the given page instead",
+       "recentchanges-page-added-to-category": "[[:$1]] added to category",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] and {{PLURAL:$2|one page|$2 pages}} added to category",
+       "recentchanges-page-removed-from-category": "[[:$1]] removed from category",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] and {{PLURAL:$2|one page|$2 pages}} removed from category",
+       "autochange-username": "MediaWiki automatic change",
        "upload": "Upload file",
        "uploadbtn": "Upload file",
        "reuploaddesc": "Cancel upload and return to the upload form",
index 5657549..ca278d7 100644 (file)
        "recentchangeslinked-summary": "Summary of [[Special:RecentChangesLinked]].",
        "recentchangeslinked-page": "{{Identical|Page name}}",
        "recentchangeslinked-to": "Checkbox in [[Special:RecentChangesLinked]].",
+       "recentchanges-page-added-to-category": "Comment message for pages added to a category\n\nParameters:\n* $1 - name of the page being added",
+       "recentchanges-page-added-to-category-bundled": "Comment message for template embedded by other pages added to a category\n\nParameters:\n* $1 - name of the page being added\n* $2 - number of additional pages being affected",
+       "recentchanges-page-removed-from-category": "Comment message for pages removed from a category\n\nParameters:\n* $1 - name of the page being removed",
+       "recentchanges-page-removed-from-category-bundled": "Comment message for templates embeddedby other pages removed from a category\n\nParameters:\n* $1 - name of the page being added\n* $2 - number of additional pages being affected",
+       "autochange-username": "Used as bot / unknown username.",
        "upload": "Display name for link to [[Special:Upload]] for uploading files to the wiki.\n\nSee also:\n* {{msg-mw|Upload}}\n* {{msg-mw|Accesskey-t-upload}}\n* {{msg-mw|Tooltip-t-upload}}\n{{Identical|Upload file}}",
        "uploadbtn": "Button name in [[Special:Upload]].\n\nSee also:\n* {{msg-mw|Uploadbtn}}\n* {{msg-mw|Accesskey-upload}}\n* {{msg-mw|Tooltip-upload}}\n{{Identical|Upload file}}",
        "reuploaddesc": "Used as button text in the Upload form on [[Special:Upload]].\n\nSee also:\n* {{msg-mw|upload-tryagain|Submit button text}}\n* {{msg-mw|ignorewarning|button text}}",
diff --git a/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php b/tests/phpunit/includes/changes/CategoryMembershipChangeTest.php
new file mode 100644 (file)
index 0000000..2622ad4
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * @covers CategoryMembershipChange
+ *
+ * @group Database
+ *
+ * @author Adam Shorland
+ */
+class CategoryMembershipChangeTest extends MediaWikiLangTestCase {
+
+       /**
+        * @var array|Title[]|User[]
+        */
+       private static $lastNotifyArgs;
+
+       /**
+        * @var int
+        */
+       private static $notifyCallCounter = 0;
+
+       /**
+        * @var RecentChange
+        */
+       private static $mockRecentChange;
+
+       public static function newForCategorizationCallback() {
+               self::$lastNotifyArgs = func_get_args();
+               self::$notifyCallCounter += 1;
+               return self::$mockRecentChange;
+       }
+
+       public function setUp() {
+               parent::setUp();
+               self::$notifyCallCounter = 0;
+               self::$mockRecentChange = self::getMock( 'RecentChange' );
+       }
+
+       private function newChange( Revision $revision = null ) {
+               $change = new CategoryMembershipChange( Title::newFromText( 'UTPage' ), $revision );
+               $change->overrideNewForCategorizationCallback(
+                       'CategoryMembershipChangeTest::newForCategorizationCallback'
+               );
+
+               return $change;
+       }
+
+       public function testChangeAddedNoRev() {
+               $change = $this->newChange();
+               $change->triggerCategoryAddedNotification( Title::newFromText( 'CategoryName', NS_CATEGORY ) );
+
+               $this->assertEquals( 1, self::$notifyCallCounter );
+
+               $this->assertTrue( strlen( self::$lastNotifyArgs[0] ) === 14 );
+               $this->assertEquals( 'Category:CategoryName', self::$lastNotifyArgs[1]->getPrefixedText() );
+               $this->assertEquals( 'MediaWiki automatic change', self::$lastNotifyArgs[2]->getName() );
+               $this->assertEquals( '[[:UTPage]] added to category', self::$lastNotifyArgs[3] );
+               $this->assertEquals( 'UTPage', self::$lastNotifyArgs[4]->getPrefixedText() );
+               $this->assertEquals( 0, self::$lastNotifyArgs[5] );
+               $this->assertEquals( 0, self::$lastNotifyArgs[6] );
+               $this->assertEquals( null, self::$lastNotifyArgs[7] );
+               $this->assertEquals( 1, self::$lastNotifyArgs[8] );
+               $this->assertEquals( null, self::$lastNotifyArgs[9] );
+               $this->assertEquals( 0, self::$lastNotifyArgs[10] );
+       }
+
+       public function testChangeRemovedNoRev() {
+               $change = $this->newChange();
+               $change->triggerCategoryRemovedNotification( Title::newFromText( 'CategoryName', NS_CATEGORY ) );
+
+               $this->assertEquals( 1, self::$notifyCallCounter );
+
+               $this->assertTrue( strlen( self::$lastNotifyArgs[0] ) === 14 );
+               $this->assertEquals( 'Category:CategoryName', self::$lastNotifyArgs[1]->getPrefixedText() );
+               $this->assertEquals( 'MediaWiki automatic change', self::$lastNotifyArgs[2]->getName() );
+               $this->assertEquals( '[[:UTPage]] removed from category', self::$lastNotifyArgs[3] );
+               $this->assertEquals( 'UTPage', self::$lastNotifyArgs[4]->getPrefixedText() );
+               $this->assertEquals( 0, self::$lastNotifyArgs[5] );
+               $this->assertEquals( 0, self::$lastNotifyArgs[6] );
+               $this->assertEquals( null, self::$lastNotifyArgs[7] );
+               $this->assertEquals( 1, self::$lastNotifyArgs[8] );
+               $this->assertEquals( null, self::$lastNotifyArgs[9] );
+               $this->assertEquals( 0, self::$lastNotifyArgs[10] );
+       }
+
+       public function testChangeAddedWithRev() {
+               $revision = Revision::newFromId( Title::newFromText( 'UTPage' )->getLatestRevID() );
+               $change = $this->newChange( $revision );
+               $change->triggerCategoryAddedNotification( Title::newFromText( 'CategoryName', NS_CATEGORY ) );
+
+               $this->assertEquals( 1, self::$notifyCallCounter );
+
+               $this->assertTrue( strlen( self::$lastNotifyArgs[0] ) === 14 );
+               $this->assertEquals( 'Category:CategoryName', self::$lastNotifyArgs[1]->getPrefixedText() );
+               $this->assertEquals( 'UTSysop', self::$lastNotifyArgs[2]->getName() );
+               $this->assertEquals( '[[:UTPage]] added to category', self::$lastNotifyArgs[3] );
+               $this->assertEquals( 'UTPage', self::$lastNotifyArgs[4]->getPrefixedText() );
+               $this->assertEquals( 0, self::$lastNotifyArgs[5] );
+               $this->assertEquals( $revision->getId(), self::$lastNotifyArgs[6] );
+               $this->assertEquals( null, self::$lastNotifyArgs[7] );
+               $this->assertEquals( 0, self::$lastNotifyArgs[8] );
+               $this->assertEquals( '127.0.0.1', self::$lastNotifyArgs[9] );
+               $this->assertEquals( 0, self::$lastNotifyArgs[10] );
+       }
+
+       public function testChangeRemovedWithRev() {
+               $revision = Revision::newFromId( Title::newFromText( 'UTPage' )->getLatestRevID() );
+               $change = $this->newChange( $revision );
+               $change->triggerCategoryRemovedNotification( Title::newFromText( 'CategoryName', NS_CATEGORY ) );
+
+               $this->assertEquals( 1, self::$notifyCallCounter );
+
+               $this->assertTrue( strlen( self::$lastNotifyArgs[0] ) === 14 );
+               $this->assertEquals( 'Category:CategoryName', self::$lastNotifyArgs[1]->getPrefixedText() );
+               $this->assertEquals( 'UTSysop', self::$lastNotifyArgs[2]->getName() );
+               $this->assertEquals( '[[:UTPage]] removed from category', self::$lastNotifyArgs[3] );
+               $this->assertEquals( 'UTPage', self::$lastNotifyArgs[4]->getPrefixedText() );
+               $this->assertEquals( 0, self::$lastNotifyArgs[5] );
+               $this->assertEquals( $revision->getId(), self::$lastNotifyArgs[6] );
+               $this->assertEquals( null, self::$lastNotifyArgs[7] );
+               $this->assertEquals( 0, self::$lastNotifyArgs[8] );
+               $this->assertEquals( '127.0.0.1', self::$lastNotifyArgs[9] );
+               $this->assertEquals( 0, self::$lastNotifyArgs[10] );
+       }
+
+}
index fd287b5..85dbe79 100644 (file)
@@ -114,6 +114,7 @@ class RecentChangeTest extends MediaWikiTestCase {
                        array( RC_NEW, 'new' ),
                        array( RC_LOG, 'log' ),
                        array( RC_EXTERNAL, 'external' ),
+                       array( RC_CATEGORIZE, 'categorize' ),
                );
        }