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