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