]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/specialpage/SpecialPageFactory.php
MediaWiki 1.30.2
[autoinstalls/mediawiki.git] / includes / specialpage / SpecialPageFactory.php
1 <?php
2 /**
3  * Factory for handling the special page list and generating SpecialPage objects.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  * @ingroup SpecialPage
22  * @defgroup SpecialPage SpecialPage
23  */
24 use MediaWiki\Linker\LinkRenderer;
25
26 /**
27  * Factory for handling the special page list and generating SpecialPage objects.
28  *
29  * To add a special page in an extension, add to $wgSpecialPages either
30  * an object instance or an array containing the name and constructor
31  * parameters. The latter is preferred for performance reasons.
32  *
33  * The object instantiated must be either an instance of SpecialPage or a
34  * sub-class thereof. It must have an execute() method, which sends the HTML
35  * for the special page to $wgOut. The parent class has an execute() method
36  * which distributes the call to the historical global functions. Additionally,
37  * execute() also checks if the user has the necessary access privileges
38  * and bails out if not.
39  *
40  * To add a core special page, use the similar static list in
41  * SpecialPageFactory::$list. To remove a core static special page at runtime, use
42  * a SpecialPage_initList hook.
43  *
44  * @ingroup SpecialPage
45  * @since 1.17
46  */
47 class SpecialPageFactory {
48         /**
49          * List of special page names to the subclass of SpecialPage which handles them.
50          */
51         private static $coreList = [
52                 // Maintenance Reports
53                 'BrokenRedirects' => 'BrokenRedirectsPage',
54                 'Deadendpages' => 'DeadendPagesPage',
55                 'DoubleRedirects' => 'DoubleRedirectsPage',
56                 'Longpages' => 'LongPagesPage',
57                 'Ancientpages' => 'AncientPagesPage',
58                 'Lonelypages' => 'LonelyPagesPage',
59                 'Fewestrevisions' => 'FewestrevisionsPage',
60                 'Withoutinterwiki' => 'WithoutInterwikiPage',
61                 'Protectedpages' => 'SpecialProtectedpages',
62                 'Protectedtitles' => 'SpecialProtectedtitles',
63                 'Shortpages' => 'ShortPagesPage',
64                 'Uncategorizedcategories' => 'UncategorizedCategoriesPage',
65                 'Uncategorizedimages' => 'UncategorizedImagesPage',
66                 'Uncategorizedpages' => 'UncategorizedPagesPage',
67                 'Uncategorizedtemplates' => 'UncategorizedTemplatesPage',
68                 'Unusedcategories' => 'UnusedCategoriesPage',
69                 'Unusedimages' => 'UnusedimagesPage',
70                 'Unusedtemplates' => 'UnusedtemplatesPage',
71                 'Unwatchedpages' => 'UnwatchedpagesPage',
72                 'Wantedcategories' => 'WantedCategoriesPage',
73                 'Wantedfiles' => 'WantedFilesPage',
74                 'Wantedpages' => 'WantedPagesPage',
75                 'Wantedtemplates' => 'WantedTemplatesPage',
76
77                 // List of pages
78                 'Allpages' => 'SpecialAllPages',
79                 'Prefixindex' => 'SpecialPrefixindex',
80                 'Categories' => 'SpecialCategories',
81                 'Listredirects' => 'ListredirectsPage',
82                 'PagesWithProp' => 'SpecialPagesWithProp',
83                 'TrackingCategories' => 'SpecialTrackingCategories',
84
85                 // Authentication
86                 'Userlogin' => 'SpecialUserLogin',
87                 'Userlogout' => 'SpecialUserLogout',
88                 'CreateAccount' => 'SpecialCreateAccount',
89                 'LinkAccounts' => 'SpecialLinkAccounts',
90                 'UnlinkAccounts' => 'SpecialUnlinkAccounts',
91                 'ChangeCredentials' => 'SpecialChangeCredentials',
92                 'RemoveCredentials' => 'SpecialRemoveCredentials',
93
94                 // Users and rights
95                 'Activeusers' => 'SpecialActiveUsers',
96                 'Block' => 'SpecialBlock',
97                 'Unblock' => 'SpecialUnblock',
98                 'BlockList' => 'SpecialBlockList',
99                 'AutoblockList' => 'SpecialAutoblockList',
100                 'ChangePassword' => 'SpecialChangePassword',
101                 'BotPasswords' => 'SpecialBotPasswords',
102                 'PasswordReset' => 'SpecialPasswordReset',
103                 'DeletedContributions' => 'DeletedContributionsPage',
104                 'Preferences' => 'SpecialPreferences',
105                 'ResetTokens' => 'SpecialResetTokens',
106                 'Contributions' => 'SpecialContributions',
107                 'Listgrouprights' => 'SpecialListGroupRights',
108                 'Listgrants' => 'SpecialListGrants',
109                 'Listusers' => 'SpecialListUsers',
110                 'Listadmins' => 'SpecialListAdmins',
111                 'Listbots' => 'SpecialListBots',
112                 'Userrights' => 'UserrightsPage',
113                 'EditWatchlist' => 'SpecialEditWatchlist',
114
115                 // Recent changes and logs
116                 'Newimages' => 'SpecialNewFiles',
117                 'Log' => 'SpecialLog',
118                 'Watchlist' => 'SpecialWatchlist',
119                 'Newpages' => 'SpecialNewpages',
120                 'Recentchanges' => 'SpecialRecentChanges',
121                 'Recentchangeslinked' => 'SpecialRecentChangesLinked',
122                 'Tags' => 'SpecialTags',
123
124                 // Media reports and uploads
125                 'Listfiles' => 'SpecialListFiles',
126                 'Filepath' => 'SpecialFilepath',
127                 'MediaStatistics' => 'MediaStatisticsPage',
128                 'MIMEsearch' => 'MIMEsearchPage',
129                 'FileDuplicateSearch' => 'FileDuplicateSearchPage',
130                 'Upload' => 'SpecialUpload',
131                 'UploadStash' => 'SpecialUploadStash',
132                 'ListDuplicatedFiles' => 'ListDuplicatedFilesPage',
133
134                 // Data and tools
135                 'ApiSandbox' => 'SpecialApiSandbox',
136                 'Statistics' => 'SpecialStatistics',
137                 'Allmessages' => 'SpecialAllMessages',
138                 'Version' => 'SpecialVersion',
139                 'Lockdb' => 'SpecialLockdb',
140                 'Unlockdb' => 'SpecialUnlockdb',
141
142                 // Redirecting special pages
143                 'LinkSearch' => 'LinkSearchPage',
144                 'Randompage' => 'RandomPage',
145                 'RandomInCategory' => 'SpecialRandomInCategory',
146                 'Randomredirect' => 'SpecialRandomredirect',
147                 'Randomrootpage' => 'SpecialRandomrootpage',
148                 'GoToInterwiki' => 'SpecialGoToInterwiki',
149
150                 // High use pages
151                 'Mostlinkedcategories' => 'MostlinkedCategoriesPage',
152                 'Mostimages' => 'MostimagesPage',
153                 'Mostinterwikis' => 'MostinterwikisPage',
154                 'Mostlinked' => 'MostlinkedPage',
155                 'Mostlinkedtemplates' => 'MostlinkedTemplatesPage',
156                 'Mostcategories' => 'MostcategoriesPage',
157                 'Mostrevisions' => 'MostrevisionsPage',
158
159                 // Page tools
160                 'ComparePages' => 'SpecialComparePages',
161                 'Export' => 'SpecialExport',
162                 'Import' => 'SpecialImport',
163                 'Undelete' => 'SpecialUndelete',
164                 'Whatlinkshere' => 'SpecialWhatLinksHere',
165                 'MergeHistory' => 'SpecialMergeHistory',
166                 'ExpandTemplates' => 'SpecialExpandTemplates',
167
168                 // Other
169                 'Booksources' => 'SpecialBookSources',
170
171                 // Unlisted / redirects
172                 'ApiHelp' => 'SpecialApiHelp',
173                 'Blankpage' => 'SpecialBlankpage',
174                 'Diff' => 'SpecialDiff',
175                 'EditTags' => 'SpecialEditTags',
176                 'Emailuser' => 'SpecialEmailUser',
177                 'Movepage' => 'MovePageForm',
178                 'Mycontributions' => 'SpecialMycontributions',
179                 'MyLanguage' => 'SpecialMyLanguage',
180                 'Mypage' => 'SpecialMypage',
181                 'Mytalk' => 'SpecialMytalk',
182                 'Myuploads' => 'SpecialMyuploads',
183                 'AllMyUploads' => 'SpecialAllMyUploads',
184                 'PermanentLink' => 'SpecialPermanentLink',
185                 'Redirect' => 'SpecialRedirect',
186                 'Revisiondelete' => 'SpecialRevisionDelete',
187                 'RunJobs' => 'SpecialRunJobs',
188                 'Specialpages' => 'SpecialSpecialpages',
189                 'PageData' => 'SpecialPageData'
190         ];
191
192         private static $list;
193         private static $aliases;
194
195         /**
196          * Reset the internal list of special pages. Useful when changing $wgSpecialPages after
197          * the internal list has already been initialized, e.g. during testing.
198          */
199         public static function resetList() {
200                 self::$list = null;
201                 self::$aliases = null;
202         }
203
204         /**
205          * Returns a list of canonical special page names.
206          * May be used to iterate over all registered special pages.
207          *
208          * @return string[]
209          */
210         public static function getNames() {
211                 return array_keys( self::getPageList() );
212         }
213
214         /**
215          * Get the special page list as an array
216          *
217          * @deprecated since 1.24, use getNames() instead.
218          * @return array
219          */
220         public static function getList() {
221                 wfDeprecated( __FUNCTION__, '1.24' );
222                 return self::getPageList();
223         }
224
225         /**
226          * Get the special page list as an array
227          *
228          * @return array
229          */
230         private static function getPageList() {
231                 global $wgSpecialPages;
232                 global $wgDisableInternalSearch, $wgEmailAuthentication;
233                 global $wgEnableEmail, $wgEnableJavaScriptTest;
234                 global $wgPageLanguageUseDB, $wgContentHandlerUseDB;
235
236                 if ( !is_array( self::$list ) ) {
237                         self::$list = self::$coreList;
238
239                         if ( !$wgDisableInternalSearch ) {
240                                 self::$list['Search'] = 'SpecialSearch';
241                         }
242
243                         if ( $wgEmailAuthentication ) {
244                                 self::$list['Confirmemail'] = 'EmailConfirmation';
245                                 self::$list['Invalidateemail'] = 'EmailInvalidation';
246                         }
247
248                         if ( $wgEnableEmail ) {
249                                 self::$list['ChangeEmail'] = 'SpecialChangeEmail';
250                         }
251
252                         if ( $wgEnableJavaScriptTest ) {
253                                 self::$list['JavaScriptTest'] = 'SpecialJavaScriptTest';
254                         }
255
256                         if ( $wgPageLanguageUseDB ) {
257                                 self::$list['PageLanguage'] = 'SpecialPageLanguage';
258                         }
259                         if ( $wgContentHandlerUseDB ) {
260                                 self::$list['ChangeContentModel'] = 'SpecialChangeContentModel';
261                         }
262
263                         // Add extension special pages
264                         self::$list = array_merge( self::$list, $wgSpecialPages );
265
266                         // This hook can be used to disable unwanted core special pages
267                         // or conditionally register special pages.
268                         Hooks::run( 'SpecialPage_initList', [ &self::$list ] );
269
270                 }
271
272                 return self::$list;
273         }
274
275         /**
276          * Initialise and return the list of special page aliases. Returns an array where
277          * the key is an alias, and the value is the canonical name of the special page.
278          * All registered special pages are guaranteed to map to themselves.
279          * @return array
280          */
281         private static function getAliasList() {
282                 if ( is_null( self::$aliases ) ) {
283                         global $wgContLang;
284                         $aliases = $wgContLang->getSpecialPageAliases();
285                         $pageList = self::getPageList();
286
287                         self::$aliases = [];
288                         $keepAlias = [];
289
290                         // Force every canonical name to be an alias for itself.
291                         foreach ( $pageList as $name => $stuff ) {
292                                 $caseFoldedAlias = $wgContLang->caseFold( $name );
293                                 self::$aliases[$caseFoldedAlias] = $name;
294                                 $keepAlias[$caseFoldedAlias] = 'canonical';
295                         }
296
297                         // Check for $aliases being an array since Language::getSpecialPageAliases can return null
298                         if ( is_array( $aliases ) ) {
299                                 foreach ( $aliases as $realName => $aliasList ) {
300                                         $aliasList = array_values( $aliasList );
301                                         foreach ( $aliasList as $i => $alias ) {
302                                                 $caseFoldedAlias = $wgContLang->caseFold( $alias );
303
304                                                 if ( isset( self::$aliases[$caseFoldedAlias] ) &&
305                                                         $realName === self::$aliases[$caseFoldedAlias]
306                                                 ) {
307                                                         // Ignore same-realName conflicts
308                                                         continue;
309                                                 }
310
311                                                 if ( !isset( $keepAlias[$caseFoldedAlias] ) ) {
312                                                         self::$aliases[$caseFoldedAlias] = $realName;
313                                                         if ( !$i ) {
314                                                                 $keepAlias[$caseFoldedAlias] = 'first';
315                                                         }
316                                                 } elseif ( !$i ) {
317                                                         wfWarn( "First alias '$alias' for $realName conflicts with " .
318                                                                 "{$keepAlias[$caseFoldedAlias]} alias for " .
319                                                                 self::$aliases[$caseFoldedAlias]
320                                                         );
321                                                 }
322                                         }
323                                 }
324                         }
325                 }
326
327                 return self::$aliases;
328         }
329
330         /**
331          * Given a special page name with a possible subpage, return an array
332          * where the first element is the special page name and the second is the
333          * subpage.
334          *
335          * @param string $alias
336          * @return array Array( String, String|null ), or array( null, null ) if the page is invalid
337          */
338         public static function resolveAlias( $alias ) {
339                 global $wgContLang;
340                 $bits = explode( '/', $alias, 2 );
341
342                 $caseFoldedAlias = $wgContLang->caseFold( $bits[0] );
343                 $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
344                 $aliases = self::getAliasList();
345                 if ( isset( $aliases[$caseFoldedAlias] ) ) {
346                         $name = $aliases[$caseFoldedAlias];
347                 } else {
348                         return [ null, null ];
349                 }
350
351                 if ( !isset( $bits[1] ) ) { // T4087
352                         $par = null;
353                 } else {
354                         $par = $bits[1];
355                 }
356
357                 return [ $name, $par ];
358         }
359
360         /**
361          * Check if a given name exist as a special page or as a special page alias
362          *
363          * @param string $name Name of a special page
364          * @return bool True if a special page exists with this name
365          */
366         public static function exists( $name ) {
367                 list( $title, /*...*/ ) = self::resolveAlias( $name );
368
369                 $specialPageList = self::getPageList();
370                 return isset( $specialPageList[$title] );
371         }
372
373         /**
374          * Find the object with a given name and return it (or NULL)
375          *
376          * @param string $name Special page name, may be localised and/or an alias
377          * @return SpecialPage|null SpecialPage object or null if the page doesn't exist
378          */
379         public static function getPage( $name ) {
380                 list( $realName, /*...*/ ) = self::resolveAlias( $name );
381
382                 $specialPageList = self::getPageList();
383
384                 if ( isset( $specialPageList[$realName] ) ) {
385                         $rec = $specialPageList[$realName];
386
387                         if ( is_callable( $rec ) ) {
388                                 // Use callback to instantiate the special page
389                                 $page = call_user_func( $rec );
390                         } elseif ( is_string( $rec ) ) {
391                                 $className = $rec;
392                                 $page = new $className;
393                         } elseif ( is_array( $rec ) ) {
394                                 $className = array_shift( $rec );
395                                 // @deprecated, officially since 1.18, unofficially since forever
396                                 wfDeprecated( "Array syntax for \$wgSpecialPages is deprecated ($className), " .
397                                         "define a subclass of SpecialPage instead.", '1.18' );
398                                 $page = ObjectFactory::getObjectFromSpec( [
399                                         'class' => $className,
400                                         'args' => $rec,
401                                         'closure_expansion' => false,
402                                 ] );
403                         } elseif ( $rec instanceof SpecialPage ) {
404                                 $page = $rec; // XXX: we should deep clone here
405                         } else {
406                                 $page = null;
407                         }
408
409                         if ( $page instanceof SpecialPage ) {
410                                 return $page;
411                         } else {
412                                 // It's not a classname, nor a callback, nor a legacy constructor array,
413                                 // nor a special page object. Give up.
414                                 wfLogWarning( "Cannot instantiate special page $realName: bad spec!" );
415                                 return null;
416                         }
417
418                 } else {
419                         return null;
420                 }
421         }
422
423         /**
424          * Return categorised listable special pages which are available
425          * for the current user, and everyone.
426          *
427          * @param User $user User object to check permissions, $wgUser will be used
428          *        if not provided
429          * @return array ( string => Specialpage )
430          */
431         public static function getUsablePages( User $user = null ) {
432                 $pages = [];
433                 if ( $user === null ) {
434                         global $wgUser;
435                         $user = $wgUser;
436                 }
437                 foreach ( self::getPageList() as $name => $rec ) {
438                         $page = self::getPage( $name );
439                         if ( $page ) { // not null
440                                 $page->setContext( RequestContext::getMain() );
441                                 if ( $page->isListed()
442                                         && ( !$page->isRestricted() || $page->userCanExecute( $user ) )
443                                 ) {
444                                         $pages[$name] = $page;
445                                 }
446                         }
447                 }
448
449                 return $pages;
450         }
451
452         /**
453          * Return categorised listable special pages for all users
454          *
455          * @return array ( string => Specialpage )
456          */
457         public static function getRegularPages() {
458                 $pages = [];
459                 foreach ( self::getPageList() as $name => $rec ) {
460                         $page = self::getPage( $name );
461                         if ( $page && $page->isListed() && !$page->isRestricted() ) {
462                                 $pages[$name] = $page;
463                         }
464                 }
465
466                 return $pages;
467         }
468
469         /**
470          * Return categorised listable special pages which are available
471          * for the current user, but not for everyone
472          *
473          * @param User|null $user User object to use or null for $wgUser
474          * @return array ( string => Specialpage )
475          */
476         public static function getRestrictedPages( User $user = null ) {
477                 $pages = [];
478                 if ( $user === null ) {
479                         global $wgUser;
480                         $user = $wgUser;
481                 }
482                 foreach ( self::getPageList() as $name => $rec ) {
483                         $page = self::getPage( $name );
484                         if ( $page
485                                 && $page->isListed()
486                                 && $page->isRestricted()
487                                 && $page->userCanExecute( $user )
488                         ) {
489                                 $pages[$name] = $page;
490                         }
491                 }
492
493                 return $pages;
494         }
495
496         /**
497          * Execute a special page path.
498          * The path may contain parameters, e.g. Special:Name/Params
499          * Extracts the special page name and call the execute method, passing the parameters
500          *
501          * Returns a title object if the page is redirected, false if there was no such special
502          * page, and true if it was successful.
503          *
504          * @param Title &$title
505          * @param IContextSource &$context
506          * @param bool $including Bool output is being captured for use in {{special:whatever}}
507          * @param LinkRenderer|null $linkRenderer (since 1.28)
508          *
509          * @return bool|Title
510          */
511         public static function executePath( Title &$title, IContextSource &$context, $including = false,
512                 LinkRenderer $linkRenderer = null
513         ) {
514                 // @todo FIXME: Redirects broken due to this call
515                 $bits = explode( '/', $title->getDBkey(), 2 );
516                 $name = $bits[0];
517                 if ( !isset( $bits[1] ) ) { // T4087
518                         $par = null;
519                 } else {
520                         $par = $bits[1];
521                 }
522
523                 $page = self::getPage( $name );
524                 if ( !$page ) {
525                         $context->getOutput()->setArticleRelated( false );
526                         $context->getOutput()->setRobotPolicy( 'noindex,nofollow' );
527
528                         global $wgSend404Code;
529                         if ( $wgSend404Code ) {
530                                 $context->getOutput()->setStatusCode( 404 );
531                         }
532
533                         $context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
534
535                         return false;
536                 }
537
538                 if ( !$including ) {
539                         // Narrow DB query expectations for this HTTP request
540                         $trxLimits = $context->getConfig()->get( 'TrxProfilerLimits' );
541                         $trxProfiler = Profiler::instance()->getTransactionProfiler();
542                         if ( $context->getRequest()->wasPosted() && !$page->doesWrites() ) {
543                                 $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
544                                 $context->getRequest()->markAsSafeRequest();
545                         }
546                 }
547
548                 // Page exists, set the context
549                 $page->setContext( $context );
550
551                 if ( !$including ) {
552                         // Redirect to canonical alias for GET commands
553                         // Not for POST, we'd lose the post data, so it's best to just distribute
554                         // the request. Such POST requests are possible for old extensions that
555                         // generate self-links without being aware that their default name has
556                         // changed.
557                         if ( $name != $page->getLocalName() && !$context->getRequest()->wasPosted() ) {
558                                 $query = $context->getRequest()->getQueryValues();
559                                 unset( $query['title'] );
560                                 $title = $page->getPageTitle( $par );
561                                 $url = $title->getFullURL( $query );
562                                 $context->getOutput()->redirect( $url );
563
564                                 return $title;
565                         } else {
566                                 $context->setTitle( $page->getPageTitle( $par ) );
567                         }
568                 } elseif ( !$page->isIncludable() ) {
569                         return false;
570                 }
571
572                 $page->including( $including );
573                 if ( $linkRenderer ) {
574                         $page->setLinkRenderer( $linkRenderer );
575                 }
576
577                 // Execute special page
578                 $page->run( $par );
579
580                 return true;
581         }
582
583         /**
584          * Just like executePath() but will override global variables and execute
585          * the page in "inclusion" mode. Returns true if the execution was
586          * successful or false if there was no such special page, or a title object
587          * if it was a redirect.
588          *
589          * Also saves the current $wgTitle, $wgOut, $wgRequest, $wgUser and $wgLang
590          * variables so that the special page will get the context it'd expect on a
591          * normal request, and then restores them to their previous values after.
592          *
593          * @param Title $title
594          * @param IContextSource $context
595          * @param LinkRenderer|null $linkRenderer (since 1.28)
596          * @return string HTML fragment
597          */
598         public static function capturePath(
599                 Title $title, IContextSource $context, LinkRenderer $linkRenderer = null
600         ) {
601                 global $wgTitle, $wgOut, $wgRequest, $wgUser, $wgLang;
602                 $main = RequestContext::getMain();
603
604                 // Save current globals and main context
605                 $glob = [
606                         'title' => $wgTitle,
607                         'output' => $wgOut,
608                         'request' => $wgRequest,
609                         'user' => $wgUser,
610                         'language' => $wgLang,
611                 ];
612                 $ctx = [
613                         'title' => $main->getTitle(),
614                         'output' => $main->getOutput(),
615                         'request' => $main->getRequest(),
616                         'user' => $main->getUser(),
617                         'language' => $main->getLanguage(),
618                 ];
619
620                 // Override
621                 $wgTitle = $title;
622                 $wgOut = $context->getOutput();
623                 $wgRequest = $context->getRequest();
624                 $wgUser = $context->getUser();
625                 $wgLang = $context->getLanguage();
626                 $main->setTitle( $title );
627                 $main->setOutput( $context->getOutput() );
628                 $main->setRequest( $context->getRequest() );
629                 $main->setUser( $context->getUser() );
630                 $main->setLanguage( $context->getLanguage() );
631
632                 // The useful part
633                 $ret = self::executePath( $title, $context, true, $linkRenderer );
634
635                 // Restore old globals and context
636                 $wgTitle = $glob['title'];
637                 $wgOut = $glob['output'];
638                 $wgRequest = $glob['request'];
639                 $wgUser = $glob['user'];
640                 $wgLang = $glob['language'];
641                 $main->setTitle( $ctx['title'] );
642                 $main->setOutput( $ctx['output'] );
643                 $main->setRequest( $ctx['request'] );
644                 $main->setUser( $ctx['user'] );
645                 $main->setLanguage( $ctx['language'] );
646
647                 return $ret;
648         }
649
650         /**
651          * Get the local name for a specified canonical name
652          *
653          * @param string $name
654          * @param string|bool $subpage
655          * @return string
656          */
657         public static function getLocalNameFor( $name, $subpage = false ) {
658                 global $wgContLang;
659                 $aliases = $wgContLang->getSpecialPageAliases();
660                 $aliasList = self::getAliasList();
661
662                 // Find the first alias that maps back to $name
663                 if ( isset( $aliases[$name] ) ) {
664                         $found = false;
665                         foreach ( $aliases[$name] as $alias ) {
666                                 $caseFoldedAlias = $wgContLang->caseFold( $alias );
667                                 $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
668                                 if ( isset( $aliasList[$caseFoldedAlias] ) &&
669                                         $aliasList[$caseFoldedAlias] === $name
670                                 ) {
671                                         $name = $alias;
672                                         $found = true;
673                                         break;
674                                 }
675                         }
676                         if ( !$found ) {
677                                 wfWarn( "Did not find a usable alias for special page '$name'. " .
678                                         "It seems all defined aliases conflict?" );
679                         }
680                 } else {
681                         // Check if someone misspelled the correct casing
682                         if ( is_array( $aliases ) ) {
683                                 foreach ( $aliases as $n => $values ) {
684                                         if ( strcasecmp( $name, $n ) === 0 ) {
685                                                 wfWarn( "Found alias defined for $n when searching for " .
686                                                         "special page aliases for $name. Case mismatch?" );
687                                                 return self::getLocalNameFor( $n, $subpage );
688                                         }
689                                 }
690                         }
691
692                         wfWarn( "Did not find alias for special page '$name'. " .
693                                 "Perhaps no aliases are defined for it?" );
694                 }
695
696                 if ( $subpage !== false && !is_null( $subpage ) ) {
697                         // Make sure it's in dbkey form
698                         $subpage = str_replace( ' ', '_', $subpage );
699                         $name = "$name/$subpage";
700                 }
701
702                 return $wgContLang->ucfirst( $name );
703         }
704
705         /**
706          * Get a title for a given alias
707          *
708          * @param string $alias
709          * @return Title|null Title or null if there is no such alias
710          */
711         public static function getTitleForAlias( $alias ) {
712                 list( $name, $subpage ) = self::resolveAlias( $alias );
713                 if ( $name != null ) {
714                         return SpecialPage::getTitleFor( $name, $subpage );
715                 } else {
716                         return null;
717                 }
718         }
719 }