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