SpecialPageFactory::getRestrictedPages()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 19
Code Lines 13

Duplication

Lines 10
Ratio 52.63 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 6
nop 1
dl 10
loc 19
rs 8.8571
c 0
b 0
f 0
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
use MediaWiki\Linker\LinkRenderer;
25
26
/**
27
 * Factory for handling the special page list and generating SpecialPage objects.
28
 *
29
 * To add a special page in an extension, add to $wgSpecialPages either
30
 * an object instance or an array containing the name and constructor
31
 * parameters. The latter is preferred for performance reasons.
32
 *
33
 * The object instantiated must be either an instance of SpecialPage or a
34
 * sub-class thereof. It must have an execute() method, which sends the HTML
35
 * for the special page to $wgOut. The parent class has an execute() method
36
 * which distributes the call to the historical global functions. Additionally,
37
 * execute() also checks if the user has the necessary access privileges
38
 * and bails out if not.
39
 *
40
 * To add a core special page, use the similar static list in
41
 * SpecialPageFactory::$list. To remove a core static special page at runtime, use
42
 * a SpecialPage_initList hook.
43
 *
44
 * @ingroup SpecialPage
45
 * @since 1.17
46
 */
47
class SpecialPageFactory {
48
	/**
49
	 * List of special page names to the subclass of SpecialPage which handles them.
50
	 */
51
	private static $coreList = [
52
		// Maintenance Reports
53
		'BrokenRedirects' => 'BrokenRedirectsPage',
54
		'Deadendpages' => 'DeadendPagesPage',
55
		'DoubleRedirects' => 'DoubleRedirectsPage',
56
		'Longpages' => 'LongPagesPage',
57
		'Ancientpages' => 'AncientPagesPage',
58
		'Lonelypages' => 'LonelyPagesPage',
59
		'Fewestrevisions' => 'FewestrevisionsPage',
60
		'Withoutinterwiki' => 'WithoutInterwikiPage',
61
		'Protectedpages' => 'SpecialProtectedpages',
62
		'Protectedtitles' => 'SpecialProtectedtitles',
63
		'Shortpages' => 'ShortPagesPage',
64
		'Uncategorizedcategories' => 'UncategorizedCategoriesPage',
65
		'Uncategorizedimages' => 'UncategorizedImagesPage',
66
		'Uncategorizedpages' => 'UncategorizedPagesPage',
67
		'Uncategorizedtemplates' => 'UncategorizedTemplatesPage',
68
		'Unusedcategories' => 'UnusedCategoriesPage',
69
		'Unusedimages' => 'UnusedimagesPage',
70
		'Unusedtemplates' => 'UnusedtemplatesPage',
71
		'Unwatchedpages' => 'UnwatchedpagesPage',
72
		'Wantedcategories' => 'WantedCategoriesPage',
73
		'Wantedfiles' => 'WantedFilesPage',
74
		'Wantedpages' => 'WantedPagesPage',
75
		'Wantedtemplates' => 'WantedTemplatesPage',
76
77
		// List of pages
78
		'Allpages' => 'SpecialAllPages',
79
		'Prefixindex' => 'SpecialPrefixindex',
80
		'Categories' => 'SpecialCategories',
81
		'Listredirects' => 'ListredirectsPage',
82
		'PagesWithProp' => 'SpecialPagesWithProp',
83
		'TrackingCategories' => 'SpecialTrackingCategories',
84
85
		// Authentication
86
		'Userlogin' => 'SpecialUserLogin',
87
		'Userlogout' => 'SpecialUserLogout',
88
		'CreateAccount' => 'SpecialCreateAccount',
89
		'LinkAccounts' => 'SpecialLinkAccounts',
90
		'UnlinkAccounts' => 'SpecialUnlinkAccounts',
91
		'ChangeCredentials' => 'SpecialChangeCredentials',
92
		'RemoveCredentials' => 'SpecialRemoveCredentials',
93
94
		// Users and rights
95
		'Activeusers' => 'SpecialActiveUsers',
96
		'Block' => 'SpecialBlock',
97
		'Unblock' => 'SpecialUnblock',
98
		'BlockList' => 'SpecialBlockList',
99
		'ChangePassword' => 'SpecialChangePassword',
100
		'BotPasswords' => 'SpecialBotPasswords',
101
		'PasswordReset' => 'SpecialPasswordReset',
102
		'DeletedContributions' => 'DeletedContributionsPage',
103
		'Preferences' => 'SpecialPreferences',
104
		'ResetTokens' => 'SpecialResetTokens',
105
		'Contributions' => 'SpecialContributions',
106
		'Listgrouprights' => 'SpecialListGroupRights',
107
		'Listgrants' => 'SpecialListGrants',
108
		'Listusers' => 'SpecialListUsers',
109
		'Listadmins' => 'SpecialListAdmins',
110
		'Listbots' => 'SpecialListBots',
111
		'Userrights' => 'UserrightsPage',
112
		'EditWatchlist' => 'SpecialEditWatchlist',
113
114
		// Recent changes and logs
115
		'Newimages' => 'SpecialNewFiles',
116
		'Log' => 'SpecialLog',
117
		'Watchlist' => 'SpecialWatchlist',
118
		'Newpages' => 'SpecialNewpages',
119
		'Recentchanges' => 'SpecialRecentChanges',
120
		'Recentchangeslinked' => 'SpecialRecentChangesLinked',
121
		'Tags' => 'SpecialTags',
122
123
		// Media reports and uploads
124
		'Listfiles' => 'SpecialListFiles',
125
		'Filepath' => 'SpecialFilepath',
126
		'MediaStatistics' => 'MediaStatisticsPage',
127
		'MIMEsearch' => 'MIMEsearchPage',
128
		'FileDuplicateSearch' => 'FileDuplicateSearchPage',
129
		'Upload' => 'SpecialUpload',
130
		'UploadStash' => 'SpecialUploadStash',
131
		'ListDuplicatedFiles' => 'ListDuplicatedFilesPage',
132
133
		// Data and tools
134
		'ApiSandbox' => 'SpecialApiSandbox',
135
		'Statistics' => 'SpecialStatistics',
136
		'Allmessages' => 'SpecialAllMessages',
137
		'Version' => 'SpecialVersion',
138
		'Lockdb' => 'SpecialLockdb',
139
		'Unlockdb' => 'SpecialUnlockdb',
140
141
		// Redirecting special pages
142
		'LinkSearch' => 'LinkSearchPage',
143
		'Randompage' => 'RandomPage',
144
		'RandomInCategory' => 'SpecialRandomInCategory',
145
		'Randomredirect' => 'SpecialRandomredirect',
146
		'Randomrootpage' => 'SpecialRandomrootpage',
147
148
		// High use pages
149
		'Mostlinkedcategories' => 'MostlinkedCategoriesPage',
150
		'Mostimages' => 'MostimagesPage',
151
		'Mostinterwikis' => 'MostinterwikisPage',
152
		'Mostlinked' => 'MostlinkedPage',
153
		'Mostlinkedtemplates' => 'MostlinkedTemplatesPage',
154
		'Mostcategories' => 'MostcategoriesPage',
155
		'Mostrevisions' => 'MostrevisionsPage',
156
157
		// Page tools
158
		'ComparePages' => 'SpecialComparePages',
159
		'Export' => 'SpecialExport',
160
		'Import' => 'SpecialImport',
161
		'Undelete' => 'SpecialUndelete',
162
		'Whatlinkshere' => 'SpecialWhatLinksHere',
163
		'MergeHistory' => 'SpecialMergeHistory',
164
		'ExpandTemplates' => 'SpecialExpandTemplates',
165
166
		// Other
167
		'Booksources' => 'SpecialBookSources',
168
169
		// Unlisted / redirects
170
		'ApiHelp' => 'SpecialApiHelp',
171
		'Blankpage' => 'SpecialBlankpage',
172
		'Diff' => 'SpecialDiff',
173
		'EditTags' => 'SpecialEditTags',
174
		'Emailuser' => 'SpecialEmailUser',
175
		'Movepage' => 'MovePageForm',
176
		'Mycontributions' => 'SpecialMycontributions',
177
		'MyLanguage' => 'SpecialMyLanguage',
178
		'Mypage' => 'SpecialMypage',
179
		'Mytalk' => 'SpecialMytalk',
180
		'Myuploads' => 'SpecialMyuploads',
181
		'AllMyUploads' => 'SpecialAllMyUploads',
182
		'PermanentLink' => 'SpecialPermanentLink',
183
		'Redirect' => 'SpecialRedirect',
184
		'Revisiondelete' => 'SpecialRevisionDelete',
185
		'RunJobs' => 'SpecialRunJobs',
186
		'Specialpages' => 'SpecialSpecialpages',
187
	];
188
189
	private static $list;
190
	private static $aliases;
191
192
	/**
193
	 * Reset the internal list of special pages. Useful when changing $wgSpecialPages after
194
	 * the internal list has already been initialized, e.g. during testing.
195
	 */
196
	public static function resetList() {
197
		self::$list = null;
198
		self::$aliases = null;
199
	}
200
201
	/**
202
	 * Returns a list of canonical special page names.
203
	 * May be used to iterate over all registered special pages.
204
	 *
205
	 * @return string[]
206
	 */
207
	public static function getNames() {
208
		return array_keys( self::getPageList() );
209
	}
210
211
	/**
212
	 * Get the special page list as an array
213
	 *
214
	 * @deprecated since 1.24, use getNames() instead.
215
	 * @return array
216
	 */
217
	public static function getList() {
218
		wfDeprecated( __FUNCTION__, '1.24' );
219
		return self::getPageList();
220
	}
221
222
	/**
223
	 * Get the special page list as an array
224
	 *
225
	 * @return array
226
	 */
227
	private static function getPageList() {
228
		global $wgSpecialPages;
229
		global $wgDisableInternalSearch, $wgEmailAuthentication;
230
		global $wgEnableEmail, $wgEnableJavaScriptTest;
231
		global $wgPageLanguageUseDB, $wgContentHandlerUseDB;
232
233
		if ( !is_array( self::$list ) ) {
234
235
			self::$list = self::$coreList;
236
237
			if ( !$wgDisableInternalSearch ) {
238
				self::$list['Search'] = 'SpecialSearch';
239
			}
240
241
			if ( $wgEmailAuthentication ) {
242
				self::$list['Confirmemail'] = 'EmailConfirmation';
243
				self::$list['Invalidateemail'] = 'EmailInvalidation';
244
			}
245
246
			if ( $wgEnableEmail ) {
247
				self::$list['ChangeEmail'] = 'SpecialChangeEmail';
248
			}
249
250
			if ( $wgEnableJavaScriptTest ) {
251
				self::$list['JavaScriptTest'] = 'SpecialJavaScriptTest';
252
			}
253
254
			if ( $wgPageLanguageUseDB ) {
255
				self::$list['PageLanguage'] = 'SpecialPageLanguage';
256
			}
257
			if ( $wgContentHandlerUseDB ) {
258
				self::$list['ChangeContentModel'] = 'SpecialChangeContentModel';
259
			}
260
261
			// Add extension special pages
262
			self::$list = array_merge( self::$list, $wgSpecialPages );
263
264
			// This hook can be used to disable unwanted core special pages
265
			// or conditionally register special pages.
266
			Hooks::run( 'SpecialPage_initList', [ &self::$list ] );
267
268
		}
269
270
		return self::$list;
271
	}
272
273
	/**
274
	 * Initialise and return the list of special page aliases. Returns an array where
275
	 * the key is an alias, and the value is the canonical name of the special page.
276
	 * All registered special pages are guaranteed to map to themselves.
277
	 * @return array
278
	 */
279
	private static function getAliasList() {
280
		if ( is_null( self::$aliases ) ) {
281
			global $wgContLang;
282
			$aliases = $wgContLang->getSpecialPageAliases();
283
			$pageList = self::getPageList();
284
285
			self::$aliases = [];
286
			$keepAlias = [];
287
288
			// Force every canonical name to be an alias for itself.
289
			foreach ( $pageList as $name => $stuff ) {
290
				$caseFoldedAlias = $wgContLang->caseFold( $name );
291
				self::$aliases[$caseFoldedAlias] = $name;
292
				$keepAlias[$caseFoldedAlias] = 'canonical';
293
			}
294
295
			// Check for $aliases being an array since Language::getSpecialPageAliases can return null
296
			if ( is_array( $aliases ) ) {
297
				foreach ( $aliases as $realName => $aliasList ) {
298
					$aliasList = array_values( $aliasList );
299
					foreach ( $aliasList as $i => $alias ) {
300
						$caseFoldedAlias = $wgContLang->caseFold( $alias );
301
302
						if ( isset( self::$aliases[$caseFoldedAlias] ) &&
303
							$realName === self::$aliases[$caseFoldedAlias]
304
						) {
305
							// Ignore same-realName conflicts
306
							continue;
307
						}
308
309
						if ( !isset( $keepAlias[$caseFoldedAlias] ) ) {
310
							self::$aliases[$caseFoldedAlias] = $realName;
311
							if ( !$i ) {
312
								$keepAlias[$caseFoldedAlias] = 'first';
313
							}
314
						} elseif ( !$i ) {
315
							wfWarn( "First alias '$alias' for $realName conflicts with " .
316
								"{$keepAlias[$caseFoldedAlias]} alias for " .
317
								self::$aliases[$caseFoldedAlias]
318
							);
319
						}
320
					}
321
				}
322
			}
323
		}
324
325
		return self::$aliases;
326
	}
327
328
	/**
329
	 * Given a special page name with a possible subpage, return an array
330
	 * where the first element is the special page name and the second is the
331
	 * subpage.
332
	 *
333
	 * @param string $alias
334
	 * @return array Array( String, String|null ), or array( null, null ) if the page is invalid
335
	 */
336
	public static function resolveAlias( $alias ) {
337
		global $wgContLang;
338
		$bits = explode( '/', $alias, 2 );
339
340
		$caseFoldedAlias = $wgContLang->caseFold( $bits[0] );
341
		$caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
342
		$aliases = self::getAliasList();
343
		if ( isset( $aliases[$caseFoldedAlias] ) ) {
344
			$name = $aliases[$caseFoldedAlias];
345
		} else {
346
			return [ null, null ];
347
		}
348
349 View Code Duplication
		if ( !isset( $bits[1] ) ) { // bug 2087
350
			$par = null;
351
		} else {
352
			$par = $bits[1];
353
		}
354
355
		return [ $name, $par ];
356
	}
357
358
	/**
359
	 * Check if a given name exist as a special page or as a special page alias
360
	 *
361
	 * @param string $name Name of a special page
362
	 * @return bool True if a special page exists with this name
363
	 */
364
	public static function exists( $name ) {
365
		list( $title, /*...*/ ) = self::resolveAlias( $name );
366
367
		$specialPageList = self::getPageList();
368
		return isset( $specialPageList[$title] );
369
	}
370
371
	/**
372
	 * Find the object with a given name and return it (or NULL)
373
	 *
374
	 * @param string $name Special page name, may be localised and/or an alias
375
	 * @return SpecialPage|null SpecialPage object or null if the page doesn't exist
376
	 */
377
	public static function getPage( $name ) {
378
		list( $realName, /*...*/ ) = self::resolveAlias( $name );
379
380
		$specialPageList = self::getPageList();
381
382
		if ( isset( $specialPageList[$realName] ) ) {
383
			$rec = $specialPageList[$realName];
384
385
			if ( is_callable( $rec ) ) {
386
				// Use callback to instantiate the special page
387
				$page = call_user_func( $rec );
388
			} elseif ( is_string( $rec ) ) {
389
				$className = $rec;
390
				$page = new $className;
391
			} elseif ( is_array( $rec ) ) {
392
				$className = array_shift( $rec );
393
				// @deprecated, officially since 1.18, unofficially since forever
394
				wfDeprecated( "Array syntax for \$wgSpecialPages is deprecated ($className), " .
395
					"define a subclass of SpecialPage instead.", '1.18' );
396
				$page = ObjectFactory::getObjectFromSpec( [
397
					'class' => $className,
398
					'args' => $rec,
399
					'closure_expansion' => false,
400
				] );
401
			} elseif ( $rec instanceof SpecialPage ) {
402
				$page = $rec; // XXX: we should deep clone here
403
			} else {
404
				$page = null;
405
			}
406
407
			if ( $page instanceof SpecialPage ) {
408
				return $page;
409
			} else {
410
				// It's not a classname, nor a callback, nor a legacy constructor array,
411
				// nor a special page object. Give up.
412
				wfLogWarning( "Cannot instantiate special page $realName: bad spec!" );
413
				return null;
414
			}
415
416
		} else {
417
			return null;
418
		}
419
	}
420
421
	/**
422
	 * Return categorised listable special pages which are available
423
	 * for the current user, and everyone.
424
	 *
425
	 * @param User $user User object to check permissions, $wgUser will be used
426
	 *        if not provided
427
	 * @return array ( string => Specialpage )
428
	 */
429
	public static function getUsablePages( User $user = null ) {
430
		$pages = [];
431
		if ( $user === null ) {
432
			global $wgUser;
433
			$user = $wgUser;
434
		}
435
		foreach ( self::getPageList() as $name => $rec ) {
436
			$page = self::getPage( $name );
437
			if ( $page ) { // not null
438
				$page->setContext( RequestContext::getMain() );
439
				if ( $page->isListed()
440
					&& ( !$page->isRestricted() || $page->userCanExecute( $user ) )
441
				) {
442
					$pages[$name] = $page;
443
				}
444
			}
445
		}
446
447
		return $pages;
448
	}
449
450
	/**
451
	 * Return categorised listable special pages for all users
452
	 *
453
	 * @return array ( string => Specialpage )
454
	 */
455
	public static function getRegularPages() {
456
		$pages = [];
457 View Code Duplication
		foreach ( self::getPageList() as $name => $rec ) {
458
			$page = self::getPage( $name );
459
			if ( $page->isListed() && !$page->isRestricted() ) {
460
				$pages[$name] = $page;
461
			}
462
		}
463
464
		return $pages;
465
	}
466
467
	/**
468
	 * Return categorised listable special pages which are available
469
	 * for the current user, but not for everyone
470
	 *
471
	 * @param User|null $user User object to use or null for $wgUser
472
	 * @return array ( string => Specialpage )
473
	 */
474
	public static function getRestrictedPages( User $user = null ) {
475
		$pages = [];
476
		if ( $user === null ) {
477
			global $wgUser;
478
			$user = $wgUser;
479
		}
480 View Code Duplication
		foreach ( self::getPageList() as $name => $rec ) {
481
			$page = self::getPage( $name );
482
			if (
483
				$page->isListed()
484
				&& $page->isRestricted()
485
				&& $page->userCanExecute( $user )
486
			) {
487
				$pages[$name] = $page;
488
			}
489
		}
490
491
		return $pages;
492
	}
493
494
	/**
495
	 * Execute a special page path.
496
	 * The path may contain parameters, e.g. Special:Name/Params
497
	 * Extracts the special page name and call the execute method, passing the parameters
498
	 *
499
	 * Returns a title object if the page is redirected, false if there was no such special
500
	 * page, and true if it was successful.
501
	 *
502
	 * @param Title $title
503
	 * @param IContextSource $context
504
	 * @param bool $including Bool output is being captured for use in {{special:whatever}}
505
	 * @param LinkRenderer|null $linkRenderer (since 1.28)
506
	 *
507
	 * @return bool
508
	 */
509
	public static function executePath( Title &$title, IContextSource &$context, $including = false,
510
		LinkRenderer $linkRenderer = null
511
	) {
512
		// @todo FIXME: Redirects broken due to this call
513
		$bits = explode( '/', $title->getDBkey(), 2 );
514
		$name = $bits[0];
515 View Code Duplication
		if ( !isset( $bits[1] ) ) { // bug 2087
516
			$par = null;
517
		} else {
518
			$par = $bits[1];
519
		}
520
521
		$page = self::getPage( $name );
522
		if ( !$page ) {
523
			$context->getOutput()->setArticleRelated( false );
524
			$context->getOutput()->setRobotPolicy( 'noindex,nofollow' );
525
526
			global $wgSend404Code;
527
			if ( $wgSend404Code ) {
528
				$context->getOutput()->setStatusCode( 404 );
529
			}
530
531
			$context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
532
533
			return false;
534
		}
535
536
		if ( !$including ) {
537
			// Narrow DB query expectations for this HTTP request
538
			$trxLimits = $context->getConfig()->get( 'TrxProfilerLimits' );
539
			$trxProfiler = Profiler::instance()->getTransactionProfiler();
540
			if ( $context->getRequest()->wasPosted() && !$page->doesWrites() ) {
541
				$trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
542
				$context->getRequest()->markAsSafeRequest();
543
			}
544
		}
545
546
		// Page exists, set the context
547
		$page->setContext( $context );
548
549
		if ( !$including ) {
550
			// Redirect to canonical alias for GET commands
551
			// Not for POST, we'd lose the post data, so it's best to just distribute
552
			// the request. Such POST requests are possible for old extensions that
553
			// generate self-links without being aware that their default name has
554
			// changed.
555
			if ( $name != $page->getLocalName() && !$context->getRequest()->wasPosted() ) {
556
				$query = $context->getRequest()->getQueryValues();
557
				unset( $query['title'] );
558
				$title = $page->getPageTitle( $par );
559
				$url = $title->getFullURL( $query );
560
				$context->getOutput()->redirect( $url );
561
562
				return $title;
563
			} else {
564
				$context->setTitle( $page->getPageTitle( $par ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface IContextSource as the method setTitle() does only exist in the following implementations of said interface: DerivativeContext, EditWatchlistNormalHTMLForm, HTMLForm, OOUIHTMLForm, OutputPage, PreferencesForm, RequestContext, UploadForm, VFormHTMLForm.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
565
			}
566
		} elseif ( !$page->isIncludable() ) {
567
			return false;
568
		}
569
570
		$page->including( $including );
571
		if ( $linkRenderer ) {
572
			$page->setLinkRenderer( $linkRenderer );
573
		}
574
575
		// Execute special page
576
		$page->run( $par );
577
578
		return true;
579
	}
580
581
	/**
582
	 * Just like executePath() but will override global variables and execute
583
	 * the page in "inclusion" mode. Returns true if the execution was
584
	 * successful or false if there was no such special page, or a title object
585
	 * if it was a redirect.
586
	 *
587
	 * Also saves the current $wgTitle, $wgOut, $wgRequest, $wgUser and $wgLang
588
	 * variables so that the special page will get the context it'd expect on a
589
	 * normal request, and then restores them to their previous values after.
590
	 *
591
	 * @param Title $title
592
	 * @param IContextSource $context
593
	 * @param LinkRenderer|null $linkRenderer (since 1.28)
594
	 * @return string HTML fragment
595
	 */
596
	public static function capturePath(
597
		Title $title, IContextSource $context, LinkRenderer $linkRenderer = null
598
	) {
599
		global $wgTitle, $wgOut, $wgRequest, $wgUser, $wgLang;
600
		$main = RequestContext::getMain();
601
602
		// Save current globals and main context
603
		$glob = [
604
			'title' => $wgTitle,
605
			'output' => $wgOut,
606
			'request' => $wgRequest,
607
			'user' => $wgUser,
608
			'language' => $wgLang,
609
		];
610
		$ctx = [
611
			'title' => $main->getTitle(),
612
			'output' => $main->getOutput(),
613
			'request' => $main->getRequest(),
614
			'user' => $main->getUser(),
615
			'language' => $main->getLanguage(),
616
		];
617
618
		// Override
619
		$wgTitle = $title;
620
		$wgOut = $context->getOutput();
621
		$wgRequest = $context->getRequest();
622
		$wgUser = $context->getUser();
623
		$wgLang = $context->getLanguage();
624
		$main->setTitle( $title );
625
		$main->setOutput( $context->getOutput() );
626
		$main->setRequest( $context->getRequest() );
627
		$main->setUser( $context->getUser() );
628
		$main->setLanguage( $context->getLanguage() );
629
630
		// The useful part
631
		$ret = self::executePath( $title, $context, true, $linkRenderer );
632
633
		// Restore old globals and context
634
		$wgTitle = $glob['title'];
635
		$wgOut = $glob['output'];
636
		$wgRequest = $glob['request'];
637
		$wgUser = $glob['user'];
638
		$wgLang = $glob['language'];
639
		$main->setTitle( $ctx['title'] );
640
		$main->setOutput( $ctx['output'] );
641
		$main->setRequest( $ctx['request'] );
642
		$main->setUser( $ctx['user'] );
643
		$main->setLanguage( $ctx['language'] );
644
645
		return $ret;
646
	}
647
648
	/**
649
	 * Get the local name for a specified canonical name
650
	 *
651
	 * @param string $name
652
	 * @param string|bool $subpage
653
	 * @return string
654
	 */
655
	public static function getLocalNameFor( $name, $subpage = false ) {
656
		global $wgContLang;
657
		$aliases = $wgContLang->getSpecialPageAliases();
658
		$aliasList = self::getAliasList();
659
660
		// Find the first alias that maps back to $name
661
		if ( isset( $aliases[$name] ) ) {
662
			$found = false;
663
			foreach ( $aliases[$name] as $alias ) {
664
				$caseFoldedAlias = $wgContLang->caseFold( $alias );
665
				$caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
666
				if ( isset( $aliasList[$caseFoldedAlias] ) &&
667
					$aliasList[$caseFoldedAlias] === $name
668
				) {
669
					$name = $alias;
670
					$found = true;
671
					break;
672
				}
673
			}
674
			if ( !$found ) {
675
				wfWarn( "Did not find a usable alias for special page '$name'. " .
676
					"It seems all defined aliases conflict?" );
677
			}
678
		} else {
679
			// Check if someone misspelled the correct casing
680
			if ( is_array( $aliases ) ) {
681
				foreach ( $aliases as $n => $values ) {
682
					if ( strcasecmp( $name, $n ) === 0 ) {
683
						wfWarn( "Found alias defined for $n when searching for " .
684
							"special page aliases for $name. Case mismatch?" );
685
						return self::getLocalNameFor( $n, $subpage );
686
					}
687
				}
688
			}
689
690
			wfWarn( "Did not find alias for special page '$name'. " .
691
				"Perhaps no aliases are defined for it?" );
692
		}
693
694
		if ( $subpage !== false && !is_null( $subpage ) ) {
695
			// Make sure it's in dbkey form
696
			$subpage = str_replace( ' ', '_', $subpage );
697
			$name = "$name/$subpage";
698
		}
699
700
		return $wgContLang->ucfirst( $name );
701
	}
702
703
	/**
704
	 * Get a title for a given alias
705
	 *
706
	 * @param string $alias
707
	 * @return Title|null Title or null if there is no such alias
708
	 */
709
	public static function getTitleForAlias( $alias ) {
710
		list( $name, $subpage ) = self::resolveAlias( $alias );
711
		if ( $name != null ) {
712
			return SpecialPage::getTitleFor( $name, $subpage );
713
		} else {
714
			return null;
715
		}
716
	}
717
}
718