Merge "Add isCurrentWikiId()/isCurrentWikiDomain()/getCurrentWikiDomain() to WikiMap"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 29 Oct 2018 22:27:15 +0000 (22:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 29 Oct 2018 22:27:15 +0000 (22:27 +0000)
1  2 
includes/MediaWiki.php
includes/jobqueue/JobQueueGroup.php
includes/mail/UserMailer.php
includes/page/WikiPage.php
includes/skins/Skin.php
includes/specials/SpecialUserrights.php
includes/user/User.php

diff --combined includes/MediaWiki.php
@@@ -43,7 -43,7 +43,7 @@@ class MediaWiki 
        private $config;
  
        /**
 -       * @var String Cache what action this request is
 +       * @var string Cache what action this request is
         */
        private $action;
  
         */
        private static function getUrlDomainDistance( $url ) {
                $clusterWiki = WikiMap::getWikiFromUrl( $url );
-               if ( $clusterWiki === wfWikiID() ) {
+               if ( WikiMap::isCurrentWikiId( $clusterWiki ) ) {
                        return 'local'; // the current wiki
-               } elseif ( $clusterWiki !== false ) {
+               }
+               if ( $clusterWiki !== false ) {
                        return 'remote'; // another wiki in this cluster/farm
                }
  
         * @since 1.26
         */
        public function doPostOutputShutdown( $mode = 'normal' ) {
 +              // Record backend request timing
 +              $timing = $this->context->getTiming();
 +              $timing->mark( 'requestShutdown' );
 +
                // Perform the last synchronous operations...
                try {
 -                      // Record backend request timing
 -                      $timing = $this->context->getTiming();
 -                      $timing->mark( 'requestShutdown' );
                        // Show visible profiling data if enabled (which cannot be post-send)
                        Profiler::instance()->logDataPageOutputOnly();
                } catch ( Exception $e ) {
                        __METHOD__
                );
  
 -              // Important: this must be the last deferred update added (T100085, T154425)
 -              DeferredUpdates::addCallableUpdate( [ JobQueueGroup::class, 'pushLazyJobs' ] );
 -
                // Do any deferred jobs; preferring to run them now if a client will not wait on them
                DeferredUpdates::doUpdates( $blocksHttpClient ? 'enqueue' : 'run' );
  
@@@ -43,6 -43,9 +43,6 @@@ class JobQueueGroup 
        /** @var array Map of (bucket => (queue => JobQueue, types => list of types) */
        protected $coalescedQueues;
  
 -      /** @var Job[] */
 -      protected $bufferedJobs = [];
 -
        const TYPE_DEFAULT = 1; // integer; jobs popped by default
        const TYPE_ANY = 2; // integer; any job
  
@@@ -75,7 -78,7 +75,7 @@@
                        self::$instances[$wiki] = new self( $wiki, wfConfiguredReadOnlyReason() );
                        // Make sure jobs are not getting pushed to bogus wikis. This can confuse
                        // the job runner system into spawning endless RPC requests that fail (T171371).
-                       if ( $wiki !== wfWikiID() && !in_array( $wiki, $wgLocalDatabases ) ) {
+                       if ( !WikiMap::isCurrentWikiId( $wiki ) && !in_array( $wiki, $wgLocalDatabases ) ) {
                                self::$instances[$wiki]->invalidWiki = true;
                        }
                }
                global $wgJobTypeConf;
  
                $conf = [ 'wiki' => $this->wiki, 'type' => $type ];
 -              if ( isset( $wgJobTypeConf[$type] ) ) {
 -                      $conf = $conf + $wgJobTypeConf[$type];
 -              } else {
 -                      $conf = $conf + $wgJobTypeConf['default'];
 -              }
 +              $conf += $wgJobTypeConf[$type] ?? $wgJobTypeConf['default'];
                $conf['aggregator'] = JobQueueAggregator::singleton();
                if ( !isset( $conf['readOnlyReason'] ) ) {
                        $conf['readOnlyReason'] = $this->readOnlyReason;
                // Throw errors now instead of on push(), when other jobs may be buffered
                $this->assertValidJobs( $jobs );
  
 -              $this->bufferedJobs = array_merge( $this->bufferedJobs, $jobs );
 +              DeferredUpdates::addUpdate( new JobQueueEnqueueUpdate( $this->wiki, $jobs ) );
        }
  
        /**
         *
         * @return void
         * @since 1.26
 +       * @deprecated Since 1.33 Not needed anymore
         */
        public static function pushLazyJobs() {
 -              foreach ( self::$instances as $group ) {
 -                      try {
 -                              $group->push( $group->bufferedJobs );
 -                              $group->bufferedJobs = [];
 -                      } catch ( Exception $e ) {
 -                              // Get in as many jobs as possible and let other post-send updates happen
 -                              MWExceptionHandler::logException( $e );
 -                      }
 -              }
 +              wfDeprecated( __METHOD__, '1.33' );
        }
  
        /**
         */
        private function getCachedConfigVar( $name ) {
                // @TODO: cleanup this whole method with a proper config system
-               if ( $this->wiki === wfWikiID() ) {
+               if ( WikiMap::isCurrentWikiId( $this->wiki ) ) {
                        return $GLOBALS[$name]; // common case
                } else {
                        $wiki = $this->wiki;
                        }
                }
        }
 -
 -      function __destruct() {
 -              $n = count( $this->bufferedJobs );
 -              if ( $n > 0 ) {
 -                      $type = implode( ', ', array_unique( array_map( 'get_class', $this->bufferedJobs ) ) );
 -                      trigger_error( __METHOD__ . ": $n buffered job(s) of type(s) $type never inserted." );
 -              }
 -      }
  }
@@@ -82,7 -82,8 +82,8 @@@ class UserMailer 
        static function makeMsgId() {
                global $wgSMTP, $wgServer;
  
-               $msgid = uniqid( wfWikiID() . ".", true ); /* true required for cygwin */
+               $domainId = WikiMap::getCurrentWikiDomain()->getId();
+               $msgid = uniqid( $domainId . ".", true /** for cygwin */ );
                if ( is_array( $wgSMTP ) && isset( $wgSMTP['IDHost'] ) && $wgSMTP['IDHost'] ) {
                        $domain = $wgSMTP['IDHost'];
                } else {
                        try {
                                foreach ( $to as $recip ) {
                                        $sent = mail(
 -                                              $recip,
 +                                              $recip->toString(),
                                                self::quotedPrintable( $subject ),
                                                $body,
                                                $headers,
@@@ -1045,22 -1045,20 +1045,22 @@@ class WikiPage implements Page, IDBAcce
                $dbw->startAtomic( __METHOD__ );
  
                if ( !$oldLatest || $oldLatest == $this->lockAndGetLatest() ) {
 +                      $contLang = MediaWikiServices::getInstance()->getContentLanguage();
 +                      $truncatedFragment = $contLang->truncateForDatabase( $rt->getFragment(), 255 );
                        $dbw->upsert(
                                'redirect',
                                [
                                        'rd_from' => $this->getId(),
                                        'rd_namespace' => $rt->getNamespace(),
                                        'rd_title' => $rt->getDBkey(),
 -                                      'rd_fragment' => $rt->getFragment(),
 +                                      'rd_fragment' => $truncatedFragment,
                                        'rd_interwiki' => $rt->getInterwiki(),
                                ],
                                [ 'rd_from' ],
                                [
                                        'rd_namespace' => $rt->getNamespace(),
                                        'rd_title' => $rt->getDBkey(),
 -                                      'rd_fragment' => $rt->getFragment(),
 +                                      'rd_fragment' => $truncatedFragment,
                                        'rd_interwiki' => $rt->getInterwiki(),
                                ],
                                __METHOD__
                        // in the job queue to avoid simultaneous deletion operations would add overhead.
                        // Number of archived revisions cannot be known beforehand, because edits can be made
                        // while deletion operations are being processed, changing the number of archivals.
 -                      $archivedRevisionCount = $dbw->selectRowCount(
 -                              'archive', '1', [ 'ar_page_id' => $id ], __METHOD__
 +                      $archivedRevisionCount = $dbw->selectField(
 +                              'archive', 'COUNT(*)',
 +                              [
 +                                      'ar_namespace' => $this->getTitle()->getNamespace(),
 +                                      'ar_title' => $this->getTitle()->getDBkey(),
 +                                      'ar_page_id' => $id
 +                              ], __METHOD__
                        );
  
                        // Clone the title and wikiPage, so we have the information we need when
                                // Do not include the namespace since there can be multiple aliases to it
                                // due to different namespace text definitions on different wikis. This only
                                // means that some cache invalidations happen that are not strictly needed.
-                               $cache->makeGlobalKey( 'interwiki-page', wfWikiID(), $title->getDBkey() )
+                               $cache->makeGlobalKey(
+                                       'interwiki-page',
+                                       WikiMap::getCurrentWikiDomain()->getId(),
+                                       $title->getDBkey()
+                               )
                        );
                } );
        }
diff --combined includes/skins/Skin.php
@@@ -342,7 -342,10 +342,7 @@@ abstract class Skin extends ContextSour
         * @return Title
         */
        public function getRelevantTitle() {
 -              if ( isset( $this->mRelevantTitle ) ) {
 -                      return $this->mRelevantTitle;
 -              }
 -              return $this->getTitle();
 +              return $this->mRelevantTitle ?? $this->getTitle();
        }
  
        /**
                        return $newMessagesAlert;
                }
  
-               if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
+               if ( count( $newtalks ) == 1 && WikiMap::isCurrentWikiId( $newtalks[0]['wiki'] ) ) {
                        $uTalkTitle = $user->getTalkPage();
                        $lastSeenRev = $newtalks[0]['rev'] ?? null;
                        $nofAuthors = 0;
         * @param string $section The designation of the section being pointed to,
         *   to be included in the link, like "&section=$section"
         * @param string|null $tooltip The tooltip to use for the link: will be escaped
 -       *   and wrapped in the 'editsectionhint' message.
 -       *   Not setting this parameter is deprecated.
 -       * @param Language|string $lang Language object or language code string.
 -       *   Type string is deprecated. Not setting this parameter is deprecated.
 +       *   and wrapped in the 'editsectionhint' message
 +       * @param Language $lang Language object
         * @return string HTML to use for edit link
         */
 -      public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
 +      public function doEditSectionLink( Title $nt, $section, $tooltip, Language $lang ) {
                // HTML generated here should probably have userlangattributes
                // added to it for LTR text on RTL pages
  
 -              if ( !$lang instanceof Language ) {
 -                      wfDeprecated( __METHOD__ . ' with other type than Language for $lang', '1.32' );
 -                      $lang = wfGetLangObj( $lang );
 -              }
 -
                $attribs = [];
                if ( !is_null( $tooltip ) ) {
                        $attribs['title'] = $this->msg( 'editsectionhint' )->rawParams( $tooltip )
@@@ -160,7 -160,7 +160,7 @@@ class UserrightsPage extends SpecialPag
  
                        // save settings
                        if ( !$fetchedStatus->isOK() ) {
 -                              $this->getOutput()->addWikiText( $fetchedStatus->getWikiText() );
 +                              $this->getOutput()->addWikiTextAsInterface( $fetchedStatus->getWikiText() );
  
                                return;
                        }
                                        return;
                                } else {
                                        // Print an error message and redisplay the form
 -                                      $out->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
 +                                      $out->wrapWikiTextAsInterface( 'error', $status->getWikiText() );
                                }
                        }
                }
        function editUserGroupsForm( $username ) {
                $status = $this->fetchUser( $username, true );
                if ( !$status->isOK() ) {
 -                      $this->getOutput()->addWikiText( $status->getWikiText() );
 +                      $this->getOutput()->addWikiTextAsInterface( $status->getWikiText() );
  
                        return;
                } else {
                $parts = explode( $this->getConfig()->get( 'UserrightsInterwikiDelimiter' ), $username );
                if ( count( $parts ) < 2 ) {
                        $name = trim( $username );
-                       $database = '';
+                       $wikiId = '';
                } else {
-                       list( $name, $database ) = array_map( 'trim', $parts );
+                       list( $name, $wikiId ) = array_map( 'trim', $parts );
  
-                       if ( $database == wfWikiID() ) {
-                               $database = '';
+                       if ( WikiMap::isCurrentWikiId( $wikiId ) ) {
+                               $wikiId = '';
                        } else {
                                if ( $writing && !$this->getUser()->isAllowed( 'userrights-interwiki' ) ) {
                                        return Status::newFatal( 'userrights-no-interwiki' );
                                }
-                               if ( !UserRightsProxy::validDatabase( $database ) ) {
-                                       return Status::newFatal( 'userrights-nodatabase', $database );
+                               if ( !UserRightsProxy::validDatabase( $wikiId ) ) {
+                                       return Status::newFatal( 'userrights-nodatabase', $wikiId );
                                }
                        }
                }
                        // We'll do a lookup for the name internally.
                        $id = intval( substr( $name, 1 ) );
  
-                       if ( $database == '' ) {
+                       if ( $wikiId == '' ) {
                                $name = User::whoIs( $id );
                        } else {
-                               $name = UserRightsProxy::whoIs( $database, $id );
+                               $name = UserRightsProxy::whoIs( $wikiId, $id );
                        }
  
                        if ( !$name ) {
                        }
                }
  
-               if ( $database == '' ) {
+               if ( $wikiId == '' ) {
                        $user = User::newFromName( $name );
                } else {
-                       $user = UserRightsProxy::newFromName( $database, $name );
+                       $user = UserRightsProxy::newFromName( $wikiId, $name );
                }
  
                if ( !$user || $user->isAnon() ) {
         * @param UserGroupMembership[] $usergroups Associative array of (group name as string =>
         *   UserGroupMembership object) for groups the user belongs to
         * @param User $user
 -       * @return Array with 2 elements: the XHTML table element with checkxboes, and
 +       * @return array Array with 2 elements: the XHTML table element with checkxboes, and
         * whether any groups are changeable
         */
        private function groupCheckboxes( $usergroups, $user ) {
diff --combined includes/user/User.php
@@@ -1817,7 -1817,11 +1817,7 @@@ class User implements IDBAccessObject, 
         */
        public static function getDefaultOption( $opt ) {
                $defOpts = self::getDefaultOptions();
 -              if ( isset( $defOpts[$opt] ) ) {
 -                      return $defOpts[$opt];
 -              } else {
 -                      return null;
 -              }
 +              return $defOpts[$opt] ?? null;
        }
  
        /**
         * Check if user is blocked from editing a particular article
         *
         * @param Title $title Title to check
 -       * @param bool $bFromSlave Whether to check the replica DB instead of the master
 +       * @param bool $fromSlave Whether to check the replica DB instead of the master
         * @return bool
         */
 -      public function isBlockedFrom( $title, $bFromSlave = false ) {
 -              global $wgBlockAllowsUTEdit;
 +      public function isBlockedFrom( $title, $fromSlave = false ) {
 +              $blocked = $this->isHidden();
  
 -              $blocked = $this->isBlocked( $bFromSlave );
 -              $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
 -              // If a user's name is suppressed, they cannot make edits anywhere
 -              if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
 -                      && $title->getNamespace() == NS_USER_TALK ) {
 -                      $blocked = false;
 -                      wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
 +              if ( !$blocked ) {
 +                      $block = $this->getBlock( $fromSlave );
 +                      if ( $block ) {
 +                              $blocked = $block->preventsEdit( $title );
 +                      }
                }
  
 +              // only for the purpose of the hook. We really don't need this here.
 +              $allowUsertalk = $this->mAllowUsertalk;
 +
                Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
  
                return $blocked;
         */
        public function isHidden() {
                if ( $this->mHideName !== null ) {
 -                      return $this->mHideName;
 +                      return (bool)$this->mHideName;
                }
                $this->getBlockedStatus();
                if ( !$this->mHideName ) {
                        $this->mHideName = $authUser && $authUser->isHidden();
                        Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
                }
 -              return $this->mHideName;
 +              return (bool)$this->mHideName;
        }
  
        /**
                        // and it is always for the same wiki, but we double-check here in
                        // case that changes some time in the future.
                        if ( count( $newMessageLinks ) === 1
-                               && $newMessageLinks[0]['wiki'] === wfWikiID()
+                               && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
                                && $newMessageLinks[0]['rev']
                        ) {
                                /** @var Revision $newMessageRevision */
  
                        if ( $count === null ) {
                                // it has not been initialized. do so.
 -                              $count = $this->initEditCount();
 +                              $count = $this->initEditCountInternal();
                        }
                        $this->mEditCount = $count;
                }
                return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
        }
  
 +      /**
 +       * Get whether the user is blocked from using Special:Upload
 +       *
 +       * @return bool
 +       */
 +      public function isBlockedFromUpload() {
 +              $this->getBlockedStatus();
 +              return $this->mBlock && $this->mBlock->prevents( 'upload' );
 +      }
 +
        /**
         * Get whether the user is allowed to create an account.
         * @return bool
        }
  
        /**
 -       * Deferred version of incEditCountImmediate()
 -       *
 -       * This function, rather than incEditCountImmediate(), should be used for
 -       * most cases as it avoids potential deadlocks caused by concurrent editing.
 +       * Schedule a deferred update to update the user's edit count
         */
        public function incEditCount() {
 -              wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
 -                      function () {
 -                              $this->incEditCountImmediate();
 -                      },
 -                      __METHOD__
 +              if ( $this->isAnon() ) {
 +                      return; // sanity
 +              }
 +
 +              DeferredUpdates::addUpdate(
 +                      new UserEditCountUpdate( $this, 1 ),
 +                      DeferredUpdates::POSTSEND
                );
        }
  
        /**
 -       * Increment the user's edit-count field.
 -       * Will have no effect for anonymous users.
 -       * @since 1.26
 +       * This method should not be called outside User/UserEditCountUpdate
 +       *
 +       * @param int $count
         */
 -      public function incEditCountImmediate() {
 -              if ( $this->isAnon() ) {
 -                      return;
 -              }
 -
 -              $dbw = wfGetDB( DB_MASTER );
 -              // No rows will be "affected" if user_editcount is NULL
 -              $dbw->update(
 -                      'user',
 -                      [ 'user_editcount=user_editcount+1' ],
 -                      [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
 -                      __METHOD__
 -              );
 -              // Lazy initialization check...
 -              if ( $dbw->affectedRows() == 0 ) {
 -                      // Now here's a goddamn hack...
 -                      $dbr = wfGetDB( DB_REPLICA );
 -                      if ( $dbr !== $dbw ) {
 -                              // If we actually have a replica DB server, the count is
 -                              // at least one behind because the current transaction
 -                              // has not been committed and replicated.
 -                              $this->mEditCount = $this->initEditCount( 1 );
 -                      } else {
 -                              // But if DB_REPLICA is selecting the master, then the
 -                              // count we just read includes the revision that was
 -                              // just added in the working transaction.
 -                              $this->mEditCount = $this->initEditCount();
 -                      }
 -              } else {
 -                      if ( $this->mEditCount === null ) {
 -                              $this->getEditCount();
 -                              $dbr = wfGetDB( DB_REPLICA );
 -                              $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
 -                      } else {
 -                              $this->mEditCount++;
 -                      }
 -              }
 -              // Edit count in user cache too
 -              $this->invalidateCache();
 +      public function setEditCountInternal( $count ) {
 +              $this->mEditCount = $count;
        }
  
        /**
         * Initialize user_editcount from data out of the revision table
         *
 -       * @param int $add Edits to add to the count from the revision table
 +       * This method should not be called outside User/UserEditCountUpdate
 +       *
         * @return int Number of edits
         */
 -      protected function initEditCount( $add = 0 ) {
 +      public function initEditCountInternal() {
                // Pull from a replica DB to be less cruel to servers
                // Accuracy isn't the point anyway here
                $dbr = wfGetDB( DB_REPLICA );
                        [],
                        $actorWhere['joins']
                );
 -              $count = $count + $add;
  
                $dbw = wfGetDB( DB_MASTER );
                $dbw->update(
                        'user',
                        [ 'user_editcount' => $count ],
 -                      [ 'user_id' => $this->getId() ],
 +                      [
 +                              'user_id' => $this->getId(),
 +                              'user_editcount IS NULL OR user_editcount < ' . (int)$count
 +                      ],
                        __METHOD__
                );