Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.06% covered (warning)
72.06%
196 / 272
28.57% covered (danger)
28.57%
4 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialPageFactory
72.06% covered (warning)
72.06%
196 / 272
28.57% covered (danger)
28.57%
4 / 14
193.45
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getNames
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPageList
67.57% covered (warning)
67.57%
50 / 74
0.00% covered (danger)
0.00%
0 / 1
11.76
 getAliasList
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
11
 resolveAlias
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 exists
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getPage
85.00% covered (warning)
85.00%
17 / 20
0.00% covered (danger)
0.00%
0 / 1
6.12
 getUsablePages
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 getRegularPages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 getListedPages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 executePath
58.33% covered (warning)
58.33%
21 / 36
0.00% covered (danger)
0.00%
0 / 1
22.42
 capturePath
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
1 / 1
3
 getLocalNameFor
70.37% covered (warning)
70.37%
19 / 27
0.00% covered (danger)
0.00%
0 / 1
14.15
 getTitleForAlias
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
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
25namespace MediaWiki\SpecialPage;
26
27use Language;
28use MediaWiki\Config\ServiceOptions;
29use MediaWiki\Context\IContextSource;
30use MediaWiki\Context\RequestContext;
31use MediaWiki\HookContainer\HookContainer;
32use MediaWiki\HookContainer\HookRunner;
33use MediaWiki\Linker\LinkRenderer;
34use MediaWiki\MainConfigNames;
35use MediaWiki\Page\PageReference;
36use MediaWiki\Profiler\ProfilingContext;
37use MediaWiki\Specials\Redirects\SpecialAllMyUploads;
38use MediaWiki\Specials\Redirects\SpecialListAdmins;
39use MediaWiki\Specials\Redirects\SpecialListBots;
40use MediaWiki\Specials\Redirects\SpecialMycontributions;
41use MediaWiki\Specials\Redirects\SpecialMylog;
42use MediaWiki\Specials\Redirects\SpecialMypage;
43use MediaWiki\Specials\Redirects\SpecialMytalk;
44use MediaWiki\Specials\Redirects\SpecialMyuploads;
45use MediaWiki\Specials\SpecialActiveUsers;
46use MediaWiki\Specials\SpecialAllMessages;
47use MediaWiki\Specials\SpecialAllPages;
48use MediaWiki\Specials\SpecialAncientPages;
49use MediaWiki\Specials\SpecialApiHelp;
50use MediaWiki\Specials\SpecialApiSandbox;
51use MediaWiki\Specials\SpecialAutoblockList;
52use MediaWiki\Specials\SpecialBlankpage;
53use MediaWiki\Specials\SpecialBlock;
54use MediaWiki\Specials\SpecialBlockList;
55use MediaWiki\Specials\SpecialBookSources;
56use MediaWiki\Specials\SpecialBotPasswords;
57use MediaWiki\Specials\SpecialBrokenRedirects;
58use MediaWiki\Specials\SpecialCategories;
59use MediaWiki\Specials\SpecialChangeContentModel;
60use MediaWiki\Specials\SpecialChangeCredentials;
61use MediaWiki\Specials\SpecialChangeEmail;
62use MediaWiki\Specials\SpecialChangePassword;
63use MediaWiki\Specials\SpecialComparePages;
64use MediaWiki\Specials\SpecialConfirmEmail;
65use MediaWiki\Specials\SpecialContribute;
66use MediaWiki\Specials\SpecialContributions;
67use MediaWiki\Specials\SpecialCreateAccount;
68use MediaWiki\Specials\SpecialDeadendPages;
69use MediaWiki\Specials\SpecialDeletedContributions;
70use MediaWiki\Specials\SpecialDeletePage;
71use MediaWiki\Specials\SpecialDiff;
72use MediaWiki\Specials\SpecialDoubleRedirects;
73use MediaWiki\Specials\SpecialEditPage;
74use MediaWiki\Specials\SpecialEditRecovery;
75use MediaWiki\Specials\SpecialEditTags;
76use MediaWiki\Specials\SpecialEditWatchlist;
77use MediaWiki\Specials\SpecialEmailInvalidate;
78use MediaWiki\Specials\SpecialEmailUser;
79use MediaWiki\Specials\SpecialExpandTemplates;
80use MediaWiki\Specials\SpecialExport;
81use MediaWiki\Specials\SpecialFewestRevisions;
82use MediaWiki\Specials\SpecialFileDuplicateSearch;
83use MediaWiki\Specials\SpecialFilepath;
84use MediaWiki\Specials\SpecialGoToInterwiki;
85use MediaWiki\Specials\SpecialImport;
86use MediaWiki\Specials\SpecialJavaScriptTest;
87use MediaWiki\Specials\SpecialLinkAccounts;
88use MediaWiki\Specials\SpecialLinkSearch;
89use MediaWiki\Specials\SpecialListDuplicatedFiles;
90use MediaWiki\Specials\SpecialListFiles;
91use MediaWiki\Specials\SpecialListGrants;
92use MediaWiki\Specials\SpecialListGroupRights;
93use MediaWiki\Specials\SpecialListRedirects;
94use MediaWiki\Specials\SpecialListUsers;
95use MediaWiki\Specials\SpecialLockdb;
96use MediaWiki\Specials\SpecialLog;
97use MediaWiki\Specials\SpecialLonelyPages;
98use MediaWiki\Specials\SpecialLongPages;
99use MediaWiki\Specials\SpecialMediaStatistics;
100use MediaWiki\Specials\SpecialMergeHistory;
101use MediaWiki\Specials\SpecialMIMESearch;
102use MediaWiki\Specials\SpecialMostCategories;
103use MediaWiki\Specials\SpecialMostImages;
104use MediaWiki\Specials\SpecialMostInterwikis;
105use MediaWiki\Specials\SpecialMostLinked;
106use MediaWiki\Specials\SpecialMostLinkedCategories;
107use MediaWiki\Specials\SpecialMostLinkedTemplates;
108use MediaWiki\Specials\SpecialMostRevisions;
109use MediaWiki\Specials\SpecialMovePage;
110use MediaWiki\Specials\SpecialMute;
111use MediaWiki\Specials\SpecialMyLanguage;
112use MediaWiki\Specials\SpecialNewFiles;
113use MediaWiki\Specials\SpecialNewPages;
114use MediaWiki\Specials\SpecialNewSection;
115use MediaWiki\Specials\SpecialPageData;
116use MediaWiki\Specials\SpecialPageHistory;
117use MediaWiki\Specials\SpecialPageInfo;
118use MediaWiki\Specials\SpecialPageLanguage;
119use MediaWiki\Specials\SpecialPagesWithProp;
120use MediaWiki\Specials\SpecialPasswordPolicies;
121use MediaWiki\Specials\SpecialPasswordReset;
122use MediaWiki\Specials\SpecialPermanentLink;
123use MediaWiki\Specials\SpecialPreferences;
124use MediaWiki\Specials\SpecialPrefixIndex;
125use MediaWiki\Specials\SpecialProtectedPages;
126use MediaWiki\Specials\SpecialProtectedTitles;
127use MediaWiki\Specials\SpecialProtectPage;
128use MediaWiki\Specials\SpecialPurge;
129use MediaWiki\Specials\SpecialRandomInCategory;
130use MediaWiki\Specials\SpecialRandomPage;
131use MediaWiki\Specials\SpecialRandomRedirect;
132use MediaWiki\Specials\SpecialRandomRootPage;
133use MediaWiki\Specials\SpecialRecentChanges;
134use MediaWiki\Specials\SpecialRecentChangesLinked;
135use MediaWiki\Specials\SpecialRedirect;
136use MediaWiki\Specials\SpecialRemoveCredentials;
137use MediaWiki\Specials\SpecialRenameUser;
138use MediaWiki\Specials\SpecialResetTokens;
139use MediaWiki\Specials\SpecialRevisionDelete;
140use MediaWiki\Specials\SpecialRunJobs;
141use MediaWiki\Specials\SpecialSearch;
142use MediaWiki\Specials\SpecialShortPages;
143use MediaWiki\Specials\SpecialSpecialPages;
144use MediaWiki\Specials\SpecialStatistics;
145use MediaWiki\Specials\SpecialTags;
146use MediaWiki\Specials\SpecialTrackingCategories;
147use MediaWiki\Specials\SpecialUnblock;
148use MediaWiki\Specials\SpecialUncategorizedCategories;
149use MediaWiki\Specials\SpecialUncategorizedImages;
150use MediaWiki\Specials\SpecialUncategorizedPages;
151use MediaWiki\Specials\SpecialUncategorizedTemplates;
152use MediaWiki\Specials\SpecialUndelete;
153use MediaWiki\Specials\SpecialUnlinkAccounts;
154use MediaWiki\Specials\SpecialUnlockdb;
155use MediaWiki\Specials\SpecialUnusedCategories;
156use MediaWiki\Specials\SpecialUnusedImages;
157use MediaWiki\Specials\SpecialUnusedTemplates;
158use MediaWiki\Specials\SpecialUnwatchedPages;
159use MediaWiki\Specials\SpecialUpload;
160use MediaWiki\Specials\SpecialUploadStash;
161use MediaWiki\Specials\SpecialUserLogin;
162use MediaWiki\Specials\SpecialUserLogout;
163use MediaWiki\Specials\SpecialUserRights;
164use MediaWiki\Specials\SpecialVersion;
165use MediaWiki\Specials\SpecialWantedCategories;
166use MediaWiki\Specials\SpecialWantedFiles;
167use MediaWiki\Specials\SpecialWantedPages;
168use MediaWiki\Specials\SpecialWantedTemplates;
169use MediaWiki\Specials\SpecialWatchlist;
170use MediaWiki\Specials\SpecialWhatLinksHere;
171use MediaWiki\Specials\SpecialWithoutInterwiki;
172use MediaWiki\Title\Title;
173use MediaWiki\Title\TitleFactory;
174use MediaWiki\User\User;
175use Profiler;
176use Wikimedia\DebugInfo\DebugInfoTrait;
177use Wikimedia\ObjectFactory\ObjectFactory;
178
179/**
180 * Factory for handling the special page list and generating SpecialPage objects.
181 *
182 * To add a special page in an extension, add to $wgSpecialPages either
183 * an object instance or an array containing the name and constructor
184 * parameters. The latter is preferred for performance reasons.
185 *
186 * The object instantiated must be either an instance of SpecialPage or a
187 * sub-class thereof. It must have an execute() method, which sends the HTML
188 * for the special page to $wgOut. The parent class has an execute() method
189 * which distributes the call to the historical global functions. Additionally,
190 * execute() also checks if the user has the necessary access privileges
191 * and bails out if not.
192 *
193 * To add a core special page, use the similar static list in
194 * SpecialPageFactory::$list. To remove a core static special page at runtime, use
195 * a SpecialPage_initList hook.
196 *
197 * @ingroup SpecialPage
198 * @since 1.17
199 */
200class SpecialPageFactory {
201    use DebugInfoTrait;
202
203    /**
204     * List of special page names to the subclass of SpecialPage which handles them.
205     */
206    private const CORE_LIST = [
207        // Maintenance Reports
208        'BrokenRedirects' => [
209            'class' => SpecialBrokenRedirects::class,
210            'services' => [
211                'ContentHandlerFactory',
212                'ConnectionProvider',
213                'LinkBatchFactory',
214            ]
215        ],
216        'Deadendpages' => [
217            'class' => SpecialDeadendPages::class,
218            'services' => [
219                'NamespaceInfo',
220                'ConnectionProvider',
221                'LinkBatchFactory',
222                'LanguageConverterFactory',
223            ]
224        ],
225        'DoubleRedirects' => [
226            'class' => SpecialDoubleRedirects::class,
227            'services' => [
228                'ContentHandlerFactory',
229                'LinkBatchFactory',
230                'ConnectionProvider',
231            ]
232        ],
233        'Longpages' => [
234            'class' => SpecialLongPages::class,
235            'services' => [
236                // Same as for Shortpages
237                'NamespaceInfo',
238                'ConnectionProvider',
239                'LinkBatchFactory',
240            ]
241        ],
242        'Ancientpages' => [
243            'class' => SpecialAncientPages::class,
244            'services' => [
245                'NamespaceInfo',
246                'ConnectionProvider',
247                'LinkBatchFactory',
248                'LanguageConverterFactory',
249            ]
250        ],
251        'Lonelypages' => [
252            'class' => SpecialLonelyPages::class,
253            'services' => [
254                'NamespaceInfo',
255                'ConnectionProvider',
256                'LinkBatchFactory',
257                'LanguageConverterFactory',
258                'LinksMigration',
259            ]
260        ],
261        'Fewestrevisions' => [
262            'class' => SpecialFewestRevisions::class,
263            'services' => [
264                // Same as for Mostrevisions
265                'NamespaceInfo',
266                'ConnectionProvider',
267                'LinkBatchFactory',
268                'LanguageConverterFactory',
269            ]
270        ],
271        'Withoutinterwiki' => [
272            'class' => SpecialWithoutInterwiki::class,
273            'services' => [
274                'NamespaceInfo',
275                'ConnectionProvider',
276                'LinkBatchFactory',
277                'LanguageConverterFactory',
278            ]
279        ],
280        'Protectedpages' => [
281            'class' => SpecialProtectedPages::class,
282            'services' => [
283                'LinkBatchFactory',
284                'ConnectionProvider',
285                'CommentStore',
286                'UserCache',
287                'RowCommentFormatter',
288                'RestrictionStore',
289            ]
290        ],
291        'Protectedtitles' => [
292            'class' => SpecialProtectedTitles::class,
293            'services' => [
294                'LinkBatchFactory',
295                'ConnectionProvider',
296            ]
297        ],
298        'Shortpages' => [
299            'class' => SpecialShortPages::class,
300            'services' => [
301                // Same as for Longpages
302                'NamespaceInfo',
303                'ConnectionProvider',
304                'LinkBatchFactory',
305            ]
306        ],
307        'Uncategorizedcategories' => [
308            'class' => SpecialUncategorizedCategories::class,
309            'services' => [
310                // Same as for SpecialUncategorizedPages and SpecialUncategorizedTemplates
311                'NamespaceInfo',
312                'ConnectionProvider',
313                'LinkBatchFactory',
314                'LanguageConverterFactory',
315            ]
316        ],
317        'Uncategorizedimages' => [
318            'class' => SpecialUncategorizedImages::class,
319            'services' => [
320                'ConnectionProvider',
321            ]
322        ],
323        'Uncategorizedpages' => [
324            'class' => SpecialUncategorizedPages::class,
325            'services' => [
326                // Same as for SpecialUncategorizedCategories and SpecialUncategorizedTemplates
327                'NamespaceInfo',
328                'ConnectionProvider',
329                'LinkBatchFactory',
330                'LanguageConverterFactory',
331            ]
332        ],
333        'Uncategorizedtemplates' => [
334            'class' => SpecialUncategorizedTemplates::class,
335            'services' => [
336                // Same as for SpecialUncategorizedCategories and SpecialUncategorizedPages
337                'NamespaceInfo',
338                'ConnectionProvider',
339                'LinkBatchFactory',
340                'LanguageConverterFactory',
341            ]
342        ],
343        'Unusedcategories' => [
344            'class' => SpecialUnusedCategories::class,
345            'services' => [
346                'ConnectionProvider',
347                'LinkBatchFactory',
348            ]
349        ],
350        'Unusedimages' => [
351            'class' => SpecialUnusedImages::class,
352            'services' => [
353                'ConnectionProvider',
354            ]
355        ],
356        'Unusedtemplates' => [
357            'class' => SpecialUnusedTemplates::class,
358            'services' => [
359                'ConnectionProvider',
360                'LinksMigration',
361            ]
362        ],
363        'Unwatchedpages' => [
364            'class' => SpecialUnwatchedPages::class,
365            'services' => [
366                'LinkBatchFactory',
367                'ConnectionProvider',
368                'LanguageConverterFactory',
369            ]
370        ],
371        'Wantedcategories' => [
372            'class' => SpecialWantedCategories::class,
373            'services' => [
374                'ConnectionProvider',
375                'LinkBatchFactory',
376                'LanguageConverterFactory',
377            ]
378        ],
379        'Wantedfiles' => [
380            'class' => SpecialWantedFiles::class,
381            'services' => [
382                'RepoGroup',
383                'ConnectionProvider',
384                'LinkBatchFactory',
385            ]
386        ],
387        'Wantedpages' => [
388            'class' => SpecialWantedPages::class,
389            'services' => [
390                'ConnectionProvider',
391                'LinkBatchFactory',
392                'LinksMigration',
393            ]
394        ],
395        'Wantedtemplates' => [
396            'class' => SpecialWantedTemplates::class,
397            'services' => [
398                'ConnectionProvider',
399                'LinkBatchFactory',
400                'LinksMigration',
401            ]
402        ],
403
404        // List of pages
405        'Allpages' => [
406            'class' => SpecialAllPages::class,
407            'services' => [
408                'ConnectionProvider',
409                'SearchEngineFactory',
410                'PageStore',
411            ]
412        ],
413        'Prefixindex' => [
414            'class' => SpecialPrefixIndex::class,
415            'services' => [
416                'ConnectionProvider',
417                'LinkCache',
418            ]
419        ],
420        'Categories' => [
421            'class' => SpecialCategories::class,
422            'services' => [
423                'LinkBatchFactory',
424                'ConnectionProvider',
425            ]
426        ],
427        'Listredirects' => [
428            'class' => SpecialListRedirects::class,
429            'services' => [
430                'LinkBatchFactory',
431                'ConnectionProvider',
432                'WikiPageFactory',
433                'RedirectLookup'
434            ]
435        ],
436        'PagesWithProp' => [
437            'class' => SpecialPagesWithProp::class,
438            'services' => [
439                'ConnectionProvider',
440            ]
441        ],
442        'TrackingCategories' => [
443            'class' => SpecialTrackingCategories::class,
444            'services' => [
445                'LinkBatchFactory',
446                'TrackingCategories',
447            ]
448        ],
449
450        // Authentication
451        'Userlogin' => [
452            'class' => SpecialUserLogin::class,
453            'services' => [
454                'AuthManager',
455            ]
456        ],
457        'Userlogout' => [
458            'class' => SpecialUserLogout::class,
459        ],
460        'CreateAccount' => [
461            'class' => SpecialCreateAccount::class,
462            'services' => [
463                'AuthManager',
464                'FormatterFactory',
465            ]
466        ],
467        'LinkAccounts' => [
468            'class' => SpecialLinkAccounts::class,
469            'services' => [
470                'AuthManager',
471            ]
472        ],
473        'UnlinkAccounts' => [
474            'class' => SpecialUnlinkAccounts::class,
475            'services' => [
476                'AuthManager',
477            ]
478        ],
479        'ChangeCredentials' => [
480            'class' => SpecialChangeCredentials::class,
481            'services' => [
482                'AuthManager',
483            ]
484        ],
485        'RemoveCredentials' => [
486            'class' => SpecialRemoveCredentials::class,
487            'services' => [
488                'AuthManager',
489            ]
490        ],
491
492        // Users and rights
493        'Activeusers' => [
494            'class' => SpecialActiveUsers::class,
495            'services' => [
496                'LinkBatchFactory',
497                'ConnectionProvider',
498                'UserGroupManager',
499                'UserIdentityLookup',
500                'HideUserUtils',
501            ]
502        ],
503        'Block' => [
504            'class' => SpecialBlock::class,
505            'services' => [
506                'BlockUtils',
507                'BlockPermissionCheckerFactory',
508                'BlockUserFactory',
509                'DatabaseBlockStore',
510                'UserNameUtils',
511                'UserNamePrefixSearch',
512                'BlockActionInfo',
513                'TitleFormatter',
514                'NamespaceInfo'
515            ]
516        ],
517        'Unblock' => [
518            'class' => SpecialUnblock::class,
519            'services' => [
520                'UnblockUserFactory',
521                'BlockUtils',
522                'DatabaseBlockStore',
523                'UserNameUtils',
524                'UserNamePrefixSearch',
525                'WatchlistManager',
526            ]
527        ],
528        'BlockList' => [
529            'class' => SpecialBlockList::class,
530            'services' => [
531                'LinkBatchFactory',
532                'DatabaseBlockStore',
533                'BlockRestrictionStore',
534                'ConnectionProvider',
535                'CommentStore',
536                'BlockUtils',
537                'HideUserUtils',
538                'BlockActionInfo',
539                'RowCommentFormatter',
540            ],
541        ],
542        'AutoblockList' => [
543            'class' => SpecialAutoblockList::class,
544            'services' => [
545                'LinkBatchFactory',
546                'BlockRestrictionStore',
547                'ConnectionProvider',
548                'CommentStore',
549                'BlockUtils',
550                'HideUserUtils',
551                'BlockActionInfo',
552                'RowCommentFormatter',
553            ],
554        ],
555        'ChangePassword' => [
556            'class' => SpecialChangePassword::class,
557        ],
558        'BotPasswords' => [
559            'class' => SpecialBotPasswords::class,
560            'services' => [
561                'PasswordFactory',
562                'AuthManager',
563                'CentralIdLookup',
564                'GrantsInfo',
565                'GrantsLocalization',
566            ]
567        ],
568        'PasswordReset' => [
569            'class' => SpecialPasswordReset::class,
570            'services' => [
571                'PasswordReset'
572            ]
573        ],
574        'DeletedContributions' => [
575            'class' => SpecialDeletedContributions::class,
576            'services' => [
577                'PermissionManager',
578                'ConnectionProvider',
579                'RevisionFactory',
580                'NamespaceInfo',
581                'UserFactory',
582                'UserNameUtils',
583                'UserNamePrefixSearch',
584                'CommentFormatter',
585                'LinkBatchFactory',
586                'DatabaseBlockStore',
587            ]
588        ],
589        'Preferences' => [
590            'class' => SpecialPreferences::class,
591            'services' => [
592                'PreferencesFactory',
593                'UserOptionsManager',
594            ]
595        ],
596        'ResetTokens' => [
597            'class' => SpecialResetTokens::class,
598        ],
599        'Contributions' => [
600            'class' => SpecialContributions::class,
601            'services' => [
602                'LinkBatchFactory',
603                'PermissionManager',
604                'ConnectionProvider',
605                'RevisionStore',
606                'NamespaceInfo',
607                'UserNameUtils',
608                'UserNamePrefixSearch',
609                'UserOptionsLookup',
610                'CommentFormatter',
611                'UserFactory',
612                'UserIdentityLookup',
613                'DatabaseBlockStore',
614            ]
615        ],
616        'Listgrouprights' => [
617            'class' => SpecialListGroupRights::class,
618            'services' => [
619                'NamespaceInfo',
620                'UserGroupManager',
621                'LanguageConverterFactory',
622                'GroupPermissionsLookup',
623            ]
624        ],
625        'Listgrants' => [
626            'class' => SpecialListGrants::class,
627            'services' => [
628                'GrantsLocalization',
629            ]
630        ],
631        'Listusers' => [
632            'class' => SpecialListUsers::class,
633            'services' => [
634                'LinkBatchFactory',
635                'ConnectionProvider',
636                'UserGroupManager',
637                'UserIdentityLookup',
638                'HideUserUtils',
639            ]
640        ],
641        'Listadmins' => [
642            'class' => SpecialListAdmins::class,
643        ],
644        'Listbots' => [
645            'class' => SpecialListBots::class,
646        ],
647        'Userrights' => [
648            'class' => SpecialUserRights::class,
649            'services' => [
650                'UserGroupManagerFactory',
651                'UserNameUtils',
652                'UserNamePrefixSearch',
653                'UserFactory',
654                'ActorStoreFactory',
655                'WatchlistManager',
656            ]
657        ],
658        'EditWatchlist' => [
659            'class' => SpecialEditWatchlist::class,
660            'services' => [
661                'WatchedItemStore',
662                'TitleParser',
663                'GenderCache',
664                'LinkBatchFactory',
665                'NamespaceInfo',
666                'WikiPageFactory',
667                'WatchlistManager',
668            ]
669        ],
670        'PasswordPolicies' => [
671            'class' => SpecialPasswordPolicies::class,
672            'services' => [
673                'UserGroupManager',
674            ]
675        ],
676
677        // Recent changes and logs
678        'Newimages' => [
679            'class' => SpecialNewFiles::class,
680            'services' => [
681                'MimeAnalyzer',
682                'GroupPermissionsLookup',
683                'ConnectionProvider',
684                'LinkBatchFactory',
685            ]
686        ],
687        'Log' => [
688            'class' => SpecialLog::class,
689            'services' => [
690                'LinkBatchFactory',
691                'ConnectionProvider',
692                'ActorNormalization',
693                'UserIdentityLookup',
694                'UserNameUtils',
695            ]
696        ],
697        'Watchlist' => [
698            'class' => SpecialWatchlist::class,
699            'services' => [
700                'WatchedItemStore',
701                'WatchlistManager',
702                'UserOptionsLookup',
703                'ChangeTagsStore',
704                'UserIdentityUtils',
705                'TempUserConfig',
706            ]
707        ],
708        'Newpages' => [
709            'class' => SpecialNewPages::class,
710            'services' => [
711                'LinkBatchFactory',
712                'ContentHandlerFactory',
713                'GroupPermissionsLookup',
714                'RevisionLookup',
715                'NamespaceInfo',
716                'UserOptionsLookup',
717                'RowCommentFormatter',
718                'ChangeTagsStore',
719            ]
720        ],
721        'Recentchanges' => [
722            'class' => SpecialRecentChanges::class,
723            'services' => [
724                'WatchedItemStore',
725                'MessageCache',
726                'UserOptionsLookup',
727                'ChangeTagsStore',
728                'UserIdentityUtils',
729                'TempUserConfig',
730            ]
731        ],
732        'Recentchangeslinked' => [
733            'class' => SpecialRecentChangesLinked::class,
734            'services' => [
735                'WatchedItemStore',
736                'MessageCache',
737                'UserOptionsLookup',
738                'SearchEngineFactory',
739                'ChangeTagsStore',
740                'UserIdentityUtils',
741                'TempUserConfig',
742            ]
743        ],
744        'Tags' => [
745            'class' => SpecialTags::class,
746            'services' => [
747                'ChangeTagsStore',
748            ]
749        ],
750
751        // Media reports and uploads
752        'Listfiles' => [
753            'class' => SpecialListFiles::class,
754            'services' => [
755                'RepoGroup',
756                'ConnectionProvider',
757                'CommentStore',
758                'UserNameUtils',
759                'UserNamePrefixSearch',
760                'UserCache',
761                'CommentFormatter',
762            ]
763        ],
764        'Filepath' => [
765            'class' => SpecialFilepath::class,
766            'services' => [
767                'SearchEngineFactory',
768            ]
769        ],
770        'MediaStatistics' => [
771            'class' => SpecialMediaStatistics::class,
772            'services' => [
773                'MimeAnalyzer',
774                'ConnectionProvider',
775                'LinkBatchFactory',
776            ]
777        ],
778        'MIMEsearch' => [
779            'class' => SpecialMIMESearch::class,
780            'services' => [
781                'ConnectionProvider',
782                'LinkBatchFactory',
783                'LanguageConverterFactory',
784            ]
785        ],
786        'FileDuplicateSearch' => [
787            'class' => SpecialFileDuplicateSearch::class,
788            'services' => [
789                'LinkBatchFactory',
790                'RepoGroup',
791                'SearchEngineFactory',
792                'LanguageConverterFactory',
793            ]
794        ],
795        'Upload' => [
796            'class' => SpecialUpload::class,
797            'services' => [
798                'RepoGroup',
799                'UserOptionsLookup',
800                'NamespaceInfo',
801            ]
802        ],
803        'UploadStash' => [
804            'class' => SpecialUploadStash::class,
805            'services' => [
806                'RepoGroup',
807                'HttpRequestFactory',
808                'UrlUtils',
809                'ConnectionProvider',
810            ]
811        ],
812        'ListDuplicatedFiles' => [
813            'class' => SpecialListDuplicatedFiles::class,
814            'services' => [
815                'ConnectionProvider',
816                'LinkBatchFactory',
817            ]
818        ],
819
820        // Data and tools
821        'ApiSandbox' => [
822            'class' => SpecialApiSandbox::class,
823        ],
824        'Statistics' => [
825            'class' => SpecialStatistics::class,
826            'services' => [
827                'UserGroupManager',
828            ]
829        ],
830        'Allmessages' => [
831            'class' => SpecialAllMessages::class,
832            'services' => [
833                'LanguageFactory',
834                'LanguageNameUtils',
835                'LocalisationCache',
836                'ConnectionProvider',
837            ]
838        ],
839        'Version' => [
840            'class' => SpecialVersion::class,
841            'services' => [
842                'ParserFactory',
843                'UrlUtils',
844                'ConnectionProvider',
845            ]
846        ],
847        'Lockdb' => [
848            'class' => SpecialLockdb::class,
849        ],
850        'Unlockdb' => [
851            'class' => SpecialUnlockdb::class,
852        ],
853
854        // Redirecting special pages
855        'LinkSearch' => [
856            'class' => SpecialLinkSearch::class,
857            'services' => [
858                'ConnectionProvider',
859                'LinkBatchFactory',
860                'UrlUtils',
861            ]
862        ],
863        'Randompage' => [
864            'class' => SpecialRandomPage::class,
865            'services' => [
866                'ConnectionProvider',
867                'NamespaceInfo',
868            ]
869        ],
870        'RandomInCategory' => [
871            'class' => SpecialRandomInCategory::class,
872            'services' => [
873                'ConnectionProvider',
874            ]
875        ],
876        'Randomredirect' => [
877            'class' => SpecialRandomRedirect::class,
878            'services' => [
879                'ConnectionProvider',
880                'NamespaceInfo',
881            ]
882        ],
883        'Randomrootpage' => [
884            'class' => SpecialRandomRootPage::class,
885            'services' => [
886                'ConnectionProvider',
887                'NamespaceInfo',
888            ]
889        ],
890        'GoToInterwiki' => [
891            'class' => SpecialGoToInterwiki::class,
892        ],
893
894        // High use pages
895        'Mostlinkedcategories' => [
896            'class' => SpecialMostLinkedCategories::class,
897            'services' => [
898                'ConnectionProvider',
899                'LinkBatchFactory',
900                'LanguageConverterFactory',
901            ]
902        ],
903        'Mostimages' => [
904            'class' => SpecialMostImages::class,
905            'services' => [
906                'ConnectionProvider',
907                'LanguageConverterFactory',
908            ]
909        ],
910        'Mostinterwikis' => [
911            'class' => SpecialMostInterwikis::class,
912            'services' => [
913                'NamespaceInfo',
914                'ConnectionProvider',
915                'LinkBatchFactory',
916            ]
917        ],
918        'Mostlinked' => [
919            'class' => SpecialMostLinked::class,
920            'services' => [
921                'ConnectionProvider',
922                'LinkBatchFactory',
923                'LinksMigration',
924            ]
925        ],
926        'Mostlinkedtemplates' => [
927            'class' => SpecialMostLinkedTemplates::class,
928            'services' => [
929                'ConnectionProvider',
930                'LinkBatchFactory',
931                'LinksMigration',
932            ]
933        ],
934        'Mostcategories' => [
935            'class' => SpecialMostCategories::class,
936            'services' => [
937                'NamespaceInfo',
938                'ConnectionProvider',
939                'LinkBatchFactory',
940            ]
941        ],
942        'Mostrevisions' => [
943            'class' => SpecialMostRevisions::class,
944            'services' => [
945                // Same as for Fewestrevisions
946                'NamespaceInfo',
947                'ConnectionProvider',
948                'LinkBatchFactory',
949                'LanguageConverterFactory',
950            ]
951        ],
952
953        // Page tools
954        'ComparePages' => [
955            'class' => SpecialComparePages::class,
956            'services' => [
957                'RevisionLookup',
958                'ContentHandlerFactory',
959            ]
960        ],
961        'Export' => [
962            'class' => SpecialExport::class,
963            'services' => [
964                'ConnectionProvider',
965                'WikiExporterFactory',
966                'TitleFormatter',
967                'LinksMigration',
968            ]
969        ],
970        'Import' => [
971            'class' => SpecialImport::class,
972            'services' => [
973                'PermissionManager',
974                'WikiImporterFactory',
975            ]
976        ],
977        'Undelete' => [
978            'class' => SpecialUndelete::class,
979            'services' => [
980                'PermissionManager',
981                'RevisionStore',
982                'RevisionRenderer',
983                'ContentHandlerFactory',
984                'ChangeTagDefStore',
985                'LinkBatchFactory',
986                'RepoGroup',
987                'ConnectionProvider',
988                'UserOptionsLookup',
989                'WikiPageFactory',
990                'SearchEngineFactory',
991                'UndeletePageFactory',
992                'ArchivedRevisionLookup',
993                'CommentFormatter',
994                'WatchlistManager',
995            ],
996        ],
997        'Whatlinkshere' => [
998            'class' => SpecialWhatLinksHere::class,
999            'services' => [
1000                'ConnectionProvider',
1001                'LinkBatchFactory',
1002                'ContentHandlerFactory',
1003                'SearchEngineFactory',
1004                'NamespaceInfo',
1005                'TitleFactory',
1006                'LinksMigration',
1007            ]
1008        ],
1009        'MergeHistory' => [
1010            'class' => SpecialMergeHistory::class,
1011            'services' => [
1012                'MergeHistoryFactory',
1013                'LinkBatchFactory',
1014                'ConnectionProvider',
1015                'RevisionStore',
1016                'CommentFormatter',
1017            ]
1018        ],
1019        'ExpandTemplates' => [
1020            'class' => SpecialExpandTemplates::class,
1021            'services' => [
1022                'ParserFactory',
1023                'UserOptionsLookup',
1024                'Tidy',
1025            ],
1026        ],
1027        'ChangeContentModel' => [
1028            'class' => SpecialChangeContentModel::class,
1029            'services' => [
1030                'ContentHandlerFactory',
1031                'ContentModelChangeFactory',
1032                'SpamChecker',
1033                'RevisionLookup',
1034                'WikiPageFactory',
1035                'SearchEngineFactory',
1036                'CollationFactory',
1037            ],
1038        ],
1039
1040        // Other
1041        'Booksources' => [
1042            'class' => SpecialBookSources::class,
1043            'services' => [
1044                'RevisionLookup',
1045                'TitleFactory',
1046            ]
1047        ],
1048
1049        // Unlisted / redirects
1050        'ApiHelp' => [
1051            'class' => SpecialApiHelp::class,
1052            'services' => [
1053                'UrlUtils',
1054            ]
1055        ],
1056        'Blankpage' => [
1057            'class' => SpecialBlankpage::class,
1058        ],
1059        'DeletePage' => [
1060            'class' => SpecialDeletePage::class,
1061            'services' => [
1062                'SearchEngineFactory',
1063            ]
1064        ],
1065        'Diff' => [
1066            'class' => SpecialDiff::class,
1067        ],
1068        'EditPage' => [
1069            'class' => SpecialEditPage::class,
1070            'services' => [
1071                'SearchEngineFactory',
1072            ]
1073        ],
1074        'EditTags' => [
1075            'class' => SpecialEditTags::class,
1076            'services' => [
1077                'PermissionManager',
1078                'ChangeTagsStore',
1079            ],
1080        ],
1081        'Emailuser' => [
1082            'class' => SpecialEmailUser::class,
1083            'services' => [
1084                'UserNameUtils',
1085                'UserNamePrefixSearch',
1086                'UserOptionsLookup',
1087                'EmailUserFactory',
1088                'UserFactory',
1089            ]
1090        ],
1091        'Movepage' => [
1092            'class' => SpecialMovePage::class,
1093            'services' => [
1094                'MovePageFactory',
1095                'PermissionManager',
1096                'UserOptionsLookup',
1097                'ConnectionProvider',
1098                'ContentHandlerFactory',
1099                'NamespaceInfo',
1100                'LinkBatchFactory',
1101                'RepoGroup',
1102                'WikiPageFactory',
1103                'SearchEngineFactory',
1104                'WatchlistManager',
1105                'RestrictionStore',
1106                'TitleFactory',
1107            ]
1108        ],
1109        'Mycontributions' => [
1110            'class' => SpecialMycontributions::class,
1111        ],
1112        'MyLanguage' => [
1113            'class' => SpecialMyLanguage::class,
1114            'services' => [
1115                'LanguageNameUtils',
1116                'RedirectLookup'
1117            ]
1118        ],
1119        'Mylog' => [
1120            'class' => SpecialMylog::class,
1121        ],
1122        'Mypage' => [
1123            'class' => SpecialMypage::class,
1124        ],
1125        'Mytalk' => [
1126            'class' => SpecialMytalk::class,
1127        ],
1128        'PageHistory' => [
1129            'class' => SpecialPageHistory::class,
1130            'services' => [
1131                'SearchEngineFactory',
1132            ]
1133        ],
1134        'PageInfo' => [
1135            'class' => SpecialPageInfo::class,
1136            'services' => [
1137                'SearchEngineFactory',
1138            ]
1139        ],
1140        'ProtectPage' => [
1141            'class' => SpecialProtectPage::class,
1142            'services' => [
1143                'SearchEngineFactory',
1144            ]
1145        ],
1146        'Purge' => [
1147            'class' => SpecialPurge::class,
1148            'services' => [
1149                'SearchEngineFactory',
1150            ]
1151        ],
1152        'Myuploads' => [
1153            'class' => SpecialMyuploads::class,
1154        ],
1155        'AllMyUploads' => [
1156            'class' => SpecialAllMyUploads::class,
1157        ],
1158        'NewSection' => [
1159            'class' => SpecialNewSection::class,
1160            'services' => [
1161                'SearchEngineFactory',
1162            ]
1163        ],
1164        'PermanentLink' => [
1165            'class' => SpecialPermanentLink::class,
1166        ],
1167        'Redirect' => [
1168            'class' => SpecialRedirect::class,
1169            'services' => [
1170                'RepoGroup',
1171                'UserFactory',
1172            ]
1173        ],
1174        'Renameuser' => [
1175            'class' => SpecialRenameUser::class,
1176            'services' => [
1177                'ConnectionProvider',
1178                'ContentLanguage',
1179                'MovePageFactory',
1180                'PermissionManager',
1181                'TitleFactory',
1182                'UserFactory',
1183                'UserNamePrefixSearch',
1184                'UserNameUtils',
1185            ]
1186        ],
1187        'Revisiondelete' => [
1188            'class' => SpecialRevisionDelete::class,
1189            'services' => [
1190                'PermissionManager',
1191                'RepoGroup',
1192            ],
1193        ],
1194        'RunJobs' => [
1195            'class' => SpecialRunJobs::class,
1196            'services' => [
1197                'JobRunner',
1198                'ReadOnlyMode',
1199            ]
1200        ],
1201        'Specialpages' => [
1202            'class' => SpecialSpecialPages::class,
1203        ],
1204        'PageData' => [
1205            'class' => SpecialPageData::class,
1206        ],
1207        'Contribute' => [
1208            'class' => SpecialContribute::class,
1209        ],
1210    ];
1211
1212    /** @var array Special page name => class name */
1213    private $list;
1214
1215    /** @var array */
1216    private $aliases;
1217
1218    /** @var ServiceOptions */
1219    private $options;
1220
1221    /** @var Language */
1222    private $contLang;
1223
1224    /**
1225     * @var ObjectFactory
1226     * @noVarDump
1227     */
1228    private $objectFactory;
1229
1230    /**
1231     * @var HookContainer
1232     * @noVarDump
1233     */
1234    private $hookContainer;
1235
1236    /**
1237     * @var HookRunner
1238     * @noVarDump
1239     */
1240    private $hookRunner;
1241
1242    /**
1243     * @internal For use by ServiceWiring
1244     */
1245    public const CONSTRUCTOR_OPTIONS = [
1246        MainConfigNames::DisableInternalSearch,
1247        MainConfigNames::EmailAuthentication,
1248        MainConfigNames::EnableEmail,
1249        MainConfigNames::EnableJavaScriptTest,
1250        MainConfigNames::EnableSpecialMute,
1251        MainConfigNames::EnableEditRecovery,
1252        MainConfigNames::PageLanguageUseDB,
1253        MainConfigNames::SpecialPages,
1254    ];
1255
1256    /**
1257     * @var TitleFactory
1258     */
1259    private $titleFactory;
1260
1261    /**
1262     * @param ServiceOptions $options
1263     * @param Language $contLang
1264     * @param ObjectFactory $objectFactory
1265     * @param TitleFactory $titleFactory
1266     * @param HookContainer $hookContainer
1267     */
1268    public function __construct(
1269        ServiceOptions $options,
1270        Language $contLang,
1271        ObjectFactory $objectFactory,
1272        TitleFactory $titleFactory,
1273        HookContainer $hookContainer
1274    ) {
1275        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
1276        $this->options = $options;
1277        $this->contLang = $contLang;
1278        $this->objectFactory = $objectFactory;
1279        $this->titleFactory = $titleFactory;
1280        $this->hookContainer = $hookContainer;
1281        $this->hookRunner = new HookRunner( $hookContainer );
1282    }
1283
1284    /**
1285     * Returns a list of canonical special page names.
1286     * May be used to iterate over all registered special pages.
1287     *
1288     * @return string[]
1289     */
1290    public function getNames(): array {
1291        return array_keys( $this->getPageList() );
1292    }
1293
1294    /**
1295     * Get the special page list as an array
1296     *
1297     * @return array
1298     */
1299    private function getPageList(): array {
1300        if ( !is_array( $this->list ) ) {
1301            $this->list = self::CORE_LIST;
1302
1303            if ( !$this->options->get( MainConfigNames::DisableInternalSearch ) ) {
1304                $this->list['Search'] = [
1305                    'class' => SpecialSearch::class,
1306                    'services' => [
1307                        'SearchEngineConfig',
1308                        'SearchEngineFactory',
1309                        'NamespaceInfo',
1310                        'ContentHandlerFactory',
1311                        'InterwikiLookup',
1312                        'ReadOnlyMode',
1313                        'UserOptionsManager',
1314                        'LanguageConverterFactory',
1315                        'RepoGroup',
1316                        'SearchResultThumbnailProvider',
1317                        'TitleMatcher',
1318                    ]
1319                ];
1320            }
1321
1322            if ( $this->options->get( MainConfigNames::EmailAuthentication ) ) {
1323                $this->list['Confirmemail'] = [
1324                    'class' => SpecialConfirmEmail::class,
1325                    'services' => [
1326                        'UserFactory',
1327                    ]
1328                ];
1329                $this->list['Invalidateemail'] = [
1330                    'class' => SpecialEmailInvalidate::class,
1331                    'services' => [
1332                        'UserFactory',
1333                    ]
1334                ];
1335            }
1336
1337            if ( $this->options->get( MainConfigNames::EnableEmail ) ) {
1338                $this->list['ChangeEmail'] = [
1339                    'class' => SpecialChangeEmail::class,
1340                    'services' => [
1341                        'AuthManager',
1342                    ],
1343                ];
1344            }
1345
1346            if ( $this->options->get( MainConfigNames::EnableJavaScriptTest ) ) {
1347                $this->list['JavaScriptTest'] = [
1348                    'class' => SpecialJavaScriptTest::class
1349                ];
1350            }
1351
1352            if ( $this->options->get( MainConfigNames::EnableSpecialMute ) ) {
1353                $this->list['Mute'] = [
1354                    'class' => SpecialMute::class,
1355                    'services' => [
1356                        'CentralIdLookup',
1357                        'UserOptionsManager',
1358                        'UserIdentityLookup',
1359                        'UserIdentityUtils',
1360                    ]
1361                ];
1362            }
1363
1364            if ( $this->options->get( MainConfigNames::PageLanguageUseDB ) ) {
1365                $this->list['PageLanguage'] = [
1366                    'class' => SpecialPageLanguage::class,
1367                    'services' => [
1368                        'ContentHandlerFactory',
1369                        'LanguageNameUtils',
1370                        'ConnectionProvider',
1371                        'SearchEngineFactory',
1372                    ]
1373                ];
1374            }
1375
1376            if ( $this->options->get( MainConfigNames::EnableEditRecovery ) ) {
1377                $this->list['EditRecovery'] = [
1378                    'class' => SpecialEditRecovery::class,
1379                    'services' => [
1380                        'UserOptionsLookup',
1381                    ],
1382                ];
1383            }
1384
1385            // Add extension special pages
1386            $this->list = array_merge( $this->list,
1387                $this->options->get( MainConfigNames::SpecialPages ) );
1388
1389            // This hook can be used to disable unwanted core special pages
1390            // or conditionally register special pages.
1391            $this->hookRunner->onSpecialPage_initList( $this->list );
1392        }
1393
1394        return $this->list;
1395    }
1396
1397    /**
1398     * Initialise and return the list of special page aliases. Returns an array where
1399     * the key is an alias, and the value is the canonical name of the special page.
1400     * All registered special pages are guaranteed to map to themselves.
1401     * @return array
1402     */
1403    private function getAliasList(): array {
1404        if ( $this->aliases === null ) {
1405            $aliases = $this->contLang->getSpecialPageAliases();
1406            $pageList = $this->getPageList();
1407
1408            $this->aliases = [];
1409            $keepAlias = [];
1410
1411            // Force every canonical name to be an alias for itself.
1412            foreach ( $pageList as $name => $stuff ) {
1413                $caseFoldedAlias = $this->contLang->caseFold( $name );
1414                $this->aliases[$caseFoldedAlias] = $name;
1415                $keepAlias[$caseFoldedAlias] = 'canonical';
1416            }
1417
1418            // Check for $aliases being an array since Language::getSpecialPageAliases can return null
1419            if ( is_array( $aliases ) ) {
1420                foreach ( $aliases as $realName => $aliasList ) {
1421                    $first = true;
1422                    foreach ( $aliasList as $alias ) {
1423                        $caseFoldedAlias = $this->contLang->caseFold( $alias );
1424
1425                        if ( isset( $this->aliases[$caseFoldedAlias] ) &&
1426                            $realName === $this->aliases[$caseFoldedAlias]
1427                        ) {
1428                            $first = false;
1429                            // Ignore same-realName conflicts
1430                            continue;
1431                        }
1432
1433                        if ( !isset( $keepAlias[$caseFoldedAlias] ) ) {
1434                            $this->aliases[$caseFoldedAlias] = $realName;
1435                            if ( $first ) {
1436                                $keepAlias[$caseFoldedAlias] = 'first';
1437                            }
1438                        } elseif ( $first ) {
1439                            wfWarn( "First alias '$alias' for $realName conflicts with " .
1440                                "{$keepAlias[$caseFoldedAlias]} alias for " .
1441                                $this->aliases[$caseFoldedAlias]
1442                            );
1443                        }
1444                        $first = false;
1445                    }
1446                }
1447            }
1448        }
1449
1450        return $this->aliases;
1451    }
1452
1453    /**
1454     * Given a special page name with a possible subpage, return an array
1455     * where the first element is the special page name and the second is the
1456     * subpage.
1457     *
1458     * @param string $alias
1459     * @return array [ String, String|null ], or [ null, null ] if the page is invalid
1460     */
1461    public function resolveAlias( $alias ) {
1462        $bits = explode( '/', $alias, 2 );
1463
1464        $caseFoldedAlias = $this->contLang->caseFold( $bits[0] );
1465        $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
1466        $aliases = $this->getAliasList();
1467        if ( !isset( $aliases[$caseFoldedAlias] ) ) {
1468            return [ null, null ];
1469        }
1470        $name = $aliases[$caseFoldedAlias];
1471        $par = $bits[1] ?? null; // T4087
1472
1473        return [ $name, $par ];
1474    }
1475
1476    /**
1477     * Check if a given name exist as a special page or as a special page alias
1478     *
1479     * @param string $name Name of a special page
1480     * @return bool True if a special page exists with this name
1481     */
1482    public function exists( $name ) {
1483        [ $title, /*...*/ ] = $this->resolveAlias( $name );
1484
1485        $specialPageList = $this->getPageList();
1486        return isset( $specialPageList[$title] );
1487    }
1488
1489    /**
1490     * Find the object with a given name and return it (or NULL)
1491     *
1492     * @param string $name Special page name, may be localised and/or an alias
1493     * @return SpecialPage|null SpecialPage object or null if the page doesn't exist
1494     */
1495    public function getPage( $name ) {
1496        [ $realName, /*...*/ ] = $this->resolveAlias( $name );
1497
1498        $specialPageList = $this->getPageList();
1499
1500        if ( isset( $specialPageList[$realName] ) ) {
1501            $rec = $specialPageList[$realName];
1502
1503            if ( is_array( $rec ) || is_string( $rec ) || is_callable( $rec ) ) {
1504                $page = $this->objectFactory->createObject(
1505                    $rec,
1506                    [
1507                        'allowClassName' => true,
1508                        'allowCallable' => true
1509                    ]
1510                );
1511            } else {
1512                $page = null;
1513            }
1514
1515            if ( $page instanceof SpecialPage ) {
1516                $page->setHookContainer( $this->hookContainer );
1517                $page->setContentLanguage( $this->contLang );
1518                $page->setSpecialPageFactory( $this );
1519                return $page;
1520            }
1521
1522            // It's not a classname, nor a callback, nor a legacy constructor array,
1523            // nor a special page object. Give up.
1524            wfLogWarning( "Cannot instantiate special page $realName: bad spec!" );
1525        }
1526
1527        return null;
1528    }
1529
1530    /**
1531     * Get listed special pages available to the current user.
1532     *
1533     * This includes both unrestricted pages, and restricted pages
1534     * that the current user has the required permissions for.
1535     *
1536     * @param User $user User object to check permissions provided
1537     * @return SpecialPage[]
1538     */
1539    public function getUsablePages( User $user ): array {
1540        $pages = [];
1541        foreach ( $this->getPageList() as $name => $rec ) {
1542            $page = $this->getPage( $name );
1543            if ( $page ) { // not null
1544                $page->setContext( RequestContext::getMain() );
1545                if ( $page->isListed()
1546                    && ( !$page->isRestricted() || $page->userCanExecute( $user ) )
1547                ) {
1548                    $pages[$name] = $page;
1549                }
1550            }
1551        }
1552
1553        return $pages;
1554    }
1555
1556    /**
1557     * Get listed special pages available to everyone by default.
1558     *
1559     * @return array<string,SpecialPage>
1560     */
1561    public function getRegularPages(): array {
1562        $pages = [];
1563        foreach ( $this->getPageList() as $name => $rec ) {
1564            $page = $this->getPage( $name );
1565            if ( $page && $page->isListed() && !$page->isRestricted() ) {
1566                $pages[$name] = $page;
1567            }
1568        }
1569
1570        return $pages;
1571    }
1572
1573    /**
1574     * Get listed special pages, including those that may require user rights.
1575     *
1576     * @since 1.42
1577     * @return array<string,SpecialPage>
1578     */
1579    public function getListedPages(): array {
1580        $pages = [];
1581        foreach ( $this->getPageList() as $name => $rec ) {
1582            $page = $this->getPage( $name );
1583            if ( $page && $page->isListed() ) {
1584                $pages[$name] = $page;
1585            }
1586        }
1587        return $pages;
1588    }
1589
1590    /**
1591     * Execute a special page path.
1592     * The path may contain parameters, e.g. Special:Name/Params
1593     * Extracts the special page name and call the execute method, passing the parameters
1594     *
1595     * Returns a title object if the page is redirected, false if there was no such special
1596     * page, and true if it was successful.
1597     *
1598     * @param PageReference|string $path
1599     * @param IContextSource $context
1600     * @param bool $including Bool output is being captured for use in {{special:whatever}}
1601     * @param LinkRenderer|null $linkRenderer (since 1.28)
1602     *
1603     * @return bool|Title
1604     */
1605    public function executePath( $path, IContextSource $context, $including = false,
1606        LinkRenderer $linkRenderer = null
1607    ) {
1608        if ( $path instanceof PageReference ) {
1609            $path = $path->getDBkey();
1610        }
1611
1612        $bits = explode( '/', $path, 2 );
1613        $name = $bits[0];
1614        $par = $bits[1] ?? null; // T4087
1615
1616        $page = $this->getPage( $name );
1617        if ( !$page ) {
1618            // Emulate SpecialPage::setHeaders()
1619            $context->getOutput()->setArticleRelated( false );
1620            $context->getOutput()->setRobotPolicy( 'noindex,nofollow' );
1621
1622            if ( $context->getConfig()->get( MainConfigNames::Send404Code ) ) {
1623                $context->getOutput()->setStatusCode( 404 );
1624            }
1625
1626            $context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
1627
1628            return false;
1629        }
1630
1631        if ( !$including ) {
1632            ProfilingContext::singleton()->init( MW_ENTRY_POINT, $page->getName() );
1633            // Narrow DB query expectations for this HTTP request
1634            $trxLimits = $context->getConfig()->get( MainConfigNames::TrxProfilerLimits );
1635            $trxProfiler = Profiler::instance()->getTransactionProfiler();
1636            if ( $context->getRequest()->wasPosted() && !$page->doesWrites() ) {
1637                $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
1638            }
1639        }
1640
1641        // Page exists, set the context
1642        $page->setContext( $context );
1643
1644        if ( !$including ) {
1645            // Redirect to canonical alias for GET commands
1646            // Not for POST, we'd lose the post data, so it's best to just distribute
1647            // the request. Such POST requests are possible for old extensions that
1648            // generate self-links without being aware that their default name has
1649            // changed.
1650            if ( $name != $page->getLocalName() && !$context->getRequest()->wasPosted() ) {
1651                $query = $context->getRequest()->getQueryValues();
1652                unset( $query['title'] );
1653                $title = $page->getPageTitle( $par ?? false );
1654                $url = $title->getFullURL( $query );
1655                $context->getOutput()->redirect( $url );
1656
1657                return $title;
1658            }
1659
1660            // @phan-suppress-next-line PhanUndeclaredMethod
1661            $context->setTitle( $page->getPageTitle( $par ?? false ) );
1662        } elseif ( !$page->isIncludable() ) {
1663            return false;
1664        }
1665
1666        $page->including( $including );
1667        if ( $linkRenderer ) {
1668            $page->setLinkRenderer( $linkRenderer );
1669        }
1670
1671        // Execute special page
1672        $page->run( $par );
1673
1674        return true;
1675    }
1676
1677    /**
1678     * Just like executePath() but will override global variables and execute
1679     * the page in "inclusion" mode. Returns true if the execution was
1680     * successful or false if there was no such special page, or a title object
1681     * if it was a redirect.
1682     *
1683     * Also saves the current $wgTitle, $wgOut, $wgRequest, $wgUser and $wgLang
1684     * variables so that the special page will get the context it'd expect on a
1685     * normal request, and then restores them to their previous values after.
1686     *
1687     * @param PageReference $page
1688     * @param IContextSource $context
1689     * @param LinkRenderer|null $linkRenderer (since 1.28)
1690     * @return bool|Title
1691     */
1692    public function capturePath(
1693        PageReference $page, IContextSource $context, LinkRenderer $linkRenderer = null
1694    ) {
1695        // phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgUser
1696        global $wgTitle, $wgOut, $wgRequest, $wgUser, $wgLang;
1697        $main = RequestContext::getMain();
1698
1699        // Save current globals and main context
1700        $glob = [
1701            'title' => $wgTitle,
1702            'output' => $wgOut,
1703            'request' => $wgRequest,
1704            'user' => $wgUser,
1705            'language' => $wgLang,
1706        ];
1707        $ctx = [
1708            'title' => $main->getTitle(),
1709            'output' => $main->getOutput(),
1710            'request' => $main->getRequest(),
1711            'user' => $main->getUser(),
1712            'language' => $main->getLanguage(),
1713        ];
1714        if ( $main->canUseWikiPage() ) {
1715            $ctx['wikipage'] = $main->getWikiPage();
1716        }
1717
1718        // just needed for $wgTitle and RequestContext::setTitle
1719        $title = $this->titleFactory->castFromPageReference( $page );
1720
1721        // Override
1722        $wgTitle = $title;
1723        $wgOut = $context->getOutput();
1724        $wgRequest = $context->getRequest();
1725        $wgUser = $context->getUser();
1726        $wgLang = $context->getLanguage();
1727        // FIXME: Once reasonably certain that no SpecialPage subclasses
1728        // rely on direct RequestContext::getMain instead of their local
1729        // context getters, these can be removed (T323184)
1730        // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1731        @$main->setTitle( $title );
1732        $main->setOutput( $context->getOutput() );
1733        $main->setRequest( $context->getRequest() );
1734        $main->setUser( $context->getUser() );
1735        $main->setLanguage( $context->getLanguage() );
1736
1737        try {
1738            // The useful part
1739            return $this->executePath( $page, $context, true, $linkRenderer );
1740        } finally {
1741            // Restore old globals and context
1742            $wgTitle = $glob['title'];
1743            $wgOut = $glob['output'];
1744            $wgRequest = $glob['request'];
1745            $wgUser = $glob['user'];
1746            $wgLang = $glob['language'];
1747            // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1748            @$main->setTitle( $ctx['title'] );
1749            $main->setOutput( $ctx['output'] );
1750            $main->setRequest( $ctx['request'] );
1751            $main->setUser( $ctx['user'] );
1752            $main->setLanguage( $ctx['language'] );
1753            if ( isset( $ctx['wikipage'] ) ) {
1754                $main->setWikiPage( $ctx['wikipage'] );
1755            }
1756        }
1757    }
1758
1759    /**
1760     * Get the local name for a specified canonical name
1761     *
1762     * @param string $name
1763     * @param string|false|null $subpage
1764     * @return string
1765     */
1766    public function getLocalNameFor( $name, $subpage = false ) {
1767        $aliases = $this->contLang->getSpecialPageAliases();
1768        $aliasList = $this->getAliasList();
1769
1770        // Find the first alias that maps back to $name
1771        if ( isset( $aliases[$name] ) ) {
1772            $found = false;
1773            foreach ( $aliases[$name] as $alias ) {
1774                $caseFoldedAlias = $this->contLang->caseFold( $alias );
1775                $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
1776                if ( isset( $aliasList[$caseFoldedAlias] ) &&
1777                    $aliasList[$caseFoldedAlias] === $name
1778                ) {
1779                    $name = $alias;
1780                    $found = true;
1781                    break;
1782                }
1783            }
1784            if ( !$found ) {
1785                wfWarn( "Did not find a usable alias for special page '$name'. " .
1786                    "It seems all defined aliases conflict?" );
1787            }
1788        } else {
1789            // Check if someone misspelled the correct casing
1790            if ( is_array( $aliases ) ) {
1791                foreach ( $aliases as $n => $values ) {
1792                    if ( strcasecmp( $name, $n ) === 0 ) {
1793                        wfWarn( "Found alias defined for $n when searching for " .
1794                            "special page aliases for $name. Case mismatch?" );
1795                        return $this->getLocalNameFor( $n, $subpage );
1796                    }
1797                }
1798            }
1799
1800            wfWarn( "Did not find alias for special page '$name'. " .
1801                "Perhaps no aliases are defined for it?" );
1802        }
1803
1804        if ( $subpage !== false && $subpage !== null ) {
1805            // Make sure it's in dbkey form
1806            $subpage = str_replace( ' ', '_', $subpage );
1807            $name = "$name/$subpage";
1808        }
1809
1810        return $this->contLang->ucfirst( $name );
1811    }
1812
1813    /**
1814     * Get a title for a given alias
1815     *
1816     * @param string $alias
1817     * @return Title|null Title or null if there is no such alias
1818     */
1819    public function getTitleForAlias( $alias ) {
1820        [ $name, $subpage ] = $this->resolveAlias( $alias );
1821        if ( $name != null ) {
1822            return SpecialPage::getTitleFor( $name, $subpage );
1823        }
1824
1825        return null;
1826    }
1827}