From: Happy-melon Date: Sun, 17 Apr 2011 11:31:11 +0000 (+0000) Subject: Refactor the factory/i18n/list/etc static methods from SpecialPage into their own... X-Git-Tag: 1.31.0-rc.0~30756 X-Git-Url: http://git.cyclocoop.org/%24href?a=commitdiff_plain;h=2c9cfd7cce6ba9de336cff9f9917385e5901b0c4;p=lhc%2Fweb%2Fwiklou.git Refactor the factory/i18n/list/etc static methods from SpecialPage into their own class; there's no reason we need to be parsing them in every single SpecialPage subclass. Leave all the methods as stubs in SpecialPage.php; if we required PHP 5.3 they could be replaced by a a __callStatic() magic method, but that doesn't work on PHP 5.2. Also make a few changes to the functions available. SpecialPageFactory::resolveAlias() now takes an optional subpage and returns array(,). Similarly merge getPage() and getPageByAlias(). There were many examples of (extensions particularly) making dubious assumptions about the presence or absence of subpages or canonical-ness. I didn't deprecate SpecialPage::getTitleFor() as it's got over six hundred calls. I'm rather undecided on the best position of getPage()/executePath(). Although the latter needs cleanup anyway. --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 1e384fa7aa..185335ec87 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -226,6 +226,7 @@ $wgAutoloadLocalClasses = array( 'SpecialMypage' => 'includes/SpecialPage.php', 'SpecialMytalk' => 'includes/SpecialPage.php', 'SpecialPage' => 'includes/SpecialPage.php', + 'SpecialPageFactory' => 'includes/SpecialPageFactory.php', 'SpecialRedirectToSpecial' => 'includes/SpecialPage.php', 'SquidUpdate' => 'includes/SquidUpdate.php', 'SquidPurgeClient' => 'includes/SquidPurgeClient.php', diff --git a/includes/ChangesList.php b/includes/ChangesList.php index 7bdca298f6..8732e6f636 100644 --- a/includes/ChangesList.php +++ b/includes/ChangesList.php @@ -558,7 +558,7 @@ class OldChangesList extends ChangesList { $this->insertLog( $s, $logtitle, $rc->mAttribs['rc_log_type'] ); // Log entries (old format) or log targets, and special pages } elseif( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) { - list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $rc->mAttribs['rc_title'] ); + list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] ); if( $name == 'Log' ) { $this->insertLog( $s, $rc->getTitle(), $subpage ); } @@ -694,7 +694,7 @@ class EnhancedChangesList extends ChangesList { $watched = false; // Log entries (old format) and special pages } elseif( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) { - list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $rc->mAttribs['rc_title'] ); + list( $specialName, $logtype ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] ); if ( $specialName == 'Log' ) { # Log updates, etc $logname = LogPage::logName( $logtype ); diff --git a/includes/Linker.php b/includes/Linker.php index 25f21e2f65..4f2b833cae 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -376,7 +376,7 @@ class Linker { static function normaliseSpecialPage( Title $title ) { if ( $title->getNamespace() == NS_SPECIAL ) { - list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() ); + list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); if ( !$name ) { return $title; } diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 2f09342a2f..120d55a01a 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2652,8 +2652,7 @@ class OutputPage { $ns = $title->getNamespace(); $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText(); if ( $ns == NS_SPECIAL ) { - $parts = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() ); - $canonicalName = $parts[0]; + list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); } else { $canonicalName = false; # bug 21115 } diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index ba11e3e643..0efe1bdde8 100644 --- a/includes/PrefixSearch.php +++ b/includes/PrefixSearch.php @@ -85,15 +85,13 @@ class PrefixSearch { // Unlike SpecialPage itself, we want the canonical forms of both // canonical and alias title forms... - SpecialPage::initList(); - SpecialPage::initAliasList(); $keys = array(); - foreach( array_keys( SpecialPage::$mList ) as $page ) { + foreach( SpecialPageFactory::getList() as $page => $class ) { $keys[$wgContLang->caseFold( $page )] = $page; } foreach( $wgContLang->getSpecialPageAliases() as $page => $aliases ) { - if( !array_key_exists( $page, SpecialPage::$mList ) ) {# bug 20885 + if( !array_key_exists( $page, SpecialPageFactory::getList() ) ) {# bug 20885 continue; } diff --git a/includes/Skin.php b/includes/Skin.php index 1bf2883595..08b238a035 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -303,6 +303,7 @@ abstract class Skin { * Special:Contributions mark the user which they are relevant to so that * things like the toolbox can display the information they usually are only * able to display on a user's userpage and talkpage. + * @return User */ public function getRelevantUser() { if ( isset($this->mRelevantUser) ) { @@ -485,7 +486,7 @@ abstract class Skin { if ( $title->getNamespace() == NS_SPECIAL ) { $type = 'ns-special'; // bug 23315: provide a class based on the canonical special page name without subpages - list( $canonicalName ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() ); + list( $canonicalName ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); if ( $canonicalName ) { $type .= ' ' . Sanitizer::escapeClass( "mw-special-$canonicalName" ); } else { @@ -1128,7 +1129,7 @@ abstract class Skin { } static function makeSpecialUrl( $name, $urlaction = '' ) { - $title = SpecialPage::getTitleFor( $name ); + $title = SpecialPage::getSafeTitleFor( $name ); return $title->getLocalURL( $urlaction ); } diff --git a/includes/SkinLegacy.php b/includes/SkinLegacy.php index 50fe82157d..22d658a81d 100644 --- a/includes/SkinLegacy.php +++ b/includes/SkinLegacy.php @@ -432,7 +432,7 @@ class LegacyTemplate extends BaseTemplate { function specialPagesList() { global $wgContLang, $wgServer, $wgRedirectScript; - $pages = SpecialPage::getUsablePages(); + $pages = SpecialPageFactory::getUsablePages(); foreach ( $pages as $name => $page ) { $pages[$name] = $page->getDescription(); diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php index 11488459fe..3f106c5e0d 100644 --- a/includes/SkinTemplate.php +++ b/includes/SkinTemplate.php @@ -608,8 +608,7 @@ class SkinTemplate extends Skin { # contain the original alias-with-subpage. $origTitle = Title::newFromText( $wgRequest->getText( 'title' ) ); if( $origTitle instanceof Title && $origTitle->getNamespace() == NS_SPECIAL ) { - list( $spName, $spPar ) = - SpecialPage::resolveAliasWithSubpage( $origTitle->getText() ); + list( $spName, $spPar ) = SpecialPageFactory::resolveAlias( $origTitle->getText() ); $active = $spName == 'Contributions' && ( ( $spPar && $spPar == $this->username ) || $wgRequest->getText( 'target' ) == $this->username ); diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php index e85e0b7d74..6f2cc5722a 100644 --- a/includes/SpecialPage.php +++ b/includes/SpecialPage.php @@ -201,64 +201,20 @@ class SpecialPage { static public $mAliases; static public $mListInitialised = false; - /**#@-*/ - /** * Initialise the special page list * This must be called before accessing SpecialPage::$mList + * @deprecated since 1.18 */ static function initList() { - global $wgSpecialPages; - global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication; - - if ( self::$mListInitialised ) { - return; - } - wfProfileIn( __METHOD__ ); - - # Better to set this now, to avoid infinite recursion in carelessly written hooks - self::$mListInitialised = true; - - if( !$wgDisableCounters ) { - self::$mList['Popularpages'] = 'PopularpagesPage'; - } - - if( !$wgDisableInternalSearch ) { - self::$mList['Search'] = 'SpecialSearch'; - } - - if( $wgEmailAuthentication ) { - self::$mList['Confirmemail'] = 'EmailConfirmation'; - self::$mList['Invalidateemail'] = 'EmailInvalidation'; - } - - # Add extension special pages - self::$mList = array_merge( self::$mList, $wgSpecialPages ); - - # Run hooks - # This hook can be used to remove undesired built-in special pages - wfRunHooks( 'SpecialPage_initList', array( &self::$mList ) ); - wfProfileOut( __METHOD__ ); + // Noop } + /** + * @deprecated since 1.18 + */ static function initAliasList() { - if ( !is_null( self::$mAliases ) ) { - return; - } - - global $wgContLang; - $aliases = $wgContLang->getSpecialPageAliases(); - $missingPages = self::$mList; - self::$mAliases = array(); - foreach ( $aliases as $realName => $aliasList ) { - foreach ( $aliasList as $alias ) { - self::$mAliases[$wgContLang->caseFold( $alias )] = $realName; - } - unset( $missingPages[$realName] ); - } - foreach ( $missingPages as $name => $stuff ) { - self::$mAliases[$wgContLang->caseFold( $name )] = $name; - } + // Noop } /** @@ -267,19 +223,11 @@ class SpecialPage { * * @param $alias String * @return String or false + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function resolveAlias( $alias ) { - global $wgContLang; - - if ( !self::$mListInitialised ) self::initList(); - if ( is_null( self::$mAliases ) ) self::initAliasList(); - $caseFoldedAlias = $wgContLang->caseFold( $alias ); - $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias ); - if ( isset( self::$mAliases[$caseFoldedAlias] ) ) { - return self::$mAliases[$caseFoldedAlias]; - } else { - return false; - } + list( $name, /*...*/ ) = SpecialPageFactory::resolveAlias( $alias ); + return $name; } /** @@ -289,16 +237,10 @@ class SpecialPage { * * @param $alias String * @return Array + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function resolveAliasWithSubpage( $alias ) { - $bits = explode( '/', $alias, 2 ); - $name = self::resolveAlias( $bits[0] ); - if( !isset( $bits[1] ) ) { // bug 2087 - $par = null; - } else { - $par = $bits[1]; - } - return array( $name, $par ); + return SpecialPageFactory::resolveAlias( $alias ); } /** @@ -307,14 +249,11 @@ class SpecialPage { * an associative record to $wgSpecialPages. This avoids autoloading SpecialPage. * * @param $page SpecialPage - * Deprecated in 1.7, warnings in 1.17, might be removed in 1.20 + * @deprecated in 1.7, warnings in 1.17, might be removed in 1.20 */ static function addPage( &$page ) { wfDeprecated( __METHOD__ ); - if ( !self::$mListInitialised ) { - self::initList(); - } - self::$mList[$page->mName] = $page; + SpecialPageFactory::getList()->{$page->mName} = $page; } /** @@ -322,47 +261,30 @@ class SpecialPage { * * @param $page Mixed: SpecialPage or string * @param $group String + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function setGroup( $page, $group ) { - global $wgSpecialPageGroups; - $name = is_object($page) ? $page->mName : $page; - $wgSpecialPageGroups[$name] = $group; + return SpecialPageFactory::setGroup( $page, $group ); } /** * Add a page to a certain display group for Special:SpecialPages * * @param $page SpecialPage + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function getGroup( &$page ) { - global $wgSpecialPageGroups; - static $specialPageGroupsCache = array(); - if( isset($specialPageGroupsCache[$page->mName]) ) { - return $specialPageGroupsCache[$page->mName]; - } - $msg = wfMessage('specialpages-specialpagegroup-'.strtolower($page->mName)); - if ( !$msg->isBlank() ) { - $group = $msg->text(); - } else { - $group = isset($wgSpecialPageGroups[$page->mName]) - ? $wgSpecialPageGroups[$page->mName] - : '-'; - } - if( $group == '-' ) $group = 'other'; - $specialPageGroupsCache[$page->mName] = $group; - return $group; + return SpecialPageFactory::getGroup( $page ); } /** * Remove a special page from the list * Formerly used to disable expensive or dangerous special pages. The * preferred method is now to add a SpecialPage_initList hook. + * @deprecated since 1.18 */ static function removePage( $name ) { - if ( !self::$mListInitialised ) { - self::initList(); - } - unset( self::$mList[$name] ); + unset( SpecialPageFactory::getList()->$name ); } /** @@ -370,24 +292,10 @@ class SpecialPage { * * @param $name String: name of a special page * @return Boolean: true if a special page exists with this name + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function exists( $name ) { - global $wgContLang; - if ( !self::$mListInitialised ) { - self::initList(); - } - if( !self::$mAliases ) { - self::initAliasList(); - } - - # Remove special pages inline parameters: - $bits = explode( '/', $name ); - $name = $wgContLang->caseFold($bits[0]); - - return - array_key_exists( $name, self::$mList ) - or array_key_exists( $name, self::$mAliases ) - ; + return SpecialPageFactory::exists( $name ); } /** @@ -395,26 +303,10 @@ class SpecialPage { * * @param $name String * @return SpecialPage object or null if the page doesn't exist + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function getPage( $name ) { - if ( !self::$mListInitialised ) { - self::initList(); - } - if ( array_key_exists( $name, self::$mList ) ) { - $rec = self::$mList[$name]; - if ( is_string( $rec ) ) { - $className = $rec; - self::$mList[$name] = new $className; - } elseif ( is_array( $rec ) ) { - // @deprecated officially since 1.18, unofficially since forever - wfDebug( "Array syntax for \$wgSpecialPages is deprecated, define a subclass of SpecialPage instead." ); - $className = array_shift( $rec ); - self::$mList[$name] = MWFunction::newObj( $className, $rec ); - } - return self::$mList[$name]; - } else { - return null; - } + return SpecialPageFactory::getPage( $name ); } /** @@ -422,14 +314,10 @@ class SpecialPage { * is no such special page. * * @return SpecialPage object or null if the page doesn't exist + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function getPageByAlias( $alias ) { - $realName = self::resolveAlias( $alias ); - if ( $realName ) { - return self::getPage( $realName ); - } else { - return null; - } + return SpecialPageFactory::getPage( $alias ); } /** @@ -437,46 +325,20 @@ class SpecialPage { * for the current user, and everyone. * * @return Associative array mapping page's name to its SpecialPage object + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function getUsablePages() { - global $wgUser; - if ( !self::$mListInitialised ) { - self::initList(); - } - $pages = array(); - - foreach ( self::$mList as $name => $rec ) { - $page = self::getPage( $name ); - if ( $page->isListed() - && ( - !$page->isRestricted() - || $page->userCanExecute( $wgUser ) - ) - ) { - $pages[$name] = $page; - } - } - return $pages; + return SpecialPageFactory::getUsablePages(); } /** * Return categorised listable special pages for all users * * @return Associative array mapping page's name to its SpecialPage object + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function getRegularPages() { - if ( !self::$mListInitialised ) { - self::initList(); - } - $pages = array(); - - foreach ( self::$mList as $name => $rec ) { - $page = self::getPage( $name ); - if ( $page->isListed() && !$page->isRestricted() ) { - $pages[$name] = $page; - } - } - return $pages; + return SpecialPageFactory::getRegularPages(); } /** @@ -484,25 +346,10 @@ class SpecialPage { * for the current user, but not for everyone * * @return Associative array mapping page's name to its SpecialPage object + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function getRestrictedPages() { - global $wgUser; - if( !self::$mListInitialised ) { - self::initList(); - } - $pages = array(); - - foreach( self::$mList as $name => $rec ) { - $page = self::getPage( $name ); - if( - $page->isListed() - && $page->isRestricted() - && $page->userCanExecute( $wgUser ) - ) { - $pages[$name] = $page; - } - } - return $pages; + return SpecialPageFactory::getRestrictedPages(); } /** @@ -516,81 +363,10 @@ class SpecialPage { * @param $title Title object * @param $context RequestContext * @param $including Bool output is being captured for use in {{special:whatever}} + * @deprecated since 1.18 call SpecialPageFactory method directly */ public static function executePath( &$title, RequestContext &$context, $including = false ) { - wfProfileIn( __METHOD__ ); - - # FIXME: redirects broken due to this call - $bits = explode( '/', $title->getDBkey(), 2 ); - $name = $bits[0]; - if( !isset( $bits[1] ) ) { // bug 2087 - $par = null; - } else { - $par = $bits[1]; - } - $page = SpecialPage::getPageByAlias( $name ); - # Nonexistent? - if ( !$page ) { - $context->output->setArticleRelated( false ); - $context->output->setRobotPolicy( 'noindex,nofollow' ); - $context->output->setStatusCode( 404 ); - $context->output->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' ); - wfProfileOut( __METHOD__ ); - return false; - } - - # Page exists, set the context - $page->setContext( $context ); - - # Check for redirect - if ( !$including ) { - $redirect = $page->getRedirect( $par ); - $query = $page->getRedirectQuery(); - if ( $redirect instanceof Title ) { - $url = $redirect->getFullUrl( $query ); - $context->output->redirect( $url ); - wfProfileOut( __METHOD__ ); - return $redirect; - } elseif( $redirect === true ) { - global $wgScript; - $url = $wgScript . '?' . wfArrayToCGI( $query ); - $context->output->redirect( $url ); - wfProfileOut( __METHOD__ ); - return $redirect; - } - } - - # Redirect to canonical alias for GET commands - # Not for POST, we'd lose the post data, so it's best to just distribute - # the request. Such POST requests are possible for old extensions that - # generate self-links without being aware that their default name has - # changed. - if ( !$including && $name != $page->getLocalName() && !$context->request->wasPosted() ) { - $query = $_GET; - unset( $query['title'] ); - $query = wfArrayToCGI( $query ); - $title = $page->getTitle( $par ); - $url = $title->getFullUrl( $query ); - $context->output->redirect( $url ); - wfProfileOut( __METHOD__ ); - return $redirect; - } - - if ( $including && !$page->includable() ) { - wfProfileOut( __METHOD__ ); - return false; - } elseif ( !$including ) { - $context->title = $page->getTitle(); - } - $page->including( $including ); - - // Execute special page - $profName = 'Special:' . $page->name(); - wfProfileIn( $profName ); - $page->execute( $par ); - wfProfileOut( $profName ); - wfProfileOut( __METHOD__ ); - return true; + return SpecialPageFactory::executePath( $title, $context, $including ); } /** @@ -599,24 +375,10 @@ class SpecialPage { * a redirect. * * @return String: HTML fragment + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function capturePath( &$title ) { - global $wgOut, $wgTitle; - - $oldTitle = $wgTitle; - $oldOut = $wgOut; - - $context = new RequestContext; - $context->setTitle( $title ); - $wgOut = $context->getOutput(); - - $ret = SpecialPage::executePath( $title, $context, true ); - if ( $ret === true ) { - $ret = $wgOut->getHTML(); - } - $wgTitle = $oldTitle; - $wgOut = $oldOut; - return $ret; + return SpecialPageFactory::capturePath( $title ); } /** @@ -626,33 +388,10 @@ class SpecialPage { * @param $subpage Mixed: boolean false, or string * * @return String + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function getLocalNameFor( $name, $subpage = false ) { - global $wgContLang; - $aliases = $wgContLang->getSpecialPageAliases(); - if ( isset( $aliases[$name][0] ) ) { - $name = $aliases[$name][0]; - } else { - // Try harder in case someone misspelled the correct casing - $found = false; - foreach ( $aliases as $n => $values ) { - if ( strcasecmp( $name, $n ) === 0 ) { - wfWarn( "Found alias defined for $n when searching for " . - "special page aliases for $name. Case mismatch?" ); - $name = $values[0]; - $found = true; - break; - } - } - if ( !$found ) { - wfWarn( "Did not find alias for special page '$name'. " . - "Perhaps no aliases are defined for it?" ); - } - } - if ( $subpage !== false && !is_null( $subpage ) ) { - $name = "$name/$subpage"; - } - return $wgContLang->ucfirst( $name ); + return SpecialPageFactory::getLocalNameFor( $name, $subpage ); } /** @@ -660,8 +399,8 @@ class SpecialPage { * * @return Title object */ - static function getTitleFor( $name, $subpage = false ) { - $name = self::getLocalNameFor( $name, $subpage ); + public static function getTitleFor( $name, $subpage = false ) { + $name = SpecialPageFactory::getLocalNameFor( $name, $subpage ); if ( $name ) { return Title::makeTitle( NS_SPECIAL, $name ); } else { @@ -674,8 +413,8 @@ class SpecialPage { * * @return Title object or null if the page doesn't exist */ - static function getSafeTitleFor( $name, $subpage = false ) { - $name = self::getLocalNameFor( $name, $subpage ); + public static function getSafeTitleFor( $name, $subpage = false ) { + $name = SpecialPageFactory::getLocalNameFor( $name, $subpage ); if ( $name ) { return Title::makeTitleSafe( NS_SPECIAL, $name ); } else { @@ -687,14 +426,10 @@ class SpecialPage { * Get a title for a given alias * * @return Title or null if there is no such alias + * @deprecated since 1.18 call SpecialPageFactory method directly */ static function getTitleForAlias( $alias ) { - $name = self::resolveAlias( $alias ); - if ( $name ) { - return self::getTitleFor( $name ); - } else { - return null; - } + return SpecialPageFactory::getTitleForAlias( $alias ); } /** @@ -800,7 +535,7 @@ class SpecialPage { */ function getLocalName() { if ( !isset( $this->mLocalName ) ) { - $this->mLocalName = self::getLocalNameFor( $this->mName ); + $this->mLocalName = SpecialPageFactory::getLocalNameFor( $this->mName ); } return $this->mLocalName; } @@ -846,7 +581,7 @@ class SpecialPage { * Output an error message telling the user what access level they have to have */ function displayRestrictionError() { - $this->getOutput()->permissionRequired( $this->mRestriction ); + throw new PermissionsError( $this->mRestriction ); } /** @@ -928,6 +663,8 @@ class SpecialPage { /** * Set whether this page is listed in Special:Specialpages, at run-time + * + * @return Bool */ function setListed( $listed ) { return wfSetVar( $this->mListed, $listed ); @@ -936,6 +673,8 @@ class SpecialPage { /** * If the special page is a redirect, then get the Title object it redirects to. * False otherwise. + * + * @return Title|false */ function getRedirect( $subpage ) { return false; @@ -971,7 +710,7 @@ class SpecialPage { * @param $context RequestContext * @since 1.18 */ - protected function setContext( $context ) { + public function setContext( $context ) { $this->mContext = $context; } @@ -1090,9 +829,9 @@ abstract class SpecialRedirectToSpecial extends UnlistedSpecialPage { function getRedirect( $subpage ) { if ( $this->redirSubpage === false ) { - return SpecialPage::getTitleFor( $this->redirName, $subpage ); + return SpecialPageFactory::getTitleFor( $this->redirName, $subpage ); } else { - return SpecialPage::getTitleFor( $this->redirName, $this->redirSubpage ); + return SpecialPageFactory::getTitleFor( $this->redirName, $this->redirSubpage ); } } } @@ -1187,7 +926,7 @@ class SpecialMycontributions extends UnlistedSpecialPage { function getRedirect( $subpage ) { global $wgUser; - return SpecialPage::getTitleFor( 'Contributions', $wgUser->getName() ); + return SpecialPageFactory::getTitleFor( 'Contributions', $wgUser->getName() ); } } @@ -1202,7 +941,7 @@ class SpecialMyuploads extends UnlistedSpecialPage { function getRedirect( $subpage ) { global $wgUser; - return SpecialPage::getTitleFor( 'Listfiles', $wgUser->getName() ); + return SpecialPageFactory::getTitleFor( 'Listfiles', $wgUser->getName() ); } } diff --git a/includes/SpecialPageFactory.php b/includes/SpecialPageFactory.php new file mode 100644 index 0000000000..2ccf966d36 --- /dev/null +++ b/includes/SpecialPageFactory.php @@ -0,0 +1,543 @@ + 'BrokenRedirectsPage', + 'Deadendpages' => 'DeadendpagesPage', + 'DoubleRedirects' => 'DoubleRedirectsPage', + 'Longpages' => 'LongpagesPage', + 'Ancientpages' => 'AncientpagesPage', + 'Lonelypages' => 'LonelypagesPage', + 'Fewestrevisions' => 'FewestrevisionsPage', + 'Withoutinterwiki' => 'WithoutinterwikiPage', + 'Protectedpages' => 'SpecialProtectedpages', + 'Protectedtitles' => 'SpecialProtectedtitles', + 'Shortpages' => 'ShortpagesPage', + 'Uncategorizedcategories' => 'UncategorizedcategoriesPage', + 'Uncategorizedimages' => 'UncategorizedimagesPage', + 'Uncategorizedpages' => 'UncategorizedpagesPage', + 'Uncategorizedtemplates' => 'UncategorizedtemplatesPage', + 'Unusedcategories' => 'UnusedcategoriesPage', + 'Unusedimages' => 'UnusedimagesPage', + 'Unusedtemplates' => 'UnusedtemplatesPage', + 'Unwatchedpages' => 'UnwatchedpagesPage', + 'Wantedcategories' => 'WantedcategoriesPage', + 'Wantedfiles' => 'WantedfilesPage', + 'Wantedpages' => 'WantedpagesPage', + 'Wantedtemplates' => 'WantedtemplatesPage', + + // List of pages + 'Allpages' => 'SpecialAllpages', + 'Prefixindex' => 'SpecialPrefixindex', + 'Categories' => 'SpecialCategories', + 'Disambiguations' => 'DisambiguationsPage', + 'Listredirects' => 'ListredirectsPage', + + // Login/create account + 'Userlogin' => 'LoginForm', + 'CreateAccount' => 'SpecialCreateAccount', + + // Users and rights + 'Block' => 'SpecialBlock', + 'Unblock' => 'SpecialUnblock', + 'BlockList' => 'SpecialBlockList', + 'Resetpass' => 'SpecialResetpass', + 'DeletedContributions' => 'DeletedContributionsPage', + 'Preferences' => 'SpecialPreferences', + 'Contributions' => 'SpecialContributions', + 'Listgrouprights' => 'SpecialListGroupRights', + 'Listusers' => 'SpecialListUsers' , + 'Listadmins' => 'SpecialListAdmins', + 'Listbots' => 'SpecialListBots', + 'Activeusers' => 'SpecialActiveUsers', + 'Userrights' => 'UserrightsPage', + 'EditWatchlist' => 'SpecialEditWatchlist', + + // Recent changes and logs + 'Newimages' => 'SpecialNewFiles', + 'Log' => 'SpecialLog', + 'Watchlist' => 'SpecialWatchlist', + 'Newpages' => 'SpecialNewpages', + 'Recentchanges' => 'SpecialRecentchanges', + 'Recentchangeslinked' => 'SpecialRecentchangeslinked', + 'Tags' => 'SpecialTags', + + // Media reports and uploads + 'Listfiles' => 'SpecialListFiles', + 'Filepath' => 'SpecialFilepath', + 'MIMEsearch' => 'MIMEsearchPage', + 'FileDuplicateSearch' => 'FileDuplicateSearchPage', + 'Upload' => 'SpecialUpload', + 'UploadStash' => 'SpecialUploadStash', + + // Wiki data and tools + 'Statistics' => 'SpecialStatistics', + 'Allmessages' => 'SpecialAllmessages', + 'Version' => 'SpecialVersion', + 'Lockdb' => 'SpecialLockdb', + 'Unlockdb' => 'SpecialUnlockdb', + + // Redirecting special pages + 'LinkSearch' => 'LinkSearchPage', + 'Randompage' => 'Randompage', + 'Randomredirect' => 'SpecialRandomredirect', + + // High use pages + 'Mostlinkedcategories' => 'MostlinkedCategoriesPage', + 'Mostimages' => 'MostimagesPage', + 'Mostlinked' => 'MostlinkedPage', + 'Mostlinkedtemplates' => 'MostlinkedTemplatesPage', + 'Mostcategories' => 'MostcategoriesPage', + 'Mostrevisions' => 'MostrevisionsPage', + + // Page tools + 'ComparePages' => 'SpecialComparePages', + 'Export' => 'SpecialExport', + 'Import' => 'SpecialImport', + 'Undelete' => 'SpecialUndelete', + 'Whatlinkshere' => 'SpecialWhatlinkshere', + 'MergeHistory' => 'SpecialMergeHistory', + + // Other + 'Booksources' => 'SpecialBookSources', + + // Unlisted / redirects + 'Blankpage' => 'SpecialBlankpage', + 'Blockme' => 'SpecialBlockme', + 'Emailuser' => 'SpecialEmailUser', + 'Movepage' => 'MovePageForm', + 'Mycontributions' => 'SpecialMycontributions', + 'Mypage' => 'SpecialMypage', + 'Mytalk' => 'SpecialMytalk', + 'Myuploads' => 'SpecialMyuploads', + 'PermanentLink' => 'SpecialPermanentLink', + 'Revisiondelete' => 'SpecialRevisionDelete', + 'Specialpages' => 'SpecialSpecialpages', + 'Userlogout' => 'SpecialUserlogout', + ); + + private static $mAliases; + + /** + * Initialise the special page list + * This must be called before accessing SpecialPage::$mList + */ + static function getList() { + global $wgSpecialPages; + global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication; + + if ( !is_object( self::$mList ) ) { + wfProfileIn( __METHOD__ ); + + if ( !$wgDisableCounters ) { + self::$mList['Popularpages'] = 'PopularpagesPage'; + } + + if ( !$wgDisableInternalSearch ) { + self::$mList['Search'] = 'SpecialSearch'; + } + + if ( $wgEmailAuthentication ) { + self::$mList['Confirmemail'] = 'EmailConfirmation'; + self::$mList['Invalidateemail'] = 'EmailInvalidation'; + } + + // Add extension special pages + self::$mList = array_merge( self::$mList, $wgSpecialPages ); + + // Run hooks + // This hook can be used to remove undesired built-in special pages + wfRunHooks( 'SpecialPage_initList', array( &self::$mList ) ); + + // Cast to object: func()[$key] doesn't work, but func()->$key does + settype( self::$mList, 'object' ); + + wfProfileOut( __METHOD__ ); + } + return self::$mList; + } + + static function getAliasList() { + if ( !is_object( self::$mAliases ) ) { + global $wgContLang; + $aliases = $wgContLang->getSpecialPageAliases(); + + // Objects are passed by reference by default, need to create a copy + $missingPages = clone self::getList(); + + self::$mAliases = array(); + foreach ( $aliases as $realName => $aliasList ) { + foreach ( $aliasList as $alias ) { + self::$mAliases[$wgContLang->caseFold( $alias )] = $realName; + } + unset( $missingPages->$realName ); + } + foreach ( $missingPages as $name => $stuff ) { + self::$mAliases[$wgContLang->caseFold( $name )] = $name; + } + + // Cast to object: func()[$key] doesn't work, but func()->$key does + self::$mAliases = (object)self::$mAliases; + } + return self::$mAliases; + } + + /** + * Given a special page name with a possible subpage, return an array + * where the first element is the special page name and the second is the + * subpage. + * + * @param $alias String + * @return Array( String, String|null ), or array( null, null ) if the page is invalid + */ + public static function resolveAlias( $alias ) { + global $wgContLang; + $bits = explode( '/', $alias, 2 ); + + $caseFoldedAlias = $wgContLang->caseFold( $bits[0] ); + $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias ); + if ( isset( self::getAliasList()->$caseFoldedAlias ) ) { + $name = self::getAliasList()->$caseFoldedAlias; + } else { + return array( null, null ); + } + + if ( !isset( $bits[1] ) ) { // bug 2087 + $par = null; + } else { + $par = $bits[1]; + } + + return array( $name, $par ); + } + + /** + * Add a page to a certain display group for Special:SpecialPages + * + * @param $page Mixed: SpecialPage or string + * @param $group String + */ + public static function setGroup( $page, $group ) { + global $wgSpecialPageGroups; + $name = is_object( $page ) ? $page->mName : $page; + $wgSpecialPageGroups[$name] = $group; + } + + /** + * Add a page to a certain display group for Special:SpecialPages + * + * @param $page SpecialPage + */ + public static function getGroup( &$page ) { + global $wgSpecialPageGroups; + static $specialPageGroupsCache = array(); + if ( isset( $specialPageGroupsCache[$page->mName] ) ) { + return $specialPageGroupsCache[$page->mName]; + } + $msg = wfMessage( 'specialpages-specialpagegroup-' . strtolower( $page->mName ) ); + if ( !$msg->isBlank() ) { + $group = $msg->text(); + } else { + $group = isset( $wgSpecialPageGroups[$page->mName] ) + ? $wgSpecialPageGroups[$page->mName] + : '-'; + } + if ( $group == '-' ) { + $group = 'other'; + } + $specialPageGroupsCache[$page->mName] = $group; + return $group; + } + + /** + * Check if a given name exist as a special page or as a special page alias + * + * @param $name String: name of a special page + * @return Boolean: true if a special page exists with this name + */ + public static function exists( $name ) { + list( $title, /*...*/ ) = self::resolveAlias( $name ); + return property_exists( self::getList(), $title ); + } + + /** + * Find the object with a given name and return it (or NULL) + * + * @param $name String Special page name, may be localised and/or an alias + * @return SpecialPage object or null if the page doesn't exist + */ + public static function getPage( $name ) { + list( $realName, /*...*/ ) = self::resolveAlias( $name ); + if ( property_exists( self::getList(), $realName ) ) { + $rec = self::getList()->$realName; + if ( is_string( $rec ) ) { + $className = $rec; + self::getList()->$realName = new $className; + } elseif ( is_array( $rec ) ) { + // @deprecated, officially since 1.18, unofficially since forever + wfDebug( "Array syntax for \$wgSpecialPages is deprecated, define a subclass of SpecialPage instead." ); + $className = array_shift( $rec ); + self::getList()->$realName = MWFunction::newObj( $className, $rec ); + } + return self::getList()->$realName; + } else { + return null; + } + } + + /** + * Return categorised listable special pages which are available + * for the current user, and everyone. + * + * @return Array( String => Specialpage ) + */ + public static function getUsablePages() { + global $wgUser; + $pages = array(); + foreach ( self::getList() as $name => $rec ) { + $page = self::getPage( $name ); + if ( $page->isListed() + && ( + !$page->isRestricted() + || $page->userCanExecute( $wgUser ) + ) + ) { + $pages[$name] = $page; + } + } + return $pages; + } + + /** + * Return categorised listable special pages for all users + * + * @return Array( String => Specialpage ) + */ + public static function getRegularPages() { + $pages = array(); + foreach ( self::getList() as $name => $rec ) { + $page = self::getPage( $name ); + if ( $page->isListed() && !$page->isRestricted() ) { + $pages[$name] = $page; + } + } + return $pages; + } + + /** + * Return categorised listable special pages which are available + * for the current user, but not for everyone + * + * @return Array( String => Specialpage ) + */ + public static function getRestrictedPages() { + global $wgUser; + $pages = array(); + foreach ( self::getList() as $name => $rec ) { + $page = self::getPage( $name ); + if ( + $page->isListed() + && $page->isRestricted() + && $page->userCanExecute( $wgUser ) + ) { + $pages[$name] = $page; + } + } + return $pages; + } + + /** + * Execute a special page path. + * The path may contain parameters, e.g. Special:Name/Params + * Extracts the special page name and call the execute method, passing the parameters + * + * Returns a title object if the page is redirected, false if there was no such special + * page, and true if it was successful. + * + * @param $title Title object + * @param $context RequestContext + * @param $including Bool output is being captured for use in {{special:whatever}} + */ + public static function executePath( Title &$title, RequestContext &$context, $including = false ) { + wfProfileIn( __METHOD__ ); + + // FIXME: redirects broken due to this call + $bits = explode( '/', $title->getDBkey(), 2 ); + $name = $bits[0]; + if ( !isset( $bits[1] ) ) { // bug 2087 + $par = null; + } else { + $par = $bits[1]; + } + $page = self::getPage( $name ); + // Nonexistent? + if ( !$page ) { + $context->output->setArticleRelated( false ); + $context->output->setRobotPolicy( 'noindex,nofollow' ); + $context->output->setStatusCode( 404 ); + $context->output->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' ); + wfProfileOut( __METHOD__ ); + return false; + } + + // Page exists, set the context + $page->setContext( $context ); + + // Check for redirect + if ( !$including ) { + $redirect = $page->getRedirect( $par ); + $query = $page->getRedirectQuery(); + if ( $redirect instanceof Title ) { + $url = $redirect->getFullUrl( $query ); + $context->output->redirect( $url ); + wfProfileOut( __METHOD__ ); + return $redirect; + } elseif ( $redirect === true ) { + global $wgScript; + $url = $wgScript . '?' . wfArrayToCGI( $query ); + $context->output->redirect( $url ); + wfProfileOut( __METHOD__ ); + return $redirect; + } + + // Redirect to canonical alias for GET commands + // Not for POST, we'd lose the post data, so it's best to just distribute + // the request. Such POST requests are possible for old extensions that + // generate self-links without being aware that their default name has + // changed. + if ( $name != $page->getLocalName() && !$context->request->wasPosted() ) { + $query = $_GET; + unset( $query['title'] ); + $query = wfArrayToCGI( $query ); + $title = $page->getTitle( $par ); + $url = $title->getFullUrl( $query ); + $context->output->redirect( $url ); + wfProfileOut( __METHOD__ ); + return $redirect; + } else { + $context->title = $page->getTitle(); + } + + } elseif ( !$page->includable() ) { + wfProfileOut( __METHOD__ ); + return false; + } + + $page->including( $including ); + + // Execute special page + $profName = 'Special:' . $page->name(); + wfProfileIn( $profName ); + $page->execute( $par ); + wfProfileOut( $profName ); + wfProfileOut( __METHOD__ ); + return true; + } + + /** + * Just like executePath() except it returns the HTML instead of outputting it + * Returns false if there was no such special page, or a title object if it was + * a redirect. + * + * @return String: HTML fragment + */ + static function capturePath( &$title ) { + global $wgOut, $wgTitle; + + $oldTitle = $wgTitle; + $oldOut = $wgOut; + + $context = new RequestContext; + $context->setTitle( $title ); + $wgOut = $context->getOutput(); + + $ret = self::executePath( $title, $context, true ); + if ( $ret === true ) { + $ret = $wgOut->getHTML(); + } + $wgTitle = $oldTitle; + $wgOut = $oldOut; + return $ret; + } + + /** + * Get the local name for a specified canonical name + * + * @param $name String + * @param $subpage String|Bool + * + * @return String + */ + static function getLocalNameFor( $name, $subpage = false ) { + global $wgContLang; + $aliases = $wgContLang->getSpecialPageAliases(); + if ( isset( $aliases[$name][0] ) ) { + $name = $aliases[$name][0]; + } else { + // Try harder in case someone misspelled the correct casing + $found = false; + foreach ( $aliases as $n => $values ) { + if ( strcasecmp( $name, $n ) === 0 ) { + wfWarn( "Found alias defined for $n when searching for " . + "special page aliases for $name. Case mismatch?" ); + $name = $values[0]; + $found = true; + break; + } + } + if ( !$found ) { + wfWarn( "Did not find alias for special page '$name'. " . + "Perhaps no aliases are defined for it?" ); + } + } + if ( $subpage !== false && !is_null( $subpage ) ) { + $name = "$name/$subpage"; + } + return $wgContLang->ucfirst( $name ); + } + + /** + * Get a title for a given alias + * + * @return Title or null if there is no such alias + */ + static function getTitleForAlias( $alias ) { + $name = self::resolveAlias( $alias ); + if ( $name ) { + return self::getTitleFor( $name ); + } else { + return null; + } + } +} \ No newline at end of file diff --git a/includes/Title.php b/includes/Title.php index 819ec46c77..5f15032688 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -1812,7 +1812,7 @@ class Title { # If it's a special page, ditch the subpage bit and check again if ( $this->getNamespace() == NS_SPECIAL ) { $name = $this->getDBkey(); - list( $name, /* $subpage */ ) = SpecialPage::resolveAliasWithSubpage( $name ); + list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name ); if ( $name === false ) { # Invalid special page, but we show standard login required message return false; @@ -3811,7 +3811,7 @@ class Title { return (bool)wfFindFile( $this ); case NS_SPECIAL: // valid special page - return SpecialPage::exists( $this->getDBkey() ); + return SpecialPageFactory::exists( $this->getDBkey() ); case NS_MAIN: // selflink, possibly with fragment return $this->mDbkeyform == ''; @@ -4038,7 +4038,7 @@ class Title { */ public function isSpecial( $name ) { if ( $this->getNamespace() == NS_SPECIAL ) { - list( $thisName, /* $subpage */ ) = SpecialPage::resolveAliasWithSubpage( $this->getDBkey() ); + list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() ); if ( $name == $thisName ) { return true; } @@ -4054,9 +4054,9 @@ class Title { */ public function fixSpecialName() { if ( $this->getNamespace() == NS_SPECIAL ) { - $canonicalName = SpecialPage::resolveAlias( $this->mDbkeyform ); + list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform ); if ( $canonicalName ) { - $localName = SpecialPage::getLocalNameFor( $canonicalName ); + $localName = SpecialPageFactory::getLocalNameFor( $canonicalName ); if ( $localName != $this->mDbkeyform ) { return Title::makeTitle( NS_SPECIAL, $localName ); } diff --git a/includes/Wiki.php b/includes/Wiki.php index d8ad709bbe..e3c17261a1 100644 --- a/includes/Wiki.php +++ b/includes/Wiki.php @@ -212,7 +212,7 @@ class MediaWiki { && !count( array_diff( array_keys( $this->context->request->getValues() ), array( 'action', 'title' ) ) ) ) { if ( $this->context->title->getNamespace() == NS_SPECIAL ) { - list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $this->context->title->getDBkey() ); + list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $this->context->title->getDBkey() ); if ( $name ) { $this->context->title = SpecialPage::getTitleFor( $name, $subpage ); } @@ -249,7 +249,7 @@ class MediaWiki { // Special pages } else if ( NS_SPECIAL == $this->context->title->getNamespace() ) { // actions that need to be made when we have a special pages - SpecialPage::executePath( $this->context->title, $this->context ); + SpecialPageFactory::executePath( $this->context->title, $this->context ); } else { // No match to special cases wfProfileOut( __METHOD__ ); @@ -553,7 +553,7 @@ class MediaWiki { break; case 'revisiondelete': // For show/hide submission from history page - $special = SpecialPage::getPage( 'Revisiondelete' ); + $special = SpecialPageFactory::getPage( 'Revisiondelete' ); $special->execute( '' ); break; default: diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 19a6d92676..981bb87091 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -430,7 +430,7 @@ class ApiQuery extends ApiBase { ApiQueryBase::addTitleInfo( $vals, $title ); $vals['special'] = ''; if ( $title->getNamespace() == NS_SPECIAL && - !SpecialPage::exists( $title->getDbKey() ) ) { + !SpecialPageFactory::exists( $title->getDbKey() ) ) { $vals['missing'] = ''; } elseif ( $title->getNamespace() == NS_MEDIA && !wfFindFile( $title ) ) { diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php index e59550a776..466c487721 100644 --- a/includes/parser/CoreParserFunctions.php +++ b/includes/parser/CoreParserFunctions.php @@ -653,7 +653,7 @@ class CoreParserFunctions { } static function special( $parser, $text ) { - list( $page, $subpage ) = SpecialPage::resolveAliasWithSubpage( $text ); + list( $page, $subpage ) = SpecialPageFactory::resolveAlias( $text ); if ( $page ) { $title = SpecialPage::getTitleFor( $page, $subpage ); return $title; diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index c367c89ae7..13c91d8078 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -3252,7 +3252,7 @@ class Parser { && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html'] ) { - $text = SpecialPage::capturePath( $title ); + $text = SpecialPageFactory::capturePath( $title ); if ( is_string( $text ) ) { $found = true; $isHTML = true; diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php index 6939bfb317..13bc4c2b39 100644 --- a/includes/specials/SpecialSpecialpages.php +++ b/includes/specials/SpecialSpecialpages.php @@ -51,7 +51,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage { private function getPageGroups() { global $wgSortSpecialPages; - $pages = SpecialPage::getUsablePages(); + $pages = SpecialPageFactory::getUsablePages(); if( !count( $pages ) ) { # Yeah, that was pointless. Thanks for coming. @@ -62,7 +62,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage { $groups = array(); foreach ( $pages as $page ) { if ( $page->isListed() ) { - $group = SpecialPage::getGroup( $page ); + $group = SpecialPageFactory::getGroup( $page ); if( !isset( $groups[$group] ) ) { $groups[$group] = array(); } diff --git a/maintenance/updateSpecialPages.php b/maintenance/updateSpecialPages.php index df3cdf5a48..46d0fcc5a6 100644 --- a/maintenance/updateSpecialPages.php +++ b/maintenance/updateSpecialPages.php @@ -78,7 +78,7 @@ class UpdateSpecialPages extends Maintenance { continue; } - $specialObj = SpecialPage::getPage( $special ); + $specialObj = SpecialPageFactory::getPage( $special ); if ( !$specialObj ) { $this->output( "No such special page: $special\n" ); exit;