3 * Factory for handling the special page list and generating SpecialPage objects.
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.
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.
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
21 * @ingroup SpecialPage
22 * @defgroup SpecialPage SpecialPage
25 namespace MediaWiki\SpecialPage
;
27 use MediaWiki\Config\ServiceOptions
;
28 use MediaWiki\Context\IContextSource
;
29 use MediaWiki\Context\RequestContext
;
30 use MediaWiki\HookContainer\HookContainer
;
31 use MediaWiki\HookContainer\HookRunner
;
32 use MediaWiki\Language\Language
;
33 use MediaWiki\Linker\LinkRenderer
;
34 use MediaWiki\MainConfigNames
;
35 use MediaWiki\Page\PageReference
;
36 use MediaWiki\Profiler\ProfilingContext
;
37 use MediaWiki\Specials\Redirects\SpecialAllMyUploads
;
38 use MediaWiki\Specials\Redirects\SpecialListAdmins
;
39 use MediaWiki\Specials\Redirects\SpecialListBots
;
40 use MediaWiki\Specials\Redirects\SpecialMycontributions
;
41 use MediaWiki\Specials\Redirects\SpecialMylog
;
42 use MediaWiki\Specials\Redirects\SpecialMypage
;
43 use MediaWiki\Specials\Redirects\SpecialMytalk
;
44 use MediaWiki\Specials\Redirects\SpecialMyuploads
;
45 use MediaWiki\Specials\Redirects\SpecialTalkPage
;
46 use MediaWiki\Specials\SpecialActiveUsers
;
47 use MediaWiki\Specials\SpecialAllMessages
;
48 use MediaWiki\Specials\SpecialAllPages
;
49 use MediaWiki\Specials\SpecialAncientPages
;
50 use MediaWiki\Specials\SpecialApiHelp
;
51 use MediaWiki\Specials\SpecialApiSandbox
;
52 use MediaWiki\Specials\SpecialAuthenticationPopupSuccess
;
53 use MediaWiki\Specials\SpecialAutoblockList
;
54 use MediaWiki\Specials\SpecialBlankpage
;
55 use MediaWiki\Specials\SpecialBlock
;
56 use MediaWiki\Specials\SpecialBlockList
;
57 use MediaWiki\Specials\SpecialBookSources
;
58 use MediaWiki\Specials\SpecialBotPasswords
;
59 use MediaWiki\Specials\SpecialBrokenRedirects
;
60 use MediaWiki\Specials\SpecialCategories
;
61 use MediaWiki\Specials\SpecialChangeContentModel
;
62 use MediaWiki\Specials\SpecialChangeCredentials
;
63 use MediaWiki\Specials\SpecialChangeEmail
;
64 use MediaWiki\Specials\SpecialChangePassword
;
65 use MediaWiki\Specials\SpecialComparePages
;
66 use MediaWiki\Specials\SpecialConfirmEmail
;
67 use MediaWiki\Specials\SpecialContribute
;
68 use MediaWiki\Specials\SpecialContributions
;
69 use MediaWiki\Specials\SpecialCreateAccount
;
70 use MediaWiki\Specials\SpecialDeadendPages
;
71 use MediaWiki\Specials\SpecialDeletedContributions
;
72 use MediaWiki\Specials\SpecialDeletePage
;
73 use MediaWiki\Specials\SpecialDiff
;
74 use MediaWiki\Specials\SpecialDoubleRedirects
;
75 use MediaWiki\Specials\SpecialEditPage
;
76 use MediaWiki\Specials\SpecialEditRecovery
;
77 use MediaWiki\Specials\SpecialEditTags
;
78 use MediaWiki\Specials\SpecialEditWatchlist
;
79 use MediaWiki\Specials\SpecialEmailInvalidate
;
80 use MediaWiki\Specials\SpecialEmailUser
;
81 use MediaWiki\Specials\SpecialExpandTemplates
;
82 use MediaWiki\Specials\SpecialExport
;
83 use MediaWiki\Specials\SpecialFewestRevisions
;
84 use MediaWiki\Specials\SpecialFileDuplicateSearch
;
85 use MediaWiki\Specials\SpecialFilepath
;
86 use MediaWiki\Specials\SpecialGoToInterwiki
;
87 use MediaWiki\Specials\SpecialImport
;
88 use MediaWiki\Specials\SpecialInterwiki
;
89 use MediaWiki\Specials\SpecialJavaScriptTest
;
90 use MediaWiki\Specials\SpecialLinkAccounts
;
91 use MediaWiki\Specials\SpecialLinkSearch
;
92 use MediaWiki\Specials\SpecialListDuplicatedFiles
;
93 use MediaWiki\Specials\SpecialListFiles
;
94 use MediaWiki\Specials\SpecialListGrants
;
95 use MediaWiki\Specials\SpecialListGroupRights
;
96 use MediaWiki\Specials\SpecialListRedirects
;
97 use MediaWiki\Specials\SpecialListUsers
;
98 use MediaWiki\Specials\SpecialLockdb
;
99 use MediaWiki\Specials\SpecialLog
;
100 use MediaWiki\Specials\SpecialLonelyPages
;
101 use MediaWiki\Specials\SpecialLongPages
;
102 use MediaWiki\Specials\SpecialMediaStatistics
;
103 use MediaWiki\Specials\SpecialMergeHistory
;
104 use MediaWiki\Specials\SpecialMIMESearch
;
105 use MediaWiki\Specials\SpecialMostCategories
;
106 use MediaWiki\Specials\SpecialMostImages
;
107 use MediaWiki\Specials\SpecialMostInterwikis
;
108 use MediaWiki\Specials\SpecialMostLinked
;
109 use MediaWiki\Specials\SpecialMostLinkedCategories
;
110 use MediaWiki\Specials\SpecialMostLinkedTemplates
;
111 use MediaWiki\Specials\SpecialMostRevisions
;
112 use MediaWiki\Specials\SpecialMovePage
;
113 use MediaWiki\Specials\SpecialMute
;
114 use MediaWiki\Specials\SpecialMyLanguage
;
115 use MediaWiki\Specials\SpecialNamespaceInfo
;
116 use MediaWiki\Specials\SpecialNewFiles
;
117 use MediaWiki\Specials\SpecialNewPages
;
118 use MediaWiki\Specials\SpecialNewSection
;
119 use MediaWiki\Specials\SpecialPageData
;
120 use MediaWiki\Specials\SpecialPageHistory
;
121 use MediaWiki\Specials\SpecialPageInfo
;
122 use MediaWiki\Specials\SpecialPageLanguage
;
123 use MediaWiki\Specials\SpecialPagesWithProp
;
124 use MediaWiki\Specials\SpecialPasswordPolicies
;
125 use MediaWiki\Specials\SpecialPasswordReset
;
126 use MediaWiki\Specials\SpecialPermanentLink
;
127 use MediaWiki\Specials\SpecialPreferences
;
128 use MediaWiki\Specials\SpecialPrefixIndex
;
129 use MediaWiki\Specials\SpecialProtectedPages
;
130 use MediaWiki\Specials\SpecialProtectedTitles
;
131 use MediaWiki\Specials\SpecialProtectPage
;
132 use MediaWiki\Specials\SpecialPurge
;
133 use MediaWiki\Specials\SpecialRandomInCategory
;
134 use MediaWiki\Specials\SpecialRandomPage
;
135 use MediaWiki\Specials\SpecialRandomRedirect
;
136 use MediaWiki\Specials\SpecialRandomRootPage
;
137 use MediaWiki\Specials\SpecialRecentChanges
;
138 use MediaWiki\Specials\SpecialRecentChangesLinked
;
139 use MediaWiki\Specials\SpecialRedirect
;
140 use MediaWiki\Specials\SpecialRemoveCredentials
;
141 use MediaWiki\Specials\SpecialRenameUser
;
142 use MediaWiki\Specials\SpecialResetTokens
;
143 use MediaWiki\Specials\SpecialRestSandbox
;
144 use MediaWiki\Specials\SpecialRevisionDelete
;
145 use MediaWiki\Specials\SpecialRunJobs
;
146 use MediaWiki\Specials\SpecialSearch
;
147 use MediaWiki\Specials\SpecialShortPages
;
148 use MediaWiki\Specials\SpecialSpecialPages
;
149 use MediaWiki\Specials\SpecialStatistics
;
150 use MediaWiki\Specials\SpecialTags
;
151 use MediaWiki\Specials\SpecialTrackingCategories
;
152 use MediaWiki\Specials\SpecialUnblock
;
153 use MediaWiki\Specials\SpecialUncategorizedCategories
;
154 use MediaWiki\Specials\SpecialUncategorizedImages
;
155 use MediaWiki\Specials\SpecialUncategorizedPages
;
156 use MediaWiki\Specials\SpecialUncategorizedTemplates
;
157 use MediaWiki\Specials\SpecialUndelete
;
158 use MediaWiki\Specials\SpecialUnlinkAccounts
;
159 use MediaWiki\Specials\SpecialUnlockdb
;
160 use MediaWiki\Specials\SpecialUnusedCategories
;
161 use MediaWiki\Specials\SpecialUnusedImages
;
162 use MediaWiki\Specials\SpecialUnusedTemplates
;
163 use MediaWiki\Specials\SpecialUnwatchedPages
;
164 use MediaWiki\Specials\SpecialUpload
;
165 use MediaWiki\Specials\SpecialUploadStash
;
166 use MediaWiki\Specials\SpecialUserLogin
;
167 use MediaWiki\Specials\SpecialUserLogout
;
168 use MediaWiki\Specials\SpecialUserRights
;
169 use MediaWiki\Specials\SpecialVersion
;
170 use MediaWiki\Specials\SpecialWantedCategories
;
171 use MediaWiki\Specials\SpecialWantedFiles
;
172 use MediaWiki\Specials\SpecialWantedPages
;
173 use MediaWiki\Specials\SpecialWantedTemplates
;
174 use MediaWiki\Specials\SpecialWatchlist
;
175 use MediaWiki\Specials\SpecialWhatLinksHere
;
176 use MediaWiki\Specials\SpecialWithoutInterwiki
;
177 use MediaWiki\Title\Title
;
178 use MediaWiki\Title\TitleFactory
;
179 use MediaWiki\User\User
;
181 use Wikimedia\DebugInfo\DebugInfoTrait
;
182 use Wikimedia\ObjectFactory\ObjectFactory
;
185 * Factory for handling the special page list and generating SpecialPage objects.
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.
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.
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.
202 * @ingroup SpecialPage
205 class SpecialPageFactory
{
209 * List of special page names to the subclass of SpecialPage which handles them.
211 private const CORE_LIST
= [
212 // Maintenance Reports
213 'BrokenRedirects' => [
214 'class' => SpecialBrokenRedirects
::class,
216 'ContentHandlerFactory',
217 'ConnectionProvider',
222 'class' => SpecialDeadendPages
::class,
225 'ConnectionProvider',
227 'LanguageConverterFactory',
230 'DoubleRedirects' => [
231 'class' => SpecialDoubleRedirects
::class,
233 'ContentHandlerFactory',
235 'ConnectionProvider',
239 'class' => SpecialLongPages
::class,
241 // Same as for Shortpages
243 'ConnectionProvider',
248 'class' => SpecialAncientPages
::class,
251 'ConnectionProvider',
253 'LanguageConverterFactory',
257 'class' => SpecialLonelyPages
::class,
260 'ConnectionProvider',
262 'LanguageConverterFactory',
266 'Fewestrevisions' => [
267 'class' => SpecialFewestRevisions
::class,
269 // Same as for Mostrevisions
271 'ConnectionProvider',
273 'LanguageConverterFactory',
276 'Withoutinterwiki' => [
277 'class' => SpecialWithoutInterwiki
::class,
280 'ConnectionProvider',
282 'LanguageConverterFactory',
285 'Protectedpages' => [
286 'class' => SpecialProtectedPages
::class,
289 'ConnectionProvider',
291 'RowCommentFormatter',
295 'Protectedtitles' => [
296 'class' => SpecialProtectedTitles
::class,
299 'ConnectionProvider',
303 'class' => SpecialShortPages
::class,
305 // Same as for Longpages
307 'ConnectionProvider',
311 'Uncategorizedcategories' => [
312 'class' => SpecialUncategorizedCategories
::class,
314 // Same as for SpecialUncategorizedPages and SpecialUncategorizedTemplates
316 'ConnectionProvider',
318 'LanguageConverterFactory',
321 'Uncategorizedimages' => [
322 'class' => SpecialUncategorizedImages
::class,
324 'ConnectionProvider',
327 'Uncategorizedpages' => [
328 'class' => SpecialUncategorizedPages
::class,
330 // Same as for SpecialUncategorizedCategories and SpecialUncategorizedTemplates
332 'ConnectionProvider',
334 'LanguageConverterFactory',
337 'Uncategorizedtemplates' => [
338 'class' => SpecialUncategorizedTemplates
::class,
340 // Same as for SpecialUncategorizedCategories and SpecialUncategorizedPages
342 'ConnectionProvider',
344 'LanguageConverterFactory',
347 'Unusedcategories' => [
348 'class' => SpecialUnusedCategories
::class,
350 'ConnectionProvider',
355 'class' => SpecialUnusedImages
::class,
357 'ConnectionProvider',
360 'Unusedtemplates' => [
361 'class' => SpecialUnusedTemplates
::class,
363 'ConnectionProvider',
367 'Unwatchedpages' => [
368 'class' => SpecialUnwatchedPages
::class,
371 'ConnectionProvider',
372 'LanguageConverterFactory',
375 'Wantedcategories' => [
376 'class' => SpecialWantedCategories
::class,
378 'ConnectionProvider',
380 'LanguageConverterFactory',
384 'class' => SpecialWantedFiles
::class,
387 'ConnectionProvider',
392 'class' => SpecialWantedPages
::class,
394 'ConnectionProvider',
399 'Wantedtemplates' => [
400 'class' => SpecialWantedTemplates
::class,
402 'ConnectionProvider',
410 'class' => SpecialAllPages
::class,
412 'ConnectionProvider',
413 'SearchEngineFactory',
418 'class' => SpecialPrefixIndex
::class,
420 'ConnectionProvider',
425 'class' => SpecialCategories
::class,
428 'ConnectionProvider',
432 'class' => SpecialListRedirects
::class,
435 'ConnectionProvider',
441 'class' => SpecialPagesWithProp
::class,
443 'ConnectionProvider',
446 'TrackingCategories' => [
447 'class' => SpecialTrackingCategories
::class,
450 'TrackingCategories',
456 'class' => SpecialUserLogin
::class,
463 'class' => SpecialUserLogout
::class,
469 'class' => SpecialCreateAccount
::class,
477 'class' => SpecialLinkAccounts
::class,
482 'UnlinkAccounts' => [
483 'class' => SpecialUnlinkAccounts
::class,
488 'ChangeCredentials' => [
489 'class' => SpecialChangeCredentials
::class,
494 'RemoveCredentials' => [
495 'class' => SpecialRemoveCredentials
::class,
500 'AuthenticationPopupSuccess' => [
501 'class' => SpecialAuthenticationPopupSuccess
::class,
509 'class' => SpecialActiveUsers
::class,
512 'ConnectionProvider',
514 'UserIdentityLookup',
519 'class' => SpecialBlock
::class,
522 'BlockPermissionCheckerFactory',
524 'DatabaseBlockStore',
526 'UserNamePrefixSearch',
533 'class' => SpecialUnblock
::class,
535 'UnblockUserFactory',
537 'DatabaseBlockStore',
539 'UserNamePrefixSearch',
544 'class' => SpecialBlockList
::class,
547 'DatabaseBlockStore',
548 'BlockRestrictionStore',
549 'ConnectionProvider',
554 'RowCommentFormatter',
558 'class' => SpecialAutoblockList
::class,
561 'BlockRestrictionStore',
562 'ConnectionProvider',
567 'RowCommentFormatter',
570 'ChangePassword' => [
571 'class' => SpecialChangePassword
::class,
574 'class' => SpecialBotPasswords
::class,
580 'GrantsLocalization',
584 'class' => SpecialPasswordReset
::class,
589 'DeletedContributions' => [
590 'class' => SpecialDeletedContributions
::class,
593 'ConnectionProvider',
597 'UserNamePrefixSearch',
602 'UserIdentityLookup',
603 'DatabaseBlockStore',
608 'class' => SpecialPreferences
::class,
610 'PreferencesFactory',
611 'UserOptionsManager',
615 'class' => SpecialResetTokens
::class,
618 'class' => SpecialContributions
::class,
622 'ConnectionProvider',
626 'UserNamePrefixSearch',
630 'UserIdentityLookup',
631 'DatabaseBlockStore',
635 'Listgrouprights' => [
636 'class' => SpecialListGroupRights
::class,
640 'LanguageConverterFactory',
641 'GroupPermissionsLookup',
645 'class' => SpecialListGrants
::class,
647 'GrantsLocalization',
651 'class' => SpecialListUsers
::class,
654 'ConnectionProvider',
656 'UserIdentityLookup',
661 'class' => SpecialListAdmins
::class,
664 'class' => SpecialListBots
::class,
667 'class' => SpecialUserRights
::class,
669 'UserGroupManagerFactory',
671 'UserNamePrefixSearch',
679 'class' => SpecialEditWatchlist
::class,
690 'PasswordPolicies' => [
691 'class' => SpecialPasswordPolicies
::class,
697 // Recent changes and logs
699 'class' => SpecialNewFiles
::class,
702 'GroupPermissionsLookup',
703 'ConnectionProvider',
708 'class' => SpecialLog
::class,
711 'ConnectionProvider',
712 'ActorNormalization',
713 'UserIdentityLookup',
715 'LogFormatterFactory',
719 'class' => SpecialWatchlist
::class,
730 'class' => SpecialNewPages
::class,
733 'ContentHandlerFactory',
734 'GroupPermissionsLookup',
738 'RowCommentFormatter',
744 'class' => SpecialRecentChanges
::class,
754 'Recentchangeslinked' => [
755 'class' => SpecialRecentChangesLinked
::class,
760 'SearchEngineFactory',
767 'class' => SpecialTags
::class,
773 // Media reports and uploads
775 'class' => SpecialListFiles
::class,
778 'ConnectionProvider',
781 'UserNamePrefixSearch',
787 'class' => SpecialFilepath
::class,
789 'SearchEngineFactory',
792 'MediaStatistics' => [
793 'class' => SpecialMediaStatistics
::class,
796 'ConnectionProvider',
801 'class' => SpecialMIMESearch
::class,
803 'ConnectionProvider',
805 'LanguageConverterFactory',
808 'FileDuplicateSearch' => [
809 'class' => SpecialFileDuplicateSearch
::class,
813 'SearchEngineFactory',
814 'LanguageConverterFactory',
818 'class' => SpecialUpload
::class,
826 'class' => SpecialUploadStash
::class,
829 'HttpRequestFactory',
831 'ConnectionProvider',
834 'ListDuplicatedFiles' => [
835 'class' => SpecialListDuplicatedFiles
::class,
837 'ConnectionProvider',
844 'class' => SpecialApiSandbox
::class,
847 'class' => SpecialInterwiki
::class,
853 'ConnectionProvider',
857 'class' => SpecialRestSandbox
::class,
863 'class' => SpecialStatistics
::class,
869 'class' => SpecialAllMessages
::class,
874 'ConnectionProvider',
878 'class' => SpecialVersion
::class,
882 'ConnectionProvider',
886 'class' => SpecialLockdb
::class,
889 'class' => SpecialUnlockdb
::class,
892 'class' => SpecialNamespaceInfo
::class,
898 // Redirecting special pages
900 'class' => SpecialLinkSearch
::class,
902 'ConnectionProvider',
908 'class' => SpecialRandomPage
::class,
910 'ConnectionProvider',
914 'RandomInCategory' => [
915 'class' => SpecialRandomInCategory
::class,
917 'ConnectionProvider',
920 'Randomredirect' => [
921 'class' => SpecialRandomRedirect
::class,
923 'ConnectionProvider',
927 'Randomrootpage' => [
928 'class' => SpecialRandomRootPage
::class,
930 'ConnectionProvider',
935 'class' => SpecialGoToInterwiki
::class,
939 'Mostlinkedcategories' => [
940 'class' => SpecialMostLinkedCategories
::class,
942 'ConnectionProvider',
944 'LanguageConverterFactory',
948 'class' => SpecialMostImages
::class,
950 'ConnectionProvider',
951 'LanguageConverterFactory',
954 'Mostinterwikis' => [
955 'class' => SpecialMostInterwikis
::class,
958 'ConnectionProvider',
963 'class' => SpecialMostLinked
::class,
965 'ConnectionProvider',
970 'Mostlinkedtemplates' => [
971 'class' => SpecialMostLinkedTemplates
::class,
973 'ConnectionProvider',
978 'Mostcategories' => [
979 'class' => SpecialMostCategories
::class,
982 'ConnectionProvider',
987 'class' => SpecialMostRevisions
::class,
989 // Same as for Fewestrevisions
991 'ConnectionProvider',
993 'LanguageConverterFactory',
999 'class' => SpecialComparePages
::class,
1002 'ContentHandlerFactory',
1006 'class' => SpecialExport
::class,
1008 'ConnectionProvider',
1009 'WikiExporterFactory',
1015 'class' => SpecialImport
::class,
1017 'WikiImporterFactory',
1021 'class' => SpecialUndelete
::class,
1023 'PermissionManager',
1026 'ContentHandlerFactory',
1027 'ChangeTagDefStore',
1030 'ConnectionProvider',
1031 'UserOptionsLookup',
1033 'SearchEngineFactory',
1034 'UndeletePageFactory',
1035 'ArchivedRevisionLookup',
1040 'Whatlinkshere' => [
1041 'class' => SpecialWhatLinksHere
::class,
1043 'ConnectionProvider',
1045 'ContentHandlerFactory',
1046 'SearchEngineFactory',
1053 'class' => SpecialMergeHistory
::class,
1055 'MergeHistoryFactory',
1057 'ConnectionProvider',
1062 'ExpandTemplates' => [
1063 'class' => SpecialExpandTemplates
::class,
1066 'UserOptionsLookup',
1070 'ChangeContentModel' => [
1071 'class' => SpecialChangeContentModel
::class,
1073 'ContentHandlerFactory',
1074 'ContentModelChangeFactory',
1078 'SearchEngineFactory',
1085 'class' => SpecialBookSources
::class,
1092 // Unlisted / redirects
1094 'class' => SpecialApiHelp
::class,
1100 'class' => SpecialBlankpage
::class,
1103 'class' => SpecialDeletePage
::class,
1105 'SearchEngineFactory',
1109 'class' => SpecialDiff
::class,
1112 'class' => SpecialEditPage
::class,
1114 'SearchEngineFactory',
1118 'class' => SpecialEditTags
::class,
1120 'PermissionManager',
1125 'class' => SpecialEmailUser
::class,
1128 'UserNamePrefixSearch',
1129 'UserOptionsLookup',
1135 'class' => SpecialMovePage
::class,
1138 'PermissionManager',
1139 'UserOptionsLookup',
1140 'ConnectionProvider',
1141 'ContentHandlerFactory',
1146 'SearchEngineFactory',
1150 'DeletePageFactory',
1153 'Mycontributions' => [
1154 'class' => SpecialMycontributions
::class,
1160 'class' => SpecialMyLanguage
::class,
1162 'LanguageNameUtils',
1167 'class' => SpecialMylog
::class,
1173 'class' => SpecialMypage
::class,
1179 'class' => SpecialMytalk
::class,
1185 'class' => SpecialPageHistory
::class,
1187 'SearchEngineFactory',
1191 'class' => SpecialPageInfo
::class,
1193 'SearchEngineFactory',
1197 'class' => SpecialProtectPage
::class,
1199 'SearchEngineFactory',
1203 'class' => SpecialPurge
::class,
1205 'SearchEngineFactory',
1209 'class' => SpecialMyuploads
::class,
1215 'class' => SpecialAllMyUploads
::class,
1218 'class' => SpecialNewSection
::class,
1220 'SearchEngineFactory',
1223 'PermanentLink' => [
1224 'class' => SpecialPermanentLink
::class,
1227 'class' => SpecialRedirect
::class,
1234 'class' => SpecialRenameUser
::class,
1236 'ConnectionProvider',
1238 'PermissionManager',
1241 'UserNamePrefixSearch',
1245 'Revisiondelete' => [
1246 'class' => SpecialRevisionDelete
::class,
1248 'PermissionManager',
1253 'class' => SpecialRunJobs
::class,
1260 'class' => SpecialSpecialPages
::class,
1263 'class' => SpecialPageData
::class,
1266 'class' => SpecialContribute
::class,
1269 'class' => SpecialTalkPage
::class,
1277 /** @var array Special page name => class name */
1283 /** @var ServiceOptions */
1286 /** @var Language */
1290 * @var ObjectFactory
1293 private $objectFactory;
1296 * @var HookContainer
1299 private $hookContainer;
1305 private $hookRunner;
1308 * @internal For use by ServiceWiring
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
,
1324 private $titleFactory;
1327 * @param ServiceOptions $options
1328 * @param Language $contLang
1329 * @param ObjectFactory $objectFactory
1330 * @param TitleFactory $titleFactory
1331 * @param HookContainer $hookContainer
1333 public function __construct(
1334 ServiceOptions
$options,
1336 ObjectFactory
$objectFactory,
1337 TitleFactory
$titleFactory,
1338 HookContainer
$hookContainer
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 );
1350 * Returns a list of canonical special page names.
1351 * May be used to iterate over all registered special pages.
1355 public function getNames(): array {
1356 return array_keys( $this->getPageList() );
1360 * Get the special page list as an array
1364 private function getPageList(): array {
1365 if ( !is_array( $this->list ) ) {
1366 $this->list = self
::CORE_LIST
;
1368 if ( !$this->options
->get( MainConfigNames
::DisableInternalSearch
) ) {
1369 $this->list['Search'] = [
1370 'class' => SpecialSearch
::class,
1372 'SearchEngineConfig',
1373 'SearchEngineFactory',
1375 'ContentHandlerFactory',
1378 'UserOptionsManager',
1379 'LanguageConverterFactory',
1381 'SearchResultThumbnailProvider',
1387 if ( $this->options
->get( MainConfigNames
::EmailAuthentication
) ) {
1388 $this->list['Confirmemail'] = [
1389 'class' => SpecialConfirmEmail
::class,
1394 $this->list['Invalidateemail'] = [
1395 'class' => SpecialEmailInvalidate
::class,
1402 if ( $this->options
->get( MainConfigNames
::EnableEmail
) ) {
1403 $this->list['ChangeEmail'] = [
1404 'class' => SpecialChangeEmail
::class,
1411 if ( $this->options
->get( MainConfigNames
::EnableJavaScriptTest
) ) {
1412 $this->list['JavaScriptTest'] = [
1413 'class' => SpecialJavaScriptTest
::class
1417 if ( $this->options
->get( MainConfigNames
::EnableSpecialMute
) ) {
1418 $this->list['Mute'] = [
1419 'class' => SpecialMute
::class,
1422 'UserOptionsManager',
1423 'UserIdentityLookup',
1424 'UserIdentityUtils',
1429 if ( $this->options
->get( MainConfigNames
::PageLanguageUseDB
) ) {
1430 $this->list['PageLanguage'] = [
1431 'class' => SpecialPageLanguage
::class,
1433 'ContentHandlerFactory',
1434 'LanguageNameUtils',
1435 'ConnectionProvider',
1436 'SearchEngineFactory',
1441 if ( $this->options
->get( MainConfigNames
::EnableEditRecovery
) ) {
1442 $this->list['EditRecovery'] = [
1443 'class' => SpecialEditRecovery
::class,
1445 'UserOptionsLookup',
1450 // Add extension special pages
1451 $this->list = array_merge( $this->list,
1452 $this->options
->get( MainConfigNames
::SpecialPages
) );
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 );
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.
1468 private function getAliasList(): array {
1469 if ( $this->aliases
=== null ) {
1470 $aliases = $this->contLang
->getSpecialPageAliases();
1471 $pageList = $this->getPageList();
1473 $this->aliases
= [];
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';
1483 // Check for $aliases being an array since Language::getSpecialPageAliases can return null
1484 if ( is_array( $aliases ) ) {
1485 foreach ( $aliases as $realName => $aliasList ) {
1487 foreach ( $aliasList as $alias ) {
1488 $caseFoldedAlias = $this->contLang
->caseFold( $alias );
1490 if ( isset( $this->aliases
[$caseFoldedAlias] ) &&
1491 $realName === $this->aliases
[$caseFoldedAlias]
1494 // Ignore same-realName conflicts
1498 if ( !isset( $keepAlias[$caseFoldedAlias] ) ) {
1499 $this->aliases
[$caseFoldedAlias] = $realName;
1501 $keepAlias[$caseFoldedAlias] = 'first';
1503 } elseif ( $first ) {
1504 wfWarn( "First alias '$alias' for $realName conflicts with " .
1505 "{$keepAlias[$caseFoldedAlias]} alias for " .
1506 $this->aliases
[$caseFoldedAlias]
1515 return $this->aliases
;
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
1523 * @param string $alias
1524 * @return array [ String, String|null ], or [ null, null ] if the page is invalid
1526 public function resolveAlias( $alias ) {
1527 $bits = explode( '/', $alias, 2 );
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 ];
1535 $name = $aliases[$caseFoldedAlias];
1536 $par = $bits[1] ??
null; // T4087
1538 return [ $name, $par ];
1542 * Check if a given name exist as a special page or as a special page alias
1544 * @param string $name Name of a special page
1545 * @return bool True if a special page exists with this name
1547 public function exists( $name ) {
1548 [ $title, /*...*/ ] = $this->resolveAlias( $name );
1550 $specialPageList = $this->getPageList();
1551 return isset( $specialPageList[$title] );
1555 * Find the object with a given name and return it (or NULL)
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
1560 public function getPage( $name ) {
1561 [ $realName, /*...*/ ] = $this->resolveAlias( $name );
1563 $specialPageList = $this->getPageList();
1565 if ( isset( $specialPageList[$realName] ) ) {
1566 $rec = $specialPageList[$realName];
1568 if ( is_array( $rec ) ||
is_string( $rec ) ||
is_callable( $rec ) ) {
1569 $page = $this->objectFactory
->createObject(
1572 'allowClassName' => true,
1573 'allowCallable' => true
1580 if ( $page instanceof SpecialPage
) {
1581 $page->setHookContainer( $this->hookContainer
);
1582 $page->setContentLanguage( $this->contLang
);
1583 $page->setSpecialPageFactory( $this );
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!" );
1596 * Get listed special pages available to the current user.
1598 * This includes both unrestricted pages, and restricted pages
1599 * that the current user has the required permissions for.
1601 * @param User $user User object to check permissions provided
1602 * @return SpecialPage[]
1604 public function getUsablePages( User
$user ): array {
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 ) )
1613 $pages[$name] = $page;
1622 * Get listed special pages available to everyone by default.
1624 * @return array<string,SpecialPage>
1626 public function getRegularPages(): array {
1628 foreach ( $this->getPageList() as $name => $rec ) {
1629 $page = $this->getPage( $name );
1630 if ( $page && $page->isListed() && !$page->isRestricted() ) {
1631 $pages[$name] = $page;
1639 * Get listed special pages, including those that may require user rights.
1642 * @return array<string,SpecialPage>
1644 public function getListedPages(): array {
1646 foreach ( $this->getPageList() as $name => $rec ) {
1647 $page = $this->getPage( $name );
1648 if ( $page && $page->isListed() ) {
1649 $pages[$name] = $page;
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
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.
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)
1668 * @return bool|Title
1670 public function executePath( $path, IContextSource
$context, $including = false,
1671 ?LinkRenderer
$linkRenderer = null
1673 if ( $path instanceof PageReference
) {
1674 $path = $path->getDBkey();
1677 $bits = explode( '/', $path, 2 );
1679 $par = $bits[1] ??
null; // T4087
1681 $page = $this->getPage( $name );
1683 // Emulate SpecialPage::setHeaders()
1684 $context->getOutput()->setArticleRelated( false );
1685 $context->getOutput()->setRobotPolicy( 'noindex,nofollow' );
1687 if ( $context->getConfig()->get( MainConfigNames
::Send404Code
) ) {
1688 $context->getOutput()->setStatusCode( 404 );
1691 $context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
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__
);
1706 // Page exists, set the context
1707 $page->setContext( $context );
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
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 );
1725 // @phan-suppress-next-line PhanUndeclaredMethod
1726 $context->setTitle( $page->getPageTitle( $par ??
false ) );
1727 } elseif ( !$page->isIncludable() ) {
1731 $page->including( $including );
1732 if ( $linkRenderer ) {
1733 $page->setLinkRenderer( $linkRenderer );
1736 // Execute special page
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.
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.
1752 * @param PageReference $page
1753 * @param IContextSource $context
1754 * @param LinkRenderer|null $linkRenderer (since 1.28)
1755 * @return bool|Title
1757 public function capturePath(
1758 PageReference
$page, IContextSource
$context, ?LinkRenderer
$linkRenderer = null
1760 // phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgUser,MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgTitle
1761 global $wgTitle, $wgOut, $wgRequest, $wgUser, $wgLang;
1762 $main = RequestContext
::getMain();
1764 // Save current globals and main context
1766 'title' => $wgTitle,
1768 'request' => $wgRequest,
1770 'language' => $wgLang,
1773 'title' => $main->getTitle(),
1774 'output' => $main->getOutput(),
1775 'request' => $main->getRequest(),
1776 'user' => $main->getUser(),
1777 'language' => $main->getLanguage(),
1779 if ( $main->canUseWikiPage() ) {
1780 $ctx['wikipage'] = $main->getWikiPage();
1783 // just needed for $wgTitle and RequestContext::setTitle
1784 $title = $this->titleFactory
->castFromPageReference( $page );
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() );
1804 return $this->executePath( $page, $context, true, $linkRenderer );
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'] );
1825 * Get the local name for a specified canonical name
1827 * @param string $name
1828 * @param string|false|null $subpage
1831 public function getLocalNameFor( $name, $subpage = false ) {
1832 $aliases = $this->contLang
->getSpecialPageAliases();
1833 $aliasList = $this->getAliasList();
1835 // Find the first alias that maps back to $name
1836 if ( isset( $aliases[$name] ) ) {
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
1850 wfWarn( "Did not find a usable alias for special page '$name'. " .
1851 "It seems all defined aliases conflict?" );
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 );
1865 wfWarn( "Did not find alias for special page '$name'. " .
1866 "Perhaps no aliases are defined for it?" );
1869 if ( $subpage !== false && $subpage !== null ) {
1870 // Make sure it's in dbkey form
1871 $subpage = str_replace( ' ', '_', $subpage );
1872 $name = "$name/$subpage";
1875 return $this->contLang
->ucfirst( $name );
1879 * Get a title for a given alias
1881 * @param string $alias
1882 * @return Title|null Title or null if there is no such alias
1884 public function getTitleForAlias( $alias ) {
1885 [ $name, $subpage ] = $this->resolveAlias( $alias );
1886 if ( $name != null ) {
1887 return SpecialPage
::getTitleFor( $name, $subpage );