'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', 'Listredirects' => 'ListredirectsPage', 'PagesWithProp' => 'SpecialPagesWithProp', 'TrackingCategories' => 'SpecialTrackingCategories', // Login/create account 'Userlogin' => 'LoginForm', 'CreateAccount' => 'SpecialCreateAccount', // Users and rights 'Block' => 'SpecialBlock', 'Unblock' => 'SpecialUnblock', 'BlockList' => 'SpecialBlockList', 'ChangePassword' => 'SpecialChangePassword', 'PasswordReset' => 'SpecialPasswordReset', 'DeletedContributions' => 'DeletedContributionsPage', 'Preferences' => 'SpecialPreferences', 'ResetTokens' => 'SpecialResetTokens', 'Contributions' => 'SpecialContributions', 'Listgrouprights' => 'SpecialListGroupRights', 'Listusers' => 'SpecialListUsers', 'Listadmins' => 'SpecialListAdmins', 'Listbots' => 'SpecialListBots', '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', 'MediaStatistics' => 'MediaStatisticsPage', 'MIMEsearch' => 'MIMEsearchPage', 'FileDuplicateSearch' => 'FileDuplicateSearchPage', 'Upload' => 'SpecialUpload', 'UploadStash' => 'SpecialUploadStash', 'ListDuplicatedFiles' => 'ListDuplicatedFilesPage', // Data and tools 'Statistics' => 'SpecialStatistics', 'Allmessages' => 'SpecialAllMessages', 'Version' => 'SpecialVersion', 'Lockdb' => 'SpecialLockdb', 'Unlockdb' => 'SpecialUnlockdb', // Redirecting special pages 'LinkSearch' => 'LinkSearchPage', 'Randompage' => 'RandomPage', 'RandomInCategory' => 'SpecialRandomInCategory', 'Randomredirect' => 'SpecialRandomredirect', // High use pages 'Mostlinkedcategories' => 'MostlinkedCategoriesPage', 'Mostimages' => 'MostimagesPage', 'Mostinterwikis' => 'MostinterwikisPage', 'Mostlinked' => 'MostlinkedPage', 'Mostlinkedtemplates' => 'MostlinkedTemplatesPage', 'Mostcategories' => 'MostcategoriesPage', 'Mostrevisions' => 'MostrevisionsPage', // Page tools 'ComparePages' => 'SpecialComparePages', 'Export' => 'SpecialExport', 'Import' => 'SpecialImport', 'Undelete' => 'SpecialUndelete', 'Whatlinkshere' => 'SpecialWhatLinksHere', 'MergeHistory' => 'SpecialMergeHistory', 'ExpandTemplates' => 'SpecialExpandTemplates', // Other 'Booksources' => 'SpecialBookSources', // Unlisted / redirects 'Blankpage' => 'SpecialBlankpage', 'Diff' => 'SpecialDiff', 'Emailuser' => 'SpecialEmailUser', 'Movepage' => 'MovePageForm', 'Mycontributions' => 'SpecialMycontributions', 'MyLanguage' => 'SpecialMyLanguage', 'Mypage' => 'SpecialMypage', 'Mytalk' => 'SpecialMytalk', 'Myuploads' => 'SpecialMyuploads', 'AllMyUploads' => 'SpecialAllMyUploads', 'PermanentLink' => 'SpecialPermanentLink', 'Redirect' => 'SpecialRedirect', 'Revisiondelete' => 'SpecialRevisionDelete', 'RunJobs' => 'SpecialRunJobs', 'Specialpages' => 'SpecialSpecialpages', 'Userlogout' => 'SpecialUserlogout', ); private static $aliases; /** * Reset the internal list of special pages. Useful when changing $wgSpecialPages after * the internal list has already been initialized, e.g. during testing. */ public static function resetList() { self::$list = null; self::$aliases = null; } /** * Returns a list of canonical special page names. * May be used to iterate over all registered special pages. * * @return string[] */ public static function getNames() { return array_keys( self::getPageList() ); } /** * Get the special page list as an array * * @deprecated since 1.24, use getNames() instead. * @return array */ public static function getList() { wfDeprecated( __FUNCTION__, '1.24' ); return self::getPageList(); } /** * Get the special page list as an array * * @return array */ private static function getPageList() { global $wgSpecialPages; global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication; global $wgEnableEmail, $wgEnableJavaScriptTest; global $wgPageLanguageUseDB; if ( !is_object( self::$list ) ) { wfProfileIn( __METHOD__ ); if ( !$wgDisableCounters ) { self::$list['Popularpages'] = 'PopularPagesPage'; } if ( !$wgDisableInternalSearch ) { self::$list['Search'] = 'SpecialSearch'; } if ( $wgEmailAuthentication ) { self::$list['Confirmemail'] = 'EmailConfirmation'; self::$list['Invalidateemail'] = 'EmailInvalidation'; } if ( $wgEnableEmail ) { self::$list['ChangeEmail'] = 'SpecialChangeEmail'; } if ( $wgEnableJavaScriptTest ) { self::$list['JavaScriptTest'] = 'SpecialJavaScriptTest'; } if ( $wgPageLanguageUseDB ) { self::$list['PageLanguage'] = 'SpecialPageLanguage'; } self::$list['Activeusers'] = 'SpecialActiveUsers'; // Add extension special pages self::$list = array_merge( self::$list, $wgSpecialPages ); // Run hooks // This hook can be used to remove undesired built-in special pages wfRunHooks( 'SpecialPage_initList', array( &self::$list ) ); wfProfileOut( __METHOD__ ); } return self::$list; } /** * Initialise and return the list of special page aliases. Returns an object with * properties which can be accessed $obj->pagename - each property name is an * alias, with the value being the canonical name of the special page. All * registered special pages are guaranteed to map to themselves. * @return object */ private static function getAliasListObject() { if ( !is_object( self::$aliases ) ) { global $wgContLang; $aliases = $wgContLang->getSpecialPageAliases(); self::$aliases = array(); $keepAlias = array(); // Force every canonical name to be an alias for itself. foreach ( self::getPageList() as $name => $stuff ) { $caseFoldedAlias = $wgContLang->caseFold( $name ); self::$aliases[$caseFoldedAlias] = $name; $keepAlias[$caseFoldedAlias] = 'canonical'; } // Check for $aliases being an array since Language::getSpecialPageAliases can return null if ( is_array( $aliases ) ) { foreach ( $aliases as $realName => $aliasList ) { $aliasList = array_values( $aliasList ); foreach ( $aliasList as $i => $alias ) { $caseFoldedAlias = $wgContLang->caseFold( $alias ); if ( isset( self::$aliases[$caseFoldedAlias] ) && $realName === self::$aliases[$caseFoldedAlias] ) { // Ignore same-realName conflicts continue; } if ( !isset( $keepAlias[$caseFoldedAlias] ) ) { self::$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] ); } } } } // Cast to object: func()[$key] doesn't work, but func()->$key does self::$aliases = (object)self::$aliases; } return self::$aliases; } /** * 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 string $alias * @return array 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::getAliasListObject()->$caseFoldedAlias ) ) { $name = self::getAliasListObject()->$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 SpecialPage|string $page * @param string $group * @deprecated since 1.21 Override SpecialPage::getGroupName */ public static function setGroup( $page, $group ) { wfDeprecated( __METHOD__, '1.21' ); global $wgSpecialPageGroups; $name = is_object( $page ) ? $page->getName() : $page; $wgSpecialPageGroups[$name] = $group; } /** * Get the group that the special page belongs in on Special:SpecialPage * * @param SpecialPage $page * @return string * @deprecated since 1.21 Use SpecialPage::getFinalGroupName */ public static function getGroup( &$page ) { wfDeprecated( __METHOD__, '1.21' ); return $page->getFinalGroupName(); } /** * Check if a given name exist as a special page or as a special page alias * * @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 ); $specialPageList = self::getPageList(); return isset( $specialPageList[$title] ); } /** * Find the object with a given name and return it (or NULL) * * @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 ); $specialPageList = self::getPageList(); if ( isset( $specialPageList[$realName] ) ) { $rec = $specialPageList[$realName]; if ( is_callable( $rec ) ) { // Use callback to instantiate the special page $page = call_user_func( $rec ); } elseif ( is_string( $rec ) ) { $className = $rec; $page = new $className; } elseif ( is_array( $rec ) ) { $className = array_shift( $rec ); // @deprecated, officially since 1.18, unofficially since forever wfDeprecated( "Array syntax for \$wgSpecialPages is deprecated ($className), " . "define a subclass of SpecialPage instead.", '1.18' ); $page = MWFunction::newObj( $className, $rec ); } elseif ( $rec instanceof SpecialPage ) { $page = $rec; //XXX: we should deep clone here } else { $page = null; } if ( $page instanceof SpecialPage ) { return $page; } else { // It's not a classname, nor a callback, nor a legacy constructor array, // nor a special page object. Give up. wfLogWarning( "Cannot instantiate special page $realName: bad spec!" ); return null; } } else { return null; } } /** * Return categorised listable special pages which are available * for the current user, and everyone. * * @param User $user User object to check permissions, $wgUser will be used * if not provided * @return array ( string => Specialpage ) */ public static function getUsablePages( User $user = null ) { $pages = array(); if ( $user === null ) { global $wgUser; $user = $wgUser; } foreach ( self::getPageList() as $name => $rec ) { $page = self::getPage( $name ); if ( $page ) { // not null $page->setContext( RequestContext::getMain() ); if ( $page->isListed() && ( !$page->isRestricted() || $page->userCanExecute( $user ) ) ) { $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::getPageList() 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 * * @param User|null $user User object to use or null for $wgUser * @return array ( string => Specialpage ) */ public static function getRestrictedPages( User $user = null ) { $pages = array(); if ( $user === null ) { global $wgUser; $user = $wgUser; } foreach ( self::getPageList() as $name => $rec ) { $page = self::getPage( $name ); if ( $page->isListed() && $page->isRestricted() && $page->userCanExecute( $user ) ) { $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 * @param IContextSource $context * @param bool $including Bool output is being captured for use in {{special:whatever}} * * @return bool */ public static function executePath( Title &$title, IContextSource &$context, $including = false ) { wfProfileIn( __METHOD__ ); // @todo 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->getOutput()->setArticleRelated( false ); $context->getOutput()->setRobotPolicy( 'noindex,nofollow' ); global $wgSend404Code; if ( $wgSend404Code ) { $context->getOutput()->setStatusCode( 404 ); } $context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' ); wfProfileOut( __METHOD__ ); return false; } // Page exists, set the context $page->setContext( $context ); if ( !$including ) { // 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->getRequest()->wasPosted() ) { $query = $context->getRequest()->getQueryValues(); unset( $query['title'] ); $title = $page->getPageTitle( $par ); $url = $title->getFullURL( $query ); $context->getOutput()->redirect( $url ); wfProfileOut( __METHOD__ ); return $title; } else { $context->setTitle( $page->getPageTitle( $par ) ); } } elseif ( !$page->isIncludable() ) { wfProfileOut( __METHOD__ ); return false; } $page->including( $including ); // Execute special page $profName = 'Special:' . $page->getName(); wfProfileIn( $profName ); $page->run( $par ); wfProfileOut( $profName ); wfProfileOut( __METHOD__ ); return true; } /** * Just like executePath() but will override global variables and execute * the page in "inclusion" mode. Returns true if the execution was * successful or false if there was no such special page, or a title object * if it was a redirect. * * Also saves the current $wgTitle, $wgOut, $wgRequest, $wgUser and $wgLang * variables so that the special page will get the context it'd expect on a * normal request, and then restores them to their previous values after. * * @param Title $title * @param IContextSource $context * @return string HTML fragment */ public static function capturePath( Title $title, IContextSource $context ) { global $wgOut, $wgTitle, $wgRequest, $wgUser, $wgLang; // Save current globals $oldTitle = $wgTitle; $oldOut = $wgOut; $oldRequest = $wgRequest; $oldUser = $wgUser; $oldLang = $wgLang; // Set the globals to the current context $wgTitle = $title; $wgOut = $context->getOutput(); $wgRequest = $context->getRequest(); $wgUser = $context->getUser(); $wgLang = $context->getLanguage(); // The useful part $ret = self::executePath( $title, $context, true ); // And restore the old globals $wgTitle = $oldTitle; $wgOut = $oldOut; $wgRequest = $oldRequest; $wgUser = $oldUser; $wgLang = $oldLang; return $ret; } /** * Get the local name for a specified canonical name * * @param string $name * @param string|bool $subpage * @return string */ public static function getLocalNameFor( $name, $subpage = false ) { global $wgContLang; $aliases = $wgContLang->getSpecialPageAliases(); $aliasList = self::getAliasListObject(); // Find the first alias that maps back to $name if ( isset( $aliases[$name] ) ) { $found = false; foreach ( $aliases[$name] as $alias ) { $caseFoldedAlias = $wgContLang->caseFold( $alias ); $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias ); if ( isset( $aliasList->$caseFoldedAlias ) && $aliasList->$caseFoldedAlias === $name ) { $name = $alias; $found = true; break; } } if ( !$found ) { wfWarn( "Did not find a usable alias for special page '$name'. " . "It seems all defined aliases conflict?" ); } } else { // Check if someone misspelled the correct casing if ( is_array( $aliases ) ) { 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?" ); return self::getLocalNameFor( $n, $subpage ); } } } 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 * * @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 ); if ( $name != null ) { return SpecialPage::getTitleFor( $name, $subpage ); } else { return null; } } }