Make SpecialPageFactory a service

Calling SpecialPageFactory methods statically is now soft-deprecated.

SpecialPageFactory::resetList() is a no-op, and I changed tests
in core to use overrideMwServices() instead.

Methods that fell back to $wgUser now require a User object being passed.

Depends-On: Ie1f80315871085b9fd4763a265b588849d94414d
Change-Id: Id8a92d57743f790b7d8c377c033cef38d1bb24de
This commit is contained in:
Aryeh Gregor 2018-08-07 13:58:31 +03:00 committed by James D. Forrester
parent 49efdca8f5
commit d4045035b0
11 changed files with 360 additions and 240 deletions

View file

@ -246,6 +246,8 @@ because of Phabricator reports.
resetServiceForTesting( 'MagicWordFactory' ) on a MediaWikiServices.
* mw.util.init() has been removed. This function is not needed anymore and was
a no-op function since 1.30.
* SpecialPageFactory::resetList() is a no-op. Call overrideMwServices()
instead.
=== Deprecations in 1.32 ===
* Use of a StartProfiler.php file is deprecated in favour of placing
@ -351,6 +353,9 @@ because of Phabricator reports.
* wfGetMainCache() is deprecated, use ObjectCache::getLocalClusterInstance()
instead.
* wfGetCache() is deprecated, use ObjectCache::getInstance() instead.
* All SpecialPageFactory static methods are deprecated. Instead, call the
methods on a SpecialPageFactory instance, which may be obtained from
MediaWikiServices.
=== Other changes in 1.32 ===
* (T198811) The following tables have had their UNIQUE indexes turned into

View file

@ -930,6 +930,7 @@ $wgAutoloadLocalClasses = [
'MediaWiki\\Search\\ParserOutputSearchDataExtractor' => __DIR__ . '/includes/search/ParserOutputSearchDataExtractor.php',
'MediaWiki\\ShellDisabledError' => __DIR__ . '/includes/exception/ShellDisabledError.php',
'MediaWiki\\Site\\MediaWikiPageNameNormalizer' => __DIR__ . '/includes/site/MediaWikiPageNameNormalizer.php',
'MediaWiki\\Special\\SpecialPageFactory' => __DIR__ . '/includes/specialpage/SpecialPageFactory.php',
'MediaWiki\\User\\UserIdentity' => __DIR__ . '/includes/user/UserIdentity.php',
'MediaWiki\\User\\UserIdentityValue' => __DIR__ . '/includes/user/UserIdentityValue.php',
'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php',
@ -1392,7 +1393,7 @@ $wgAutoloadLocalClasses = [
'SpecialPage' => __DIR__ . '/includes/specialpage/SpecialPage.php',
'SpecialPageAction' => __DIR__ . '/includes/actions/SpecialPageAction.php',
'SpecialPageData' => __DIR__ . '/includes/specials/SpecialPageData.php',
'SpecialPageFactory' => __DIR__ . '/includes/specialpage/SpecialPageFactory.php',
'SpecialPageFactory' => __DIR__ . '/includes/specialpage/SpecialPageFactory_deprecated.php',
'SpecialPageLanguage' => __DIR__ . '/includes/specials/SpecialPageLanguage.php',
'SpecialPagesWithProp' => __DIR__ . '/includes/specials/SpecialPagesWithProp.php',
'SpecialPasswordPolicies' => __DIR__ . '/includes/specials/SpecialPasswordPolicies.php',

View file

@ -15,6 +15,7 @@ use IBufferingStatsdDataFactory;
use MediaWiki\Http\HttpRequestFactory;
use MediaWiki\Preferences\PreferencesFactory;
use MediaWiki\Shell\CommandFactory;
use MediaWiki\Special\SpecialPageFactory;
use MediaWiki\Storage\BlobStore;
use MediaWiki\Storage\BlobStoreFactory;
use MediaWiki\Storage\NameTableStore;
@ -828,6 +829,14 @@ class MediaWikiServices extends ServiceContainer {
return $this->getService( 'SlotRoleStore' );
}
/**
* @since 1.32
* @return SpecialPageFactory
*/
public function getSpecialPageFactory() : SpecialPageFactory {
return $this->getService( 'SpecialPageFactory' );
}
/**
* @since 1.27
* @return IBufferingStatsdDataFactory

View file

@ -48,6 +48,7 @@ use MediaWiki\MediaWikiServices;
use MediaWiki\Preferences\PreferencesFactory;
use MediaWiki\Preferences\DefaultPreferencesFactory;
use MediaWiki\Shell\CommandFactory;
use MediaWiki\Special\SpecialPageFactory;
use MediaWiki\Storage\BlobStore;
use MediaWiki\Storage\BlobStoreFactory;
use MediaWiki\Storage\NameTableStore;
@ -545,6 +546,13 @@ return [
);
},
'SpecialPageFactory' => function ( MediaWikiServices $services ) : SpecialPageFactory {
return new SpecialPageFactory(
$services->getMainConfig(),
$services->getContentLanguage()
);
},
'StatsdDataFactory' => function ( MediaWikiServices $services ) : IBufferingStatsdDataFactory {
return new BufferingStatsdDataFactory(
rtrim( $services->getMainConfig()->get( 'StatsdMetricPrefix' ), '.' )

View file

@ -21,8 +21,19 @@
* @ingroup SpecialPage
* @defgroup SpecialPage SpecialPage
*/
namespace MediaWiki\Special;
use Config;
use Hooks;
use IContextSource;
use Language;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
use Profiler;
use RequestContext;
use SpecialPage;
use Title;
use User;
use Wikimedia\ObjectFactory;
/**
@ -43,165 +54,180 @@ use Wikimedia\ObjectFactory;
* SpecialPageFactory::$list. To remove a core static special page at runtime, use
* a SpecialPage_initList hook.
*
* @note There are two classes called SpecialPageFactory. You should use this first one, in
* namespace MediaWiki\Special, which is a service. \SpecialPageFactory is a deprecated collection
* of static methods that forwards to the global service.
*
* @ingroup SpecialPage
* @since 1.17
*/
class SpecialPageFactory {
/**
* List of special page names to the subclass of SpecialPage which handles them.
* @todo Make this a const when we drop HHVM support (T192166). It can still be private in PHP
* 7.1.
*/
private static $coreList = [
// Maintenance Reports
'BrokenRedirects' => BrokenRedirectsPage::class,
'Deadendpages' => DeadendPagesPage::class,
'DoubleRedirects' => DoubleRedirectsPage::class,
'Longpages' => LongPagesPage::class,
'Ancientpages' => AncientPagesPage::class,
'Lonelypages' => LonelyPagesPage::class,
'Fewestrevisions' => FewestrevisionsPage::class,
'Withoutinterwiki' => WithoutInterwikiPage::class,
'Protectedpages' => SpecialProtectedpages::class,
'Protectedtitles' => SpecialProtectedtitles::class,
'Shortpages' => ShortPagesPage::class,
'Uncategorizedcategories' => UncategorizedCategoriesPage::class,
'Uncategorizedimages' => UncategorizedImagesPage::class,
'Uncategorizedpages' => UncategorizedPagesPage::class,
'Uncategorizedtemplates' => UncategorizedTemplatesPage::class,
'Unusedcategories' => UnusedCategoriesPage::class,
'Unusedimages' => UnusedimagesPage::class,
'Unusedtemplates' => UnusedtemplatesPage::class,
'Unwatchedpages' => UnwatchedpagesPage::class,
'Wantedcategories' => WantedCategoriesPage::class,
'Wantedfiles' => WantedFilesPage::class,
'Wantedpages' => WantedPagesPage::class,
'Wantedtemplates' => WantedTemplatesPage::class,
'BrokenRedirects' => \BrokenRedirectsPage::class,
'Deadendpages' => \DeadendPagesPage::class,
'DoubleRedirects' => \DoubleRedirectsPage::class,
'Longpages' => \LongPagesPage::class,
'Ancientpages' => \AncientPagesPage::class,
'Lonelypages' => \LonelyPagesPage::class,
'Fewestrevisions' => \FewestrevisionsPage::class,
'Withoutinterwiki' => \WithoutInterwikiPage::class,
'Protectedpages' => \SpecialProtectedpages::class,
'Protectedtitles' => \SpecialProtectedtitles::class,
'Shortpages' => \ShortPagesPage::class,
'Uncategorizedcategories' => \UncategorizedCategoriesPage::class,
'Uncategorizedimages' => \UncategorizedImagesPage::class,
'Uncategorizedpages' => \UncategorizedPagesPage::class,
'Uncategorizedtemplates' => \UncategorizedTemplatesPage::class,
'Unusedcategories' => \UnusedCategoriesPage::class,
'Unusedimages' => \UnusedimagesPage::class,
'Unusedtemplates' => \UnusedtemplatesPage::class,
'Unwatchedpages' => \UnwatchedpagesPage::class,
'Wantedcategories' => \WantedCategoriesPage::class,
'Wantedfiles' => \WantedFilesPage::class,
'Wantedpages' => \WantedPagesPage::class,
'Wantedtemplates' => \WantedTemplatesPage::class,
// List of pages
'Allpages' => SpecialAllPages::class,
'Prefixindex' => SpecialPrefixindex::class,
'Categories' => SpecialCategories::class,
'Listredirects' => ListredirectsPage::class,
'PagesWithProp' => SpecialPagesWithProp::class,
'TrackingCategories' => SpecialTrackingCategories::class,
'Allpages' => \SpecialAllPages::class,
'Prefixindex' => \SpecialPrefixindex::class,
'Categories' => \SpecialCategories::class,
'Listredirects' => \ListredirectsPage::class,
'PagesWithProp' => \SpecialPagesWithProp::class,
'TrackingCategories' => \SpecialTrackingCategories::class,
// Authentication
'Userlogin' => SpecialUserLogin::class,
'Userlogout' => SpecialUserLogout::class,
'CreateAccount' => SpecialCreateAccount::class,
'LinkAccounts' => SpecialLinkAccounts::class,
'UnlinkAccounts' => SpecialUnlinkAccounts::class,
'ChangeCredentials' => SpecialChangeCredentials::class,
'RemoveCredentials' => SpecialRemoveCredentials::class,
'Userlogin' => \SpecialUserLogin::class,
'Userlogout' => \SpecialUserLogout::class,
'CreateAccount' => \SpecialCreateAccount::class,
'LinkAccounts' => \SpecialLinkAccounts::class,
'UnlinkAccounts' => \SpecialUnlinkAccounts::class,
'ChangeCredentials' => \SpecialChangeCredentials::class,
'RemoveCredentials' => \SpecialRemoveCredentials::class,
// Users and rights
'Activeusers' => SpecialActiveUsers::class,
'Block' => SpecialBlock::class,
'Unblock' => SpecialUnblock::class,
'BlockList' => SpecialBlockList::class,
'AutoblockList' => SpecialAutoblockList::class,
'ChangePassword' => SpecialChangePassword::class,
'BotPasswords' => SpecialBotPasswords::class,
'PasswordReset' => SpecialPasswordReset::class,
'DeletedContributions' => DeletedContributionsPage::class,
'Preferences' => SpecialPreferences::class,
'ResetTokens' => SpecialResetTokens::class,
'Contributions' => SpecialContributions::class,
'Listgrouprights' => SpecialListGroupRights::class,
'Listgrants' => SpecialListGrants::class,
'Listusers' => SpecialListUsers::class,
'Listadmins' => SpecialListAdmins::class,
'Listbots' => SpecialListBots::class,
'Userrights' => UserrightsPage::class,
'EditWatchlist' => SpecialEditWatchlist::class,
'PasswordPolicies' => SpecialPasswordPolicies::class,
'Activeusers' => \SpecialActiveUsers::class,
'Block' => \SpecialBlock::class,
'Unblock' => \SpecialUnblock::class,
'BlockList' => \SpecialBlockList::class,
'AutoblockList' => \SpecialAutoblockList::class,
'ChangePassword' => \SpecialChangePassword::class,
'BotPasswords' => \SpecialBotPasswords::class,
'PasswordReset' => \SpecialPasswordReset::class,
'DeletedContributions' => \DeletedContributionsPage::class,
'Preferences' => \SpecialPreferences::class,
'ResetTokens' => \SpecialResetTokens::class,
'Contributions' => \SpecialContributions::class,
'Listgrouprights' => \SpecialListGroupRights::class,
'Listgrants' => \SpecialListGrants::class,
'Listusers' => \SpecialListUsers::class,
'Listadmins' => \SpecialListAdmins::class,
'Listbots' => \SpecialListBots::class,
'Userrights' => \UserrightsPage::class,
'EditWatchlist' => \SpecialEditWatchlist::class,
'PasswordPolicies' => \SpecialPasswordPolicies::class,
// Recent changes and logs
'Newimages' => SpecialNewFiles::class,
'Log' => SpecialLog::class,
'Watchlist' => SpecialWatchlist::class,
'Newpages' => SpecialNewpages::class,
'Recentchanges' => SpecialRecentChanges::class,
'Recentchangeslinked' => SpecialRecentChangesLinked::class,
'Tags' => SpecialTags::class,
'Newimages' => \SpecialNewFiles::class,
'Log' => \SpecialLog::class,
'Watchlist' => \SpecialWatchlist::class,
'Newpages' => \SpecialNewpages::class,
'Recentchanges' => \SpecialRecentChanges::class,
'Recentchangeslinked' => \SpecialRecentChangesLinked::class,
'Tags' => \SpecialTags::class,
// Media reports and uploads
'Listfiles' => SpecialListFiles::class,
'Filepath' => SpecialFilepath::class,
'MediaStatistics' => MediaStatisticsPage::class,
'MIMEsearch' => MIMEsearchPage::class,
'FileDuplicateSearch' => FileDuplicateSearchPage::class,
'Upload' => SpecialUpload::class,
'UploadStash' => SpecialUploadStash::class,
'ListDuplicatedFiles' => ListDuplicatedFilesPage::class,
'Listfiles' => \SpecialListFiles::class,
'Filepath' => \SpecialFilepath::class,
'MediaStatistics' => \MediaStatisticsPage::class,
'MIMEsearch' => \MIMEsearchPage::class,
'FileDuplicateSearch' => \FileDuplicateSearchPage::class,
'Upload' => \SpecialUpload::class,
'UploadStash' => \SpecialUploadStash::class,
'ListDuplicatedFiles' => \ListDuplicatedFilesPage::class,
// Data and tools
'ApiSandbox' => SpecialApiSandbox::class,
'Statistics' => SpecialStatistics::class,
'Allmessages' => SpecialAllMessages::class,
'Version' => SpecialVersion::class,
'Lockdb' => SpecialLockdb::class,
'Unlockdb' => SpecialUnlockdb::class,
'ApiSandbox' => \SpecialApiSandbox::class,
'Statistics' => \SpecialStatistics::class,
'Allmessages' => \SpecialAllMessages::class,
'Version' => \SpecialVersion::class,
'Lockdb' => \SpecialLockdb::class,
'Unlockdb' => \SpecialUnlockdb::class,
// Redirecting special pages
'LinkSearch' => LinkSearchPage::class,
'Randompage' => RandomPage::class,
'RandomInCategory' => SpecialRandomInCategory::class,
'Randomredirect' => SpecialRandomredirect::class,
'Randomrootpage' => SpecialRandomrootpage::class,
'GoToInterwiki' => SpecialGoToInterwiki::class,
'LinkSearch' => \LinkSearchPage::class,
'Randompage' => \RandomPage::class,
'RandomInCategory' => \SpecialRandomInCategory::class,
'Randomredirect' => \SpecialRandomredirect::class,
'Randomrootpage' => \SpecialRandomrootpage::class,
'GoToInterwiki' => \SpecialGoToInterwiki::class,
// High use pages
'Mostlinkedcategories' => MostlinkedCategoriesPage::class,
'Mostimages' => MostimagesPage::class,
'Mostinterwikis' => MostinterwikisPage::class,
'Mostlinked' => MostlinkedPage::class,
'Mostlinkedtemplates' => MostlinkedTemplatesPage::class,
'Mostcategories' => MostcategoriesPage::class,
'Mostrevisions' => MostrevisionsPage::class,
'Mostlinkedcategories' => \MostlinkedCategoriesPage::class,
'Mostimages' => \MostimagesPage::class,
'Mostinterwikis' => \MostinterwikisPage::class,
'Mostlinked' => \MostlinkedPage::class,
'Mostlinkedtemplates' => \MostlinkedTemplatesPage::class,
'Mostcategories' => \MostcategoriesPage::class,
'Mostrevisions' => \MostrevisionsPage::class,
// Page tools
'ComparePages' => SpecialComparePages::class,
'Export' => SpecialExport::class,
'Import' => SpecialImport::class,
'Undelete' => SpecialUndelete::class,
'Whatlinkshere' => SpecialWhatLinksHere::class,
'MergeHistory' => SpecialMergeHistory::class,
'ExpandTemplates' => SpecialExpandTemplates::class,
'ComparePages' => \SpecialComparePages::class,
'Export' => \SpecialExport::class,
'Import' => \SpecialImport::class,
'Undelete' => \SpecialUndelete::class,
'Whatlinkshere' => \SpecialWhatLinksHere::class,
'MergeHistory' => \SpecialMergeHistory::class,
'ExpandTemplates' => \SpecialExpandTemplates::class,
// Other
'Booksources' => SpecialBookSources::class,
'Booksources' => \SpecialBookSources::class,
// Unlisted / redirects
'ApiHelp' => SpecialApiHelp::class,
'Blankpage' => SpecialBlankpage::class,
'Diff' => SpecialDiff::class,
'EditTags' => SpecialEditTags::class,
'Emailuser' => SpecialEmailUser::class,
'Movepage' => MovePageForm::class,
'Mycontributions' => SpecialMycontributions::class,
'MyLanguage' => SpecialMyLanguage::class,
'Mypage' => SpecialMypage::class,
'Mytalk' => SpecialMytalk::class,
'Myuploads' => SpecialMyuploads::class,
'AllMyUploads' => SpecialAllMyUploads::class,
'PermanentLink' => SpecialPermanentLink::class,
'Redirect' => SpecialRedirect::class,
'Revisiondelete' => SpecialRevisionDelete::class,
'RunJobs' => SpecialRunJobs::class,
'Specialpages' => SpecialSpecialpages::class,
'PageData' => SpecialPageData::class,
'ApiHelp' => \SpecialApiHelp::class,
'Blankpage' => \SpecialBlankpage::class,
'Diff' => \SpecialDiff::class,
'EditTags' => \SpecialEditTags::class,
'Emailuser' => \SpecialEmailUser::class,
'Movepage' => \MovePageForm::class,
'Mycontributions' => \SpecialMycontributions::class,
'MyLanguage' => \SpecialMyLanguage::class,
'Mypage' => \SpecialMypage::class,
'Mytalk' => \SpecialMytalk::class,
'Myuploads' => \SpecialMyuploads::class,
'AllMyUploads' => \SpecialAllMyUploads::class,
'PermanentLink' => \SpecialPermanentLink::class,
'Redirect' => \SpecialRedirect::class,
'Revisiondelete' => \SpecialRevisionDelete::class,
'RunJobs' => \SpecialRunJobs::class,
'Specialpages' => \SpecialSpecialpages::class,
'PageData' => \SpecialPageData::class,
];
private static $list;
private static $aliases;
/** @var array Special page name => class name */
private $list;
/** @var array */
private $aliases;
/** @var Config */
private $config;
/** @var Language */
private $contLang;
/**
* Reset the internal list of special pages. Useful when changing $wgSpecialPages after
* the internal list has already been initialized, e.g. during testing.
* @param Config $config
* @param Language $contLang
*/
public static function resetList() {
self::$list = null;
self::$aliases = null;
public function __construct( Config $config, Language $contLang ) {
$this->config = $config;
$this->contLang = $contLang;
}
/**
@ -210,8 +236,8 @@ class SpecialPageFactory {
*
* @return string[]
*/
public static function getNames() {
return array_keys( self::getPageList() );
public function getNames() : array {
return array_keys( $this->getPageList() );
}
/**
@ -219,49 +245,44 @@ class SpecialPageFactory {
*
* @return array
*/
private static function getPageList() {
global $wgSpecialPages;
global $wgDisableInternalSearch, $wgEmailAuthentication;
global $wgEnableEmail, $wgEnableJavaScriptTest;
global $wgPageLanguageUseDB, $wgContentHandlerUseDB;
private function getPageList() : array {
if ( !is_array( $this->list ) ) {
$this->list = self::$coreList;
if ( !is_array( self::$list ) ) {
self::$list = self::$coreList;
if ( !$wgDisableInternalSearch ) {
self::$list['Search'] = SpecialSearch::class;
if ( !$this->config->get( 'DisableInternalSearch' ) ) {
$this->list['Search'] = \SpecialSearch::class;
}
if ( $wgEmailAuthentication ) {
self::$list['Confirmemail'] = EmailConfirmation::class;
self::$list['Invalidateemail'] = EmailInvalidation::class;
if ( $this->config->get( 'EmailAuthentication' ) ) {
$this->list['Confirmemail'] = \EmailConfirmation::class;
$this->list['Invalidateemail'] = \EmailInvalidation::class;
}
if ( $wgEnableEmail ) {
self::$list['ChangeEmail'] = SpecialChangeEmail::class;
if ( $this->config->get( 'EnableEmail' ) ) {
$this->list['ChangeEmail'] = \SpecialChangeEmail::class;
}
if ( $wgEnableJavaScriptTest ) {
self::$list['JavaScriptTest'] = SpecialJavaScriptTest::class;
if ( $this->config->get( 'EnableJavaScriptTest' ) ) {
$this->list['JavaScriptTest'] = \SpecialJavaScriptTest::class;
}
if ( $wgPageLanguageUseDB ) {
self::$list['PageLanguage'] = SpecialPageLanguage::class;
if ( $this->config->get( 'PageLanguageUseDB' ) ) {
$this->list['PageLanguage'] = \SpecialPageLanguage::class;
}
if ( $wgContentHandlerUseDB ) {
self::$list['ChangeContentModel'] = SpecialChangeContentModel::class;
if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
$this->list['ChangeContentModel'] = \SpecialChangeContentModel::class;
}
// Add extension special pages
self::$list = array_merge( self::$list, $wgSpecialPages );
$this->list = array_merge( $this->list, $this->config->get( 'SpecialPages' ) );
// This hook can be used to disable unwanted core special pages
// or conditionally register special pages.
Hooks::run( 'SpecialPage_initList', [ &self::$list ] );
Hooks::run( 'SpecialPage_initList', [ &$this->list ] );
}
return self::$list;
return $this->list;
}
/**
@ -270,19 +291,18 @@ class SpecialPageFactory {
* All registered special pages are guaranteed to map to themselves.
* @return array
*/
private static function getAliasList() {
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
if ( is_null( self::$aliases ) ) {
$aliases = $contLang->getSpecialPageAliases();
$pageList = self::getPageList();
private function getAliasList() : array {
if ( is_null( $this->aliases ) ) {
$aliases = $this->contLang->getSpecialPageAliases();
$pageList = $this->getPageList();
self::$aliases = [];
$this->aliases = [];
$keepAlias = [];
// Force every canonical name to be an alias for itself.
foreach ( $pageList as $name => $stuff ) {
$caseFoldedAlias = $contLang->caseFold( $name );
self::$aliases[$caseFoldedAlias] = $name;
$caseFoldedAlias = $this->contLang->caseFold( $name );
$this->aliases[$caseFoldedAlias] = $name;
$keepAlias[$caseFoldedAlias] = 'canonical';
}
@ -291,24 +311,24 @@ class SpecialPageFactory {
foreach ( $aliases as $realName => $aliasList ) {
$aliasList = array_values( $aliasList );
foreach ( $aliasList as $i => $alias ) {
$caseFoldedAlias = $contLang->caseFold( $alias );
$caseFoldedAlias = $this->contLang->caseFold( $alias );
if ( isset( self::$aliases[$caseFoldedAlias] ) &&
$realName === self::$aliases[$caseFoldedAlias]
if ( isset( $this->aliases[$caseFoldedAlias] ) &&
$realName === $this->aliases[$caseFoldedAlias]
) {
// Ignore same-realName conflicts
continue;
}
if ( !isset( $keepAlias[$caseFoldedAlias] ) ) {
self::$aliases[$caseFoldedAlias] = $realName;
$this->aliases[$caseFoldedAlias] = $realName;
if ( !$i ) {
$keepAlias[$caseFoldedAlias] = 'first';
}
} elseif ( !$i ) {
wfWarn( "First alias '$alias' for $realName conflicts with " .
"{$keepAlias[$caseFoldedAlias]} alias for " .
self::$aliases[$caseFoldedAlias]
$this->aliases[$caseFoldedAlias]
);
}
}
@ -316,7 +336,7 @@ class SpecialPageFactory {
}
}
return self::$aliases;
return $this->aliases;
}
/**
@ -327,13 +347,12 @@ class SpecialPageFactory {
* @param string $alias
* @return array Array( String, String|null ), or array( null, null ) if the page is invalid
*/
public static function resolveAlias( $alias ) {
public function resolveAlias( $alias ) {
$bits = explode( '/', $alias, 2 );
$caseFoldedAlias = MediaWikiServices::getInstance()->getContentLanguage()->
caseFold( $bits[0] );
$caseFoldedAlias = $this->contLang->caseFold( $bits[0] );
$caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
$aliases = self::getAliasList();
$aliases = $this->getAliasList();
if ( isset( $aliases[$caseFoldedAlias] ) ) {
$name = $aliases[$caseFoldedAlias];
} else {
@ -355,10 +374,10 @@ class SpecialPageFactory {
* @param string $name Name of a special page
* @return bool True if a special page exists with this name
*/
public static function exists( $name ) {
list( $title, /*...*/ ) = self::resolveAlias( $name );
public function exists( $name ) {
list( $title, /*...*/ ) = $this->resolveAlias( $name );
$specialPageList = self::getPageList();
$specialPageList = $this->getPageList();
return isset( $specialPageList[$title] );
}
@ -368,17 +387,17 @@ class SpecialPageFactory {
* @param string $name Special page name, may be localised and/or an alias
* @return SpecialPage|null SpecialPage object or null if the page doesn't exist
*/
public static function getPage( $name ) {
list( $realName, /*...*/ ) = self::resolveAlias( $name );
public function getPage( $name ) {
list( $realName, /*...*/ ) = $this->resolveAlias( $name );
$specialPageList = self::getPageList();
$specialPageList = $this->getPageList();
if ( isset( $specialPageList[$realName] ) ) {
$rec = $specialPageList[$realName];
if ( is_callable( $rec ) ) {
// Use callback to instantiate the special page
$page = call_user_func( $rec );
$page = $rec();
} elseif ( is_string( $rec ) ) {
$className = $rec;
$page = new $className;
@ -416,18 +435,14 @@ class SpecialPageFactory {
* Return categorised listable special pages which are available
* for the current user, and everyone.
*
* @param User|null $user User object to check permissions, $wgUser will be used
* if not provided
* @param User $user User object to check permissions
* provided
* @return array ( string => Specialpage )
*/
public static function getUsablePages( User $user = null ) {
public function getUsablePages( User $user ) : array {
$pages = [];
if ( $user === null ) {
global $wgUser;
$user = $wgUser;
}
foreach ( self::getPageList() as $name => $rec ) {
$page = self::getPage( $name );
foreach ( $this->getPageList() as $name => $rec ) {
$page = $this->getPage( $name );
if ( $page ) { // not null
$page->setContext( RequestContext::getMain() );
if ( $page->isListed()
@ -446,10 +461,10 @@ class SpecialPageFactory {
*
* @return array ( string => Specialpage )
*/
public static function getRegularPages() {
public function getRegularPages() : array {
$pages = [];
foreach ( self::getPageList() as $name => $rec ) {
$page = self::getPage( $name );
foreach ( $this->getPageList() as $name => $rec ) {
$page = $this->getPage( $name );
if ( $page && $page->isListed() && !$page->isRestricted() ) {
$pages[$name] = $page;
}
@ -462,17 +477,13 @@ class SpecialPageFactory {
* Return categorised listable special pages which are available
* for the current user, but not for everyone
*
* @param User|null $user User object to use or null for $wgUser
* @param User $user User object to use
* @return array ( string => Specialpage )
*/
public static function getRestrictedPages( User $user = null ) {
public function getRestrictedPages( User $user ) : array {
$pages = [];
if ( $user === null ) {
global $wgUser;
$user = $wgUser;
}
foreach ( self::getPageList() as $name => $rec ) {
$page = self::getPage( $name );
foreach ( $this->getPageList() as $name => $rec ) {
$page = $this->getPage( $name );
if ( $page
&& $page->isListed()
&& $page->isRestricted()
@ -500,7 +511,7 @@ class SpecialPageFactory {
*
* @return bool|Title
*/
public static function executePath( Title &$title, IContextSource &$context, $including = false,
public function executePath( Title &$title, IContextSource &$context, $including = false,
LinkRenderer $linkRenderer = null
) {
// @todo FIXME: Redirects broken due to this call
@ -512,7 +523,7 @@ class SpecialPageFactory {
$par = $bits[1];
}
$page = self::getPage( $name );
$page = $this->getPage( $name );
if ( !$page ) {
$context->getOutput()->setArticleRelated( false );
$context->getOutput()->setRobotPolicy( 'noindex,nofollow' );
@ -587,7 +598,7 @@ class SpecialPageFactory {
* @param LinkRenderer|null $linkRenderer (since 1.28)
* @return string HTML fragment
*/
public static function capturePath(
public function capturePath(
Title $title, IContextSource $context, LinkRenderer $linkRenderer = null
) {
global $wgTitle, $wgOut, $wgRequest, $wgUser, $wgLang;
@ -622,7 +633,7 @@ class SpecialPageFactory {
$main->setLanguage( $context->getLanguage() );
// The useful part
$ret = self::executePath( $title, $context, true, $linkRenderer );
$ret = $this->executePath( $title, $context, true, $linkRenderer );
// Restore old globals and context
$wgTitle = $glob['title'];
@ -646,16 +657,15 @@ class SpecialPageFactory {
* @param string|bool $subpage
* @return string
*/
public static function getLocalNameFor( $name, $subpage = false ) {
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
$aliases = $contLang->getSpecialPageAliases();
$aliasList = self::getAliasList();
public function getLocalNameFor( $name, $subpage = false ) {
$aliases = $this->contLang->getSpecialPageAliases();
$aliasList = $this->getAliasList();
// Find the first alias that maps back to $name
if ( isset( $aliases[$name] ) ) {
$found = false;
foreach ( $aliases[$name] as $alias ) {
$caseFoldedAlias = $contLang->caseFold( $alias );
$caseFoldedAlias = $this->contLang->caseFold( $alias );
$caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
if ( isset( $aliasList[$caseFoldedAlias] ) &&
$aliasList[$caseFoldedAlias] === $name
@ -676,7 +686,7 @@ class SpecialPageFactory {
if ( strcasecmp( $name, $n ) === 0 ) {
wfWarn( "Found alias defined for $n when searching for " .
"special page aliases for $name. Case mismatch?" );
return self::getLocalNameFor( $n, $subpage );
return $this->getLocalNameFor( $n, $subpage );
}
}
}
@ -691,7 +701,7 @@ class SpecialPageFactory {
$name = "$name/$subpage";
}
return $contLang->ucfirst( $name );
return $this->contLang->ucfirst( $name );
}
/**
@ -700,8 +710,8 @@ class SpecialPageFactory {
* @param string $alias
* @return Title|null Title or null if there is no such alias
*/
public static function getTitleForAlias( $alias ) {
list( $name, $subpage ) = self::resolveAlias( $alias );
public function getTitleForAlias( $alias ) {
list( $name, $subpage ) = $this->resolveAlias( $alias );
if ( $name != null ) {
return SpecialPage::getTitleFor( $name, $subpage );
} else {

View file

@ -0,0 +1,96 @@
<?php
/**
* Factory for handling the special page list and generating SpecialPage objects.
*
* 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 SpecialPage
* @defgroup SpecialPage SpecialPage
*/
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
// phpcs:disable MediaWiki.Files.ClassMatchesFilename.NotMatch
/**
* Wrapper for backward compatibility for old callers that used static methods.
*
* @deprecated since 1.32, use the SpecialPageFactory service instead
*/
class SpecialPageFactory {
public static function getNames() : array {
return MediaWikiServices::getInstance()->getSpecialPageFactory()->getNames();
}
public static function resolveAlias( $alias ) : array {
return MediaWikiServices::getInstance()->getSpecialPageFactory()->resolveAlias( $alias );
}
public static function exists( $name ) {
return MediaWikiServices::getInstance()->getSpecialPageFactory()->exists( $name );
}
public static function getPage( $name ) {
return MediaWikiServices::getInstance()->getSpecialPageFactory()->getPage( $name );
}
public static function getUsablePages( User $user = null ) : array {
global $wgUser;
$user = $user ?? $wgUser;
return MediaWikiServices::getInstance()->getSpecialPageFactory()->getUsablePages( $user );
}
public static function getRegularPages() : array {
return MediaWikiServices::getInstance()->getSpecialPageFactory()->getRegularPages();
}
public static function getRestrictedPages( User $user = null ) : array {
global $wgUser;
$user = $user ?? $wgUser;
return MediaWikiServices::getInstance()->getSpecialPageFactory()->getRestrictedPages( $user );
}
public static function executePath( Title &$title, IContextSource &$context, $including = false,
LinkRenderer $linkRenderer = null
) {
return MediaWikiServices::getInstance()->getSpecialPageFactory()
->executePath( $title, $context, $including, $linkRenderer );
}
public static function capturePath(
Title $title, IContextSource $context, LinkRenderer $linkRenderer = null
) {
return MediaWikiServices::getInstance()->getSpecialPageFactory()
->capturePath( $title, $context, $linkRenderer );
}
public static function getLocalNameFor( $name, $subpage = false ) {
return MediaWikiServices::getInstance()->getSpecialPageFactory()
->getLocalNameFor( $name, $subpage );
}
public static function getTitleForAlias( $alias ) {
return MediaWikiServices::getInstance()->getSpecialPageFactory()
->getTitleForAlias( $alias );
}
/**
* No-op since 1.32, call overrideMwServices() instead
*/
public static function resetList() {
}
}

View file

@ -947,7 +947,9 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
* @return MediaWikiServices
* @throws MWException
*/
protected function overrideMwServices( Config $configOverrides = null, array $services = [] ) {
protected static function overrideMwServices(
Config $configOverrides = null, array $services = []
) {
if ( !$configOverrides ) {
$configOverrides = new HashConfig();
}

View file

@ -61,7 +61,7 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
$this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers;
TestingAccessWrapper::newFromClass( Hooks::class )->handlers = [];
SpecialPageFactory::resetList();
$this->overrideMwServices();
}
public function tearDown() {
@ -69,7 +69,7 @@ class PrefixSearchTest extends MediaWikiLangTestCase {
TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;
SpecialPageFactory::resetList();
$this->overrideMwServices();
}
protected function searchProvision( array $results = null ) {

View file

@ -69,7 +69,7 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
$this->originalHandlers = TestingAccessWrapper::newFromClass( Hooks::class )->handlers;
TestingAccessWrapper::newFromClass( Hooks::class )->handlers = [];
SpecialPageFactory::resetList();
$this->overrideMwServices();
}
public function tearDown() {
@ -77,7 +77,7 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;
SpecialPageFactory::resetList();
$this->overrideMwServices();
}
protected function searchProvision( array $results = null ) {

View file

@ -21,22 +21,10 @@ use Wikimedia\ScopedCallback;
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @covers SpecialPageFactory
* @covers \MediaWiki\Special\SpecialPageFactory
* @group SpecialPage
*/
class SpecialPageFactoryTest extends MediaWikiTestCase {
protected function tearDown() {
parent::tearDown();
SpecialPageFactory::resetList();
}
public function testResetList() {
SpecialPageFactory::resetList();
$this->assertContains( 'Specialpages', SpecialPageFactory::getNames() );
}
public function testHookNotCalledTwice() {
$count = 0;
$this->mergeMwGlobalArrayValue( 'wgHooks', [
@ -45,9 +33,10 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
$count++;
}
] ] );
SpecialPageFactory::resetList();
SpecialPageFactory::getNames();
SpecialPageFactory::getNames();
$this->overrideMwServices();
$spf = MediaWikiServices::getInstance()->getSpecialPageFactory();
$spf->getNames();
$spf->getNames();
$this->assertEquals( 1, $count );
}
@ -82,7 +71,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
*/
public function testGetPage( $spec, $shouldReuseInstance ) {
$this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => $spec ] );
SpecialPageFactory::resetList();
$this->overrideMwServices();
$page = SpecialPageFactory::getPage( 'testdummy' );
$this->assertInstanceOf( SpecialPage::class, $page );
@ -96,7 +85,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
*/
public function testGetNames() {
$this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => SpecialAllPages::class ] );
SpecialPageFactory::resetList();
$this->overrideMwServices();
$names = SpecialPageFactory::getNames();
$this->assertInternalType( 'array', $names );
@ -108,7 +97,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
*/
public function testResolveAlias() {
$this->setContentLang( 'de' );
SpecialPageFactory::resetList();
$this->overrideMwServices();
list( $name, $param ) = SpecialPageFactory::resolveAlias( 'Spezialseiten/Foo' );
$this->assertEquals( 'Specialpages', $name );
@ -120,7 +109,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
*/
public function testGetLocalNameFor() {
$this->setContentLang( 'de' );
SpecialPageFactory::resetList();
$this->overrideMwServices();
$name = SpecialPageFactory::getLocalNameFor( 'Specialpages', 'Foo' );
$this->assertEquals( 'Spezialseiten/Foo', $name );
@ -131,7 +120,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
*/
public function testGetTitleForAlias() {
$this->setContentLang( 'de' );
SpecialPageFactory::resetList();
$this->overrideMwServices();
$title = SpecialPageFactory::getTitleForAlias( 'Specialpages/Foo' );
$this->assertEquals( 'Spezialseiten/Foo', $title->getText() );
@ -146,11 +135,11 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
) {
$lang = clone MediaWikiServices::getInstance()->getContentLanguage();
$lang->mExtendedSpecialPageAliases = $aliasesList;
$this->setContentLang( $lang );
$this->setMwGlobals( 'wgSpecialPages',
array_combine( array_keys( $aliasesList ), array_keys( $aliasesList ) )
);
SpecialPageFactory::resetList();
$this->overrideMwServices();
$this->setContentLang( $lang );
// Catch the warnings we expect to be raised
$warnings = [];
@ -278,7 +267,7 @@ class SpecialPageFactoryTest extends MediaWikiTestCase {
}
],
] );
SpecialPageFactory::resetList();
$this->overrideMwServices();
SpecialPageFactory::getLocalNameFor( 'Specialpages' );
$this->assertTrue( $called, 'Recursive call succeeded' );
}

View file

@ -14,11 +14,11 @@ class SpecialPageFatalTest extends MediaWikiTestCase {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
SpecialPageFactory::resetList();
self::overrideMwServices();
}
public static function tearDownAfterClass() {
SpecialPageFactory::resetList();
self::overrideMwServices();
parent::tearDownAfterClass();
}