Merge "Improve docs for Title::getInternalURL/getCanonicalURL"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sun, 7 Apr 2019 17:25:33 +0000 (17:25 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sun, 7 Apr 2019 17:25:33 +0000 (17:25 +0000)
1  2 
docs/hooks.txt
includes/Title.php

diff --combined docs/hooks.txt
@@@ -787,6 -787,16 +787,6 @@@ $extraData: An array (string => string
    added to log contexts. Fields it might include:
    - appId: the application ID, only if the login was with a bot password
  
 -'AuthPluginAutoCreate': DEPRECATED since 1.27! Use the 'LocalUserCreated' hook
 -instead. Called when creating a local account for an user logged in from an
 -external authentication method.
 -$user: User object created locally
 -
 -'AuthPluginSetup': DEPRECATED since 1.27! Extensions should be updated to use
 -AuthManager. Update or replace authentication plugin object ($wgAuth). Gives a
 -chance for an extension to set it programmatically to a variable class.
 -&$auth: the $wgAuth object, probably a stub
 -
  'AutopromoteCondition': Check autopromote condition for user.
  $type: condition type
  $args: arguments
@@@ -1612,7 -1622,7 +1612,7 @@@ $out: OutputPage objec
  notifications.
  &$title: Title object of page
  &$url: string value as output (out parameter, can modify)
- $query: query options passed to Title::getCanonicalURL()
+ $query: query options as string passed to Title::getCanonicalURL()
  
  'GetContentModels': Add content models to the list of available models.
  &$models: array containing current model list, as strings. Extensions should add to this list.
@@@ -1650,7 -1660,7 +1650,7 @@@ $single: Only extract the current langu
  'GetFullURL': Modify fully-qualified URLs used in redirects/export/offsite data.
  &$title: Title object of page
  &$url: string value as output (out parameter, can modify)
- $query: query options passed to Title::getFullURL()
+ $query: query options as string passed to Title::getFullURL()
  
  'GetHumanTimestamp': Pre-emptively override the human-readable timestamp
  generated by MWTimestamp::getHumanTimestamp(). Return false in this hook to use
@@@ -1664,7 -1674,7 +1664,7 @@@ $lang: Language that will be used to re
  'GetInternalURL': Modify fully-qualified URLs used for squid cache purging.
  &$title: Title object of page
  &$url: string value as output (out parameter, can modify)
- $query: query options passed to Title::getInternalURL()
+ $query: query options as string passed to Title::getInternalURL()
  
  'GetIP': modify the ip of the current user (called only once).
  &$ip: string holding the ip as determined so far
@@@ -1689,7 -1699,7 +1689,7 @@@ be buggy for internal urls on render i
  hack that Title::getLocalURL uses in your own extension.
  &$title: Title object of page
  &$url: string value as output (out parameter, can modify)
- $query: query options passed to Title::getLocalURL()
+ $query: query options as string passed to Title::getLocalURL()
  
  'GetLocalURL::Article': Modify local URLs specifically pointing to article paths
  without any fancy queries or variants.
  'GetLocalURL::Internal': Modify local URLs to internal pages.
  &$title: Title object of page
  &$url: string value as output (out parameter, can modify)
- $query: query options passed to Title::getLocalURL()
+ $query: query options as string passed to Title::getLocalURL()
  
  'GetLogTypesOnUser': Add log types where the target is a userpage
  &$types: Array of log types
@@@ -2198,16 -2208,6 +2198,16 @@@ Special:LonelyPages
  'MagicWordwgVariableIDs': When defining new magic words IDs.
  &$variableIDs: array of strings
  
 +'MaintenanceUpdateAddParams': allow extensions to add params to the update.php
 +maintenance script.
 +&$params: array to populate with the params to be added. Array elements are keyed by
 +the param name. Each param is an associative array that must include the following keys:
 +  - desc The description of the param to show on --help
 +  - require Is the param required? Defaults to false if not set.
 +  - withArg Is an argument required with this option?  Defaults to false if not set.
 +  - shortName Character to use as short name, or false if none.  Defaults to false if not set.
 +  - multiOccurrence Can this option be passed multiple times?  Defaults to false if not set.
 +
  'MaintenanceRefreshLinksInit': before executing the refreshLinks.php maintenance
  script.
  $refreshLinks: RefreshLinks object
@@@ -2220,17 -2220,12 +2220,17 @@@ ResourceLoaderGetConfigVars instead
    Skin::makeVariablesScript
  $out: The OutputPage which called the hook, can be used to get the real title.
  
 +'ManualLogEntryBeforePublish': Allows to access or modify log entry just before it is
 +published.
 +$logEntry: ManualLogEntry object
 +
  'MarkPatrolled': Before an edit is marked patrolled.
  $rcid: ID of the revision to be marked patrolled
  &$user: the user (object) marking the revision as patrolled
  $wcOnlySysopsCanPatrol: config setting indicating whether the user needs to be a
    sysop in order to mark an edit patrolled.
  $auto: true if the edit is being marked as patrolled automatically
 +&$tags: the tags to be applied to the patrol log entry
  
  'MarkPatrolledComplete': After an edit is marked patrolled.
  $rcid: ID of the revision marked as patrolled
@@@ -2446,25 -2441,19 +2446,25 @@@ $flags: Flags passed to WikiPage::doEdi
  $revision: New Revision of the article
  
  'PageContentLanguage': Allows changing the language in which the content of a
 -page is written. Defaults to the wiki content language ($wgContLang).
 +page is written. Defaults to the wiki content language.
  $title: Title object
 -&$pageLang: the page content language (either an object or a language code)
 -$wgLang: the user language
 +&$pageLang: the page content language. Input can be anything (under control of
 +  hook subscribers), but hooks should return Language objects. Language code
 +  strings are deprecated.
 +$userLang: the user language (Language or StubUserLang object)
  
  'PageContentSave': Before an article is saved.
  $wikiPage: the WikiPage (object) being saved
  $user: the user (object) saving the article
  $content: the new article content, as a Content object
 -$summary: the article summary (comment)
 -$isminor: minor flag
 -$iswatch: watch flag
 -$section: section #
 +&$summary: CommentStoreComment object containing the edit comment. Can be replaced with a new one.
 +$isminor: Boolean flag specifying if the edit was marked as minor.
 +$iswatch: Previously a watch flag. Currently unused, always null.
 +$section: Previously the section number being edited. Currently unused, always null.
 +$flags: All EDIT_… flags (including EDIT_MINOR) as an integer number. See WikiPage::doEditContent
 +  documentation for flags' definition.
 +$status: StatusValue object for the hook handlers resulting status. Either set $status->fatal() or
 +  return false to abort the save action.
  
  'PageContentSaveComplete': After an article has been updated.
  $wikiPage: WikiPage modified
@@@ -2844,17 -2833,17 +2844,17 @@@ such as when responding to a resourc
  loader request or generating HTML output.
  &$resourceLoader: ResourceLoader object
  
 -'ResourceLoaderTestModules': Let you add new JavaScript testing modules. This is
 -called after the addition of 'qunit' and MediaWiki testing resources.
 -&$testModules: array of JavaScript testing modules. The 'qunit' framework,
 -  included in core, is fed using tests/qunit/QUnitTestResources.php.
 -  To add a new qunit module named 'myext.tests':
 -      $testModules['qunit']['myext.tests'] = [
 -              'script' => 'extension/myext/tests.js',
 -              'dependencies' => <any module dependency you might have>
 +'ResourceLoaderTestModules': DEPRECATED since 1.33! Register ResourceLoader modules
 +that are only available when `$wgEnableJavaScriptTest` is true. Use this for test
 +suites and other test-only resources.
 +&$testModules: one array of modules per test framework. The modules array
 +follows the same format as `$wgResourceModules`. For example:
 +      $testModules['qunit']['ext.Example.test'] = [
 +              'localBasePath' => __DIR__ . '/tests/qunit',
 +              'remoteExtPath' => 'Example/tests/qunit',
 +              'script' => [ 'tests/qunit/foo.js' ],
 +              'dependencies' => [ 'ext.Example.foo' ]
         ];
 -  For QUnit framework, the mediawiki.tests.qunit.testrunner dependency will be
 -  added to any module.
  &$ResourceLoader: object
  
  'RevisionDataUpdates': Called when constructing a list of DeferrableUpdate to be
@@@ -3848,23 -3837,6 +3848,23 @@@ the database) have been saved. Compare 
  called before.
  $user: The User for which the options have been saved
  
 +'UserSendConfirmationMail': Called just before a confirmation email is sent to
 +a user. Hook handlers can modify the email that will be sent.
 +$user: The User for which the confirmation email is going to be sent
 +&$mail: Associative array describing the email, with the following keys:
 +  - subject: Subject line of the email
 +  - body: Email body. Can be a string, or an array with keys 'text' and 'html'
 +  - from: User object, or null meaning $wgPasswordSender will be used
 +  - replyTo: MailAddress object or null
 +$info: Associative array with additional information:
 +  - type: 'created' if the user's account was just created; 'set' if the user
 +    set an email address when they previously didn't have one; 'changed' if
 +    the user had an email address and changed it
 +  - ip: The IP address from which the user set/changed their email address
 +  - confirmURL: URL the user should visit to confirm their email
 +  - invalidateURL: URL the user should visit to invalidate confirmURL
 +  - expiration: time and date when confirmURL expires
 +
  'UserSetCookies': DEPRECATED since 1.27! If you're trying to replace core
  session cookie handling, you want to create a subclass of
  MediaWiki\Session\CookieSessionProvider instead. Otherwise, you can no longer
diff --combined includes/Title.php
@@@ -22,7 -22,6 +22,7 @@@
   * @file
   */
  
 +use MediaWiki\Permissions\PermissionManager;
  use Wikimedia\Rdbms\Database;
  use Wikimedia\Rdbms\IDatabase;
  use MediaWiki\Linker\LinkTarget;
@@@ -38,8 -37,8 +38,8 @@@ use MediaWiki\MediaWikiServices
   *       and does not rely on global state or the database.
   */
  class Title implements LinkTarget, IDBAccessObject {
 -      /** @var MapCacheLRU */
 -      static private $titleCache = null;
 +      /** @var MapCacheLRU|null */
 +      private static $titleCache = null;
  
        /**
         * Title::newFromText maintains a cache to avoid expensive re-normalization of
         */
        const GAID_FOR_UPDATE = 1;
  
 +      /**
 +       * Flag for use with factory methods like newFromLinkTarget() that have
 +       * a $forceClone parameter. If set, the method must return a new instance.
 +       * Without this flag, some factory methods may return existing instances.
 +       *
 +       * @since 1.33
 +       */
 +      const NEW_CLONE = 'clone';
 +
        /**
         * @name Private member variables
         * Please use the accessor functions instead.
         * Only public to share cache with TitleFormatter
         *
         * @private
 -       * @var string
 +       * @var string|null
         */
        public $prefixedText = null;
  
         * the database or false if not loaded, yet. */
        private $mDbPageLanguage = false;
  
 -      /** @var TitleValue A corresponding TitleValue object */
 +      /** @var TitleValue|null A corresponding TitleValue object */
        private $mTitleValue = null;
  
 -      /** @var bool Would deleting this page be a big deletion? */
 +      /** @var bool|null Would deleting this page be a big deletion? */
        private $mIsBigDeletion = null;
        // @}
  
        }
  
        /**
 -       * @access protected
 +       * @protected
         */
        function __construct() {
        }
         * @return Title|null Title, or null on an error
         */
        public static function newFromDBkey( $key ) {
 -              $t = new Title();
 +              $t = new self();
                $t->mDbkeyform = $key;
  
                try {
        }
  
        /**
 -       * Create a new Title from a TitleValue
 +       * Returns a Title given a TitleValue.
 +       * If the given TitleValue is already a Title instance, that instance is returned,
 +       * unless $forceClone is "clone". If $forceClone is "clone" and the given TitleValue
 +       * is already a Title instance, that instance is copied using the clone operator.
         *
         * @param TitleValue $titleValue Assumed to be safe.
 +       * @param string $forceClone set to NEW_CLONE to ensure a fresh instance is returned.
         *
         * @return Title
         */
 -      public static function newFromTitleValue( TitleValue $titleValue ) {
 -              return self::newFromLinkTarget( $titleValue );
 +      public static function newFromTitleValue( TitleValue $titleValue, $forceClone = '' ) {
 +              return self::newFromLinkTarget( $titleValue, $forceClone );
        }
  
        /**
 -       * Create a new Title from a LinkTarget
 +       * Returns a Title given a LinkTarget.
 +       * If the given LinkTarget is already a Title instance, that instance is returned,
 +       * unless $forceClone is "clone". If $forceClone is "clone" and the given LinkTarget
 +       * is already a Title instance, that instance is copied using the clone operator.
         *
         * @param LinkTarget $linkTarget Assumed to be safe.
 +       * @param string $forceClone set to NEW_CLONE to ensure a fresh instance is returned.
         *
         * @return Title
         */
 -      public static function newFromLinkTarget( LinkTarget $linkTarget ) {
 +      public static function newFromLinkTarget( LinkTarget $linkTarget, $forceClone = '' ) {
                if ( $linkTarget instanceof Title ) {
                        // Special case if it's already a Title object
 -                      return $linkTarget;
 +                      if ( $forceClone === self::NEW_CLONE ) {
 +                              return clone $linkTarget;
 +                      } else {
 +                              return $linkTarget;
 +                      }
                }
                return self::makeTitle(
                        $linkTarget->getNamespace(),
         * Title objects returned by this method are guaranteed to be valid, and
         * thus return true from the isValid() method.
         *
 +       * @note The Title instance returned by this method is not guaranteed to be a fresh instance.
 +       * It may instead be a cached instance created previously, with references to it remaining
 +       * elsewhere.
 +       *
         * @param string|int|null $text The link text; spaces, prefixes, and an
         *   initial ':' indicating the main namespace are accepted.
         * @param int $defaultNamespace The namespace to use if none is specified
                }
  
                try {
 -                      return self::newFromTextThrow( strval( $text ), $defaultNamespace );
 +                      return self::newFromTextThrow( (string)$text, $defaultNamespace );
                } catch ( MalformedTitleException $ex ) {
                        return null;
                }
         * Title objects returned by this method are guaranteed to be valid, and
         * thus return true from the isValid() method.
         *
 +       * @note The Title instance returned by this method is not guaranteed to be a fresh instance.
 +       * It may instead be a cached instance created previously, with references to it remaining
 +       * elsewhere.
 +       *
         * @see Title::newFromText
         *
         * @since 1.25
  
                $t = new Title();
                $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
 -              $t->mDefaultNamespace = intval( $defaultNamespace );
 +              $t->mDefaultNamespace = (int)$defaultNamespace;
  
                $t->secureAndSplit();
                if ( $defaultNamespace == NS_MAIN ) {
         * @return MapCacheLRU
         */
        private static function getTitleCache() {
 -              if ( self::$titleCache == null ) {
 +              if ( self::$titleCache === null ) {
                        self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
                }
                return self::$titleCache;
                } else {
                        $title = null;
                }
 +
                return $title;
        }
  
                                $this->mLatestID = (int)$row->page_latest;
                        }
                        if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
 -                              $this->mContentModel = strval( $row->page_content_model );
 +                              $this->mContentModel = (string)$row->page_content_model;
                        } elseif ( !$this->mForcedContentModel ) {
                                $this->mContentModel = false; # initialized lazily in getContentModel()
                        }
                $t = new Title();
                $t->mInterwiki = $interwiki;
                $t->mFragment = $fragment;
 -              $t->mNamespace = $ns = intval( $ns );
 +              $t->mNamespace = $ns = (int)$ns;
                $t->mDbkeyform = strtr( $title, ' ', '_' );
                $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
                $t->mUrlform = wfUrlencode( $t->mDbkeyform );
        /**
         * Create a new Title for the Main Page
         *
 +       * @note The Title instance returned by this method is not guaranteed to be a fresh instance.
 +       * It may instead be a cached instance created previously, with references to it remaining
 +       * elsewhere.
 +       *
         * @return Title The new object
         */
        public static function newMainPage() {
                                // Allow unicode if a single high-bit character appears
                                $r0 = sprintf( '\x%02x', $ord0 );
                                $allowUnicode = true;
 +                              // @phan-suppress-next-line PhanParamSuspiciousOrder false positive
                        } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
                                $r0 = '\\' . $d0;
                        } else {
                return $name;
        }
  
 -      /**
 -       * Escape a text fragment, say from a link, for a URL
 -       *
 -       * @deprecated since 1.30, use Sanitizer::escapeIdForLink() or escapeIdForExternalInterwiki()
 -       *
 -       * @param string $fragment Containing a URL or link fragment (after the "#")
 -       * @return string Escaped string
 -       */
 -      static function escapeFragmentForURL( $fragment ) {
 -              wfDeprecated( __METHOD__, '1.30' );
 -              # Note that we don't urlencode the fragment.  urlencoded Unicode
 -              # fragments appear not to work in IE (at least up to 7) or in at least
 -              # one version of Opera 9.x.  The W3C validator, for one, doesn't seem
 -              # to care if they aren't encoded.
 -              return Sanitizer::escapeId( $fragment, 'noninitial' );
 -      }
 -
        /**
         * Callback for usort() to do title sorts by (namespace, title)
         *
                        getNsText( MWNamespace::getTalk( $this->mNamespace ) );
        }
  
 -      /**
 -       * Can this title have a corresponding talk page?
 -       *
 -       * @deprecated since 1.30, use canHaveTalkPage() instead.
 -       *
 -       * @return bool True if this title either is a talk page or can have a talk page associated.
 -       */
 -      public function canTalk() {
 -              return $this->canHaveTalkPage();
 -      }
 -
        /**
         * Can this title have a corresponding talk page?
         *
                );
        }
  
 -      /**
 -       * @return bool
 -       * @deprecated Since 1.31; use ::isSiteConfigPage() instead (which also checks for JSON pages)
 -       */
 -      public function isCssOrJsPage() {
 -              wfDeprecated( __METHOD__, '1.31' );
 -              return ( NS_MEDIAWIKI == $this->mNamespace
 -                              && ( $this->hasContentModel( CONTENT_MODEL_CSS )
 -                                      || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
 -      }
 -
        /**
         * Is this a "config" (.css, .json, or .js) sub-page of a user page?
         *
                );
        }
  
 -      /**
 -       * @return bool
 -       * @deprecated Since 1.31; use ::isUserConfigPage() instead (which also checks for JSON pages)
 -       */
 -      public function isCssJsSubpage() {
 -              wfDeprecated( __METHOD__, '1.31' );
 -              return ( NS_USER == $this->mNamespace && $this->isSubpage()
 -                              && ( $this->hasContentModel( CONTENT_MODEL_CSS )
 -                                      || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
 -      }
 -
        /**
         * Trim down a .css, .json, or .js subpage title to get the corresponding skin name
         *
                return substr( $subpage, 0, $lastdot );
        }
  
 -      /**
 -       * @deprecated Since 1.31; use ::getSkinFromConfigSubpage() instead
 -       * @return string Containing skin name from .css, .json, or .js subpage title
 -       */
 -      public function getSkinFromCssJsSubpage() {
 -              wfDeprecated( __METHOD__, '1.31' );
 -              return $this->getSkinFromConfigSubpage();
 -      }
 -
        /**
         * Is this a CSS "config" sub-page of a user page?
         *
                );
        }
  
 -      /**
 -       * @deprecated Since 1.31; use ::isUserCssConfigPage()
 -       * @return bool
 -       */
 -      public function isCssSubpage() {
 -              wfDeprecated( __METHOD__, '1.31' );
 -              return $this->isUserCssConfigPage();
 -      }
 -
        /**
         * Is this a JSON "config" sub-page of a user page?
         *
                );
        }
  
 -      /**
 -       * @deprecated Since 1.31; use ::isUserJsConfigPage()
 -       * @return bool
 -       */
 -      public function isJsSubpage() {
 -              wfDeprecated( __METHOD__, '1.31' );
 -              return $this->isUserJsConfigPage();
 -      }
 -
        /**
         * Is this a sitewide CSS "config" page?
         *
         * @return string Base name
         */
        public function getBaseText() {
 +              $text = $this->getText();
                if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
 -                      return $this->getText();
 +                      return $text;
                }
  
 -              $parts = explode( '/', $this->getText() );
 -              # Don't discard the real title if there's no subpage involved
 -              if ( count( $parts ) > 1 ) {
 -                      unset( $parts[count( $parts ) - 1] );
 +              $lastSlashPos = strrpos( $text, '/' );
 +              // Don't discard the real title if there's no subpage involved
 +              if ( $lastSlashPos === false ) {
 +                      return $text;
                }
 -              return implode( '/', $parts );
 +
 +              return substr( $text, 0, $lastSlashPos );
        }
  
        /**
         * @endcode
         *
         * @param string $text The subpage name to add to the title
 -       * @return Title Subpage title
 +       * @return Title|null Subpage title, or null on an error
         * @since 1.20
         */
        public function getSubpage( $text ) {
         * protocol-relative, the URL will be expanded to http://
         *
         * @see self::getLocalURL for the arguments.
-        * @param string $query
-        * @param string|bool $query2
+        * @param string|string[] $query
+        * @param string|bool $query2 Deprecated
         * @return string The URL
         */
        public function getInternalURL( $query = '', $query2 = false ) {
         * NOTE: Unlike getInternalURL(), the canonical URL includes the fragment
         *
         * @see self::getLocalURL for the arguments.
-        * @param string $query
-        * @param string|bool $query2
+        * @param string|string[] $query
+        * @param string|bool $query2 Deprecated
         * @return string The URL
         * @since 1.18
         */
         *
         * @param string $action Action that permission needs to be checked for
         * @param User|null $user User to check (since 1.19); $wgUser will be used if not provided.
 +       *
         * @return bool
 +       * @throws Exception
 +       *
 +       * @deprecated since 1.33,
 +       * use MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan(..) instead
 +       *
         */
        public function quickUserCan( $action, $user = null ) {
                return $this->userCan( $action, $user, false );
         * @param User|null $user User to check (since 1.19); $wgUser will be used if not
         *   provided.
         * @param string $rigor Same format as Title::getUserPermissionsErrors()
 +       *
         * @return bool
 +       * @throws Exception
 +       *
 +       * @deprecated since 1.33,
 +       * use MediaWikiServices::getInstance()->getPermissionManager()->userCan(..) instead
 +       *
         */
 -      public function userCan( $action, $user = null, $rigor = 'secure' ) {
 +      public function userCan( $action, $user = null, $rigor = PermissionManager::RIGOR_SECURE ) {
                if ( !$user instanceof User ) {
                        global $wgUser;
                        $user = $wgUser;
                }
  
 -              return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
 +              // TODO: this is for b/c, eventually will be removed
 +              if ( $rigor === true ) {
 +                      $rigor = PermissionManager::RIGOR_SECURE; // b/c
 +              } elseif ( $rigor === false ) {
 +                      $rigor = PermissionManager::RIGOR_QUICK; // b/c
 +              }
 +
 +              return MediaWikiServices::getInstance()->getPermissionManager()
 +                      ->userCan( $action, $user, $this, $rigor );
        }
  
        /**
         *   - secure : does cheap and expensive checks, using the master as needed
         * @param array $ignoreErrors Array of Strings Set this to a list of message keys
         *   whose corresponding errors may be ignored.
 +       *
         * @return array Array of arrays of the arguments to wfMessage to explain permissions problems.
 -       */
 -      public function getUserPermissionsErrors(
 -              $action, $user, $rigor = 'secure', $ignoreErrors = []
 -      ) {
 -              $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
 -
 -              // Remove the errors being ignored.
 -              foreach ( $errors as $index => $error ) {
 -                      $errKey = is_array( $error ) ? $error[0] : $error;
 -
 -                      if ( in_array( $errKey, $ignoreErrors ) ) {
 -                              unset( $errors[$index] );
 -                      }
 -                      if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
 -                              unset( $errors[$index] );
 -                      }
 -              }
 -
 -              return $errors;
 -      }
 -
 -      /**
 -       * Permissions checks that fail most often, and which are easiest to test.
 +       * @throws Exception
         *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 +       * @deprecated since 1.33,
 +       * use MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissionsErrors()
         *
 -       * @return array List of errors
         */
 -      private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
 -              if ( !Hooks::run( 'TitleQuickPermissions',
 -                      [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
 -              ) {
 -                      return $errors;
 -              }
 -
 -              if ( $action == 'create' ) {
 -                      if (
 -                              ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
 -                              ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
 -                      ) {
 -                              $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
 -                      }
 -              } elseif ( $action == 'move' ) {
 -                      if ( !$user->isAllowed( 'move-rootuserpages' )
 -                                      && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
 -                              // Show user page-specific message only if the user can move other pages
 -                              $errors[] = [ 'cant-move-user-page' ];
 -                      }
 -
 -                      // Check if user is allowed to move files if it's a file
 -                      if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
 -                              $errors[] = [ 'movenotallowedfile' ];
 -                      }
 -
 -                      // Check if user is allowed to move category pages if it's a category page
 -                      if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
 -                              $errors[] = [ 'cant-move-category-page' ];
 -                      }
 -
 -                      if ( !$user->isAllowed( 'move' ) ) {
 -                              // User can't move anything
 -                              $userCanMove = User::groupHasPermission( 'user', 'move' );
 -                              $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
 -                              if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
 -                                      // custom message if logged-in users without any special rights can move
 -                                      $errors[] = [ 'movenologintext' ];
 -                              } else {
 -                                      $errors[] = [ 'movenotallowed' ];
 -                              }
 -                      }
 -              } elseif ( $action == 'move-target' ) {
 -                      if ( !$user->isAllowed( 'move' ) ) {
 -                              // User can't move anything
 -                              $errors[] = [ 'movenotallowed' ];
 -                      } elseif ( !$user->isAllowed( 'move-rootuserpages' )
 -                                      && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
 -                              // Show user page-specific message only if the user can move other pages
 -                              $errors[] = [ 'cant-move-to-user-page' ];
 -                      } elseif ( !$user->isAllowed( 'move-categorypages' )
 -                                      && $this->mNamespace == NS_CATEGORY ) {
 -                              // Show category page-specific message only if the user can move other pages
 -                              $errors[] = [ 'cant-move-to-category-page' ];
 -                      }
 -              } elseif ( !$user->isAllowed( $action ) ) {
 -                      $errors[] = $this->missingPermissionError( $action, $short );
 +      public function getUserPermissionsErrors(
 +              $action, $user, $rigor = PermissionManager::RIGOR_SECURE, $ignoreErrors = []
 +      ) {
 +              // TODO: this is for b/c, eventually will be removed
 +              if ( $rigor === true ) {
 +                      $rigor = PermissionManager::RIGOR_SECURE; // b/c
 +              } elseif ( $rigor === false ) {
 +                      $rigor = PermissionManager::RIGOR_QUICK; // b/c
                }
  
 -              return $errors;
 +              return MediaWikiServices::getInstance()->getPermissionManager()
 +                      ->getPermissionErrors( $action, $user, $this, $rigor, $ignoreErrors );
        }
  
        /**
                return $errors;
        }
  
 -      /**
 -       * Check various permission hooks
 -       *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 -       *
 -       * @return array List of errors
 -       */
 -      private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
 -              // Use getUserPermissionsErrors instead
 -              $result = '';
 -              // Avoid PHP 7.1 warning from passing $this by reference
 -              $titleRef = $this;
 -              if ( !Hooks::run( 'userCan', [ &$titleRef, &$user, $action, &$result ] ) ) {
 -                      return $result ? [] : [ [ 'badaccess-group0' ] ];
 -              }
 -              // Check getUserPermissionsErrors hook
 -              // Avoid PHP 7.1 warning from passing $this by reference
 -              $titleRef = $this;
 -              if ( !Hooks::run( 'getUserPermissionsErrors', [ &$titleRef, &$user, $action, &$result ] ) ) {
 -                      $errors = $this->resultToError( $errors, $result );
 -              }
 -              // Check getUserPermissionsErrorsExpensive hook
 -              if (
 -                      $rigor !== 'quick'
 -                      && !( $short && count( $errors ) > 0 )
 -                      && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$titleRef, &$user, $action, &$result ] )
 -              ) {
 -                      $errors = $this->resultToError( $errors, $result );
 -              }
 -
 -              return $errors;
 -      }
 -
 -      /**
 -       * Check permissions on special pages & namespaces
 -       *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 -       *
 -       * @return array List of errors
 -       */
 -      private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
 -              # Only 'createaccount' can be performed on special pages,
 -              # which don't actually exist in the DB.
 -              if ( $this->isSpecialPage() && $action !== 'createaccount' ) {
 -                      $errors[] = [ 'ns-specialprotected' ];
 -              }
 -
 -              # Check $wgNamespaceProtection for restricted namespaces
 -              if ( $this->isNamespaceProtected( $user ) ) {
 -                      $ns = $this->mNamespace == NS_MAIN ?
 -                              wfMessage( 'nstab-main' )->text() : $this->getNsText();
 -                      $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
 -                              [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
 -              }
 -
 -              return $errors;
 -      }
 -
 -      /**
 -       * Check sitewide CSS/JSON/JS permissions
 -       *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 -       *
 -       * @return array List of errors
 -       */
 -      private function checkSiteConfigPermissions( $action, $user, $errors, $rigor, $short ) {
 -              if ( $action != 'patrol' ) {
 -                      $error = null;
 -                      // Sitewide CSS/JSON/JS changes, like all NS_MEDIAWIKI changes, also require the
 -                      // editinterface right. That's implemented as a restriction so no check needed here.
 -                      if ( $this->isSiteCssConfigPage() && !$user->isAllowed( 'editsitecss' ) ) {
 -                              $error = [ 'sitecssprotected', $action ];
 -                      } elseif ( $this->isSiteJsonConfigPage() && !$user->isAllowed( 'editsitejson' ) ) {
 -                              $error = [ 'sitejsonprotected', $action ];
 -                      } elseif ( $this->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) {
 -                              $error = [ 'sitejsprotected', $action ];
 -                      } elseif ( $this->isRawHtmlMessage() ) {
 -                              // Raw HTML can be used to deploy CSS or JS so require rights for both.
 -                              if ( !$user->isAllowed( 'editsitejs' ) ) {
 -                                      $error = [ 'sitejsprotected', $action ];
 -                              } elseif ( !$user->isAllowed( 'editsitecss' ) ) {
 -                                      $error = [ 'sitecssprotected', $action ];
 -                              }
 -                      }
 -
 -                      if ( $error ) {
 -                              if ( $user->isAllowed( 'editinterface' ) ) {
 -                                      // Most users / site admins will probably find out about the new, more restrictive
 -                                      // permissions by failing to edit something. Give them more info.
 -                                      // TODO remove this a few release cycles after 1.32
 -                                      $error = [ 'interfaceadmin-info', wfMessage( $error[0], $error[1] ) ];
 -                              }
 -                              $errors[] = $error;
 -                      }
 -              }
 -
 -              return $errors;
 -      }
 -
 -      /**
 -       * Check CSS/JSON/JS sub-page permissions
 -       *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 -       *
 -       * @return array List of errors
 -       */
 -      private function checkUserConfigPermissions( $action, $user, $errors, $rigor, $short ) {
 -              # Protect css/json/js subpages of user pages
 -              # XXX: this might be better using restrictions
 -
 -              if ( $action === 'patrol' ) {
 -                      return $errors;
 -              }
 -
 -              if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
 -                      // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
 -                      if (
 -                              $this->isUserCssConfigPage()
 -                              && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
 -                      ) {
 -                              $errors[] = [ 'mycustomcssprotected', $action ];
 -                      } elseif (
 -                              $this->isUserJsonConfigPage()
 -                              && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
 -                      ) {
 -                              $errors[] = [ 'mycustomjsonprotected', $action ];
 -                      } elseif (
 -                              $this->isUserJsConfigPage()
 -                              && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
 -                      ) {
 -                              $errors[] = [ 'mycustomjsprotected', $action ];
 -                      }
 -              } else {
 -                      // Users need editmyuser* to edit their own CSS/JSON/JS subpages, except for
 -                      // deletion/suppression which cannot be used for attacks and we want to avoid the
 -                      // situation where an unprivileged user can post abusive content on their subpages
 -                      // and only very highly privileged users could remove it.
 -                      if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
 -                              if (
 -                                      $this->isUserCssConfigPage()
 -                                      && !$user->isAllowed( 'editusercss' )
 -                              ) {
 -                                      $errors[] = [ 'customcssprotected', $action ];
 -                              } elseif (
 -                                      $this->isUserJsonConfigPage()
 -                                      && !$user->isAllowed( 'edituserjson' )
 -                              ) {
 -                                      $errors[] = [ 'customjsonprotected', $action ];
 -                              } elseif (
 -                                      $this->isUserJsConfigPage()
 -                                      && !$user->isAllowed( 'edituserjs' )
 -                              ) {
 -                                      $errors[] = [ 'customjsprotected', $action ];
 -                              }
 -                      }
 -              }
 -
 -              return $errors;
 -      }
 -
 -      /**
 -       * Check against page_restrictions table requirements on this
 -       * page. The user must possess all required rights for this
 -       * action.
 -       *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 -       *
 -       * @return array List of errors
 -       */
 -      private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
 -              foreach ( $this->getRestrictions( $action ) as $right ) {
 -                      // Backwards compatibility, rewrite sysop -> editprotected
 -                      if ( $right == 'sysop' ) {
 -                              $right = 'editprotected';
 -                      }
 -                      // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
 -                      if ( $right == 'autoconfirmed' ) {
 -                              $right = 'editsemiprotected';
 -                      }
 -                      if ( $right == '' ) {
 -                              continue;
 -                      }
 -                      if ( !$user->isAllowed( $right ) ) {
 -                              $errors[] = [ 'protectedpagetext', $right, $action ];
 -                      } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
 -                              $errors[] = [ 'protectedpagetext', 'protect', $action ];
 -                      }
 -              }
 -
 -              return $errors;
 -      }
 -
 -      /**
 -       * Check restrictions on cascading pages.
 -       *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 -       *
 -       * @return array List of errors
 -       */
 -      private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
 -              if ( $rigor !== 'quick' && !$this->isUserConfigPage() ) {
 -                      # We /could/ use the protection level on the source page, but it's
 -                      # fairly ugly as we have to establish a precedence hierarchy for pages
 -                      # included by multiple cascade-protected pages. So just restrict
 -                      # it to people with 'protect' permission, as they could remove the
 -                      # protection anyway.
 -                      list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
 -                      # Cascading protection depends on more than this page...
 -                      # Several cascading protected pages may include this page...
 -                      # Check each cascading level
 -                      # This is only for protection restrictions, not for all actions
 -                      if ( isset( $restrictions[$action] ) ) {
 -                              foreach ( $restrictions[$action] as $right ) {
 -                                      // Backwards compatibility, rewrite sysop -> editprotected
 -                                      if ( $right == 'sysop' ) {
 -                                              $right = 'editprotected';
 -                                      }
 -                                      // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
 -                                      if ( $right == 'autoconfirmed' ) {
 -                                              $right = 'editsemiprotected';
 -                                      }
 -                                      if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
 -                                              $pages = '';
 -                                              foreach ( $cascadingSources as $page ) {
 -                                                      $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
 -                                              }
 -                                              $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
 -                                      }
 -                              }
 -                      }
 -              }
 -
 -              return $errors;
 -      }
 -
 -      /**
 -       * Check action permissions not already checked in checkQuickPermissions
 -       *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 -       *
 -       * @return array List of errors
 -       */
 -      private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
 -              global $wgDeleteRevisionsLimit, $wgLang;
 -
 -              if ( $action == 'protect' ) {
 -                      if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
 -                              // If they can't edit, they shouldn't protect.
 -                              $errors[] = [ 'protect-cantedit' ];
 -                      }
 -              } elseif ( $action == 'create' ) {
 -                      $title_protection = $this->getTitleProtection();
 -                      if ( $title_protection ) {
 -                              if ( $title_protection['permission'] == ''
 -                                      || !$user->isAllowed( $title_protection['permission'] )
 -                              ) {
 -                                      $errors[] = [
 -                                              'titleprotected',
 -                                              User::whoIs( $title_protection['user'] ),
 -                                              $title_protection['reason']
 -                                      ];
 -                              }
 -                      }
 -              } elseif ( $action == 'move' ) {
 -                      // Check for immobile pages
 -                      if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
 -                              // Specific message for this case
 -                              $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
 -                      } elseif ( !$this->isMovable() ) {
 -                              // Less specific message for rarer cases
 -                              $errors[] = [ 'immobile-source-page' ];
 -                      }
 -              } elseif ( $action == 'move-target' ) {
 -                      if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
 -                              $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
 -                      } elseif ( !$this->isMovable() ) {
 -                              $errors[] = [ 'immobile-target-page' ];
 -                      }
 -              } elseif ( $action == 'delete' ) {
 -                      $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
 -                      if ( !$tempErrors ) {
 -                              $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
 -                                      $user, $tempErrors, $rigor, true );
 -                      }
 -                      if ( $tempErrors ) {
 -                              // If protection keeps them from editing, they shouldn't be able to delete.
 -                              $errors[] = [ 'deleteprotected' ];
 -                      }
 -                      if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
 -                              && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
 -                      ) {
 -                              $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
 -                      }
 -              } elseif ( $action === 'undelete' ) {
 -                      if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
 -                              // Undeleting implies editing
 -                              $errors[] = [ 'undelete-cantedit' ];
 -                      }
 -                      if ( !$this->exists()
 -                              && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
 -                      ) {
 -                              // Undeleting where nothing currently exists implies creating
 -                              $errors[] = [ 'undelete-cantcreate' ];
 -                      }
 -              }
 -              return $errors;
 -      }
 -
 -      /**
 -       * Check that the user isn't blocked from editing.
 -       *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 -       *
 -       * @return array List of errors
 -       */
 -      private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
 -              global $wgEmailConfirmToEdit, $wgBlockDisablesLogin;
 -              // Account creation blocks handled at userlogin.
 -              // Unblocking handled in SpecialUnblock
 -              if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
 -                      return $errors;
 -              }
 -
 -              // Optimize for a very common case
 -              if ( $action === 'read' && !$wgBlockDisablesLogin ) {
 -                      return $errors;
 -              }
 -
 -              if ( $wgEmailConfirmToEdit
 -                      && !$user->isEmailConfirmed()
 -                      && $action === 'edit'
 -              ) {
 -                      $errors[] = [ 'confirmedittext' ];
 -              }
 -
 -              $useReplica = ( $rigor !== 'secure' );
 -              $block = $user->getBlock( $useReplica );
 -
 -              // The block may explicitly allow an action (like "read" or "upload").
 -              if ( $block && $block->prevents( $action ) === false ) {
 -                      return $errors;
 -              }
 -
 -              // Determine if the user is blocked from this action on this page.
 -              // What gets passed into this method is a user right, not an action nmae.
 -              // There is no way to instantiate an action by restriction. However, this
 -              // will get the action where the restriction is the same. This may result
 -              // in actions being blocked that shouldn't be.
 -              if ( Action::exists( $action ) ) {
 -                      // Clone the title to prevent mutations to this object which is done
 -                      // by Title::loadFromRow() in WikiPage::loadFromRow().
 -                      $page = WikiPage::factory( clone $this );
 -                      // Creating an action will perform several database queries to ensure that
 -                      // the action has not been overridden by the content type.
 -                      // @todo FIXME: Pass the relevant context into this function.
 -                      $action = Action::factory( $action, $page, RequestContext::getMain() );
 -              } else {
 -                      $action = null;
 -              }
 -
 -              // If no action object is returned, assume that the action requires unblock
 -              // which is the default.
 -              if ( !$action || $action->requiresUnblock() ) {
 -                      if ( $user->isBlockedFrom( $this, $useReplica ) ) {
 -                              // @todo FIXME: Pass the relevant context into this function.
 -                              $errors[] = $block
 -                                      ? $block->getPermissionsError( RequestContext::getMain() )
 -                                      : [ 'actionblockedtext' ];
 -                      }
 -              }
 -
 -              return $errors;
 -      }
 -
 -      /**
 -       * Check that the user is allowed to read this page.
 -       *
 -       * @param string $action The action to check
 -       * @param User $user User to check
 -       * @param array $errors List of current errors
 -       * @param string $rigor Same format as Title::getUserPermissionsErrors()
 -       * @param bool $short Short circuit on first error
 -       *
 -       * @return array List of errors
 -       */
 -      private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
 -              global $wgWhitelistRead, $wgWhitelistReadRegexp;
 -
 -              $whitelisted = false;
 -              if ( User::isEveryoneAllowed( 'read' ) ) {
 -                      # Shortcut for public wikis, allows skipping quite a bit of code
 -                      $whitelisted = true;
 -              } elseif ( $user->isAllowed( 'read' ) ) {
 -                      # If the user is allowed to read pages, he is allowed to read all pages
 -                      $whitelisted = true;
 -              } elseif ( $this->isSpecial( 'Userlogin' )
 -                      || $this->isSpecial( 'PasswordReset' )
 -                      || $this->isSpecial( 'Userlogout' )
 -              ) {
 -                      # Always grant access to the login page.
 -                      # Even anons need to be able to log in.
 -                      $whitelisted = true;
 -              } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
 -                      # Time to check the whitelist
 -                      # Only do these checks is there's something to check against
 -                      $name = $this->getPrefixedText();
 -                      $dbName = $this->getPrefixedDBkey();
 -
 -                      // Check for explicit whitelisting with and without underscores
 -                      if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
 -                              $whitelisted = true;
 -                      } elseif ( $this->mNamespace == NS_MAIN ) {
 -                              # Old settings might have the title prefixed with
 -                              # a colon for main-namespace pages
 -                              if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
 -                                      $whitelisted = true;
 -                              }
 -                      } elseif ( $this->isSpecialPage() ) {
 -                              # If it's a special page, ditch the subpage bit and check again
 -                              $name = $this->mDbkeyform;
 -                              list( $name, /* $subpage */ ) =
 -                                      MediaWikiServices::getInstance()->getSpecialPageFactory()->
 -                                              resolveAlias( $name );
 -                              if ( $name ) {
 -                                      $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
 -                                      if ( in_array( $pure, $wgWhitelistRead, true ) ) {
 -                                              $whitelisted = true;
 -                                      }
 -                              }
 -                      }
 -              }
 -
 -              if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
 -                      $name = $this->getPrefixedText();
 -                      // Check for regex whitelisting
 -                      foreach ( $wgWhitelistReadRegexp as $listItem ) {
 -                              if ( preg_match( $listItem, $name ) ) {
 -                                      $whitelisted = true;
 -                                      break;
 -                              }
 -                      }
 -              }
 -
 -              if ( !$whitelisted ) {
 -                      # If the title is not whitelisted, give extensions a chance to do so...
 -                      Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
 -                      if ( !$whitelisted ) {
 -                              $errors[] = $this->missingPermissionError( $action, $short );
 -                      }
 -              }
 -
 -              return $errors;
 -      }
 -
 -      /**
 -       * Get a description array when the user doesn't have the right to perform
 -       * $action (i.e. when User::isAllowed() returns false)
 -       *
 -       * @param string $action The action to check
 -       * @param bool $short Short circuit on first error
 -       * @return array Array containing an error message key and any parameters
 -       */
 -      private function missingPermissionError( $action, $short ) {
 -              // We avoid expensive display logic for quickUserCan's and such
 -              if ( $short ) {
 -                      return [ 'badaccess-group0' ];
 -              }
 -
 -              return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
 -      }
 -
 -      /**
 -       * Can $user perform $action on this page? This is an internal function,
 -       * with multiple levels of checks depending on performance needs; see $rigor below.
 -       * It does not check wfReadOnly().
 -       *
 -       * @param string $action Action that permission needs to be checked for
 -       * @param User $user User to check
 -       * @param string $rigor One of (quick,full,secure)
 -       *   - quick  : does cheap permission checks from replica DBs (usable for GUI creation)
 -       *   - full   : does cheap and expensive checks possibly from a replica DB
 -       *   - secure : does cheap and expensive checks, using the master as needed
 -       * @param bool $short Set this to true to stop after the first permission error.
 -       * @return array Array of arrays of the arguments to wfMessage to explain permissions problems.
 -       */
 -      protected function getUserPermissionsErrorsInternal(
 -              $action, $user, $rigor = 'secure', $short = false
 -      ) {
 -              if ( $rigor === true ) {
 -                      $rigor = 'secure'; // b/c
 -              } elseif ( $rigor === false ) {
 -                      $rigor = 'quick'; // b/c
 -              } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
 -                      throw new Exception( "Invalid rigor parameter '$rigor'." );
 -              }
 -
 -              # Read has special handling
 -              if ( $action == 'read' ) {
 -                      $checks = [
 -                              'checkPermissionHooks',
 -                              'checkReadPermissions',
 -                              'checkUserBlock', // for wgBlockDisablesLogin
 -                      ];
 -              # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
 -              # or checkUserConfigPermissions here as it will lead to duplicate
 -              # error messages. This is okay to do since anywhere that checks for
 -              # create will also check for edit, and those checks are called for edit.
 -              } elseif ( $action == 'create' ) {
 -                      $checks = [
 -                              'checkQuickPermissions',
 -                              'checkPermissionHooks',
 -                              'checkPageRestrictions',
 -                              'checkCascadingSourcesRestrictions',
 -                              'checkActionPermissions',
 -                              'checkUserBlock'
 -                      ];
 -              } else {
 -                      $checks = [
 -                              'checkQuickPermissions',
 -                              'checkPermissionHooks',
 -                              'checkSpecialsAndNSPermissions',
 -                              'checkSiteConfigPermissions',
 -                              'checkUserConfigPermissions',
 -                              'checkPageRestrictions',
 -                              'checkCascadingSourcesRestrictions',
 -                              'checkActionPermissions',
 -                              'checkUserBlock'
 -                      ];
 -              }
 -
 -              $errors = [];
 -              foreach ( $checks as $method ) {
 -                      $errors = $this->$method( $action, $user, $errors, $rigor, $short );
 -
 -                      if ( $short && $errors !== [] ) {
 -                              break;
 -                      }
 -              }
 -
 -              return $errors;
 -      }
 -
        /**
         * Get a filtered list of all restriction types supported by this wiki.
         * @param bool $exists True to get all restriction types that apply to
                }
  
                if ( $this->mOldRestrictions === false ) {
 -                      $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
 -                              [ 'page_id' => $this->getArticleID() ], __METHOD__ );
 +                      $linkCache = MediaWikiServices::getInstance()->getLinkCache();
 +                      $linkCache->addLinkObj( $this ); # in case we already had an article ID
 +                      $this->mOldRestrictions = $linkCache->getGoodLinkFieldObj( $this, 'restrictions' );
                }
  
                if ( $this->mOldRestrictions != '' ) {
                $id = $this->getArticleID();
                if ( $id ) {
                        $fname = __METHOD__;
 -                      $loadRestrictionsFromDb = function ( Database $dbr ) use ( $fname, $id ) {
 +                      $loadRestrictionsFromDb = function ( IDatabase $dbr ) use ( $fname, $id ) {
                                return iterator_to_array(
                                        $dbr->select(
                                                'page_restrictions',
                                $dbr = wfGetDB( DB_MASTER );
                                $rows = $loadRestrictionsFromDb( $dbr );
                        } else {
 -                              $cache = ObjectCache::getMainWANInstance();
 +                              $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
                                $rows = $cache->getWithSetCallback(
                                        // Page protections always leave a new null revision
 -                                      $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ),
 +                                      $cache->makeKey( 'page-restrictions', 'v1', $id, $this->getLatestRevID() ),
                                        $cache::TTL_DAY,
                                        function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
                                                $dbr = wfGetDB( DB_REPLICA );
  
                                                $setOpts += Database::getCacheSetOptions( $dbr );
 +                                              $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
 +                                              if ( $lb->hasOrMadeRecentMasterChanges() ) {
 +                                                      // @TODO: cleanup Title cache and caller assumption mess in general
 +                                                      $ttl = WANObjectCache::TTL_UNCACHEABLE;
 +                                              }
  
                                                return $loadRestrictionsFromDb( $dbr );
                                        }
                        $linkCache->clearLink( $this );
                        $this->mArticleID = $linkCache->addLinkObj( $this );
                        $linkCache->forUpdate( $oldUpdate );
 -              } else {
 -                      if ( $this->mArticleID == -1 ) {
 -                              $this->mArticleID = $linkCache->addLinkObj( $this );
 -                      }
 +              } elseif ( $this->mArticleID == -1 ) {
 +                      $this->mArticleID = $linkCache->addLinkObj( $this );
                }
                return $this->mArticleID;
        }
                // @todo: get rid of secureAndSplit, refactor parsing code.
                // @note: getTitleParser() returns a TitleParser implementation which does not have a
                //        splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
 +              /** @var MediaWikiTitleCodec $titleCodec */
                $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
                // MalformedTitleException can be thrown here
                $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace );
                return $urls;
        }
  
 -      /**
 -       * @deprecated since 1.27 use getCdnUrls()
 -       */
 -      public function getSquidURLs() {
 -              return $this->getCdnUrls();
 -      }
 -
        /**
         * Purge all applicable CDN URLs
         */
                return $errors ?: true;
        }
  
 -      /**
 -       * Check if the requested move target is a valid file move target
 -       * @todo move this to MovePage
 -       * @param Title $nt Target title
 -       * @return array List of errors
 -       */
 -      protected function validateFileMoveOperation( $nt ) {
 -              global $wgUser;
 -
 -              $errors = [];
 -
 -              $destFile = wfLocalFile( $nt );
 -              $destFile->load( File::READ_LATEST );
 -              if ( !$wgUser->isAllowed( 'reupload-shared' )
 -                      && !$destFile->exists() && wfFindFile( $nt )
 -              ) {
 -                      $errors[] = [ 'file-exists-sharedrepo' ];
 -              }
 -
 -              return $errors;
 -      }
 -
        /**
         * Move a title to a new location
         *
                $dbw->onTransactionPreCommitOrIdle(
                        function () use ( $dbw ) {
                                ResourceLoaderWikiModule::invalidateModuleCache(
 -                                      $this, null, null, $dbw->getDomainId() );
 +                                      $this, null, null, $dbw->getDomainID() );
                        },
                        __METHOD__
                );
        public function canUseNoindex() {
                global $wgExemptFromUserRobotsControl;
  
 -              $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
 -                      ? MWNamespace::getContentNamespaces()
 -                      : $wgExemptFromUserRobotsControl;
 +              $bannedNamespaces = $wgExemptFromUserRobotsControl ?? MWNamespace::getContentNamespaces();
  
                return !in_array( $this->mNamespace, $bannedNamespaces );
        }