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