jenkins-bot has submitted this change and it was merged.
Change subject: Enable users to watch category membership changes
......................................................................
Enable users to watch category membership changes
Bug: T9148
Change-Id: I5a89d8f19804b1120f4c755d834e2da6ca12ceae
---
M autoload.php
M includes/DefaultSettings.php
M includes/Defines.php
M includes/Preferences.php
M includes/api/ApiFeedRecentChanges.php
M includes/api/ApiQueryRecentChanges.php
M includes/api/ApiQueryWatchlist.php
M includes/api/i18n/en.json
M includes/api/i18n/qqq.json
A includes/changes/CategoryMembershipChange.php
M includes/changes/ChangesList.php
M includes/changes/EnhancedChangesList.php
M includes/changes/OldChangesList.php
M includes/changes/RCCacheEntryFactory.php
M includes/changes/RecentChange.php
M includes/deferred/LinksUpdate.php
M includes/jobqueue/jobs/RefreshLinksJob.php
M includes/page/WikiPage.php
M includes/specialpage/ChangesListSpecialPage.php
M includes/specials/SpecialRecentchanges.php
M includes/specials/SpecialWatchlist.php
M languages/i18n/en.json
M languages/i18n/qqq.json
M tests/phpunit/includes/changes/EnhancedChangesListTest.php
M tests/phpunit/includes/changes/TestRecentChangesHelper.php
M tests/phpunit/includes/deferred/LinksUpdateTest.php
26 files changed, 614 insertions(+), 57 deletions(-)
Approvals:
Kaldari: Looks good to me, approved
Addshore: Looks good to me, but someone else must approve
jenkins-bot: Verified
diff --git a/autoload.php b/autoload.php
index 6444e3e..acb272f 100644
--- a/autoload.php
+++ b/autoload.php
@@ -197,6 +197,7 @@
'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',
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 6050ba7..12aa938 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -4503,6 +4503,7 @@
'gender' => 'unknown',
'hideminor' => 0,
'hidepatrolled' => 0,
+ 'hidecategorization' => 0,
'imagesize' => 2,
'math' => 1,
'minordefault' => 0,
@@ -4534,6 +4535,7 @@
'watchlisthideminor' => 0,
'watchlisthideown' => 0,
'watchlisthidepatrolled' => 0,
+ 'watchlisthidecategorization' => 0,
'watchmoves' => 0,
'watchrollback' => 0,
'wllimit' => 250,
diff --git a/includes/Defines.php b/includes/Defines.php
index d55bbcf..38f2d42 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -170,6 +170,7 @@
define( 'RC_NEW', 1 );
define( 'RC_LOG', 3 );
define( 'RC_EXTERNAL', 5 );
+define( 'RC_CATEGORIZE', 6 );
/**@}*/
/**@{
diff --git a/includes/Preferences.php b/includes/Preferences.php
index 9497ee7..deea757 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -888,6 +888,12 @@
'section' => 'rc/advancedrc',
);
+ $defaultPreferences['hidecategorization'] = array(
+ 'type' => 'toggle',
+ 'label-message' => 'tog-hidecategorization',
+ 'section' => 'rc/advancedrc',
+ );
+
if ( $user->useRCPatrol() ) {
$defaultPreferences['hidepatrolled'] = array(
'type' => 'toggle',
@@ -995,6 +1001,12 @@
'label-message' => 'tog-watchlisthideliu',
);
+ $defaultPreferences['watchlisthidecategorization'] = array(
+ 'type' => 'toggle',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'tog-watchlisthidecategorization',
+ );
+
if ( $user->useRCPatrol() ) {
$defaultPreferences['watchlisthidepatrolled'] = array(
'type' => 'toggle',
diff --git a/includes/api/ApiFeedRecentChanges.php
b/includes/api/ApiFeedRecentChanges.php
index d24112c..5adde87 100644
--- a/includes/api/ApiFeedRecentChanges.php
+++ b/includes/api/ApiFeedRecentChanges.php
@@ -155,6 +155,7 @@
'hideliu' => false,
'hidepatrolled' => false,
'hidemyself' => false,
+ 'hidecategorization' => false,
'tagfilter' => array(
ApiBase::PARAM_TYPE => 'string',
diff --git a/includes/api/ApiQueryRecentChanges.php
b/includes/api/ApiQueryRecentChanges.php
index 74bccc2..b6d2c40 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -667,14 +667,9 @@
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'type' => array(
- ApiBase::PARAM_DFLT => 'edit|new|log',
+ ApiBase::PARAM_DFLT =>
'edit|new|log|categorize',
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => array(
- 'edit',
- 'external',
- 'new',
- 'log'
- )
+ ApiBase::PARAM_TYPE =>
RecentChange::getChangeTypes()
),
'toponly' => false,
'continue' => array(
diff --git a/includes/api/ApiQueryWatchlist.php
b/includes/api/ApiQueryWatchlist.php
index 648d259..9d35a55 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -483,14 +483,9 @@
)
),
'type' => array(
- ApiBase::PARAM_DFLT => 'edit|new|log',
+ ApiBase::PARAM_DFLT =>
'edit|new|log|categorize',
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => array(
- 'edit',
- 'external',
- 'new',
- 'log',
- )
+ ApiBase::PARAM_TYPE =>
RecentChange::getChangeTypes()
),
'owner' => array(
ApiBase::PARAM_TYPE => 'user'
diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json
index 5a574d9..d09718e 100644
--- a/includes/api/i18n/en.json
+++ b/includes/api/i18n/en.json
@@ -162,6 +162,7 @@
"apihelp-feedrecentchanges-param-hideliu": "Hide changes made by
registered users.",
"apihelp-feedrecentchanges-param-hidepatrolled": "Hide patrolled
changes.",
"apihelp-feedrecentchanges-param-hidemyself": "Hide changes made by the
current user.",
+ "apihelp-feedrecentchanges-param-hidecategorization": "Hide category
membership changes.",
"apihelp-feedrecentchanges-param-tagfilter": "Filter by tag.",
"apihelp-feedrecentchanges-param-target": "Show only changes on pages
linked from this page.",
"apihelp-feedrecentchanges-param-showlinkedto": "Show changes on pages
linked to the selected page instead.",
@@ -1013,7 +1014,7 @@
"apihelp-query+watchlist-paramvalue-prop-loginfo": "Adds log
information where appropriate.",
"apihelp-query+watchlist-param-show": "Show only items that meet these
criteria. For example, to see only minor edits done by logged-in users, set
$1show=minor|!anon.",
- "apihelp-query+watchlist-param-type": "Which types of changes to
show:\n;edit:Regular page edits.\n;external:External changes.\n;new:Page
creations.\n;log:Log entries.",
+ "apihelp-query+watchlist-param-type": "Which types of changes to
show:\n;edit:Regular page edits.\n;external:External changes.\n;new:Page
creations.\n;log:Log entries.\n;categorize:Category membership changes.",
"apihelp-query+watchlist-param-owner": "Used along with $1token to
access a different user's watchlist.",
"apihelp-query+watchlist-param-token": "A security token (available in
the user's [[Special:Preferences#mw-prefsection-watchlist|preferences]]) to
allow access to another user's watchlist.",
"apihelp-query+watchlist-example-simple": "List the top revision for
recently changed pages on the current user's watchlist.",
diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json
index 95562d8..e333784 100644
--- a/includes/api/i18n/qqq.json
+++ b/includes/api/i18n/qqq.json
@@ -157,6 +157,7 @@
"apihelp-feedrecentchanges-param-hideliu":
"{{doc-apihelp-param|feedrecentchanges|hideliu}}",
"apihelp-feedrecentchanges-param-hidepatrolled":
"{{doc-apihelp-param|feedrecentchanges|hidepatrolled}}",
"apihelp-feedrecentchanges-param-hidemyself":
"{{doc-apihelp-param|feedrecentchanges|hidemyself}}",
+ "apihelp-feedrecentchanges-param-hidecategorization":
"{{doc-apihelp-param|feedrecentchanges|hidecategorization}}",
"apihelp-feedrecentchanges-param-tagfilter":
"{{doc-apihelp-param|feedrecentchanges|tagfilter}}",
"apihelp-feedrecentchanges-param-target":
"{{doc-apihelp-param|feedrecentchanges|target}}",
"apihelp-feedrecentchanges-param-showlinkedto":
"{{doc-apihelp-param|feedrecentchanges|showlinkedto}}",
diff --git a/includes/changes/CategoryMembershipChange.php
b/includes/changes/CategoryMembershipChange.php
new file mode 100644
index 0000000..8bc4cf5
--- /dev/null
+++ b/includes/changes/CategoryMembershipChange.php
@@ -0,0 +1,205 @@
+<?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
+ * @since 1.26
+ */
+
+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 WikiPage WikiPage instance of the categorized page */
+ private $page;
+
+ /** @var Revision 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 User
+ * instance of the user that created CategoryMembershipChange::$revision
+ */
+ private $user;
+
+ /**
+ * @var null|RecentChange
+ * RecentChange that is referred to in
CategoryMembershipChange::$revision
+ */
+ private $correspondingRC;
+
+ /**
+ * @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->page = WikiPage::factory( $pageTitle );
+ $this->timestamp = wfTimestampNow();
+
+ # if no revision is given, the change was probably triggered by
parser functions
+ if ( $revision ) {
+ $this->revision = $revision;
+ $this->correspondingRC =
$this->revision->getRecentChange();
+ $this->user = $this->getRevisionUser();
+ } else {
+ $this->user = User::newFromId( 0 );
+ }
+ }
+
+ /**
+ * Determines the number of template links for recursive link updates
+ */
+ public function setRecursive() {
+ $this->numTemplateLinks =
$this->pageTitle->getBacklinkCache()->getNumLinks( 'templatelinks' );
+ }
+
+ /**
+ * Create a recentchanges entry for category additions
+ * @param string $categoryName
+ */
+ public function pageAddedToCategory( $categoryName ) {
+ $this->createRecentChangesEntry( $categoryName,
self::CATEGORY_ADDITION );
+ }
+
+ /**
+ * Create a recentchanges entry for category removals
+ * @param string $categoryName
+ */
+ public function pageRemovedFromCategory( $categoryName ) {
+ $this->createRecentChangesEntry( $categoryName,
self::CATEGORY_REMOVAL );
+ }
+
+ /**
+ * Create a recentchanges entry using
RecentChange::notifyCategorization()
+ * @param string $categoryName
+ * @param int $type
+ */
+ private function createRecentChangesEntry( $categoryName, $type ) {
+ $categoryTitle = Title::newFromText( $categoryName, NS_CATEGORY
);
+ if ( !$categoryTitle ) {
+ return;
+ }
+
+ $previousRevTimestamp = $this->getPreviousRevisionTimestamp();
+ $unpatrolled = $this->revision ?
$this->revision->isUnpatrolled() : 0;
+
+ $lastRevId = 0;
+ $bot = 0;
+ $ip = '';
+ if ( $this->correspondingRC ) {
+ $lastRevId = $this->correspondingRC->getAttribute(
'rc_last_oldid' ) ?: 0;
+ $bot = $this->correspondingRC->getAttribute( 'rc_bot' )
?: 0;
+ $ip = $this->correspondingRC->getAttribute( 'rc_ip' )
?: '';
+ }
+
+ RecentChange::notifyCategorization(
+ $this->timestamp,
+ $categoryTitle,
+ $this->user,
+ $this->getChangeMessage( $type, array(
+ 'prefixedUrl' =>
$this->page->getTitle()->getPrefixedURL(),
+ 'numTemplateLinks' => $this->numTemplateLinks
+ ) ),
+ $this->pageTitle,
+ $lastRevId,
+ $this->revision ? $this->revision->getId() : 0,
+ $previousRevTimestamp,
+ $bot,
+ $ip,
+ $unpatrolled ? 0 : 1,
+ $this->revision ? $this->revision->getVisibility() &
Revision::SUPPRESSED_USER : 0
+ );
+ }
+
+ /**
+ * Get the user who created the revision. may be an anonymous IP
+ * @return User
+ */
+ private function getRevisionUser() {
+ $userId = $this->revision->getUser( Revision::RAW );
+ if ( $userId === 0 ) {
+ return User::newFromName( $this->revision->getUserText(
Revision::RAW ), false );
+ } else {
+ return User::newFromId( $userId );
+ }
+ }
+
+ /**
+ * 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()
+ * - numTemplateLinks
+ * @return string
+ */
+ private function getChangeMessage( $type, array $params ) {
+ $msgKey = 'recentchanges-';
+
+ switch ( $type ) {
+ case self::CATEGORY_ADDITION:
+ $msgKey .= 'page-added-to-category';
+ break;
+ case self::CATEGORY_REMOVAL:
+ $msgKey .= 'page-removed-from-category';
+ break;
+ }
+
+ 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() {
+ $latestRev = Revision::newFromId(
$this->pageTitle->getLatestRevID() );
+ $previousRev = Revision::newFromId( $latestRev->getParentId() );
+
+ return $previousRev ? $previousRev->getTimestamp() : null;
+ }
+
+}
diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php
index fdc9944..249f53f 100644
--- a/includes/changes/ChangesList.php
+++ b/includes/changes/ChangesList.php
@@ -457,6 +457,28 @@
}
/**
+ * @param RCCacheEntry $rc
+ * @param array $query array of key/value pairs to append as a query
string
+ * @return string
+ * @since 1.26
+ */
+ public function getDiffHistLinks( RCCacheEntry $rc, array $query ) {
+ $pageTitle = $rc->getTitle();
+ if ( intval( $rc->getAttribute( 'rc_type' ) ) === RC_CATEGORIZE
) {
+ $pageTitle = Title::newFromID( $rc->getAttribute(
'rc_cur_id' ) );
+ }
+
+ $retVal = ' ' . $this->msg( 'parentheses' )
+ ->rawParams( $rc->difflink . $this->message['pipe-separator'] .
Linker::linkKnown(
+ $pageTitle,
+ $this->message['hist'],
+ array(),
+ $query
+ ) )->escaped();
+ return $retVal;
+ }
+
+ /**
* Check whether to enable recent changes patrol features
*
* @deprecated since 1.22
@@ -630,4 +652,17 @@
return false;
}
+
+ /**
+ * Determines whether a revision is linked to this change; this may not
be the case
+ * when the categorization wasn't done by an edit but a conditional
parser function
+ *
+ * @param RecentChange|RCCacheEntry $rcObj
+ * @return bool
+ */
+ protected function isCategorizationWithoutRevision( $rcObj ) {
+ return intval( $rcObj->getAttribute( 'rc_type' ) ) ===
RC_CATEGORIZE
+ && intval( $rcObj->getAttribute( 'rc_this_oldid' ) ) === 0;
+ }
+
}
diff --git a/includes/changes/EnhancedChangesList.php
b/includes/changes/EnhancedChangesList.php
index e5916bd..a1e9d28 100644
--- a/includes/changes/EnhancedChangesList.php
+++ b/includes/changes/EnhancedChangesList.php
@@ -364,6 +364,8 @@
if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
$data['logEntry'] = $this->insertLogEntry(
$rcObj );
+ } elseif ( $this->isCategorizationWithoutRevision(
$rcObj ) ) {
+ $data['comment'] = $this->insertComment( $rcObj
);
} else {
# User links
$data['userLink'] = $rcObj->userlink;
@@ -566,15 +568,9 @@
}
# Diff and hist links
- if ( $type != RC_LOG ) {
+ if ( intval( $type ) !== RC_LOG && intval( $type ) !==
RC_CATEGORIZE ) {
$query['action'] = 'history';
- $data['historyLink'] = ' ' . $this->msg( 'parentheses' )
- ->rawParams( $rcObj->difflink .
$this->message['pipe-separator'] . Linker::linkKnown(
- $rcObj->getTitle(),
- $this->message['hist'],
- array(),
- $query
- ) )->escaped();
+ $data['historyLink'] = $this->getDiffHistLinks( $rcObj,
$query );
}
$data['separatorAfterLinks'] = ' <span
class="mw-changeslist-separator">. .</span> ';
@@ -589,10 +585,15 @@
if ( $type == RC_LOG ) {
$data['logEntry'] = $this->insertLogEntry( $rcObj );
+ } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
+ $data['comment'] = $this->insertComment( $rcObj );
} else {
$data['userLink'] = $rcObj->userlink;
$data['userTalkLink'] = $rcObj->usertalklink;
$data['comment'] = $this->insertComment( $rcObj );
+ if ( intval( $type ) === RC_CATEGORIZE ) {
+ $data['historyLink'] = $this->getDiffHistLinks(
$rcObj, $query );
+ }
$data['rollback'] = $this->getRollback( $rcObj );
}
diff --git a/includes/changes/OldChangesList.php
b/includes/changes/OldChangesList.php
index 4ce564d..1bd78ca 100644
--- a/includes/changes/OldChangesList.php
+++ b/includes/changes/OldChangesList.php
@@ -88,7 +88,9 @@
} else {
$unpatrolled = $this->showAsUnpatrolled( $rc );
- $this->insertDiffHist( $html, $rc, $unpatrolled );
+ if ( !$this->isCategorizationWithoutRevision( $rc ) ) {
+ $this->insertDiffHist( $html, $rc, $unpatrolled
);
+ }
# M, N, b and ! (minor, new, bot and unpatrolled)
$html .= $this->recentChangesFlags(
array(
@@ -113,6 +115,8 @@
if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
$html .= $this->insertLogEntry( $rc );
+ } elseif ( $this->isCategorizationWithoutRevision( $rc ) ) {
+ $html .= $this->insertComment( $rc );
} else {
# User tool links
$this->insertUserRelatedLinks( $html, $rc );
diff --git a/includes/changes/RCCacheEntryFactory.php
b/includes/changes/RCCacheEntryFactory.php
index c3fe183..e41f735 100644
--- a/includes/changes/RCCacheEntryFactory.php
+++ b/includes/changes/RCCacheEntryFactory.php
@@ -209,6 +209,10 @@
$diffLink = $diffMessage;
} elseif ( in_array( $cacheEntry->mAttribs['rc_type'],
$logTypes ) ) {
$diffLink = $diffMessage;
+ } elseif ( intval( $cacheEntry->getAttribute( 'rc_type' ) ) ===
RC_CATEGORIZE ) {
+ $pageTitle = Title::newFromID(
$cacheEntry->getAttribute( 'rc_cur_id' ) );
+ $diffUrl = htmlspecialchars( $pageTitle->getLinkURL(
$queryParams ) );
+ $diffLink = "<a href=\"$diffUrl\"
tabindex=\"$counter\">$diffMessage</a>";
} else {
$diffUrl = htmlspecialchars(
$cacheEntry->getTitle()->getLinkURL( $queryParams ) );
$diffLink = "<a href=\"$diffUrl\"
tabindex=\"$counter\">$diffMessage</a>";
diff --git a/includes/changes/RecentChange.php
b/includes/changes/RecentChange.php
index 77bf5df..a73ed5b 100644
--- a/includes/changes/RecentChange.php
+++ b/includes/changes/RecentChange.php
@@ -67,6 +67,7 @@
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();
@@ -88,6 +89,17 @@
* @var int Line number of recent change. Default -1.
*/
public $counter = -1;
+
+ /**
+ * @var array Array of change types
+ */
+ private static $changeTypes = array(
+ 'edit' => RC_EDIT,
+ 'new' => RC_NEW,
+ 'log' => RC_LOG,
+ 'external' => RC_EXTERNAL,
+ 'categorize' => RC_CATEGORIZE,
+ );
# Factory methods
@@ -119,18 +131,10 @@
return $retval;
}
- switch ( $type ) {
- case 'edit':
- return RC_EDIT;
- case 'new':
- return RC_NEW;
- case 'log':
- return RC_LOG;
- case 'external':
- return RC_EXTERNAL;
- default:
- throw new MWException( "Unknown type '$type'" );
+ if ( !array_key_exists( $type, self::$changeTypes ) ) {
+ throw new MWException( "Unknown type '$type'" );
}
+ return self::$changeTypes[$type];
}
/**
@@ -140,24 +144,15 @@
* @return string $type
*/
public static function parseFromRCType( $rcType ) {
- switch ( $rcType ) {
- case RC_EDIT:
- $type = 'edit';
- break;
- case RC_NEW:
- $type = 'new';
- break;
- case RC_LOG:
- $type = 'log';
- break;
- case RC_EXTERNAL:
- $type = 'external';
- break;
- default:
- $type = "$rcType";
- }
+ return array_search( $rcType, self::$changeTypes, true ) ?:
"$rcType";
+ }
- return $type;
+ /**
+ * Get an array of all change types
+ * @return array
+ */
+ public static function getChangeTypes() {
+ return array_keys( self::$changeTypes );
}
/**
@@ -747,6 +742,72 @@
}
/**
+ * Makes an entry in the database corresponding to a categorization
+ *
+ * @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 $patrol Indicates whether the change is
patrolled/unpatrolled
+ * @param int $deleted Indicates whether the change has been deleted
+ * @param array|null $params Additional parameters as explained on
+ * https://www.mediawiki.org/wiki/Manual:Logging_table#log_params
+ * @return RecentChange
+ * @throws MWException
+ * @since 1.26
+ */
+ public static function notifyCategorization( $timestamp, Title
$categoryTitle, User $user = null,
+ $comment, Title $pageTitle, $oldRevId, $newRevId,
$lastTimestamp, $bot, $ip = '',
+ $patrol = 0, $deleted = 0, $params = null ) {
+
+ $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' => intval( $patrol ),
+ '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' => $params ? serialize( $params ) : ''
+ );
+
+ $rc->mExtra = array(
+ 'prefixedDBkey' => $categoryTitle->getPrefixedDBkey(),
+ 'lastTimestamp' => $lastTimestamp,
+ 'oldSize' => 0,
+ 'newSize' => 0,
+ 'pageStatus' => 'changed'
+ );
+ $rc->save();
+
+ return $rc;
+ }
+
+ /**
* Initialises the members of this object from a mysql row object
*
* @param mixed $row
diff --git a/includes/deferred/LinksUpdate.php
b/includes/deferred/LinksUpdate.php
index e9ec7ff..36cc865 100644
--- a/includes/deferred/LinksUpdate.php
+++ b/includes/deferred/LinksUpdate.php
@@ -61,6 +61,12 @@
/** @var bool Whether to queue jobs for recursive updates */
public $mRecursive;
+ /** @var bool Whether this job was triggered by a recursive update job
*/
+ private $mTriggeredRecursive;
+
+ /** @var Revision Revision for which this update has been triggered */
+ private $mRevision;
+
/**
* @var null|array Added links if calculated.
*/
@@ -147,7 +153,6 @@
}
protected function doIncrementalUpdate() {
-
# Page links
$existing = $this->getExistingLinks();
$this->linkDeletions = $this->getLinkDeletions( $existing );
@@ -198,6 +203,27 @@
$categoryUpdates = $categoryInserts + $categoryDeletes;
$this->invalidateCategories( $categoryUpdates );
$this->updateCategoryCounts( $categoryInserts, $categoryDeletes
);
+
+ # Category membership changes
+ if ( !$this->mTriggeredRecursive && ( $categoryInserts ||
$categoryDeletes ) ) {
+ try {
+ $catMembChange = new CategoryMembershipChange(
$this->mTitle, $this->mRevision );
+
+ if ( $this->mRecursive ) {
+ $catMembChange->setRecursive();
+ }
+
+ foreach ( $categoryInserts as $categoryName =>
$value ) {
+ $catMembChange->pageAddedToCategory(
$categoryName );
+ }
+
+ foreach ( $categoryDeletes as $categoryName =>
$value ) {
+
$catMembChange->pageRemovedFromCategory( $categoryName );
+ }
+ } catch ( MWException $e ) {
+ MWExceptionHandler::logException( $e );
+ }
+ }
# Page properties
$existing = $this->getExistingProperties();
@@ -864,6 +890,21 @@
}
/**
+ * Set this object as being triggered by a recursive LinksUpdate
+ */
+ public function setTriggeredRecursive() {
+ $this->mTriggeredRecursive = true;
+ }
+
+ /**
+ * Set the revision corresponding to this LinksUpdate
+ * @param Revision $revision
+ */
+ public function setRevision( Revision $revision ) {
+ $this->mRevision = $revision;
+ }
+
+ /**
* Invalidate any necessary link lists related to page property changes
* @param array $changed
*/
diff --git a/includes/jobqueue/jobs/RefreshLinksJob.php
b/includes/jobqueue/jobs/RefreshLinksJob.php
index dec944a..c66da41 100644
--- a/includes/jobqueue/jobs/RefreshLinksJob.php
+++ b/includes/jobqueue/jobs/RefreshLinksJob.php
@@ -83,6 +83,7 @@
} else {
$extraParams['masterPos'] = false;
}
+ $extraParams['triggeredRecursive'] = true;
// Convert this into no more than $wgUpdateRowsPerJob
RefreshLinks per-title
// jobs and possibly a recursive RefreshLinks job for
the rest of the backlinks
$jobs = BacklinkJobUtils::partitionBacklinkJob(
@@ -196,6 +197,12 @@
}
$updates = $content->getSecondaryDataUpdates( $title, null,
false, $parserOutput );
+ foreach ( $updates as $key => $update ) {
+ if ( $update instanceof LinksUpdate && isset(
$this->params['triggeredRecursive'] ) ) {
+ $update->setTriggeredRecursive();
+ }
+ }
+
DataUpdate::runUpdates( $updates );
InfoAction::invalidateCache( $title );
diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php
index f7f2528..7c599e9 100644
--- a/includes/page/WikiPage.php
+++ b/includes/page/WikiPage.php
@@ -2180,6 +2180,9 @@
$updates = $content->getSecondaryDataUpdates(
$this->getTitle(), null, $recursive,
$editInfo->output );
foreach ( $updates as $update ) {
+ if ( $update instanceof LinksUpdate ) {
+ $update->setRevision( $revision );
+ }
DeferredUpdates::addUpdate( $update );
}
}
diff --git a/includes/specialpage/ChangesListSpecialPage.php
b/includes/specialpage/ChangesListSpecialPage.php
index 23bd394..0938316 100644
--- a/includes/specialpage/ChangesListSpecialPage.php
+++ b/includes/specialpage/ChangesListSpecialPage.php
@@ -144,6 +144,7 @@
$opts->add( 'hideliu', false );
$opts->add( 'hidepatrolled', false );
$opts->add( 'hidemyself', false );
+ $opts->add( 'hidecategorization', false );
$opts->add( 'namespace', '', FormOptions::INTNULL );
$opts->add( 'invert', false );
@@ -249,6 +250,9 @@
$conds[] = 'rc_user_text != ' .
$dbr->addQuotes( $user->getName() );
}
}
+ if ( $opts['hidecategorization'] === true ) {
+ $conds[] = 'rc_type != ' . $dbr->addQuotes(
RC_CATEGORIZE );
+ }
// Namespace filtering
if ( $opts['namespace'] !== '' ) {
diff --git a/includes/specials/SpecialRecentchanges.php
b/includes/specials/SpecialRecentchanges.php
index 96d512c..4b2d2d4 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -83,6 +83,7 @@
$opts->add( 'hideliu', false );
$opts->add( 'hidepatrolled', $user->getBoolOption(
'hidepatrolled' ) );
$opts->add( 'hidemyself', false );
+ $opts->add( 'hidecategorization', $user->getBoolOption(
'hidecategorization' ) );
$opts->add( 'categories', '' );
$opts->add( 'categories_any', false );
@@ -137,6 +138,9 @@
}
if ( 'hidemyself' === $bit ) {
$opts['hidemyself'] = true;
+ }
+ if ( 'hidecategorization' === $bit ) {
+ $opts['hidecategorization'] = true;
}
if ( is_numeric( $bit ) ) {
@@ -723,7 +727,8 @@
'hideanons' => 'rcshowhideanons',
'hideliu' => 'rcshowhideliu',
'hidepatrolled' => 'rcshowhidepatr',
- 'hidemyself' => 'rcshowhidemine'
+ 'hidemyself' => 'rcshowhidemine',
+ 'hidecategorization' => 'rcshowhidecategorization'
);
$showhide = array( 'show', 'hide' );
@@ -741,7 +746,8 @@
// The following messages are used here:
// rcshowhideminor-show, rcshowhideminor-hide,
rcshowhidebots-show, rcshowhidebots-hide,
// rcshowhideanons-show, rcshowhideanons-hide,
rcshowhideliu-show, rcshowhideliu-hide,
- // rcshowhidepatr-show, rcshowhidepatr-hide,
rcshowhidemine-show, rcshowhidemine-hide.
+ // rcshowhidepatr-show, rcshowhidepatr-hide,
rcshowhidemine-show, rcshowhidemine-hide,
+ // rcshowhidecategorization-show,
rcshowhidecategorization-hide.
$linkMessage = $this->msg( $msg . '-' . $showhide[1 -
$options[$key]] );
// Extensions can define additional filters, but don't
need to define the corresponding
// messages. If they don't exist, just fall back to
'show' and 'hide'.
diff --git a/includes/specials/SpecialWatchlist.php
b/includes/specials/SpecialWatchlist.php
index 20f5776..088a3fe 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -110,6 +110,7 @@
$opts->add( 'hideliu', $user->getBoolOption( 'watchlisthideliu'
) );
$opts->add( 'hidepatrolled', $user->getBoolOption(
'watchlisthidepatrolled' ) );
$opts->add( 'hidemyself', $user->getBoolOption(
'watchlisthideown' ) );
+ $opts->add( 'hidecategorization', $user->getBoolOption(
'watchlisthidecategorization' ) );
$opts->add( 'extended', $user->getBoolOption( 'extendwatchlist'
) );
@@ -423,7 +424,8 @@
'hideanons' => 'rcshowhideanons',
'hideliu' => 'rcshowhideliu',
'hidemyself' => 'rcshowhidemine',
- 'hidepatrolled' => 'rcshowhidepatr'
+ 'hidepatrolled' => 'rcshowhidepatr',
+ 'hidecategorization' => 'rcshowhidecategorization',
);
foreach ( $this->getCustomFilters() as $key => $params ) {
$filters[$key] = $params['msg'];
diff --git a/languages/i18n/en.json b/languages/i18n/en.json
index 613885f..654c179 100644
--- a/languages/i18n/en.json
+++ b/languages/i18n/en.json
@@ -7,6 +7,7 @@
"tog-hideminor": "Hide minor edits from recent changes",
"tog-hidepatrolled": "Hide patrolled edits from recent changes",
"tog-newpageshidepatrolled": "Hide patrolled pages from new page list",
+ "tog-hidecategorization": "Hide categorization of pages",
"tog-extendwatchlist": "Expand watchlist to show all changes, not just
the most recent",
"tog-usenewrc": "Group changes by page in recent changes and watchlist",
"tog-numberheadings": "Auto-number headings",
@@ -36,6 +37,7 @@
"tog-watchlisthideliu": "Hide edits by logged in users from the
watchlist",
"tog-watchlisthideanons": "Hide edits by anonymous users from the
watchlist",
"tog-watchlisthidepatrolled": "Hide patrolled edits from the watchlist",
+ "tog-watchlisthidecategorization": "Hide categorization of pages",
"tog-ccmeonemails": "Send me copies of emails I send to other users",
"tog-diffonly": "Do not show page content below diffs",
"tog-showhiddencats": "Show hidden categories",
@@ -1256,6 +1258,9 @@
"rcshowhidemine": "$1 my edits",
"rcshowhidemine-show": "Show",
"rcshowhidemine-hide": "Hide",
+ "rcshowhidecategorization": "$1 page categorization",
+ "rcshowhidecategorization-show": "Show",
+ "rcshowhidecategorization-hide": "Hide",
"rclinks": "Show last $1 changes in last $2 days<br />$3",
"diff": "diff",
"hist": "hist",
@@ -1282,6 +1287,10 @@
"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",
"upload": "Upload file",
"uploadbtn": "Upload file",
"reuploaddesc": "Cancel upload and return to the upload form",
diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json
index 0867cf7..99286b8 100644
--- a/languages/i18n/qqq.json
+++ b/languages/i18n/qqq.json
@@ -178,6 +178,7 @@
"tog-hideminor": "[[Special:Preferences]], tab 'Recent changes'. Offers
user to hide minor edits in recent changes or not. {{Gender}}",
"tog-hidepatrolled": "Option in Recent changes tab of
[[Special:Preferences]] (if [[mw:Manual:$wgUseRCPatrol|$wgUseRCPatrol]] is
enabled). {{Gender}}",
"tog-newpageshidepatrolled": "Toggle in [[Special:Preferences]],
section \"Recent changes\" (if [[mw:Manual:$wgUseRCPatrol|$wgUseRCPatrol]] is
enabled). {{Gender}}",
+ "tog-hidecategorization": "Option in \"Recent changes\" tab of
[[Special:Preferences]]. Offers user to hide/show categorization of pages.",
"tog-extendwatchlist": "[[Special:Preferences]], tab 'Watchlist'.
Offers user to show all applicable changes in watchlist (by default only the
last change to a page on the watchlist is shown). {{Gender}}",
"tog-usenewrc": "{{Gender}}\nUsed as label for the checkbox in
[[Special:Preferences]], tab \"Recent changes\".\n\nOffers user to use
alternative representation of [[Special:RecentChanges]] and watchlist.",
"tog-numberheadings": "[[Special:Preferences]], tab 'Misc'. Offers
numbered headings on content pages to user. {{Gender}}",
@@ -207,6 +208,7 @@
"tog-watchlisthideliu": "Option in tab 'Watchlist' of
[[Special:Preferences]]. {{Gender}}",
"tog-watchlisthideanons": "Option in tab 'Watchlist' of
[[Special:Preferences]]. {{Gender}}",
"tog-watchlisthidepatrolled": "Option in Watchlist tab of
[[Special:Preferences]]. {{Gender}}",
+ "tog-watchlisthidecategorization": "Option in Watchlist tab of
[[Special:Preferences]]. Offers user to hide/show categorization of pages.",
"tog-ccmeonemails": "Option in [[Special:Preferences]] >
{{int:prefs-personal}} > {{int:email}}. {{Gender}}",
"tog-diffonly": "Toggle option used in [[Special:Preferences]].
{{Gender}}",
"tog-showhiddencats": "Toggle option used in [[Special:Preferences]].
{{Gender}}",
@@ -1427,6 +1429,9 @@
"rcshowhidemine": "Option text in [[Special:RecentChanges]].
Parameters:\n* $1 - the \"show/hide\" command, with the text taken from either
{{msg-mw|rcshowhidemine-show}} or {{msg-mw|rcshowhidemine-hide}}",
"rcshowhidemine-show": "{{doc-actionlink}}\nOption text in
[[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhidemine}}.\n\nSee
also:\n* {{msg-mw|rcshowhidemine-hide}}\n{{Identical|show}}",
"rcshowhidemine-hide": "{{doc-actionlink}}\nOption text in
[[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhidemine}}.\n\nSee
also:\n* {{msg-mw|rcshowhidemine-show}}\n{{Identical|hide}}",
+ "rcshowhidecategorization": "Option text in [[Special:RecentChanges]].
Parameters:\n* $1 - the \"show/hide\" command, with the text taken from either
{{msg-mw|rcshowhidecategorization-show}} or
{{msg-mw|rcshowhidecategorization-hide}}",
+ "rcshowhidecategorization-show": "{{doc-actionlink}}\nOption text in
[[Special:RecentChanges]] in conjunction with
{{msg-mw|rcshowhidecategorization}}.\n\nSee also:\n*
{{msg-mw|rcshowhidecategorization-hide}}\n{{Identical|show}}",
+ "rcshowhidecategorization-hide": "{{doc-actionlink}}\nOption text in
[[Special:RecentChanges]] in conjunction with
{{msg-mw|rcshowhidecategorization}}.\n\nSee also:\n*
{{msg-mw|rcshowhidecategorization-show}}\n{{Identical|hide}}",
"rclinks": "Used on [[Special:RecentChanges]].\n* $1 - a list of
different choices with number of pages to be shown.<br /> Example:
\"''50{{int:pipe-separator}}100{{int:pipe-separator}}250{{int:pipe-separator}}500\".\n*
$2 - a list of clickable links with a number of days for which recent changes
are to be displayed.<br /> Example:
\"''1{{int:pipe-separator}}3{{int:pipe-separator}}7{{int:pipe-separator}}14{{int:pipe-separator}}30''\".\n*
$3 - a block of text that consists of other messages.<br /> Example:
\"''Hide minor edits{{int:pipe-separator}}Show bots{{int:pipe-separator}}Hide
anonymous users{{int:pipe-separator}}Hide logged-in
users{{int:pipe-separator}}Hide patrolled edits{{int:pipe-separator}}Hide my
edits''\"\nList elements are separated by {{msg-mw|Pipe-separator}} each. Each
list element is, or contains, a link.",
"diff": "Short form of \"differences\". Used on
[[Special:RecentChanges]], [[Special:Watchlist]], ...\n{{Identical|Diff}}",
"hist": "Short form of \"history\". Used on [[Special:RecentChanges]],
[[Special:Watchlist]], ...",
@@ -1453,6 +1458,10 @@
"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 embedded by other pages removed from a category\n\nParameters:\n*
$1 - name of the page being added\n* $2 - number of additional pages being
affected",
"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/EnhancedChangesListTest.php
b/tests/phpunit/includes/changes/EnhancedChangesListTest.php
index a14a50d..01e221f 100644
--- a/tests/phpunit/includes/changes/EnhancedChangesListTest.php
+++ b/tests/phpunit/includes/changes/EnhancedChangesListTest.php
@@ -74,6 +74,20 @@
$this->assertEquals( '', $html );
}
+ public function testCategorizationLineFormatting() {
+ $html = $this->createCategorizationLine(
+ $this->getCategorizationChange( '20150629191735', 0, 0 )
+ );
+ $this->assertNotContains( '(diff | hist)', strip_tags( $html )
);
+ }
+
+ public function testCategorizationLineFormattingWithRevision() {
+ $html = $this->createCategorizationLine(
+ $this->getCategorizationChange( '20150629191735', 1025,
1024 )
+ );
+ $this->assertContains( '(diff | hist)', strip_tags( $html ) );
+ }
+
/**
* @todo more tests for actual formatting, this is more of a smoke test
*/
@@ -116,6 +130,24 @@
}
/**
+ * @return RecentChange
+ */
+ private function getCategorizationChange( $timestamp, $thisId, $lastId
) {
+ $wikiPage = new WikiPage( Title::newFromText( 'Testpage' ) );
+ $wikiPage->doEditContent( new WikitextContent( 'Some random
text' ), 'page created' );
+
+ $wikiPage = new WikiPage( Title::newFromText( 'Category:Foo' )
);
+ $wikiPage->doEditContent( new WikitextContent( 'Some random
text' ), 'category page created' );
+
+ $user = $this->getTestUser();
+ $recentChange =
$this->testRecentChangesHelper->makeCategorizationRecentChange(
+ $user, 'Category:Foo', $wikiPage->getId(), $thisId,
$lastId, $timestamp
+ );
+
+ return $recentChange;
+ }
+
+ /**
* @return User
*/
private function getTestUser() {
@@ -128,4 +160,15 @@
return $user;
}
+ private function createCategorizationLine( $recentChange ) {
+ $enhancedChangesList = $this->newEnhancedChangesList();
+ $cacheEntry = $this->testRecentChangesHelper->getCacheEntry(
$recentChange );
+
+ $reflection = new \ReflectionClass( get_class(
$enhancedChangesList ) );
+ $method = $reflection->getMethod( 'recentChangesBlockLine' );
+ $method->setAccessible( true );
+
+ return $method->invokeArgs( $enhancedChangesList, array(
$cacheEntry ) );
+ }
+
}
diff --git a/tests/phpunit/includes/changes/TestRecentChangesHelper.php
b/tests/phpunit/includes/changes/TestRecentChangesHelper.php
index 2506087..e59825a 100644
--- a/tests/phpunit/includes/changes/TestRecentChangesHelper.php
+++ b/tests/phpunit/includes/changes/TestRecentChangesHelper.php
@@ -97,6 +97,36 @@
return $change;
}
+ public function getCacheEntry( $recentChange ) {
+ $rcCacheFactory = new RCCacheEntryFactory(
+ new RequestContext(),
+ array( 'diff' => 'diff', 'cur' => 'cur', 'last' =>
'last' )
+ );
+ return $rcCacheFactory->newFromRecentChange( $recentChange,
false );
+ }
+
+ public function makeCategorizationRecentChange(
+ User $user, $titleText, $curid, $thisid, $lastid, $timestamp
+ ) {
+
+ $attribs = array_merge(
+ $this->getDefaultAttributes( $titleText, $timestamp ),
+ array(
+ 'rc_type' => RC_CATEGORIZE,
+ 'rc_user' => $user->getId(),
+ 'rc_user_text' => $user->getName(),
+ 'rc_this_oldid' => $thisid,
+ 'rc_last_oldid' => $lastid,
+ 'rc_cur_id' => $curid,
+ 'rc_comment' => '[[:Testpage]] added to
category',
+ 'rc_old_len' => 0,
+ 'rc_new_len' => 0,
+ )
+ );
+
+ return $this->makeRecentChange( $attribs, 0, 0 );
+ }
+
private function getDefaultAttributes( $titleText, $timestamp ) {
return array(
'rc_id' => 545,
diff --git a/tests/phpunit/includes/deferred/LinksUpdateTest.php
b/tests/phpunit/includes/deferred/LinksUpdateTest.php
index 02f6b2a..efbfe6f 100644
--- a/tests/phpunit/includes/deferred/LinksUpdateTest.php
+++ b/tests/phpunit/includes/deferred/LinksUpdateTest.php
@@ -19,7 +19,8 @@
'externallinks',
'imagelinks',
'templatelinks',
- 'iwlinks'
+ 'iwlinks',
+ 'recentchanges',
)
);
}
@@ -39,6 +40,12 @@
'iw_wikiid' => 'linksupdatetest',
)
);
+ }
+
+ public function addDBData() {
+ $this->insertPage( 'Testing' );
+ $this->insertPage( 'Some_other_page' );
+ $this->insertPage( 'Template:TestingTemplate' );
}
protected function makeTitleAndParserOutput( $name, $id ) {
@@ -131,6 +138,61 @@
$this->assertLinksUpdate( $t, $po, 'categorylinks', 'cl_to,
cl_sortkey', 'cl_from = 111', array(
array( 'Foo', "FOO\nTESTING" ),
) );
+ }
+
+ public function
testOnAddingAndRemovingCategory_recentChangesRowIsAdded() {
+ $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' );
+
+ $title = Title::newFromText( 'Testing' );
+ $wikiPage = new WikiPage( $title );
+ $wikiPage->doEditContent( new WikitextContent(
'[[Category:Foo]]' ), 'added category' );
+
+ $this->assertRecentChangeByCategorization(
+ $title,
+ $wikiPage->getParserOutput( new ParserOptions() ),
+ Title::newFromText( 'Category:Foo' ),
+ array( array( 'Foo', '[[:Testing]] added to category' )
)
+ );
+
+ $wikiPage->doEditContent( new WikitextContent(
'[[Category:Bar]]' ), 'added category' );
+ $this->assertRecentChangeByCategorization(
+ $title,
+ $wikiPage->getParserOutput( new ParserOptions() ),
+ Title::newFromText( 'Category:Foo' ),
+ array(
+ array( 'Foo', '[[:Testing]] added to category'
),
+ array( 'Foo', '[[:Testing]] removed from
category' ),
+ )
+ );
+
+ $this->assertRecentChangeByCategorization(
+ $title,
+ $wikiPage->getParserOutput( new ParserOptions() ),
+ Title::newFromText( 'Category:Bar' ),
+ array(
+ array( 'Bar', '[[:Testing]] added to category'
),
+ )
+ );
+ }
+
+ public function
testOnAddingAndRemovingCategoryToTemplates_embeddingPagesAreIgnored() {
+ $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' );
+
+ $templateTitle = Title::newFromText( 'Template:TestingTemplate'
);
+ $templatePage = new WikiPage( $templateTitle );
+
+ $wikiPage = new WikiPage( Title::newFromText( 'Testing' ) );
+ $wikiPage->doEditContent( new WikitextContent(
'{{TestingTemplate}}' ), 'added template' );
+ $otherWikiPage = new WikiPage( Title::newFromText(
'Some_other_page' ) );
+ $otherWikiPage->doEditContent( new WikitextContent(
'{{TestingTemplate}}' ), 'added template' );
+ $templatePage->doEditContent( new WikitextContent(
'[[Category:Foo]]' ), 'added category' );
+
+ $this->assertRecentChangeByCategorization(
+ $templateTitle,
+ $templatePage->getParserOutput( new ParserOptions() ),
+ Title::newFromText( 'Foo' ),
+ array( array( 'Foo', '[[:Template:TestingTemplate]] and
2 pages added to category' ) )
+ );
}
/**
@@ -263,4 +325,26 @@
$this->assertSelect( $table, $fields, $condition, $expectedRows
);
return $update;
}
+
+ protected function assertRecentChangeByCategorization(
+ Title $pageTitle, ParserOutput $parserOutput, Title
$categoryTitle, $expectedRows
+ ) {
+ $update = new LinksUpdate( $pageTitle, $parserOutput );
+ $revision = Revision::newFromTitle( $pageTitle );
+ $update->setRevision( $revision );
+ $update->beginTransaction();
+ $update->doUpdate();
+ $update->commitTransaction();
+
+ $this->assertSelect(
+ 'recentchanges',
+ 'rc_title, rc_comment',
+ array(
+ 'rc_type' => RC_CATEGORIZE,
+ 'rc_namespace' => NS_CATEGORY,
+ 'rc_title' => $categoryTitle->getDBkey()
+ ),
+ $expectedRows
+ );
+ }
}
--
To view, visit https://gerrit.wikimedia.org/r/211526
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I5a89d8f19804b1120f4c755d834e2da6ca12ceae
Gerrit-PatchSet: 20
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Kai Nissen (WMDE) <[email protected]>
Gerrit-Reviewer: Aaron Schulz <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Kai Nissen (WMDE) <[email protected]>
Gerrit-Reviewer: Kaldari <[email protected]>
Gerrit-Reviewer: Legoktm <[email protected]>
Gerrit-Reviewer: Mattflaschen <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: Tobias Gritschacher <[email protected]>
Gerrit-Reviewer: WMDE-Fisch <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits