Merge "Move User::getAllRights to PermissionManager."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 29 Aug 2019 21:00:23 +0000 (21:00 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 29 Aug 2019 21:00:23 +0000 (21:00 +0000)
94 files changed:
RELEASE-NOTES-1.34
autoload.php
includes/DefaultSettings.php
includes/EditPage.php
includes/OutputPage.php
includes/Permissions/PermissionManager.php
includes/TemplatesOnThisPageFormatter.php
includes/actions/pagers/HistoryPager.php
includes/cache/MessageCache.php
includes/changes/ChangesList.php
includes/content/UnknownContent.php [new file with mode: 0644]
includes/content/UnknownContentHandler.php [new file with mode: 0644]
includes/db/MWLBFactory.php
includes/diff/DifferenceEngine.php
includes/diff/SlotDiffRenderer.php
includes/diff/TextSlotDiffRenderer.php
includes/diff/UnsupportedSlotDiffRenderer.php [new file with mode: 0644]
includes/filerepo/FileRepo.php
includes/filerepo/file/LocalFile.php
includes/libs/HashRing.php
includes/libs/filebackend/FSFileBackend.php
includes/libs/filebackend/FileBackend.php
includes/libs/filebackend/FileBackendStore.php
includes/libs/filebackend/MemoryFileBackend.php
includes/libs/filebackend/SwiftFileBackend.php
includes/libs/mime/MSCompoundFileReader.php
includes/logging/LogEventsList.php
includes/objectcache/ObjectCache.php
includes/page/Article.php
includes/page/ImageHistoryList.php
includes/page/ImagePage.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specials/SpecialMovepage.php
includes/specials/SpecialSearch.php
includes/specials/pagers/ContribsPager.php
languages/i18n/en.json
languages/i18n/qqq.json
package-lock.json
tests/common/TestsAutoLoader.php
tests/phpunit/MediaWikiIntegrationTestCase.php
tests/phpunit/MediaWikiTestCaseTrait.php
tests/phpunit/MediaWikiUnitTestCase.php
tests/phpunit/includes/ActorMigrationTest.php
tests/phpunit/includes/ContentSecurityPolicyTest.php
tests/phpunit/includes/GlobalFunctions/GlobalTest.php
tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
tests/phpunit/includes/MessageTest.php
tests/phpunit/includes/MovePageTest.php
tests/phpunit/includes/Permissions/PermissionManagerTest.php
tests/phpunit/includes/PrefixSearchTest.php
tests/phpunit/includes/Rest/BasicAccess/MWBasicRequestAuthorizerTest.php
tests/phpunit/includes/Revision/RevisionQueryInfoTest.php
tests/phpunit/includes/Revision/RevisionStoreDbTestBase.php
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/api/ApiBlockTest.php
tests/phpunit/includes/api/ApiDeleteTest.php
tests/phpunit/includes/api/ApiEditPageTest.php
tests/phpunit/includes/api/ApiMainTest.php
tests/phpunit/includes/api/ApiMoveTest.php
tests/phpunit/includes/api/ApiOptionsTest.php
tests/phpunit/includes/api/ApiParseTest.php
tests/phpunit/includes/api/ApiQuerySiteinfoTest.php
tests/phpunit/includes/api/ApiUserrightsTest.php
tests/phpunit/includes/api/query/ApiQueryUserContribsTest.php
tests/phpunit/includes/auth/AuthManagerTest.php
tests/phpunit/includes/block/BlockManagerTest.php
tests/phpunit/includes/cache/MessageCacheTest.php
tests/phpunit/includes/content/UnknownContentHandlerTest.php [new file with mode: 0644]
tests/phpunit/includes/content/UnknownContentTest.php [new file with mode: 0644]
tests/phpunit/includes/diff/DifferenceEngineTest.php
tests/phpunit/includes/diff/TextSlotDiffRendererTest.php
tests/phpunit/includes/diff/UnsupportedSlotDiffRendererTest.php [new file with mode: 0644]
tests/phpunit/includes/filebackend/FileBackendGroupIntegrationTest.php
tests/phpunit/includes/filerepo/RepoGroupTest.php
tests/phpunit/includes/interwiki/InterwikiTest.php
tests/phpunit/includes/objectcache/ObjectCacheTest.php
tests/phpunit/includes/page/PageArchiveTestBase.php
tests/phpunit/includes/page/WikiPageDbTestBase.php
tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
tests/phpunit/includes/search/SearchEnginePrefixTest.php
tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php
tests/phpunit/includes/title/NamespaceInfoTest.php
tests/phpunit/includes/user/UserGroupMembershipTest.php
tests/phpunit/includes/user/UserTest.php
tests/phpunit/includes/watcheditem/WatchedItemStoreUnitTest.php
tests/phpunit/languages/LanguageFallbackStaticMethodsTest.php [new file with mode: 0644]
tests/phpunit/tests/MediaWikiTestCaseTest.php
tests/phpunit/unit/includes/filebackend/lockmanager/LockManagerGroupFactoryTest.php
tests/phpunit/unit/includes/language/LanguageFallbackTestTrait.php [new file with mode: 0644]

index f7790cb..e57dacc 100644 (file)
@@ -81,6 +81,7 @@ For notes on 1.33.x and older releases, see HISTORY.
   password setups and deprecated since 1.24, is now removed.
 * $wgDBOracleDRCP - If you must use persistent connections, set DBO_PERSISTENT
   in the 'flags' field for servers in $wgDBServers (or $wgLBFactoryConf).
+* $wgMemCachedDebug - Set the cache "debug" field in $wgObjectCaches instead.
 
 === New user-facing features in 1.34 ===
 * Special:Mute has been added as a quick way for users to block unwanted emails
index 35c9b0a..eb54f7c 100644 (file)
@@ -1521,9 +1521,12 @@ $wgAutoloadLocalClasses = [
        'UncategorizedTemplatesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedtemplates.php',
        'Undelete' => __DIR__ . '/maintenance/undelete.php',
        'UnifiedDiffFormatter' => __DIR__ . '/includes/diff/UnifiedDiffFormatter.php',
+       'UnknownContent' => __DIR__ . '/includes/content/UnknownContent.php',
+       'UnknownContentHandler' => __DIR__ . '/includes/content/UnknownContentHandler.php',
        'UnlistedSpecialPage' => __DIR__ . '/includes/specialpage/UnlistedSpecialPage.php',
        'UnprotectAction' => __DIR__ . '/includes/actions/UnprotectAction.php',
        'UnregisteredLocalFile' => __DIR__ . '/includes/filerepo/file/UnregisteredLocalFile.php',
+       'UnsupportedSlotDiffRenderer' => __DIR__ . '/includes/diff/UnsupportedSlotDiffRenderer.php',
        'UnusedCategoriesPage' => __DIR__ . '/includes/specials/SpecialUnusedcategories.php',
        'UnusedimagesPage' => __DIR__ . '/includes/specials/SpecialUnusedimages.php',
        'UnusedtemplatesPage' => __DIR__ . '/includes/specials/SpecialUnusedtemplates.php',
index 93a5919..9577a48 100644 (file)
@@ -2554,11 +2554,6 @@ $wgPHPSessionHandling = 'enable';
  */
 $wgSessionPbkdf2Iterations = 10001;
 
-/**
- * If enabled, will send MemCached debugging information to $wgDebugLogFile
- */
-$wgMemCachedDebug = false;
-
 /**
  * The list of MemCached servers and port numbers
  */
index d0a5080..e51fc52 100644 (file)
@@ -689,10 +689,6 @@ class EditPage {
                # checking, etc.
                if ( $this->formtype == 'initial' || $this->firsttime ) {
                        if ( $this->initialiseForm() === false ) {
-                               $out = $this->context->getOutput();
-                               if ( $out->getRedirect() === '' ) { // mcrundo hack redirects, don't override it
-                                       $this->noSuchSectionPage();
-                               }
                                return;
                        }
 
@@ -1145,8 +1141,26 @@ class EditPage {
 
                $content = $this->getContentObject( false ); # TODO: track content object?!
                if ( $content === false ) {
+                       $out = $this->context->getOutput();
+                       if ( $out->getRedirect() === '' ) { // mcrundo hack redirects, don't override it
+                               $this->noSuchSectionPage();
+                       }
+                       return false;
+               }
+
+               if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
+                       $modelMsg = $this->getContext()->msg( 'content-model-' . $content->getModel() );
+                       $modelName = $modelMsg->exists() ? $modelMsg->text() : $content->getModel();
+
+                       $out = $this->context->getOutput();
+                       $out->showErrorPage(
+                               'modeleditnotsupported-title',
+                               'modeleditnotsupported-text',
+                               $modelName
+                       );
                        return false;
                }
+
                $this->textbox1 = $this->toEditText( $content );
 
                $user = $this->context->getUser();
index b2ca53a..9af16d3 100644 (file)
@@ -3311,12 +3311,10 @@ class OutputPage extends ContextSource {
                        $vars['wgUserVariant'] = $contLang->getPreferredVariant();
                }
                // Same test as SkinTemplate
-               $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
-                       && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
+               $vars['wgIsProbablyEditable'] = $this->userCanEditOrCreate( $user, $title );
 
-               $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
-                       && $relevantTitle->quickUserCan( 'edit', $user )
-                       && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
+               $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle &&
+                       $this->userCanEditOrCreate( $user, $relevantTitle );
 
                foreach ( $title->getRestrictionTypes() as $type ) {
                        // Following keys are set in $vars:
@@ -3383,6 +3381,21 @@ class OutputPage extends ContextSource {
                return true;
        }
 
+       /**
+        * @param User $user
+        * @param LinkTarget $title
+        * @return bool
+        */
+       private function userCanEditOrCreate(
+               User $user,
+               LinkTarget $title
+       ) {
+               $pm = MediaWikiServices::getInstance()->getPermissionManager();
+               return $pm->quickUserCan( 'edit', $user, $title )
+               && ( $this->getTitle()->exists() ||
+                        $pm->quickUserCan( 'create', $user, $title ) );
+       }
+
        /**
         * @return array Array in format "link name or number => 'link html'".
         */
@@ -3447,11 +3460,7 @@ class OutputPage extends ContextSource {
 
                # Universal edit button
                if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
-                       $user = $this->getUser();
-                       if ( $this->getTitle()->quickUserCan( 'edit', $user )
-                               && ( $this->getTitle()->exists() ||
-                                       $this->getTitle()->quickUserCan( 'create', $user ) )
-                       ) {
+                       if ( $this->userCanEditOrCreate( $this->getUser(), $this->getTitle() ) ) {
                                // Original UniversalEditButton
                                $msg = $this->msg( 'edit' )->text();
                                $tags['universal-edit-button'] = Html::element( 'link', [
index f9ad3eb..37791d0 100644 (file)
@@ -231,6 +231,25 @@ class PermissionManager {
                return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
        }
 
+       /**
+        * A convenience method for calling PermissionManager::userCan
+        * with PermissionManager::RIGOR_QUICK
+        *
+        * Suitable for use for nonessential UI controls in common cases, but
+        * _not_ for functional access control.
+        * May provide false positives, but should never provide a false negative.
+        *
+        * @see PermissionManager::userCan()
+        *
+        * @param string $action
+        * @param User $user
+        * @param LinkTarget $page
+        * @return bool
+        */
+       public function quickUserCan( $action, User $user, LinkTarget $page ) {
+               return $this->userCan( $action, $user, $page, self::RIGOR_QUICK );
+       }
+
        /**
         * Can $user perform $action on a page?
         *
index bca1ef6..215e4ec 100644 (file)
@@ -20,6 +20,7 @@
 
 use MediaWiki\Linker\LinkRenderer;
 use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Handles formatting for the "templates used on this page"
@@ -158,11 +159,13 @@ class TemplatesOnThisPageFormatter {
         * Return a link to the edit page, with the text
         * saying "view source" if the user can't edit the page
         *
-        * @param Title $titleObj
+        * @param LinkTarget $titleObj
         * @return string
         */
-       private function buildEditLink( Title $titleObj ) {
-               if ( $titleObj->quickUserCan( 'edit', $this->context->getUser() ) ) {
+       private function buildEditLink( LinkTarget $titleObj ) {
+               if ( MediaWikiServices::getInstance()->getPermissionManager()
+                               ->quickUserCan( 'edit', $this->context->getUser(), $titleObj )
+               ) {
                        $linkMsg = 'editlink';
                } else {
                        $linkMsg = 'viewsourcelink';
index fd2fbd0..f178911 100644 (file)
@@ -401,8 +401,11 @@ class HistoryPager extends ReverseChronologicalPager {
                $tools = [];
 
                # Rollback and undo links
-               if ( $prevRev && $this->getTitle()->quickUserCan( 'edit', $user ) ) {
-                       if ( $latest && $this->getTitle()->quickUserCan( 'rollback', $user ) ) {
+
+               if ( $prevRev && $permissionManager->quickUserCan( 'edit', $user, $this->getTitle() ) ) {
+                       if ( $latest && $permissionManager->quickUserCan( 'rollback',
+                                       $user, $this->getTitle() )
+                       ) {
                                // Get a rollback link without the brackets
                                $rollbackLink = Linker::generateRollback(
                                        $rev,
index dfbc50a..3a6d892 100644 (file)
@@ -165,8 +165,8 @@ class MessageCache {
                global $wgUser;
 
                if ( !$this->mParserOptions ) {
-                       if ( !$wgUser->isSafeToLoad() ) {
-                               // $wgUser isn't unstubbable yet, so don't try to get a
+                       if ( !$wgUser || !$wgUser->isSafeToLoad() ) {
+                               // $wgUser isn't available yet, so don't try to get a
                                // ParserOptions for it. And don't cache this ParserOptions
                                // either.
                                $po = ParserOptions::newFromAnon();
index 7807877..0382d73 100644 (file)
@@ -716,7 +716,9 @@ class ChangesList extends ContextSource {
                        /** Check for rollback permissions, disallow special pages, and only
                         * show a link on the top-most revision
                         */
-                       if ( $title->quickUserCan( 'rollback', $this->getUser() ) ) {
+                       if ( MediaWikiServices::getInstance()->getPermissionManager()
+                               ->quickUserCan( 'rollback', $this->getUser(), $title )
+                       ) {
                                $rev = new Revision( [
                                        'title' => $title,
                                        'id' => $rc->mAttribs['rc_this_oldid'],
diff --git a/includes/content/UnknownContent.php b/includes/content/UnknownContent.php
new file mode 100644 (file)
index 0000000..27199a0
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+/**
+ * Content object implementation for representing unknown content.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.34
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Content object implementation representing unknown content.
+ *
+ * This can be used to handle content for which no ContentHandler exists on the system,
+ * perhaps because the extension that provided it has been removed.
+ *
+ * UnknownContent instances are immutable.
+ *
+ * @ingroup Content
+ */
+class UnknownContent extends AbstractContent {
+
+       /** @var string */
+       private $data;
+
+       /**
+        * @param string $data
+        * @param string $model_id The model ID to handle
+        */
+       public function __construct( $data, $model_id ) {
+               parent::__construct( $model_id );
+
+               $this->data = $data;
+       }
+
+       /**
+        * @return Content $this
+        */
+       public function copy() {
+               // UnknownContent is immutable, so no need to copy.
+               return $this;
+       }
+
+       /**
+        * Returns an empty string.
+        *
+        * @param int $maxlength
+        *
+        * @return string
+        */
+       public function getTextForSummary( $maxlength = 250 ) {
+               return '';
+       }
+
+       /**
+        * Returns the data size in bytes.
+        *
+        * @return int
+        */
+       public function getSize() {
+               return strlen( $this->data );
+       }
+
+       /**
+        * Returns false.
+        *
+        * @param bool|null $hasLinks If it is known whether this content contains links,
+        * provide this information here, to avoid redundant parsing to find out.
+        *
+        * @return bool
+        */
+       public function isCountable( $hasLinks = null ) {
+               return false;
+       }
+
+       /**
+        * @return string data of unknown format and meaning
+        */
+       public function getNativeData() {
+               return $this->getData();
+       }
+
+       /**
+        * @return string data of unknown format and meaning
+        */
+       public function getData() {
+               return $this->data;
+       }
+
+       /**
+        * Returns an empty string.
+        *
+        * @return string The raw text.
+        */
+       public function getTextForSearchIndex() {
+               return '';
+       }
+
+       /**
+        * Returns false.
+        */
+       public function getWikitextForTransclusion() {
+               return false;
+       }
+
+       /**
+        * Fills the ParserOutput with an error message.
+        */
+       protected function fillParserOutput( Title $title, $revId,
+               ParserOptions $options, $generateHtml, ParserOutput &$output
+       ) {
+               $msg = wfMessage( 'unsupported-content-model', [ $this->getModel() ] );
+               $html = Html::rawElement( 'div', [ 'class' => 'error' ], $msg->inContentLanguage()->parse() );
+               $output->setText( $html );
+       }
+
+       /**
+        * Returns false.
+        */
+       public function convert( $toModel, $lossy = '' ) {
+               return false;
+       }
+
+       protected function equalsInternal( Content $that ) {
+               if ( !$that instanceof UnknownContent ) {
+                       return false;
+               }
+
+               return $this->getData() == $that->getData();
+       }
+
+}
diff --git a/includes/content/UnknownContentHandler.php b/includes/content/UnknownContentHandler.php
new file mode 100644 (file)
index 0000000..1427e2b
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Base content handler class for flat text contents.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.34
+ *
+ * @file
+ * @ingroup Content
+ */
+
+/**
+ * Content handler implementation for unknown content.
+ *
+ * This can be used to handle content for which no ContentHandler exists on the system,
+ * perhaps because the extension that provided it has been removed.
+ *
+ * @ingroup Content
+ */
+class UnknownContentHandler extends ContentHandler {
+
+       /**
+        * Constructs an UnknownContentHandler. Since UnknownContentHandler can be registered
+        * for multiple model IDs on a system, multiple instances of UnknownContentHandler may
+        * coexist.
+        *
+        * To preserve the serialization format of the original content model, it must be supplied
+        * to the constructor via the $formats parameter. If not given, the default format is
+        * reported as 'application/octet-stream'.
+        *
+        * @param string $modelId
+        * @param string[]|null $formats
+        */
+       public function __construct( $modelId, $formats = null ) {
+               parent::__construct(
+                       $modelId,
+                       $formats ?? [
+                               'application/octet-stream',
+                               'application/unknown',
+                               'application/x-binary',
+                               'text/unknown',
+                               'unknown/unknown',
+                       ]
+               );
+       }
+
+       /**
+        * Returns the content's data as-is.
+        *
+        * @param Content $content
+        * @param string|null $format The serialization format to check
+        *
+        * @return mixed
+        */
+       public function serializeContent( Content $content, $format = null ) {
+               /** @var UnknownContent $content */
+               return $content->getData();
+       }
+
+       /**
+        * Constructs an UnknownContent instance wrapping the given data.
+        *
+        * @since 1.21
+        *
+        * @param string $blob serialized content in an unknown format
+        * @param string|null $format ignored
+        *
+        * @return Content The UnknownContent object wrapping $data
+        */
+       public function unserializeContent( $blob, $format = null ) {
+               return new UnknownContent( $blob, $this->getModelID() );
+       }
+
+       /**
+        * Creates an empty UnknownContent object.
+        *
+        * @since 1.21
+        *
+        * @return Content A new UnknownContent object with empty text.
+        */
+       public function makeEmptyContent() {
+               return $this->unserializeContent( '' );
+       }
+
+       /**
+        * @return false
+        */
+       public function supportsDirectEditing() {
+               return false;
+       }
+
+       /**
+        * @param IContextSource $context
+        *
+        * @return SlotDiffRenderer
+        */
+       protected function getSlotDiffRendererInternal( IContextSource $context ) {
+               return new UnsupportedSlotDiffRenderer( $context );
+       }
+}
index 80eb2f7..1803009 100644 (file)
@@ -66,7 +66,7 @@ abstract class MWLBFactory {
         * @param array $lbConf Config for LBFactory::__construct()
         * @param ServiceOptions $options
         * @param ConfiguredReadOnlyMode $readOnlyMode
-        * @param BagOStuff $srvCace
+        * @param BagOStuff $srvCache
         * @param BagOStuff $mainStash
         * @param WANObjectCache $wanCache
         * @return array
@@ -76,7 +76,7 @@ abstract class MWLBFactory {
                array $lbConf,
                ServiceOptions $options,
                ConfiguredReadOnlyMode $readOnlyMode,
-               BagOStuff $srvCace,
+               BagOStuff $srvCache,
                BagOStuff $mainStash,
                WANObjectCache $wanCache
        ) {
@@ -159,7 +159,7 @@ abstract class MWLBFactory {
                        $options->get( 'DBprefix' )
                );
 
-               $lbConf = self::injectObjectCaches( $lbConf, $srvCace, $mainStash, $wanCache );
+               $lbConf = self::injectObjectCaches( $lbConf, $srvCache, $mainStash, $wanCache );
 
                return $lbConf;
        }
@@ -222,6 +222,11 @@ abstract class MWLBFactory {
        private static function injectObjectCaches(
                array $lbConf, BagOStuff $sCache, BagOStuff $mStash, WANObjectCache $wCache
        ) {
+               // Fallback if APC style caching is not an option
+               if ( $sCache instanceof EmptyBagOStuff ) {
+                       $sCache = new HashBagOStuff( [ 'maxKeys' => 100 ] );
+               }
+
                // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
                if ( $sCache->getQoS( $sCache::ATTR_EMULATION ) > $sCache::QOS_EMULATION_SQL ) {
                        $lbConf['srvCache'] = $sCache;
index 1d3b402..b8697e5 100644 (file)
@@ -22,7 +22,6 @@
  */
 
 use MediaWiki\MediaWikiServices;
-use MediaWiki\Permissions\PermissionManager;
 use MediaWiki\Revision\RevisionRecord;
 use MediaWiki\Revision\SlotRecord;
 use MediaWiki\Storage\NameTableAccessException;
@@ -542,8 +541,8 @@ class DifferenceEngine extends ContextSource {
 
                        $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
-                       if ( $samePage && $this->mNewPage && $permissionManager->userCan(
-                               'edit', $user, $this->mNewPage, PermissionManager::RIGOR_QUICK
+                       if ( $samePage && $this->mNewPage && $permissionManager->quickUserCan(
+                               'edit', $user, $this->mNewPage
                        ) ) {
                                if ( $this->mNewRev->isCurrent() && $permissionManager->userCan(
                                        'rollback', $user, $this->mNewPage
@@ -556,8 +555,8 @@ class DifferenceEngine extends ContextSource {
                                        }
                                }
 
-                               if ( !$this->mOldRev->isDeleted( RevisionRecord::DELETED_TEXT ) &&
-                                       !$this->mNewRev->isDeleted( RevisionRecord::DELETED_TEXT )
+                               if ( $this->userCanEdit( $this->mOldRev ) &&
+                                       $this->userCanEdit( $this->mNewRev )
                                ) {
                                        $undoLink = Html::element( 'a', [
                                                        'href' => $this->mNewPage->getLocalURL( [
@@ -766,12 +765,14 @@ class DifferenceEngine extends ContextSource {
        protected function getMarkPatrolledLinkInfo() {
                $user = $this->getUser();
                $config = $this->getConfig();
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
                // Prepare a change patrol link, if applicable
                if (
                        // Is patrolling enabled and the user allowed to?
                        $config->get( 'UseRCPatrol' ) &&
-                       $this->mNewPage && $this->mNewPage->quickUserCan( 'patrol', $user ) &&
+                       $this->mNewPage &&
+                       $permissionManager->quickUserCan( 'patrol', $user, $this->mNewPage ) &&
                        // Only do this if the revision isn't more than 6 hours older
                        // than the Max RC age (6h because the RC might not be cleaned out regularly)
                        RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 )
@@ -804,8 +805,7 @@ class DifferenceEngine extends ContextSource {
                        // Build the link
                        if ( $rcid ) {
                                $this->getOutput()->preventClickjacking();
-                               if ( MediaWikiServices::getInstance()->getPermissionManager()
-                                               ->userHasRight( $user, 'writeapi' ) ) {
+                               if ( $permissionManager->userHasRight( $user, 'writeapi' ) ) {
                                        $this->getOutput()->addModules( 'mediawiki.page.patrol.ajax' );
                                }
 
@@ -900,7 +900,11 @@ class DifferenceEngine extends ContextSource {
                                        ) {
                                                $out->addParserOutput( $parserOutput, [
                                                        'enableSectionEditLinks' => $this->mNewRev->isCurrent()
-                                                               && $this->mNewRev->getTitle()->quickUserCan( 'edit', $this->getUser() ),
+                                                               && MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan(
+                                                                       'edit',
+                                                                       $this->getUser(),
+                                                                       $this->mNewRev->getTitle()
+                                                               )
                                                ] );
                                        }
                                }
@@ -1500,6 +1504,24 @@ class DifferenceEngine extends ContextSource {
                return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
        }
 
+       /**
+        * @param Revision $rev
+        * @return bool whether the user can see and edit the revision.
+        */
+       private function userCanEdit( Revision $rev ) {
+               $user = $this->getUser();
+
+               if ( !$rev->getContentHandler()->supportsDirectEditing() ) {
+                       return false;
+               }
+
+               if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
+                       return false;
+               }
+
+               return true;
+       }
+
        /**
         * Get a header for a specified revision.
         *
@@ -1533,13 +1555,14 @@ class DifferenceEngine extends ContextSource {
                $header = Linker::linkKnown( $title, $header, [],
                        [ 'oldid' => $rev->getId() ] );
 
-               if ( $rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
+               if ( $this->userCanEdit( $rev ) ) {
                        $editQuery = [ 'action' => 'edit' ];
                        if ( !$rev->isCurrent() ) {
                                $editQuery['oldid'] = $rev->getId();
                        }
 
-                       $key = $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold';
+                       $key = MediaWikiServices::getInstance()->getPermissionManager()
+                               ->quickUserCan( 'edit', $user, $title ) ? 'editold' : 'viewsourceold';
                        $msg = $this->msg( $key )->escaped();
                        $editLink = $this->msg( 'parentheses' )->rawParams(
                                Linker::linkKnown( $title, $msg, [], $editQuery ) )->escaped();
index 969e0ba..c58502b 100644 (file)
@@ -44,7 +44,7 @@ abstract class SlotDiffRenderer {
         * must have the same content model that was used to obtain this diff renderer.
         * @param Content|null $oldContent
         * @param Content|null $newContent
-        * @return string
+        * @return string HTML, one or more <tr> tags.
         */
        abstract public function getDiff( Content $oldContent = null, Content $newContent = null );
 
index 510465b..935172a 100644 (file)
@@ -112,7 +112,7 @@ class TextSlotDiffRenderer extends SlotDiffRenderer {
         * Diff the text representations of two content objects (or just two pieces of text in general).
         * @param string $oldText
         * @param string $newText
-        * @return string
+        * @return string HTML, one or more <tr> tags.
         */
        public function getTextDiff( $oldText, $newText ) {
                Assert::parameterType( 'string', $oldText, '$oldText' );
diff --git a/includes/diff/UnsupportedSlotDiffRenderer.php b/includes/diff/UnsupportedSlotDiffRenderer.php
new file mode 100644 (file)
index 0000000..db1b868
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Renders a slot diff by doing a text diff on the native representation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ */
+
+/**
+ * Produces a warning message about not being able to render a slot diff.
+ *
+ * @since 1.34
+ *
+ * @ingroup DifferenceEngine
+ */
+class UnsupportedSlotDiffRenderer extends SlotDiffRenderer {
+
+       /**
+        * @var MessageLocalizer
+        */
+       private $localizer;
+
+       /**
+        * UnsupportedSlotDiffRenderer constructor.
+        *
+        * @param MessageLocalizer $localizer
+        */
+       public function __construct( MessageLocalizer $localizer ) {
+               $this->localizer = $localizer;
+       }
+
+       /** @inheritDoc */
+       public function getDiff( Content $oldContent = null, Content $newContent = null ) {
+               $this->normalizeContents( $oldContent, $newContent );
+
+               $oldModel = $oldContent->getModel();
+               $newModel = $newContent->getModel();
+
+               if ( $oldModel !== $newModel ) {
+                       $msg = $this->localizer->msg( 'unsupported-content-diff2', $oldModel, $newModel );
+               } else {
+                       $msg = $this->localizer->msg( 'unsupported-content-diff', $oldModel );
+               }
+
+               return Html::rawElement(
+                       'tr',
+                       [],
+                       Html::rawElement(
+                               'td',
+                               [ 'colspan' => 4, 'class' => 'error' ],
+                               $msg->parse()
+                       )
+               );
+       }
+
+}
index 60f1607..42e78ff 100644 (file)
@@ -899,20 +899,15 @@ class FileRepo {
                        }
 
                        // Resolve source to a storage path if virtual
-                       $srcPath = $this->resolveToStoragePath( $srcPath );
+                       $srcPath = $this->resolveToStoragePathIfVirtual( $srcPath );
 
-                       // Get the appropriate file operation
-                       if ( FileBackend::isStoragePath( $srcPath ) ) {
-                               $opName = 'copy';
-                       } else {
-                               $opName = 'store';
-                       }
+                       // Copy the source file to the destination
                        $operations[] = [
-                               'op' => $opName,
-                               'src' => $srcPath,
+                               'op' => FileBackend::isStoragePath( $srcPath ) ? 'copy' : 'store',
+                               'src' => $srcPath, // storage path (copy) or local file path (store)
                                'dst' => $dstPath,
-                               'overwrite' => $flags & self::OVERWRITE,
-                               'overwriteSame' => $flags & self::OVERWRITE_SAME,
+                               'overwrite' => ( $flags & self::OVERWRITE ) ? true : false,
+                               'overwriteSame' => ( $flags & self::OVERWRITE_SAME ) ? true : false,
                        ];
                }
 
@@ -949,7 +944,7 @@ class FileRepo {
                                $path = $this->getZonePath( $zone ) . "/$rel";
                        } else {
                                // Resolve source to a storage path if virtual
-                               $path = $this->resolveToStoragePath( $path );
+                               $path = $this->resolveToStoragePathIfVirtual( $path );
                        }
                        $operations[] = [ 'op' => 'delete', 'src' => $path ];
                }
@@ -1002,7 +997,7 @@ class FileRepo {
        public function quickCleanDir( $dir ) {
                $status = $this->newGood();
                $status->merge( $this->backend->clean(
-                       [ 'dir' => $this->resolveToStoragePath( $dir ) ] ) );
+                       [ 'dir' => $this->resolveToStoragePathIfVirtual( $dir ) ] ) );
 
                return $status;
        }
@@ -1027,10 +1022,10 @@ class FileRepo {
                        if ( $src instanceof FSFile ) {
                                $op = 'store';
                        } else {
-                               $src = $this->resolveToStoragePath( $src );
+                               $src = $this->resolveToStoragePathIfVirtual( $src );
                                $op = FileBackend::isStoragePath( $src ) ? 'copy' : 'store';
                        }
-                       $dst = $this->resolveToStoragePath( $dst );
+                       $dst = $this->resolveToStoragePathIfVirtual( $dst );
 
                        if ( !isset( $triple[2] ) ) {
                                $headers = [];
@@ -1070,7 +1065,7 @@ class FileRepo {
                foreach ( $paths as $path ) {
                        $operations[] = [
                                'op' => 'delete',
-                               'src' => $this->resolveToStoragePath( $path ),
+                               'src' => $this->resolveToStoragePathIfVirtual( $path ),
                                'ignoreMissingSource' => true
                        ];
                }
@@ -1139,7 +1134,7 @@ class FileRepo {
                $sources = [];
                foreach ( $srcPaths as $srcPath ) {
                        // Resolve source to a storage path if virtual
-                       $source = $this->resolveToStoragePath( $srcPath );
+                       $source = $this->resolveToStoragePathIfVirtual( $srcPath );
                        $sources[] = $source; // chunk to merge
                }
 
@@ -1226,7 +1221,7 @@ class FileRepo {
 
                        $options = $ntuple[3] ?? [];
                        // Resolve source to a storage path if virtual
-                       $srcPath = $this->resolveToStoragePath( $srcPath );
+                       $srcPath = $this->resolveToStoragePathIfVirtual( $srcPath );
                        if ( !$this->validateFilename( $dstRel ) ) {
                                throw new MWException( 'Validation error in $dstRel' );
                        }
@@ -1266,27 +1261,17 @@ class FileRepo {
 
                        // Copy (or move) the source file to the destination
                        if ( FileBackend::isStoragePath( $srcPath ) ) {
-                               if ( $flags & self::DELETE_SOURCE ) {
-                                       $operations[] = [
-                                               'op' => 'move',
-                                               'src' => $srcPath,
-                                               'dst' => $dstPath,
-                                               'overwrite' => true, // replace current
-                                               'headers' => $headers
-                                       ];
-                               } else {
-                                       $operations[] = [
-                                               'op' => 'copy',
-                                               'src' => $srcPath,
-                                               'dst' => $dstPath,
-                                               'overwrite' => true, // replace current
-                                               'headers' => $headers
-                                       ];
-                               }
-                       } else { // FS source path
+                               $operations[] = [
+                                       'op' => ( $flags & self::DELETE_SOURCE ) ? 'move' : 'copy',
+                                       'src' => $srcPath,
+                                       'dst' => $dstPath,
+                                       'overwrite' => true, // replace current
+                                       'headers' => $headers
+                               ];
+                       } else {
                                $operations[] = [
                                        'op' => 'store',
-                                       'src' => $src, // prefer FSFile objects
+                                       'src' => $src, // FSFile (preferred) or local file path
                                        'dst' => $dstPath,
                                        'overwrite' => true, // replace current
                                        'headers' => $headers
@@ -1327,7 +1312,7 @@ class FileRepo {
         * @return Status
         */
        protected function initDirectory( $dir ) {
-               $path = $this->resolveToStoragePath( $dir );
+               $path = $this->resolveToStoragePathIfVirtual( $dir );
                list( , $container, ) = FileBackend::splitStoragePath( $path );
 
                $params = [ 'dir' => $path ];
@@ -1357,7 +1342,7 @@ class FileRepo {
 
                $status = $this->newGood();
                $status->merge( $this->backend->clean(
-                       [ 'dir' => $this->resolveToStoragePath( $dir ) ] ) );
+                       [ 'dir' => $this->resolveToStoragePathIfVirtual( $dir ) ] ) );
 
                return $status;
        }
@@ -1381,12 +1366,12 @@ class FileRepo {
         * @return array Map of files and existence flags, or false
         */
        public function fileExistsBatch( array $files ) {
-               $paths = array_map( [ $this, 'resolveToStoragePath' ], $files );
+               $paths = array_map( [ $this, 'resolveToStoragePathIfVirtual' ], $files );
                $this->backend->preloadFileStat( [ 'srcs' => $paths ] );
 
                $result = [];
                foreach ( $files as $key => $file ) {
-                       $path = $this->resolveToStoragePath( $file );
+                       $path = $this->resolveToStoragePathIfVirtual( $file );
                        $result[$key] = $this->backend->fileExists( [ 'src' => $path ] );
                }
 
@@ -1517,7 +1502,7 @@ class FileRepo {
         * @return string
         * @throws MWException
         */
-       protected function resolveToStoragePath( $path ) {
+       protected function resolveToStoragePathIfVirtual( $path ) {
                if ( self::isVirtualUrl( $path ) ) {
                        return $this->resolveVirtualUrl( $path );
                }
@@ -1533,7 +1518,7 @@ class FileRepo {
         * @return TempFSFile|null Returns null on failure
         */
        public function getLocalCopy( $virtualUrl ) {
-               $path = $this->resolveToStoragePath( $virtualUrl );
+               $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
 
                return $this->backend->getLocalCopy( [ 'src' => $path ] );
        }
@@ -1547,7 +1532,7 @@ class FileRepo {
         * @return FSFile|null Returns null on failure.
         */
        public function getLocalReference( $virtualUrl ) {
-               $path = $this->resolveToStoragePath( $virtualUrl );
+               $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
 
                return $this->backend->getLocalReference( [ 'src' => $path ] );
        }
@@ -1578,7 +1563,7 @@ class FileRepo {
         * @return string|bool False on failure
         */
        public function getFileTimestamp( $virtualUrl ) {
-               $path = $this->resolveToStoragePath( $virtualUrl );
+               $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
 
                return $this->backend->getFileTimestamp( [ 'src' => $path ] );
        }
@@ -1590,7 +1575,7 @@ class FileRepo {
         * @return int|bool False on failure
         */
        public function getFileSize( $virtualUrl ) {
-               $path = $this->resolveToStoragePath( $virtualUrl );
+               $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
 
                return $this->backend->getFileSize( [ 'src' => $path ] );
        }
@@ -1602,7 +1587,7 @@ class FileRepo {
         * @return string|bool
         */
        public function getFileSha1( $virtualUrl ) {
-               $path = $this->resolveToStoragePath( $virtualUrl );
+               $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
 
                return $this->backend->getFileSha1Base36( [ 'src' => $path ] );
        }
@@ -1617,7 +1602,7 @@ class FileRepo {
         * @since 1.27
         */
        public function streamFileWithStatus( $virtualUrl, $headers = [], $optHeaders = [] ) {
-               $path = $this->resolveToStoragePath( $virtualUrl );
+               $path = $this->resolveToStoragePathIfVirtual( $virtualUrl );
                $params = [ 'src' => $path, 'headers' => $headers, 'options' => $optHeaders ];
 
                // T172851: HHVM does not flush the output properly, causing OOM
index 54fc251..6143c31 100644 (file)
@@ -1923,10 +1923,9 @@ class LocalFile extends File {
 
                wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
 
-               // Purge the source and target files...
+               // Purge the source and target files outside the transaction...
                $oldTitleFile = $localRepo->newFile( $this->title );
                $newTitleFile = $localRepo->newFile( $target );
-               // To avoid slow purges in the transaction, move them outside...
                DeferredUpdates::addUpdate(
                        new AutoCommitUpdate(
                                $this->getRepo()->getMasterDB(),
@@ -1934,6 +1933,7 @@ class LocalFile extends File {
                                function () use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
                                        $oldTitleFile->purgeEverything();
                                        foreach ( $archiveNames as $archiveName ) {
+                                               /** @var OldLocalFile $oldTitleFile */
                                                $oldTitleFile->purgeOldThumbnails( $archiveName );
                                        }
                                        $newTitleFile->purgeEverything();
index 251fa88..f8ab6a3 100644 (file)
@@ -44,9 +44,9 @@ class HashRing implements Serializable {
        /** @var int[] Map of (location => UNIX timestamp) */
        protected $ejectExpiryByLocation;
 
-       /** @var array[] Non-empty list of (float, node name, location name) */
+       /** @var array[] Non-empty position-ordered list of (position, location name) */
        protected $baseRing;
-       /** @var array[] Non-empty list of (float, node name, location name) */
+       /** @var array[] Non-empty position-ordered list of (position, location name) */
        protected $liveRing;
 
        /** @var float Number of positions on the ring */
@@ -96,7 +96,7 @@ class HashRing implements Serializable {
                $this->algo = $algo;
                $this->weightByLocation = $weightByLocation;
                $this->ejectExpiryByLocation = $ejections;
-               $this->baseRing = $this->buildLocationRing( $this->weightByLocation, $this->algo );
+               $this->baseRing = $this->buildLocationRing( $this->weightByLocation );
        }
 
        /**
@@ -111,12 +111,13 @@ class HashRing implements Serializable {
        }
 
        /**
-        * Get the location of an item on the ring, as well as the next locations
+        * Get the location of an item on the ring followed by the next ring locations
         *
         * @param string $item
         * @param int $limit Maximum number of locations to return
         * @param int $from One of the RING_* class constants
         * @return string[] List of locations
+        * @throws InvalidArgumentException
         * @throws UnexpectedValueException
         */
        public function getLocations( $item, $limit, $from = self::RING_ALL ) {
@@ -128,22 +129,25 @@ class HashRing implements Serializable {
                        throw new InvalidArgumentException( "Invalid ring source specified." );
                }
 
-               // Locate this item's position on the hash ring
-               $position = $this->getItemPosition( $item );
-               $itemNodeIndex = $this->findNodeIndexForPosition( $position, $ring );
+               // Locate the node index for this item's position on the hash ring
+               $itemIndex = $this->findNodeIndexForPosition( $this->getItemPosition( $item ), $ring );
 
                $locations = [];
-               $currentIndex = $itemNodeIndex;
+               $currentIndex = null;
                while ( count( $locations ) < $limit ) {
+                       if ( $currentIndex === null ) {
+                               $currentIndex = $itemIndex;
+                       } else {
+                               $currentIndex = $this->getNextClockwiseNodeIndex( $currentIndex, $ring );
+                               if ( $currentIndex === $itemIndex ) {
+                                       break; // all nodes visited
+                               }
+                       }
                        $nodeLocation = $ring[$currentIndex][self::KEY_LOCATION];
                        if ( !in_array( $nodeLocation, $locations, true ) ) {
                                // Ignore other nodes for the same locations already added
                                $locations[] = $nodeLocation;
                        }
-                       $currentIndex = $this->getNextClockwiseNodeIndex( $currentIndex, $ring );
-                       if ( $currentIndex === $itemNodeIndex ) {
-                               break; // all nodes visited
-                       }
                }
 
                return $locations;
@@ -159,18 +163,22 @@ class HashRing implements Serializable {
                if ( $count === 0 ) {
                        return null;
                }
+
+               $index = null;
                $lowPos = 0;
                $highPos = $count;
                while ( true ) {
-                       $midPos = intval( ( $lowPos + $highPos ) / 2 );
+                       $midPos = (int)( ( $lowPos + $highPos ) / 2 );
                        if ( $midPos === $count ) {
-                               return 0;
+                               $index = 0;
+                               break;
                        }
-                       $midVal = $ring[$midPos][self::KEY_POS];
-                       $midMinusOneVal = $midPos === 0 ? 0 : $ring[$midPos - 1][self::KEY_POS];
 
+                       $midVal = $ring[$midPos][self::KEY_POS];
+                       $midMinusOneVal = ( $midPos === 0 ) ? 0 : $ring[$midPos - 1][self::KEY_POS];
                        if ( $position <= $midVal && $position > $midMinusOneVal ) {
-                               return $midPos;
+                               $index = $midPos;
+                               break;
                        }
 
                        if ( $midVal < $position ) {
@@ -180,9 +188,12 @@ class HashRing implements Serializable {
                        }
 
                        if ( $lowPos > $highPos ) {
-                               return 0;
+                               $index = 0;
+                               break;
                        }
                }
+
+               return $index;
        }
 
        /**
@@ -260,10 +271,9 @@ class HashRing implements Serializable {
 
        /**
         * @param int[] $weightByLocation
-        * @param string $algo Hashing algorithm
         * @return array[]
         */
-       private function buildLocationRing( array $weightByLocation, $algo ) {
+       private function buildLocationRing( array $weightByLocation ) {
                $locationCount = count( $weightByLocation );
                $totalWeight = array_sum( $weightByLocation );
 
@@ -323,7 +333,14 @@ class HashRing implements Serializable {
                        throw new UnexpectedValueException( __METHOD__ . ": {$this->algo} is < 32 bits." );
                }
 
-               return (float)sprintf( '%u', unpack( 'V', $octets )[1] );
+               $pos = unpack( 'V', $octets )[1];
+               if ( $pos < 0 ) {
+                       // Most-significant-bit is set, causing unpack() to return a negative integer due
+                       // to the fact that it returns a signed int. Cast it to an unsigned integer string.
+                       $pos = sprintf( '%u', $pos );
+               }
+
+               return (float)$pos;
        }
 
        /**
index c05dc28..c1a796f 100644 (file)
@@ -593,7 +593,7 @@ class FSFileBackend extends FileBackendStore {
                } elseif ( !$hadError ) {
                        return false; // file does not exist
                } else {
-                       return null; // failure
+                       return self::UNKNOWN; // failure
                }
        }
 
@@ -610,7 +610,7 @@ class FSFileBackend extends FileBackendStore {
                $exists = is_dir( $dir );
                $hadError = $this->untrapWarnings();
 
-               return $hadError ? null : $exists;
+               return $hadError ? self::UNKNOWN : $exists;
        }
 
        /**
@@ -632,7 +632,7 @@ class FSFileBackend extends FileBackendStore {
                } elseif ( !is_readable( $dir ) ) {
                        $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
 
-                       return null; // bad permissions?
+                       return self::UNKNOWN; // bad permissions?
                }
 
                return new FSFileBackendDirList( $dir, $params );
@@ -657,7 +657,7 @@ class FSFileBackend extends FileBackendStore {
                } elseif ( !is_readable( $dir ) ) {
                        $this->logger->warning( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
 
-                       return null; // bad permissions?
+                       return self::UNKNOWN; // bad permissions?
                }
 
                return new FSFileBackendFileList( $dir, $params );
index 4cacb7a..905e925 100644 (file)
@@ -131,6 +131,9 @@ abstract class FileBackend implements LoggerAwareInterface {
        const ATTR_METADATA = 2; // files can be stored with metadata key/values
        const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII)
 
+       /** @var null Idiom for "could not determine due to I/O errors" */
+       const UNKNOWN = null;
+
        /**
         * Create a new backend instance from configuration.
         * This should only be called from within FileBackendGroup.
@@ -209,7 +212,8 @@ abstract class FileBackend implements LoggerAwareInterface {
        }
 
        /**
-        * Get the unique backend name.
+        * Get the unique backend name
+        *
         * We may have multiple different backends of the same type.
         * For example, we can have two Swift backends using different proxies.
         *
@@ -231,6 +235,7 @@ abstract class FileBackend implements LoggerAwareInterface {
 
        /**
         * Alias to getDomainId()
+        *
         * @return string
         * @since 1.20
         * @deprecated Since 1.34 Use getDomainId()
@@ -1164,21 +1169,34 @@ abstract class FileBackend implements LoggerAwareInterface {
        abstract public function getFileHttpUrl( array $params );
 
        /**
-        * Check if a directory exists at a given storage path.
-        * Backends using key/value stores will check if the path is a
-        * virtual directory, meaning there are files under the given directory.
+        * Check if a directory exists at a given storage path
+        *
+        * For backends using key/value stores, a directory is said to exist whenever
+        * there exist any files with paths using the given directory path as a prefix
+        * followed by a forward slash. For example, if there is a file called
+        * "mwstore://backend/container/dir/path.svg" then directories are said to exist
+        * at "mwstore://backend/container" and "mwstore://backend/container/dir". These
+        * can be thought of as "virtual" directories.
+        *
+        * Backends that directly use a filesystem layer might enumerate empty directories.
+        * The clean() method should always be used when files are deleted or moved if this
+        * is a concern. This is a trade-off to avoid write amplication/contention on file
+        * changes or read amplification when calling this method.
         *
         * Storage backends with eventual consistency might return stale data.
         *
+        * @see FileBackend::clean()
+        *
         * @param array $params Parameters include:
         *   - dir : storage directory
-        * @return bool|null Returns null on failure
+        * @return bool|null Whether a directory exists or null on failure
         * @since 1.20
         */
        abstract public function directoryExists( array $params );
 
        /**
-        * Get an iterator to list *all* directories under a storage directory.
+        * Get an iterator to list *all* directories under a storage directory
+        *
         * If the directory is of the form "mwstore://backend/container",
         * then all directories in the container will be listed.
         * If the directory is of form "mwstore://backend/container/dir",
@@ -1189,10 +1207,12 @@ abstract class FileBackend implements LoggerAwareInterface {
         *
         * Failures during iteration can result in FileBackendError exceptions (since 1.22).
         *
+        * @see FileBackend::directoryExists()
+        *
         * @param array $params Parameters include:
         *   - dir     : storage directory
         *   - topOnly : only return direct child dirs of the directory
-        * @return Traversable|array|null Returns null on failure
+        * @return Traversable|array|null Directory list enumerator null on failure
         * @since 1.20
         */
        abstract public function getDirectoryList( array $params );
@@ -1205,9 +1225,11 @@ abstract class FileBackend implements LoggerAwareInterface {
         *
         * Failures during iteration can result in FileBackendError exceptions (since 1.22).
         *
+        * @see FileBackend::directoryExists()
+        *
         * @param array $params Parameters include:
         *   - dir : storage directory
-        * @return Traversable|array|null Returns null on failure
+        * @return Traversable|array|null Directory list enumerator or null on failure
         * @since 1.20
         */
        final public function getTopDirectoryList( array $params ) {
@@ -1215,12 +1237,12 @@ abstract class FileBackend implements LoggerAwareInterface {
        }
 
        /**
-        * Get an iterator to list *all* stored files under a storage directory.
-        * If the directory is of the form "mwstore://backend/container",
-        * then all files in the container will be listed.
-        * If the directory is of form "mwstore://backend/container/dir",
-        * then all files under that directory will be listed.
-        * Results will be storage paths relative to the given directory.
+        * Get an iterator to list *all* stored files under a storage directory
+        *
+        * If the directory is of the form "mwstore://backend/container", then all
+        * files in the container will be listed. If the directory is of form
+        * "mwstore://backend/container/dir", then all files under that directory will
+        * be listed. Results will be storage paths relative to the given directory.
         *
         * Storage backends with eventual consistency might return stale data.
         *
@@ -1230,7 +1252,7 @@ abstract class FileBackend implements LoggerAwareInterface {
         *   - dir        : storage directory
         *   - topOnly    : only return direct child files of the directory (since 1.20)
         *   - adviseStat : set to true if stat requests will be made on the files (since 1.22)
-        * @return Traversable|array|null Returns null on failure
+        * @return Traversable|array|null File list enumerator or null on failure
         */
        abstract public function getFileList( array $params );
 
@@ -1245,7 +1267,7 @@ abstract class FileBackend implements LoggerAwareInterface {
         * @param array $params Parameters include:
         *   - dir        : storage directory
         *   - adviseStat : set to true if stat requests will be made on the files (since 1.22)
-        * @return Traversable|array|null Returns null on failure
+        * @return Traversable|array|null File list enumerator or null on failure
         * @since 1.20
         */
        final public function getTopFileList( array $params ) {
@@ -1283,7 +1305,7 @@ abstract class FileBackend implements LoggerAwareInterface {
         * @param array $params Parameters include:
         *   - srcs        : list of source storage paths
         *   - latest      : use the latest available data
-        * @return bool All requests proceeded without I/O errors (since 1.24)
+        * @return bool Whether all requests proceeded without I/O errors (since 1.24)
         * @since 1.23
         */
        abstract public function preloadFileStat( array $params );
@@ -1526,7 +1548,7 @@ abstract class FileBackend implements LoggerAwareInterface {
         *
         * @param string $type One of (attachment, inline)
         * @param string $filename Suggested file name (should not contain slashes)
-        * @throws FileBackendError
+        * @throws InvalidArgumentException
         * @return string
         * @since 1.20
         */
index aa95ee4..9b901dd 100644 (file)
@@ -604,7 +604,7 @@ abstract class FileBackendStore extends FileBackend {
                $ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
                $stat = $this->getFileStat( $params );
 
-               return ( $stat === null ) ? null : (bool)$stat; // null => failure
+               return ( $stat === self::UNKNOWN ) ? self::UNKNOWN : (bool)$stat;
        }
 
        final public function getFileTimestamp( array $params ) {
@@ -637,7 +637,7 @@ abstract class FileBackendStore extends FileBackend {
                        // cache entries from mass object listings that do not include the SHA-1. In that
                        // case, loading the persistent stat cache will likely yield the SHA-1.
                        if (
-                               $stat === null ||
+                               $stat === self::UNKNOWN ||
                                ( $requireSHA1 && is_array( $stat ) && !isset( $stat['sha1'] ) )
                        ) {
                                $this->primeFileCache( [ $path ] ); // check persistent cache
@@ -936,7 +936,7 @@ abstract class FileBackendStore extends FileBackend {
                                        $res = true;
                                        break; // found one!
                                } elseif ( $exists === null ) { // error?
-                                       $res = null; // if we don't find anything, it is indeterminate
+                                       $res = self::UNKNOWN; // if we don't find anything, it is indeterminate
                                }
                        }
 
@@ -957,7 +957,7 @@ abstract class FileBackendStore extends FileBackend {
        final public function getDirectoryList( array $params ) {
                list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
                if ( $dir === null ) { // invalid storage path
-                       return null;
+                       return self::UNKNOWN;
                }
                if ( $shard !== null ) {
                        // File listing is confined to a single container/shard
@@ -987,7 +987,7 @@ abstract class FileBackendStore extends FileBackend {
        final public function getFileList( array $params ) {
                list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
                if ( $dir === null ) { // invalid storage path
-                       return null;
+                       return self::UNKNOWN;
                }
                if ( $shard !== null ) {
                        // File listing is confined to a single container/shard
index f0cbf3b..88b281e 100644 (file)
@@ -148,7 +148,7 @@ class MemoryFileBackend extends FileBackendStore {
        protected function doGetFileStat( array $params ) {
                $src = $this->resolveHashKey( $params['src'] );
                if ( $src === null ) {
-                       return null;
+                       return false; // invalid path
                }
 
                if ( isset( $this->files[$src] ) ) {
index afd1688..e576c64 100644 (file)
@@ -593,7 +593,7 @@ class SwiftFileBackend extends FileBackendStore {
                $stat = $this->getContainerStat( $fullCont );
                if ( is_array( $stat ) ) {
                        return $status; // already there
-               } elseif ( $stat === null ) {
+               } elseif ( $stat === self::UNKNOWN ) {
                        $status->fatal( 'backend-fail-internal', $this->name );
                        $this->logger->error( __METHOD__ . ': cannot get container stat' );
 
@@ -832,7 +832,7 @@ class SwiftFileBackend extends FileBackendStore {
                        return ( count( $status->value ) ) > 0;
                }
 
-               return null; // error
+               return self::UNKNOWN; // error
        }
 
        /**
@@ -1401,7 +1401,7 @@ class SwiftFileBackend extends FileBackendStore {
                if ( !$this->containerStatCache->hasField( $container, 'stat' ) ) {
                        $auth = $this->getAuthentication();
                        if ( !$auth ) {
-                               return null;
+                               return self::UNKNOWN;
                        }
 
                        list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( [
@@ -1427,7 +1427,7 @@ class SwiftFileBackend extends FileBackendStore {
                                $this->onError( null, __METHOD__,
                                        [ 'cont' => $container ], $rerr, $rcode, $rdesc );
 
-                               return null;
+                               return self::UNKNOWN;
                        }
                }
 
@@ -1599,7 +1599,7 @@ class SwiftFileBackend extends FileBackendStore {
                                $stats[$path] = false;
                                continue; // invalid storage path
                        } elseif ( !$auth ) {
-                               $stats[$path] = null;
+                               $stats[$path] = self::UNKNOWN;
                                continue;
                        }
 
@@ -1609,7 +1609,7 @@ class SwiftFileBackend extends FileBackendStore {
                                $stats[$path] = false;
                                continue; // ok, nothing to do
                        } elseif ( !is_array( $cstat ) ) {
-                               $stats[$path] = null;
+                               $stats[$path] = self::UNKNOWN;
                                continue;
                        }
 
@@ -1642,7 +1642,7 @@ class SwiftFileBackend extends FileBackendStore {
                        } elseif ( $rcode === 404 ) {
                                $stat = false;
                        } else {
-                               $stat = null;
+                               $stat = self::UNKNOWN;
                                $this->onError( null, __METHOD__, $params, $rerr, $rcode, $rdesc );
                        }
                        $stats[$path] = $stat;
index 8afaa38..0383def 100644 (file)
@@ -182,11 +182,6 @@ class MSCompoundFileReader {
                return $this->unpack( $block, 0, $struct );
        }
 
-       private function unpackSector( $sectorNumber, $struct ) {
-               $offset = $this->sectorOffset( $sectorNumber );
-               return $this->unpackOffset( $offset, array_sum( $struct ) );
-       }
-
        private function unpack( $block, $offset, $struct ) {
                $data = [];
                foreach ( $struct as $key => $length ) {
index 66be436..4179bd5 100644 (file)
@@ -220,21 +220,6 @@ class LogEventsList extends ContextSource {
                ];
        }
 
-       private function getDefaultQuery() {
-               if ( !isset( $this->mDefaultQuery ) ) {
-                       $this->mDefaultQuery = $this->getRequest()->getQueryValues();
-                       unset( $this->mDefaultQuery['title'] );
-                       unset( $this->mDefaultQuery['dir'] );
-                       unset( $this->mDefaultQuery['offset'] );
-                       unset( $this->mDefaultQuery['limit'] );
-                       unset( $this->mDefaultQuery['order'] );
-                       unset( $this->mDefaultQuery['month'] );
-                       unset( $this->mDefaultQuery['year'] );
-               }
-
-               return $this->mDefaultQuery;
-       }
-
        /**
         * @param array $queryTypes
         * @return array Form descriptor
index ad0f67e..8ffe824 100644 (file)
@@ -204,9 +204,6 @@ class ObjectCache {
                                if ( !isset( $params['servers'] ) ) {
                                        $params['servers'] = $GLOBALS['wgMemCachedServers'];
                                }
-                               if ( !isset( $params['debug'] ) ) {
-                                       $params['debug'] = $GLOBALS['wgMemCachedDebug'];
-                               }
                                if ( !isset( $params['persistent'] ) ) {
                                        $params['persistent'] = $GLOBALS['wgMemCachedPersistent'];
                                }
@@ -393,12 +390,19 @@ class ObjectCache {
         */
        public static function detectLocalServerCache() {
                if ( function_exists( 'apcu_fetch' ) ) {
-                       return 'apcu';
+                       // Make sure the APCu methods actually store anything
+                       if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
+                               return 'apcu';
+                       }
                } elseif ( function_exists( 'apc_fetch' ) ) {
-                       return 'apc';
+                       // Make sure the APC methods actually store anything
+                       if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
+                               return 'apc';
+                       }
                } elseif ( function_exists( 'wincache_ucache_get' ) ) {
                        return 'wincache';
                }
+
                return CACHE_NONE;
        }
 }
index fcfb83d..d8cd1c5 100644 (file)
@@ -635,8 +635,9 @@ class Article implements Page {
                if ( $outputPage->isPrintable() ) {
                        $parserOptions->setIsPrintable( true );
                        $poOptions['enableSectionEditLinks'] = false;
-               } elseif ( $this->viewIsRenderAction
-                       || !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user )
+               } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
+                       !MediaWikiServices::getInstance()->getPermissionManager()
+                               ->quickUserCan( 'edit', $user, $this->getTitle() )
                ) {
                        $poOptions['enableSectionEditLinks'] = false;
                }
@@ -1181,7 +1182,8 @@ class Article implements Page {
                $title = $this->getTitle();
                $rc = false;
 
-               if ( !$title->quickUserCan( 'patrol', $user )
+               if ( !MediaWikiServices::getInstance()->getPermissionManager()
+                               ->quickUserCan( 'patrol', $user, $title )
                        || !( $wgUseRCPatrol || $wgUseNPPatrol
                                || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
                ) {
@@ -1450,6 +1452,7 @@ class Article implements Page {
 
                # Show error message
                $oldid = $this->getOldID();
+               $pm = MediaWikiServices::getInstance()->getPermissionManager();
                if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
                        // use fake Content object for system message
                        $parserOptions = ParserOptions::newCanonical( 'canonical' );
@@ -1457,8 +1460,8 @@ class Article implements Page {
                } else {
                        if ( $oldid ) {
                                $text = wfMessage( 'missing-revision', $oldid )->plain();
-                       } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
-                               && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
+                       } elseif ( $pm->quickUserCan( 'create', $this->getContext()->getUser(), $title ) &&
+                               $pm->quickUserCan( 'edit', $this->getContext()->getUser(), $title )
                        ) {
                                $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
                                $text = wfMessage( $message )->plain();
index 2de82bf..dc75541 100644 (file)
@@ -118,6 +118,7 @@ class ImageHistoryList extends ContextSource {
        public function imageHistoryLine( $iscur, $file ) {
                $user = $this->getUser();
                $lang = $this->getLanguage();
+               $pm = MediaWikiServices::getInstance()->getPermissionManager();
                $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
                $img = $iscur ? $file->getName() : $file->getArchiveName();
                $userId = $file->getUser( 'id' );
@@ -128,9 +129,7 @@ class ImageHistoryList extends ContextSource {
                $row = $selected = '';
 
                // Deletion link
-               if ( $local && ( MediaWikiServices::getInstance()
-                               ->getPermissionManager()
-                               ->userHasAnyRight( $user, 'delete', 'deletedhistory' ) )
+               if ( $local && ( $pm->userHasAnyRight( $user, 'delete', 'deletedhistory' ) )
                ) {
                        $row .= '<td>';
                        # Link to remove from history
@@ -173,8 +172,8 @@ class ImageHistoryList extends ContextSource {
                $row .= '<td>';
                if ( $iscur ) {
                        $row .= $this->msg( 'filehist-current' )->escaped();
-               } elseif ( $local && $this->title->quickUserCan( 'edit', $user )
-                       && $this->title->quickUserCan( 'upload', $user )
+               } elseif ( $local && $pm->quickUserCan( 'edit', $user, $this->title )
+                       && $pm->quickUserCan( 'upload', $user, $this->title )
                ) {
                        if ( $file->isDeleted( File::DELETED_FILE ) ) {
                                $row .= $this->msg( 'filehist-revert' )->escaped();
index 4f08995..2e43e8c 100644 (file)
@@ -748,7 +748,8 @@ EOT
                        return;
                }
 
-               $canUpload = $this->getTitle()->quickUserCan( 'upload', $this->getContext()->getUser() );
+               $canUpload = MediaWikiServices::getInstance()->getPermissionManager()
+                       ->quickUserCan( 'upload', $this->getContext()->getUser(), $this->getTitle() );
                if ( $canUpload && UploadBase::userCanReUpload(
                                $this->getContext()->getUser(),
                                $this->mPage->getFile() )
index 212ac47..b0d0678 100644 (file)
@@ -461,7 +461,9 @@ abstract class Skin extends ContextSource {
                                $type = 'ns-subject';
                        }
                        // T208315: add HTML class when the user can edit the page
-                       if ( $title->quickUserCan( 'edit', $user ) ) {
+                       if ( MediaWikiServices::getInstance()->getPermissionManager()
+                                       ->quickUserCan( 'edit', $user, $title )
+                       ) {
                                $type .= ' mw-editable';
                        }
                }
@@ -725,14 +727,17 @@ abstract class Skin extends ContextSource {
                $action = $this->getRequest()->getVal( 'action', 'view' );
                $title = $this->getTitle();
                $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
                if ( ( !$title->exists() || $action == 'history' ) &&
-                       $title->quickUserCan( 'deletedhistory', $this->getUser() )
+                       $permissionManager->quickUserCan( 'deletedhistory', $this->getUser(), $title )
                ) {
                        $n = $title->isDeleted();
 
                        if ( $n ) {
-                               if ( $this->getTitle()->quickUserCan( 'undelete', $this->getUser() ) ) {
+                               if ( $permissionManager->quickUserCan( 'undelete',
+                                               $this->getUser(), $this->getTitle() )
+                               ) {
                                        $msg = 'thisisdeleted';
                                } else {
                                        $msg = 'viewdeleted';
index aeca016..3e8972c 100644 (file)
@@ -884,6 +884,7 @@ class SkinTemplate extends Skin {
                $out = $this->getOutput();
                $request = $this->getRequest();
                $user = $this->getUser();
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
                $content_navigation = [
                        'namespaces' => [],
@@ -895,7 +896,7 @@ class SkinTemplate extends Skin {
                // parameters
                $action = $request->getVal( 'action', 'view' );
 
-               $userCanRead = $title->quickUserCan( 'read', $user );
+               $userCanRead = $permissionManager->quickUserCan( 'read', $user, $title );
 
                // Avoid PHP 7.1 warning of passing $this by reference
                $skinTemplate = $this;
@@ -965,8 +966,9 @@ class SkinTemplate extends Skin {
                                }
 
                                // Checks if user can edit the current page if it exists or create it otherwise
-                               if ( $title->quickUserCan( 'edit', $user )
-                                       && ( $title->exists() || $title->quickUserCan( 'create', $user ) )
+                               if ( $permissionManager->quickUserCan( 'edit', $user, $title ) &&
+                                        ( $title->exists() ||
+                                                $permissionManager->quickUserCan( 'create', $user, $title ) )
                                ) {
                                        // Builds CSS class for talk page links
                                        $isTalkClass = $isTalk ? ' istalk' : '';
@@ -1031,7 +1033,7 @@ class SkinTemplate extends Skin {
                                                'href' => $title->getLocalURL( 'action=history' ),
                                        ];
 
-                                       if ( $title->quickUserCan( 'delete', $user ) ) {
+                                       if ( $permissionManager->quickUserCan( 'delete', $user, $title ) ) {
                                                $content_navigation['actions']['delete'] = [
                                                        'class' => ( $onPage && $action == 'delete' ) ? 'selected' : false,
                                                        'text' => wfMessageFallback( "$skname-action-delete", 'delete' )
@@ -1040,7 +1042,7 @@ class SkinTemplate extends Skin {
                                                ];
                                        }
 
-                                       if ( $title->quickUserCan( 'move', $user ) ) {
+                                       if ( $permissionManager->quickUserCan( 'move', $user, $title ) ) {
                                                $moveTitle = SpecialPage::getTitleFor( 'Movepage', $title->getPrefixedDBkey() );
                                                $content_navigation['actions']['move'] = [
                                                        'class' => $this->getTitle()->isSpecial( 'Movepage' ) ? 'selected' : false,
@@ -1051,13 +1053,14 @@ class SkinTemplate extends Skin {
                                        }
                                } else {
                                        // article doesn't exist or is deleted
-                                       if ( $title->quickUserCan( 'deletedhistory', $user ) ) {
+                                       if ( $permissionManager->quickUserCan( 'deletedhistory', $user, $title ) ) {
                                                $n = $title->isDeleted();
                                                if ( $n ) {
                                                        $undelTitle = SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() );
                                                        // If the user can't undelete but can view deleted
                                                        // history show them a "View .. deleted" tab instead.
-                                                       $msgKey = $title->quickUserCan( 'undelete', $user ) ? 'undelete' : 'viewdeleted';
+                                                       $msgKey = $permissionManager->quickUserCan( 'undelete',
+                                                               $user, $title ) ? 'undelete' : 'viewdeleted';
                                                        $content_navigation['actions']['undelete'] = [
                                                                'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
                                                                'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" )
@@ -1068,9 +1071,10 @@ class SkinTemplate extends Skin {
                                        }
                                }
 
-                               if ( $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() &&
-                                       MediaWikiServices::getInstance()->getPermissionManager()
-                                               ->getNamespaceRestrictionLevels( $title->getNamespace(), $user ) !== [ '' ]
+                               if ( $permissionManager->quickUserCan( 'protect', $user, $title ) &&
+                                        $title->getRestrictionTypes() &&
+                                        $permissionManager->getNamespaceRestrictionLevels( $title->getNamespace(),
+                                                $user ) !== [ '' ]
                                ) {
                                        $mode = $title->isProtected() ? 'unprotect' : 'protect';
                                        $content_navigation['actions'][$mode] = [
@@ -1082,9 +1086,8 @@ class SkinTemplate extends Skin {
                                }
 
                                // Checks if the user is logged in
-                               if ( $this->loggedin && MediaWikiServices::getInstance()
-                                               ->getPermissionManager()
-                                               ->userHasAllRights( $user, 'viewmywatchlist', 'editmywatchlist' )
+                               if ( $this->loggedin && $permissionManager->userHasAllRights( $user,
+                                               'viewmywatchlist', 'editmywatchlist' )
                                ) {
                                        /**
                                         * The following actions use messages which, if made particular to
index da34d81..85f65bb 100644 (file)
@@ -197,7 +197,8 @@ class MovePageForm extends UnlistedSpecialPage {
                }
 
                if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'articleexists'
-                       && $newTitle->quickUserCan( 'delete', $user )
+                       && MediaWikiServices::getInstance()->getPermissionManager()
+                                ->quickUserCan( 'delete', $user, $newTitle )
                ) {
                        $out->wrapWikiMsg(
                                "<div class='warningbox'>\n$1\n</div>\n",
index ad045e4..2ae4afc 100644 (file)
@@ -518,7 +518,8 @@ class SpecialSearch extends SpecialPage {
                                $messageName = 'searchmenu-exists';
                                $linkClass = 'mw-search-exists';
                        } elseif ( ContentHandler::getForTitle( $title )->supportsDirectEditing()
-                               && $title->quickUserCan( 'create', $this->getUser() )
+                               && MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan( 'create',
+                                       $this->getUser(), $title )
                        ) {
                                $messageName = 'searchmenu-new';
                        }
index d62951c..b80a584 100644 (file)
@@ -439,6 +439,16 @@ class ContribsPager extends RangeChronologicalPager {
                return $this->tagFilter;
        }
 
+       /**
+        * @deprecated since 1.34, redundant.
+        *
+        * @return string "users"
+        */
+       public function getContribs() {
+               // Brought back for backwards compatibility, see T231540.
+               return 'users';
+       }
+
        /**
         * @return string
         */
@@ -573,6 +583,7 @@ class ContribsPager extends RangeChronologicalPager {
                $attribs = [];
 
                $linkRenderer = $this->getLinkRenderer();
+               $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
 
                $page = null;
                // Create a title for the revision if possible
@@ -598,8 +609,9 @@ class ContribsPager extends RangeChronologicalPager {
                                $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
                                $classes[] = 'mw-contributions-current';
                                # Add rollback link
-                               if ( !$row->page_is_new && $page->quickUserCan( 'rollback', $user )
-                                       && $page->quickUserCan( 'edit', $user )
+                               if ( !$row->page_is_new &&
+                                       $permissionManager->quickUserCan( 'rollback', $user, $page ) &&
+                                       $permissionManager->quickUserCan( 'edit', $user, $page )
                                ) {
                                        $this->preventClickjacking();
                                        $topmarktext .= ' ' . Linker::generateRollback( $rev, $this->getContext(),
index 106d6a7..3cb9c66 100644 (file)
        "nocreate-loggedin": "You do not have permission to create new pages.",
        "sectioneditnotsupported-title": "Section editing not supported",
        "sectioneditnotsupported-text": "Section editing is not supported in this page.",
+       "modeleditnotsupported-title": "Editing not supported",
+       "modeleditnotsupported-text": "Editing is not supported for content model $1.",
        "permissionserrors": "Permission error",
        "permissionserrorstext": "You do not have permission to do that, for the following {{PLURAL:$1|reason|reasons}}:",
        "permissionserrorstext-withaction": "You do not have permission to $2, for the following {{PLURAL:$1|reason|reasons}}:",
        "content-model-json": "JSON",
        "content-json-empty-object": "Empty object",
        "content-json-empty-array": "Empty array",
+       "unsupported-content-model": "<strong>Warning:</strong> Content model $1 is not supported on this wiki.",
+       "unsupported-content-diff": "Diffs are not supported for content model $1.",
+       "unsupported-content-diff2": "Diffs between the content models $1 and $2 are not supported on this wiki.",
        "deprecated-self-close-category": "Pages using invalid self-closed HTML tags",
        "deprecated-self-close-category-desc": "The page contains invalid self-closed HTML tags, such as <code>&lt;b/></code> or <code>&lt;span/></code>.  The behavior of these will change soon to be consistent with the HTML5 specification, so their use in wikitext is deprecated.",
        "duplicate-args-warning": "<strong>Warning:</strong> [[:$1]] is calling [[:$2]] with more than one value for the \"$3\" parameter. Only the last value provided will be used.",
index 5fdcb7d..03c1da4 100644 (file)
        "nocreate-loggedin": "Used as error message.\n\nSee also:\n* {{msg-mw|Nocreatetext}}",
        "sectioneditnotsupported-title": "Page title of special page, which presumably appears when someone tries to edit a section, and section editing is disabled. Explanation of section editing on [[meta:Help:Section_editing#Section_editing|meta]].",
        "sectioneditnotsupported-text": "I think this is the text of an error message, which presumably appears when someone tries to edit a section, and section editing is disabled. Explanation of section editing on [[meta:Help:Section_editing#Section_editing|meta]].",
+       "modeleditnotsupported-title": "Page title used on the edit page when editing is not supported for the page's content model.",
+       "modeleditnotsupported-text": "Error message show on the edit page when editing is not supported for the page's content model..\n\nParameters:\n* $1 - the name of the content model.",
        "permissionserrors": "Used as title of error message.\n\nSee also:\n* {{msg-mw|loginreqtitle}}\n{{Identical|Permission error}}",
        "permissionserrorstext": "This message is \"without action\" version of {{msg-mw|Permissionserrorstext-withaction}}.\n\nParameters:\n* $1 - the number of reasons that were found why ''the action'' cannot be performed",
        "permissionserrorstext-withaction": "This message is \"with action\" version of {{msg-mw|Permissionserrorstext}}.\n\nParameters:\n* $1 - the number of reasons that were found why the action cannot be performed\n* $2 - one of the action-* messages (for example {{msg-mw|action-edit}}) or other such messages tagged with {{tl|doc-action}} in their documentation\n\nPlease report at [[Support]] if you are unable to properly translate this message. Also see [[phab:T16246]] (now closed) for background.",
        "content-model-json": "{{optional}}\nName for the JSON content model, used when decribing what type of content a page contains.\n\nThis message is substituted in:\n*{{msg-mw|Bad-target-model}}\n*{{msg-mw|Content-not-allowed-here}}\n{{identical|JSON}}",
        "content-json-empty-object": "Used to represent an object with no properties on a JSON content model page.",
        "content-json-empty-array": "Used to represent an array with no values on a JSON content model page.",
+       "unsupported-content-model": "Warning shown when trying to display content with an unknown model.\n\nParameters:\n* $1 - the technical name of the content model.",
+       "unsupported-content-diff": "Warning shown when trying to display a diff between content with a model that does not support diffing (perhaps because it's an unknown model).\n\nParameters:\n* $1 - the technical name of the model of the content",
+       "unsupported-content-diff2": "Warning shown when trying to display a diff between content that uses models that do not support diffing with each other.\n\nParameters:\n* $1 - the technical name of the model of the old content\n* $2 - the technical name of the model of the new content.",
        "deprecated-self-close-category": "This message is used as a category name for a [[mw:Special:MyLanguage/Help:Tracking categories|tracking category]] where pages are placed automatically if they contain invalid self-closed HTML tags, such as <code>&lt;b/></code> or <code>&lt;span/></code>.  The behavior of these will change soon to be consistent with the HTML5 specification, so their use in wikitext is deprecated.",
        "deprecated-self-close-category-desc": "Invalid self-closed HTML tag category description. Shown on [[Special:TrackingCategories]].\n\nSee also:\n* {{msg-mw|deprecated-self-close-category}}",
        "duplicate-args-warning": "If a page calls a template and specifies the same argument more than once, such as <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> or <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>, this warning is displayed when previewing.\n\nParameters:\n* $1 - The calling page\n* $2 - The called template\n* $3 - The name of the duplicated argument",
index 1def4bd..0b49359 100644 (file)
       }
     },
     "eslint-utils": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz",
-      "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==",
-      "dev": true
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
+      "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
+      "dev": true,
+      "requires": {
+        "eslint-visitor-keys": "^1.0.0"
+      }
     },
     "eslint-visitor-keys": {
       "version": "1.0.0",
index 9f1d67b..6cd7811 100644 (file)
@@ -224,6 +224,9 @@ $wgAutoloadClasses += [
        # tests/phpunit/unit/includes/filebackend
        'FileBackendGroupTestTrait' => "$testDir/phpunit/unit/includes/filebackend/FileBackendGroupTestTrait.php",
 
+       # tests/phpunit/unit/includes/language
+       'LanguageFallbackTestTrait' => "$testDir/phpunit/unit/includes/language/LanguageFallbackTestTrait.php",
+
        # tests/phpunit/unit/includes/libs/filebackend/fsfile
        'TempFSFileTestTrait' => "$testDir/phpunit/unit/includes/libs/filebackend/fsfile/TempFSFileTestTrait.php",
 
index 33518ef..83f27e8 100644 (file)
@@ -584,24 +584,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                $this->tmpFiles = array_merge( $this->tmpFiles, (array)$files );
        }
 
-       // @todo Make const when we no longer support HHVM (T192166)
-       private static $namespaceAffectingSettings = [
-               'wgAllowImageMoving',
-               'wgCanonicalNamespaceNames',
-               'wgCapitalLinkOverrides',
-               'wgCapitalLinks',
-               'wgContentNamespaces',
-               'wgExtensionMessagesFiles',
-               'wgExtensionNamespaces',
-               'wgExtraNamespaces',
-               'wgExtraSignatureNamespaces',
-               'wgNamespaceContentModels',
-               'wgNamespaceProtection',
-               'wgNamespacesWithSubpages',
-               'wgNonincludableNamespaces',
-               'wgRestrictionLevels',
-       ];
-
        protected function tearDown() {
                global $wgRequest, $wgSQLMode;
 
@@ -648,12 +630,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                foreach ( $this->iniSettings as $name => $value ) {
                        ini_set( $name, $value );
                }
-               if (
-                       array_intersect( self::$namespaceAffectingSettings, array_keys( $this->mwGlobals ) ) ||
-                       array_intersect( self::$namespaceAffectingSettings, $this->mwGlobalsToUnset )
-               ) {
-                       $this->resetNamespaces();
-               }
                $this->mwGlobals = [];
                $this->mwGlobalsToUnset = [];
                $this->restoreLoggers();
@@ -742,7 +718,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                );
 
                if ( $name === 'ContentLanguage' ) {
-                       $this->doSetMwGlobals( [ 'wgContLang' => $this->localServices->getContentLanguage() ] );
+                       $this->setMwGlobals( [ 'wgContLang' => $this->localServices->getContentLanguage() ] );
                }
        }
 
@@ -753,9 +729,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
         * The key is added to the array of globals that will be reset afterwards
         * in the tearDown().
         *
-        * It may be necessary to call resetServices() to allow any changed configuration variables
-        * to take effect on services that get initialized based on these variables.
-        *
         * @par Example
         * @code
         *     protected function setUp() {
@@ -779,8 +752,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
         * @param mixed|null $value Value to set the global to (ignored
         *  if an array is given as first argument).
         *
-        * @note To allow changes to global variables to take effect on global service instances,
-        *       call resetServices().
+        * @note This will call resetServices().
         *
         * @since 1.21
         */
@@ -789,29 +761,13 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                        $pairs = [ $pairs => $value ];
                }
 
-               if ( isset( $pairs['wgContLang'] ) ) {
-                       throw new MWException(
-                               'No setting $wgContLang, use setContentLang() or setService( \'ContentLanguage\' )'
-                       );
-               }
-
-               $this->doSetMwGlobals( $pairs, $value );
-       }
-
-       /**
-        * An internal method that allows setService() to set globals that tests are not supposed to
-        * touch.
-        */
-       private function doSetMwGlobals( $pairs, $value = null ) {
                $this->doStashMwGlobals( array_keys( $pairs ) );
 
                foreach ( $pairs as $key => $value ) {
                        $GLOBALS[$key] = $value;
                }
 
-               if ( array_intersect( self::$namespaceAffectingSettings, array_keys( $pairs ) ) ) {
-                       $this->resetNamespaces();
-               }
+               $this->resetServices();
        }
 
        /**
@@ -826,23 +782,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                ini_set( $name, $value );
        }
 
-       /**
-        * Must be called whenever namespaces are changed, e.g., $wgExtraNamespaces is altered.
-        * Otherwise old namespace data will lurk and cause bugs.
-        */
-       private function resetNamespaces() {
-               if ( !$this->localServices ) {
-                       throw new Exception( __METHOD__ . ' must be called after MediaWikiTestCase::run()' );
-               }
-
-               if ( $this->localServices !== MediaWikiServices::getInstance() ) {
-                       throw new Exception( __METHOD__ . ' will not work because the global MediaWikiServices '
-                               . 'instance has been replaced by test code.' );
-               }
-
-               Language::clearCaches();
-       }
-
        /**
         * Check if we can back up a value by performing a shallow copy.
         * Values which fail this test are copied recursively.
@@ -939,16 +878,12 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
         * Useful for setting some entries in a configuration array, instead of
         * setting the entire array.
         *
-        * It may be necessary to call resetServices() to allow any changed configuration variables
-        * to take effect on services that get initialized based on these variables.
-        *
         * @param string $name The name of the global, as in wgFooBar
         * @param array $values The array containing the entries to set in that global
         *
         * @throws MWException If the designated global is not an array.
         *
-        * @note To allow changes to global variables to take effect on global service instances,
-        *       call resetServices().
+        * @note This will call resetServices().
         *
         * @since 1.21
         */
@@ -998,6 +933,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                }
 
                self::resetGlobalParser();
+               Language::clearCaches();
        }
 
        /**
@@ -1182,16 +1118,11 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
         */
        public function setContentLang( $lang ) {
                if ( $lang instanceof Language ) {
-                       $this->setMwGlobals( 'wgLanguageCode', $lang->getCode() );
                        // Set to the exact object requested
                        $this->setService( 'ContentLanguage', $lang );
+                       $this->setMwGlobals( 'wgLanguageCode', $lang->getCode() );
                } else {
                        $this->setMwGlobals( 'wgLanguageCode', $lang );
-                       // Let the service handler make up the object.  Avoid calling setService(), because if
-                       // we do, overrideMwServices() will complain if it's called later on.
-                       $services = MediaWikiServices::getInstance();
-                       $services->resetServiceForTesting( 'ContentLanguage' );
-                       $this->doSetMwGlobals( [ 'wgContLang' => $services->getContentLanguage() ] );
                }
        }
 
@@ -1202,6 +1133,8 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
         * or three values to set a single permission, like
         *   $this->setGroupPermissions( '*', 'read', false );
         *
+        * @note This will call resetServices().
+        *
         * @since 1.31
         * @param array|string $newPerms Either an array of permissions to change,
         *   in which case the next two parameters are ignored; or a single string
@@ -1224,11 +1157,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
                }
 
                $this->setMwGlobals( 'wgGroupPermissions', $newPermissions );
-
-               // Reset services so they pick up the new permissions.
-               // Resetting just PermissionManager is not sufficient, since other services may
-               // have the old instance of PermissionManager injected.
-               $this->resetServices();
        }
 
        /**
@@ -2422,6 +2350,9 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
        /**
         * Create a temporary hook handler which will be reset by tearDown.
         * This replaces other handlers for the same hook.
+        *
+        * @note This will call resetServices().
+        *
         * @param string $hookName Hook name
         * @param mixed $handler Value suitable for a hook handler
         * @since 1.28
index 77d7c04..f047d82 100644 (file)
@@ -17,4 +17,16 @@ trait MediaWikiTestCaseTrait {
                        ...array_map( [ $this, 'matches' ], $values )
                ) );
        }
+
+       /**
+        * Return a PHPUnit mock that is expected to never have any methods called on it.
+        *
+        * @param string $type
+        * @return object
+        */
+       protected function createNoOpMock( $type ) {
+               $mock = $this->createMock( $type );
+               $mock->expects( $this->never() )->method( $this->anything() );
+               return $mock;
+       }
 }
index ccf3357..edd8195 100644 (file)
@@ -72,4 +72,34 @@ abstract class MediaWikiUnitTestCase extends TestCase {
                global $wgHooks;
                $wgHooks[$hookName] = [ $handler ];
        }
+
+       protected function getMockMessage( $text, ...$params ) {
+               if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+                       $params = $params[0];
+               }
+
+               $msg = $this->getMockBuilder( Message::class )
+                       ->disableOriginalConstructor()
+                       ->setMethods( [] )
+                       ->getMock();
+
+               $msg->method( 'toString' )->willReturn( $text );
+               $msg->method( '__toString' )->willReturn( $text );
+               $msg->method( 'text' )->willReturn( $text );
+               $msg->method( 'parse' )->willReturn( $text );
+               $msg->method( 'plain' )->willReturn( $text );
+               $msg->method( 'parseAsBlock' )->willReturn( $text );
+               $msg->method( 'escaped' )->willReturn( $text );
+
+               $msg->method( 'title' )->willReturn( $msg );
+               $msg->method( 'inLanguage' )->willReturn( $msg );
+               $msg->method( 'inContentLanguage' )->willReturn( $msg );
+               $msg->method( 'useDatabase' )->willReturn( $msg );
+               $msg->method( 'setContext' )->willReturn( $msg );
+
+               $msg->method( 'exists' )->willReturn( true );
+               $msg->method( 'content' )->willReturn( new MessageContent( $msg ) );
+
+               return $msg;
+       }
 }
index 40c45dc..46c0c42 100644 (file)
@@ -658,7 +658,6 @@ class ActorMigrationTest extends MediaWikiLangTestCase {
                        // for User::getActorId()
                        'wgActorTableSchemaMigrationStage' => $stage
                ] );
-               $this->overrideMwServices();
 
                $user = $this->getMutableTestUser()->getUser();
                $userIdentity = $this->getMock( UserIdentity::class );
index 5d6c067..beddfe8 100644 (file)
@@ -41,7 +41,6 @@ class ContentSecurityPolicyTest extends MediaWikiTestCase {
                // Note, there are some obscure globals which
                // could affect the results which aren't included above.
 
-               $this->overrideMwServices();
                $context = RequestContext::getMain();
                $resp = $context->getRequest()->response();
                $conf = $context->getConfig();
index 660734e..6b650cd 100644 (file)
@@ -120,9 +120,6 @@ class GlobalTest extends MediaWikiTestCase {
                fwrite( $f, 'Message' );
                fclose( $f );
 
-               // Reset the service to avoid cached results
-               $this->overrideMwServices();
-
                $this->assertTrue( wfReadOnly() );
                $this->assertTrue( wfReadOnly() ); # Check cached
        }
@@ -137,7 +134,6 @@ class GlobalTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgReadOnly' => 'reason'
                ] );
-               $this->overrideMwServices();
 
                $this->assertSame( 'reason', wfReadOnlyReason() );
        }
@@ -321,33 +317,35 @@ class GlobalTest extends MediaWikiTestCase {
                ] );
                $this->setLogger( 'wfDebug', new LegacyLogger( 'wfDebug' ) );
 
+               unlink( $debugLogFile );
                wfDebug( "This is a normal string" );
                $this->assertEquals( "This is a normal string\n", file_get_contents( $debugLogFile ) );
-               unlink( $debugLogFile );
 
+               unlink( $debugLogFile );
                wfDebug( "This is nöt an ASCII string" );
                $this->assertEquals( "This is nöt an ASCII string\n", file_get_contents( $debugLogFile ) );
-               unlink( $debugLogFile );
 
+               unlink( $debugLogFile );
                wfDebug( "\00305This has böth UTF and control chars\003" );
                $this->assertEquals(
                        " 05This has böth UTF and control chars \n",
                        file_get_contents( $debugLogFile )
                );
-               unlink( $debugLogFile );
 
+               unlink( $debugLogFile );
                wfDebugMem();
                $this->assertGreaterThan(
                        1000,
                        preg_replace( '/\D/', '', file_get_contents( $debugLogFile ) )
                );
-               unlink( $debugLogFile );
 
+               unlink( $debugLogFile );
                wfDebugMem( true );
                $this->assertGreaterThan(
                        1000000,
                        preg_replace( '/\D/', '', file_get_contents( $debugLogFile ) )
                );
+
                unlink( $debugLogFile );
        }
 
index 95571f2..d8d5495 100644 (file)
@@ -18,9 +18,6 @@ class GlobalWithDBTest extends MediaWikiTestCase {
 
                // Don't try to fetch the files from Commons or anything, please
                $this->setMwGlobals( 'wgForeignFileRepos', [] );
-               // We need to reset services immediately so that editPage() doesn't use the old RepoGroup
-               // and hit the network
-               $this->resetServices();
 
                // XXX How do we get file redirects to work?
                $this->editPage( 'File:Redirect to bad.jpg', '#REDIRECT [[Bad.jpg]]' );
index fb32ef7..37d4e0c 100644 (file)
@@ -404,9 +404,6 @@ class MessageTest extends MediaWikiLangTestCase {
         */
        public function testRawHtmlInMsg() {
                $this->setMwGlobals( 'wgRawHtml', true );
-               // We have to reset the core hook registration.
-               // to register the html hook
-               $this->overrideMwServices();
 
                $msg = new RawMessage( '<html><script>alert("xss")</script></html>' );
                $txt = '<span class="error">&lt;html&gt; tags cannot be' .
index 2895fa2..5cf6fbe 100644 (file)
@@ -11,16 +11,6 @@ use Wikimedia\Rdbms\LoadBalancer;
  * @group Database
  */
 class MovePageTest extends MediaWikiTestCase {
-       /**
-        * @param string $class
-        * @return object A mock that throws on any method call
-        */
-       private function getNoOpMock( $class ) {
-               $mock = $this->createMock( $class );
-               $mock->expects( $this->never() )->method( $this->anythingBut( '__destruct' ) );
-               return $mock;
-       }
-
        /**
         * The only files that exist are 'File:Existent.jpg', 'File:Existent2.jpg', and
         * 'File:Existent-file-no-page.jpg'. Calling unexpected methods causes a test failure.
@@ -72,7 +62,7 @@ class MovePageTest extends MediaWikiTestCase {
        private function newMovePage( $old, $new, array $params = [] ) : MovePage {
                $mockLB = $this->createMock( LoadBalancer::class );
                $mockLB->method( 'getConnection' )
-                       ->willReturn( $params['db'] ?? $this->getNoOpMock( IDatabase::class ) );
+                       ->willReturn( $params['db'] ?? $this->createNoOpMock( IDatabase::class ) );
                $mockLB->expects( $this->never() )
                        ->method( $this->anythingBut( 'getConnection', '__destruct' ) );
 
@@ -98,8 +88,8 @@ class MovePageTest extends MediaWikiTestCase {
                        ),
                        $mockLB,
                        $params['nsInfo'] ?? $mockNsInfo,
-                       $params['wiStore'] ?? $this->getNoOpMock( WatchedItemStore::class ),
-                       $params['permMgr'] ?? $this->getNoOpMock( PermissionManager::class ),
+                       $params['wiStore'] ?? $this->createNoOpMock( WatchedItemStore::class ),
+                       $params['permMgr'] ?? $this->createNoOpMock( PermissionManager::class ),
                        $params['repoGroup'] ?? $this->getMockRepoGroup()
                );
        }
@@ -197,7 +187,6 @@ class MovePageTest extends MediaWikiTestCase {
                foreach ( $extraOptions as $key => $val ) {
                        $this->setMwGlobals( "wg$key", $val );
                }
-               $this->overrideMwServices();
                $this->setService( 'RepoGroup', $this->getMockRepoGroup() );
                // This returns true instead of an array if there are no errors
                $this->hideDeprecated( 'Title::isValidMoveOperation' );
index 3c22a23..0e788e7 100644 (file)
@@ -122,8 +122,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                        $this->user = $this->userUser;
                }
-
-               $this->resetServices();
        }
 
        public function tearDown() {
@@ -143,7 +141,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                } else {
                        $this->user = $this->altUser;
                }
-               $this->resetServices();
        }
 
        /**
@@ -421,8 +418,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                        global $wgGroupPermissions;
                        $old = $wgGroupPermissions;
-                       $wgGroupPermissions = [];
-                       $this->resetServices();
+                       $this->setMwGlobals( 'wgGroupPermissions', [] );
 
                        $this->assertEquals( $check[$action][1],
                                MediaWikiServices::getInstance()->getPermissionManager()
@@ -433,8 +429,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        $this->assertEquals( $check[$action][1],
                                MediaWikiServices::getInstance()->getPermissionManager()
                                        ->getPermissionErrors( $action, $this->user, $this->title, 'secure' ) );
-                       $wgGroupPermissions = $old;
-                       $this->resetServices();
+                       $this->setMwGlobals( 'wgGroupPermissions', $old );
 
                        $this->overrideUserPermissions( $this->user, $action );
                        $this->assertEquals( $check[$action][2],
@@ -453,46 +448,39 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                                        ->userCan( $action, $this->user, $this->title, true ) );
                        $this->assertEquals( $check[$action][3],
                                MediaWikiServices::getInstance()->getPermissionManager()
-                                       ->userCan( $action, $this->user, $this->title,
-                                       PermissionManager::RIGOR_QUICK ) );
+                                       ->quickUserCan( $action, $this->user, $this->title ) );
                        # count( User::getGroupsWithPermissions( $action ) ) < 1
                }
        }
 
        protected function runGroupPermissions( $perm, $action, $result, $result2 = null ) {
-               global $wgGroupPermissions;
-
                if ( $result2 === null ) {
                        $result2 = $result;
                }
 
-               $wgGroupPermissions['autoconfirmed']['move'] = false;
-               $wgGroupPermissions['user']['move'] = false;
-               $this->resetServices();
+               $this->setGroupPermissions( 'autoconfirmed', 'move', false );
+               $this->setGroupPermissions( 'user', 'move', false );
                $this->overrideUserPermissions( $this->user, $perm );
                $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( $action, $this->user, $this->title );
                $this->assertEquals( $result, $res );
 
-               $wgGroupPermissions['autoconfirmed']['move'] = true;
-               $wgGroupPermissions['user']['move'] = false;
-               $this->resetServices();
+               $this->setGroupPermissions( 'autoconfirmed', 'move', true );
+               $this->setGroupPermissions( 'user', 'move', false );
                $this->overrideUserPermissions( $this->user, $perm );
                $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( $action, $this->user, $this->title );
                $this->assertEquals( $result2, $res );
 
-               $wgGroupPermissions['autoconfirmed']['move'] = true;
-               $wgGroupPermissions['user']['move'] = true;
-               $this->resetServices();
+               $this->setGroupPermissions( 'autoconfirmed', 'move', true );
+               $this->setGroupPermissions( 'user', 'move', true );
                $this->overrideUserPermissions( $this->user, $perm );
                $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( $action, $this->user, $this->title );
                $this->assertEquals( $result2, $res );
 
-               $wgGroupPermissions['autoconfirmed']['move'] = false;
-               $wgGroupPermissions['user']['move'] = true;
-               $this->resetServices();
+               $this->setGroupPermissions( 'autoconfirmed', 'move', false );
+               $this->setGroupPermissions( 'user', 'move', true );
                $this->overrideUserPermissions( $this->user, $perm );
                $res = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getPermissionErrors( $action, $this->user, $this->title );
@@ -899,7 +887,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                $this->assertEquals( true,
                        MediaWikiServices::getInstance()->getPermissionManager()
-                               ->userCan( 'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
+                               ->quickUserCan( 'edit', $this->user, $this->title ) );
 
                $this->title->mRestrictions = [ "edit" => [ 'bogus', "sysop", "protect", "" ],
                        "bogus" => [ 'bogus', "sysop", "protect", "" ] ];
@@ -945,11 +933,11 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                $this->assertEquals( false,
                        MediaWikiServices::getInstance()->getPermissionManager()
-                               ->userCan( 'bogus', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
+                               ->quickUserCan( 'bogus', $this->user, $this->title ) );
 
                $this->assertEquals( false,
-                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
-                               'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan(
+                               'edit', $this->user, $this->title ) );
 
                $this->assertEquals( [ [ 'badaccess-group0' ],
                        [ 'protectedpagetext', 'bogus', 'bogus' ],
@@ -965,12 +953,12 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
 
                $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] );
                $this->assertEquals( false,
-                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
-                               'bogus', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan(
+                               'bogus', $this->user, $this->title ) );
 
                $this->assertEquals( false,
-                       MediaWikiServices::getInstance()->getPermissionManager()->userCan(
-                               'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
+                       MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan(
+                               'edit', $this->user, $this->title ) );
 
                $this->assertEquals( [ [ 'badaccess-group0' ],
                        [ 'protectedpagetext', 'bogus', 'bogus' ],
@@ -1144,7 +1132,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                                ->getPermissionErrors( 'edit', $this->user, $this->title ) );
 
                $this->setMwGlobals( 'wgEmailConfirmToEdit', false );
-               $this->resetServices();
                $this->overrideUserPermissions( $this->user, [
                        'createpage',
                        'edit',
@@ -1188,8 +1175,7 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        ->userCan( 'move-target', $this->user, $this->title ) );
                // quickUserCan should ignore user blocks
                $this->assertEquals( true, MediaWikiServices::getInstance()->getPermissionManager()
-                       ->userCan( 'move-target', $this->user, $this->title,
-                               PermissionManager::RIGOR_QUICK ) );
+                       ->quickUserCan( 'move-target', $this->user, $this->title ) );
 
                global $wgLocalTZoffset;
                $wgLocalTZoffset = -60;
@@ -1577,7 +1563,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        $rights = array_diff( $rights, [ 'writetest' ] );
                } );
 
-               $this->resetServices();
                $rights = MediaWikiServices::getInstance()
                        ->getPermissionManager()
                        ->getUserPermissions( $user );
@@ -1859,7 +1844,6 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        'wgRestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
                        'wgAutopromote' => []
                ] );
-               $this->resetServices();
                $user = is_null( $userGroups ) ? null : $this->getTestUser( $userGroups )->getUser();
                $this->assertSame( $expected, MediaWikiServices::getInstance()
                        ->getPermissionManager()
index 685cb40..4d9f8e1 100644 (file)
@@ -60,8 +60,6 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
 
                $this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers;
                TestingAccessWrapper::newFromClass( Hooks::class )->handlers = [];
-
-               $this->overrideMwServices();
        }
 
        public function tearDown() {
index 49dcf07..3c6573a 100644 (file)
@@ -25,7 +25,6 @@ class MWBasicRequestAuthorizerTest extends MediaWikiTestCase {
                $user = User::newFromName( 'Test user' );
                // Don't allow the rights to everybody so that user rights kick in.
                $this->mergeMwGlobalArrayValue( 'wgGroupPermissions', [ '*' => $userRights ] );
-               $this->resetServices();
                $this->overrideUserPermissions(
                        $user,
                        array_keys( array_filter( $userRights ), function ( $value ) {
index 57619c5..5cc3646 100644 (file)
@@ -960,7 +960,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
         */
        public function testRevisionSelectFields( $migrationStageSettings, $expected ) {
                $this->setMwGlobals( $migrationStageSettings );
-               $this->overrideMwServices();
 
                $this->hideDeprecated( 'Revision::selectFields' );
                $this->assertArrayEqualsIgnoringIntKeyOrder( $expected, Revision::selectFields() );
@@ -972,7 +971,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
         */
        public function testRevisionSelectArchiveFields( $migrationStageSettings, $expected ) {
                $this->setMwGlobals( $migrationStageSettings );
-               $this->overrideMwServices();
 
                $this->hideDeprecated( 'Revision::selectArchiveFields' );
                $this->assertArrayEqualsIgnoringIntKeyOrder( $expected, Revision::selectArchiveFields() );
@@ -984,7 +982,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
        public function testRevisionUserJoinCond() {
                $this->hideDeprecated( 'Revision::userJoinCond' );
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_OLD );
-               $this->overrideMwServices();
                $this->assertEquals(
                        [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
                        Revision::userJoinCond()
@@ -1041,7 +1038,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
         */
        public function testRevisionGetArchiveQueryInfo( $migrationStageSettings, $expected ) {
                $this->setMwGlobals( $migrationStageSettings );
-               $this->overrideMwServices();
 
                $queryInfo = Revision::getArchiveQueryInfo();
                $this->assertQueryInfoEquals( $expected, $queryInfo );
@@ -1053,7 +1049,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
         */
        public function testRevisionGetQueryInfo( $migrationStageSettings, $options, $expected ) {
                $this->setMwGlobals( $migrationStageSettings );
-               $this->overrideMwServices();
 
                $queryInfo = Revision::getQueryInfo( $options );
                $this->assertQueryInfoEquals( $expected, $queryInfo );
@@ -1065,7 +1060,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
         */
        public function testRevisionStoreGetQueryInfo( $migrationStageSettings, $options, $expected ) {
                $this->setMwGlobals( $migrationStageSettings );
-               $this->overrideMwServices();
 
                $store = MediaWikiServices::getInstance()->getRevisionStore();
 
@@ -1083,7 +1077,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
                $expected
        ) {
                $this->setMwGlobals( $migrationStageSettings );
-               $this->overrideMwServices();
 
                $store = MediaWikiServices::getInstance()->getRevisionStore();
 
@@ -1097,7 +1090,6 @@ class RevisionQueryInfoTest extends MediaWikiTestCase {
         */
        public function testRevisionStoreGetArchiveQueryInfo( $migrationStageSettings, $expected ) {
                $this->setMwGlobals( $migrationStageSettings );
-               $this->overrideMwServices();
 
                $store = MediaWikiServices::getInstance()->getRevisionStore();
 
index d4393dd..55bfab7 100644 (file)
@@ -83,8 +83,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                        'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
                        'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                ] );
-
-               $this->overrideMwServices();
        }
 
        protected function addCoreDBData() {
@@ -403,7 +401,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                $title = $this->getTestPageTitle();
                $rev = $this->getRevisionRecordFromDetailsArray( $revDetails );
 
-               $this->overrideMwServices();
                $store = MediaWikiServices::getInstance()->getRevisionStore();
                $return = $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
 
@@ -480,7 +477,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
                        'user' => true,
                ];
 
-               $this->overrideMwServices();
                $store = MediaWikiServices::getInstance()->getRevisionStore();
 
                // Insert the first revision
@@ -594,8 +590,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
         * @covers \MediaWiki\Revision\RevisionStore::findSlotContentId
         */
        public function testNewNullRevision( Title $title, $revDetails, $comment, $minor = false ) {
-               $this->overrideMwServices();
-
                $user = TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser();
                $page = WikiPage::factory( $title );
 
@@ -1021,7 +1015,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
         */
        public function testNewRevisionFromRow_anonEdit_legacyEncoding() {
                $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
-               $this->overrideMwServices();
                $page = $this->getTestPage();
                $text = __METHOD__ . 'a-ä';
                /** @var Revision $rev */
@@ -1101,7 +1094,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
         */
        public function testNewRevisionFromArchiveRow_legacyEncoding() {
                $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
-               $this->overrideMwServices();
                $store = MediaWikiServices::getInstance()->getRevisionStore();
                $title = Title::newFromText( __METHOD__ );
                $text = __METHOD__ . '-bä';
@@ -1797,7 +1789,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
        public function testGetKnownCurrentRevision_userNameChange() {
                global $wgActorTableSchemaMigrationStage;
 
-               $this->overrideMwServices();
                $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
                $this->setService( 'MainWANObjectCache', $cache );
 
@@ -1838,7 +1829,6 @@ abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
         * @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision
         */
        public function testGetKnownCurrentRevision_revDelete() {
-               $this->overrideMwServices();
                $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
                $this->setService( 'MainWANObjectCache', $cache );
 
index 83e8d85..7b334ee 100644 (file)
@@ -95,8 +95,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                ] );
 
-               $this->overrideMwServices();
-
                if ( !$this->testPage ) {
                        /**
                         * We have to create a new page for each subclass as the page creation may result
index d62e4c7..865bd31 100644 (file)
@@ -281,7 +281,6 @@ class RevisionTest extends MediaWikiTestCase {
         * @covers \MediaWiki\Revision\RevisionStore::newMutableRevisionFromArray
         */
        public function testConstructFromRowWithBadPageId() {
-               $this->overrideMwServices();
                Wikimedia\suppressWarnings();
                $rev = new Revision( (object)[
                        'rev_page' => 77777777,
@@ -603,7 +602,6 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testLoadFromTitle() {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-               $this->overrideMwServices();
                $title = $this->getMockTitle();
 
                $conditions = [
index 91c4a13..6c17bf5 100644 (file)
@@ -72,7 +72,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        $this->user = $this->userUser;
                }
-               $this->resetServices();
        }
 
        protected function setTitle( $ns, $title = "Main_Page" ) {
@@ -329,9 +328,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 
                        global $wgGroupPermissions;
                        $old = $wgGroupPermissions;
-                       $wgGroupPermissions = [];
-
-                       $this->resetServices();
+                       $this->setMwGlobals( 'wgGroupPermissions', [] );
 
                        $this->assertEquals( $check[$action][1],
                                $this->title->getUserPermissionsErrors( $action, $this->user, true ) );
@@ -340,8 +337,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $this->assertEquals( $check[$action][1],
                                $this->title->getUserPermissionsErrors( $action, $this->user, 'secure' ) );
 
-                       $wgGroupPermissions = $old;
-                       $this->resetServices();
+                       $this->setMwGlobals( 'wgGroupPermissions', $old );
 
                        $this->overrideUserPermissions( $this->user, $action );
                        $this->assertEquals( $check[$action][2],
@@ -361,8 +357,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
        }
 
        protected function runGroupPermissions( $action, $result, $result2 = null ) {
-               global $wgGroupPermissions;
-
                if ( $result2 === null ) {
                        $result2 = $result;
                }
@@ -375,30 +369,26 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                $userPermsOverrides = MediaWikiServices::getInstance()->getPermissionManager()
                        ->getUserPermissions( $this->user );
 
-               $wgGroupPermissions['autoconfirmed']['move'] = false;
-               $wgGroupPermissions['user']['move'] = false;
-               $this->resetServices();
+               $this->setGroupPermissions( 'autoconfirmed', 'move', false );
+               $this->setGroupPermissions( 'user', 'move', false );
                $this->overrideUserPermissions( $this->user, $userPermsOverrides );
                $res = $this->title->getUserPermissionsErrors( $action, $this->user );
                $this->assertEquals( $result, $res );
 
-               $wgGroupPermissions['autoconfirmed']['move'] = true;
-               $wgGroupPermissions['user']['move'] = false;
-               $this->resetServices();
+               $this->setGroupPermissions( 'autoconfirmed', 'move', true );
+               $this->setGroupPermissions( 'user', 'move', false );
                $this->overrideUserPermissions( $this->user, $userPermsOverrides );
                $res = $this->title->getUserPermissionsErrors( $action, $this->user );
                $this->assertEquals( $result2, $res );
 
-               $wgGroupPermissions['autoconfirmed']['move'] = true;
-               $wgGroupPermissions['user']['move'] = true;
-               $this->resetServices();
+               $this->setGroupPermissions( 'autoconfirmed', 'move', true );
+               $this->setGroupPermissions( 'user', 'move', true );
                $this->overrideUserPermissions( $this->user, $userPermsOverrides );
                $res = $this->title->getUserPermissionsErrors( $action, $this->user );
                $this->assertEquals( $result2, $res );
 
-               $wgGroupPermissions['autoconfirmed']['move'] = false;
-               $wgGroupPermissions['user']['move'] = true;
-               $this->resetServices();
+               $this->setGroupPermissions( 'autoconfirmed', 'move', false );
+               $this->setGroupPermissions( 'user', 'move', true );
                $this->overrideUserPermissions( $this->user, $userPermsOverrides );
                $res = $this->title->getUserPermissionsErrors( $action, $this->user );
                $this->assertEquals( $result2, $res );
@@ -906,7 +896,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        'wgEmailAuthentication' => true,
                        'wgBlockDisablesLogin' => false,
                ] );
-               $this->resetServices();
 
                $this->overrideUserPermissions(
                        $this->user,
@@ -921,7 +910,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        $this->title->getUserPermissionsErrors( 'edit', $this->user ) );
 
                $this->setMwGlobals( 'wgEmailConfirmToEdit', false );
-               $this->resetServices();
                $this->overrideUserPermissions(
                        $this->user,
                        [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ]
@@ -1088,7 +1076,6 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                                ],
                        ],
                ] );
-               $this->resetServices();
 
                $now = time();
                $this->user->mBlockedby = $this->user->getName();
index 6cfc377..18faeea 100644 (file)
@@ -150,9 +150,6 @@ class TitleTest extends MediaWikiTestCase {
                                ]
                        ]
                ] );
-
-               // Reset services since we modified $wgLocalInterwikis
-               $this->overrideMwServices();
        }
 
        /**
index c2917b6..2d19d38 100644 (file)
@@ -150,8 +150,6 @@ class ApiBlockTest extends ApiTestCase {
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'applychangetags' => true ] ] );
 
-               $this->resetServices();
-
                $this->doBlock( [ 'tags' => 'custom tag' ] );
        }
 
@@ -162,7 +160,6 @@ class ApiBlockTest extends ApiTestCase {
                $this->mergeMwGlobalArrayValue( 'wgGroupPermissions',
                        [ 'sysop' => $newPermissions ] );
 
-               $this->resetServices();
                $res = $this->doBlock( [ 'hidename' => '' ] );
 
                $dbw = wfGetDB( DB_MASTER );
@@ -212,8 +209,6 @@ class ApiBlockTest extends ApiTestCase {
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'sysop' => [ 'blockemail' => true ] ] );
 
-               $this->resetServices();
-
                $this->doBlock( [ 'noemail' => '' ] );
        }
 
index cc5dada..c68954c 100644 (file)
@@ -143,7 +143,6 @@ class ApiDeleteTest extends ApiTestCase {
                ChangeTags::defineTag( 'custom tag' );
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'applychangetags' => true ] ] );
-               $this->resetServices();
 
                $this->editPage( $name, 'Some text' );
 
index 5e5fea3..aafb3a2 100644 (file)
@@ -39,7 +39,6 @@ class ApiEditPageTest extends ApiTestCase {
                        $this->tablesUsed,
                        [ 'change_tag', 'change_tag_def', 'logging' ]
                );
-               $this->resetServices();
        }
 
        public function testEdit() {
@@ -1368,8 +1367,6 @@ class ApiEditPageTest extends ApiTestCase {
                ChangeTags::defineTag( 'custom tag' );
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'applychangetags' => true ] ] );
-               // Supply services with updated globals
-               $this->resetServices();
 
                try {
                        $this->doApiRequestWithToken( [
@@ -1498,8 +1495,6 @@ class ApiEditPageTest extends ApiTestCase {
 
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'upload' => true ] ] );
-               // Supply services with updated globals
-               $this->resetServices();
 
                $this->doApiRequestWithToken( [
                        'action' => 'edit',
@@ -1515,8 +1510,6 @@ class ApiEditPageTest extends ApiTestCase {
                        'The content you supplied exceeds the article size limit of 1 kilobyte.' );
 
                $this->setMwGlobals( 'wgMaxArticleSize', 1 );
-               // Supply services with updated globals
-               $this->resetServices();
 
                $text = str_repeat( '!', 1025 );
 
@@ -1534,8 +1527,6 @@ class ApiEditPageTest extends ApiTestCase {
                        'The action you have requested is limited to users in the group: ' );
 
                $this->setMwGlobals( 'wgRevokePermissions', [ '*' => [ 'edit' => true ] ] );
-               // Supply services with updated globals
-               $this->resetServices();
 
                $this->doApiRequestWithToken( [
                        'action' => 'edit',
@@ -1552,8 +1543,6 @@ class ApiEditPageTest extends ApiTestCase {
 
                $this->setMwGlobals( 'wgRevokePermissions',
                        [ 'user' => [ 'editcontentmodel' => true ] ] );
-               // Supply services with updated globals
-               $this->resetServices();
 
                $this->doApiRequestWithToken( [
                        'action' => 'edit',
index 580efcd..1e2135b 100644 (file)
@@ -141,7 +141,6 @@ class ApiMainTest extends ApiTestCase {
        public function testSetCacheModeUnrecognized() {
                $api = new ApiMain();
                $api->setCacheMode( 'unrecognized' );
-               $this->resetServices();
                $this->assertSame(
                        'private',
                        TestingAccessWrapper::newFromObject( $api )->mCacheMode,
index c98308c..e09e6ad 100644 (file)
@@ -294,7 +294,6 @@ class ApiMoveTest extends ApiTestCase {
                $name = ucfirst( __FUNCTION__ );
 
                $this->mergeMwGlobalArrayValue( 'wgNamespacesWithSubpages', [ NS_MAIN => true ] );
-               $this->resetServices();
 
                $pages = [ $name, "$name/1", "$name/2", "Talk:$name", "Talk:$name/1", "Talk:$name/3" ];
                $ids = [];
index bdce70c..2d0ce26 100644 (file)
@@ -42,6 +42,16 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                $this->mUserMock->method( 'getOptions' )
                        ->willReturn( [] );
 
+               // DefaultPreferencesFactory calls a ton of user methods, but we still want to list all of
+               // them in case bugs are caused by unexpected things returning null that shouldn't.
+               $this->mUserMock->expects( $this->never() )->method( $this->anythingBut(
+                       'getEffectiveGroups', 'getOptionKinds', 'getInstanceForUpdate', 'getOptions', 'getId',
+                       'isAnon', 'getRequest', 'isLoggedIn', 'getName', 'getGroupMemberships', 'getEditCount',
+                       'getRegistration', 'isAllowed', 'getRealName', 'getOption', 'getStubThreshold',
+                       'getBoolOption', 'getEmail', 'getDatePreference', 'useRCPatrol', 'useNPPatrol',
+                       'setOption', 'saveSettings', 'resetOptions', 'isRegistered'
+               ) );
+
                // Create a new context
                $this->mContext = new DerivativeContext( new RequestContext() );
                $this->mContext->getContext()->setTitle( Title::newFromText( 'Test' ) );
@@ -153,6 +163,8 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
 
        private function executeQuery( $request ) {
                $this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) );
+               $this->mUserMock->method( 'getRequest' )->willReturn( $this->mContext->getRequest() );
+
                $this->mTested->execute();
 
                return $this->mTested->getResult()->getResultData( null, [ 'Strip' => 'all' ] );
index a87160a..06b4cf2 100644 (file)
@@ -121,7 +121,6 @@ class ApiParseTest extends ApiTestCase {
 
                $this->setMwGlobals( 'wgExtraInterlanguageLinkPrefixes', [ 'madeuplanguage' ] );
                $this->tablesUsed[] = 'interwiki';
-               $this->resetServices();
        }
 
        /**
index 282188d..0a2558a 100644 (file)
@@ -210,15 +210,16 @@ class ApiQuerySiteinfoTest extends ApiTestCase {
                        $this->setExpectedApiException( 'apierror-siteinfo-includealldenied' );
                }
 
-               $mockLB = $this->getMockBuilder( LoadBalancer::class )
-                       ->disableOriginalConstructor()
-                       ->setMethods( [ 'getMaxLag', 'getLagTimes', 'getServerName', '__destruct' ] )
-                       ->getMock();
+               $mockLB = $this->createMock( LoadBalancer::class );
                $mockLB->method( 'getMaxLag' )->willReturn( [ null, 7, 1 ] );
                $mockLB->method( 'getLagTimes' )->willReturn( [ 5, 7 ] );
                $mockLB->method( 'getServerName' )->will( $this->returnValueMap( [
                        [ 0, 'apple' ], [ 1, 'carrot' ]
                ] ) );
+               $mockLB->method( 'getLocalDomainID' )->willReturn( 'testdomain' );
+               $mockLB->expects( $this->never() )->method( $this->anythingBut(
+                       'getMaxLag', 'getLagTimes', 'getServerName', 'getLocalDomainID', '__destruct'
+               ) );
                $this->setService( 'DBLoadBalancer', $mockLB );
 
                $this->setMwGlobals( 'wgShowHostnames', $showHostnames );
index 0d7ad0c..92804fd 100644 (file)
@@ -37,8 +37,6 @@ class ApiUserrightsTest extends ApiTestCase {
                if ( $remove ) {
                        $this->mergeMwGlobalArrayValue( 'wgRemoveGroups', [ 'bureaucrat' => $remove ] );
                }
-
-               $this->resetServices();
        }
 
        /**
@@ -221,7 +219,6 @@ class ApiUserrightsTest extends ApiTestCase {
                ChangeTags::defineTag( 'custom tag' );
 
                $this->setGroupPermissions( 'user', 'applychangetags', false );
-               $this->resetServices();
 
                $this->doFailedRightsChange(
                        'You do not have permission to apply change tags along with your changes.',
index 92c71bd..301ed10 100644 (file)
@@ -11,13 +11,11 @@ class ApiQueryUserContribsTest extends ApiTestCase {
                global $wgActorTableSchemaMigrationStage;
 
                $reset = new \Wikimedia\ScopedCallback( function ( $v ) {
-                       global $wgActorTableSchemaMigrationStage;
-                       $wgActorTableSchemaMigrationStage = $v;
-                       $this->overrideMwServices();
+                       $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $v );
                }, [ $wgActorTableSchemaMigrationStage ] );
                // Needs to WRITE_BOTH so READ_OLD tests below work. READ mode here doesn't really matter.
-               $wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW;
-               $this->overrideMwServices();
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage',
+                       SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW );
 
                $users = [
                        User::newFromName( '192.168.2.2', false ),
@@ -55,7 +53,6 @@ class ApiQueryUserContribsTest extends ApiTestCase {
                $this->markTestSkippedIfDbType( 'sqlite' );
 
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
-               $this->overrideMwServices();
 
                if ( isset( $params['ucuserids'] ) ) {
                        $params['ucuserids'] = implode( '|', array_map( 'User::idFromName', $params['ucuserids'] ) );
@@ -150,7 +147,6 @@ class ApiQueryUserContribsTest extends ApiTestCase {
         */
        public function testInterwikiUser( $stage ) {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
-               $this->overrideMwServices();
 
                $params = [
                        'action' => 'query',
index fc6f688..f8be1d4 100644 (file)
@@ -1473,12 +1473,10 @@ class AuthManagerTest extends \MediaWikiTestCase {
                        ],
                        'wgProxyWhitelist' => [],
                ] );
-               $this->resetServices();
                $status = $this->manager->checkAccountCreatePermissions( new \User );
                $this->assertFalse( $status->isOK() );
                $this->assertTrue( $status->hasMessage( 'sorbs_create_account_reason' ) );
                $this->setMwGlobals( 'wgProxyWhitelist', [ '127.0.0.1' ] );
-               $this->resetServices();
                $status = $this->manager->checkAccountCreatePermissions( new \User );
                $this->assertTrue( $status->isGood() );
        }
@@ -1610,9 +1608,9 @@ class AuthManagerTest extends \MediaWikiTestCase {
                $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
                $this->assertSame( 'noname', $ret->message->getKey() );
 
+               $this->hook( 'LocalUserCreated', $this->never() );
                $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
                $readOnlyMode->setReason( 'Because' );
-               $this->hook( 'LocalUserCreated', $this->never() );
                $userReq->username = self::usernameForCreation();
                $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
                $this->unhook( 'LocalUserCreated' );
@@ -1782,11 +1780,11 @@ class AuthManagerTest extends \MediaWikiTestCase {
                        $session, $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
                );
 
+               $this->hook( 'LocalUserCreated', $this->never() );
                $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
                        [ 'username' => $creator->getName() ] + $session );
                $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
                $readOnlyMode->setReason( 'Because' );
-               $this->hook( 'LocalUserCreated', $this->never() );
                $ret = $this->manager->continueAccountCreation( [] );
                $this->unhook( 'LocalUserCreated' );
                $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
@@ -2366,8 +2364,6 @@ class AuthManagerTest extends \MediaWikiTestCase {
                $this->mergeMwGlobalArrayValue( 'wgObjectCaches',
                        [ __METHOD__ => [ 'class' => 'HashBagOStuff' ] ] );
                $this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] );
-               // Supply services with updated globals
-               $this->resetServices();
 
                // Set up lots of mocks...
                $mocks = [];
@@ -2486,10 +2482,10 @@ class AuthManagerTest extends \MediaWikiTestCase {
 
                // Wiki is read-only
                $session->clear();
+               $this->hook( 'LocalUserCreated', $this->never() );
                $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
                $readOnlyMode->setReason( 'Because' );
                $user = \User::newFromName( $username );
-               $this->hook( 'LocalUserCreated', $this->never() );
                $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
                $this->unhook( 'LocalUserCreated' );
                $this->assertEquals( \Status::newFatal( wfMessage( 'readonlytext', 'Because' ) ), $ret );
index e085d88..d4133b7 100644 (file)
@@ -48,7 +48,6 @@ class BlockManagerTest extends MediaWikiTestCase {
        private function getBlockManagerConstructorArgs( $overrideConfig ) {
                $blockManagerConfig = array_merge( $this->blockManagerConfig, $overrideConfig );
                $this->setMwGlobals( $blockManagerConfig );
-               $this->overrideMwServices();
                return [
                        new LoggedServiceOptions(
                                self::$serviceOptionsAccessLog,
index 74ad84a..7abddd4 100644 (file)
@@ -24,7 +24,6 @@ class MessageCacheTest extends MediaWikiLangTestCase {
                // let's choose e.g. German (de)
                $this->setUserLang( 'de' );
                $this->setContentLang( 'de' );
-               $this->resetServices();
        }
 
        function addDBDataOnce() {
@@ -150,7 +149,6 @@ class MessageCacheTest extends MediaWikiLangTestCase {
                                ]
                        ]
                ] );
-               $this->overrideMwServices();
 
                $messageCache = MessageCache::singleton();
                $messageCache->enable();
diff --git a/tests/phpunit/includes/content/UnknownContentHandlerTest.php b/tests/phpunit/includes/content/UnknownContentHandlerTest.php
new file mode 100644 (file)
index 0000000..bc1d3c6
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+
+use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SlotRenderingProvider;
+
+/**
+ * @group ContentHandler
+ */
+class UnknownContentHandlerTest extends MediaWikiLangTestCase {
+       /**
+        * @covers UnknownContentHandler::supportsDirectEditing
+        */
+       public function testSupportsDirectEditing() {
+               $handler = new UnknownContentHandler( 'horkyporky' );
+               $this->assertFalse( $handler->supportsDirectEditing(), 'direct editing supported' );
+       }
+
+       /**
+        * @covers UnknownContentHandler::serializeContent
+        */
+       public function testSerializeContent() {
+               $handler = new UnknownContentHandler( 'horkyporky' );
+               $content = new UnknownContent( 'hello world', 'horkyporky' );
+
+               $this->assertEquals( 'hello world', $handler->serializeContent( $content ) );
+               $this->assertEquals(
+                       'hello world',
+                       $handler->serializeContent( $content, 'application/horkyporky' )
+               );
+       }
+
+       /**
+        * @covers UnknownContentHandler::unserializeContent
+        */
+       public function testUnserializeContent() {
+               $handler = new UnknownContentHandler( 'horkyporky' );
+               $content = $handler->unserializeContent( 'hello world' );
+               $this->assertEquals( 'hello world', $content->getData() );
+
+               $content = $handler->unserializeContent( 'hello world', 'application/horkyporky' );
+               $this->assertEquals( 'hello world', $content->getData() );
+       }
+
+       /**
+        * @covers UnknownContentHandler::makeEmptyContent
+        */
+       public function testMakeEmptyContent() {
+               $handler = new UnknownContentHandler( 'horkyporky' );
+               $content = $handler->makeEmptyContent();
+
+               $this->assertTrue( $content->isEmpty() );
+               $this->assertEquals( '', $content->getData() );
+       }
+
+       public static function dataIsSupportedFormat() {
+               return [
+                       [ null, true ],
+                       [ 'application/octet-stream', true ],
+                       [ 'unknown/unknown', true ],
+                       [ 'text/plain', false ],
+                       [ 99887766, false ],
+               ];
+       }
+
+       /**
+        * @dataProvider dataIsSupportedFormat
+        * @covers UnknownContentHandler::isSupportedFormat
+        */
+       public function testIsSupportedFormat( $format, $supported ) {
+               $handler = new UnknownContentHandler( 'horkyporky' );
+               $this->assertEquals( $supported, $handler->isSupportedFormat( $format ) );
+       }
+
+       /**
+        * @covers ContentHandler::getSecondaryDataUpdates
+        */
+       public function testGetSecondaryDataUpdates() {
+               $title = Title::newFromText( 'Somefile.jpg', NS_FILE );
+               $content = new UnknownContent( '', 'horkyporky' );
+
+               /** @var SlotRenderingProvider $srp */
+               $srp = $this->getMock( SlotRenderingProvider::class );
+
+               $handler = new UnknownContentHandler( 'horkyporky' );
+               $updates = $handler->getSecondaryDataUpdates( $title, $content, SlotRecord::MAIN, $srp );
+
+               $this->assertEquals( [], $updates );
+       }
+
+       /**
+        * @covers ContentHandler::getDeletionUpdates
+        */
+       public function testGetDeletionUpdates() {
+               $title = Title::newFromText( 'Somefile.jpg', NS_FILE );
+
+               $handler = new UnknownContentHandler( 'horkyporky' );
+               $updates = $handler->getDeletionUpdates( $title, SlotRecord::MAIN );
+
+               $this->assertEquals( [], $updates );
+       }
+
+       /**
+        * @covers ContentHandler::getDeletionUpdates
+        */
+       public function testGetSlotDiffRenderer() {
+               $context = new RequestContext();
+               $context->setRequest( new FauxRequest() );
+
+               $handler = new UnknownContentHandler( 'horkyporky' );
+               $slotDiffRenderer = $handler->getSlotDiffRenderer( $context );
+
+               $oldContent = $handler->unserializeContent( 'Foo' );
+               $newContent = $handler->unserializeContent( 'Foo bar' );
+
+               $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+               $this->assertNotEmpty( $diff );
+       }
+
+}
diff --git a/tests/phpunit/includes/content/UnknownContentTest.php b/tests/phpunit/includes/content/UnknownContentTest.php
new file mode 100644 (file)
index 0000000..fd8e3ba
--- /dev/null
@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * @group ContentHandler
+ */
+class UnknownContentTest extends MediaWikiLangTestCase {
+
+       /**
+        * @param string $data
+        * @return UnknownContent
+        */
+       public function newContent( $data, $type = 'xyzzy' ) {
+               return new UnknownContent( $data, $type );
+       }
+
+       /**
+        * @covers UnknownContent::getParserOutput
+        */
+       public function testGetParserOutput() {
+               $this->setUserLang( 'en' );
+               $this->setContentLang( 'qqx' );
+
+               $title = Title::newFromText( 'Test' );
+               $content = $this->newContent( 'Horkyporky' );
+
+               $po = $content->getParserOutput( $title );
+               $html = $po->getText();
+               $html = preg_replace( '#<!--.*?-->#sm', '', $html ); // strip comments
+
+               $this->assertNotContains( 'Horkyporky', $html );
+               $this->assertNotContains( '(unsupported-content-model)', $html );
+       }
+
+       /**
+        * @covers UnknownContent::preSaveTransform
+        */
+       public function testPreSaveTransform() {
+               $title = Title::newFromText( 'Test' );
+               $user = $this->getTestUser()->getUser();
+               $content = $this->newContent( 'Horkyporky ~~~' );
+
+               $options = new ParserOptions();
+
+               $this->assertSame( $content, $content->preSaveTransform( $title, $user, $options ) );
+       }
+
+       /**
+        * @covers UnknownContent::preloadTransform
+        */
+       public function testPreloadTransform() {
+               $title = Title::newFromText( 'Test' );
+               $content = $this->newContent( 'Horkyporky ~~~' );
+
+               $options = new ParserOptions();
+
+               $this->assertSame( $content, $content->preloadTransform( $title, $options ) );
+       }
+
+       /**
+        * @covers UnknownContent::getRedirectTarget
+        */
+       public function testGetRedirectTarget() {
+               $content = $this->newContent( '#REDIRECT [[Horkyporky]]' );
+               $this->assertNull( $content->getRedirectTarget() );
+       }
+
+       /**
+        * @covers UnknownContent::isRedirect
+        */
+       public function testIsRedirect() {
+               $content = $this->newContent( '#REDIRECT [[Horkyporky]]' );
+               $this->assertFalse( $content->isRedirect() );
+       }
+
+       /**
+        * @covers UnknownContent::isCountable
+        */
+       public function testIsCountable() {
+               $content = $this->newContent( '[[Horkyporky]]' );
+               $this->assertFalse( $content->isCountable( true ) );
+       }
+
+       /**
+        * @covers UnknownContent::getTextForSummary
+        */
+       public function testGetTextForSummary() {
+               $content = $this->newContent( 'Horkyporky' );
+               $this->assertSame( '', $content->getTextForSummary() );
+       }
+
+       /**
+        * @covers UnknownContent::getTextForSearchIndex
+        */
+       public function testGetTextForSearchIndex() {
+               $content = $this->newContent( 'Horkyporky' );
+               $this->assertSame( '', $content->getTextForSearchIndex() );
+       }
+
+       /**
+        * @covers UnknownContent::copy
+        */
+       public function testCopy() {
+               $content = $this->newContent( 'hello world.' );
+               $copy = $content->copy();
+
+               $this->assertSame( $content, $copy );
+       }
+
+       /**
+        * @covers UnknownContent::getSize
+        */
+       public function testGetSize() {
+               $content = $this->newContent( 'hello world.' );
+
+               $this->assertEquals( 12, $content->getSize() );
+       }
+
+       /**
+        * @covers UnknownContent::getData
+        */
+       public function testGetData() {
+               $content = $this->newContent( 'hello world.' );
+
+               $this->assertEquals( 'hello world.', $content->getData() );
+       }
+
+       /**
+        * @covers UnknownContent::getNativeData
+        */
+       public function testGetNativeData() {
+               $content = $this->newContent( 'hello world.' );
+
+               $this->assertEquals( 'hello world.', $content->getNativeData() );
+       }
+
+       /**
+        * @covers UnknownContent::getWikitextForTransclusion
+        */
+       public function testGetWikitextForTransclusion() {
+               $content = $this->newContent( 'hello world.' );
+
+               $this->assertEquals( '', $content->getWikitextForTransclusion() );
+       }
+
+       /**
+        * @covers UnknownContent::getModel
+        */
+       public function testGetModel() {
+               $content = $this->newContent( "hello world.", 'horkyporky' );
+
+               $this->assertEquals( 'horkyporky', $content->getModel() );
+       }
+
+       /**
+        * @covers UnknownContent::getContentHandler
+        */
+       public function testGetContentHandler() {
+               $this->mergeMwGlobalArrayValue(
+                       'wgContentHandlers',
+                       [ 'horkyporky' => 'UnknownContentHandler' ]
+               );
+
+               $content = $this->newContent( "hello world.", 'horkyporky' );
+
+               $this->assertInstanceOf( UnknownContentHandler::class, $content->getContentHandler() );
+               $this->assertEquals( 'horkyporky', $content->getContentHandler()->getModelID() );
+       }
+
+       public static function dataIsEmpty() {
+               return [
+                       [ '', true ],
+                       [ '  ', false ],
+                       [ '0', false ],
+                       [ 'hallo welt.', false ],
+               ];
+       }
+
+       /**
+        * @dataProvider dataIsEmpty
+        * @covers UnknownContent::isEmpty
+        */
+       public function testIsEmpty( $text, $empty ) {
+               $content = $this->newContent( $text );
+
+               $this->assertEquals( $empty, $content->isEmpty() );
+       }
+
+       public function provideEquals() {
+               return [
+                       [ new UnknownContent( "hallo", 'horky' ), null, false ],
+                       [ new UnknownContent( "hallo", 'horky' ), new UnknownContent( "hallo", 'horky' ), true ],
+                       [ new UnknownContent( "hallo", 'horky' ), new UnknownContent( "hallo", 'xyzzy' ), false ],
+                       [ new UnknownContent( "hallo", 'horky' ), new JavaScriptContent( "hallo" ), false ],
+                       [ new UnknownContent( "hallo", 'horky' ), new WikitextContent( "hallo" ), false ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideEquals
+        * @covers UnknownContent::equals
+        */
+       public function testEquals( Content $a, Content $b = null, $equal = false ) {
+               $this->assertEquals( $equal, $a->equals( $b ) );
+       }
+
+       public static function provideConvert() {
+               return [
+                       [ // #0
+                               'Hallo Welt',
+                               CONTENT_MODEL_WIKITEXT,
+                               'lossless',
+                               'Hallo Welt'
+                       ],
+                       [ // #1
+                               'Hallo Welt',
+                               CONTENT_MODEL_WIKITEXT,
+                               'lossless',
+                               'Hallo Welt'
+                       ],
+                       [ // #1
+                               'Hallo Welt',
+                               CONTENT_MODEL_CSS,
+                               'lossless',
+                               'Hallo Welt'
+                       ],
+                       [ // #1
+                               'Hallo Welt',
+                               CONTENT_MODEL_JAVASCRIPT,
+                               'lossless',
+                               'Hallo Welt'
+                       ],
+               ];
+       }
+
+       /**
+        * @covers UnknownContent::convert
+        */
+       public function testConvert() {
+               $content = $this->newContent( 'More horkyporky?' );
+
+               $this->assertFalse( $content->convert( CONTENT_MODEL_TEXT ) );
+       }
+
+       /**
+        * @covers UnknownContent::__construct
+        * @covers UnknownContentHandler::serializeContent
+        */
+       public function testSerialize() {
+               $this->mergeMwGlobalArrayValue(
+                       'wgContentHandlers',
+                       [ 'horkyporky' => 'UnknownContentHandler' ]
+               );
+
+               $content = $this->newContent( 'Hörkypörky', 'horkyporky' );
+
+               $this->assertSame( 'Hörkypörky', $content->serialize() );
+       }
+
+}
index ba31b4f..c1bb1a0 100644 (file)
@@ -157,8 +157,14 @@ class DifferenceEngineTest extends MediaWikiTestCase {
         * @dataProvider provideGenerateContentDiffBody
         */
        public function testGenerateContentDiffBody(
-               Content $oldContent, Content $newContent, $expectedDiff
+               array $oldContentArgs, array $newContentArgs, $expectedDiff
        ) {
+               $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
+                       'testing-nontext' => DummyNonTextContentHandler::class,
+               ] );
+               $oldContent = ContentHandler::makeContent( ...$oldContentArgs );
+               $newContent = ContentHandler::makeContent( ...$newContentArgs );
+
                // Set $wgExternalDiffEngine to something bogus to try to force use of
                // the PHP engine rather than wikidiff2.
                $this->setMwGlobals( [
@@ -170,12 +176,9 @@ class DifferenceEngineTest extends MediaWikiTestCase {
                $this->assertSame( $expectedDiff, $this->getPlainDiff( $diff ) );
        }
 
-       public function provideGenerateContentDiffBody() {
-               $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
-                       'testing-nontext' => DummyNonTextContentHandler::class,
-               ] );
-               $content1 = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
-               $content2 = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+       public static function provideGenerateContentDiffBody() {
+               $content1 = [ 'xxx', null, CONTENT_MODEL_TEXT ];
+               $content2 = [ 'yyy', null, CONTENT_MODEL_TEXT ];
 
                return [
                        'self-diff' => [ $content1, $content1, '' ],
index c523561..3eb1201 100644 (file)
@@ -9,14 +9,22 @@ class TextSlotDiffRendererTest extends MediaWikiTestCase {
 
        /**
         * @dataProvider provideGetDiff
-        * @param Content|null $oldContent
-        * @param Content|null $newContent
+        * @param array|null $oldContentArgs To pass to makeContent() (if not null)
+        * @param array|null $newContentArgs
         * @param string|Exception $expectedResult
         * @throws Exception
         */
        public function testGetDiff(
-               Content $oldContent = null, Content $newContent = null, $expectedResult
+               array $oldContentArgs = null, array $newContentArgs = null, $expectedResult
        ) {
+               $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
+                       'testing' => DummyContentHandlerForTesting::class,
+                       'testing-nontext' => DummyNonTextContentHandler::class,
+               ] );
+
+               $oldContent = $oldContentArgs ? self::makeContent( ...$oldContentArgs ) : null;
+               $newContent = $newContentArgs ? self::makeContent( ...$newContentArgs ) : null;
+
                if ( $expectedResult instanceof Exception ) {
                        $this->setExpectedException( get_class( $expectedResult ), $expectedResult->getMessage() );
                }
@@ -30,31 +38,26 @@ class TextSlotDiffRendererTest extends MediaWikiTestCase {
                $this->assertSame( $expectedResult, $plainDiff );
        }
 
-       public function provideGetDiff() {
-               $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
-                       'testing' => DummyContentHandlerForTesting::class,
-                       'testing-nontext' => DummyNonTextContentHandler::class,
-               ] );
-
+       public static function provideGetDiff() {
                return [
                        'same text' => [
-                               $this->makeContent( "aaa\nbbb\nccc" ),
-                               $this->makeContent( "aaa\nbbb\nccc" ),
+                               [ "aaa\nbbb\nccc" ],
+                               [ "aaa\nbbb\nccc" ],
                                "",
                        ],
                        'different text' => [
-                               $this->makeContent( "aaa\nbbb\nccc" ),
-                               $this->makeContent( "aaa\nxxx\nccc" ),
+                               [ "aaa\nbbb\nccc" ],
+                               [ "aaa\nxxx\nccc" ],
                                " aaa aaa\n-bbb+xxx\n ccc ccc",
                        ],
                        'no right content' => [
-                               $this->makeContent( "aaa\nbbb\nccc" ),
+                               [ "aaa\nbbb\nccc" ],
                                null,
                                "-aaa+ \n-bbb \n-ccc ",
                        ],
                        'no left content' => [
                                null,
-                               $this->makeContent( "aaa\nbbb\nccc" ),
+                               [ "aaa\nbbb\nccc" ],
                                "- +aaa\n +bbb\n +ccc",
                        ],
                        'no content' => [
@@ -63,13 +66,13 @@ class TextSlotDiffRendererTest extends MediaWikiTestCase {
                                new InvalidArgumentException( '$oldContent and $newContent cannot both be null' ),
                        ],
                        'non-text left content' => [
-                               $this->makeContent( '', 'testing-nontext' ),
-                               $this->makeContent( "aaa\nbbb\nccc" ),
+                               [ '', 'testing-nontext' ],
+                               [ "aaa\nbbb\nccc" ],
                                new ParameterTypeException( '$oldContent', 'TextContent|null' ),
                        ],
                        'non-text right content' => [
-                               $this->makeContent( "aaa\nbbb\nccc" ),
-                               $this->makeContent( '', 'testing-nontext' ),
+                               [ "aaa\nbbb\nccc" ],
+                               [ '', 'testing-nontext' ],
                                new ParameterTypeException( '$newContent', 'TextContent|null' ),
                        ],
                ];
@@ -107,7 +110,7 @@ class TextSlotDiffRendererTest extends MediaWikiTestCase {
         * @param string $model
         * @return null|TextContent
         */
-       private function makeContent( $str, $model = CONTENT_MODEL_TEXT ) {
+       private static function makeContent( $str, $model = CONTENT_MODEL_TEXT ) {
                return ContentHandler::makeContent( $str, null, $model );
        }
 
diff --git a/tests/phpunit/includes/diff/UnsupportedSlotDiffRendererTest.php b/tests/phpunit/includes/diff/UnsupportedSlotDiffRendererTest.php
new file mode 100644 (file)
index 0000000..e8f0bb4
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @covers UnsupportedSlotDiffRenderer
+ */
+class UnsupportedSlotDiffRendererTest extends MediaWikiTestCase {
+
+       public function provideDiff() {
+               $oldContent = new TextContent( 'Kittens' );
+               $newContent = new TextContent( 'Goats' );
+               $badContent = new UnknownContent( 'Dragons', 'xyzzy' );
+
+               yield [ '(unsupported-content-diff)', $oldContent, null ];
+               yield [ '(unsupported-content-diff)', null, $newContent ];
+               yield [ '(unsupported-content-diff)', $oldContent, $newContent ];
+               yield [ '(unsupported-content-diff2)', $badContent, $newContent ];
+               yield [ '(unsupported-content-diff2)', $oldContent, $badContent ];
+               yield [ '(unsupported-content-diff)', null, $badContent ];
+               yield [ '(unsupported-content-diff)', $badContent, null ];
+       }
+
+       /**
+        * @dataProvider provideDiff
+        */
+       public function testDiff( $expected, $oldContent, $newContent ) {
+               $this->mergeMwGlobalArrayValue(
+                       'wgContentHandlers',
+                       [ 'xyzzy' => 'UnknownContentHandler' ]
+               );
+
+               $localizer = $this->getMock( MessageLocalizer::class );
+
+               $localizer->method( 'msg' )
+                       ->willReturnCallback( function ( $key, ...$params ) {
+                               return new RawMessage( "($key)", $params );
+                       } );
+
+               $sdr = new UnsupportedSlotDiffRenderer( $localizer );
+               $this->assertContains( $expected, $sdr->getDiff( $oldContent, $newContent ) );
+       }
+
+}
index ee3262c..00fa1bb 100644 (file)
@@ -49,7 +49,11 @@ class FileBackendGroupIntegrationTest extends MediaWikiIntegrationTestCase {
                $services = MediaWikiServices::getInstance();
 
                foreach ( $serviceMembers as $key => $name ) {
-                       $this->$key = $services->getService( $name );
+                       if ( $key === 'srvCache' ) {
+                               $this->$key = ObjectCache::getLocalServerInstance( 'hash' );
+                       } else {
+                               $this->$key = $services->getService( $name );
+                       }
                }
 
                return FileBackendGroup::singleton();
index 67de698..04452f2 100644 (file)
@@ -7,7 +7,6 @@ class RepoGroupTest extends MediaWikiTestCase {
 
        function testHasForeignRepoNegative() {
                $this->setMwGlobals( 'wgForeignFileRepos', [] );
-               $this->overrideMwServices();
                FileBackendGroup::destroySingleton();
                $this->assertFalse( RepoGroup::singleton()->hasForeignRepos() );
        }
@@ -27,7 +26,6 @@ class RepoGroupTest extends MediaWikiTestCase {
 
        function testForEachForeignRepoNone() {
                $this->setMwGlobals( 'wgForeignFileRepos', [] );
-               $this->overrideMwServices();
                FileBackendGroup::destroySingleton();
                $fakeCallback = $this->createMock( RepoGroupTestHelper::class );
                $fakeCallback->expects( $this->never() )->method( 'callback' );
@@ -48,7 +46,6 @@ class RepoGroupTest extends MediaWikiTestCase {
                        'apiThumbCacheExpiry' => 86400,
                        'directory' => $wgUploadDirectory
                ] ] );
-               $this->overrideMwServices();
                FileBackendGroup::destroySingleton();
        }
 }
index d2c9a32..88ac923 100644 (file)
@@ -51,7 +51,6 @@ class InterwikiTest extends MediaWikiTestCase {
        }
 
        private function setWgInterwikiCache( $interwikiCache ) {
-               $this->overrideMwServices();
                MediaWikiServices::getInstance()->resetServiceForTesting( 'InterwikiLookup' );
                $this->setMwGlobals( 'wgInterwikiCache', $interwikiCache );
        }
index 4318852..7f52ca6 100644 (file)
@@ -82,9 +82,6 @@ class ObjectCacheTest extends MediaWikiTestCase {
 
        /** @covers ObjectCache::newAnything */
        public function testNewAnythingNoAccelNoDb() {
-               $this->overrideMwServices(); // Ensures restore on tear down
-               MediaWiki\MediaWikiServices::disableStorageBackend();
-
                $this->setMwGlobals( [
                        'wgMainCacheType' => CACHE_ACCEL
                ] );
@@ -94,6 +91,8 @@ class ObjectCacheTest extends MediaWikiTestCase {
                        CACHE_ACCEL => [ 'class' => EmptyBagOStuff::class ]
                ] );
 
+               MediaWiki\MediaWikiServices::disableStorageBackend();
+
                $this->assertInstanceOf(
                        EmptyBagOStuff::class,
                        ObjectCache::newAnything( [] ),
@@ -103,7 +102,6 @@ class ObjectCacheTest extends MediaWikiTestCase {
 
        /** @covers ObjectCache::newAnything */
        public function testNewAnythingNothingNoDb() {
-               $this->overrideMwServices();
                MediaWiki\MediaWikiServices::disableStorageBackend();
 
                $this->assertInstanceOf(
index 42edf38..bcbc1ed 100644 (file)
@@ -83,13 +83,11 @@ abstract class PageArchiveTestBase extends MediaWikiTestCase {
 
                $this->tablesUsed += $this->getMcrTablesToReset();
 
-               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-               $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
-               $this->setMwGlobals(
-                       'wgMultiContentRevisionSchemaMigrationStage',
-                       $this->getMcrMigrationStage()
-               );
-               $this->overrideMwServices();
+               $this->setMwGlobals( [
+                       'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
+                       'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
+                       'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
+               ] );
 
                // First create our dummy page
                $page = Title::newFromText( 'PageArchiveTest_thePage' );
index 30973c8..f071e4b 100644 (file)
@@ -65,8 +65,6 @@ abstract class WikiPageDbTestBase extends MediaWikiLangTestCase {
                        $this->getMcrMigrationStage()
                );
                $this->pagesToDelete = [];
-
-               $this->overrideMwServices();
        }
 
        protected function tearDown() {
index ac4a1ca..428778e 100644 (file)
@@ -18,8 +18,6 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
         * @coversNothing
         */
        public function testServiceWiring() {
-               $this->overrideMwServices();
-
                $ranHook = 0;
                $this->setMwGlobals( 'wgHooks', [
                        'ResourceLoaderRegisterModules' => [
index 372cb33..ed75076 100644 (file)
@@ -68,8 +68,6 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
 
                $this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers;
                TestingAccessWrapper::newFromClass( Hooks::class )->handlers = [];
-
-               $this->overrideMwServices();
        }
 
        public function tearDown() {
index 9d58cef..68433c6 100644 (file)
@@ -225,7 +225,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
 
        public function testRcHidemyselfFilter() {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-               $this->overrideMwServices();
 
                $user = $this->getTestUser()->getUser();
                $user->getActorId( wfGetDB( DB_MASTER ) );
@@ -258,7 +257,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                $this->setMwGlobals(
                        'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
                );
-               $this->overrideMwServices();
 
                $user = $this->getTestUser()->getUser();
                $user->getActorId( wfGetDB( DB_MASTER ) );
@@ -289,7 +287,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
 
        public function testRcHidebyothersFilter() {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-               $this->overrideMwServices();
 
                $user = $this->getTestUser()->getUser();
                $user->getActorId( wfGetDB( DB_MASTER ) );
@@ -322,7 +319,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                $this->setMwGlobals(
                        'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
                );
-               $this->overrideMwServices();
 
                $user = $this->getTestUser()->getUser();
                $user->getActorId( wfGetDB( DB_MASTER ) );
@@ -555,7 +551,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
 
        public function testFilterUserExpLevelAllExperienceLevels() {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-               $this->overrideMwServices();
 
                $this->assertConditions(
                        [
@@ -573,7 +568,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                $this->setMwGlobals(
                        'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
                );
-               $this->overrideMwServices();
 
                $this->assertConditions(
                        [
@@ -589,7 +583,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
 
        public function testFilterUserExpLevelRegistrered() {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-               $this->overrideMwServices();
 
                $this->assertConditions(
                        [
@@ -607,7 +600,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                $this->setMwGlobals(
                        'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
                );
-               $this->overrideMwServices();
 
                $this->assertConditions(
                        [
@@ -623,7 +615,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
 
        public function testFilterUserExpLevelUnregistrered() {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-               $this->overrideMwServices();
 
                $this->assertConditions(
                        [
@@ -641,7 +632,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                $this->setMwGlobals(
                        'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
                );
-               $this->overrideMwServices();
 
                $this->assertConditions(
                        [
@@ -657,7 +647,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
 
        public function testFilterUserExpLevelRegistreredOrLearner() {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-               $this->overrideMwServices();
 
                $this->assertConditions(
                        [
@@ -675,7 +664,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                $this->setMwGlobals(
                        'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
                );
-               $this->overrideMwServices();
 
                $this->assertConditions(
                        [
@@ -691,7 +679,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
 
        public function testFilterUserExpLevelUnregistreredOrExperienced() {
                $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_NEW );
-               $this->overrideMwServices();
 
                $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
 
@@ -707,7 +694,6 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                $this->setMwGlobals(
                        'wgActorTableSchemaMigrationStage', SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD
                );
-               $this->overrideMwServices();
 
                $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
 
index 04a89d2..c0376ad 100644 (file)
@@ -33,7 +33,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
                                        $count++;
                                }
                ] ] );
-               $this->overrideMwServices();
                $spf = MediaWikiServices::getInstance()->getSpecialPageFactory();
                $spf->getNames();
                $spf->getNames();
@@ -71,7 +70,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
         */
        public function testGetPage( $spec, $shouldReuseInstance ) {
                $this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => $spec ] );
-               $this->overrideMwServices();
 
                $page = SpecialPageFactory::getPage( 'testdummy' );
                $this->assertInstanceOf( SpecialPage::class, $page );
@@ -85,7 +83,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
         */
        public function testGetNames() {
                $this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => SpecialAllPages::class ] );
-               $this->overrideMwServices();
 
                $names = SpecialPageFactory::getNames();
                $this->assertInternalType( 'array', $names );
@@ -97,7 +94,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
         */
        public function testResolveAlias() {
                $this->setContentLang( 'de' );
-               $this->overrideMwServices();
 
                list( $name, $param ) = SpecialPageFactory::resolveAlias( 'Spezialseiten/Foo' );
                $this->assertEquals( 'Specialpages', $name );
@@ -109,7 +105,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
         */
        public function testGetLocalNameFor() {
                $this->setContentLang( 'de' );
-               $this->overrideMwServices();
 
                $name = SpecialPageFactory::getLocalNameFor( 'Specialpages', 'Foo' );
                $this->assertEquals( 'Spezialseiten/Foo', $name );
@@ -120,7 +115,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
         */
        public function testGetTitleForAlias() {
                $this->setContentLang( 'de' );
-               $this->overrideMwServices();
 
                $title = SpecialPageFactory::getTitleForAlias( 'Specialpages/Foo' );
                $this->assertEquals( 'Spezialseiten/Foo', $title->getText() );
@@ -138,7 +132,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
                $this->setMwGlobals( 'wgSpecialPages',
                        array_combine( array_keys( $aliasesList ), array_keys( $aliasesList ) )
                );
-               $this->overrideMwServices();
                $this->setContentLang( $lang );
 
                // Catch the warnings we expect to be raised
@@ -267,7 +260,6 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
                                }
                        ],
                ] );
-               $this->overrideMwServices();
                SpecialPageFactory::getLocalNameFor( 'Specialpages' );
                $this->assertTrue( $called, 'Recursive call succeeded' );
        }
index 7f97a16..03ba960 100644 (file)
@@ -1275,7 +1275,6 @@ class NamespaceInfoTest extends MediaWikiTestCase {
                        'wgRestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
                        'wgAutopromote' => []
                ] );
-               $this->resetServices();
                $obj = $this->newObj();
                $user = is_null( $groups ) ? null : $this->getTestUser( $groups )->getUser();
                $this->assertSame( $expected, $obj->getRestrictionLevels( $ns, $user ) );
index 340a4c3..4862747 100644 (file)
@@ -46,8 +46,6 @@ class UserGroupMembershipTest extends MediaWikiTestCase {
                $this->userTester->addGroup( 'unittesters' );
                $this->expiryTime = wfTimestamp( TS_MW, time() + 100500 );
                $this->userTester->addGroup( 'testwriters', $this->expiryTime );
-
-               $this->resetServices();
        }
 
        /**
index 9ff0521..2d87c78 100644 (file)
@@ -33,7 +33,6 @@ class UserTest extends MediaWikiTestCase {
                        'wgRevokePermissions' => [],
                        'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
                ] );
-               $this->overrideMwServices();
 
                $this->setUpPermissionGlobals();
 
@@ -73,7 +72,6 @@ class UserTest extends MediaWikiTestCase {
                RequestContext::getMain()->setRequest( $request );
                TestingAccessWrapper::newFromObject( $user )->mRequest = $request;
                $request->getSession()->setUser( $user );
-               $this->overrideMwServices();
        }
 
        /**
@@ -136,7 +134,6 @@ class UserTest extends MediaWikiTestCase {
                        $rights = array_diff( $rights, [ 'writetest' ] );
                } );
 
-               $this->resetServices();
                $rights = $user->getRights();
                $this->assertContains( 'test', $rights );
                $this->assertContains( 'runtest', $rights );
@@ -1091,7 +1088,6 @@ class UserTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
                ] );
-               $this->overrideMwServices();
 
                $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
                $this->hideDeprecated( 'User::selectFields' );
index 9616672..fbb893e 100644 (file)
@@ -1837,8 +1837,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
                        new WatchedItem( $user, $targets[0], '20151212010101' ),
                        new WatchedItem( $user, $targets[1], null ),
                ];
-               $mockDb = $this->getMockDb();
-               $mockDb->expects( $this->never() )->method( $this->anything() );
+               $mockDb = $this->createNoOpMock( IDatabase::class );
 
                $mockCache = $this->getMockCache();
                $mockCache->expects( $this->at( 1 ) )
@@ -1864,16 +1863,18 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
        }
 
        public function testGetNotificationTimestampsBatch_anonymousUser() {
+               if ( defined( 'HHVM_VERSION' ) ) {
+                       $this->markTestSkipped( 'HHVM Reflection buggy' );
+               }
+
                $targets = [
                        new TitleValue( 0, 'SomeDbKey' ),
                        new TitleValue( 1, 'AnotherDbKey' ),
                ];
 
-               $mockDb = $this->getMockDb();
-               $mockDb->expects( $this->never() )->method( $this->anything() );
+               $mockDb = $this->createNoOpMock( IDatabase::class );
 
-               $mockCache = $this->getMockCache();
-               $mockCache->expects( $this->never() )->method( $this->anything() );
+               $mockCache = $this->createNoOpMock( HashBagOStuff::class );
 
                $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
 
@@ -2086,8 +2087,7 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
 
                $mockQueueGroup = $this->getMockJobQueueGroup();
 
-               $mockRevisionRecord = $this->createMock( RevisionRecord::class );
-               $mockRevisionRecord->expects( $this->never() )->method( $this->anything() );
+               $mockRevisionRecord = $this->createNoOpMock( RevisionRecord::class );
 
                $mockRevisionLookup = $this->getMockRevisionLookup( [
                        'getTimestampFromId' => function () {
@@ -2144,11 +2144,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
                $oldid = 22;
                $title = new TitleValue( 0, 'SomeDbKey' );
 
-               $mockRevision = $this->createMock( RevisionRecord::class );
-               $mockRevision->expects( $this->never() )->method( $this->anything() );
-
-               $mockNextRevision = $this->createMock( RevisionRecord::class );
-               $mockNextRevision->expects( $this->never() )->method( $this->anything() );
+               $mockRevision = $this->createNoOpMock( RevisionRecord::class );
+               $mockNextRevision = $this->createNoOpMock( RevisionRecord::class );
 
                $mockDb = $this->getMockDb();
                $mockDb->expects( $this->once() )
@@ -2258,11 +2255,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
 
                $mockQueueGroup = $this->getMockJobQueueGroup();
 
-               $mockRevision = $this->createMock( RevisionRecord::class );
-               $mockRevision->expects( $this->never() )->method( $this->anything() );
-
-               $mockNextRevision = $this->createMock( RevisionRecord::class );
-               $mockNextRevision->expects( $this->never() )->method( $this->anything() );
+               $mockRevision = $this->createNoOpMock( RevisionRecord::class );
+               $mockNextRevision = $this->createNoOpMock( RevisionRecord::class );
 
                $mockRevisionLookup = $this->getMockRevisionLookup(
                        [
@@ -2350,11 +2344,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
 
                $mockQueueGroup = $this->getMockJobQueueGroup();
 
-               $mockRevision = $this->createMock( RevisionRecord::class );
-               $mockRevision->expects( $this->never() )->method( $this->anything() );
-
-               $mockNextRevision = $this->createMock( RevisionRecord::class );
-               $mockNextRevision->expects( $this->never() )->method( $this->anything() );
+               $mockRevision = $this->createNoOpMock( RevisionRecord::class );
+               $mockNextRevision = $this->createNoOpMock( RevisionRecord::class );
 
                $mockRevisionLookup = $this->getMockRevisionLookup(
                        [
@@ -2442,11 +2433,8 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
 
                $mockQueueGroup = $this->getMockJobQueueGroup();
 
-               $mockRevision = $this->createMock( RevisionRecord::class );
-               $mockRevision->expects( $this->never() )->method( $this->anything() );
-
-               $mockNextRevision = $this->createMock( RevisionRecord::class );
-               $mockNextRevision->expects( $this->never() )->method( $this->anything() );
+               $mockRevision = $this->createNoOpMock( RevisionRecord::class );
+               $mockNextRevision = $this->createNoOpMock( RevisionRecord::class );
 
                $mockRevisionLookup = $this->getMockRevisionLookup(
                        [
diff --git a/tests/phpunit/languages/LanguageFallbackStaticMethodsTest.php b/tests/phpunit/languages/LanguageFallbackStaticMethodsTest.php
new file mode 100644 (file)
index 0000000..a683c9a
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @coversDefaultClass Language
+ */
+class LanguageFallbackStaticMethodsTest extends MediaWikiIntegrationTestCase {
+       use LanguageFallbackTestTrait;
+
+       private function getCallee( array $options = [] ) {
+               if ( isset( $options['siteLangCode'] ) ) {
+                       $this->setMwGlobals( 'wgLanguageCode', $options['siteLangCode'] );
+                       $this->resetServices();
+               }
+               return Language::class;
+       }
+
+       private function getMessagesKey() {
+               return Language::MESSAGES_FALLBACKS;
+       }
+
+       private function getStrictKey() {
+               return Language::STRICT_FALLBACKS;
+       }
+}
index 37fa030..a46f25d 100644 (file)
@@ -212,7 +212,6 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
 
                // the actual test: change config, reset services.
                $this->setMwGlobals( 'wgLanguageCode', 'qqx' );
-               $this->resetServices();
 
                // the overridden service instance should still be there
                $this->assertSame( $myReadOnlyMode, $services->getService( 'ReadOnlyMode' ) );
index 38fcf29..ca39341 100644 (file)
@@ -10,8 +10,7 @@ use Wikimedia\Rdbms\LBFactory;
  */
 class LockManagerGroupFactoryTest extends MediaWikiUnitTestCase {
        public function testGetLockManagerGroup() {
-               $mockLbFactory = $this->createMock( LBFactory::class );
-               $mockLbFactory->expects( $this->never() )->method( $this->anything() );
+               $mockLbFactory = $this->createNoOpMock( LBFactory::class );
 
                $factory = new LockManagerGroupFactory( 'defaultDomain', [], $mockLbFactory );
                $lbmUnspecified = $factory->getLockManagerGroup();
diff --git a/tests/phpunit/unit/includes/language/LanguageFallbackTestTrait.php b/tests/phpunit/unit/includes/language/LanguageFallbackTestTrait.php
new file mode 100644 (file)
index 0000000..b500b21
--- /dev/null
@@ -0,0 +1,184 @@
+<?php
+
+/**
+ * Code to test the getFallbackFor, getFallbacksFor, and getFallbacksIncludingSiteLanguage methods
+ * that have historically been static methods of the Language class. It can be used to test any
+ * class or object that implements those three methods.
+ */
+trait LanguageFallbackTestTrait {
+       /**
+        * @param array $options Valid keys:
+        *   * expectedGets: How many times we expect to hit the localisation cache. (This can be
+        *   ignored in integration tests -- it's enough to test in unit tests.)
+        *   * siteLangCode
+        * @return string|object Name of class or object with the three methods getFallbackFor,
+        *   getFallbacksFor, and getFallbacksIncludingSiteLanguage.
+        */
+       abstract protected function getCallee( array $options = [] );
+
+       /**
+        * @return int Value that was historically in Language::MESSAGES_FALLBACKS
+        */
+       abstract protected function getMessagesKey();
+
+       /**
+        * @return int Value that was historically in Language::STRICT_FALLBACKS
+        */
+       abstract protected function getStrictKey();
+
+       /**
+        * Convenience/readability wrapper to call a method on a class or object.
+        *
+        * @param string|object As in return value of getCallee()
+        * @param string $method Name of method to call
+        * @param mixed ...$params To pass to method
+        * @return mixed Return value of method
+        */
+       private function callMethod( $callee, $method, ...$params ) {
+               return [ $callee, $method ]( ...$params );
+       }
+
+       /**
+        * @param string $code
+        * @param array $expected
+        * @param array $options
+        * @dataProvider provideGetFallbacksFor
+        * @covers ::getFallbackFor
+        * @covers Language::getFallbackFor
+        */
+       public function testGetFallbackFor( $code, array $expected, array $options = [] ) {
+               $callee = $this->getCallee( $options );
+               // One behavior difference between the old static methods and the new instance methods:
+               // returning null instead of false.
+               $defaultExpected = is_object( $callee ) ? null : false;
+               $this->assertSame( $expected[0] ?? $defaultExpected,
+                       $this->callMethod( $callee, 'getFallbackFor', $code ) );
+       }
+
+       /**
+        * @param string $code
+        * @param array $expected
+        * @param array $options
+        * @dataProvider provideGetFallbacksFor
+        * @covers ::getFallbacksFor
+        * @covers Language::getFallbacksFor
+        */
+       public function testGetFallbacksFor( $code, array $expected, array $options = [] ) {
+               $this->assertSame( $expected,
+                       $this->callMethod( $this->getCallee( $options ), 'getFallbacksFor', $code ) );
+       }
+
+       /**
+        * @param string $code
+        * @param array $expected
+        * @param array $options
+        * @dataProvider provideGetFallbacksFor
+        * @covers ::getFallbacksFor
+        * @covers Language::getFallbacksFor
+        */
+       public function testGetFallbacksFor_messages( $code, array $expected, array $options = [] ) {
+               $this->assertSame( $expected,
+                       $this->callMethod( $this->getCallee( $options ), 'getFallbacksFor',
+                               $code, $this->getMessagesKey() ) );
+       }
+
+       public static function provideGetFallbacksFor() {
+               return [
+                       'en' => [ 'en', [], [ 'expectedGets' => 0 ] ],
+                       'fr' => [ 'fr', [ 'en' ] ],
+                       'sco' => [ 'sco', [ 'en' ] ],
+                       'yi' => [ 'yi', [ 'he', 'en' ] ],
+                       'ruq' => [ 'ruq', [ 'ruq-latn', 'ro', 'en' ] ],
+                       'sh' => [ 'sh', [ 'bs', 'sr-el', 'hr', 'en' ] ],
+               ];
+       }
+
+       /**
+        * @param string $code
+        * @param array $expected
+        * @param array $options
+        * @dataProvider provideGetFallbacksFor_strict
+        * @covers ::getFallbacksFor
+        * @covers Language::getFallbacksFor
+        */
+       public function testGetFallbacksFor_strict( $code, array $expected, array $options = [] ) {
+               $this->assertSame( $expected,
+                       $this->callMethod( $this->getCallee( $options ), 'getFallbacksFor',
+                               $code, $this->getStrictKey() ) );
+       }
+
+       public static function provideGetFallbacksFor_strict() {
+               return [
+                       'en' => [ 'en', [], [ 'expectedGets' => 0 ] ],
+                       'fr' => [ 'fr', [] ],
+                       'sco' => [ 'sco', [ 'en' ] ],
+                       'yi' => [ 'yi', [ 'he' ] ],
+                       'ruq' => [ 'ruq', [ 'ruq-latn', 'ro' ] ],
+                       'sh' => [ 'sh', [ 'bs', 'sr-el', 'hr' ] ],
+               ];
+       }
+
+       /**
+        * @covers ::getFallbacksFor
+        * @covers Language::getFallbacksFor
+        */
+       public function testGetFallbacksFor_exception() {
+               $this->setExpectedException( MWException::class, 'Invalid fallback mode "7"' );
+
+               $callee = $this->getCallee( [ 'expectedGets' => 0 ] );
+
+               // These should not throw, because of short-circuiting. If they do, it will fail the test,
+               // because we pass 5 and 6 instead of 7.
+               $this->callMethod( $callee, 'getFallbacksFor', 'en', 5 );
+               $this->callMethod( $callee, 'getFallbacksFor', '!!!', 6 );
+
+               // This is the one that should throw.
+               $this->callMethod( $callee, 'getFallbacksFor', 'fr', 7 );
+       }
+
+       /**
+        * @param string $code
+        * @param string $siteLangCode
+        * @param array $expected
+        * @param int $expectedGets
+        * @dataProvider provideGetFallbacksIncludingSiteLanguage
+        * @covers ::getFallbacksIncludingSiteLanguage
+        * @covers Language::getFallbacksIncludingSiteLanguage
+        */
+       public function testGetFallbacksIncludingSiteLanguage(
+               $code, $siteLangCode, array $expected, $expectedGets = 1
+       ) {
+               $callee = $this->getCallee(
+                       [ 'siteLangCode' => $siteLangCode, 'expectedGets' => $expectedGets ] );
+               $this->assertSame( $expected,
+                       $this->callMethod( $callee, 'getFallbacksIncludingSiteLanguage', $code ) );
+
+               // Call again to make sure we don't call LocalisationCache again
+               $this->callMethod( $callee, 'getFallbacksIncludingSiteLanguage', $code );
+       }
+
+       public static function provideGetFallbacksIncludingSiteLanguage() {
+               return [
+                       'en on en' => [ 'en', 'en', [ [], [ 'en' ] ], 0 ],
+                       'fr on en' => [ 'fr', 'en', [ [ 'en' ], [] ] ],
+                       'en on fr' => [ 'en', 'fr', [ [], [ 'fr', 'en' ] ] ],
+                       'fr on fr' => [ 'fr', 'fr', [ [ 'en' ], [ 'fr' ] ] ],
+
+                       'sco on en' => [ 'sco', 'en', [ [ 'en' ], [] ] ],
+                       'en on sco' => [ 'en', 'sco', [ [], [ 'sco', 'en' ] ] ],
+                       'sco on sco' => [ 'sco', 'sco', [ [ 'en' ], [ 'sco' ] ] ],
+
+                       'fr on sco' => [ 'fr', 'sco', [ [ 'en' ], [ 'sco' ] ], 2 ],
+                       'sco on fr' => [ 'sco', 'fr', [ [ 'en' ], [ 'fr' ] ], 2 ],
+
+                       'fr on yi' => [ 'fr', 'yi', [ [ 'en' ], [ 'yi', 'he' ] ], 2 ],
+                       'yi on fr' => [ 'yi', 'fr', [ [ 'he', 'en' ], [ 'fr' ] ], 2 ],
+                       'yi on yi' => [ 'yi', 'yi', [ [ 'he', 'en' ], [ 'yi' ] ] ],
+
+                       'sh on ruq' => [ 'sh', 'ruq',
+                               [ [ 'bs', 'sr-el', 'hr', 'en' ], [ 'ruq', 'ruq-latn', 'ro' ] ], 2 ],
+                       'ruq on sh' => [ 'ruq', 'sh',
+                               [ [ 'ruq-latn', 'ro', 'en' ], [ 'sh', 'bs', 'sr-el', 'hr' ] ], 2 ],
+               ];
+       }
+}