Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Title often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Title, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 36 | class Title implements LinkTarget { | 
            ||
| 37 | /** @var HashBagOStuff */  | 
            ||
| 38 | static private $titleCache = null;  | 
            ||
| 39 | |||
| 40 | /**  | 
            ||
| 41 | * Title::newFromText maintains a cache to avoid expensive re-normalization of  | 
            ||
| 42 | * commonly used titles. On a batch operation this can become a memory leak  | 
            ||
| 43 | * if not bounded. After hitting this many titles reset the cache.  | 
            ||
| 44 | */  | 
            ||
| 45 | const CACHE_MAX = 1000;  | 
            ||
| 46 | |||
| 47 | /**  | 
            ||
| 48 | * Used to be GAID_FOR_UPDATE define. Used with getArticleID() and friends  | 
            ||
| 49 | * to use the master DB  | 
            ||
| 50 | */  | 
            ||
| 51 | const GAID_FOR_UPDATE = 1;  | 
            ||
| 52 | |||
| 53 | /**  | 
            ||
| 54 | * @name Private member variables  | 
            ||
| 55 | * Please use the accessor functions instead.  | 
            ||
| 56 | * @private  | 
            ||
| 57 | */  | 
            ||
| 58 | 	// @{ | 
            ||
| 59 | |||
| 60 | /** @var string Text form (spaces not underscores) of the main part */  | 
            ||
| 61 | public $mTextform = '';  | 
            ||
| 62 | |||
| 63 | /** @var string URL-encoded form of the main part */  | 
            ||
| 64 | public $mUrlform = '';  | 
            ||
| 65 | |||
| 66 | /** @var string Main part with underscores */  | 
            ||
| 67 | public $mDbkeyform = '';  | 
            ||
| 68 | |||
| 69 | /** @var string Database key with the initial letter in the case specified by the user */  | 
            ||
| 70 | protected $mUserCaseDBKey;  | 
            ||
| 71 | |||
| 72 | /** @var int Namespace index, i.e. one of the NS_xxxx constants */  | 
            ||
| 73 | public $mNamespace = NS_MAIN;  | 
            ||
| 74 | |||
| 75 | /** @var string Interwiki prefix */  | 
            ||
| 76 | public $mInterwiki = '';  | 
            ||
| 77 | |||
| 78 | /** @var bool Was this Title created from a string with a local interwiki prefix? */  | 
            ||
| 79 | private $mLocalInterwiki = false;  | 
            ||
| 80 | |||
| 81 | /** @var string Title fragment (i.e. the bit after the #) */  | 
            ||
| 82 | public $mFragment = '';  | 
            ||
| 83 | |||
| 84 | /** @var int Article ID, fetched from the link cache on demand */  | 
            ||
| 85 | public $mArticleID = -1;  | 
            ||
| 86 | |||
| 87 | /** @var bool|int ID of most recent revision */  | 
            ||
| 88 | protected $mLatestID = false;  | 
            ||
| 89 | |||
| 90 | /**  | 
            ||
| 91 | * @var bool|string ID of the page's content model, i.e. one of the  | 
            ||
| 92 | * CONTENT_MODEL_XXX constants  | 
            ||
| 93 | */  | 
            ||
| 94 | public $mContentModel = false;  | 
            ||
| 95 | |||
| 96 | /** @var int Estimated number of revisions; null of not loaded */  | 
            ||
| 97 | private $mEstimateRevisions;  | 
            ||
| 98 | |||
| 99 | /** @var array Array of groups allowed to edit this article */  | 
            ||
| 100 | public $mRestrictions = [];  | 
            ||
| 101 | |||
| 102 | /** @var string|bool */  | 
            ||
| 103 | protected $mOldRestrictions = false;  | 
            ||
| 104 | |||
| 105 | /** @var bool Cascade restrictions on this page to included templates and images? */  | 
            ||
| 106 | public $mCascadeRestriction;  | 
            ||
| 107 | |||
| 108 | /** Caching the results of getCascadeProtectionSources */  | 
            ||
| 109 | public $mCascadingRestrictions;  | 
            ||
| 110 | |||
| 111 | /** @var array When do the restrictions on this page expire? */  | 
            ||
| 112 | protected $mRestrictionsExpiry = [];  | 
            ||
| 113 | |||
| 114 | /** @var bool Are cascading restrictions in effect on this page? */  | 
            ||
| 115 | protected $mHasCascadingRestrictions;  | 
            ||
| 116 | |||
| 117 | /** @var array Where are the cascading restrictions coming from on this page? */  | 
            ||
| 118 | public $mCascadeSources;  | 
            ||
| 119 | |||
| 120 | /** @var bool Boolean for initialisation on demand */  | 
            ||
| 121 | public $mRestrictionsLoaded = false;  | 
            ||
| 122 | |||
| 123 | /** @var string Text form including namespace/interwiki, initialised on demand */  | 
            ||
| 124 | protected $mPrefixedText = null;  | 
            ||
| 125 | |||
| 126 | /** @var mixed Cached value for getTitleProtection (create protection) */  | 
            ||
| 127 | public $mTitleProtection;  | 
            ||
| 128 | |||
| 129 | /**  | 
            ||
| 130 | * @var int Namespace index when there is no namespace. Don't change the  | 
            ||
| 131 | * following default, NS_MAIN is hardcoded in several places. See bug 696.  | 
            ||
| 132 | 	 *   Zero except in {{transclusion}} tags. | 
            ||
| 133 | */  | 
            ||
| 134 | public $mDefaultNamespace = NS_MAIN;  | 
            ||
| 135 | |||
| 136 | /** @var int The page length, 0 for special pages */  | 
            ||
| 137 | protected $mLength = -1;  | 
            ||
| 138 | |||
| 139 | /** @var null Is the article at this title a redirect? */  | 
            ||
| 140 | public $mRedirect = null;  | 
            ||
| 141 | |||
| 142 | /** @var array Associative array of user ID -> timestamp/false */  | 
            ||
| 143 | private $mNotificationTimestamp = [];  | 
            ||
| 144 | |||
| 145 | /** @var bool Whether a page has any subpages */  | 
            ||
| 146 | private $mHasSubpages;  | 
            ||
| 147 | |||
| 148 | /** @var bool The (string) language code of the page's language and content code. */  | 
            ||
| 149 | private $mPageLanguage = false;  | 
            ||
| 150 | |||
| 151 | /** @var string|bool|null The page language code from the database, null if not saved in  | 
            ||
| 152 | * the database or false if not loaded, yet. */  | 
            ||
| 153 | private $mDbPageLanguage = false;  | 
            ||
| 154 | |||
| 155 | /** @var TitleValue A corresponding TitleValue object */  | 
            ||
| 156 | private $mTitleValue = null;  | 
            ||
| 157 | |||
| 158 | /** @var bool Would deleting this page be a big deletion? */  | 
            ||
| 159 | private $mIsBigDeletion = null;  | 
            ||
| 160 | // @}  | 
            ||
| 161 | |||
| 162 | /**  | 
            ||
| 163 | * B/C kludge: provide a TitleParser for use by Title.  | 
            ||
| 164 | * Ideally, Title would have no methods that need this.  | 
            ||
| 165 | * Avoid usage of this singleton by using TitleValue  | 
            ||
| 166 | * and the associated services when possible.  | 
            ||
| 167 | *  | 
            ||
| 168 | * @return TitleFormatter  | 
            ||
| 169 | */  | 
            ||
| 170 | 	private static function getTitleFormatter() { | 
            ||
| 171 | return MediaWikiServices::getInstance()->getTitleFormatter();  | 
            ||
| 172 | }  | 
            ||
| 173 | |||
| 174 | /**  | 
            ||
| 175 | * B/C kludge: provide an InterwikiLookup for use by Title.  | 
            ||
| 176 | * Ideally, Title would have no methods that need this.  | 
            ||
| 177 | * Avoid usage of this singleton by using TitleValue  | 
            ||
| 178 | * and the associated services when possible.  | 
            ||
| 179 | *  | 
            ||
| 180 | * @return InterwikiLookup  | 
            ||
| 181 | */  | 
            ||
| 182 | 	private static function getInterwikiLookup() { | 
            ||
| 185 | |||
| 186 | /**  | 
            ||
| 187 | * @access protected  | 
            ||
| 188 | */  | 
            ||
| 189 | 	function __construct() { | 
            ||
| 190 | }  | 
            ||
| 191 | |||
| 192 | /**  | 
            ||
| 193 | * Create a new Title from a prefixed DB key  | 
            ||
| 194 | *  | 
            ||
| 195 | * @param string $key The database key, which has underscores  | 
            ||
| 196 | * instead of spaces, possibly including namespace and  | 
            ||
| 197 | * interwiki prefixes  | 
            ||
| 198 | * @return Title|null Title, or null on an error  | 
            ||
| 199 | */  | 
            ||
| 200 | 	public static function newFromDBkey( $key ) { | 
            ||
| 201 | $t = new Title();  | 
            ||
| 202 | $t->mDbkeyform = $key;  | 
            ||
| 203 | |||
| 204 | 		try { | 
            ||
| 205 | $t->secureAndSplit();  | 
            ||
| 206 | return $t;  | 
            ||
| 207 | 		} catch ( MalformedTitleException $ex ) { | 
            ||
| 208 | return null;  | 
            ||
| 209 | }  | 
            ||
| 210 | }  | 
            ||
| 211 | |||
| 212 | /**  | 
            ||
| 213 | * Create a new Title from a TitleValue  | 
            ||
| 214 | *  | 
            ||
| 215 | * @param TitleValue $titleValue Assumed to be safe.  | 
            ||
| 216 | *  | 
            ||
| 217 | * @return Title  | 
            ||
| 218 | */  | 
            ||
| 219 | 	public static function newFromTitleValue( TitleValue $titleValue ) { | 
            ||
| 220 | return self::newFromLinkTarget( $titleValue );  | 
            ||
| 221 | }  | 
            ||
| 222 | |||
| 223 | /**  | 
            ||
| 224 | * Create a new Title from a LinkTarget  | 
            ||
| 225 | *  | 
            ||
| 226 | * @param LinkTarget $linkTarget Assumed to be safe.  | 
            ||
| 227 | *  | 
            ||
| 228 | * @return Title  | 
            ||
| 229 | */  | 
            ||
| 230 | 	public static function newFromLinkTarget( LinkTarget $linkTarget ) { | 
            ||
| 231 | 		if ( $linkTarget instanceof Title ) { | 
            ||
| 232 | // Special case if it's already a Title object  | 
            ||
| 233 | return $linkTarget;  | 
            ||
| 234 | }  | 
            ||
| 235 | return self::makeTitle(  | 
            ||
| 236 | $linkTarget->getNamespace(),  | 
            ||
| 237 | $linkTarget->getText(),  | 
            ||
| 238 | $linkTarget->getFragment(),  | 
            ||
| 239 | $linkTarget->getInterwiki()  | 
            ||
| 240 | );  | 
            ||
| 241 | }  | 
            ||
| 242 | |||
| 243 | /**  | 
            ||
| 244 | * Create a new Title from text, such as what one would find in a link. De-  | 
            ||
| 245 | * codes any HTML entities in the text.  | 
            ||
| 246 | *  | 
            ||
| 247 | * @param string|int|null $text The link text; spaces, prefixes, and an  | 
            ||
| 248 | * initial ':' indicating the main namespace are accepted.  | 
            ||
| 249 | * @param int $defaultNamespace The namespace to use if none is specified  | 
            ||
| 250 | * by a prefix. If you want to force a specific namespace even if  | 
            ||
| 251 | * $text might begin with a namespace prefix, use makeTitle() or  | 
            ||
| 252 | * makeTitleSafe().  | 
            ||
| 253 | * @throws InvalidArgumentException  | 
            ||
| 254 | * @return Title|null Title or null on an error.  | 
            ||
| 255 | */  | 
            ||
| 256 | 	public static function newFromText( $text, $defaultNamespace = NS_MAIN ) { | 
            ||
| 257 | // DWIM: Integers can be passed in here when page titles are used as array keys.  | 
            ||
| 258 | 		if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) { | 
            ||
| 259 | throw new InvalidArgumentException( '$text must be a string.' );  | 
            ||
| 260 | }  | 
            ||
| 261 | 		if ( $text === null ) { | 
            ||
| 262 | return null;  | 
            ||
| 263 | }  | 
            ||
| 264 | |||
| 265 | 		try { | 
            ||
| 266 | return Title::newFromTextThrow( strval( $text ), $defaultNamespace );  | 
            ||
| 267 | 		} catch ( MalformedTitleException $ex ) { | 
            ||
| 268 | return null;  | 
            ||
| 269 | }  | 
            ||
| 270 | }  | 
            ||
| 271 | |||
| 272 | /**  | 
            ||
| 273 | * Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,  | 
            ||
| 274 | * rather than returning null.  | 
            ||
| 275 | *  | 
            ||
| 276 | * The exception subclasses encode detailed information about why the title is invalid.  | 
            ||
| 277 | *  | 
            ||
| 278 | * @see Title::newFromText  | 
            ||
| 279 | *  | 
            ||
| 280 | * @since 1.25  | 
            ||
| 281 | * @param string $text Title text to check  | 
            ||
| 282 | * @param int $defaultNamespace  | 
            ||
| 283 | * @throws MalformedTitleException If the title is invalid  | 
            ||
| 284 | * @return Title  | 
            ||
| 285 | */  | 
            ||
| 286 | 	public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) { | 
            ||
| 287 | 		if ( is_object( $text ) ) { | 
            ||
| 288 | throw new MWException( '$text must be a string, given an object' );  | 
            ||
| 289 | }  | 
            ||
| 290 | |||
| 291 | $titleCache = self::getTitleCache();  | 
            ||
| 292 | |||
| 293 | // Wiki pages often contain multiple links to the same page.  | 
            ||
| 294 | // Title normalization and parsing can become expensive on pages with many  | 
            ||
| 295 | // links, so we can save a little time by caching them.  | 
            ||
| 296 | // In theory these are value objects and won't get changed...  | 
            ||
| 297 | 		if ( $defaultNamespace == NS_MAIN ) { | 
            ||
| 298 | $t = $titleCache->get( $text );  | 
            ||
| 299 | 			if ( $t ) { | 
            ||
| 300 | return $t;  | 
            ||
| 301 | }  | 
            ||
| 302 | }  | 
            ||
| 303 | |||
| 304 | // Convert things like é ā or 〗 into normalized (bug 14952) text  | 
            ||
| 305 | $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );  | 
            ||
| 306 | |||
| 307 | $t = new Title();  | 
            ||
| 308 | $t->mDbkeyform = strtr( $filteredText, ' ', '_' );  | 
            ||
| 309 | $t->mDefaultNamespace = intval( $defaultNamespace );  | 
            ||
| 310 | |||
| 311 | $t->secureAndSplit();  | 
            ||
| 312 | 		if ( $defaultNamespace == NS_MAIN ) { | 
            ||
| 313 | $titleCache->set( $text, $t );  | 
            ||
| 314 | }  | 
            ||
| 315 | return $t;  | 
            ||
| 316 | }  | 
            ||
| 317 | |||
| 318 | /**  | 
            ||
| 319 | * THIS IS NOT THE FUNCTION YOU WANT. Use Title::newFromText().  | 
            ||
| 320 | *  | 
            ||
| 321 | * Example of wrong and broken code:  | 
            ||
| 322 | * $title = Title::newFromURL( $wgRequest->getVal( 'title' ) );  | 
            ||
| 323 | *  | 
            ||
| 324 | * Example of right code:  | 
            ||
| 325 | * $title = Title::newFromText( $wgRequest->getVal( 'title' ) );  | 
            ||
| 326 | *  | 
            ||
| 327 | * Create a new Title from URL-encoded text. Ensures that  | 
            ||
| 328 | * the given title's length does not exceed the maximum.  | 
            ||
| 329 | *  | 
            ||
| 330 | * @param string $url The title, as might be taken from a URL  | 
            ||
| 331 | * @return Title|null The new object, or null on an error  | 
            ||
| 332 | */  | 
            ||
| 333 | 	public static function newFromURL( $url ) { | 
            ||
| 334 | $t = new Title();  | 
            ||
| 335 | |||
| 336 | # For compatibility with old buggy URLs. "+" is usually not valid in titles,  | 
            ||
| 337 | # but some URLs used it as a space replacement and they still come  | 
            ||
| 338 | # from some external search tools.  | 
            ||
| 339 | 		if ( strpos( self::legalChars(), '+' ) === false ) { | 
            ||
| 340 | $url = strtr( $url, '+', ' ' );  | 
            ||
| 341 | }  | 
            ||
| 342 | |||
| 343 | $t->mDbkeyform = strtr( $url, ' ', '_' );  | 
            ||
| 344 | |||
| 345 | 		try { | 
            ||
| 346 | $t->secureAndSplit();  | 
            ||
| 347 | return $t;  | 
            ||
| 348 | 		} catch ( MalformedTitleException $ex ) { | 
            ||
| 349 | return null;  | 
            ||
| 350 | }  | 
            ||
| 351 | }  | 
            ||
| 352 | |||
| 353 | /**  | 
            ||
| 354 | * @return HashBagOStuff  | 
            ||
| 355 | */  | 
            ||
| 356 | 	private static function getTitleCache() { | 
            ||
| 357 | 		if ( self::$titleCache == null ) { | 
            ||
| 358 | self::$titleCache = new HashBagOStuff( [ 'maxKeys' => self::CACHE_MAX ] );  | 
            ||
| 359 | }  | 
            ||
| 360 | return self::$titleCache;  | 
            ||
| 361 | }  | 
            ||
| 362 | |||
| 363 | /**  | 
            ||
| 364 | * Returns a list of fields that are to be selected for initializing Title  | 
            ||
| 365 | * objects or LinkCache entries. Uses $wgContentHandlerUseDB to determine  | 
            ||
| 366 | * whether to include page_content_model.  | 
            ||
| 367 | *  | 
            ||
| 368 | * @return array  | 
            ||
| 369 | */  | 
            ||
| 370 | View Code Duplication | 	protected static function getSelectFields() { | 
            |
| 371 | global $wgContentHandlerUseDB, $wgPageLanguageUseDB;  | 
            ||
| 372 | |||
| 373 | $fields = [  | 
            ||
| 374 | 'page_namespace', 'page_title', 'page_id',  | 
            ||
| 375 | 'page_len', 'page_is_redirect', 'page_latest',  | 
            ||
| 376 | ];  | 
            ||
| 377 | |||
| 378 | 		if ( $wgContentHandlerUseDB ) { | 
            ||
| 379 | $fields[] = 'page_content_model';  | 
            ||
| 380 | }  | 
            ||
| 381 | |||
| 382 | 		if ( $wgPageLanguageUseDB ) { | 
            ||
| 383 | $fields[] = 'page_lang';  | 
            ||
| 384 | }  | 
            ||
| 385 | |||
| 386 | return $fields;  | 
            ||
| 387 | }  | 
            ||
| 388 | |||
| 389 | /**  | 
            ||
| 390 | * Create a new Title from an article ID  | 
            ||
| 391 | *  | 
            ||
| 392 | * @param int $id The page_id corresponding to the Title to create  | 
            ||
| 393 | * @param int $flags Use Title::GAID_FOR_UPDATE to use master  | 
            ||
| 394 | * @return Title|null The new object, or null on an error  | 
            ||
| 395 | */  | 
            ||
| 396 | 	public static function newFromID( $id, $flags = 0 ) { | 
            ||
| 397 | $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );  | 
            ||
| 398 | $row = $db->selectRow(  | 
            ||
| 399 | 'page',  | 
            ||
| 400 | self::getSelectFields(),  | 
            ||
| 401 | [ 'page_id' => $id ],  | 
            ||
| 402 | __METHOD__  | 
            ||
| 403 | );  | 
            ||
| 404 | 		if ( $row !== false ) { | 
            ||
| 405 | $title = Title::newFromRow( $row );  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 406 | 		} else { | 
            ||
| 407 | $title = null;  | 
            ||
| 408 | }  | 
            ||
| 409 | return $title;  | 
            ||
| 410 | }  | 
            ||
| 411 | |||
| 412 | /**  | 
            ||
| 413 | * Make an array of titles from an array of IDs  | 
            ||
| 414 | *  | 
            ||
| 415 | * @param int[] $ids Array of IDs  | 
            ||
| 416 | * @return Title[] Array of Titles  | 
            ||
| 417 | */  | 
            ||
| 418 | 	public static function newFromIDs( $ids ) { | 
            ||
| 419 | 		if ( !count( $ids ) ) { | 
            ||
| 420 | return [];  | 
            ||
| 421 | }  | 
            ||
| 422 | $dbr = wfGetDB( DB_SLAVE );  | 
            ||
| 423 | |||
| 424 | $res = $dbr->select(  | 
            ||
| 425 | 'page',  | 
            ||
| 426 | self::getSelectFields(),  | 
            ||
| 427 | [ 'page_id' => $ids ],  | 
            ||
| 428 | __METHOD__  | 
            ||
| 429 | );  | 
            ||
| 430 | |||
| 431 | $titles = [];  | 
            ||
| 432 | 		foreach ( $res as $row ) { | 
            ||
| 433 | $titles[] = Title::newFromRow( $row );  | 
            ||
| 434 | }  | 
            ||
| 435 | return $titles;  | 
            ||
| 436 | }  | 
            ||
| 437 | |||
| 438 | /**  | 
            ||
| 439 | * Make a Title object from a DB row  | 
            ||
| 440 | *  | 
            ||
| 441 | * @param stdClass $row Object database row (needs at least page_title,page_namespace)  | 
            ||
| 442 | * @return Title Corresponding Title  | 
            ||
| 443 | */  | 
            ||
| 444 | 	public static function newFromRow( $row ) { | 
            ||
| 445 | $t = self::makeTitle( $row->page_namespace, $row->page_title );  | 
            ||
| 446 | $t->loadFromRow( $row );  | 
            ||
| 447 | return $t;  | 
            ||
| 448 | }  | 
            ||
| 449 | |||
| 450 | /**  | 
            ||
| 451 | * Load Title object fields from a DB row.  | 
            ||
| 452 | * If false is given, the title will be treated as non-existing.  | 
            ||
| 453 | *  | 
            ||
| 454 | * @param stdClass|bool $row Database row  | 
            ||
| 455 | */  | 
            ||
| 456 | 	public function loadFromRow( $row ) { | 
            ||
| 457 | 		if ( $row ) { // page found | 
            ||
| 458 | 			if ( isset( $row->page_id ) ) { | 
            ||
| 459 | $this->mArticleID = (int)$row->page_id;  | 
            ||
| 460 | }  | 
            ||
| 461 | 			if ( isset( $row->page_len ) ) { | 
            ||
| 462 | $this->mLength = (int)$row->page_len;  | 
            ||
| 463 | }  | 
            ||
| 464 | 			if ( isset( $row->page_is_redirect ) ) { | 
            ||
| 465 | $this->mRedirect = (bool)$row->page_is_redirect;  | 
            ||
| 466 | }  | 
            ||
| 467 | 			if ( isset( $row->page_latest ) ) { | 
            ||
| 468 | $this->mLatestID = (int)$row->page_latest;  | 
            ||
| 469 | }  | 
            ||
| 470 | 			if ( isset( $row->page_content_model ) ) { | 
            ||
| 471 | $this->mContentModel = strval( $row->page_content_model );  | 
            ||
| 472 | 			} else { | 
            ||
| 473 | $this->mContentModel = false; # initialized lazily in getContentModel()  | 
            ||
| 474 | }  | 
            ||
| 475 | 			if ( isset( $row->page_lang ) ) { | 
            ||
| 476 | $this->mDbPageLanguage = (string)$row->page_lang;  | 
            ||
| 477 | }  | 
            ||
| 478 | 			if ( isset( $row->page_restrictions ) ) { | 
            ||
| 479 | $this->mOldRestrictions = $row->page_restrictions;  | 
            ||
| 480 | }  | 
            ||
| 481 | 		} else { // page not found | 
            ||
| 482 | $this->mArticleID = 0;  | 
            ||
| 483 | $this->mLength = 0;  | 
            ||
| 484 | $this->mRedirect = false;  | 
            ||
| 485 | $this->mLatestID = 0;  | 
            ||
| 486 | $this->mContentModel = false; # initialized lazily in getContentModel()  | 
            ||
| 487 | }  | 
            ||
| 488 | }  | 
            ||
| 489 | |||
| 490 | /**  | 
            ||
| 491 | * Create a new Title from a namespace index and a DB key.  | 
            ||
| 492 | * It's assumed that $ns and $title are *valid*, for instance when  | 
            ||
| 493 | * they came directly from the database or a special page name.  | 
            ||
| 494 | * For convenience, spaces are converted to underscores so that  | 
            ||
| 495 | * eg user_text fields can be used directly.  | 
            ||
| 496 | *  | 
            ||
| 497 | * @param int $ns The namespace of the article  | 
            ||
| 498 | * @param string $title The unprefixed database key form  | 
            ||
| 499 | * @param string $fragment The link fragment (after the "#")  | 
            ||
| 500 | * @param string $interwiki The interwiki prefix  | 
            ||
| 501 | * @return Title The new object  | 
            ||
| 502 | */  | 
            ||
| 503 | 	public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) { | 
            ||
| 504 | $t = new Title();  | 
            ||
| 505 | $t->mInterwiki = $interwiki;  | 
            ||
| 506 | $t->mFragment = $fragment;  | 
            ||
| 507 | $t->mNamespace = $ns = intval( $ns );  | 
            ||
| 508 | $t->mDbkeyform = strtr( $title, ' ', '_' );  | 
            ||
| 509 | $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;  | 
            ||
| 510 | $t->mUrlform = wfUrlencode( $t->mDbkeyform );  | 
            ||
| 511 | $t->mTextform = strtr( $title, '_', ' ' );  | 
            ||
| 512 | $t->mContentModel = false; # initialized lazily in getContentModel()  | 
            ||
| 513 | return $t;  | 
            ||
| 514 | }  | 
            ||
| 515 | |||
| 516 | /**  | 
            ||
| 517 | * Create a new Title from a namespace index and a DB key.  | 
            ||
| 518 | * The parameters will be checked for validity, which is a bit slower  | 
            ||
| 519 | * than makeTitle() but safer for user-provided data.  | 
            ||
| 520 | *  | 
            ||
| 521 | * @param int $ns The namespace of the article  | 
            ||
| 522 | * @param string $title Database key form  | 
            ||
| 523 | * @param string $fragment The link fragment (after the "#")  | 
            ||
| 524 | * @param string $interwiki Interwiki prefix  | 
            ||
| 525 | * @return Title|null The new object, or null on an error  | 
            ||
| 526 | */  | 
            ||
| 527 | 	public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) { | 
            ||
| 528 | 		if ( !MWNamespace::exists( $ns ) ) { | 
            ||
| 529 | return null;  | 
            ||
| 530 | }  | 
            ||
| 531 | |||
| 532 | $t = new Title();  | 
            ||
| 533 | $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );  | 
            ||
| 534 | |||
| 535 | 		try { | 
            ||
| 536 | $t->secureAndSplit();  | 
            ||
| 537 | return $t;  | 
            ||
| 538 | 		} catch ( MalformedTitleException $ex ) { | 
            ||
| 539 | return null;  | 
            ||
| 540 | }  | 
            ||
| 541 | }  | 
            ||
| 542 | |||
| 543 | /**  | 
            ||
| 544 | * Create a new Title for the Main Page  | 
            ||
| 545 | *  | 
            ||
| 546 | * @return Title The new object  | 
            ||
| 547 | */  | 
            ||
| 548 | 	public static function newMainPage() { | 
            ||
| 549 | $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );  | 
            ||
| 550 | // Don't give fatal errors if the message is broken  | 
            ||
| 551 | 		if ( !$title ) { | 
            ||
| 552 | $title = Title::newFromText( 'Main Page' );  | 
            ||
| 553 | }  | 
            ||
| 554 | return $title;  | 
            ||
| 555 | }  | 
            ||
| 556 | |||
| 557 | /**  | 
            ||
| 558 | * Get the prefixed DB key associated with an ID  | 
            ||
| 559 | *  | 
            ||
| 560 | * @param int $id The page_id of the article  | 
            ||
| 561 | * @return Title|null An object representing the article, or null if no such article was found  | 
            ||
| 562 | */  | 
            ||
| 563 | 	public static function nameOf( $id ) { | 
            ||
| 564 | $dbr = wfGetDB( DB_SLAVE );  | 
            ||
| 565 | |||
| 566 | $s = $dbr->selectRow(  | 
            ||
| 567 | 'page',  | 
            ||
| 568 | [ 'page_namespace', 'page_title' ],  | 
            ||
| 569 | [ 'page_id' => $id ],  | 
            ||
| 570 | __METHOD__  | 
            ||
| 571 | );  | 
            ||
| 572 | 		if ( $s === false ) { | 
            ||
| 573 | return null;  | 
            ||
| 574 | }  | 
            ||
| 575 | |||
| 576 | $n = self::makeName( $s->page_namespace, $s->page_title );  | 
            ||
| 577 | return $n;  | 
            ||
| 578 | }  | 
            ||
| 579 | |||
| 580 | /**  | 
            ||
| 581 | * Get a regex character class describing the legal characters in a link  | 
            ||
| 582 | *  | 
            ||
| 583 | * @return string The list of characters, not delimited  | 
            ||
| 584 | */  | 
            ||
| 585 | 	public static function legalChars() { | 
            ||
| 586 | global $wgLegalTitleChars;  | 
            ||
| 587 | return $wgLegalTitleChars;  | 
            ||
| 588 | }  | 
            ||
| 589 | |||
| 590 | /**  | 
            ||
| 591 | * Returns a simple regex that will match on characters and sequences invalid in titles.  | 
            ||
| 592 | * Note that this doesn't pick up many things that could be wrong with titles, but that  | 
            ||
| 593 | * replacing this regex with something valid will make many titles valid.  | 
            ||
| 594 | *  | 
            ||
| 595 | * @deprecated since 1.25, use MediaWikiTitleCodec::getTitleInvalidRegex() instead  | 
            ||
| 596 | *  | 
            ||
| 597 | * @return string Regex string  | 
            ||
| 598 | */  | 
            ||
| 599 | 	static function getTitleInvalidRegex() { | 
            ||
| 600 | wfDeprecated( __METHOD__, '1.25' );  | 
            ||
| 601 | return MediaWikiTitleCodec::getTitleInvalidRegex();  | 
            ||
| 602 | }  | 
            ||
| 603 | |||
| 604 | /**  | 
            ||
| 605 | * Utility method for converting a character sequence from bytes to Unicode.  | 
            ||
| 606 | *  | 
            ||
| 607 | * Primary usecase being converting $wgLegalTitleChars to a sequence usable in  | 
            ||
| 608 | * javascript, as PHP uses UTF-8 bytes where javascript uses Unicode code units.  | 
            ||
| 609 | *  | 
            ||
| 610 | * @param string $byteClass  | 
            ||
| 611 | * @return string  | 
            ||
| 612 | */  | 
            ||
| 613 | 	public static function convertByteClassToUnicodeClass( $byteClass ) { | 
            ||
| 614 | $length = strlen( $byteClass );  | 
            ||
| 615 | // Input token queue  | 
            ||
| 616 | $x0 = $x1 = $x2 = '';  | 
            ||
| 617 | // Decoded queue  | 
            ||
| 618 | $d0 = $d1 = $d2 = '';  | 
            ||
| 619 | // Decoded integer codepoints  | 
            ||
| 620 | $ord0 = $ord1 = $ord2 = 0;  | 
            ||
| 621 | // Re-encoded queue  | 
            ||
| 622 | $r0 = $r1 = $r2 = '';  | 
            ||
| 623 | // Output  | 
            ||
| 624 | $out = '';  | 
            ||
| 625 | // Flags  | 
            ||
| 626 | $allowUnicode = false;  | 
            ||
| 627 | 		for ( $pos = 0; $pos < $length; $pos++ ) { | 
            ||
| 628 | // Shift the queues down  | 
            ||
| 629 | $x2 = $x1;  | 
            ||
| 630 | $x1 = $x0;  | 
            ||
| 631 | $d2 = $d1;  | 
            ||
| 632 | $d1 = $d0;  | 
            ||
| 633 | $ord2 = $ord1;  | 
            ||
| 634 | $ord1 = $ord0;  | 
            ||
| 635 | $r2 = $r1;  | 
            ||
| 636 | $r1 = $r0;  | 
            ||
| 637 | // Load the current input token and decoded values  | 
            ||
| 638 | $inChar = $byteClass[$pos];  | 
            ||
| 639 | 			if ( $inChar == '\\' ) { | 
            ||
| 640 | 				if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) { | 
            ||
| 641 | $x0 = $inChar . $m[0];  | 
            ||
| 642 | $d0 = chr( hexdec( $m[1] ) );  | 
            ||
| 643 | $pos += strlen( $m[0] );  | 
            ||
| 644 | 				} elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) { | 
            ||
| 645 | $x0 = $inChar . $m[0];  | 
            ||
| 646 | $d0 = chr( octdec( $m[0] ) );  | 
            ||
| 647 | $pos += strlen( $m[0] );  | 
            ||
| 648 | 				} elseif ( $pos + 1 >= $length ) { | 
            ||
| 649 | $x0 = $d0 = '\\';  | 
            ||
| 650 | 				} else { | 
            ||
| 651 | $d0 = $byteClass[$pos + 1];  | 
            ||
| 652 | $x0 = $inChar . $d0;  | 
            ||
| 653 | $pos += 1;  | 
            ||
| 654 | }  | 
            ||
| 655 | 			} else { | 
            ||
| 656 | $x0 = $d0 = $inChar;  | 
            ||
| 657 | }  | 
            ||
| 658 | $ord0 = ord( $d0 );  | 
            ||
| 659 | // Load the current re-encoded value  | 
            ||
| 660 | 			if ( $ord0 < 32 || $ord0 == 0x7f ) { | 
            ||
| 661 | $r0 = sprintf( '\x%02x', $ord0 );  | 
            ||
| 662 | 			} elseif ( $ord0 >= 0x80 ) { | 
            ||
| 663 | // Allow unicode if a single high-bit character appears  | 
            ||
| 664 | $r0 = sprintf( '\x%02x', $ord0 );  | 
            ||
| 665 | $allowUnicode = true;  | 
            ||
| 666 | 			} elseif ( strpos( '-\\[]^', $d0 ) !== false ) { | 
            ||
| 667 | $r0 = '\\' . $d0;  | 
            ||
| 668 | 			} else { | 
            ||
| 669 | $r0 = $d0;  | 
            ||
| 670 | }  | 
            ||
| 671 | // Do the output  | 
            ||
| 672 | 			if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) { | 
            ||
| 673 | // Range  | 
            ||
| 674 | 				if ( $ord2 > $ord0 ) { | 
            ||
| 675 | // Empty range  | 
            ||
| 676 | 				} elseif ( $ord0 >= 0x80 ) { | 
            ||
| 677 | // Unicode range  | 
            ||
| 678 | $allowUnicode = true;  | 
            ||
| 679 | 					if ( $ord2 < 0x80 ) { | 
            ||
| 680 | // Keep the non-unicode section of the range  | 
            ||
| 681 | $out .= "$r2-\\x7F";  | 
            ||
| 682 | }  | 
            ||
| 683 | 				} else { | 
            ||
| 684 | // Normal range  | 
            ||
| 685 | $out .= "$r2-$r0";  | 
            ||
| 686 | }  | 
            ||
| 687 | // Reset state to the initial value  | 
            ||
| 688 | $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';  | 
            ||
| 689 | 			} elseif ( $ord2 < 0x80 ) { | 
            ||
| 690 | // ASCII character  | 
            ||
| 691 | $out .= $r2;  | 
            ||
| 692 | }  | 
            ||
| 693 | }  | 
            ||
| 694 | 		if ( $ord1 < 0x80 ) { | 
            ||
| 695 | $out .= $r1;  | 
            ||
| 696 | }  | 
            ||
| 697 | 		if ( $ord0 < 0x80 ) { | 
            ||
| 698 | $out .= $r0;  | 
            ||
| 699 | }  | 
            ||
| 700 | 		if ( $allowUnicode ) { | 
            ||
| 701 | $out .= '\u0080-\uFFFF';  | 
            ||
| 702 | }  | 
            ||
| 703 | return $out;  | 
            ||
| 704 | }  | 
            ||
| 705 | |||
| 706 | /**  | 
            ||
| 707 | * Make a prefixed DB key from a DB key and a namespace index  | 
            ||
| 708 | *  | 
            ||
| 709 | * @param int $ns Numerical representation of the namespace  | 
            ||
| 710 | * @param string $title The DB key form the title  | 
            ||
| 711 | * @param string $fragment The link fragment (after the "#")  | 
            ||
| 712 | * @param string $interwiki The interwiki prefix  | 
            ||
| 713 | * @param bool $canonicalNamespace If true, use the canonical name for  | 
            ||
| 714 | * $ns instead of the localized version.  | 
            ||
| 715 | * @return string The prefixed form of the title  | 
            ||
| 716 | */  | 
            ||
| 717 | public static function makeName( $ns, $title, $fragment = '', $interwiki = '',  | 
            ||
| 718 | $canonicalNamespace = false  | 
            ||
| 719 | 	) { | 
            ||
| 720 | global $wgContLang;  | 
            ||
| 721 | |||
| 722 | 		if ( $canonicalNamespace ) { | 
            ||
| 723 | $namespace = MWNamespace::getCanonicalName( $ns );  | 
            ||
| 724 | 		} else { | 
            ||
| 725 | $namespace = $wgContLang->getNsText( $ns );  | 
            ||
| 726 | }  | 
            ||
| 727 | $name = $namespace == '' ? $title : "$namespace:$title";  | 
            ||
| 728 | 		if ( strval( $interwiki ) != '' ) { | 
            ||
| 729 | $name = "$interwiki:$name";  | 
            ||
| 730 | }  | 
            ||
| 731 | 		if ( strval( $fragment ) != '' ) { | 
            ||
| 732 | $name .= '#' . $fragment;  | 
            ||
| 733 | }  | 
            ||
| 734 | return $name;  | 
            ||
| 735 | }  | 
            ||
| 736 | |||
| 737 | /**  | 
            ||
| 738 | * Escape a text fragment, say from a link, for a URL  | 
            ||
| 739 | *  | 
            ||
| 740 | * @param string $fragment Containing a URL or link fragment (after the "#")  | 
            ||
| 741 | * @return string Escaped string  | 
            ||
| 742 | */  | 
            ||
| 743 | 	static function escapeFragmentForURL( $fragment ) { | 
            ||
| 744 | # Note that we don't urlencode the fragment. urlencoded Unicode  | 
            ||
| 745 | # fragments appear not to work in IE (at least up to 7) or in at least  | 
            ||
| 746 | # one version of Opera 9.x. The W3C validator, for one, doesn't seem  | 
            ||
| 747 | # to care if they aren't encoded.  | 
            ||
| 748 | return Sanitizer::escapeId( $fragment, 'noninitial' );  | 
            ||
| 749 | }  | 
            ||
| 750 | |||
| 751 | /**  | 
            ||
| 752 | * Callback for usort() to do title sorts by (namespace, title)  | 
            ||
| 753 | *  | 
            ||
| 754 | * @param Title $a  | 
            ||
| 755 | * @param Title $b  | 
            ||
| 756 | *  | 
            ||
| 757 | * @return int Result of string comparison, or namespace comparison  | 
            ||
| 758 | */  | 
            ||
| 759 | 	public static function compare( $a, $b ) { | 
            ||
| 766 | |||
| 767 | /**  | 
            ||
| 768 | * Determine whether the object refers to a page within  | 
            ||
| 769 | * this project (either this wiki or a wiki with a local  | 
            ||
| 770 | * interwiki, see https://www.mediawiki.org/wiki/Manual:Interwiki_table#iw_local )  | 
            ||
| 771 | *  | 
            ||
| 772 | * @return bool True if this is an in-project interwiki link or a wikilink, false otherwise  | 
            ||
| 773 | */  | 
            ||
| 774 | 	public function isLocal() { | 
            ||
| 783 | |||
| 784 | /**  | 
            ||
| 785 | * Is this Title interwiki?  | 
            ||
| 786 | *  | 
            ||
| 787 | * @return bool  | 
            ||
| 788 | */  | 
            ||
| 789 | 	public function isExternal() { | 
            ||
| 790 | return $this->mInterwiki !== '';  | 
            ||
| 791 | }  | 
            ||
| 792 | |||
| 793 | /**  | 
            ||
| 794 | * Get the interwiki prefix  | 
            ||
| 795 | *  | 
            ||
| 796 | * Use Title::isExternal to check if a interwiki is set  | 
            ||
| 797 | *  | 
            ||
| 798 | * @return string Interwiki prefix  | 
            ||
| 799 | */  | 
            ||
| 800 | 	public function getInterwiki() { | 
            ||
| 801 | return $this->mInterwiki;  | 
            ||
| 802 | }  | 
            ||
| 803 | |||
| 804 | /**  | 
            ||
| 805 | * Was this a local interwiki link?  | 
            ||
| 806 | *  | 
            ||
| 807 | * @return bool  | 
            ||
| 808 | */  | 
            ||
| 809 | 	public function wasLocalInterwiki() { | 
            ||
| 812 | |||
| 813 | /**  | 
            ||
| 814 | * Determine whether the object refers to a page within  | 
            ||
| 815 | * this project and is transcludable.  | 
            ||
| 816 | *  | 
            ||
| 817 | * @return bool True if this is transcludable  | 
            ||
| 818 | */  | 
            ||
| 819 | 	public function isTrans() { | 
            ||
| 820 | 		if ( !$this->isExternal() ) { | 
            ||
| 821 | return false;  | 
            ||
| 822 | }  | 
            ||
| 823 | |||
| 824 | return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();  | 
            ||
| 825 | }  | 
            ||
| 826 | |||
| 827 | /**  | 
            ||
| 828 | * Returns the DB name of the distant wiki which owns the object.  | 
            ||
| 829 | *  | 
            ||
| 830 | * @return string The DB name  | 
            ||
| 831 | */  | 
            ||
| 832 | 	public function getTransWikiID() { | 
            ||
| 839 | |||
| 840 | /**  | 
            ||
| 841 | * Get a TitleValue object representing this Title.  | 
            ||
| 842 | *  | 
            ||
| 843 | * @note Not all valid Titles have a corresponding valid TitleValue  | 
            ||
| 844 | * (e.g. TitleValues cannot represent page-local links that have a  | 
            ||
| 845 | * fragment but no title text).  | 
            ||
| 846 | *  | 
            ||
| 847 | * @return TitleValue|null  | 
            ||
| 848 | */  | 
            ||
| 849 | 	public function getTitleValue() { | 
            ||
| 850 | 		if ( $this->mTitleValue === null ) { | 
            ||
| 851 | 			try { | 
            ||
| 852 | $this->mTitleValue = new TitleValue(  | 
            ||
| 853 | $this->getNamespace(),  | 
            ||
| 854 | $this->getDBkey(),  | 
            ||
| 855 | $this->getFragment(),  | 
            ||
| 856 | $this->getInterwiki()  | 
            ||
| 857 | );  | 
            ||
| 858 | 			} catch ( InvalidArgumentException $ex ) { | 
            ||
| 859 | wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .  | 
            ||
| 860 | $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );  | 
            ||
| 861 | }  | 
            ||
| 862 | }  | 
            ||
| 863 | |||
| 864 | return $this->mTitleValue;  | 
            ||
| 865 | }  | 
            ||
| 866 | |||
| 867 | /**  | 
            ||
| 868 | * Get the text form (spaces not underscores) of the main part  | 
            ||
| 869 | *  | 
            ||
| 870 | * @return string Main part of the title  | 
            ||
| 871 | */  | 
            ||
| 872 | 	public function getText() { | 
            ||
| 873 | return $this->mTextform;  | 
            ||
| 874 | }  | 
            ||
| 875 | |||
| 876 | /**  | 
            ||
| 877 | * Get the URL-encoded form of the main part  | 
            ||
| 878 | *  | 
            ||
| 879 | * @return string Main part of the title, URL-encoded  | 
            ||
| 880 | */  | 
            ||
| 881 | 	public function getPartialURL() { | 
            ||
| 882 | return $this->mUrlform;  | 
            ||
| 883 | }  | 
            ||
| 884 | |||
| 885 | /**  | 
            ||
| 886 | * Get the main part with underscores  | 
            ||
| 887 | *  | 
            ||
| 888 | * @return string Main part of the title, with underscores  | 
            ||
| 889 | */  | 
            ||
| 890 | 	public function getDBkey() { | 
            ||
| 891 | return $this->mDbkeyform;  | 
            ||
| 892 | }  | 
            ||
| 893 | |||
| 894 | /**  | 
            ||
| 895 | * Get the DB key with the initial letter case as specified by the user  | 
            ||
| 896 | *  | 
            ||
| 897 | * @return string DB key  | 
            ||
| 898 | */  | 
            ||
| 899 | 	function getUserCaseDBKey() { | 
            ||
| 900 | 		if ( !is_null( $this->mUserCaseDBKey ) ) { | 
            ||
| 901 | return $this->mUserCaseDBKey;  | 
            ||
| 902 | 		} else { | 
            ||
| 903 | // If created via makeTitle(), $this->mUserCaseDBKey is not set.  | 
            ||
| 904 | return $this->mDbkeyform;  | 
            ||
| 905 | }  | 
            ||
| 906 | }  | 
            ||
| 907 | |||
| 908 | /**  | 
            ||
| 909 | * Get the namespace index, i.e. one of the NS_xxxx constants.  | 
            ||
| 910 | *  | 
            ||
| 911 | * @return int Namespace index  | 
            ||
| 912 | */  | 
            ||
| 913 | 	public function getNamespace() { | 
            ||
| 914 | return $this->mNamespace;  | 
            ||
| 915 | }  | 
            ||
| 916 | |||
| 917 | /**  | 
            ||
| 918 | * Get the page's content model id, see the CONTENT_MODEL_XXX constants.  | 
            ||
| 919 | *  | 
            ||
| 920 | * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update  | 
            ||
| 921 | * @return string Content model id  | 
            ||
| 922 | */  | 
            ||
| 923 | 	public function getContentModel( $flags = 0 ) { | 
            ||
| 938 | |||
| 939 | /**  | 
            ||
| 940 | * Convenience method for checking a title's content model name  | 
            ||
| 941 | *  | 
            ||
| 942 | * @param string $id The content model ID (use the CONTENT_MODEL_XXX constants).  | 
            ||
| 943 | * @return bool True if $this->getContentModel() == $id  | 
            ||
| 944 | */  | 
            ||
| 945 | 	public function hasContentModel( $id ) { | 
            ||
| 946 | return $this->getContentModel() == $id;  | 
            ||
| 947 | }  | 
            ||
| 948 | |||
| 949 | /**  | 
            ||
| 950 | * Get the namespace text  | 
            ||
| 951 | *  | 
            ||
| 952 | * @return string Namespace text  | 
            ||
| 953 | */  | 
            ||
| 954 | 	public function getNsText() { | 
            ||
| 955 | 		if ( $this->isExternal() ) { | 
            ||
| 956 | // This probably shouldn't even happen,  | 
            ||
| 957 | // but for interwiki transclusion it sometimes does.  | 
            ||
| 958 | // Use the canonical namespaces if possible to try to  | 
            ||
| 959 | // resolve a foreign namespace.  | 
            ||
| 960 | 			if ( MWNamespace::exists( $this->mNamespace ) ) { | 
            ||
| 961 | return MWNamespace::getCanonicalName( $this->mNamespace );  | 
            ||
| 962 | }  | 
            ||
| 963 | }  | 
            ||
| 964 | |||
| 965 | 		try { | 
            ||
| 966 | $formatter = self::getTitleFormatter();  | 
            ||
| 967 | return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );  | 
            ||
| 968 | 		} catch ( InvalidArgumentException $ex ) { | 
            ||
| 969 | wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );  | 
            ||
| 970 | return false;  | 
            ||
| 971 | }  | 
            ||
| 972 | }  | 
            ||
| 973 | |||
| 974 | /**  | 
            ||
| 975 | * Get the namespace text of the subject (rather than talk) page  | 
            ||
| 976 | *  | 
            ||
| 977 | * @return string Namespace text  | 
            ||
| 978 | */  | 
            ||
| 979 | 	public function getSubjectNsText() { | 
            ||
| 980 | global $wgContLang;  | 
            ||
| 981 | return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );  | 
            ||
| 982 | }  | 
            ||
| 983 | |||
| 984 | /**  | 
            ||
| 985 | * Get the namespace text of the talk page  | 
            ||
| 986 | *  | 
            ||
| 987 | * @return string Namespace text  | 
            ||
| 988 | */  | 
            ||
| 989 | 	public function getTalkNsText() { | 
            ||
| 990 | global $wgContLang;  | 
            ||
| 991 | return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );  | 
            ||
| 992 | }  | 
            ||
| 993 | |||
| 994 | /**  | 
            ||
| 995 | * Could this title have a corresponding talk page?  | 
            ||
| 996 | *  | 
            ||
| 997 | * @return bool  | 
            ||
| 998 | */  | 
            ||
| 999 | 	public function canTalk() { | 
            ||
| 1000 | return MWNamespace::canTalk( $this->mNamespace );  | 
            ||
| 1001 | }  | 
            ||
| 1002 | |||
| 1003 | /**  | 
            ||
| 1004 | * Is this in a namespace that allows actual pages?  | 
            ||
| 1005 | *  | 
            ||
| 1006 | * @return bool  | 
            ||
| 1007 | */  | 
            ||
| 1008 | 	public function canExist() { | 
            ||
| 1009 | return $this->mNamespace >= NS_MAIN;  | 
            ||
| 1010 | }  | 
            ||
| 1011 | |||
| 1012 | /**  | 
            ||
| 1013 | * Can this title be added to a user's watchlist?  | 
            ||
| 1014 | *  | 
            ||
| 1015 | * @return bool  | 
            ||
| 1016 | */  | 
            ||
| 1017 | 	public function isWatchable() { | 
            ||
| 1018 | return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );  | 
            ||
| 1019 | }  | 
            ||
| 1020 | |||
| 1021 | /**  | 
            ||
| 1022 | * Returns true if this is a special page.  | 
            ||
| 1023 | *  | 
            ||
| 1024 | * @return bool  | 
            ||
| 1025 | */  | 
            ||
| 1026 | 	public function isSpecialPage() { | 
            ||
| 1027 | return $this->getNamespace() == NS_SPECIAL;  | 
            ||
| 1028 | }  | 
            ||
| 1029 | |||
| 1030 | /**  | 
            ||
| 1031 | * Returns true if this title resolves to the named special page  | 
            ||
| 1032 | *  | 
            ||
| 1033 | * @param string $name The special page name  | 
            ||
| 1034 | * @return bool  | 
            ||
| 1035 | */  | 
            ||
| 1036 | 	public function isSpecial( $name ) { | 
            ||
| 1037 | 		if ( $this->isSpecialPage() ) { | 
            ||
| 1038 | list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );  | 
            ||
| 1039 | 			if ( $name == $thisName ) { | 
            ||
| 1040 | return true;  | 
            ||
| 1041 | }  | 
            ||
| 1042 | }  | 
            ||
| 1043 | return false;  | 
            ||
| 1044 | }  | 
            ||
| 1045 | |||
| 1046 | /**  | 
            ||
| 1047 | * If the Title refers to a special page alias which is not the local default, resolve  | 
            ||
| 1048 | * the alias, and localise the name as necessary. Otherwise, return $this  | 
            ||
| 1049 | *  | 
            ||
| 1050 | * @return Title  | 
            ||
| 1051 | */  | 
            ||
| 1052 | 	public function fixSpecialName() { | 
            ||
| 1053 | 		if ( $this->isSpecialPage() ) { | 
            ||
| 1054 | list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );  | 
            ||
| 1055 | 			if ( $canonicalName ) { | 
            ||
| 1056 | $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );  | 
            ||
| 1057 | 				if ( $localName != $this->mDbkeyform ) { | 
            ||
| 1058 | return Title::makeTitle( NS_SPECIAL, $localName );  | 
            ||
| 1059 | }  | 
            ||
| 1060 | }  | 
            ||
| 1061 | }  | 
            ||
| 1062 | return $this;  | 
            ||
| 1063 | }  | 
            ||
| 1064 | |||
| 1065 | /**  | 
            ||
| 1066 | * Returns true if the title is inside the specified namespace.  | 
            ||
| 1067 | *  | 
            ||
| 1068 | * Please make use of this instead of comparing to getNamespace()  | 
            ||
| 1069 | * This function is much more resistant to changes we may make  | 
            ||
| 1070 | * to namespaces than code that makes direct comparisons.  | 
            ||
| 1071 | * @param int $ns The namespace  | 
            ||
| 1072 | * @return bool  | 
            ||
| 1073 | * @since 1.19  | 
            ||
| 1074 | */  | 
            ||
| 1075 | 	public function inNamespace( $ns ) { | 
            ||
| 1076 | return MWNamespace::equals( $this->getNamespace(), $ns );  | 
            ||
| 1077 | }  | 
            ||
| 1078 | |||
| 1079 | /**  | 
            ||
| 1080 | * Returns true if the title is inside one of the specified namespaces.  | 
            ||
| 1081 | *  | 
            ||
| 1082 | * @param int $namespaces,... The namespaces to check for  | 
            ||
| 1083 | * @return bool  | 
            ||
| 1084 | * @since 1.19  | 
            ||
| 1085 | */  | 
            ||
| 1086 | 	public function inNamespaces( /* ... */ ) { | 
            ||
| 1087 | $namespaces = func_get_args();  | 
            ||
| 1088 | 		if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) { | 
            ||
| 1089 | $namespaces = $namespaces[0];  | 
            ||
| 1090 | }  | 
            ||
| 1091 | |||
| 1092 | 		foreach ( $namespaces as $ns ) { | 
            ||
| 1093 | 			if ( $this->inNamespace( $ns ) ) { | 
            ||
| 1094 | return true;  | 
            ||
| 1095 | }  | 
            ||
| 1096 | }  | 
            ||
| 1097 | |||
| 1098 | return false;  | 
            ||
| 1099 | }  | 
            ||
| 1100 | |||
| 1101 | /**  | 
            ||
| 1102 | * Returns true if the title has the same subject namespace as the  | 
            ||
| 1103 | * namespace specified.  | 
            ||
| 1104 | * For example this method will take NS_USER and return true if namespace  | 
            ||
| 1105 | * is either NS_USER or NS_USER_TALK since both of them have NS_USER  | 
            ||
| 1106 | * as their subject namespace.  | 
            ||
| 1107 | *  | 
            ||
| 1108 | * This is MUCH simpler than individually testing for equivalence  | 
            ||
| 1109 | * against both NS_USER and NS_USER_TALK, and is also forward compatible.  | 
            ||
| 1110 | * @since 1.19  | 
            ||
| 1111 | * @param int $ns  | 
            ||
| 1112 | * @return bool  | 
            ||
| 1113 | */  | 
            ||
| 1114 | 	public function hasSubjectNamespace( $ns ) { | 
            ||
| 1115 | return MWNamespace::subjectEquals( $this->getNamespace(), $ns );  | 
            ||
| 1116 | }  | 
            ||
| 1117 | |||
| 1118 | /**  | 
            ||
| 1119 | * Is this Title in a namespace which contains content?  | 
            ||
| 1120 | * In other words, is this a content page, for the purposes of calculating  | 
            ||
| 1121 | * statistics, etc?  | 
            ||
| 1122 | *  | 
            ||
| 1123 | * @return bool  | 
            ||
| 1124 | */  | 
            ||
| 1125 | 	public function isContentPage() { | 
            ||
| 1126 | return MWNamespace::isContent( $this->getNamespace() );  | 
            ||
| 1127 | }  | 
            ||
| 1128 | |||
| 1129 | /**  | 
            ||
| 1130 | * Would anybody with sufficient privileges be able to move this page?  | 
            ||
| 1131 | * Some pages just aren't movable.  | 
            ||
| 1132 | *  | 
            ||
| 1133 | * @return bool  | 
            ||
| 1134 | */  | 
            ||
| 1135 | 	public function isMovable() { | 
            ||
| 1136 | 		if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) { | 
            ||
| 1137 | // Interwiki title or immovable namespace. Hooks don't get to override here  | 
            ||
| 1138 | return false;  | 
            ||
| 1139 | }  | 
            ||
| 1140 | |||
| 1141 | $result = true;  | 
            ||
| 1142 | Hooks::run( 'TitleIsMovable', [ $this, &$result ] );  | 
            ||
| 1143 | return $result;  | 
            ||
| 1144 | }  | 
            ||
| 1145 | |||
| 1146 | /**  | 
            ||
| 1147 | * Is this the mainpage?  | 
            ||
| 1148 | * @note Title::newFromText seems to be sufficiently optimized by the title  | 
            ||
| 1149 | * cache that we don't need to over-optimize by doing direct comparisons and  | 
            ||
| 1150 | * accidentally creating new bugs where $title->equals( Title::newFromText() )  | 
            ||
| 1151 | * ends up reporting something differently than $title->isMainPage();  | 
            ||
| 1152 | *  | 
            ||
| 1153 | * @since 1.18  | 
            ||
| 1154 | * @return bool  | 
            ||
| 1155 | */  | 
            ||
| 1156 | 	public function isMainPage() { | 
            ||
| 1157 | return $this->equals( Title::newMainPage() );  | 
            ||
| 1158 | }  | 
            ||
| 1159 | |||
| 1160 | /**  | 
            ||
| 1161 | * Is this a subpage?  | 
            ||
| 1162 | *  | 
            ||
| 1163 | * @return bool  | 
            ||
| 1164 | */  | 
            ||
| 1165 | 	public function isSubpage() { | 
            ||
| 1166 | return MWNamespace::hasSubpages( $this->mNamespace )  | 
            ||
| 1167 | ? strpos( $this->getText(), '/' ) !== false  | 
            ||
| 1168 | : false;  | 
            ||
| 1169 | }  | 
            ||
| 1170 | |||
| 1171 | /**  | 
            ||
| 1172 | * Is this a conversion table for the LanguageConverter?  | 
            ||
| 1173 | *  | 
            ||
| 1174 | * @return bool  | 
            ||
| 1175 | */  | 
            ||
| 1176 | 	public function isConversionTable() { | 
            ||
| 1177 | // @todo ConversionTable should become a separate content model.  | 
            ||
| 1178 | |||
| 1179 | return $this->getNamespace() == NS_MEDIAWIKI &&  | 
            ||
| 1180 | strpos( $this->getText(), 'Conversiontable/' ) === 0;  | 
            ||
| 1181 | }  | 
            ||
| 1182 | |||
| 1183 | /**  | 
            ||
| 1184 | * Does that page contain wikitext, or it is JS, CSS or whatever?  | 
            ||
| 1185 | *  | 
            ||
| 1186 | * @return bool  | 
            ||
| 1187 | */  | 
            ||
| 1188 | 	public function isWikitextPage() { | 
            ||
| 1189 | return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );  | 
            ||
| 1190 | }  | 
            ||
| 1191 | |||
| 1192 | /**  | 
            ||
| 1193 | * Could this page contain custom CSS or JavaScript for the global UI.  | 
            ||
| 1194 | * This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS  | 
            ||
| 1195 | * or CONTENT_MODEL_JAVASCRIPT.  | 
            ||
| 1196 | *  | 
            ||
| 1197 | * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage()  | 
            ||
| 1198 | * for that!  | 
            ||
| 1199 | *  | 
            ||
| 1200 | * Note that this method should not return true for pages that contain and  | 
            ||
| 1201 | * show "inactive" CSS or JS.  | 
            ||
| 1202 | *  | 
            ||
| 1203 | * @return bool  | 
            ||
| 1204 | * @todo FIXME: Rename to isSiteConfigPage() and remove deprecated hook  | 
            ||
| 1205 | */  | 
            ||
| 1206 | 	public function isCssOrJsPage() { | 
            ||
| 1207 | $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace  | 
            ||
| 1208 | && ( $this->hasContentModel( CONTENT_MODEL_CSS )  | 
            ||
| 1209 | || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );  | 
            ||
| 1210 | |||
| 1211 | # @note This hook is also called in ContentHandler::getDefaultModel.  | 
            ||
| 1212 | # It's called here again to make sure hook functions can force this  | 
            ||
| 1213 | # method to return true even outside the MediaWiki namespace.  | 
            ||
| 1214 | |||
| 1215 | Hooks::run( 'TitleIsCssOrJsPage', [ $this, &$isCssOrJsPage ], '1.25' );  | 
            ||
| 1216 | |||
| 1217 | return $isCssOrJsPage;  | 
            ||
| 1218 | }  | 
            ||
| 1219 | |||
| 1220 | /**  | 
            ||
| 1221 | * Is this a .css or .js subpage of a user page?  | 
            ||
| 1222 | * @return bool  | 
            ||
| 1223 | * @todo FIXME: Rename to isUserConfigPage()  | 
            ||
| 1224 | */  | 
            ||
| 1225 | 	public function isCssJsSubpage() { | 
            ||
| 1226 | return ( NS_USER == $this->mNamespace && $this->isSubpage()  | 
            ||
| 1227 | && ( $this->hasContentModel( CONTENT_MODEL_CSS )  | 
            ||
| 1228 | || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );  | 
            ||
| 1229 | }  | 
            ||
| 1230 | |||
| 1231 | /**  | 
            ||
| 1232 | * Trim down a .css or .js subpage title to get the corresponding skin name  | 
            ||
| 1233 | *  | 
            ||
| 1234 | * @return string Containing skin name from .css or .js subpage title  | 
            ||
| 1235 | */  | 
            ||
| 1236 | 	public function getSkinFromCssJsSubpage() { | 
            ||
| 1237 | $subpage = explode( '/', $this->mTextform );  | 
            ||
| 1238 | $subpage = $subpage[count( $subpage ) - 1];  | 
            ||
| 1239 | $lastdot = strrpos( $subpage, '.' );  | 
            ||
| 1240 | 		if ( $lastdot === false ) { | 
            ||
| 1241 | return $subpage; # Never happens: only called for names ending in '.css' or '.js'  | 
            ||
| 1242 | }  | 
            ||
| 1243 | return substr( $subpage, 0, $lastdot );  | 
            ||
| 1244 | }  | 
            ||
| 1245 | |||
| 1246 | /**  | 
            ||
| 1247 | * Is this a .css subpage of a user page?  | 
            ||
| 1248 | *  | 
            ||
| 1249 | * @return bool  | 
            ||
| 1250 | */  | 
            ||
| 1251 | 	public function isCssSubpage() { | 
            ||
| 1252 | return ( NS_USER == $this->mNamespace && $this->isSubpage()  | 
            ||
| 1253 | && $this->hasContentModel( CONTENT_MODEL_CSS ) );  | 
            ||
| 1254 | }  | 
            ||
| 1255 | |||
| 1256 | /**  | 
            ||
| 1257 | * Is this a .js subpage of a user page?  | 
            ||
| 1258 | *  | 
            ||
| 1259 | * @return bool  | 
            ||
| 1260 | */  | 
            ||
| 1261 | 	public function isJsSubpage() { | 
            ||
| 1262 | return ( NS_USER == $this->mNamespace && $this->isSubpage()  | 
            ||
| 1263 | && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );  | 
            ||
| 1264 | }  | 
            ||
| 1265 | |||
| 1266 | /**  | 
            ||
| 1267 | * Is this a talk page of some sort?  | 
            ||
| 1268 | *  | 
            ||
| 1269 | * @return bool  | 
            ||
| 1270 | */  | 
            ||
| 1271 | 	public function isTalkPage() { | 
            ||
| 1272 | return MWNamespace::isTalk( $this->getNamespace() );  | 
            ||
| 1273 | }  | 
            ||
| 1274 | |||
| 1275 | /**  | 
            ||
| 1276 | * Get a Title object associated with the talk page of this article  | 
            ||
| 1277 | *  | 
            ||
| 1278 | * @return Title The object for the talk page  | 
            ||
| 1279 | */  | 
            ||
| 1280 | 	public function getTalkPage() { | 
            ||
| 1281 | return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );  | 
            ||
| 1282 | }  | 
            ||
| 1283 | |||
| 1284 | /**  | 
            ||
| 1285 | * Get a title object associated with the subject page of this  | 
            ||
| 1286 | * talk page  | 
            ||
| 1287 | *  | 
            ||
| 1288 | * @return Title The object for the subject page  | 
            ||
| 1289 | */  | 
            ||
| 1290 | 	public function getSubjectPage() { | 
            ||
| 1291 | // Is this the same title?  | 
            ||
| 1292 | $subjectNS = MWNamespace::getSubject( $this->getNamespace() );  | 
            ||
| 1293 | 		if ( $this->getNamespace() == $subjectNS ) { | 
            ||
| 1294 | return $this;  | 
            ||
| 1295 | }  | 
            ||
| 1296 | return Title::makeTitle( $subjectNS, $this->getDBkey() );  | 
            ||
| 1297 | }  | 
            ||
| 1298 | |||
| 1299 | /**  | 
            ||
| 1300 | * Get the other title for this page, if this is a subject page  | 
            ||
| 1301 | * get the talk page, if it is a subject page get the talk page  | 
            ||
| 1302 | *  | 
            ||
| 1303 | * @since 1.25  | 
            ||
| 1304 | * @throws MWException  | 
            ||
| 1305 | * @return Title  | 
            ||
| 1306 | */  | 
            ||
| 1307 | 	public function getOtherPage() { | 
            ||
| 1308 | 		if ( $this->isSpecialPage() ) { | 
            ||
| 1309 | throw new MWException( 'Special pages cannot have other pages' );  | 
            ||
| 1310 | }  | 
            ||
| 1311 | 		if ( $this->isTalkPage() ) { | 
            ||
| 1312 | return $this->getSubjectPage();  | 
            ||
| 1313 | 		} else { | 
            ||
| 1314 | return $this->getTalkPage();  | 
            ||
| 1315 | }  | 
            ||
| 1316 | }  | 
            ||
| 1317 | |||
| 1318 | /**  | 
            ||
| 1319 | * Get the default namespace index, for when there is no namespace  | 
            ||
| 1320 | *  | 
            ||
| 1321 | * @return int Default namespace index  | 
            ||
| 1322 | */  | 
            ||
| 1323 | 	public function getDefaultNamespace() { | 
            ||
| 1324 | return $this->mDefaultNamespace;  | 
            ||
| 1325 | }  | 
            ||
| 1326 | |||
| 1327 | /**  | 
            ||
| 1328 | * Get the Title fragment (i.e.\ the bit after the #) in text form  | 
            ||
| 1329 | *  | 
            ||
| 1330 | * Use Title::hasFragment to check for a fragment  | 
            ||
| 1331 | *  | 
            ||
| 1332 | * @return string Title fragment  | 
            ||
| 1333 | */  | 
            ||
| 1334 | 	public function getFragment() { | 
            ||
| 1335 | return $this->mFragment;  | 
            ||
| 1336 | }  | 
            ||
| 1337 | |||
| 1338 | /**  | 
            ||
| 1339 | * Check if a Title fragment is set  | 
            ||
| 1340 | *  | 
            ||
| 1341 | * @return bool  | 
            ||
| 1342 | * @since 1.23  | 
            ||
| 1343 | */  | 
            ||
| 1344 | 	public function hasFragment() { | 
            ||
| 1345 | return $this->mFragment !== '';  | 
            ||
| 1346 | }  | 
            ||
| 1347 | |||
| 1348 | /**  | 
            ||
| 1349 | * Get the fragment in URL form, including the "#" character if there is one  | 
            ||
| 1350 | * @return string Fragment in URL form  | 
            ||
| 1351 | */  | 
            ||
| 1352 | 	public function getFragmentForURL() { | 
            ||
| 1353 | 		if ( !$this->hasFragment() ) { | 
            ||
| 1354 | return '';  | 
            ||
| 1355 | 		} else { | 
            ||
| 1356 | return '#' . Title::escapeFragmentForURL( $this->getFragment() );  | 
            ||
| 1357 | }  | 
            ||
| 1358 | }  | 
            ||
| 1359 | |||
| 1360 | /**  | 
            ||
| 1361 | * Set the fragment for this title. Removes the first character from the  | 
            ||
| 1362 | * specified fragment before setting, so it assumes you're passing it with  | 
            ||
| 1363 | * an initial "#".  | 
            ||
| 1364 | *  | 
            ||
| 1365 | * Deprecated for public use, use Title::makeTitle() with fragment parameter,  | 
            ||
| 1366 | * or Title::createFragmentTarget().  | 
            ||
| 1367 | * Still in active use privately.  | 
            ||
| 1368 | *  | 
            ||
| 1369 | * @private  | 
            ||
| 1370 | * @param string $fragment Text  | 
            ||
| 1371 | */  | 
            ||
| 1372 | 	public function setFragment( $fragment ) { | 
            ||
| 1373 | $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );  | 
            ||
| 1374 | }  | 
            ||
| 1375 | |||
| 1376 | /**  | 
            ||
| 1377 | * Creates a new Title for a different fragment of the same page.  | 
            ||
| 1378 | *  | 
            ||
| 1379 | * @since 1.27  | 
            ||
| 1380 | * @param string $fragment  | 
            ||
| 1381 | * @return Title  | 
            ||
| 1382 | */  | 
            ||
| 1383 | 	public function createFragmentTarget( $fragment ) { | 
            ||
| 1384 | return self::makeTitle(  | 
            ||
| 1385 | $this->getNamespace(),  | 
            ||
| 1386 | $this->getText(),  | 
            ||
| 1387 | $fragment,  | 
            ||
| 1388 | $this->getInterwiki()  | 
            ||
| 1389 | );  | 
            ||
| 1390 | |||
| 1391 | }  | 
            ||
| 1392 | |||
| 1393 | /**  | 
            ||
| 1394 | * Prefix some arbitrary text with the namespace or interwiki prefix  | 
            ||
| 1395 | * of this object  | 
            ||
| 1396 | *  | 
            ||
| 1397 | * @param string $name The text  | 
            ||
| 1398 | * @return string The prefixed text  | 
            ||
| 1399 | */  | 
            ||
| 1400 | 	private function prefix( $name ) { | 
            ||
| 1401 | $p = '';  | 
            ||
| 1402 | 		if ( $this->isExternal() ) { | 
            ||
| 1403 | $p = $this->mInterwiki . ':';  | 
            ||
| 1404 | }  | 
            ||
| 1405 | |||
| 1406 | 		if ( 0 != $this->mNamespace ) { | 
            ||
| 1407 | $p .= $this->getNsText() . ':';  | 
            ||
| 1408 | }  | 
            ||
| 1409 | return $p . $name;  | 
            ||
| 1410 | }  | 
            ||
| 1411 | |||
| 1412 | /**  | 
            ||
| 1413 | * Get the prefixed database key form  | 
            ||
| 1414 | *  | 
            ||
| 1415 | * @return string The prefixed title, with underscores and  | 
            ||
| 1416 | * any interwiki and namespace prefixes  | 
            ||
| 1417 | */  | 
            ||
| 1418 | 	public function getPrefixedDBkey() { | 
            ||
| 1419 | $s = $this->prefix( $this->mDbkeyform );  | 
            ||
| 1420 | $s = strtr( $s, ' ', '_' );  | 
            ||
| 1421 | return $s;  | 
            ||
| 1422 | }  | 
            ||
| 1423 | |||
| 1424 | /**  | 
            ||
| 1425 | * Get the prefixed title with spaces.  | 
            ||
| 1426 | * This is the form usually used for display  | 
            ||
| 1427 | *  | 
            ||
| 1428 | * @return string The prefixed title, with spaces  | 
            ||
| 1429 | */  | 
            ||
| 1430 | 	public function getPrefixedText() { | 
            ||
| 1431 | 		if ( $this->mPrefixedText === null ) { | 
            ||
| 1432 | $s = $this->prefix( $this->mTextform );  | 
            ||
| 1433 | $s = strtr( $s, '_', ' ' );  | 
            ||
| 1434 | $this->mPrefixedText = $s;  | 
            ||
| 1435 | }  | 
            ||
| 1436 | return $this->mPrefixedText;  | 
            ||
| 1437 | }  | 
            ||
| 1438 | |||
| 1439 | /**  | 
            ||
| 1440 | * Return a string representation of this title  | 
            ||
| 1441 | *  | 
            ||
| 1442 | * @return string Representation of this title  | 
            ||
| 1443 | */  | 
            ||
| 1444 | 	public function __toString() { | 
            ||
| 1445 | return $this->getPrefixedText();  | 
            ||
| 1446 | }  | 
            ||
| 1447 | |||
| 1448 | /**  | 
            ||
| 1449 | * Get the prefixed title with spaces, plus any fragment  | 
            ||
| 1450 | * (part beginning with '#')  | 
            ||
| 1451 | *  | 
            ||
| 1452 | * @return string The prefixed title, with spaces and the fragment, including '#'  | 
            ||
| 1453 | */  | 
            ||
| 1454 | 	public function getFullText() { | 
            ||
| 1455 | $text = $this->getPrefixedText();  | 
            ||
| 1456 | 		if ( $this->hasFragment() ) { | 
            ||
| 1457 | $text .= '#' . $this->getFragment();  | 
            ||
| 1458 | }  | 
            ||
| 1459 | return $text;  | 
            ||
| 1460 | }  | 
            ||
| 1461 | |||
| 1462 | /**  | 
            ||
| 1463 | * Get the root page name text without a namespace, i.e. the leftmost part before any slashes  | 
            ||
| 1464 | *  | 
            ||
| 1465 | * @par Example:  | 
            ||
| 1466 | * @code  | 
            ||
| 1467 | 	 * Title::newFromText('User:Foo/Bar/Baz')->getRootText(); | 
            ||
| 1468 | * # returns: 'Foo'  | 
            ||
| 1469 | * @endcode  | 
            ||
| 1470 | *  | 
            ||
| 1471 | * @return string Root name  | 
            ||
| 1472 | * @since 1.20  | 
            ||
| 1473 | */  | 
            ||
| 1474 | 	public function getRootText() { | 
            ||
| 1475 | 		if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { | 
            ||
| 1476 | return $this->getText();  | 
            ||
| 1477 | }  | 
            ||
| 1478 | |||
| 1479 | return strtok( $this->getText(), '/' );  | 
            ||
| 1480 | }  | 
            ||
| 1481 | |||
| 1482 | /**  | 
            ||
| 1483 | * Get the root page name title, i.e. the leftmost part before any slashes  | 
            ||
| 1484 | *  | 
            ||
| 1485 | * @par Example:  | 
            ||
| 1486 | * @code  | 
            ||
| 1487 | 	 * Title::newFromText('User:Foo/Bar/Baz')->getRootTitle(); | 
            ||
| 1488 | 	 * # returns: Title{User:Foo} | 
            ||
| 1489 | * @endcode  | 
            ||
| 1490 | *  | 
            ||
| 1491 | * @return Title Root title  | 
            ||
| 1492 | * @since 1.20  | 
            ||
| 1493 | */  | 
            ||
| 1494 | 	public function getRootTitle() { | 
            ||
| 1495 | return Title::makeTitle( $this->getNamespace(), $this->getRootText() );  | 
            ||
| 1496 | }  | 
            ||
| 1497 | |||
| 1498 | /**  | 
            ||
| 1499 | * Get the base page name without a namespace, i.e. the part before the subpage name  | 
            ||
| 1500 | *  | 
            ||
| 1501 | * @par Example:  | 
            ||
| 1502 | * @code  | 
            ||
| 1503 | 	 * Title::newFromText('User:Foo/Bar/Baz')->getBaseText(); | 
            ||
| 1504 | * # returns: 'Foo/Bar'  | 
            ||
| 1505 | * @endcode  | 
            ||
| 1506 | *  | 
            ||
| 1507 | * @return string Base name  | 
            ||
| 1508 | */  | 
            ||
| 1509 | 	public function getBaseText() { | 
            ||
| 1510 | 		if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { | 
            ||
| 1511 | return $this->getText();  | 
            ||
| 1512 | }  | 
            ||
| 1513 | |||
| 1514 | $parts = explode( '/', $this->getText() );  | 
            ||
| 1515 | # Don't discard the real title if there's no subpage involved  | 
            ||
| 1516 | 		if ( count( $parts ) > 1 ) { | 
            ||
| 1517 | unset( $parts[count( $parts ) - 1] );  | 
            ||
| 1518 | }  | 
            ||
| 1519 | return implode( '/', $parts );  | 
            ||
| 1520 | }  | 
            ||
| 1521 | |||
| 1522 | /**  | 
            ||
| 1523 | * Get the base page name title, i.e. the part before the subpage name  | 
            ||
| 1524 | *  | 
            ||
| 1525 | * @par Example:  | 
            ||
| 1526 | * @code  | 
            ||
| 1527 | 	 * Title::newFromText('User:Foo/Bar/Baz')->getBaseTitle(); | 
            ||
| 1528 | 	 * # returns: Title{User:Foo/Bar} | 
            ||
| 1529 | * @endcode  | 
            ||
| 1530 | *  | 
            ||
| 1531 | * @return Title Base title  | 
            ||
| 1532 | * @since 1.20  | 
            ||
| 1533 | */  | 
            ||
| 1534 | 	public function getBaseTitle() { | 
            ||
| 1535 | return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );  | 
            ||
| 1536 | }  | 
            ||
| 1537 | |||
| 1538 | /**  | 
            ||
| 1539 | * Get the lowest-level subpage name, i.e. the rightmost part after any slashes  | 
            ||
| 1540 | *  | 
            ||
| 1541 | * @par Example:  | 
            ||
| 1542 | * @code  | 
            ||
| 1543 | 	 * Title::newFromText('User:Foo/Bar/Baz')->getSubpageText(); | 
            ||
| 1544 | * # returns: "Baz"  | 
            ||
| 1545 | * @endcode  | 
            ||
| 1546 | *  | 
            ||
| 1547 | * @return string Subpage name  | 
            ||
| 1548 | */  | 
            ||
| 1549 | 	public function getSubpageText() { | 
            ||
| 1550 | 		if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { | 
            ||
| 1551 | return $this->mTextform;  | 
            ||
| 1552 | }  | 
            ||
| 1553 | $parts = explode( '/', $this->mTextform );  | 
            ||
| 1554 | return $parts[count( $parts ) - 1];  | 
            ||
| 1555 | }  | 
            ||
| 1556 | |||
| 1557 | /**  | 
            ||
| 1558 | * Get the title for a subpage of the current page  | 
            ||
| 1559 | *  | 
            ||
| 1560 | * @par Example:  | 
            ||
| 1561 | * @code  | 
            ||
| 1562 | 	 * Title::newFromText('User:Foo/Bar/Baz')->getSubpage("Asdf"); | 
            ||
| 1563 | 	 * # returns: Title{User:Foo/Bar/Baz/Asdf} | 
            ||
| 1564 | * @endcode  | 
            ||
| 1565 | *  | 
            ||
| 1566 | * @param string $text The subpage name to add to the title  | 
            ||
| 1567 | * @return Title Subpage title  | 
            ||
| 1568 | * @since 1.20  | 
            ||
| 1569 | */  | 
            ||
| 1570 | 	public function getSubpage( $text ) { | 
            ||
| 1571 | return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );  | 
            ||
| 1572 | }  | 
            ||
| 1573 | |||
| 1574 | /**  | 
            ||
| 1575 | * Get a URL-encoded form of the subpage text  | 
            ||
| 1576 | *  | 
            ||
| 1577 | * @return string URL-encoded subpage name  | 
            ||
| 1578 | */  | 
            ||
| 1579 | 	public function getSubpageUrlForm() { | 
            ||
| 1580 | $text = $this->getSubpageText();  | 
            ||
| 1581 | $text = wfUrlencode( strtr( $text, ' ', '_' ) );  | 
            ||
| 1582 | return $text;  | 
            ||
| 1583 | }  | 
            ||
| 1584 | |||
| 1585 | /**  | 
            ||
| 1586 | * Get a URL-encoded title (not an actual URL) including interwiki  | 
            ||
| 1587 | *  | 
            ||
| 1588 | * @return string The URL-encoded form  | 
            ||
| 1589 | */  | 
            ||
| 1590 | 	public function getPrefixedURL() { | 
            ||
| 1591 | $s = $this->prefix( $this->mDbkeyform );  | 
            ||
| 1592 | $s = wfUrlencode( strtr( $s, ' ', '_' ) );  | 
            ||
| 1593 | return $s;  | 
            ||
| 1594 | }  | 
            ||
| 1595 | |||
| 1596 | /**  | 
            ||
| 1597 | 	 * Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args | 
            ||
| 1598 | 	 * get{Canonical,Full,Link,Local,Internal}URL methods accepted an optional | 
            ||
| 1599 | * second argument named variant. This was deprecated in favor  | 
            ||
| 1600 | * of passing an array of option with a "variant" key  | 
            ||
| 1601 | * Once $query2 is removed for good, this helper can be dropped  | 
            ||
| 1602 | * and the wfArrayToCgi moved to getLocalURL();  | 
            ||
| 1603 | *  | 
            ||
| 1604 | * @since 1.19 (r105919)  | 
            ||
| 1605 | * @param array|string $query  | 
            ||
| 1606 | * @param bool $query2  | 
            ||
| 1607 | * @return string  | 
            ||
| 1608 | */  | 
            ||
| 1609 | 	private static function fixUrlQueryArgs( $query, $query2 = false ) { | 
            ||
| 1610 | 		if ( $query2 !== false ) { | 
            ||
| 1611 | 			wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " . | 
            ||
| 1612 | "method called with a second parameter is deprecated. Add your " .  | 
            ||
| 1613 | "parameter to an array passed as the first parameter.", "1.19" );  | 
            ||
| 1614 | }  | 
            ||
| 1615 | 		if ( is_array( $query ) ) { | 
            ||
| 1616 | $query = wfArrayToCgi( $query );  | 
            ||
| 1617 | }  | 
            ||
| 1618 | 		if ( $query2 ) { | 
            ||
| 1619 | 			if ( is_string( $query2 ) ) { | 
            ||
| 1620 | // $query2 is a string, we will consider this to be  | 
            ||
| 1621 | // a deprecated $variant argument and add it to the query  | 
            ||
| 1622 | $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );  | 
            ||
| 1623 | 			} else { | 
            ||
| 1624 | $query2 = wfArrayToCgi( $query2 );  | 
            ||
| 1625 | }  | 
            ||
| 1626 | // If we have $query content add a & to it first  | 
            ||
| 1627 | 			if ( $query ) { | 
            ||
| 1628 | $query .= '&';  | 
            ||
| 1629 | }  | 
            ||
| 1630 | // Now append the queries together  | 
            ||
| 1631 | $query .= $query2;  | 
            ||
| 1632 | }  | 
            ||
| 1633 | return $query;  | 
            ||
| 1634 | }  | 
            ||
| 1635 | |||
| 1636 | /**  | 
            ||
| 1637 | * Get a real URL referring to this title, with interwiki link and  | 
            ||
| 1638 | * fragment  | 
            ||
| 1639 | *  | 
            ||
| 1640 | * @see self::getLocalURL for the arguments.  | 
            ||
| 1641 | * @see wfExpandUrl  | 
            ||
| 1642 | * @param array|string $query  | 
            ||
| 1643 | * @param bool $query2  | 
            ||
| 1644 | * @param string $proto Protocol type to use in URL  | 
            ||
| 1645 | * @return string The URL  | 
            ||
| 1646 | */  | 
            ||
| 1647 | 	public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) { | 
            ||
| 1648 | $query = self::fixUrlQueryArgs( $query, $query2 );  | 
            ||
| 1649 | |||
| 1650 | # Hand off all the decisions on urls to getLocalURL  | 
            ||
| 1651 | $url = $this->getLocalURL( $query );  | 
            ||
| 1652 | |||
| 1653 | # Expand the url to make it a full url. Note that getLocalURL has the  | 
            ||
| 1654 | # potential to output full urls for a variety of reasons, so we use  | 
            ||
| 1655 | # wfExpandUrl instead of simply prepending $wgServer  | 
            ||
| 1656 | $url = wfExpandUrl( $url, $proto );  | 
            ||
| 1657 | |||
| 1658 | # Finally, add the fragment.  | 
            ||
| 1659 | $url .= $this->getFragmentForURL();  | 
            ||
| 1660 | |||
| 1661 | Hooks::run( 'GetFullURL', [ &$this, &$url, $query ] );  | 
            ||
| 1662 | return $url;  | 
            ||
| 1663 | }  | 
            ||
| 1664 | |||
| 1665 | /**  | 
            ||
| 1666 | * Get a URL with no fragment or server name (relative URL) from a Title object.  | 
            ||
| 1667 | * If this page is generated with action=render, however,  | 
            ||
| 1668 | * $wgServer is prepended to make an absolute URL.  | 
            ||
| 1669 | *  | 
            ||
| 1670 | * @see self::getFullURL to always get an absolute URL.  | 
            ||
| 1671 | * @see self::getLinkURL to always get a URL that's the simplest URL that will be  | 
            ||
| 1672 | * valid to link, locally, to the current Title.  | 
            ||
| 1673 | * @see self::newFromText to produce a Title object.  | 
            ||
| 1674 | *  | 
            ||
| 1675 | * @param string|array $query An optional query string,  | 
            ||
| 1676 | * not used for interwiki links. Can be specified as an associative array as well,  | 
            ||
| 1677 | * e.g., array( 'action' => 'edit' ) (keys and values will be URL-escaped).  | 
            ||
| 1678 | * Some query patterns will trigger various shorturl path replacements.  | 
            ||
| 1679 | * @param array $query2 An optional secondary query array. This one MUST  | 
            ||
| 1680 | * be an array. If a string is passed it will be interpreted as a deprecated  | 
            ||
| 1681 | * variant argument and urlencoded into a variant= argument.  | 
            ||
| 1682 | * This second query argument will be added to the $query  | 
            ||
| 1683 | * The second parameter is deprecated since 1.19. Pass it as a key,value  | 
            ||
| 1684 | * pair in the first parameter array instead.  | 
            ||
| 1685 | *  | 
            ||
| 1686 | * @return string String of the URL.  | 
            ||
| 1687 | */  | 
            ||
| 1688 | 	public function getLocalURL( $query = '', $query2 = false ) { | 
            ||
| 1763 | |||
| 1764 | /**  | 
            ||
| 1765 | * Get a URL that's the simplest URL that will be valid to link, locally,  | 
            ||
| 1766 | * to the current Title. It includes the fragment, but does not include  | 
            ||
| 1767 | * the server unless action=render is used (or the link is external). If  | 
            ||
| 1768 | * there's a fragment but the prefixed text is empty, we just return a link  | 
            ||
| 1769 | * to the fragment.  | 
            ||
| 1770 | *  | 
            ||
| 1771 | * The result obviously should not be URL-escaped, but does need to be  | 
            ||
| 1772 | * HTML-escaped if it's being output in HTML.  | 
            ||
| 1773 | *  | 
            ||
| 1774 | * @param array $query  | 
            ||
| 1775 | * @param bool $query2  | 
            ||
| 1776 | * @param string $proto Protocol to use; setting this will cause a full URL to be used  | 
            ||
| 1777 | * @see self::getLocalURL for the arguments.  | 
            ||
| 1778 | * @return string The URL  | 
            ||
| 1779 | */  | 
            ||
| 1780 | 	public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) { | 
            ||
| 1781 | 		if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) { | 
            ||
| 1782 | $ret = $this->getFullURL( $query, $query2, $proto );  | 
            ||
| 1783 | 		} elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) { | 
            ||
| 1784 | $ret = $this->getFragmentForURL();  | 
            ||
| 1785 | 		} else { | 
            ||
| 1786 | $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();  | 
            ||
| 1787 | }  | 
            ||
| 1788 | return $ret;  | 
            ||
| 1789 | }  | 
            ||
| 1790 | |||
| 1791 | /**  | 
            ||
| 1792 | * Get the URL form for an internal link.  | 
            ||
| 1793 | * - Used in various CDN-related code, in case we have a different  | 
            ||
| 1794 | * internal hostname for the server from the exposed one.  | 
            ||
| 1795 | *  | 
            ||
| 1796 | * This uses $wgInternalServer to qualify the path, or $wgServer  | 
            ||
| 1797 | * if $wgInternalServer is not set. If the server variable used is  | 
            ||
| 1798 | * protocol-relative, the URL will be expanded to http://  | 
            ||
| 1799 | *  | 
            ||
| 1800 | * @see self::getLocalURL for the arguments.  | 
            ||
| 1801 | * @return string The URL  | 
            ||
| 1802 | */  | 
            ||
| 1803 | 	public function getInternalURL( $query = '', $query2 = false ) { | 
            ||
| 1804 | global $wgInternalServer, $wgServer;  | 
            ||
| 1805 | $query = self::fixUrlQueryArgs( $query, $query2 );  | 
            ||
| 1806 | $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;  | 
            ||
| 1807 | $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );  | 
            ||
| 1808 | Hooks::run( 'GetInternalURL', [ &$this, &$url, $query ] );  | 
            ||
| 1809 | return $url;  | 
            ||
| 1810 | }  | 
            ||
| 1811 | |||
| 1812 | /**  | 
            ||
| 1813 | * Get the URL for a canonical link, for use in things like IRC and  | 
            ||
| 1814 | * e-mail notifications. Uses $wgCanonicalServer and the  | 
            ||
| 1815 | * GetCanonicalURL hook.  | 
            ||
| 1816 | *  | 
            ||
| 1817 | * NOTE: Unlike getInternalURL(), the canonical URL includes the fragment  | 
            ||
| 1818 | *  | 
            ||
| 1819 | * @see self::getLocalURL for the arguments.  | 
            ||
| 1820 | * @return string The URL  | 
            ||
| 1821 | * @since 1.18  | 
            ||
| 1822 | */  | 
            ||
| 1823 | 	public function getCanonicalURL( $query = '', $query2 = false ) { | 
            ||
| 1824 | $query = self::fixUrlQueryArgs( $query, $query2 );  | 
            ||
| 1825 | $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );  | 
            ||
| 1826 | Hooks::run( 'GetCanonicalURL', [ &$this, &$url, $query ] );  | 
            ||
| 1827 | return $url;  | 
            ||
| 1828 | }  | 
            ||
| 1829 | |||
| 1830 | /**  | 
            ||
| 1831 | * Get the edit URL for this Title  | 
            ||
| 1832 | *  | 
            ||
| 1833 | * @return string The URL, or a null string if this is an interwiki link  | 
            ||
| 1834 | */  | 
            ||
| 1835 | 	public function getEditURL() { | 
            ||
| 1836 | 		if ( $this->isExternal() ) { | 
            ||
| 1837 | return '';  | 
            ||
| 1838 | }  | 
            ||
| 1839 | $s = $this->getLocalURL( 'action=edit' );  | 
            ||
| 1840 | |||
| 1841 | return $s;  | 
            ||
| 1842 | }  | 
            ||
| 1843 | |||
| 1844 | /**  | 
            ||
| 1845 | * Can $user perform $action on this page?  | 
            ||
| 1846 | * This skips potentially expensive cascading permission checks  | 
            ||
| 1847 | * as well as avoids expensive error formatting  | 
            ||
| 1848 | *  | 
            ||
| 1849 | * Suitable for use for nonessential UI controls in common cases, but  | 
            ||
| 1850 | * _not_ for functional access control.  | 
            ||
| 1851 | *  | 
            ||
| 1852 | * May provide false positives, but should never provide a false negative.  | 
            ||
| 1853 | *  | 
            ||
| 1854 | * @param string $action Action that permission needs to be checked for  | 
            ||
| 1855 | * @param User $user User to check (since 1.19); $wgUser will be used if not provided.  | 
            ||
| 1856 | * @return bool  | 
            ||
| 1857 | */  | 
            ||
| 1858 | 	public function quickUserCan( $action, $user = null ) { | 
            ||
| 1859 | return $this->userCan( $action, $user, false );  | 
            ||
| 1860 | }  | 
            ||
| 1861 | |||
| 1862 | /**  | 
            ||
| 1863 | * Can $user perform $action on this page?  | 
            ||
| 1864 | *  | 
            ||
| 1865 | * @param string $action Action that permission needs to be checked for  | 
            ||
| 1866 | * @param User $user User to check (since 1.19); $wgUser will be used if not  | 
            ||
| 1867 | * provided.  | 
            ||
| 1868 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 1869 | * @return bool  | 
            ||
| 1870 | */  | 
            ||
| 1871 | 	public function userCan( $action, $user = null, $rigor = 'secure' ) { | 
            ||
| 1872 | 		if ( !$user instanceof User ) { | 
            ||
| 1873 | global $wgUser;  | 
            ||
| 1874 | $user = $wgUser;  | 
            ||
| 1875 | }  | 
            ||
| 1876 | |||
| 1877 | return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );  | 
            ||
| 1878 | }  | 
            ||
| 1879 | |||
| 1880 | /**  | 
            ||
| 1881 | * Can $user perform $action on this page?  | 
            ||
| 1882 | *  | 
            ||
| 1883 | * @todo FIXME: This *does not* check throttles (User::pingLimiter()).  | 
            ||
| 1884 | *  | 
            ||
| 1885 | * @param string $action Action that permission needs to be checked for  | 
            ||
| 1886 | * @param User $user User to check  | 
            ||
| 1887 | * @param string $rigor One of (quick,full,secure)  | 
            ||
| 1888 | * - quick : does cheap permission checks from slaves (usable for GUI creation)  | 
            ||
| 1889 | * - full : does cheap and expensive checks possibly from a slave  | 
            ||
| 1890 | * - secure : does cheap and expensive checks, using the master as needed  | 
            ||
| 1891 | * @param array $ignoreErrors Array of Strings Set this to a list of message keys  | 
            ||
| 1892 | * whose corresponding errors may be ignored.  | 
            ||
| 1893 | * @return array Array of arrays of the arguments to wfMessage to explain permissions problems.  | 
            ||
| 1894 | */  | 
            ||
| 1895 | public function getUserPermissionsErrors(  | 
            ||
| 1896 | $action, $user, $rigor = 'secure', $ignoreErrors = []  | 
            ||
| 1897 | 	) { | 
            ||
| 1898 | $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );  | 
            ||
| 1899 | |||
| 1900 | // Remove the errors being ignored.  | 
            ||
| 1901 | 		foreach ( $errors as $index => $error ) { | 
            ||
| 1902 | $errKey = is_array( $error ) ? $error[0] : $error;  | 
            ||
| 1903 | |||
| 1904 | 			if ( in_array( $errKey, $ignoreErrors ) ) { | 
            ||
| 1905 | unset( $errors[$index] );  | 
            ||
| 1906 | }  | 
            ||
| 1907 | 			if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) { | 
            ||
| 1908 | unset( $errors[$index] );  | 
            ||
| 1909 | }  | 
            ||
| 1910 | }  | 
            ||
| 1911 | |||
| 1912 | return $errors;  | 
            ||
| 1913 | }  | 
            ||
| 1914 | |||
| 1915 | /**  | 
            ||
| 1916 | * Permissions checks that fail most often, and which are easiest to test.  | 
            ||
| 1917 | *  | 
            ||
| 1918 | * @param string $action The action to check  | 
            ||
| 1919 | * @param User $user User to check  | 
            ||
| 1920 | * @param array $errors List of current errors  | 
            ||
| 1921 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 1922 | * @param bool $short Short circuit on first error  | 
            ||
| 1923 | *  | 
            ||
| 1924 | * @return array List of errors  | 
            ||
| 1925 | */  | 
            ||
| 1926 | 	private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) { | 
            ||
| 1927 | if ( !Hooks::run( 'TitleQuickPermissions',  | 
            ||
| 1928 | [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )  | 
            ||
| 1929 | 		) { | 
            ||
| 1930 | return $errors;  | 
            ||
| 1931 | }  | 
            ||
| 1932 | |||
| 1933 | 		if ( $action == 'create' ) { | 
            ||
| 1934 | if (  | 
            ||
| 1935 | ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||  | 
            ||
| 1936 | ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )  | 
            ||
| 1937 | 			) { | 
            ||
| 1938 | $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];  | 
            ||
| 1939 | }  | 
            ||
| 1940 | 		} elseif ( $action == 'move' ) { | 
            ||
| 1941 | View Code Duplication | if ( !$user->isAllowed( 'move-rootuserpages' )  | 
            |
| 1942 | 					&& $this->mNamespace == NS_USER && !$this->isSubpage() ) { | 
            ||
| 1943 | // Show user page-specific message only if the user can move other pages  | 
            ||
| 1944 | $errors[] = [ 'cant-move-user-page' ];  | 
            ||
| 1945 | }  | 
            ||
| 1946 | |||
| 1947 | // Check if user is allowed to move files if it's a file  | 
            ||
| 1948 | 			if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) { | 
            ||
| 1949 | $errors[] = [ 'movenotallowedfile' ];  | 
            ||
| 1950 | }  | 
            ||
| 1951 | |||
| 1952 | // Check if user is allowed to move category pages if it's a category page  | 
            ||
| 1953 | 			if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) { | 
            ||
| 1954 | $errors[] = [ 'cant-move-category-page' ];  | 
            ||
| 1955 | }  | 
            ||
| 1956 | |||
| 1957 | 			if ( !$user->isAllowed( 'move' ) ) { | 
            ||
| 1958 | // User can't move anything  | 
            ||
| 1959 | $userCanMove = User::groupHasPermission( 'user', 'move' );  | 
            ||
| 1960 | $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );  | 
            ||
| 1961 | 				if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) { | 
            ||
| 1962 | // custom message if logged-in users without any special rights can move  | 
            ||
| 1963 | $errors[] = [ 'movenologintext' ];  | 
            ||
| 1964 | 				} else { | 
            ||
| 1965 | $errors[] = [ 'movenotallowed' ];  | 
            ||
| 1966 | }  | 
            ||
| 1967 | }  | 
            ||
| 1968 | 		} elseif ( $action == 'move-target' ) { | 
            ||
| 1969 | 			if ( !$user->isAllowed( 'move' ) ) { | 
            ||
| 1970 | // User can't move anything  | 
            ||
| 1971 | $errors[] = [ 'movenotallowed' ];  | 
            ||
| 1972 | View Code Duplication | } elseif ( !$user->isAllowed( 'move-rootuserpages' )  | 
            |
| 1973 | 					&& $this->mNamespace == NS_USER && !$this->isSubpage() ) { | 
            ||
| 1974 | // Show user page-specific message only if the user can move other pages  | 
            ||
| 1975 | $errors[] = [ 'cant-move-to-user-page' ];  | 
            ||
| 1976 | } elseif ( !$user->isAllowed( 'move-categorypages' )  | 
            ||
| 1977 | 					&& $this->mNamespace == NS_CATEGORY ) { | 
            ||
| 1978 | // Show category page-specific message only if the user can move other pages  | 
            ||
| 1979 | $errors[] = [ 'cant-move-to-category-page' ];  | 
            ||
| 1980 | }  | 
            ||
| 1981 | 		} elseif ( !$user->isAllowed( $action ) ) { | 
            ||
| 1982 | $errors[] = $this->missingPermissionError( $action, $short );  | 
            ||
| 1983 | }  | 
            ||
| 1984 | |||
| 1985 | return $errors;  | 
            ||
| 1986 | }  | 
            ||
| 1987 | |||
| 1988 | /**  | 
            ||
| 1989 | * Add the resulting error code to the errors array  | 
            ||
| 1990 | *  | 
            ||
| 1991 | * @param array $errors List of current errors  | 
            ||
| 1992 | * @param array $result Result of errors  | 
            ||
| 1993 | *  | 
            ||
| 1994 | * @return array List of errors  | 
            ||
| 1995 | */  | 
            ||
| 1996 | 	private function resultToError( $errors, $result ) { | 
            ||
| 1997 | 		if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) { | 
            ||
| 1998 | // A single array representing an error  | 
            ||
| 1999 | $errors[] = $result;  | 
            ||
| 2000 | 		} elseif ( is_array( $result ) && is_array( $result[0] ) ) { | 
            ||
| 2001 | // A nested array representing multiple errors  | 
            ||
| 2002 | $errors = array_merge( $errors, $result );  | 
            ||
| 2003 | 		} elseif ( $result !== '' && is_string( $result ) ) { | 
            ||
| 2004 | // A string representing a message-id  | 
            ||
| 2005 | $errors[] = [ $result ];  | 
            ||
| 2006 | 		} elseif ( $result instanceof MessageSpecifier ) { | 
            ||
| 2007 | // A message specifier representing an error  | 
            ||
| 2008 | $errors[] = [ $result ];  | 
            ||
| 2009 | 		} elseif ( $result === false ) { | 
            ||
| 2010 | // a generic "We don't want them to do that"  | 
            ||
| 2011 | $errors[] = [ 'badaccess-group0' ];  | 
            ||
| 2012 | }  | 
            ||
| 2013 | return $errors;  | 
            ||
| 2014 | }  | 
            ||
| 2015 | |||
| 2016 | /**  | 
            ||
| 2017 | * Check various permission hooks  | 
            ||
| 2018 | *  | 
            ||
| 2019 | * @param string $action The action to check  | 
            ||
| 2020 | * @param User $user User to check  | 
            ||
| 2021 | * @param array $errors List of current errors  | 
            ||
| 2022 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 2023 | * @param bool $short Short circuit on first error  | 
            ||
| 2024 | *  | 
            ||
| 2025 | * @return array List of errors  | 
            ||
| 2026 | */  | 
            ||
| 2027 | 	private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) { | 
            ||
| 2028 | // Use getUserPermissionsErrors instead  | 
            ||
| 2029 | $result = '';  | 
            ||
| 2030 | 		if ( !Hooks::run( 'userCan', [ &$this, &$user, $action, &$result ] ) ) { | 
            ||
| 2031 | return $result ? [] : [ [ 'badaccess-group0' ] ];  | 
            ||
| 2032 | }  | 
            ||
| 2033 | // Check getUserPermissionsErrors hook  | 
            ||
| 2034 | 		if ( !Hooks::run( 'getUserPermissionsErrors', [ &$this, &$user, $action, &$result ] ) ) { | 
            ||
| 2035 | $errors = $this->resultToError( $errors, $result );  | 
            ||
| 2036 | }  | 
            ||
| 2037 | // Check getUserPermissionsErrorsExpensive hook  | 
            ||
| 2038 | if (  | 
            ||
| 2039 | $rigor !== 'quick'  | 
            ||
| 2040 | && !( $short && count( $errors ) > 0 )  | 
            ||
| 2041 | && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$this, &$user, $action, &$result ] )  | 
            ||
| 2042 | 		) { | 
            ||
| 2043 | $errors = $this->resultToError( $errors, $result );  | 
            ||
| 2044 | }  | 
            ||
| 2045 | |||
| 2046 | return $errors;  | 
            ||
| 2047 | }  | 
            ||
| 2048 | |||
| 2049 | /**  | 
            ||
| 2050 | * Check permissions on special pages & namespaces  | 
            ||
| 2051 | *  | 
            ||
| 2052 | * @param string $action The action to check  | 
            ||
| 2053 | * @param User $user User to check  | 
            ||
| 2054 | * @param array $errors List of current errors  | 
            ||
| 2055 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 2056 | * @param bool $short Short circuit on first error  | 
            ||
| 2057 | *  | 
            ||
| 2058 | * @return array List of errors  | 
            ||
| 2059 | */  | 
            ||
| 2060 | 	private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) { | 
            ||
| 2061 | # Only 'createaccount' can be performed on special pages,  | 
            ||
| 2062 | # which don't actually exist in the DB.  | 
            ||
| 2063 | 		if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) { | 
            ||
| 2064 | $errors[] = [ 'ns-specialprotected' ];  | 
            ||
| 2065 | }  | 
            ||
| 2066 | |||
| 2067 | # Check $wgNamespaceProtection for restricted namespaces  | 
            ||
| 2068 | 		if ( $this->isNamespaceProtected( $user ) ) { | 
            ||
| 2069 | $ns = $this->mNamespace == NS_MAIN ?  | 
            ||
| 2070 | wfMessage( 'nstab-main' )->text() : $this->getNsText();  | 
            ||
| 2071 | $errors[] = $this->mNamespace == NS_MEDIAWIKI ?  | 
            ||
| 2072 | [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];  | 
            ||
| 2073 | }  | 
            ||
| 2074 | |||
| 2075 | return $errors;  | 
            ||
| 2076 | }  | 
            ||
| 2077 | |||
| 2078 | /**  | 
            ||
| 2079 | * Check CSS/JS sub-page permissions  | 
            ||
| 2080 | *  | 
            ||
| 2081 | * @param string $action The action to check  | 
            ||
| 2082 | * @param User $user User to check  | 
            ||
| 2083 | * @param array $errors List of current errors  | 
            ||
| 2084 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 2085 | * @param bool $short Short circuit on first error  | 
            ||
| 2086 | *  | 
            ||
| 2087 | * @return array List of errors  | 
            ||
| 2088 | */  | 
            ||
| 2089 | 	private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) { | 
            ||
| 2090 | # Protect css/js subpages of user pages  | 
            ||
| 2091 | # XXX: this might be better using restrictions  | 
            ||
| 2092 | # XXX: right 'editusercssjs' is deprecated, for backward compatibility only  | 
            ||
| 2093 | 		if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) { | 
            ||
| 2094 | 			if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) { | 
            ||
| 2095 | View Code Duplication | 				if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) { | 
            |
| 2096 | $errors[] = [ 'mycustomcssprotected', $action ];  | 
            ||
| 2097 | 				} elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) { | 
            ||
| 2098 | $errors[] = [ 'mycustomjsprotected', $action ];  | 
            ||
| 2099 | }  | 
            ||
| 2100 | View Code Duplication | 			} else { | 
            |
| 2101 | 				if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) { | 
            ||
| 2102 | $errors[] = [ 'customcssprotected', $action ];  | 
            ||
| 2103 | 				} elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) { | 
            ||
| 2104 | $errors[] = [ 'customjsprotected', $action ];  | 
            ||
| 2105 | }  | 
            ||
| 2106 | }  | 
            ||
| 2107 | }  | 
            ||
| 2108 | |||
| 2109 | return $errors;  | 
            ||
| 2110 | }  | 
            ||
| 2111 | |||
| 2112 | /**  | 
            ||
| 2113 | * Check against page_restrictions table requirements on this  | 
            ||
| 2114 | * page. The user must possess all required rights for this  | 
            ||
| 2115 | * action.  | 
            ||
| 2116 | *  | 
            ||
| 2117 | * @param string $action The action to check  | 
            ||
| 2118 | * @param User $user User to check  | 
            ||
| 2119 | * @param array $errors List of current errors  | 
            ||
| 2120 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 2121 | * @param bool $short Short circuit on first error  | 
            ||
| 2122 | *  | 
            ||
| 2123 | * @return array List of errors  | 
            ||
| 2124 | */  | 
            ||
| 2125 | 	private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) { | 
            ||
| 2126 | 		foreach ( $this->getRestrictions( $action ) as $right ) { | 
            ||
| 2127 | // Backwards compatibility, rewrite sysop -> editprotected  | 
            ||
| 2128 | 			if ( $right == 'sysop' ) { | 
            ||
| 2129 | $right = 'editprotected';  | 
            ||
| 2130 | }  | 
            ||
| 2131 | // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected  | 
            ||
| 2132 | 			if ( $right == 'autoconfirmed' ) { | 
            ||
| 2133 | $right = 'editsemiprotected';  | 
            ||
| 2134 | }  | 
            ||
| 2135 | 			if ( $right == '' ) { | 
            ||
| 2136 | continue;  | 
            ||
| 2137 | }  | 
            ||
| 2138 | 			if ( !$user->isAllowed( $right ) ) { | 
            ||
| 2139 | $errors[] = [ 'protectedpagetext', $right, $action ];  | 
            ||
| 2140 | 			} elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) { | 
            ||
| 2141 | $errors[] = [ 'protectedpagetext', 'protect', $action ];  | 
            ||
| 2142 | }  | 
            ||
| 2143 | }  | 
            ||
| 2144 | |||
| 2145 | return $errors;  | 
            ||
| 2146 | }  | 
            ||
| 2147 | |||
| 2148 | /**  | 
            ||
| 2149 | * Check restrictions on cascading pages.  | 
            ||
| 2150 | *  | 
            ||
| 2151 | * @param string $action The action to check  | 
            ||
| 2152 | * @param User $user User to check  | 
            ||
| 2153 | * @param array $errors List of current errors  | 
            ||
| 2154 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 2155 | * @param bool $short Short circuit on first error  | 
            ||
| 2156 | *  | 
            ||
| 2157 | * @return array List of errors  | 
            ||
| 2158 | */  | 
            ||
| 2159 | 	private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) { | 
            ||
| 2160 | 		if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) { | 
            ||
| 2161 | # We /could/ use the protection level on the source page, but it's  | 
            ||
| 2162 | # fairly ugly as we have to establish a precedence hierarchy for pages  | 
            ||
| 2163 | # included by multiple cascade-protected pages. So just restrict  | 
            ||
| 2164 | # it to people with 'protect' permission, as they could remove the  | 
            ||
| 2165 | # protection anyway.  | 
            ||
| 2166 | list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();  | 
            ||
| 2167 | # Cascading protection depends on more than this page...  | 
            ||
| 2168 | # Several cascading protected pages may include this page...  | 
            ||
| 2169 | # Check each cascading level  | 
            ||
| 2170 | # This is only for protection restrictions, not for all actions  | 
            ||
| 2171 | 			if ( isset( $restrictions[$action] ) ) { | 
            ||
| 2172 | 				foreach ( $restrictions[$action] as $right ) { | 
            ||
| 2173 | // Backwards compatibility, rewrite sysop -> editprotected  | 
            ||
| 2174 | 					if ( $right == 'sysop' ) { | 
            ||
| 2175 | $right = 'editprotected';  | 
            ||
| 2176 | }  | 
            ||
| 2177 | // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected  | 
            ||
| 2178 | 					if ( $right == 'autoconfirmed' ) { | 
            ||
| 2179 | $right = 'editsemiprotected';  | 
            ||
| 2180 | }  | 
            ||
| 2181 | 					if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) { | 
            ||
| 2182 | $pages = '';  | 
            ||
| 2183 | 						foreach ( $cascadingSources as $page ) { | 
            ||
| 2184 | $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";  | 
            ||
| 2185 | }  | 
            ||
| 2186 | $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];  | 
            ||
| 2187 | }  | 
            ||
| 2188 | }  | 
            ||
| 2189 | }  | 
            ||
| 2190 | }  | 
            ||
| 2191 | |||
| 2192 | return $errors;  | 
            ||
| 2193 | }  | 
            ||
| 2194 | |||
| 2195 | /**  | 
            ||
| 2196 | * Check action permissions not already checked in checkQuickPermissions  | 
            ||
| 2197 | *  | 
            ||
| 2198 | * @param string $action The action to check  | 
            ||
| 2199 | * @param User $user User to check  | 
            ||
| 2200 | * @param array $errors List of current errors  | 
            ||
| 2201 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 2202 | * @param bool $short Short circuit on first error  | 
            ||
| 2203 | *  | 
            ||
| 2204 | * @return array List of errors  | 
            ||
| 2205 | */  | 
            ||
| 2206 | 	private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) { | 
            ||
| 2207 | global $wgDeleteRevisionsLimit, $wgLang;  | 
            ||
| 2208 | |||
| 2209 | 		if ( $action == 'protect' ) { | 
            ||
| 2210 | 			if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) { | 
            ||
| 2211 | // If they can't edit, they shouldn't protect.  | 
            ||
| 2212 | $errors[] = [ 'protect-cantedit' ];  | 
            ||
| 2213 | }  | 
            ||
| 2214 | 		} elseif ( $action == 'create' ) { | 
            ||
| 2215 | $title_protection = $this->getTitleProtection();  | 
            ||
| 2216 | 			if ( $title_protection ) { | 
            ||
| 2217 | if ( $title_protection['permission'] == ''  | 
            ||
| 2218 | || !$user->isAllowed( $title_protection['permission'] )  | 
            ||
| 2219 | 				) { | 
            ||
| 2220 | $errors[] = [  | 
            ||
| 2221 | 'titleprotected',  | 
            ||
| 2222 | User::whoIs( $title_protection['user'] ),  | 
            ||
| 2223 | $title_protection['reason']  | 
            ||
| 2224 | ];  | 
            ||
| 2225 | }  | 
            ||
| 2226 | }  | 
            ||
| 2227 | 		} elseif ( $action == 'move' ) { | 
            ||
| 2228 | // Check for immobile pages  | 
            ||
| 2229 | 			if ( !MWNamespace::isMovable( $this->mNamespace ) ) { | 
            ||
| 2230 | // Specific message for this case  | 
            ||
| 2231 | $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];  | 
            ||
| 2232 | 			} elseif ( !$this->isMovable() ) { | 
            ||
| 2233 | // Less specific message for rarer cases  | 
            ||
| 2234 | $errors[] = [ 'immobile-source-page' ];  | 
            ||
| 2235 | }  | 
            ||
| 2236 | 		} elseif ( $action == 'move-target' ) { | 
            ||
| 2237 | 			if ( !MWNamespace::isMovable( $this->mNamespace ) ) { | 
            ||
| 2238 | $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];  | 
            ||
| 2239 | 			} elseif ( !$this->isMovable() ) { | 
            ||
| 2240 | $errors[] = [ 'immobile-target-page' ];  | 
            ||
| 2241 | }  | 
            ||
| 2242 | 		} elseif ( $action == 'delete' ) { | 
            ||
| 2243 | $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );  | 
            ||
| 2244 | 			if ( !$tempErrors ) { | 
            ||
| 2245 | $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',  | 
            ||
| 2246 | $user, $tempErrors, $rigor, true );  | 
            ||
| 2247 | }  | 
            ||
| 2248 | 			if ( $tempErrors ) { | 
            ||
| 2249 | // If protection keeps them from editing, they shouldn't be able to delete.  | 
            ||
| 2250 | $errors[] = [ 'deleteprotected' ];  | 
            ||
| 2251 | }  | 
            ||
| 2252 | if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit  | 
            ||
| 2253 | && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()  | 
            ||
| 2254 | 			) { | 
            ||
| 2255 | $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];  | 
            ||
| 2256 | }  | 
            ||
| 2257 | }  | 
            ||
| 2258 | return $errors;  | 
            ||
| 2259 | }  | 
            ||
| 2260 | |||
| 2261 | /**  | 
            ||
| 2262 | * Check that the user isn't blocked from editing.  | 
            ||
| 2263 | *  | 
            ||
| 2264 | * @param string $action The action to check  | 
            ||
| 2265 | * @param User $user User to check  | 
            ||
| 2266 | * @param array $errors List of current errors  | 
            ||
| 2267 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 2268 | * @param bool $short Short circuit on first error  | 
            ||
| 2269 | *  | 
            ||
| 2270 | * @return array List of errors  | 
            ||
| 2271 | */  | 
            ||
| 2272 | 	private function checkUserBlock( $action, $user, $errors, $rigor, $short ) { | 
            ||
| 2273 | // Account creation blocks handled at userlogin.  | 
            ||
| 2274 | // Unblocking handled in SpecialUnblock  | 
            ||
| 2275 | 		if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) { | 
            ||
| 2276 | return $errors;  | 
            ||
| 2277 | }  | 
            ||
| 2278 | |||
| 2279 | global $wgEmailConfirmToEdit;  | 
            ||
| 2280 | |||
| 2281 | 		if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) { | 
            ||
| 2282 | $errors[] = [ 'confirmedittext' ];  | 
            ||
| 2283 | }  | 
            ||
| 2284 | |||
| 2285 | $useSlave = ( $rigor !== 'secure' );  | 
            ||
| 2286 | if ( ( $action == 'edit' || $action == 'create' )  | 
            ||
| 2287 | && !$user->isBlockedFrom( $this, $useSlave )  | 
            ||
| 2288 | 		) { | 
            ||
| 2289 | // Don't block the user from editing their own talk page unless they've been  | 
            ||
| 2290 | // explicitly blocked from that too.  | 
            ||
| 2291 | 		} elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) { | 
            ||
| 2292 | // @todo FIXME: Pass the relevant context into this function.  | 
            ||
| 2293 | $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );  | 
            ||
| 2294 | }  | 
            ||
| 2295 | |||
| 2296 | return $errors;  | 
            ||
| 2297 | }  | 
            ||
| 2298 | |||
| 2299 | /**  | 
            ||
| 2300 | * Check that the user is allowed to read this page.  | 
            ||
| 2301 | *  | 
            ||
| 2302 | * @param string $action The action to check  | 
            ||
| 2303 | * @param User $user User to check  | 
            ||
| 2304 | * @param array $errors List of current errors  | 
            ||
| 2305 | * @param string $rigor Same format as Title::getUserPermissionsErrors()  | 
            ||
| 2306 | * @param bool $short Short circuit on first error  | 
            ||
| 2307 | *  | 
            ||
| 2308 | * @return array List of errors  | 
            ||
| 2309 | */  | 
            ||
| 2310 | 	private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) { | 
            ||
| 2311 | global $wgWhitelistRead, $wgWhitelistReadRegexp;  | 
            ||
| 2312 | |||
| 2313 | $whitelisted = false;  | 
            ||
| 2314 | 		if ( User::isEveryoneAllowed( 'read' ) ) { | 
            ||
| 2315 | # Shortcut for public wikis, allows skipping quite a bit of code  | 
            ||
| 2316 | $whitelisted = true;  | 
            ||
| 2317 | 		} elseif ( $user->isAllowed( 'read' ) ) { | 
            ||
| 2318 | # If the user is allowed to read pages, he is allowed to read all pages  | 
            ||
| 2319 | $whitelisted = true;  | 
            ||
| 2320 | } elseif ( $this->isSpecial( 'Userlogin' )  | 
            ||
| 2321 | || $this->isSpecial( 'ChangePassword' )  | 
            ||
| 2322 | || $this->isSpecial( 'PasswordReset' )  | 
            ||
| 2323 | 		) { | 
            ||
| 2324 | # Always grant access to the login page.  | 
            ||
| 2325 | # Even anons need to be able to log in.  | 
            ||
| 2326 | $whitelisted = true;  | 
            ||
| 2327 | 		} elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) { | 
            ||
| 2328 | # Time to check the whitelist  | 
            ||
| 2329 | # Only do these checks is there's something to check against  | 
            ||
| 2330 | $name = $this->getPrefixedText();  | 
            ||
| 2331 | $dbName = $this->getPrefixedDBkey();  | 
            ||
| 2332 | |||
| 2333 | // Check for explicit whitelisting with and without underscores  | 
            ||
| 2334 | 			if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) { | 
            ||
| 2335 | $whitelisted = true;  | 
            ||
| 2336 | 			} elseif ( $this->getNamespace() == NS_MAIN ) { | 
            ||
| 2337 | # Old settings might have the title prefixed with  | 
            ||
| 2338 | # a colon for main-namespace pages  | 
            ||
| 2339 | 				if ( in_array( ':' . $name, $wgWhitelistRead ) ) { | 
            ||
| 2340 | $whitelisted = true;  | 
            ||
| 2341 | }  | 
            ||
| 2342 | 			} elseif ( $this->isSpecialPage() ) { | 
            ||
| 2343 | # If it's a special page, ditch the subpage bit and check again  | 
            ||
| 2344 | $name = $this->getDBkey();  | 
            ||
| 2345 | list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );  | 
            ||
| 2346 | 				if ( $name ) { | 
            ||
| 2347 | $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();  | 
            ||
| 2348 | 					if ( in_array( $pure, $wgWhitelistRead, true ) ) { | 
            ||
| 2349 | $whitelisted = true;  | 
            ||
| 2350 | }  | 
            ||
| 2351 | }  | 
            ||
| 2352 | }  | 
            ||
| 2353 | }  | 
            ||
| 2354 | |||
| 2355 | 		if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) { | 
            ||
| 2356 | $name = $this->getPrefixedText();  | 
            ||
| 2357 | // Check for regex whitelisting  | 
            ||
| 2358 | 			foreach ( $wgWhitelistReadRegexp as $listItem ) { | 
            ||
| 2359 | 				if ( preg_match( $listItem, $name ) ) { | 
            ||
| 2360 | $whitelisted = true;  | 
            ||
| 2361 | break;  | 
            ||
| 2362 | }  | 
            ||
| 2363 | }  | 
            ||
| 2364 | }  | 
            ||
| 2365 | |||
| 2366 | 		if ( !$whitelisted ) { | 
            ||
| 2367 | # If the title is not whitelisted, give extensions a chance to do so...  | 
            ||
| 2368 | Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );  | 
            ||
| 2369 | 			if ( !$whitelisted ) { | 
            ||
| 2370 | $errors[] = $this->missingPermissionError( $action, $short );  | 
            ||
| 2371 | }  | 
            ||
| 2372 | }  | 
            ||
| 2373 | |||
| 2374 | return $errors;  | 
            ||
| 2375 | }  | 
            ||
| 2376 | |||
| 2377 | /**  | 
            ||
| 2378 | * Get a description array when the user doesn't have the right to perform  | 
            ||
| 2379 | * $action (i.e. when User::isAllowed() returns false)  | 
            ||
| 2380 | *  | 
            ||
| 2381 | * @param string $action The action to check  | 
            ||
| 2382 | * @param bool $short Short circuit on first error  | 
            ||
| 2383 | * @return array List of errors  | 
            ||
| 2384 | */  | 
            ||
| 2385 | 	private function missingPermissionError( $action, $short ) { | 
            ||
| 2386 | // We avoid expensive display logic for quickUserCan's and such  | 
            ||
| 2387 | 		if ( $short ) { | 
            ||
| 2388 | return [ 'badaccess-group0' ];  | 
            ||
| 2389 | }  | 
            ||
| 2390 | |||
| 2391 | $groups = array_map( [ 'User', 'makeGroupLinkWiki' ],  | 
            ||
| 2392 | User::getGroupsWithPermission( $action ) );  | 
            ||
| 2393 | |||
| 2394 | 		if ( count( $groups ) ) { | 
            ||
| 2395 | global $wgLang;  | 
            ||
| 2396 | return [  | 
            ||
| 2397 | 'badaccess-groups',  | 
            ||
| 2398 | $wgLang->commaList( $groups ),  | 
            ||
| 2399 | count( $groups )  | 
            ||
| 2400 | ];  | 
            ||
| 2401 | 		} else { | 
            ||
| 2402 | return [ 'badaccess-group0' ];  | 
            ||
| 2403 | }  | 
            ||
| 2404 | }  | 
            ||
| 2405 | |||
| 2406 | /**  | 
            ||
| 2407 | * Can $user perform $action on this page? This is an internal function,  | 
            ||
| 2408 | * with multiple levels of checks depending on performance needs; see $rigor below.  | 
            ||
| 2409 | * It does not check wfReadOnly().  | 
            ||
| 2410 | *  | 
            ||
| 2411 | * @param string $action Action that permission needs to be checked for  | 
            ||
| 2412 | * @param User $user User to check  | 
            ||
| 2413 | * @param string $rigor One of (quick,full,secure)  | 
            ||
| 2414 | * - quick : does cheap permission checks from slaves (usable for GUI creation)  | 
            ||
| 2415 | * - full : does cheap and expensive checks possibly from a slave  | 
            ||
| 2416 | * - secure : does cheap and expensive checks, using the master as needed  | 
            ||
| 2417 | * @param bool $short Set this to true to stop after the first permission error.  | 
            ||
| 2418 | * @return array Array of arrays of the arguments to wfMessage to explain permissions problems.  | 
            ||
| 2419 | */  | 
            ||
| 2420 | protected function getUserPermissionsErrorsInternal(  | 
            ||
| 2421 | $action, $user, $rigor = 'secure', $short = false  | 
            ||
| 2422 | 	) { | 
            ||
| 2423 | 		if ( $rigor === true ) { | 
            ||
| 2424 | $rigor = 'secure'; // b/c  | 
            ||
| 2425 | 		} elseif ( $rigor === false ) { | 
            ||
| 2426 | $rigor = 'quick'; // b/c  | 
            ||
| 2427 | 		} elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) { | 
            ||
| 2428 | throw new Exception( "Invalid rigor parameter '$rigor'." );  | 
            ||
| 2429 | }  | 
            ||
| 2430 | |||
| 2431 | # Read has special handling  | 
            ||
| 2432 | 		if ( $action == 'read' ) { | 
            ||
| 2433 | $checks = [  | 
            ||
| 2434 | 'checkPermissionHooks',  | 
            ||
| 2435 | 'checkReadPermissions',  | 
            ||
| 2436 | ];  | 
            ||
| 2437 | # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions  | 
            ||
| 2438 | # here as it will lead to duplicate error messages. This is okay to do  | 
            ||
| 2439 | # since anywhere that checks for create will also check for edit, and  | 
            ||
| 2440 | # those checks are called for edit.  | 
            ||
| 2441 | 		} elseif ( $action == 'create' ) { | 
            ||
| 2442 | $checks = [  | 
            ||
| 2443 | 'checkQuickPermissions',  | 
            ||
| 2444 | 'checkPermissionHooks',  | 
            ||
| 2445 | 'checkPageRestrictions',  | 
            ||
| 2446 | 'checkCascadingSourcesRestrictions',  | 
            ||
| 2447 | 'checkActionPermissions',  | 
            ||
| 2448 | 'checkUserBlock'  | 
            ||
| 2449 | ];  | 
            ||
| 2450 | 		} else { | 
            ||
| 2451 | $checks = [  | 
            ||
| 2452 | 'checkQuickPermissions',  | 
            ||
| 2453 | 'checkPermissionHooks',  | 
            ||
| 2454 | 'checkSpecialsAndNSPermissions',  | 
            ||
| 2455 | 'checkCSSandJSPermissions',  | 
            ||
| 2456 | 'checkPageRestrictions',  | 
            ||
| 2457 | 'checkCascadingSourcesRestrictions',  | 
            ||
| 2458 | 'checkActionPermissions',  | 
            ||
| 2459 | 'checkUserBlock'  | 
            ||
| 2460 | ];  | 
            ||
| 2461 | }  | 
            ||
| 2462 | |||
| 2463 | $errors = [];  | 
            ||
| 2464 | while ( count( $checks ) > 0 &&  | 
            ||
| 2465 | 				!( $short && count( $errors ) > 0 ) ) { | 
            ||
| 2466 | $method = array_shift( $checks );  | 
            ||
| 2467 | $errors = $this->$method( $action, $user, $errors, $rigor, $short );  | 
            ||
| 2468 | }  | 
            ||
| 2469 | |||
| 2470 | return $errors;  | 
            ||
| 2471 | }  | 
            ||
| 2472 | |||
| 2473 | /**  | 
            ||
| 2474 | * Get a filtered list of all restriction types supported by this wiki.  | 
            ||
| 2475 | * @param bool $exists True to get all restriction types that apply to  | 
            ||
| 2476 | * titles that do exist, False for all restriction types that apply to  | 
            ||
| 2477 | * titles that do not exist  | 
            ||
| 2478 | * @return array  | 
            ||
| 2479 | */  | 
            ||
| 2480 | 	public static function getFilteredRestrictionTypes( $exists = true ) { | 
            ||
| 2481 | global $wgRestrictionTypes;  | 
            ||
| 2482 | $types = $wgRestrictionTypes;  | 
            ||
| 2483 | 		if ( $exists ) { | 
            ||
| 2484 | # Remove the create restriction for existing titles  | 
            ||
| 2485 | $types = array_diff( $types, [ 'create' ] );  | 
            ||
| 2486 | 		} else { | 
            ||
| 2487 | # Only the create and upload restrictions apply to non-existing titles  | 
            ||
| 2488 | $types = array_intersect( $types, [ 'create', 'upload' ] );  | 
            ||
| 2489 | }  | 
            ||
| 2490 | return $types;  | 
            ||
| 2491 | }  | 
            ||
| 2492 | |||
| 2493 | /**  | 
            ||
| 2494 | * Returns restriction types for the current Title  | 
            ||
| 2495 | *  | 
            ||
| 2496 | * @return array Applicable restriction types  | 
            ||
| 2497 | */  | 
            ||
| 2498 | 	public function getRestrictionTypes() { | 
            ||
| 2499 | 		if ( $this->isSpecialPage() ) { | 
            ||
| 2500 | return [];  | 
            ||
| 2501 | }  | 
            ||
| 2502 | |||
| 2503 | $types = self::getFilteredRestrictionTypes( $this->exists() );  | 
            ||
| 2504 | |||
| 2505 | 		if ( $this->getNamespace() != NS_FILE ) { | 
            ||
| 2506 | # Remove the upload restriction for non-file titles  | 
            ||
| 2507 | $types = array_diff( $types, [ 'upload' ] );  | 
            ||
| 2508 | }  | 
            ||
| 2509 | |||
| 2510 | Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );  | 
            ||
| 2511 | |||
| 2512 | wfDebug( __METHOD__ . ': applicable restrictions to [[' .  | 
            ||
| 2513 | 			$this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" ); | 
            ||
| 2514 | |||
| 2515 | return $types;  | 
            ||
| 2516 | }  | 
            ||
| 2517 | |||
| 2518 | /**  | 
            ||
| 2519 | * Is this title subject to title protection?  | 
            ||
| 2520 | * Title protection is the one applied against creation of such title.  | 
            ||
| 2521 | *  | 
            ||
| 2522 | * @return array|bool An associative array representing any existent title  | 
            ||
| 2523 | * protection, or false if there's none.  | 
            ||
| 2524 | */  | 
            ||
| 2525 | 	public function getTitleProtection() { | 
            ||
| 2526 | // Can't protect pages in special namespaces  | 
            ||
| 2527 | 		if ( $this->getNamespace() < 0 ) { | 
            ||
| 2528 | return false;  | 
            ||
| 2529 | }  | 
            ||
| 2530 | |||
| 2531 | // Can't protect pages that exist.  | 
            ||
| 2532 | 		if ( $this->exists() ) { | 
            ||
| 2533 | return false;  | 
            ||
| 2534 | }  | 
            ||
| 2535 | |||
| 2536 | 		if ( $this->mTitleProtection === null ) { | 
            ||
| 2537 | $dbr = wfGetDB( DB_SLAVE );  | 
            ||
| 2538 | $res = $dbr->select(  | 
            ||
| 2539 | 'protected_titles',  | 
            ||
| 2540 | [  | 
            ||
| 2541 | 'user' => 'pt_user',  | 
            ||
| 2542 | 'reason' => 'pt_reason',  | 
            ||
| 2543 | 'expiry' => 'pt_expiry',  | 
            ||
| 2544 | 'permission' => 'pt_create_perm'  | 
            ||
| 2545 | ],  | 
            ||
| 2546 | [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],  | 
            ||
| 2547 | __METHOD__  | 
            ||
| 2548 | );  | 
            ||
| 2549 | |||
| 2550 | // fetchRow returns false if there are no rows.  | 
            ||
| 2551 | $row = $dbr->fetchRow( $res );  | 
            ||
| 2552 | 			if ( $row ) { | 
            ||
| 2553 | 				if ( $row['permission'] == 'sysop' ) { | 
            ||
| 2554 | $row['permission'] = 'editprotected'; // B/C  | 
            ||
| 2555 | }  | 
            ||
| 2556 | 				if ( $row['permission'] == 'autoconfirmed' ) { | 
            ||
| 2557 | $row['permission'] = 'editsemiprotected'; // B/C  | 
            ||
| 2558 | }  | 
            ||
| 2559 | $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );  | 
            ||
| 2560 | }  | 
            ||
| 2561 | $this->mTitleProtection = $row;  | 
            ||
| 2562 | }  | 
            ||
| 2563 | return $this->mTitleProtection;  | 
            ||
| 2564 | }  | 
            ||
| 2565 | |||
| 2566 | /**  | 
            ||
| 2567 | * Remove any title protection due to page existing  | 
            ||
| 2568 | */  | 
            ||
| 2569 | 	public function deleteTitleProtection() { | 
            ||
| 2570 | $dbw = wfGetDB( DB_MASTER );  | 
            ||
| 2571 | |||
| 2572 | $dbw->delete(  | 
            ||
| 2573 | 'protected_titles',  | 
            ||
| 2574 | [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],  | 
            ||
| 2575 | __METHOD__  | 
            ||
| 2576 | );  | 
            ||
| 2577 | $this->mTitleProtection = false;  | 
            ||
| 2578 | }  | 
            ||
| 2579 | |||
| 2580 | /**  | 
            ||
| 2581 | * Is this page "semi-protected" - the *only* protection levels are listed  | 
            ||
| 2582 | * in $wgSemiprotectedRestrictionLevels?  | 
            ||
| 2583 | *  | 
            ||
| 2584 | * @param string $action Action to check (default: edit)  | 
            ||
| 2585 | * @return bool  | 
            ||
| 2586 | */  | 
            ||
| 2587 | 	public function isSemiProtected( $action = 'edit' ) { | 
            ||
| 2588 | global $wgSemiprotectedRestrictionLevels;  | 
            ||
| 2589 | |||
| 2590 | $restrictions = $this->getRestrictions( $action );  | 
            ||
| 2591 | $semi = $wgSemiprotectedRestrictionLevels;  | 
            ||
| 2592 | 		if ( !$restrictions || !$semi ) { | 
            ||
| 2593 | // Not protected, or all protection is full protection  | 
            ||
| 2594 | return false;  | 
            ||
| 2595 | }  | 
            ||
| 2596 | |||
| 2597 | // Remap autoconfirmed to editsemiprotected for BC  | 
            ||
| 2598 | 		foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) { | 
            ||
| 2599 | $semi[$key] = 'editsemiprotected';  | 
            ||
| 2600 | }  | 
            ||
| 2601 | 		foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) { | 
            ||
| 2602 | $restrictions[$key] = 'editsemiprotected';  | 
            ||
| 2603 | }  | 
            ||
| 2604 | |||
| 2605 | return !array_diff( $restrictions, $semi );  | 
            ||
| 2606 | }  | 
            ||
| 2607 | |||
| 2608 | /**  | 
            ||
| 2609 | * Does the title correspond to a protected article?  | 
            ||
| 2610 | *  | 
            ||
| 2611 | * @param string $action The action the page is protected from,  | 
            ||
| 2612 | * by default checks all actions.  | 
            ||
| 2613 | * @return bool  | 
            ||
| 2614 | */  | 
            ||
| 2615 | 	public function isProtected( $action = '' ) { | 
            ||
| 2616 | global $wgRestrictionLevels;  | 
            ||
| 2617 | |||
| 2618 | $restrictionTypes = $this->getRestrictionTypes();  | 
            ||
| 2619 | |||
| 2620 | # Special pages have inherent protection  | 
            ||
| 2621 | 		if ( $this->isSpecialPage() ) { | 
            ||
| 2622 | return true;  | 
            ||
| 2623 | }  | 
            ||
| 2624 | |||
| 2625 | # Check regular protection levels  | 
            ||
| 2626 | 		foreach ( $restrictionTypes as $type ) { | 
            ||
| 2627 | 			if ( $action == $type || $action == '' ) { | 
            ||
| 2628 | $r = $this->getRestrictions( $type );  | 
            ||
| 2629 | 				foreach ( $wgRestrictionLevels as $level ) { | 
            ||
| 2630 | 					if ( in_array( $level, $r ) && $level != '' ) { | 
            ||
| 2631 | return true;  | 
            ||
| 2632 | }  | 
            ||
| 2633 | }  | 
            ||
| 2634 | }  | 
            ||
| 2635 | }  | 
            ||
| 2636 | |||
| 2637 | return false;  | 
            ||
| 2638 | }  | 
            ||
| 2639 | |||
| 2640 | /**  | 
            ||
| 2641 | * Determines if $user is unable to edit this page because it has been protected  | 
            ||
| 2642 | * by $wgNamespaceProtection.  | 
            ||
| 2643 | *  | 
            ||
| 2644 | * @param User $user User object to check permissions  | 
            ||
| 2645 | * @return bool  | 
            ||
| 2646 | */  | 
            ||
| 2647 | 	public function isNamespaceProtected( User $user ) { | 
            ||
| 2648 | global $wgNamespaceProtection;  | 
            ||
| 2649 | |||
| 2650 | 		if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) { | 
            ||
| 2651 | 			foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) { | 
            ||
| 2652 | 				if ( $right != '' && !$user->isAllowed( $right ) ) { | 
            ||
| 2653 | return true;  | 
            ||
| 2654 | }  | 
            ||
| 2655 | }  | 
            ||
| 2656 | }  | 
            ||
| 2657 | return false;  | 
            ||
| 2658 | }  | 
            ||
| 2659 | |||
| 2660 | /**  | 
            ||
| 2661 | * Cascading protection: Return true if cascading restrictions apply to this page, false if not.  | 
            ||
| 2662 | *  | 
            ||
| 2663 | * @return bool If the page is subject to cascading restrictions.  | 
            ||
| 2664 | */  | 
            ||
| 2665 | 	public function isCascadeProtected() { | 
            ||
| 2666 | list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );  | 
            ||
| 2667 | return ( $sources > 0 );  | 
            ||
| 2668 | }  | 
            ||
| 2669 | |||
| 2670 | /**  | 
            ||
| 2671 | * Determines whether cascading protection sources have already been loaded from  | 
            ||
| 2672 | * the database.  | 
            ||
| 2673 | *  | 
            ||
| 2674 | * @param bool $getPages True to check if the pages are loaded, or false to check  | 
            ||
| 2675 | * if the status is loaded.  | 
            ||
| 2676 | * @return bool Whether or not the specified information has been loaded  | 
            ||
| 2677 | * @since 1.23  | 
            ||
| 2678 | */  | 
            ||
| 2679 | 	public function areCascadeProtectionSourcesLoaded( $getPages = true ) { | 
            ||
| 2680 | return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;  | 
            ||
| 2681 | }  | 
            ||
| 2682 | |||
| 2683 | /**  | 
            ||
| 2684 | * Cascading protection: Get the source of any cascading restrictions on this page.  | 
            ||
| 2685 | *  | 
            ||
| 2686 | * @param bool $getPages Whether or not to retrieve the actual pages  | 
            ||
| 2687 | * that the restrictions have come from and the actual restrictions  | 
            ||
| 2688 | * themselves.  | 
            ||
| 2689 | * @return array Two elements: First is an array of Title objects of the  | 
            ||
| 2690 | * pages from which cascading restrictions have come, false for  | 
            ||
| 2691 | * none, or true if such restrictions exist but $getPages was not  | 
            ||
| 2692 | * set. Second is an array like that returned by  | 
            ||
| 2693 | * Title::getAllRestrictions(), or an empty array if $getPages is  | 
            ||
| 2694 | * false.  | 
            ||
| 2695 | */  | 
            ||
| 2696 | 	public function getCascadeProtectionSources( $getPages = true ) { | 
            ||
| 2697 | $pagerestrictions = [];  | 
            ||
| 2698 | |||
| 2699 | 		if ( $this->mCascadeSources !== null && $getPages ) { | 
            ||
| 2700 | return [ $this->mCascadeSources, $this->mCascadingRestrictions ];  | 
            ||
| 2701 | 		} elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) { | 
            ||
| 2702 | return [ $this->mHasCascadingRestrictions, $pagerestrictions ];  | 
            ||
| 2703 | }  | 
            ||
| 2704 | |||
| 2705 | $dbr = wfGetDB( DB_SLAVE );  | 
            ||
| 2706 | |||
| 2707 | 		if ( $this->getNamespace() == NS_FILE ) { | 
            ||
| 2708 | $tables = [ 'imagelinks', 'page_restrictions' ];  | 
            ||
| 2709 | $where_clauses = [  | 
            ||
| 2710 | 'il_to' => $this->getDBkey(),  | 
            ||
| 2711 | 'il_from=pr_page',  | 
            ||
| 2712 | 'pr_cascade' => 1  | 
            ||
| 2713 | ];  | 
            ||
| 2714 | 		} else { | 
            ||
| 2715 | $tables = [ 'templatelinks', 'page_restrictions' ];  | 
            ||
| 2716 | $where_clauses = [  | 
            ||
| 2717 | 'tl_namespace' => $this->getNamespace(),  | 
            ||
| 2718 | 'tl_title' => $this->getDBkey(),  | 
            ||
| 2719 | 'tl_from=pr_page',  | 
            ||
| 2720 | 'pr_cascade' => 1  | 
            ||
| 2721 | ];  | 
            ||
| 2722 | }  | 
            ||
| 2723 | |||
| 2724 | 		if ( $getPages ) { | 
            ||
| 2725 | $cols = [ 'pr_page', 'page_namespace', 'page_title',  | 
            ||
| 2726 | 'pr_expiry', 'pr_type', 'pr_level' ];  | 
            ||
| 2727 | $where_clauses[] = 'page_id=pr_page';  | 
            ||
| 2728 | $tables[] = 'page';  | 
            ||
| 2729 | 		} else { | 
            ||
| 2730 | $cols = [ 'pr_expiry' ];  | 
            ||
| 2731 | }  | 
            ||
| 2732 | |||
| 2733 | $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );  | 
            ||
| 2734 | |||
| 2735 | $sources = $getPages ? [] : false;  | 
            ||
| 2736 | $now = wfTimestampNow();  | 
            ||
| 2737 | |||
| 2738 | 		foreach ( $res as $row ) { | 
            ||
| 2739 | $expiry = $dbr->decodeExpiry( $row->pr_expiry );  | 
            ||
| 2740 | 			if ( $expiry > $now ) { | 
            ||
| 2741 | 				if ( $getPages ) { | 
            ||
| 2742 | $page_id = $row->pr_page;  | 
            ||
| 2743 | $page_ns = $row->page_namespace;  | 
            ||
| 2744 | $page_title = $row->page_title;  | 
            ||
| 2745 | $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );  | 
            ||
| 2746 | # Add groups needed for each restriction type if its not already there  | 
            ||
| 2747 | # Make sure this restriction type still exists  | 
            ||
| 2748 | |||
| 2749 | 					if ( !isset( $pagerestrictions[$row->pr_type] ) ) { | 
            ||
| 2750 | $pagerestrictions[$row->pr_type] = [];  | 
            ||
| 2751 | }  | 
            ||
| 2752 | |||
| 2753 | if (  | 
            ||
| 2754 | isset( $pagerestrictions[$row->pr_type] )  | 
            ||
| 2755 | && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )  | 
            ||
| 2756 | 					) { | 
            ||
| 2757 | $pagerestrictions[$row->pr_type][] = $row->pr_level;  | 
            ||
| 2758 | }  | 
            ||
| 2759 | 				} else { | 
            ||
| 2760 | $sources = true;  | 
            ||
| 2761 | }  | 
            ||
| 2762 | }  | 
            ||
| 2763 | }  | 
            ||
| 2764 | |||
| 2765 | 		if ( $getPages ) { | 
            ||
| 2766 | $this->mCascadeSources = $sources;  | 
            ||
| 2767 | $this->mCascadingRestrictions = $pagerestrictions;  | 
            ||
| 2768 | 		} else { | 
            ||
| 2769 | $this->mHasCascadingRestrictions = $sources;  | 
            ||
| 2770 | }  | 
            ||
| 2771 | |||
| 2772 | return [ $sources, $pagerestrictions ];  | 
            ||
| 2773 | }  | 
            ||
| 2774 | |||
| 2775 | /**  | 
            ||
| 2776 | * Accessor for mRestrictionsLoaded  | 
            ||
| 2777 | *  | 
            ||
| 2778 | * @return bool Whether or not the page's restrictions have already been  | 
            ||
| 2779 | * loaded from the database  | 
            ||
| 2780 | * @since 1.23  | 
            ||
| 2781 | */  | 
            ||
| 2782 | 	public function areRestrictionsLoaded() { | 
            ||
| 2783 | return $this->mRestrictionsLoaded;  | 
            ||
| 2784 | }  | 
            ||
| 2785 | |||
| 2786 | /**  | 
            ||
| 2787 | * Accessor/initialisation for mRestrictions  | 
            ||
| 2788 | *  | 
            ||
| 2789 | * @param string $action Action that permission needs to be checked for  | 
            ||
| 2790 | * @return array Restriction levels needed to take the action. All levels are  | 
            ||
| 2791 | * required. Note that restriction levels are normally user rights, but 'sysop'  | 
            ||
| 2792 | * and 'autoconfirmed' are also allowed for backwards compatibility. These should  | 
            ||
| 2793 | * be mapped to 'editprotected' and 'editsemiprotected' respectively.  | 
            ||
| 2794 | */  | 
            ||
| 2795 | 	public function getRestrictions( $action ) { | 
            ||
| 2796 | 		if ( !$this->mRestrictionsLoaded ) { | 
            ||
| 2797 | $this->loadRestrictions();  | 
            ||
| 2798 | }  | 
            ||
| 2799 | return isset( $this->mRestrictions[$action] )  | 
            ||
| 2800 | ? $this->mRestrictions[$action]  | 
            ||
| 2801 | : [];  | 
            ||
| 2802 | }  | 
            ||
| 2803 | |||
| 2804 | /**  | 
            ||
| 2805 | * Accessor/initialisation for mRestrictions  | 
            ||
| 2806 | *  | 
            ||
| 2807 | * @return array Keys are actions, values are arrays as returned by  | 
            ||
| 2808 | * Title::getRestrictions()  | 
            ||
| 2809 | * @since 1.23  | 
            ||
| 2810 | */  | 
            ||
| 2811 | 	public function getAllRestrictions() { | 
            ||
| 2812 | 		if ( !$this->mRestrictionsLoaded ) { | 
            ||
| 2813 | $this->loadRestrictions();  | 
            ||
| 2814 | }  | 
            ||
| 2815 | return $this->mRestrictions;  | 
            ||
| 2816 | }  | 
            ||
| 2817 | |||
| 2818 | /**  | 
            ||
| 2819 | * Get the expiry time for the restriction against a given action  | 
            ||
| 2820 | *  | 
            ||
| 2821 | * @param string $action  | 
            ||
| 2822 | * @return string|bool 14-char timestamp, or 'infinity' if the page is protected forever  | 
            ||
| 2823 | * or not protected at all, or false if the action is not recognised.  | 
            ||
| 2824 | */  | 
            ||
| 2825 | 	public function getRestrictionExpiry( $action ) { | 
            ||
| 2826 | 		if ( !$this->mRestrictionsLoaded ) { | 
            ||
| 2827 | $this->loadRestrictions();  | 
            ||
| 2828 | }  | 
            ||
| 2829 | return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;  | 
            ||
| 2830 | }  | 
            ||
| 2831 | |||
| 2832 | /**  | 
            ||
| 2833 | * Returns cascading restrictions for the current article  | 
            ||
| 2834 | *  | 
            ||
| 2835 | * @return bool  | 
            ||
| 2836 | */  | 
            ||
| 2837 | 	function areRestrictionsCascading() { | 
            ||
| 2838 | 		if ( !$this->mRestrictionsLoaded ) { | 
            ||
| 2839 | $this->loadRestrictions();  | 
            ||
| 2840 | }  | 
            ||
| 2841 | |||
| 2842 | return $this->mCascadeRestriction;  | 
            ||
| 2843 | }  | 
            ||
| 2844 | |||
| 2845 | /**  | 
            ||
| 2846 | * Loads a string into mRestrictions array  | 
            ||
| 2847 | *  | 
            ||
| 2848 | * @param ResultWrapper $res Resource restrictions as an SQL result.  | 
            ||
| 2849 | * @param string $oldFashionedRestrictions Comma-separated list of page  | 
            ||
| 2850 | * restrictions from page table (pre 1.10)  | 
            ||
| 2851 | */  | 
            ||
| 2852 | 	private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) { | 
            ||
| 2853 | $rows = [];  | 
            ||
| 2854 | |||
| 2855 | 		foreach ( $res as $row ) { | 
            ||
| 2856 | $rows[] = $row;  | 
            ||
| 2857 | }  | 
            ||
| 2858 | |||
| 2859 | $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );  | 
            ||
| 2860 | }  | 
            ||
| 2861 | |||
| 2862 | /**  | 
            ||
| 2863 | * Compiles list of active page restrictions from both page table (pre 1.10)  | 
            ||
| 2864 | * and page_restrictions table for this existing page.  | 
            ||
| 2865 | * Public for usage by LiquidThreads.  | 
            ||
| 2866 | *  | 
            ||
| 2867 | * @param array $rows Array of db result objects  | 
            ||
| 2868 | * @param string $oldFashionedRestrictions Comma-separated list of page  | 
            ||
| 2869 | * restrictions from page table (pre 1.10)  | 
            ||
| 2870 | */  | 
            ||
| 2871 | 	public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) { | 
            ||
| 2872 | $dbr = wfGetDB( DB_SLAVE );  | 
            ||
| 2873 | |||
| 2874 | $restrictionTypes = $this->getRestrictionTypes();  | 
            ||
| 2875 | |||
| 2876 | 		foreach ( $restrictionTypes as $type ) { | 
            ||
| 2877 | $this->mRestrictions[$type] = [];  | 
            ||
| 2878 | $this->mRestrictionsExpiry[$type] = 'infinity';  | 
            ||
| 2879 | }  | 
            ||
| 2880 | |||
| 2881 | $this->mCascadeRestriction = false;  | 
            ||
| 2882 | |||
| 2883 | # Backwards-compatibility: also load the restrictions from the page record (old format).  | 
            ||
| 2884 | 		if ( $oldFashionedRestrictions !== null ) { | 
            ||
| 2885 | $this->mOldRestrictions = $oldFashionedRestrictions;  | 
            ||
| 2886 | }  | 
            ||
| 2887 | |||
| 2888 | 		if ( $this->mOldRestrictions === false ) { | 
            ||
| 2889 | $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',  | 
            ||
| 2890 | [ 'page_id' => $this->getArticleID() ], __METHOD__ );  | 
            ||
| 2891 | }  | 
            ||
| 2892 | |||
| 2893 | 		if ( $this->mOldRestrictions != '' ) { | 
            ||
| 2894 | 			foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) { | 
            ||
| 2895 | $temp = explode( '=', trim( $restrict ) );  | 
            ||
| 2896 | 				if ( count( $temp ) == 1 ) { | 
            ||
| 2897 | // old old format should be treated as edit/move restriction  | 
            ||
| 2898 | $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );  | 
            ||
| 2899 | $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );  | 
            ||
| 2900 | 				} else { | 
            ||
| 2901 | $restriction = trim( $temp[1] );  | 
            ||
| 2902 | 					if ( $restriction != '' ) { // some old entries are empty | 
            ||
| 2903 | $this->mRestrictions[$temp[0]] = explode( ',', $restriction );  | 
            ||
| 2904 | }  | 
            ||
| 2905 | }  | 
            ||
| 2906 | }  | 
            ||
| 2907 | }  | 
            ||
| 2908 | |||
| 2909 | 		if ( count( $rows ) ) { | 
            ||
| 2910 | # Current system - load second to make them override.  | 
            ||
| 2911 | $now = wfTimestampNow();  | 
            ||
| 2912 | |||
| 2913 | # Cycle through all the restrictions.  | 
            ||
| 2914 | 			foreach ( $rows as $row ) { | 
            ||
| 2915 | |||
| 2916 | // Don't take care of restrictions types that aren't allowed  | 
            ||
| 2917 | 				if ( !in_array( $row->pr_type, $restrictionTypes ) ) { | 
            ||
| 2918 | continue;  | 
            ||
| 2919 | }  | 
            ||
| 2920 | |||
| 2921 | // This code should be refactored, now that it's being used more generally,  | 
            ||
| 2922 | // But I don't really see any harm in leaving it in Block for now -werdna  | 
            ||
| 2923 | $expiry = $dbr->decodeExpiry( $row->pr_expiry );  | 
            ||
| 2924 | |||
| 2925 | // Only apply the restrictions if they haven't expired!  | 
            ||
| 2926 | 				if ( !$expiry || $expiry > $now ) { | 
            ||
| 2927 | $this->mRestrictionsExpiry[$row->pr_type] = $expiry;  | 
            ||
| 2928 | $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );  | 
            ||
| 2929 | |||
| 2930 | $this->mCascadeRestriction |= $row->pr_cascade;  | 
            ||
| 2931 | }  | 
            ||
| 2932 | }  | 
            ||
| 2933 | }  | 
            ||
| 2934 | |||
| 2935 | $this->mRestrictionsLoaded = true;  | 
            ||
| 2936 | }  | 
            ||
| 2937 | |||
| 2938 | /**  | 
            ||
| 2939 | * Load restrictions from the page_restrictions table  | 
            ||
| 2940 | *  | 
            ||
| 2941 | * @param string $oldFashionedRestrictions Comma-separated list of page  | 
            ||
| 2942 | * restrictions from page table (pre 1.10)  | 
            ||
| 2943 | */  | 
            ||
| 2944 | 	public function loadRestrictions( $oldFashionedRestrictions = null ) { | 
            ||
| 2945 | 		if ( !$this->mRestrictionsLoaded ) { | 
            ||
| 2946 | $dbr = wfGetDB( DB_SLAVE );  | 
            ||
| 2947 | 			if ( $this->exists() ) { | 
            ||
| 2948 | $res = $dbr->select(  | 
            ||
| 2949 | 'page_restrictions',  | 
            ||
| 2950 | [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],  | 
            ||
| 2951 | [ 'pr_page' => $this->getArticleID() ],  | 
            ||
| 2952 | __METHOD__  | 
            ||
| 2953 | );  | 
            ||
| 2954 | |||
| 2955 | $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );  | 
            ||
| 2956 | 			} else { | 
            ||
| 2957 | $title_protection = $this->getTitleProtection();  | 
            ||
| 2958 | |||
| 2959 | 				if ( $title_protection ) { | 
            ||
| 2960 | $now = wfTimestampNow();  | 
            ||
| 2961 | $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );  | 
            ||
| 2962 | |||
| 2963 | 					if ( !$expiry || $expiry > $now ) { | 
            ||
| 2964 | // Apply the restrictions  | 
            ||
| 2965 | $this->mRestrictionsExpiry['create'] = $expiry;  | 
            ||
| 2966 | $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );  | 
            ||
| 2967 | 					} else { // Get rid of the old restrictions | 
            ||
| 2968 | $this->mTitleProtection = false;  | 
            ||
| 2969 | }  | 
            ||
| 2970 | 				} else { | 
            ||
| 2971 | $this->mRestrictionsExpiry['create'] = 'infinity';  | 
            ||
| 2972 | }  | 
            ||
| 2973 | $this->mRestrictionsLoaded = true;  | 
            ||
| 2974 | }  | 
            ||
| 2975 | }  | 
            ||
| 2976 | }  | 
            ||
| 2977 | |||
| 2978 | /**  | 
            ||
| 2979 | * Flush the protection cache in this object and force reload from the database.  | 
            ||
| 2980 | * This is used when updating protection from WikiPage::doUpdateRestrictions().  | 
            ||
| 2981 | */  | 
            ||
| 2982 | 	public function flushRestrictions() { | 
            ||
| 2983 | $this->mRestrictionsLoaded = false;  | 
            ||
| 2984 | $this->mTitleProtection = null;  | 
            ||
| 2985 | }  | 
            ||
| 2986 | |||
| 2987 | /**  | 
            ||
| 2988 | * Purge expired restrictions from the page_restrictions table  | 
            ||
| 2989 | *  | 
            ||
| 2990 | * This will purge no more than $wgUpdateRowsPerQuery page_restrictions rows  | 
            ||
| 2991 | */  | 
            ||
| 2992 | 	static function purgeExpiredRestrictions() { | 
            ||
| 3027 | |||
| 3028 | /**  | 
            ||
| 3029 | * Does this have subpages? (Warning, usually requires an extra DB query.)  | 
            ||
| 3030 | *  | 
            ||
| 3031 | * @return bool  | 
            ||
| 3032 | */  | 
            ||
| 3033 | 	public function hasSubpages() { | 
            ||
| 3034 | 		if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { | 
            ||
| 3035 | # Duh  | 
            ||
| 3036 | return false;  | 
            ||
| 3037 | }  | 
            ||
| 3038 | |||
| 3039 | # We dynamically add a member variable for the purpose of this method  | 
            ||
| 3040 | # alone to cache the result. There's no point in having it hanging  | 
            ||
| 3041 | # around uninitialized in every Title object; therefore we only add it  | 
            ||
| 3042 | # if needed and don't declare it statically.  | 
            ||
| 3043 | 		if ( $this->mHasSubpages === null ) { | 
            ||
| 3044 | $this->mHasSubpages = false;  | 
            ||
| 3045 | $subpages = $this->getSubpages( 1 );  | 
            ||
| 3046 | 			if ( $subpages instanceof TitleArray ) { | 
            ||
| 3047 | $this->mHasSubpages = (bool)$subpages->count();  | 
            ||
| 3048 | }  | 
            ||
| 3049 | }  | 
            ||
| 3050 | |||
| 3051 | return $this->mHasSubpages;  | 
            ||
| 3052 | }  | 
            ||
| 3053 | |||
| 3054 | /**  | 
            ||
| 3055 | * Get all subpages of this page.  | 
            ||
| 3056 | *  | 
            ||
| 3057 | * @param int $limit Maximum number of subpages to fetch; -1 for no limit  | 
            ||
| 3058 | * @return TitleArray|array TitleArray, or empty array if this page's namespace  | 
            ||
| 3059 | * doesn't allow subpages  | 
            ||
| 3060 | */  | 
            ||
| 3061 | 	public function getSubpages( $limit = -1 ) { | 
            ||
| 3062 | 		if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) { | 
            ||
| 3063 | return [];  | 
            ||
| 3064 | }  | 
            ||
| 3065 | |||
| 3066 | $dbr = wfGetDB( DB_SLAVE );  | 
            ||
| 3067 | $conds['page_namespace'] = $this->getNamespace();  | 
            ||
| 3068 | $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );  | 
            ||
| 3069 | $options = [];  | 
            ||
| 3070 | 		if ( $limit > -1 ) { | 
            ||
| 3071 | $options['LIMIT'] = $limit;  | 
            ||
| 3072 | }  | 
            ||
| 3073 | $this->mSubpages = TitleArray::newFromResult(  | 
            ||
| 3074 | $dbr->select( 'page',  | 
            ||
| 3075 | [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],  | 
            ||
| 3076 | $conds,  | 
            ||
| 3077 | __METHOD__,  | 
            ||
| 3078 | $options  | 
            ||
| 3079 | )  | 
            ||
| 3080 | );  | 
            ||
| 3081 | return $this->mSubpages;  | 
            ||
| 3082 | }  | 
            ||
| 3083 | |||
| 3084 | /**  | 
            ||
| 3085 | * Is there a version of this page in the deletion archive?  | 
            ||
| 3086 | *  | 
            ||
| 3087 | * @return int The number of archived revisions  | 
            ||
| 3088 | */  | 
            ||
| 3089 | 	public function isDeleted() { | 
            ||
| 3090 | 		if ( $this->getNamespace() < 0 ) { | 
            ||
| 3091 | $n = 0;  | 
            ||
| 3092 | 		} else { | 
            ||
| 3093 | $dbr = wfGetDB( DB_SLAVE );  | 
            ||
| 3094 | |||
| 3095 | $n = $dbr->selectField( 'archive', 'COUNT(*)',  | 
            ||
| 3096 | [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],  | 
            ||
| 3097 | __METHOD__  | 
            ||
| 3098 | );  | 
            ||
| 3099 | View Code Duplication | 			if ( $this->getNamespace() == NS_FILE ) { | 
            |
| 3100 | $n += $dbr->selectField( 'filearchive', 'COUNT(*)',  | 
            ||
| 3101 | [ 'fa_name' => $this->getDBkey() ],  | 
            ||
| 3102 | __METHOD__  | 
            ||
| 3103 | );  | 
            ||
| 3104 | }  | 
            ||
| 3105 | }  | 
            ||
| 3106 | return (int)$n;  | 
            ||
| 3107 | }  | 
            ||
| 3108 | |||
| 3109 | /**  | 
            ||
| 3110 | * Is there a version of this page in the deletion archive?  | 
            ||
| 3111 | *  | 
            ||
| 3112 | * @return bool  | 
            ||
| 3113 | */  | 
            ||
| 3114 | 	public function isDeletedQuick() { | 
            ||
| 3115 | 		if ( $this->getNamespace() < 0 ) { | 
            ||
| 3116 | return false;  | 
            ||
| 3117 | }  | 
            ||
| 3118 | $dbr = wfGetDB( DB_SLAVE );  | 
            ||
| 3119 | $deleted = (bool)$dbr->selectField( 'archive', '1',  | 
            ||
| 3120 | [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],  | 
            ||
| 3121 | __METHOD__  | 
            ||
| 3122 | );  | 
            ||
| 3123 | View Code Duplication | 		if ( !$deleted && $this->getNamespace() == NS_FILE ) { | 
            |
| 3124 | $deleted = (bool)$dbr->selectField( 'filearchive', '1',  | 
            ||
| 3125 | [ 'fa_name' => $this->getDBkey() ],  | 
            ||
| 3126 | __METHOD__  | 
            ||
| 3127 | );  | 
            ||
| 3128 | }  | 
            ||
| 3129 | return $deleted;  | 
            ||
| 3130 | }  | 
            ||
| 3131 | |||
| 3132 | /**  | 
            ||
| 3133 | * Get the article ID for this Title from the link cache,  | 
            ||
| 3134 | * adding it if necessary  | 
            ||
| 3135 | *  | 
            ||
| 3136 | * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select  | 
            ||
| 3137 | * for update  | 
            ||
| 3138 | * @return int The ID  | 
            ||
| 3139 | */  | 
            ||
| 3140 | 	public function getArticleID( $flags = 0 ) { | 
            ||
| 3141 | 		if ( $this->getNamespace() < 0 ) { | 
            ||
| 3142 | $this->mArticleID = 0;  | 
            ||
| 3143 | return $this->mArticleID;  | 
            ||
| 3144 | }  | 
            ||
| 3145 | $linkCache = LinkCache::singleton();  | 
            ||
| 3146 | 		if ( $flags & self::GAID_FOR_UPDATE ) { | 
            ||
| 3147 | $oldUpdate = $linkCache->forUpdate( true );  | 
            ||
| 3148 | $linkCache->clearLink( $this );  | 
            ||
| 3149 | $this->mArticleID = $linkCache->addLinkObj( $this );  | 
            ||
| 3150 | $linkCache->forUpdate( $oldUpdate );  | 
            ||
| 3158 | |||
| 3159 | /**  | 
            ||
| 3160 | * Is this an article that is a redirect page?  | 
            ||
| 3161 | * Uses link cache, adding it if necessary  | 
            ||
| 3162 | *  | 
            ||
| 3163 | * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update  | 
            ||
| 3164 | * @return bool  | 
            ||
| 3165 | */  | 
            ||
| 3166 | 	public function isRedirect( $flags = 0 ) { | 
            ||
| 3193 | |||
| 3194 | /**  | 
            ||
| 3195 | * What is the length of this page?  | 
            ||
| 3196 | * Uses link cache, adding it if necessary  | 
            ||
| 3197 | *  | 
            ||
| 3198 | * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update  | 
            ||
| 3199 | * @return int  | 
            ||
| 3200 | */  | 
            ||
| 3201 | 	public function getLength( $flags = 0 ) { | 
            ||
| 3222 | |||
| 3223 | /**  | 
            ||
| 3224 | * What is the page_latest field for this page?  | 
            ||
| 3225 | *  | 
            ||
| 3226 | * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update  | 
            ||
| 3227 | * @return int Int or 0 if the page doesn't exist  | 
            ||
| 3228 | */  | 
            ||
| 3229 | 	public function getLatestRevID( $flags = 0 ) { | 
            ||
| 3250 | |||
| 3251 | /**  | 
            ||
| 3252 | * This clears some fields in this object, and clears any associated  | 
            ||
| 3253 | * keys in the "bad links" section of the link cache.  | 
            ||
| 3254 | *  | 
            ||
| 3255 | * - This is called from WikiPage::doEdit() and WikiPage::insertOn() to allow  | 
            ||
| 3256 | * loading of the new page_id. It's also called from  | 
            ||
| 3257 | * WikiPage::doDeleteArticleReal()  | 
            ||
| 3258 | *  | 
            ||
| 3259 | * @param int $newid The new Article ID  | 
            ||
| 3260 | */  | 
            ||
| 3261 | 	public function resetArticleID( $newid ) { | 
            ||
| 3282 | |||
| 3283 | 	public static function clearCaches() { | 
            ||
| 3290 | |||
| 3291 | /**  | 
            ||
| 3292 | * Capitalize a text string for a title if it belongs to a namespace that capitalizes  | 
            ||
| 3293 | *  | 
            ||
| 3294 | * @param string $text Containing title to capitalize  | 
            ||
| 3295 | * @param int $ns Namespace index, defaults to NS_MAIN  | 
            ||
| 3296 | * @return string Containing capitalized title  | 
            ||
| 3297 | */  | 
            ||
| 3298 | 	public static function capitalize( $text, $ns = NS_MAIN ) { | 
            ||
| 3307 | |||
| 3308 | /**  | 
            ||
| 3309 | * Secure and split - main initialisation function for this object  | 
            ||
| 3310 | *  | 
            ||
| 3311 | * Assumes that mDbkeyform has been set, and is urldecoded  | 
            ||
| 3312 | * and uses underscores, but not otherwise munged. This function  | 
            ||
| 3313 | * removes illegal characters, splits off the interwiki and  | 
            ||
| 3314 | * namespace prefixes, sets the other forms, and canonicalizes  | 
            ||
| 3315 | * everything.  | 
            ||
| 3316 | *  | 
            ||
| 3317 | * @throws MalformedTitleException On invalid titles  | 
            ||
| 3318 | * @return bool True on success  | 
            ||
| 3319 | */  | 
            ||
| 3320 | 	private function secureAndSplit() { | 
            ||
| 3355 | |||
| 3356 | /**  | 
            ||
| 3357 | * Get an array of Title objects linking to this Title  | 
            ||
| 3358 | * Also stores the IDs in the link cache.  | 
            ||
| 3359 | *  | 
            ||
| 3360 | * WARNING: do not use this function on arbitrary user-supplied titles!  | 
            ||
| 3361 | * On heavily-used templates it will max out the memory.  | 
            ||
| 3362 | *  | 
            ||
| 3363 | * @param array $options May be FOR UPDATE  | 
            ||
| 3364 | * @param string $table Table name  | 
            ||
| 3365 | * @param string $prefix Fields prefix  | 
            ||
| 3366 | * @return Title[] Array of Title objects linking here  | 
            ||
| 3367 | */  | 
            ||
| 3368 | 	public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) { | 
            ||
| 3399 | |||
| 3400 | /**  | 
            ||
| 3401 | * Get an array of Title objects using this Title as a template  | 
            ||
| 3402 | * Also stores the IDs in the link cache.  | 
            ||
| 3403 | *  | 
            ||
| 3404 | * WARNING: do not use this function on arbitrary user-supplied titles!  | 
            ||
| 3405 | * On heavily-used templates it will max out the memory.  | 
            ||
| 3406 | *  | 
            ||
| 3407 | * @param array $options Query option to Database::select()  | 
            ||
| 3408 | * @return Title[] Array of Title the Title objects linking here  | 
            ||
| 3409 | */  | 
            ||
| 3410 | 	public function getTemplateLinksTo( $options = [] ) { | 
            ||
| 3413 | |||
| 3414 | /**  | 
            ||
| 3415 | * Get an array of Title objects linked from this Title  | 
            ||
| 3416 | * Also stores the IDs in the link cache.  | 
            ||
| 3417 | *  | 
            ||
| 3418 | * WARNING: do not use this function on arbitrary user-supplied titles!  | 
            ||
| 3419 | * On heavily-used templates it will max out the memory.  | 
            ||
| 3420 | *  | 
            ||
| 3421 | * @param array $options Query option to Database::select()  | 
            ||
| 3422 | * @param string $table Table name  | 
            ||
| 3423 | * @param string $prefix Fields prefix  | 
            ||
| 3424 | * @return array Array of Title objects linking here  | 
            ||
| 3425 | */  | 
            ||
| 3426 | 	public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) { | 
            ||
| 3468 | |||
| 3469 | /**  | 
            ||
| 3470 | * Get an array of Title objects used on this Title as a template  | 
            ||
| 3471 | * Also stores the IDs in the link cache.  | 
            ||
| 3472 | *  | 
            ||
| 3473 | * WARNING: do not use this function on arbitrary user-supplied titles!  | 
            ||
| 3474 | * On heavily-used templates it will max out the memory.  | 
            ||
| 3475 | *  | 
            ||
| 3476 | * @param array $options May be FOR UPDATE  | 
            ||
| 3477 | * @return Title[] Array of Title the Title objects used here  | 
            ||
| 3478 | */  | 
            ||
| 3479 | 	public function getTemplateLinksFrom( $options = [] ) { | 
            ||
| 3482 | |||
| 3483 | /**  | 
            ||
| 3484 | * Get an array of Title objects referring to non-existent articles linked  | 
            ||
| 3485 | * from this page.  | 
            ||
| 3486 | *  | 
            ||
| 3487 | * @todo check if needed (used only in SpecialBrokenRedirects.php, and  | 
            ||
| 3488 | * should use redirect table in this case).  | 
            ||
| 3489 | * @return Title[] Array of Title the Title objects  | 
            ||
| 3490 | */  | 
            ||
| 3491 | 	public function getBrokenLinksFrom() { | 
            ||
| 3520 | |||
| 3521 | /**  | 
            ||
| 3522 | * Get a list of URLs to purge from the CDN cache when this  | 
            ||
| 3523 | * page changes  | 
            ||
| 3524 | *  | 
            ||
| 3525 | * @return string[] Array of String the URLs  | 
            ||
| 3526 | */  | 
            ||
| 3527 | 	public function getCdnUrls() { | 
            ||
| 3551 | |||
| 3552 | /**  | 
            ||
| 3553 | * @deprecated since 1.27 use getCdnUrls()  | 
            ||
| 3554 | */  | 
            ||
| 3555 | 	public function getSquidURLs() { | 
            ||
| 3558 | |||
| 3559 | /**  | 
            ||
| 3560 | * Purge all applicable CDN URLs  | 
            ||
| 3561 | */  | 
            ||
| 3562 | 	public function purgeSquid() { | 
            ||
| 3568 | |||
| 3569 | /**  | 
            ||
| 3570 | * Move this page without authentication  | 
            ||
| 3571 | *  | 
            ||
| 3572 | * @deprecated since 1.25 use MovePage class instead  | 
            ||
| 3573 | * @param Title $nt The new page Title  | 
            ||
| 3574 | * @return array|bool True on success, getUserPermissionsErrors()-like array on failure  | 
            ||
| 3575 | */  | 
            ||
| 3576 | 	public function moveNoAuth( &$nt ) { | 
            ||
| 3580 | |||
| 3581 | /**  | 
            ||
| 3582 | * Check whether a given move operation would be valid.  | 
            ||
| 3583 | * Returns true if ok, or a getUserPermissionsErrors()-like array otherwise  | 
            ||
| 3584 | *  | 
            ||
| 3585 | * @deprecated since 1.25, use MovePage's methods instead  | 
            ||
| 3586 | * @param Title $nt The new title  | 
            ||
| 3587 | * @param bool $auth Whether to check user permissions (uses $wgUser)  | 
            ||
| 3588 | * @param string $reason Is the log summary of the move, used for spam checking  | 
            ||
| 3589 | * @return array|bool True on success, getUserPermissionsErrors()-like array on failure  | 
            ||
| 3590 | */  | 
            ||
| 3591 | 	public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) { | 
            ||
| 3611 | |||
| 3612 | /**  | 
            ||
| 3613 | * Check if the requested move target is a valid file move target  | 
            ||
| 3614 | * @todo move this to MovePage  | 
            ||
| 3615 | * @param Title $nt Target title  | 
            ||
| 3616 | * @return array List of errors  | 
            ||
| 3617 | */  | 
            ||
| 3618 | 	protected function validateFileMoveOperation( $nt ) { | 
            ||
| 3633 | |||
| 3634 | /**  | 
            ||
| 3635 | * Move a title to a new location  | 
            ||
| 3636 | *  | 
            ||
| 3637 | * @deprecated since 1.25, use the MovePage class instead  | 
            ||
| 3638 | * @param Title $nt The new title  | 
            ||
| 3639 | * @param bool $auth Indicates whether $wgUser's permissions  | 
            ||
| 3640 | * should be checked  | 
            ||
| 3641 | * @param string $reason The reason for the move  | 
            ||
| 3642 | * @param bool $createRedirect Whether to create a redirect from the old title to the new title.  | 
            ||
| 3643 | * Ignored if the user doesn't have the suppressredirect right.  | 
            ||
| 3644 | * @return array|bool True on success, getUserPermissionsErrors()-like array on failure  | 
            ||
| 3645 | */  | 
            ||
| 3646 | 	public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) { | 
            ||
| 3667 | |||
| 3668 | /**  | 
            ||
| 3669 | * Move this page's subpages to be subpages of $nt  | 
            ||
| 3670 | *  | 
            ||
| 3671 | * @param Title $nt Move target  | 
            ||
| 3672 | * @param bool $auth Whether $wgUser's permissions should be checked  | 
            ||
| 3673 | * @param string $reason The reason for the move  | 
            ||
| 3674 | * @param bool $createRedirect Whether to create redirects from the old subpages to  | 
            ||
| 3675 | * the new ones Ignored if the user doesn't have the 'suppressredirect' right  | 
            ||
| 3676 | * @return array Array with old page titles as keys, and strings (new page titles) or  | 
            ||
| 3677 | * arrays (errors) as values, or an error array with numeric indices if no pages  | 
            ||
| 3678 | * were moved  | 
            ||
| 3679 | */  | 
            ||
| 3680 | 	public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) { | 
            ||
| 3740 | |||
| 3741 | /**  | 
            ||
| 3742 | * Checks if this page is just a one-rev redirect.  | 
            ||
| 3743 | * Adds lock, so don't use just for light purposes.  | 
            ||
| 3744 | *  | 
            ||
| 3745 | * @return bool  | 
            ||
| 3746 | */  | 
            ||
| 3747 | 	public function isSingleRevRedirect() { | 
            ||
| 3789 | |||
| 3790 | /**  | 
            ||
| 3791 | * Checks if $this can be moved to a given Title  | 
            ||
| 3792 | * - Selects for update, so don't call it unless you mean business  | 
            ||
| 3793 | *  | 
            ||
| 3794 | * @deprecated since 1.25, use MovePage's methods instead  | 
            ||
| 3795 | * @param Title $nt The new title to check  | 
            ||
| 3796 | * @return bool  | 
            ||
| 3797 | */  | 
            ||
| 3798 | 	public function isValidMoveTarget( $nt ) { | 
            ||
| 3838 | |||
| 3839 | /**  | 
            ||
| 3840 | * Get categories to which this Title belongs and return an array of  | 
            ||
| 3841 | * categories' names.  | 
            ||
| 3842 | *  | 
            ||
| 3843 | * @return array Array of parents in the form:  | 
            ||
| 3844 | * $parent => $currentarticle  | 
            ||
| 3845 | */  | 
            ||
| 3846 | 	public function getParentCategories() { | 
            ||
| 3874 | |||
| 3875 | /**  | 
            ||
| 3876 | * Get a tree of parent categories  | 
            ||
| 3877 | *  | 
            ||
| 3878 | * @param array $children Array with the children in the keys, to check for circular refs  | 
            ||
| 3879 | * @return array Tree of parent categories  | 
            ||
| 3880 | */  | 
            ||
| 3881 | 	public function getParentCategoryTree( $children = [] ) { | 
            ||
| 3901 | |||
| 3902 | /**  | 
            ||
| 3903 | * Get an associative array for selecting this title from  | 
            ||
| 3904 | * the "page" table  | 
            ||
| 3905 | *  | 
            ||
| 3906 | * @return array Array suitable for the $where parameter of DB::select()  | 
            ||
| 3907 | */  | 
            ||
| 3908 | 	public function pageCond() { | 
            ||
| 3916 | |||
| 3917 | /**  | 
            ||
| 3918 | * Get the revision ID of the previous revision  | 
            ||
| 3919 | *  | 
            ||
| 3920 | * @param int $revId Revision ID. Get the revision that was before this one.  | 
            ||
| 3921 | * @param int $flags Title::GAID_FOR_UPDATE  | 
            ||
| 3922 | * @return int|bool Old revision ID, or false if none exists  | 
            ||
| 3923 | */  | 
            ||
| 3924 | View Code Duplication | 	public function getPreviousRevisionID( $revId, $flags = 0 ) { | 
            |
| 3941 | |||
| 3942 | /**  | 
            ||
| 3943 | * Get the revision ID of the next revision  | 
            ||
| 3944 | *  | 
            ||
| 3945 | * @param int $revId Revision ID. Get the revision that was after this one.  | 
            ||
| 3946 | * @param int $flags Title::GAID_FOR_UPDATE  | 
            ||
| 3947 | * @return int|bool Next revision ID, or false if none exists  | 
            ||
| 3948 | */  | 
            ||
| 3949 | View Code Duplication | 	public function getNextRevisionID( $revId, $flags = 0 ) { | 
            |
| 3966 | |||
| 3967 | /**  | 
            ||
| 3968 | * Get the first revision of the page  | 
            ||
| 3969 | *  | 
            ||
| 3970 | * @param int $flags Title::GAID_FOR_UPDATE  | 
            ||
| 3971 | * @return Revision|null If page doesn't exist  | 
            ||
| 3972 | */  | 
            ||
| 3973 | 	public function getFirstRevision( $flags = 0 ) { | 
            ||
| 3988 | |||
| 3989 | /**  | 
            ||
| 3990 | * Get the oldest revision timestamp of this page  | 
            ||
| 3991 | *  | 
            ||
| 3992 | * @param int $flags Title::GAID_FOR_UPDATE  | 
            ||
| 3993 | * @return string MW timestamp  | 
            ||
| 3994 | */  | 
            ||
| 3995 | 	public function getEarliestRevTime( $flags = 0 ) { | 
            ||
| 3999 | |||
| 4000 | /**  | 
            ||
| 4001 | * Check if this is a new page  | 
            ||
| 4002 | *  | 
            ||
| 4003 | * @return bool  | 
            ||
| 4004 | */  | 
            ||
| 4005 | 	public function isNewPage() { | 
            ||
| 4009 | |||
| 4010 | /**  | 
            ||
| 4011 | * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit  | 
            ||
| 4012 | *  | 
            ||
| 4013 | * @return bool  | 
            ||
| 4014 | */  | 
            ||
| 4015 | 	public function isBigDeletion() { | 
            ||
| 4038 | |||
| 4039 | /**  | 
            ||
| 4040 | * Get the approximate revision count of this page.  | 
            ||
| 4041 | *  | 
            ||
| 4042 | * @return int  | 
            ||
| 4043 | */  | 
            ||
| 4044 | 	public function estimateRevisionCount() { | 
            ||
| 4057 | |||
| 4058 | /**  | 
            ||
| 4059 | * Get the number of revisions between the given revision.  | 
            ||
| 4060 | * Used for diffs and other things that really need it.  | 
            ||
| 4061 | *  | 
            ||
| 4062 | * @param int|Revision $old Old revision or rev ID (first before range)  | 
            ||
| 4063 | * @param int|Revision $new New revision or rev ID (first after range)  | 
            ||
| 4064 | * @param int|null $max Limit of Revisions to count, will be incremented to detect truncations  | 
            ||
| 4065 | * @return int Number of revisions between these revisions.  | 
            ||
| 4066 | */  | 
            ||
| 4067 | 	public function countRevisionsBetween( $old, $new, $max = null ) { | 
            ||
| 4093 | |||
| 4094 | /**  | 
            ||
| 4095 | * Get the authors between the given revisions or revision IDs.  | 
            ||
| 4096 | * Used for diffs and other things that really need it.  | 
            ||
| 4097 | *  | 
            ||
| 4098 | * @since 1.23  | 
            ||
| 4099 | *  | 
            ||
| 4100 | * @param int|Revision $old Old revision or rev ID (first before range by default)  | 
            ||
| 4101 | * @param int|Revision $new New revision or rev ID (first after range by default)  | 
            ||
| 4102 | * @param int $limit Maximum number of authors  | 
            ||
| 4103 | * @param string|array $options (Optional): Single option, or an array of options:  | 
            ||
| 4104 | * 'include_old' Include $old in the range; $new is excluded.  | 
            ||
| 4105 | * 'include_new' Include $new in the range; $old is excluded.  | 
            ||
| 4106 | * 'include_both' Include both $old and $new in the range.  | 
            ||
| 4107 | * Unknown option values are ignored.  | 
            ||
| 4108 | * @return array|null Names of revision authors in the range; null if not both revisions exist  | 
            ||
| 4109 | */  | 
            ||
| 4110 | 	public function getAuthorsBetween( $old, $new, $limit, $options = [] ) { | 
            ||
| 4169 | |||
| 4170 | /**  | 
            ||
| 4171 | * Get the number of authors between the given revisions or revision IDs.  | 
            ||
| 4172 | * Used for diffs and other things that really need it.  | 
            ||
| 4173 | *  | 
            ||
| 4174 | * @param int|Revision $old Old revision or rev ID (first before range by default)  | 
            ||
| 4175 | * @param int|Revision $new New revision or rev ID (first after range by default)  | 
            ||
| 4176 | * @param int $limit Maximum number of authors  | 
            ||
| 4177 | * @param string|array $options (Optional): Single option, or an array of options:  | 
            ||
| 4178 | * 'include_old' Include $old in the range; $new is excluded.  | 
            ||
| 4179 | * 'include_new' Include $new in the range; $old is excluded.  | 
            ||
| 4180 | * 'include_both' Include both $old and $new in the range.  | 
            ||
| 4181 | * Unknown option values are ignored.  | 
            ||
| 4182 | * @return int Number of revision authors in the range; zero if not both revisions exist  | 
            ||
| 4183 | */  | 
            ||
| 4184 | 	public function countAuthorsBetween( $old, $new, $limit, $options = [] ) { | 
            ||
| 4188 | |||
| 4189 | /**  | 
            ||
| 4190 | * Compare with another title.  | 
            ||
| 4191 | *  | 
            ||
| 4192 | * @param Title $title  | 
            ||
| 4193 | * @return bool  | 
            ||
| 4194 | */  | 
            ||
| 4195 | 	public function equals( Title $title ) { | 
            ||
| 4201 | |||
| 4202 | /**  | 
            ||
| 4203 | * Check if this title is a subpage of another title  | 
            ||
| 4204 | *  | 
            ||
| 4205 | * @param Title $title  | 
            ||
| 4206 | * @return bool  | 
            ||
| 4207 | */  | 
            ||
| 4208 | 	public function isSubpageOf( Title $title ) { | 
            ||
| 4213 | |||
| 4214 | /**  | 
            ||
| 4215 | * Check if page exists. For historical reasons, this function simply  | 
            ||
| 4216 | * checks for the existence of the title in the page table, and will  | 
            ||
| 4217 | * thus return false for interwiki links, special pages and the like.  | 
            ||
| 4218 | * If you want to know if a title can be meaningfully viewed, you should  | 
            ||
| 4219 | * probably call the isKnown() method instead.  | 
            ||
| 4220 | *  | 
            ||
| 4221 | * @param int $flags An optional bit field; may be Title::GAID_FOR_UPDATE to check  | 
            ||
| 4222 | * from master/for update  | 
            ||
| 4223 | * @return bool  | 
            ||
| 4224 | */  | 
            ||
| 4225 | 	public function exists( $flags = 0 ) { | 
            ||
| 4230 | |||
| 4231 | /**  | 
            ||
| 4232 | * Should links to this title be shown as potentially viewable (i.e. as  | 
            ||
| 4233 | * "bluelinks"), even if there's no record by this title in the page  | 
            ||
| 4234 | * table?  | 
            ||
| 4235 | *  | 
            ||
| 4236 | * This function is semi-deprecated for public use, as well as somewhat  | 
            ||
| 4237 | * misleadingly named. You probably just want to call isKnown(), which  | 
            ||
| 4238 | * calls this function internally.  | 
            ||
| 4239 | *  | 
            ||
| 4240 | * (ISSUE: Most of these checks are cheap, but the file existence check  | 
            ||
| 4241 | * can potentially be quite expensive. Including it here fixes a lot of  | 
            ||
| 4242 | * existing code, but we might want to add an optional parameter to skip  | 
            ||
| 4243 | * it and any other expensive checks.)  | 
            ||
| 4244 | *  | 
            ||
| 4245 | * @return bool  | 
            ||
| 4246 | */  | 
            ||
| 4247 | 	public function isAlwaysKnown() { | 
            ||
| 4288 | |||
| 4289 | /**  | 
            ||
| 4290 | * Does this title refer to a page that can (or might) be meaningfully  | 
            ||
| 4291 | * viewed? In particular, this function may be used to determine if  | 
            ||
| 4292 | * links to the title should be rendered as "bluelinks" (as opposed to  | 
            ||
| 4293 | * "redlinks" to non-existent pages).  | 
            ||
| 4294 | * Adding something else to this function will cause inconsistency  | 
            ||
| 4295 | * since LinkHolderArray calls isAlwaysKnown() and does its own  | 
            ||
| 4296 | * page existence check.  | 
            ||
| 4297 | *  | 
            ||
| 4298 | * @return bool  | 
            ||
| 4299 | */  | 
            ||
| 4300 | 	public function isKnown() { | 
            ||
| 4303 | |||
| 4304 | /**  | 
            ||
| 4305 | * Does this page have source text?  | 
            ||
| 4306 | *  | 
            ||
| 4307 | * @return bool  | 
            ||
| 4308 | */  | 
            ||
| 4309 | 	public function hasSourceText() { | 
            ||
| 4329 | |||
| 4330 | /**  | 
            ||
| 4331 | * Get the default message text or false if the message doesn't exist  | 
            ||
| 4332 | *  | 
            ||
| 4333 | * @return string|bool  | 
            ||
| 4334 | */  | 
            ||
| 4335 | 	public function getDefaultMessageText() { | 
            ||
| 4353 | |||
| 4354 | /**  | 
            ||
| 4355 | * Updates page_touched for this page; called from LinksUpdate.php  | 
            ||
| 4356 | *  | 
            ||
| 4357 | * @param string $purgeTime [optional] TS_MW timestamp  | 
            ||
| 4358 | * @return bool True if the update succeeded  | 
            ||
| 4359 | */  | 
            ||
| 4360 | 	public function invalidateCache( $purgeTime = null ) { | 
            ||
| 4385 | |||
| 4386 | /**  | 
            ||
| 4387 | * Update page_touched timestamps and send CDN purge messages for  | 
            ||
| 4388 | * pages linking to this title. May be sent to the job queue depending  | 
            ||
| 4389 | * on the number of links. Typically called on create and delete.  | 
            ||
| 4390 | */  | 
            ||
| 4391 | 	public function touchLinks() { | 
            ||
| 4397 | |||
| 4398 | /**  | 
            ||
| 4399 | * Get the last touched timestamp  | 
            ||
| 4400 | *  | 
            ||
| 4401 | * @param IDatabase $db Optional db  | 
            ||
| 4402 | * @return string Last-touched timestamp  | 
            ||
| 4403 | */  | 
            ||
| 4404 | 	public function getTouched( $db = null ) { | 
            ||
| 4411 | |||
| 4412 | /**  | 
            ||
| 4413 | * Get the timestamp when this page was updated since the user last saw it.  | 
            ||
| 4414 | *  | 
            ||
| 4415 | * @param User $user  | 
            ||
| 4416 | * @return string|null  | 
            ||
| 4417 | */  | 
            ||
| 4418 | 	public function getNotificationTimestamp( $user = null ) { | 
            ||
| 4449 | |||
| 4450 | /**  | 
            ||
| 4451 | * Generate strings used for xml 'id' names in monobook tabs  | 
            ||
| 4452 | *  | 
            ||
| 4453 | * @param string $prepend Defaults to 'nstab-'  | 
            ||
| 4454 | * @return string XML 'id' name  | 
            ||
| 4455 | */  | 
            ||
| 4456 | 	public function getNamespaceKey( $prepend = 'nstab-' ) { | 
            ||
| 4480 | |||
| 4481 | /**  | 
            ||
| 4482 | * Get all extant redirects to this Title  | 
            ||
| 4483 | *  | 
            ||
| 4484 | * @param int|null $ns Single namespace to consider; null to consider all namespaces  | 
            ||
| 4485 | * @return Title[] Array of Title redirects to this title  | 
            ||
| 4486 | */  | 
            ||
| 4487 | 	public function getRedirectsHere( $ns = null ) { | 
            ||
| 4517 | |||
| 4518 | /**  | 
            ||
| 4519 | * Check if this Title is a valid redirect target  | 
            ||
| 4520 | *  | 
            ||
| 4521 | * @return bool  | 
            ||
| 4522 | */  | 
            ||
| 4523 | 	public function isValidRedirectTarget() { | 
            ||
| 4541 | |||
| 4542 | /**  | 
            ||
| 4543 | * Get a backlink cache object  | 
            ||
| 4544 | *  | 
            ||
| 4545 | * @return BacklinkCache  | 
            ||
| 4546 | */  | 
            ||
| 4547 | 	public function getBacklinkCache() { | 
            ||
| 4550 | |||
| 4551 | /**  | 
            ||
| 4552 | * Whether the magic words __INDEX__ and __NOINDEX__ function for this page.  | 
            ||
| 4553 | *  | 
            ||
| 4554 | * @return bool  | 
            ||
| 4555 | */  | 
            ||
| 4556 | 	public function canUseNoindex() { | 
            ||
| 4566 | |||
| 4567 | /**  | 
            ||
| 4568 | * Returns the raw sort key to be used for categories, with the specified  | 
            ||
| 4569 | * prefix. This will be fed to Collation::getSortKey() to get a  | 
            ||
| 4570 | * binary sortkey that can be used for actual sorting.  | 
            ||
| 4571 | *  | 
            ||
| 4572 | * @param string $prefix The prefix to be used, specified using  | 
            ||
| 4573 | 	 *   {{defaultsort:}} or like [[Category:Foo|prefix]].  Empty for no | 
            ||
| 4574 | * prefix.  | 
            ||
| 4575 | * @return string  | 
            ||
| 4576 | */  | 
            ||
| 4577 | 	public function getCategorySortkey( $prefix = '' ) { | 
            ||
| 4595 | |||
| 4596 | /**  | 
            ||
| 4597 | * Returns the page language code saved in the database, if $wgPageLanguageUseDB is set  | 
            ||
| 4598 | * to true in LocalSettings.php, otherwise returns false. If there is no language saved in  | 
            ||
| 4599 | * the db, it will return NULL.  | 
            ||
| 4600 | *  | 
            ||
| 4601 | * @return string|null|bool  | 
            ||
| 4602 | */  | 
            ||
| 4603 | 	private function getDbPageLanguageCode() { | 
            ||
| 4616 | |||
| 4617 | /**  | 
            ||
| 4618 | * Get the language in which the content of this page is written in  | 
            ||
| 4619 | * wikitext. Defaults to $wgContLang, but in certain cases it can be  | 
            ||
| 4620 | * e.g. $wgLang (such as special pages, which are in the user language).  | 
            ||
| 4621 | *  | 
            ||
| 4622 | * @since 1.18  | 
            ||
| 4623 | * @return Language  | 
            ||
| 4624 | */  | 
            ||
| 4625 | 	public function getPageLanguage() { | 
            ||
| 4654 | |||
| 4655 | /**  | 
            ||
| 4656 | * Get the language in which the content of this page is written when  | 
            ||
| 4657 | * viewed by user. Defaults to $wgContLang, but in certain cases it can be  | 
            ||
| 4658 | * e.g. $wgLang (such as special pages, which are in the user language).  | 
            ||
| 4659 | *  | 
            ||
| 4660 | * @since 1.20  | 
            ||
| 4661 | * @return Language  | 
            ||
| 4662 | */  | 
            ||
| 4663 | 	public function getPageViewLanguage() { | 
            ||
| 4696 | |||
| 4697 | /**  | 
            ||
| 4698 | * Get a list of rendered edit notices for this page.  | 
            ||
| 4699 | *  | 
            ||
| 4700 | * Array is keyed by the original message key, and values are rendered using parseAsBlock, so  | 
            ||
| 4701 | * they will already be wrapped in paragraphs.  | 
            ||
| 4702 | *  | 
            ||
| 4703 | * @since 1.21  | 
            ||
| 4704 | * @param int $oldid Revision ID that's being edited  | 
            ||
| 4705 | * @return array  | 
            ||
| 4706 | */  | 
            ||
| 4707 | 	public function getEditNotices( $oldid = 0 ) { | 
            ||
| 4774 | |||
| 4775 | /**  | 
            ||
| 4776 | * @return array  | 
            ||
| 4777 | */  | 
            ||
| 4778 | 	public function __sleep() { | 
            ||
| 4789 | |||
| 4790 | 	public function __wakeup() { | 
            ||
| 4795 | |||
| 4796 | }  | 
            ||
| 4797 | 
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.