123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- <?php
- use MediaWiki\MediaWikiServices;
- use MediaWiki\Revision\RevisionRecord;
- /**
- * 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 Addshore
- * @since 1.27
- */
- 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::checkTemplateLinks()
- */
- private $numTemplateLinks = 0;
- /**
- * @var callable|null
- */
- private $newForCategorizationCallback = null;
- /**
- * @param Title $pageTitle Title instance of the categorized page
- * @param Revision|null $revision Latest Revision instance of the categorized page
- *
- * @throws MWException
- */
- public function __construct( Title $pageTitle, Revision $revision = null ) {
- $this->pageTitle = $pageTitle;
- if ( $revision === null ) {
- $this->timestamp = wfTimestampNow();
- } else {
- $this->timestamp = $revision->getTimestamp();
- }
- $this->revision = $revision;
- $this->newForCategorizationCallback = [ RecentChange::class, '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( callable $callback ) {
- if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
- throw new MWException( 'Cannot override newForCategorization callback in operation.' );
- }
- $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,
- $this->pageTitle->getPrefixedText(),
- $this->numTemplateLinks
- ),
- $this->pageTitle,
- $this->getPreviousRevisionTimestamp(),
- $this->revision,
- $type === self::CATEGORY_ADDITION
- );
- }
- /**
- * @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|null $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
- * @param bool $added true, if the category was added, false for removed
- *
- * @throws MWException
- */
- private function notifyCategorization(
- $timestamp,
- Title $categoryTitle,
- User $user = null,
- $comment,
- Title $pageTitle,
- $lastTimestamp,
- $revision,
- $added
- ) {
- $deleted = $revision ? $revision->getVisibility() & RevisionRecord::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 replica DB lag
- */
- $bot = 1;
- $lastRevId = 0;
- $ip = '';
- # If no revision is given, the change was probably triggered by parser functions
- if ( $revision !== null ) {
- $correspondingRc = $this->revision->getRecentChange();
- if ( $correspondingRc === null ) {
- $correspondingRc = $this->revision->getRecentChange( Revision::READ_LATEST );
- }
- if ( $correspondingRc !== null ) {
- $bot = $correspondingRc->getAttribute( 'rc_bot' ) ?: 0;
- $ip = $correspondingRc->getAttribute( 'rc_ip' ) ?: '';
- $lastRevId = $correspondingRc->getAttribute( 'rc_last_oldid' ) ?: 0;
- }
- }
- /** @var RecentChange $rc */
- $rc = ( $this->newForCategorizationCallback )(
- $timestamp,
- $categoryTitle,
- $user,
- $comment,
- $pageTitle,
- $lastRevId,
- $newRevId,
- $lastTimestamp,
- $bot,
- $ip,
- $deleted,
- $added
- );
- $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( RevisionRecord::RAW );
- if ( $userId === 0 ) {
- return User::newFromName( $this->revision->getUserText( RevisionRecord::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 string $prefixedText result of Title::->getPrefixedText()
- * @param int $numTemplateLinks
- *
- * @return string
- */
- private function getChangeMessageText( $type, $prefixedText, $numTemplateLinks ) {
- $array = [
- self::CATEGORY_ADDITION => 'recentchanges-page-added-to-category',
- self::CATEGORY_REMOVAL => 'recentchanges-page-removed-from-category',
- ];
- $msgKey = $array[$type];
- if ( intval( $numTemplateLinks ) > 0 ) {
- $msgKey .= '-bundled';
- }
- return wfMessage( $msgKey, $prefixedText )->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() {
- $rl = MediaWikiServices::getInstance()->getRevisionLookup();
- $latestRev = $rl->getRevisionByTitle( $this->pageTitle );
- if ( $latestRev ) {
- $previousRev = $rl->getPreviousRevision( $latestRev );
- if ( $previousRev ) {
- return $previousRev->getTimestamp();
- }
- }
- return null;
- }
- }
|