Http::getProxy() method to get proxy configuration
[lhc/web/wiklou.git] / includes / changes / CategoryMembershipChange.php
index 8bc4cf5..b4086f9 100644 (file)
  *
  * @file
  * @author Kai Nissen
- * @since 1.26
+ * @author Adam Shorland
+ * @since 1.27
  */
 
+use Wikimedia\Assert\Assert;
+
 class CategoryMembershipChange {
 
        const CATEGORY_ADDITION = 1;
        const CATEGORY_REMOVAL = -1;
 
-       /** @var string Current timestamp, set during CategoryMembershipChange::__construct() */
+       /**
+        * @var string Current timestamp, set during CategoryMembershipChange::__construct()
+        */
        private $timestamp;
 
-       /** @var Title Title instance of the categorized page */
+       /**
+        * @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 */
+       /**
+        * @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()
+        * Number of pages this WikiPage is embedded by
+        * Set by CategoryMembershipChange::checkTemplateLinks()
         */
        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
+        * @var callable|null
         */
-       private $correspondingRC;
+       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->page = WikiPage::factory( $pageTitle );
                $this->timestamp = wfTimestampNow();
+               $this->revision = $revision;
+               $this->newForCategorizationCallback = array( 'RecentChange', 'newForCategorization' );
+       }
 
-               # 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 );
+       /**
+        * 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 setRecursive() {
+       public function checkTemplateLinks() {
                $this->numTemplateLinks = $this->pageTitle->getBacklinkCache()->getNumLinks( 'templatelinks' );
        }
 
        /**
         * Create a recentchanges entry for category additions
-        * @param string $categoryName
+        *
+        * @param Title $categoryTitle
         */
-       public function pageAddedToCategory( $categoryName ) {
-               $this->createRecentChangesEntry( $categoryName, self::CATEGORY_ADDITION );
+       public function triggerCategoryAddedNotification( Title $categoryTitle ) {
+               $this->createRecentChangesEntry( $categoryTitle, self::CATEGORY_ADDITION );
        }
 
        /**
         * Create a recentchanges entry for category removals
-        * @param string $categoryName
+        *
+        * @param Title $categoryTitle
         */
-       public function pageRemovedFromCategory( $categoryName ) {
-               $this->createRecentChangesEntry( $categoryName, self::CATEGORY_REMOVAL );
+       public function triggerCategoryRemovedNotification( Title $categoryTitle ) {
+               $this->createRecentChangesEntry( $categoryTitle, self::CATEGORY_REMOVAL );
        }
 
        /**
         * Create a recentchanges entry using RecentChange::notifyCategorization()
-        * @param string $categoryName
+        *
+        * @param Title $categoryTitle
         * @param int $type
         */
-       private function createRecentChangesEntry( $categoryName, $type ) {
-               $categoryTitle = Title::newFromText( $categoryName, NS_CATEGORY );
-               if ( !$categoryTitle ) {
-                       return;
-               }
+       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
+               );
+       }
 
-               $previousRevTimestamp = $this->getPreviousRevisionTimestamp();
-               $unpatrolled = $this->revision ? $this->revision->isUnpatrolled() : 0;
+       /**
+        * @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;
-               $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' ) ?: '';
+
+               # 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;
+                       }
                }
 
-               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
+               $rc = call_user_func_array(
+                       $this->newForCategorizationCallback,
+                       array(
+                               $timestamp,
+                               $categoryTitle,
+                               $user,
+                               $comment,
+                               $pageTitle,
+                               $lastRevId,
+                               $newRevId,
+                               $lastTimestamp,
+                               $bot,
+                               $ip,
+                               $deleted
+                       )
                );
+               $rc->save();
        }
 
        /**
-        * Get the user who created the revision. may be an anonymous IP
-        * @return User
+        * 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 getRevisionUser() {
-               $userId = $this->revision->getUser( Revision::RAW );
-               if ( $userId === 0 ) {
-                       return User::newFromName( $this->revision->getUserText( Revision::RAW ), false );
-               } else {
-                       return User::newFromId( $userId );
+       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;
        }
 
        /**
@@ -167,21 +240,17 @@ class CategoryMembershipChange {
         * @param int $type may be CategoryMembershipChange::CATEGORY_ADDITION
         * or CategoryMembershipChange::CATEGORY_REMOVAL
         * @param array $params
-        * - prefixedUrl: result of Title::->getPrefixedURL()
-        * - numTemplateLinks
+        * - prefixedText: result of Title::->getPrefixedText()
+        *
         * @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;
-               }
+       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';
@@ -193,11 +262,13 @@ class CategoryMembershipChange {
        /**
         * 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() );
+               $previousRev = Revision::newFromId(
+                               $this->pageTitle->getPreviousRevisionID( $this->pageTitle->getLatestRevID() )
+                       );
 
                return $previousRev ? $previousRev->getTimestamp() : null;
        }