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