MediaWiki
REL1_20
|
00001 <?php 00033 class Title { 00035 // @{ 00036 static private $titleCache = array(); 00037 // @} 00038 00044 const CACHE_MAX = 1000; 00045 00050 const GAID_FOR_UPDATE = 1; 00051 00057 // @{ 00058 00059 var $mTextform = ''; // /< Text form (spaces not underscores) of the main part 00060 var $mUrlform = ''; // /< URL-encoded form of the main part 00061 var $mDbkeyform = ''; // /< Main part with underscores 00062 var $mUserCaseDBKey; // /< DB key with the initial letter in the case specified by the user 00063 var $mNamespace = NS_MAIN; // /< Namespace index, i.e. one of the NS_xxxx constants 00064 var $mInterwiki = ''; // /< Interwiki prefix (or null string) 00065 var $mFragment; // /< Title fragment (i.e. the bit after the #) 00066 var $mArticleID = -1; // /< Article ID, fetched from the link cache on demand 00067 var $mLatestID = false; // /< ID of most recent revision 00068 private $mEstimateRevisions; // /< Estimated number of revisions; null of not loaded 00069 var $mRestrictions = array(); // /< Array of groups allowed to edit this article 00070 var $mOldRestrictions = false; 00071 var $mCascadeRestriction; 00072 var $mCascadingRestrictions; // Caching the results of getCascadeProtectionSources 00073 var $mRestrictionsExpiry = array(); 00074 var $mHasCascadingRestrictions; 00075 var $mCascadeSources; 00076 var $mRestrictionsLoaded = false; 00077 var $mPrefixedText; 00078 var $mTitleProtection; 00079 # Don't change the following default, NS_MAIN is hardcoded in several 00080 # places. See bug 696. 00081 var $mDefaultNamespace = NS_MAIN; // /< Namespace index when there is no namespace 00082 # Zero except in {{transclusion}} tags 00083 var $mWatched = null; // /< Is $wgUser watching this page? null if unfilled, accessed through userIsWatching() 00084 var $mLength = -1; // /< The page length, 0 for special pages 00085 var $mRedirect = null; // /< Is the article at this title a redirect? 00086 var $mNotificationTimestamp = array(); // /< Associative array of user ID -> timestamp/false 00087 var $mHasSubpage; // /< Whether a page has any subpages 00088 // @} 00089 00090 00094 /*protected*/ function __construct() { } 00095 00104 public static function newFromDBkey( $key ) { 00105 $t = new Title(); 00106 $t->mDbkeyform = $key; 00107 if ( $t->secureAndSplit() ) { 00108 return $t; 00109 } else { 00110 return null; 00111 } 00112 } 00113 00127 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) { 00128 if ( is_object( $text ) ) { 00129 throw new MWException( 'Title::newFromText given an object' ); 00130 } 00131 00140 if ( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) { 00141 return Title::$titleCache[$text]; 00142 } 00143 00144 # Convert things like é ā or 〗 into normalized (bug 14952) text 00145 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text ); 00146 00147 $t = new Title(); 00148 $t->mDbkeyform = str_replace( ' ', '_', $filteredText ); 00149 $t->mDefaultNamespace = $defaultNamespace; 00150 00151 static $cachedcount = 0 ; 00152 if ( $t->secureAndSplit() ) { 00153 if ( $defaultNamespace == NS_MAIN ) { 00154 if ( $cachedcount >= self::CACHE_MAX ) { 00155 # Avoid memory leaks on mass operations... 00156 Title::$titleCache = array(); 00157 $cachedcount = 0; 00158 } 00159 $cachedcount++; 00160 Title::$titleCache[$text] =& $t; 00161 } 00162 return $t; 00163 } else { 00164 $ret = null; 00165 return $ret; 00166 } 00167 } 00168 00184 public static function newFromURL( $url ) { 00185 $t = new Title(); 00186 00187 # For compatibility with old buggy URLs. "+" is usually not valid in titles, 00188 # but some URLs used it as a space replacement and they still come 00189 # from some external search tools. 00190 if ( strpos( self::legalChars(), '+' ) === false ) { 00191 $url = str_replace( '+', ' ', $url ); 00192 } 00193 00194 $t->mDbkeyform = str_replace( ' ', '_', $url ); 00195 if ( $t->secureAndSplit() ) { 00196 return $t; 00197 } else { 00198 return null; 00199 } 00200 } 00201 00209 public static function newFromID( $id, $flags = 0 ) { 00210 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE ); 00211 $row = $db->selectRow( 00212 'page', 00213 array( 00214 'page_namespace', 'page_title', 'page_id', 00215 'page_len', 'page_is_redirect', 'page_latest', 00216 ), 00217 array( 'page_id' => $id ), 00218 __METHOD__ 00219 ); 00220 if ( $row !== false ) { 00221 $title = Title::newFromRow( $row ); 00222 } else { 00223 $title = null; 00224 } 00225 return $title; 00226 } 00227 00234 public static function newFromIDs( $ids ) { 00235 if ( !count( $ids ) ) { 00236 return array(); 00237 } 00238 $dbr = wfGetDB( DB_SLAVE ); 00239 00240 $res = $dbr->select( 00241 'page', 00242 array( 00243 'page_namespace', 'page_title', 'page_id', 00244 'page_len', 'page_is_redirect', 'page_latest', 00245 ), 00246 array( 'page_id' => $ids ), 00247 __METHOD__ 00248 ); 00249 00250 $titles = array(); 00251 foreach ( $res as $row ) { 00252 $titles[] = Title::newFromRow( $row ); 00253 } 00254 return $titles; 00255 } 00256 00263 public static function newFromRow( $row ) { 00264 $t = self::makeTitle( $row->page_namespace, $row->page_title ); 00265 $t->loadFromRow( $row ); 00266 return $t; 00267 } 00268 00275 public function loadFromRow( $row ) { 00276 if ( $row ) { // page found 00277 if ( isset( $row->page_id ) ) 00278 $this->mArticleID = (int)$row->page_id; 00279 if ( isset( $row->page_len ) ) 00280 $this->mLength = (int)$row->page_len; 00281 if ( isset( $row->page_is_redirect ) ) 00282 $this->mRedirect = (bool)$row->page_is_redirect; 00283 if ( isset( $row->page_latest ) ) 00284 $this->mLatestID = (int)$row->page_latest; 00285 } else { // page not found 00286 $this->mArticleID = 0; 00287 $this->mLength = 0; 00288 $this->mRedirect = false; 00289 $this->mLatestID = 0; 00290 } 00291 } 00292 00306 public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) { 00307 $t = new Title(); 00308 $t->mInterwiki = $interwiki; 00309 $t->mFragment = $fragment; 00310 $t->mNamespace = $ns = intval( $ns ); 00311 $t->mDbkeyform = str_replace( ' ', '_', $title ); 00312 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0; 00313 $t->mUrlform = wfUrlencode( $t->mDbkeyform ); 00314 $t->mTextform = str_replace( '_', ' ', $title ); 00315 return $t; 00316 } 00317 00329 public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) { 00330 if ( !MWNamespace::exists( $ns ) ) { 00331 return null; 00332 } 00333 00334 $t = new Title(); 00335 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki ); 00336 if ( $t->secureAndSplit() ) { 00337 return $t; 00338 } else { 00339 return null; 00340 } 00341 } 00342 00348 public static function newMainPage() { 00349 $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() ); 00350 // Don't give fatal errors if the message is broken 00351 if ( !$title ) { 00352 $title = Title::newFromText( 'Main Page' ); 00353 } 00354 return $title; 00355 } 00356 00366 public static function newFromRedirect( $text ) { 00367 return self::newFromRedirectInternal( $text ); 00368 } 00369 00379 public static function newFromRedirectRecurse( $text ) { 00380 $titles = self::newFromRedirectArray( $text ); 00381 return $titles ? array_pop( $titles ) : null; 00382 } 00383 00393 public static function newFromRedirectArray( $text ) { 00394 global $wgMaxRedirects; 00395 $title = self::newFromRedirectInternal( $text ); 00396 if ( is_null( $title ) ) { 00397 return null; 00398 } 00399 // recursive check to follow double redirects 00400 $recurse = $wgMaxRedirects; 00401 $titles = array( $title ); 00402 while ( --$recurse > 0 ) { 00403 if ( $title->isRedirect() ) { 00404 $page = WikiPage::factory( $title ); 00405 $newtitle = $page->getRedirectTarget(); 00406 } else { 00407 break; 00408 } 00409 // Redirects to some special pages are not permitted 00410 if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) { 00411 // the new title passes the checks, so make that our current title so that further recursion can be checked 00412 $title = $newtitle; 00413 $titles[] = $newtitle; 00414 } else { 00415 break; 00416 } 00417 } 00418 return $titles; 00419 } 00420 00428 protected static function newFromRedirectInternal( $text ) { 00429 global $wgMaxRedirects; 00430 if ( $wgMaxRedirects < 1 ) { 00431 //redirects are disabled, so quit early 00432 return null; 00433 } 00434 $redir = MagicWord::get( 'redirect' ); 00435 $text = trim( $text ); 00436 if ( $redir->matchStartAndRemove( $text ) ) { 00437 // Extract the first link and see if it's usable 00438 // Ensure that it really does come directly after #REDIRECT 00439 // Some older redirects included a colon, so don't freak about that! 00440 $m = array(); 00441 if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) { 00442 // Strip preceding colon used to "escape" categories, etc. 00443 // and URL-decode links 00444 if ( strpos( $m[1], '%' ) !== false ) { 00445 // Match behavior of inline link parsing here; 00446 $m[1] = rawurldecode( ltrim( $m[1], ':' ) ); 00447 } 00448 $title = Title::newFromText( $m[1] ); 00449 // If the title is a redirect to bad special pages or is invalid, return null 00450 if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) { 00451 return null; 00452 } 00453 return $title; 00454 } 00455 } 00456 return null; 00457 } 00458 00465 public static function nameOf( $id ) { 00466 $dbr = wfGetDB( DB_SLAVE ); 00467 00468 $s = $dbr->selectRow( 00469 'page', 00470 array( 'page_namespace', 'page_title' ), 00471 array( 'page_id' => $id ), 00472 __METHOD__ 00473 ); 00474 if ( $s === false ) { 00475 return null; 00476 } 00477 00478 $n = self::makeName( $s->page_namespace, $s->page_title ); 00479 return $n; 00480 } 00481 00487 public static function legalChars() { 00488 global $wgLegalTitleChars; 00489 return $wgLegalTitleChars; 00490 } 00491 00499 static function getTitleInvalidRegex() { 00500 static $rxTc = false; 00501 if ( !$rxTc ) { 00502 # Matching titles will be held as illegal. 00503 $rxTc = '/' . 00504 # Any character not allowed is forbidden... 00505 '[^' . self::legalChars() . ']' . 00506 # URL percent encoding sequences interfere with the ability 00507 # to round-trip titles -- you can't link to them consistently. 00508 '|%[0-9A-Fa-f]{2}' . 00509 # XML/HTML character references produce similar issues. 00510 '|&[A-Za-z0-9\x80-\xff]+;' . 00511 '|&#[0-9]+;' . 00512 '|&#x[0-9A-Fa-f]+;' . 00513 '/S'; 00514 } 00515 00516 return $rxTc; 00517 } 00518 00527 public static function indexTitle( $ns, $title ) { 00528 global $wgContLang; 00529 00530 $lc = SearchEngine::legalSearchChars() . '&#;'; 00531 $t = $wgContLang->normalizeForSearch( $title ); 00532 $t = preg_replace( "/[^{$lc}]+/", ' ', $t ); 00533 $t = $wgContLang->lc( $t ); 00534 00535 # Handle 's, s' 00536 $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t ); 00537 $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t ); 00538 00539 $t = preg_replace( "/\\s+/", ' ', $t ); 00540 00541 if ( $ns == NS_FILE ) { 00542 $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t ); 00543 } 00544 return trim( $t ); 00545 } 00546 00556 public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) { 00557 global $wgContLang; 00558 00559 $namespace = $wgContLang->getNsText( $ns ); 00560 $name = $namespace == '' ? $title : "$namespace:$title"; 00561 if ( strval( $interwiki ) != '' ) { 00562 $name = "$interwiki:$name"; 00563 } 00564 if ( strval( $fragment ) != '' ) { 00565 $name .= '#' . $fragment; 00566 } 00567 return $name; 00568 } 00569 00576 static function escapeFragmentForURL( $fragment ) { 00577 # Note that we don't urlencode the fragment. urlencoded Unicode 00578 # fragments appear not to work in IE (at least up to 7) or in at least 00579 # one version of Opera 9.x. The W3C validator, for one, doesn't seem 00580 # to care if they aren't encoded. 00581 return Sanitizer::escapeId( $fragment, 'noninitial' ); 00582 } 00583 00592 public static function compare( $a, $b ) { 00593 if ( $a->getNamespace() == $b->getNamespace() ) { 00594 return strcmp( $a->getText(), $b->getText() ); 00595 } else { 00596 return $a->getNamespace() - $b->getNamespace(); 00597 } 00598 } 00599 00606 public function isLocal() { 00607 if ( $this->mInterwiki != '' ) { 00608 return Interwiki::fetch( $this->mInterwiki )->isLocal(); 00609 } else { 00610 return true; 00611 } 00612 } 00613 00619 public function isExternal() { 00620 return ( $this->mInterwiki != '' ); 00621 } 00622 00628 public function getInterwiki() { 00629 return $this->mInterwiki; 00630 } 00631 00638 public function isTrans() { 00639 if ( $this->mInterwiki == '' ) { 00640 return false; 00641 } 00642 00643 return Interwiki::fetch( $this->mInterwiki )->isTranscludable(); 00644 } 00645 00651 public function getTransWikiID() { 00652 if ( $this->mInterwiki == '' ) { 00653 return false; 00654 } 00655 00656 return Interwiki::fetch( $this->mInterwiki )->getWikiID(); 00657 } 00658 00664 public function getText() { 00665 return $this->mTextform; 00666 } 00667 00673 public function getPartialURL() { 00674 return $this->mUrlform; 00675 } 00676 00682 public function getDBkey() { 00683 return $this->mDbkeyform; 00684 } 00685 00691 function getUserCaseDBKey() { 00692 return $this->mUserCaseDBKey; 00693 } 00694 00700 public function getNamespace() { 00701 return $this->mNamespace; 00702 } 00703 00709 public function getNsText() { 00710 global $wgContLang; 00711 00712 if ( $this->mInterwiki != '' ) { 00713 // This probably shouldn't even happen. ohh man, oh yuck. 00714 // But for interwiki transclusion it sometimes does. 00715 // Shit. Shit shit shit. 00716 // 00717 // Use the canonical namespaces if possible to try to 00718 // resolve a foreign namespace. 00719 if ( MWNamespace::exists( $this->mNamespace ) ) { 00720 return MWNamespace::getCanonicalName( $this->mNamespace ); 00721 } 00722 } 00723 00724 if ( $wgContLang->needsGenderDistinction() && 00725 MWNamespace::hasGenderDistinction( $this->mNamespace ) ) { 00726 $gender = GenderCache::singleton()->getGenderOf( $this->getText(), __METHOD__ ); 00727 return $wgContLang->getGenderNsText( $this->mNamespace, $gender ); 00728 } 00729 00730 return $wgContLang->getNsText( $this->mNamespace ); 00731 } 00732 00738 public function getSubjectNsText() { 00739 global $wgContLang; 00740 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) ); 00741 } 00742 00748 public function getTalkNsText() { 00749 global $wgContLang; 00750 return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) ); 00751 } 00752 00758 public function canTalk() { 00759 return( MWNamespace::canTalk( $this->mNamespace ) ); 00760 } 00761 00768 public function canExist() { 00769 return $this->mNamespace >= NS_MAIN; 00770 } 00771 00777 public function isWatchable() { 00778 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() ); 00779 } 00780 00786 public function isSpecialPage() { 00787 return $this->getNamespace() == NS_SPECIAL; 00788 } 00789 00796 public function isSpecial( $name ) { 00797 if ( $this->isSpecialPage() ) { 00798 list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() ); 00799 if ( $name == $thisName ) { 00800 return true; 00801 } 00802 } 00803 return false; 00804 } 00805 00812 public function fixSpecialName() { 00813 if ( $this->isSpecialPage() ) { 00814 list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform ); 00815 if ( $canonicalName ) { 00816 $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par ); 00817 if ( $localName != $this->mDbkeyform ) { 00818 return Title::makeTitle( NS_SPECIAL, $localName ); 00819 } 00820 } 00821 } 00822 return $this; 00823 } 00824 00835 public function inNamespace( $ns ) { 00836 return MWNamespace::equals( $this->getNamespace(), $ns ); 00837 } 00838 00846 public function inNamespaces( /* ... */ ) { 00847 $namespaces = func_get_args(); 00848 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) { 00849 $namespaces = $namespaces[0]; 00850 } 00851 00852 foreach ( $namespaces as $ns ) { 00853 if ( $this->inNamespace( $ns ) ) { 00854 return true; 00855 } 00856 } 00857 00858 return false; 00859 } 00860 00874 public function hasSubjectNamespace( $ns ) { 00875 return MWNamespace::subjectEquals( $this->getNamespace(), $ns ); 00876 } 00877 00885 public function isContentPage() { 00886 return MWNamespace::isContent( $this->getNamespace() ); 00887 } 00888 00895 public function isMovable() { 00896 if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->getInterwiki() != '' ) { 00897 // Interwiki title or immovable namespace. Hooks don't get to override here 00898 return false; 00899 } 00900 00901 $result = true; 00902 wfRunHooks( 'TitleIsMovable', array( $this, &$result ) ); 00903 return $result; 00904 } 00905 00916 public function isMainPage() { 00917 return $this->equals( Title::newMainPage() ); 00918 } 00919 00925 public function isSubpage() { 00926 return MWNamespace::hasSubpages( $this->mNamespace ) 00927 ? strpos( $this->getText(), '/' ) !== false 00928 : false; 00929 } 00930 00936 public function isConversionTable() { 00937 return $this->getNamespace() == NS_MEDIAWIKI && 00938 strpos( $this->getText(), 'Conversiontable/' ) === 0; 00939 } 00940 00946 public function isWikitextPage() { 00947 $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage(); 00948 wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) ); 00949 return $retval; 00950 } 00951 00958 public function isCssOrJsPage() { 00959 $retval = $this->mNamespace == NS_MEDIAWIKI 00960 && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0; 00961 wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) ); 00962 return $retval; 00963 } 00964 00969 public function isCssJsSubpage() { 00970 return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) ); 00971 } 00972 00978 public function getSkinFromCssJsSubpage() { 00979 $subpage = explode( '/', $this->mTextform ); 00980 $subpage = $subpage[ count( $subpage ) - 1 ]; 00981 $lastdot = strrpos( $subpage, '.' ); 00982 if ( $lastdot === false ) 00983 return $subpage; # Never happens: only called for names ending in '.css' or '.js' 00984 return substr( $subpage, 0, $lastdot ); 00985 } 00986 00992 public function isCssSubpage() { 00993 return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) ); 00994 } 00995 01001 public function isJsSubpage() { 01002 return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) ); 01003 } 01004 01010 public function isTalkPage() { 01011 return MWNamespace::isTalk( $this->getNamespace() ); 01012 } 01013 01019 public function getTalkPage() { 01020 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() ); 01021 } 01022 01029 public function getSubjectPage() { 01030 // Is this the same title? 01031 $subjectNS = MWNamespace::getSubject( $this->getNamespace() ); 01032 if ( $this->getNamespace() == $subjectNS ) { 01033 return $this; 01034 } 01035 return Title::makeTitle( $subjectNS, $this->getDBkey() ); 01036 } 01037 01043 public function getDefaultNamespace() { 01044 return $this->mDefaultNamespace; 01045 } 01046 01053 public function getIndexTitle() { 01054 return Title::indexTitle( $this->mNamespace, $this->mTextform ); 01055 } 01056 01062 public function getFragment() { 01063 return $this->mFragment; 01064 } 01065 01070 public function getFragmentForURL() { 01071 if ( $this->mFragment == '' ) { 01072 return ''; 01073 } else { 01074 return '#' . Title::escapeFragmentForURL( $this->mFragment ); 01075 } 01076 } 01077 01088 public function setFragment( $fragment ) { 01089 $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) ); 01090 } 01091 01100 private function prefix( $name ) { 01101 $p = ''; 01102 if ( $this->mInterwiki != '' ) { 01103 $p = $this->mInterwiki . ':'; 01104 } 01105 01106 if ( 0 != $this->mNamespace ) { 01107 $p .= $this->getNsText() . ':'; 01108 } 01109 return $p . $name; 01110 } 01111 01118 public function getPrefixedDBkey() { 01119 $s = $this->prefix( $this->mDbkeyform ); 01120 $s = str_replace( ' ', '_', $s ); 01121 return $s; 01122 } 01123 01130 public function getPrefixedText() { 01131 // @todo FIXME: Bad usage of empty() ? 01132 if ( empty( $this->mPrefixedText ) ) { 01133 $s = $this->prefix( $this->mTextform ); 01134 $s = str_replace( '_', ' ', $s ); 01135 $this->mPrefixedText = $s; 01136 } 01137 return $this->mPrefixedText; 01138 } 01139 01145 public function __toString() { 01146 return $this->getPrefixedText(); 01147 } 01148 01155 public function getFullText() { 01156 $text = $this->getPrefixedText(); 01157 if ( $this->mFragment != '' ) { 01158 $text .= '#' . $this->mFragment; 01159 } 01160 return $text; 01161 } 01162 01168 public function getBaseText() { 01169 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { 01170 return $this->getText(); 01171 } 01172 01173 $parts = explode( '/', $this->getText() ); 01174 # Don't discard the real title if there's no subpage involved 01175 if ( count( $parts ) > 1 ) { 01176 unset( $parts[count( $parts ) - 1] ); 01177 } 01178 return implode( '/', $parts ); 01179 } 01180 01186 public function getSubpageText() { 01187 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { 01188 return( $this->mTextform ); 01189 } 01190 $parts = explode( '/', $this->mTextform ); 01191 return( $parts[count( $parts ) - 1] ); 01192 } 01193 01200 public function getEscapedText() { 01201 wfDeprecated( __METHOD__, '1.19' ); 01202 return htmlspecialchars( $this->getPrefixedText() ); 01203 } 01204 01210 public function getSubpageUrlForm() { 01211 $text = $this->getSubpageText(); 01212 $text = wfUrlencode( str_replace( ' ', '_', $text ) ); 01213 return( $text ); 01214 } 01215 01221 public function getPrefixedURL() { 01222 $s = $this->prefix( $this->mDbkeyform ); 01223 $s = wfUrlencode( str_replace( ' ', '_', $s ) ); 01224 return $s; 01225 } 01226 01240 private static function fixUrlQueryArgs( $query, $query2 = false ) { 01241 if( $query2 !== false ) { 01242 wfDeprecated( "Title::get{Canonical,Full,Link,Local} method called with a second parameter is deprecated. Add your parameter to an array passed as the first parameter.", "1.19" ); 01243 } 01244 if ( is_array( $query ) ) { 01245 $query = wfArrayToCGI( $query ); 01246 } 01247 if ( $query2 ) { 01248 if ( is_string( $query2 ) ) { 01249 // $query2 is a string, we will consider this to be 01250 // a deprecated $variant argument and add it to the query 01251 $query2 = wfArrayToCGI( array( 'variant' => $query2 ) ); 01252 } else { 01253 $query2 = wfArrayToCGI( $query2 ); 01254 } 01255 // If we have $query content add a & to it first 01256 if ( $query ) { 01257 $query .= '&'; 01258 } 01259 // Now append the queries together 01260 $query .= $query2; 01261 } 01262 return $query; 01263 } 01264 01276 public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) { 01277 $query = self::fixUrlQueryArgs( $query, $query2 ); 01278 01279 # Hand off all the decisions on urls to getLocalURL 01280 $url = $this->getLocalURL( $query ); 01281 01282 # Expand the url to make it a full url. Note that getLocalURL has the 01283 # potential to output full urls for a variety of reasons, so we use 01284 # wfExpandUrl instead of simply prepending $wgServer 01285 $url = wfExpandUrl( $url, $proto ); 01286 01287 # Finally, add the fragment. 01288 $url .= $this->getFragmentForURL(); 01289 01290 wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) ); 01291 return $url; 01292 } 01293 01312 public function getLocalURL( $query = '', $query2 = false ) { 01313 global $wgArticlePath, $wgScript, $wgServer, $wgRequest; 01314 01315 $query = self::fixUrlQueryArgs( $query, $query2 ); 01316 01317 $interwiki = Interwiki::fetch( $this->mInterwiki ); 01318 if ( $interwiki ) { 01319 $namespace = $this->getNsText(); 01320 if ( $namespace != '' ) { 01321 # Can this actually happen? Interwikis shouldn't be parsed. 01322 # Yes! It can in interwiki transclusion. But... it probably shouldn't. 01323 $namespace .= ':'; 01324 } 01325 $url = $interwiki->getURL( $namespace . $this->getDBkey() ); 01326 $url = wfAppendQuery( $url, $query ); 01327 } else { 01328 $dbkey = wfUrlencode( $this->getPrefixedDBkey() ); 01329 if ( $query == '' ) { 01330 $url = str_replace( '$1', $dbkey, $wgArticlePath ); 01331 wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) ); 01332 } else { 01333 global $wgVariantArticlePath, $wgActionPaths; 01334 $url = false; 01335 $matches = array(); 01336 01337 if ( !empty( $wgActionPaths ) && 01338 preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) ) 01339 { 01340 $action = urldecode( $matches[2] ); 01341 if ( isset( $wgActionPaths[$action] ) ) { 01342 $query = $matches[1]; 01343 if ( isset( $matches[4] ) ) { 01344 $query .= $matches[4]; 01345 } 01346 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] ); 01347 if ( $query != '' ) { 01348 $url = wfAppendQuery( $url, $query ); 01349 } 01350 } 01351 } 01352 01353 if ( $url === false && 01354 $wgVariantArticlePath && 01355 $this->getPageLanguage()->hasVariants() && 01356 preg_match( '/^variant=([^&]*)$/', $query, $matches ) ) 01357 { 01358 $variant = urldecode( $matches[1] ); 01359 if ( $this->getPageLanguage()->hasVariant( $variant ) ) { 01360 // Only do the variant replacement if the given variant is a valid 01361 // variant for the page's language. 01362 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath ); 01363 $url = str_replace( '$1', $dbkey, $url ); 01364 } 01365 } 01366 01367 if ( $url === false ) { 01368 if ( $query == '-' ) { 01369 $query = ''; 01370 } 01371 $url = "{$wgScript}?title={$dbkey}&{$query}"; 01372 } 01373 } 01374 01375 wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) ); 01376 01377 // @todo FIXME: This causes breakage in various places when we 01378 // actually expected a local URL and end up with dupe prefixes. 01379 if ( $wgRequest->getVal( 'action' ) == 'render' ) { 01380 $url = $wgServer . $url; 01381 } 01382 } 01383 wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) ); 01384 return $url; 01385 } 01386 01402 public function getLinkURL( $query = '', $query2 = false ) { 01403 wfProfileIn( __METHOD__ ); 01404 if ( $this->isExternal() ) { 01405 $ret = $this->getFullURL( $query, $query2 ); 01406 } elseif ( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) { 01407 $ret = $this->getFragmentForURL(); 01408 } else { 01409 $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL(); 01410 } 01411 wfProfileOut( __METHOD__ ); 01412 return $ret; 01413 } 01414 01426 public function escapeLocalURL( $query = '', $query2 = false ) { 01427 wfDeprecated( __METHOD__, '1.19' ); 01428 return htmlspecialchars( $this->getLocalURL( $query, $query2 ) ); 01429 } 01430 01440 public function escapeFullURL( $query = '', $query2 = false ) { 01441 wfDeprecated( __METHOD__, '1.19' ); 01442 return htmlspecialchars( $this->getFullURL( $query, $query2 ) ); 01443 } 01444 01459 public function getInternalURL( $query = '', $query2 = false ) { 01460 global $wgInternalServer, $wgServer; 01461 $query = self::fixUrlQueryArgs( $query, $query2 ); 01462 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer; 01463 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP ); 01464 wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) ); 01465 return $url; 01466 } 01467 01481 public function getCanonicalURL( $query = '', $query2 = false ) { 01482 $query = self::fixUrlQueryArgs( $query, $query2 ); 01483 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL ); 01484 wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) ); 01485 return $url; 01486 } 01487 01497 public function escapeCanonicalURL( $query = '', $query2 = false ) { 01498 wfDeprecated( __METHOD__, '1.19' ); 01499 return htmlspecialchars( $this->getCanonicalURL( $query, $query2 ) ); 01500 } 01501 01508 public function getEditURL() { 01509 if ( $this->mInterwiki != '' ) { 01510 return ''; 01511 } 01512 $s = $this->getLocalURL( 'action=edit' ); 01513 01514 return $s; 01515 } 01516 01523 public function userIsWatching() { 01524 global $wgUser; 01525 01526 if ( is_null( $this->mWatched ) ) { 01527 if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn() ) { 01528 $this->mWatched = false; 01529 } else { 01530 $this->mWatched = $wgUser->isWatched( $this ); 01531 } 01532 } 01533 return $this->mWatched; 01534 } 01535 01543 public function userCanRead() { 01544 wfDeprecated( __METHOD__, '1.19' ); 01545 return $this->userCan( 'read' ); 01546 } 01547 01563 public function quickUserCan( $action, $user = null ) { 01564 return $this->userCan( $action, $user, false ); 01565 } 01566 01577 public function userCan( $action, $user = null, $doExpensiveQueries = true ) { 01578 if ( !$user instanceof User ) { 01579 global $wgUser; 01580 $user = $wgUser; 01581 } 01582 return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries, true ) ); 01583 } 01584 01598 public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) { 01599 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries ); 01600 01601 // Remove the errors being ignored. 01602 foreach ( $errors as $index => $error ) { 01603 $error_key = is_array( $error ) ? $error[0] : $error; 01604 01605 if ( in_array( $error_key, $ignoreErrors ) ) { 01606 unset( $errors[$index] ); 01607 } 01608 } 01609 01610 return $errors; 01611 } 01612 01624 private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) { 01625 if ( $action == 'create' ) { 01626 if ( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) || 01627 ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) { 01628 $errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' ); 01629 } 01630 } elseif ( $action == 'move' ) { 01631 if ( !$user->isAllowed( 'move-rootuserpages' ) 01632 && $this->mNamespace == NS_USER && !$this->isSubpage() ) { 01633 // Show user page-specific message only if the user can move other pages 01634 $errors[] = array( 'cant-move-user-page' ); 01635 } 01636 01637 // Check if user is allowed to move files if it's a file 01638 if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) { 01639 $errors[] = array( 'movenotallowedfile' ); 01640 } 01641 01642 if ( !$user->isAllowed( 'move' ) ) { 01643 // User can't move anything 01644 global $wgGroupPermissions; 01645 $userCanMove = false; 01646 if ( isset( $wgGroupPermissions['user']['move'] ) ) { 01647 $userCanMove = $wgGroupPermissions['user']['move']; 01648 } 01649 $autoconfirmedCanMove = false; 01650 if ( isset( $wgGroupPermissions['autoconfirmed']['move'] ) ) { 01651 $autoconfirmedCanMove = $wgGroupPermissions['autoconfirmed']['move']; 01652 } 01653 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) { 01654 // custom message if logged-in users without any special rights can move 01655 $errors[] = array( 'movenologintext' ); 01656 } else { 01657 $errors[] = array( 'movenotallowed' ); 01658 } 01659 } 01660 } elseif ( $action == 'move-target' ) { 01661 if ( !$user->isAllowed( 'move' ) ) { 01662 // User can't move anything 01663 $errors[] = array( 'movenotallowed' ); 01664 } elseif ( !$user->isAllowed( 'move-rootuserpages' ) 01665 && $this->mNamespace == NS_USER && !$this->isSubpage() ) { 01666 // Show user page-specific message only if the user can move other pages 01667 $errors[] = array( 'cant-move-to-user-page' ); 01668 } 01669 } elseif ( !$user->isAllowed( $action ) ) { 01670 $errors[] = $this->missingPermissionError( $action, $short ); 01671 } 01672 01673 return $errors; 01674 } 01675 01684 private function resultToError( $errors, $result ) { 01685 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) { 01686 // A single array representing an error 01687 $errors[] = $result; 01688 } elseif ( is_array( $result ) && is_array( $result[0] ) ) { 01689 // A nested array representing multiple errors 01690 $errors = array_merge( $errors, $result ); 01691 } elseif ( $result !== '' && is_string( $result ) ) { 01692 // A string representing a message-id 01693 $errors[] = array( $result ); 01694 } elseif ( $result === false ) { 01695 // a generic "We don't want them to do that" 01696 $errors[] = array( 'badaccess-group0' ); 01697 } 01698 return $errors; 01699 } 01700 01712 private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) { 01713 // Use getUserPermissionsErrors instead 01714 $result = ''; 01715 if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) { 01716 return $result ? array() : array( array( 'badaccess-group0' ) ); 01717 } 01718 // Check getUserPermissionsErrors hook 01719 if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) { 01720 $errors = $this->resultToError( $errors, $result ); 01721 } 01722 // Check getUserPermissionsErrorsExpensive hook 01723 if ( $doExpensiveQueries && !( $short && count( $errors ) > 0 ) && 01724 !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) ) ) { 01725 $errors = $this->resultToError( $errors, $result ); 01726 } 01727 01728 return $errors; 01729 } 01730 01742 private function checkSpecialsAndNSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) { 01743 # Only 'createaccount' and 'execute' can be performed on 01744 # special pages, which don't actually exist in the DB. 01745 $specialOKActions = array( 'createaccount', 'execute', 'read' ); 01746 if ( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions ) ) { 01747 $errors[] = array( 'ns-specialprotected' ); 01748 } 01749 01750 # Check $wgNamespaceProtection for restricted namespaces 01751 if ( $this->isNamespaceProtected( $user ) ) { 01752 $ns = $this->mNamespace == NS_MAIN ? 01753 wfMessage( 'nstab-main' )->text() : $this->getNsText(); 01754 $errors[] = $this->mNamespace == NS_MEDIAWIKI ? 01755 array( 'protectedinterface' ) : array( 'namespaceprotected', $ns ); 01756 } 01757 01758 return $errors; 01759 } 01760 01772 private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) { 01773 # Protect css/js subpages of user pages 01774 # XXX: this might be better using restrictions 01775 # XXX: right 'editusercssjs' is deprecated, for backward compatibility only 01776 if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) 01777 && !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) { 01778 if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) { 01779 $errors[] = array( 'customcssprotected' ); 01780 } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) { 01781 $errors[] = array( 'customjsprotected' ); 01782 } 01783 } 01784 01785 return $errors; 01786 } 01787 01801 private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) { 01802 foreach ( $this->getRestrictions( $action ) as $right ) { 01803 // Backwards compatibility, rewrite sysop -> protect 01804 if ( $right == 'sysop' ) { 01805 $right = 'protect'; 01806 } 01807 if ( $right != '' && !$user->isAllowed( $right ) ) { 01808 // Users with 'editprotected' permission can edit protected pages 01809 // without cascading option turned on. 01810 if ( $action != 'edit' || !$user->isAllowed( 'editprotected' ) 01811 || $this->mCascadeRestriction ) 01812 { 01813 $errors[] = array( 'protectedpagetext', $right ); 01814 } 01815 } 01816 } 01817 01818 return $errors; 01819 } 01820 01832 private function checkCascadingSourcesRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) { 01833 if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) { 01834 # We /could/ use the protection level on the source page, but it's 01835 # fairly ugly as we have to establish a precedence hierarchy for pages 01836 # included by multiple cascade-protected pages. So just restrict 01837 # it to people with 'protect' permission, as they could remove the 01838 # protection anyway. 01839 list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources(); 01840 # Cascading protection depends on more than this page... 01841 # Several cascading protected pages may include this page... 01842 # Check each cascading level 01843 # This is only for protection restrictions, not for all actions 01844 if ( isset( $restrictions[$action] ) ) { 01845 foreach ( $restrictions[$action] as $right ) { 01846 $right = ( $right == 'sysop' ) ? 'protect' : $right; 01847 if ( $right != '' && !$user->isAllowed( $right ) ) { 01848 $pages = ''; 01849 foreach ( $cascadingSources as $page ) 01850 $pages .= '* [[:' . $page->getPrefixedText() . "]]\n"; 01851 $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages ); 01852 } 01853 } 01854 } 01855 } 01856 01857 return $errors; 01858 } 01859 01871 private function checkActionPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) { 01872 global $wgDeleteRevisionsLimit, $wgLang; 01873 01874 if ( $action == 'protect' ) { 01875 if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $doExpensiveQueries, true ) ) ) { 01876 // If they can't edit, they shouldn't protect. 01877 $errors[] = array( 'protect-cantedit' ); 01878 } 01879 } elseif ( $action == 'create' ) { 01880 $title_protection = $this->getTitleProtection(); 01881 if( $title_protection ) { 01882 if( $title_protection['pt_create_perm'] == 'sysop' ) { 01883 $title_protection['pt_create_perm'] = 'protect'; // B/C 01884 } 01885 if( $title_protection['pt_create_perm'] == '' || 01886 !$user->isAllowed( $title_protection['pt_create_perm'] ) ) 01887 { 01888 $errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] ); 01889 } 01890 } 01891 } elseif ( $action == 'move' ) { 01892 // Check for immobile pages 01893 if ( !MWNamespace::isMovable( $this->mNamespace ) ) { 01894 // Specific message for this case 01895 $errors[] = array( 'immobile-source-namespace', $this->getNsText() ); 01896 } elseif ( !$this->isMovable() ) { 01897 // Less specific message for rarer cases 01898 $errors[] = array( 'immobile-source-page' ); 01899 } 01900 } elseif ( $action == 'move-target' ) { 01901 if ( !MWNamespace::isMovable( $this->mNamespace ) ) { 01902 $errors[] = array( 'immobile-target-namespace', $this->getNsText() ); 01903 } elseif ( !$this->isMovable() ) { 01904 $errors[] = array( 'immobile-target-page' ); 01905 } 01906 } elseif ( $action == 'delete' ) { 01907 if ( $doExpensiveQueries && $wgDeleteRevisionsLimit 01908 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion() ) 01909 { 01910 $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ); 01911 } 01912 } 01913 return $errors; 01914 } 01915 01927 private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) { 01928 // Account creation blocks handled at userlogin. 01929 // Unblocking handled in SpecialUnblock 01930 if( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) { 01931 return $errors; 01932 } 01933 01934 global $wgContLang, $wgLang, $wgEmailConfirmToEdit; 01935 01936 if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) { 01937 $errors[] = array( 'confirmedittext' ); 01938 } 01939 01940 if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) { 01941 // Don't block the user from editing their own talk page unless they've been 01942 // explicitly blocked from that too. 01943 } elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) { 01944 $block = $user->getBlock(); 01945 01946 // This is from OutputPage::blockedPage 01947 // Copied at r23888 by werdna 01948 01949 $id = $user->blockedBy(); 01950 $reason = $user->blockedFor(); 01951 if ( $reason == '' ) { 01952 $reason = wfMessage( 'blockednoreason' )->text(); 01953 } 01954 $ip = $user->getRequest()->getIP(); 01955 01956 if ( is_numeric( $id ) ) { 01957 $name = User::whoIs( $id ); 01958 } else { 01959 $name = $id; 01960 } 01961 01962 $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]"; 01963 $blockid = $block->getId(); 01964 $blockExpiry = $block->getExpiry(); 01965 $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true ); 01966 if ( $blockExpiry == 'infinity' ) { 01967 $blockExpiry = wfMessage( 'infiniteblock' )->text(); 01968 } else { 01969 $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true ); 01970 } 01971 01972 $intended = strval( $block->getTarget() ); 01973 01974 $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name, 01975 $blockid, $blockExpiry, $intended, $blockTimestamp ); 01976 } 01977 01978 return $errors; 01979 } 01980 01992 private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) { 01993 global $wgWhitelistRead, $wgGroupPermissions, $wgRevokePermissions; 01994 static $useShortcut = null; 01995 01996 # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below 01997 if ( is_null( $useShortcut ) ) { 01998 $useShortcut = true; 01999 if ( empty( $wgGroupPermissions['*']['read'] ) ) { 02000 # Not a public wiki, so no shortcut 02001 $useShortcut = false; 02002 } elseif ( !empty( $wgRevokePermissions ) ) { 02009 foreach ( $wgRevokePermissions as $perms ) { 02010 if ( !empty( $perms['read'] ) ) { 02011 # We might be removing the read right from the user, so no shortcut 02012 $useShortcut = false; 02013 break; 02014 } 02015 } 02016 } 02017 } 02018 02019 $whitelisted = false; 02020 if ( $useShortcut ) { 02021 # Shortcut for public wikis, allows skipping quite a bit of code 02022 $whitelisted = true; 02023 } elseif ( $user->isAllowed( 'read' ) ) { 02024 # If the user is allowed to read pages, he is allowed to read all pages 02025 $whitelisted = true; 02026 } elseif ( $this->isSpecial( 'Userlogin' ) 02027 || $this->isSpecial( 'ChangePassword' ) 02028 || $this->isSpecial( 'PasswordReset' ) 02029 ) { 02030 # Always grant access to the login page. 02031 # Even anons need to be able to log in. 02032 $whitelisted = true; 02033 } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) { 02034 # Time to check the whitelist 02035 # Only do these checks is there's something to check against 02036 $name = $this->getPrefixedText(); 02037 $dbName = $this->getPrefixedDBKey(); 02038 02039 // Check for explicit whitelisting with and without underscores 02040 if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) { 02041 $whitelisted = true; 02042 } elseif ( $this->getNamespace() == NS_MAIN ) { 02043 # Old settings might have the title prefixed with 02044 # a colon for main-namespace pages 02045 if ( in_array( ':' . $name, $wgWhitelistRead ) ) { 02046 $whitelisted = true; 02047 } 02048 } elseif ( $this->isSpecialPage() ) { 02049 # If it's a special page, ditch the subpage bit and check again 02050 $name = $this->getDBkey(); 02051 list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name ); 02052 if ( $name !== false ) { 02053 $pure = SpecialPage::getTitleFor( $name )->getPrefixedText(); 02054 if ( in_array( $pure, $wgWhitelistRead, true ) ) { 02055 $whitelisted = true; 02056 } 02057 } 02058 } 02059 } 02060 02061 if ( !$whitelisted ) { 02062 # If the title is not whitelisted, give extensions a chance to do so... 02063 wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) ); 02064 if ( !$whitelisted ) { 02065 $errors[] = $this->missingPermissionError( $action, $short ); 02066 } 02067 } 02068 02069 return $errors; 02070 } 02071 02080 private function missingPermissionError( $action, $short ) { 02081 // We avoid expensive display logic for quickUserCan's and such 02082 if ( $short ) { 02083 return array( 'badaccess-group0' ); 02084 } 02085 02086 $groups = array_map( array( 'User', 'makeGroupLinkWiki' ), 02087 User::getGroupsWithPermission( $action ) ); 02088 02089 if ( count( $groups ) ) { 02090 global $wgLang; 02091 return array( 02092 'badaccess-groups', 02093 $wgLang->commaList( $groups ), 02094 count( $groups ) 02095 ); 02096 } else { 02097 return array( 'badaccess-group0' ); 02098 } 02099 } 02100 02112 protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) { 02113 wfProfileIn( __METHOD__ ); 02114 02115 # Read has special handling 02116 if ( $action == 'read' ) { 02117 $checks = array( 02118 'checkPermissionHooks', 02119 'checkReadPermissions', 02120 ); 02121 } else { 02122 $checks = array( 02123 'checkQuickPermissions', 02124 'checkPermissionHooks', 02125 'checkSpecialsAndNSPermissions', 02126 'checkCSSandJSPermissions', 02127 'checkPageRestrictions', 02128 'checkCascadingSourcesRestrictions', 02129 'checkActionPermissions', 02130 'checkUserBlock' 02131 ); 02132 } 02133 02134 $errors = array(); 02135 while( count( $checks ) > 0 && 02136 !( $short && count( $errors ) > 0 ) ) { 02137 $method = array_shift( $checks ); 02138 $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short ); 02139 } 02140 02141 wfProfileOut( __METHOD__ ); 02142 return $errors; 02143 } 02144 02152 public function userCanEditCssSubpage() { 02153 global $wgUser; 02154 wfDeprecated( __METHOD__, '1.19' ); 02155 return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) ) 02156 || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) ); 02157 } 02158 02166 public function userCanEditJsSubpage() { 02167 global $wgUser; 02168 wfDeprecated( __METHOD__, '1.19' ); 02169 return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) ) 02170 || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) ); 02171 } 02172 02180 public static function getFilteredRestrictionTypes( $exists = true ) { 02181 global $wgRestrictionTypes; 02182 $types = $wgRestrictionTypes; 02183 if ( $exists ) { 02184 # Remove the create restriction for existing titles 02185 $types = array_diff( $types, array( 'create' ) ); 02186 } else { 02187 # Only the create and upload restrictions apply to non-existing titles 02188 $types = array_intersect( $types, array( 'create', 'upload' ) ); 02189 } 02190 return $types; 02191 } 02192 02198 public function getRestrictionTypes() { 02199 if ( $this->isSpecialPage() ) { 02200 return array(); 02201 } 02202 02203 $types = self::getFilteredRestrictionTypes( $this->exists() ); 02204 02205 if ( $this->getNamespace() != NS_FILE ) { 02206 # Remove the upload restriction for non-file titles 02207 $types = array_diff( $types, array( 'upload' ) ); 02208 } 02209 02210 wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) ); 02211 02212 wfDebug( __METHOD__ . ': applicable restrictions to [[' . 02213 $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" ); 02214 02215 return $types; 02216 } 02217 02225 private function getTitleProtection() { 02226 // Can't protect pages in special namespaces 02227 if ( $this->getNamespace() < 0 ) { 02228 return false; 02229 } 02230 02231 // Can't protect pages that exist. 02232 if ( $this->exists() ) { 02233 return false; 02234 } 02235 02236 if ( !isset( $this->mTitleProtection ) ) { 02237 $dbr = wfGetDB( DB_SLAVE ); 02238 $res = $dbr->select( 'protected_titles', '*', 02239 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ), 02240 __METHOD__ ); 02241 02242 // fetchRow returns false if there are no rows. 02243 $this->mTitleProtection = $dbr->fetchRow( $res ); 02244 } 02245 return $this->mTitleProtection; 02246 } 02247 02257 public function updateTitleProtection( $create_perm, $reason, $expiry ) { 02258 wfDeprecated( __METHOD__, '1.19' ); 02259 02260 global $wgUser; 02261 02262 $limit = array( 'create' => $create_perm ); 02263 $expiry = array( 'create' => $expiry ); 02264 02265 $page = WikiPage::factory( $this ); 02266 $status = $page->doUpdateRestrictions( $limit, $expiry, false, $reason, $wgUser ); 02267 02268 return $status->isOK(); 02269 } 02270 02274 public function deleteTitleProtection() { 02275 $dbw = wfGetDB( DB_MASTER ); 02276 02277 $dbw->delete( 02278 'protected_titles', 02279 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ), 02280 __METHOD__ 02281 ); 02282 $this->mTitleProtection = false; 02283 } 02284 02291 public function isSemiProtected( $action = 'edit' ) { 02292 if ( $this->exists() ) { 02293 $restrictions = $this->getRestrictions( $action ); 02294 if ( count( $restrictions ) > 0 ) { 02295 foreach ( $restrictions as $restriction ) { 02296 if ( strtolower( $restriction ) != 'autoconfirmed' ) { 02297 return false; 02298 } 02299 } 02300 } else { 02301 # Not protected 02302 return false; 02303 } 02304 return true; 02305 } else { 02306 # If it doesn't exist, it can't be protected 02307 return false; 02308 } 02309 } 02310 02318 public function isProtected( $action = '' ) { 02319 global $wgRestrictionLevels; 02320 02321 $restrictionTypes = $this->getRestrictionTypes(); 02322 02323 # Special pages have inherent protection 02324 if( $this->isSpecialPage() ) { 02325 return true; 02326 } 02327 02328 # Check regular protection levels 02329 foreach ( $restrictionTypes as $type ) { 02330 if ( $action == $type || $action == '' ) { 02331 $r = $this->getRestrictions( $type ); 02332 foreach ( $wgRestrictionLevels as $level ) { 02333 if ( in_array( $level, $r ) && $level != '' ) { 02334 return true; 02335 } 02336 } 02337 } 02338 } 02339 02340 return false; 02341 } 02342 02350 public function isNamespaceProtected( User $user ) { 02351 global $wgNamespaceProtection; 02352 02353 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) { 02354 foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) { 02355 if ( $right != '' && !$user->isAllowed( $right ) ) { 02356 return true; 02357 } 02358 } 02359 } 02360 return false; 02361 } 02362 02368 public function isCascadeProtected() { 02369 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false ); 02370 return ( $sources > 0 ); 02371 } 02372 02383 public function getCascadeProtectionSources( $getPages = true ) { 02384 global $wgContLang; 02385 $pagerestrictions = array(); 02386 02387 if ( isset( $this->mCascadeSources ) && $getPages ) { 02388 return array( $this->mCascadeSources, $this->mCascadingRestrictions ); 02389 } elseif ( isset( $this->mHasCascadingRestrictions ) && !$getPages ) { 02390 return array( $this->mHasCascadingRestrictions, $pagerestrictions ); 02391 } 02392 02393 wfProfileIn( __METHOD__ ); 02394 02395 $dbr = wfGetDB( DB_SLAVE ); 02396 02397 if ( $this->getNamespace() == NS_FILE ) { 02398 $tables = array( 'imagelinks', 'page_restrictions' ); 02399 $where_clauses = array( 02400 'il_to' => $this->getDBkey(), 02401 'il_from=pr_page', 02402 'pr_cascade' => 1 02403 ); 02404 } else { 02405 $tables = array( 'templatelinks', 'page_restrictions' ); 02406 $where_clauses = array( 02407 'tl_namespace' => $this->getNamespace(), 02408 'tl_title' => $this->getDBkey(), 02409 'tl_from=pr_page', 02410 'pr_cascade' => 1 02411 ); 02412 } 02413 02414 if ( $getPages ) { 02415 $cols = array( 'pr_page', 'page_namespace', 'page_title', 02416 'pr_expiry', 'pr_type', 'pr_level' ); 02417 $where_clauses[] = 'page_id=pr_page'; 02418 $tables[] = 'page'; 02419 } else { 02420 $cols = array( 'pr_expiry' ); 02421 } 02422 02423 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ ); 02424 02425 $sources = $getPages ? array() : false; 02426 $now = wfTimestampNow(); 02427 $purgeExpired = false; 02428 02429 foreach ( $res as $row ) { 02430 $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW ); 02431 if ( $expiry > $now ) { 02432 if ( $getPages ) { 02433 $page_id = $row->pr_page; 02434 $page_ns = $row->page_namespace; 02435 $page_title = $row->page_title; 02436 $sources[$page_id] = Title::makeTitle( $page_ns, $page_title ); 02437 # Add groups needed for each restriction type if its not already there 02438 # Make sure this restriction type still exists 02439 02440 if ( !isset( $pagerestrictions[$row->pr_type] ) ) { 02441 $pagerestrictions[$row->pr_type] = array(); 02442 } 02443 02444 if ( isset( $pagerestrictions[$row->pr_type] ) && 02445 !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] ) ) { 02446 $pagerestrictions[$row->pr_type][] = $row->pr_level; 02447 } 02448 } else { 02449 $sources = true; 02450 } 02451 } else { 02452 // Trigger lazy purge of expired restrictions from the db 02453 $purgeExpired = true; 02454 } 02455 } 02456 if ( $purgeExpired ) { 02457 Title::purgeExpiredRestrictions(); 02458 } 02459 02460 if ( $getPages ) { 02461 $this->mCascadeSources = $sources; 02462 $this->mCascadingRestrictions = $pagerestrictions; 02463 } else { 02464 $this->mHasCascadingRestrictions = $sources; 02465 } 02466 02467 wfProfileOut( __METHOD__ ); 02468 return array( $sources, $pagerestrictions ); 02469 } 02470 02477 public function getRestrictions( $action ) { 02478 if ( !$this->mRestrictionsLoaded ) { 02479 $this->loadRestrictions(); 02480 } 02481 return isset( $this->mRestrictions[$action] ) 02482 ? $this->mRestrictions[$action] 02483 : array(); 02484 } 02485 02493 public function getRestrictionExpiry( $action ) { 02494 if ( !$this->mRestrictionsLoaded ) { 02495 $this->loadRestrictions(); 02496 } 02497 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false; 02498 } 02499 02505 function areRestrictionsCascading() { 02506 if ( !$this->mRestrictionsLoaded ) { 02507 $this->loadRestrictions(); 02508 } 02509 02510 return $this->mCascadeRestriction; 02511 } 02512 02520 private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) { 02521 $rows = array(); 02522 02523 foreach ( $res as $row ) { 02524 $rows[] = $row; 02525 } 02526 02527 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions ); 02528 } 02529 02539 public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) { 02540 global $wgContLang; 02541 $dbr = wfGetDB( DB_SLAVE ); 02542 02543 $restrictionTypes = $this->getRestrictionTypes(); 02544 02545 foreach ( $restrictionTypes as $type ) { 02546 $this->mRestrictions[$type] = array(); 02547 $this->mRestrictionsExpiry[$type] = $wgContLang->formatExpiry( '', TS_MW ); 02548 } 02549 02550 $this->mCascadeRestriction = false; 02551 02552 # Backwards-compatibility: also load the restrictions from the page record (old format). 02553 02554 if ( $oldFashionedRestrictions === null ) { 02555 $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions', 02556 array( 'page_id' => $this->getArticleID() ), __METHOD__ ); 02557 } 02558 02559 if ( $oldFashionedRestrictions != '' ) { 02560 02561 foreach ( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) { 02562 $temp = explode( '=', trim( $restrict ) ); 02563 if ( count( $temp ) == 1 ) { 02564 // old old format should be treated as edit/move restriction 02565 $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) ); 02566 $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) ); 02567 } else { 02568 $restriction = trim( $temp[1] ); 02569 if( $restriction != '' ) { //some old entries are empty 02570 $this->mRestrictions[$temp[0]] = explode( ',', $restriction ); 02571 } 02572 } 02573 } 02574 02575 $this->mOldRestrictions = true; 02576 02577 } 02578 02579 if ( count( $rows ) ) { 02580 # Current system - load second to make them override. 02581 $now = wfTimestampNow(); 02582 $purgeExpired = false; 02583 02584 # Cycle through all the restrictions. 02585 foreach ( $rows as $row ) { 02586 02587 // Don't take care of restrictions types that aren't allowed 02588 if ( !in_array( $row->pr_type, $restrictionTypes ) ) 02589 continue; 02590 02591 // This code should be refactored, now that it's being used more generally, 02592 // But I don't really see any harm in leaving it in Block for now -werdna 02593 $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW ); 02594 02595 // Only apply the restrictions if they haven't expired! 02596 if ( !$expiry || $expiry > $now ) { 02597 $this->mRestrictionsExpiry[$row->pr_type] = $expiry; 02598 $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) ); 02599 02600 $this->mCascadeRestriction |= $row->pr_cascade; 02601 } else { 02602 // Trigger a lazy purge of expired restrictions 02603 $purgeExpired = true; 02604 } 02605 } 02606 02607 if ( $purgeExpired ) { 02608 Title::purgeExpiredRestrictions(); 02609 } 02610 } 02611 02612 $this->mRestrictionsLoaded = true; 02613 } 02614 02621 public function loadRestrictions( $oldFashionedRestrictions = null ) { 02622 global $wgContLang; 02623 if ( !$this->mRestrictionsLoaded ) { 02624 if ( $this->exists() ) { 02625 $dbr = wfGetDB( DB_SLAVE ); 02626 02627 $res = $dbr->select( 02628 'page_restrictions', 02629 '*', 02630 array( 'pr_page' => $this->getArticleID() ), 02631 __METHOD__ 02632 ); 02633 02634 $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions ); 02635 } else { 02636 $title_protection = $this->getTitleProtection(); 02637 02638 if ( $title_protection ) { 02639 $now = wfTimestampNow(); 02640 $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW ); 02641 02642 if ( !$expiry || $expiry > $now ) { 02643 // Apply the restrictions 02644 $this->mRestrictionsExpiry['create'] = $expiry; 02645 $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) ); 02646 } else { // Get rid of the old restrictions 02647 Title::purgeExpiredRestrictions(); 02648 $this->mTitleProtection = false; 02649 } 02650 } else { 02651 $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW ); 02652 } 02653 $this->mRestrictionsLoaded = true; 02654 } 02655 } 02656 } 02657 02662 public function flushRestrictions() { 02663 $this->mRestrictionsLoaded = false; 02664 $this->mTitleProtection = null; 02665 } 02666 02670 static function purgeExpiredRestrictions() { 02671 $dbw = wfGetDB( DB_MASTER ); 02672 $dbw->delete( 02673 'page_restrictions', 02674 array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), 02675 __METHOD__ 02676 ); 02677 02678 $dbw->delete( 02679 'protected_titles', 02680 array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), 02681 __METHOD__ 02682 ); 02683 } 02684 02690 public function hasSubpages() { 02691 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { 02692 # Duh 02693 return false; 02694 } 02695 02696 # We dynamically add a member variable for the purpose of this method 02697 # alone to cache the result. There's no point in having it hanging 02698 # around uninitialized in every Title object; therefore we only add it 02699 # if needed and don't declare it statically. 02700 if ( isset( $this->mHasSubpages ) ) { 02701 return $this->mHasSubpages; 02702 } 02703 02704 $subpages = $this->getSubpages( 1 ); 02705 if ( $subpages instanceof TitleArray ) { 02706 return $this->mHasSubpages = (bool)$subpages->count(); 02707 } 02708 return $this->mHasSubpages = false; 02709 } 02710 02718 public function getSubpages( $limit = -1 ) { 02719 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) { 02720 return array(); 02721 } 02722 02723 $dbr = wfGetDB( DB_SLAVE ); 02724 $conds['page_namespace'] = $this->getNamespace(); 02725 $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() ); 02726 $options = array(); 02727 if ( $limit > -1 ) { 02728 $options['LIMIT'] = $limit; 02729 } 02730 return $this->mSubpages = TitleArray::newFromResult( 02731 $dbr->select( 'page', 02732 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ), 02733 $conds, 02734 __METHOD__, 02735 $options 02736 ) 02737 ); 02738 } 02739 02745 public function isDeleted() { 02746 if ( $this->getNamespace() < 0 ) { 02747 $n = 0; 02748 } else { 02749 $dbr = wfGetDB( DB_SLAVE ); 02750 02751 $n = $dbr->selectField( 'archive', 'COUNT(*)', 02752 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ), 02753 __METHOD__ 02754 ); 02755 if ( $this->getNamespace() == NS_FILE ) { 02756 $n += $dbr->selectField( 'filearchive', 'COUNT(*)', 02757 array( 'fa_name' => $this->getDBkey() ), 02758 __METHOD__ 02759 ); 02760 } 02761 } 02762 return (int)$n; 02763 } 02764 02770 public function isDeletedQuick() { 02771 if ( $this->getNamespace() < 0 ) { 02772 return false; 02773 } 02774 $dbr = wfGetDB( DB_SLAVE ); 02775 $deleted = (bool)$dbr->selectField( 'archive', '1', 02776 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ), 02777 __METHOD__ 02778 ); 02779 if ( !$deleted && $this->getNamespace() == NS_FILE ) { 02780 $deleted = (bool)$dbr->selectField( 'filearchive', '1', 02781 array( 'fa_name' => $this->getDBkey() ), 02782 __METHOD__ 02783 ); 02784 } 02785 return $deleted; 02786 } 02787 02796 public function getArticleID( $flags = 0 ) { 02797 if ( $this->getNamespace() < 0 ) { 02798 return $this->mArticleID = 0; 02799 } 02800 $linkCache = LinkCache::singleton(); 02801 if ( $flags & self::GAID_FOR_UPDATE ) { 02802 $oldUpdate = $linkCache->forUpdate( true ); 02803 $linkCache->clearLink( $this ); 02804 $this->mArticleID = $linkCache->addLinkObj( $this ); 02805 $linkCache->forUpdate( $oldUpdate ); 02806 } else { 02807 if ( -1 == $this->mArticleID ) { 02808 $this->mArticleID = $linkCache->addLinkObj( $this ); 02809 } 02810 } 02811 return $this->mArticleID; 02812 } 02813 02821 public function isRedirect( $flags = 0 ) { 02822 if ( !is_null( $this->mRedirect ) ) { 02823 return $this->mRedirect; 02824 } 02825 # Calling getArticleID() loads the field from cache as needed 02826 if ( !$this->getArticleID( $flags ) ) { 02827 return $this->mRedirect = false; 02828 } 02829 $linkCache = LinkCache::singleton(); 02830 $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' ); 02831 02832 return $this->mRedirect; 02833 } 02834 02842 public function getLength( $flags = 0 ) { 02843 if ( $this->mLength != -1 ) { 02844 return $this->mLength; 02845 } 02846 # Calling getArticleID() loads the field from cache as needed 02847 if ( !$this->getArticleID( $flags ) ) { 02848 return $this->mLength = 0; 02849 } 02850 $linkCache = LinkCache::singleton(); 02851 $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) ); 02852 02853 return $this->mLength; 02854 } 02855 02862 public function getLatestRevID( $flags = 0 ) { 02863 if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) { 02864 return intval( $this->mLatestID ); 02865 } 02866 # Calling getArticleID() loads the field from cache as needed 02867 if ( !$this->getArticleID( $flags ) ) { 02868 return $this->mLatestID = 0; 02869 } 02870 $linkCache = LinkCache::singleton(); 02871 $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) ); 02872 02873 return $this->mLatestID; 02874 } 02875 02886 public function resetArticleID( $newid ) { 02887 $linkCache = LinkCache::singleton(); 02888 $linkCache->clearLink( $this ); 02889 02890 if ( $newid === false ) { 02891 $this->mArticleID = -1; 02892 } else { 02893 $this->mArticleID = intval( $newid ); 02894 } 02895 $this->mRestrictionsLoaded = false; 02896 $this->mRestrictions = array(); 02897 $this->mRedirect = null; 02898 $this->mLength = -1; 02899 $this->mLatestID = false; 02900 $this->mEstimateRevisions = null; 02901 } 02902 02910 public static function capitalize( $text, $ns = NS_MAIN ) { 02911 global $wgContLang; 02912 02913 if ( MWNamespace::isCapitalized( $ns ) ) { 02914 return $wgContLang->ucfirst( $text ); 02915 } else { 02916 return $text; 02917 } 02918 } 02919 02931 private function secureAndSplit() { 02932 global $wgContLang, $wgLocalInterwiki; 02933 02934 # Initialisation 02935 $this->mInterwiki = $this->mFragment = ''; 02936 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN 02937 02938 $dbkey = $this->mDbkeyform; 02939 02940 # Strip Unicode bidi override characters. 02941 # Sometimes they slip into cut-n-pasted page titles, where the 02942 # override chars get included in list displays. 02943 $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey ); 02944 02945 # Clean up whitespace 02946 # Note: use of the /u option on preg_replace here will cause 02947 # input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x, 02948 # conveniently disabling them. 02949 $dbkey = preg_replace( '/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u', '_', $dbkey ); 02950 $dbkey = trim( $dbkey, '_' ); 02951 02952 if ( $dbkey == '' ) { 02953 return false; 02954 } 02955 02956 if ( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) { 02957 # Contained illegal UTF-8 sequences or forbidden Unicode chars. 02958 return false; 02959 } 02960 02961 $this->mDbkeyform = $dbkey; 02962 02963 # Initial colon indicates main namespace rather than specified default 02964 # but should not create invalid {ns,title} pairs such as {0,Project:Foo} 02965 if ( ':' == $dbkey[0] ) { 02966 $this->mNamespace = NS_MAIN; 02967 $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing 02968 $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace 02969 } 02970 02971 # Namespace or interwiki prefix 02972 $firstPass = true; 02973 $prefixRegexp = "/^(.+?)_*:_*(.*)$/S"; 02974 do { 02975 $m = array(); 02976 if ( preg_match( $prefixRegexp, $dbkey, $m ) ) { 02977 $p = $m[1]; 02978 if ( ( $ns = $wgContLang->getNsIndex( $p ) ) !== false ) { 02979 # Ordinary namespace 02980 $dbkey = $m[2]; 02981 $this->mNamespace = $ns; 02982 # For Talk:X pages, check if X has a "namespace" prefix 02983 if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) { 02984 if ( $wgContLang->getNsIndex( $x[1] ) ) { 02985 # Disallow Talk:File:x type titles... 02986 return false; 02987 } elseif ( Interwiki::isValidInterwiki( $x[1] ) ) { 02988 # Disallow Talk:Interwiki:x type titles... 02989 return false; 02990 } 02991 } 02992 } elseif ( Interwiki::isValidInterwiki( $p ) ) { 02993 if ( !$firstPass ) { 02994 # Can't make a local interwiki link to an interwiki link. 02995 # That's just crazy! 02996 return false; 02997 } 02998 02999 # Interwiki link 03000 $dbkey = $m[2]; 03001 $this->mInterwiki = $wgContLang->lc( $p ); 03002 03003 # Redundant interwiki prefix to the local wiki 03004 if ( $wgLocalInterwiki !== false 03005 && 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) 03006 { 03007 if ( $dbkey == '' ) { 03008 # Can't have an empty self-link 03009 return false; 03010 } 03011 $this->mInterwiki = ''; 03012 $firstPass = false; 03013 # Do another namespace split... 03014 continue; 03015 } 03016 03017 # If there's an initial colon after the interwiki, that also 03018 # resets the default namespace 03019 if ( $dbkey !== '' && $dbkey[0] == ':' ) { 03020 $this->mNamespace = NS_MAIN; 03021 $dbkey = substr( $dbkey, 1 ); 03022 } 03023 } 03024 # If there's no recognized interwiki or namespace, 03025 # then let the colon expression be part of the title. 03026 } 03027 break; 03028 } while ( true ); 03029 03030 # We already know that some pages won't be in the database! 03031 if ( $this->mInterwiki != '' || NS_SPECIAL == $this->mNamespace ) { 03032 $this->mArticleID = 0; 03033 } 03034 $fragment = strstr( $dbkey, '#' ); 03035 if ( false !== $fragment ) { 03036 $this->setFragment( $fragment ); 03037 $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) ); 03038 # remove whitespace again: prevents "Foo_bar_#" 03039 # becoming "Foo_bar_" 03040 $dbkey = preg_replace( '/_*$/', '', $dbkey ); 03041 } 03042 03043 # Reject illegal characters. 03044 $rxTc = self::getTitleInvalidRegex(); 03045 if ( preg_match( $rxTc, $dbkey ) ) { 03046 return false; 03047 } 03048 03049 # Pages with "/./" or "/../" appearing in the URLs will often be un- 03050 # reachable due to the way web browsers deal with 'relative' URLs. 03051 # Also, they conflict with subpage syntax. Forbid them explicitly. 03052 if ( strpos( $dbkey, '.' ) !== false && 03053 ( $dbkey === '.' || $dbkey === '..' || 03054 strpos( $dbkey, './' ) === 0 || 03055 strpos( $dbkey, '../' ) === 0 || 03056 strpos( $dbkey, '/./' ) !== false || 03057 strpos( $dbkey, '/../' ) !== false || 03058 substr( $dbkey, -2 ) == '/.' || 03059 substr( $dbkey, -3 ) == '/..' ) ) 03060 { 03061 return false; 03062 } 03063 03064 # Magic tilde sequences? Nu-uh! 03065 if ( strpos( $dbkey, '~~~' ) !== false ) { 03066 return false; 03067 } 03068 03069 # Limit the size of titles to 255 bytes. This is typically the size of the 03070 # underlying database field. We make an exception for special pages, which 03071 # don't need to be stored in the database, and may edge over 255 bytes due 03072 # to subpage syntax for long titles, e.g. [[Special:Block/Long name]] 03073 if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) || 03074 strlen( $dbkey ) > 512 ) 03075 { 03076 return false; 03077 } 03078 03079 # Normally, all wiki links are forced to have an initial capital letter so [[foo]] 03080 # and [[Foo]] point to the same place. Don't force it for interwikis, since the 03081 # other site might be case-sensitive. 03082 $this->mUserCaseDBKey = $dbkey; 03083 if ( $this->mInterwiki == '' ) { 03084 $dbkey = self::capitalize( $dbkey, $this->mNamespace ); 03085 } 03086 03087 # Can't make a link to a namespace alone... "empty" local links can only be 03088 # self-links with a fragment identifier. 03089 if ( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) { 03090 return false; 03091 } 03092 03093 // Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles. 03094 // IP names are not allowed for accounts, and can only be referring to 03095 // edits from the IP. Given '::' abbreviations and caps/lowercaps, 03096 // there are numerous ways to present the same IP. Having sp:contribs scan 03097 // them all is silly and having some show the edits and others not is 03098 // inconsistent. Same for talk/userpages. Keep them normalized instead. 03099 $dbkey = ( $this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK ) 03100 ? IP::sanitizeIP( $dbkey ) 03101 : $dbkey; 03102 03103 // Any remaining initial :s are illegal. 03104 if ( $dbkey !== '' && ':' == $dbkey[0] ) { 03105 return false; 03106 } 03107 03108 # Fill fields 03109 $this->mDbkeyform = $dbkey; 03110 $this->mUrlform = wfUrlencode( $dbkey ); 03111 03112 $this->mTextform = str_replace( '_', ' ', $dbkey ); 03113 03114 return true; 03115 } 03116 03129 public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) { 03130 if ( count( $options ) > 0 ) { 03131 $db = wfGetDB( DB_MASTER ); 03132 } else { 03133 $db = wfGetDB( DB_SLAVE ); 03134 } 03135 03136 $res = $db->select( 03137 array( 'page', $table ), 03138 array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ), 03139 array( 03140 "{$prefix}_from=page_id", 03141 "{$prefix}_namespace" => $this->getNamespace(), 03142 "{$prefix}_title" => $this->getDBkey() ), 03143 __METHOD__, 03144 $options 03145 ); 03146 03147 $retVal = array(); 03148 if ( $res->numRows() ) { 03149 $linkCache = LinkCache::singleton(); 03150 foreach ( $res as $row ) { 03151 $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ); 03152 if ( $titleObj ) { 03153 $linkCache->addGoodLinkObjFromRow( $titleObj, $row ); 03154 $retVal[] = $titleObj; 03155 } 03156 } 03157 } 03158 return $retVal; 03159 } 03160 03171 public function getTemplateLinksTo( $options = array() ) { 03172 return $this->getLinksTo( $options, 'templatelinks', 'tl' ); 03173 } 03174 03187 public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) { 03188 $id = $this->getArticleID(); 03189 03190 # If the page doesn't exist; there can't be any link from this page 03191 if ( !$id ) { 03192 return array(); 03193 } 03194 03195 if ( count( $options ) > 0 ) { 03196 $db = wfGetDB( DB_MASTER ); 03197 } else { 03198 $db = wfGetDB( DB_SLAVE ); 03199 } 03200 03201 $namespaceFiled = "{$prefix}_namespace"; 03202 $titleField = "{$prefix}_title"; 03203 03204 $res = $db->select( 03205 array( $table, 'page' ), 03206 array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ), 03207 array( "{$prefix}_from" => $id ), 03208 __METHOD__, 03209 $options, 03210 array( 'page' => array( 'LEFT JOIN', array( "page_namespace=$namespaceFiled", "page_title=$titleField" ) ) ) 03211 ); 03212 03213 $retVal = array(); 03214 if ( $res->numRows() ) { 03215 $linkCache = LinkCache::singleton(); 03216 foreach ( $res as $row ) { 03217 $titleObj = Title::makeTitle( $row->$namespaceFiled, $row->$titleField ); 03218 if ( $titleObj ) { 03219 if ( $row->page_id ) { 03220 $linkCache->addGoodLinkObjFromRow( $titleObj, $row ); 03221 } else { 03222 $linkCache->addBadLinkObj( $titleObj ); 03223 } 03224 $retVal[] = $titleObj; 03225 } 03226 } 03227 } 03228 return $retVal; 03229 } 03230 03241 public function getTemplateLinksFrom( $options = array() ) { 03242 return $this->getLinksFrom( $options, 'templatelinks', 'tl' ); 03243 } 03244 03251 public function getBrokenLinksFrom() { 03252 if ( $this->getArticleID() == 0 ) { 03253 # All links from article ID 0 are false positives 03254 return array(); 03255 } 03256 03257 $dbr = wfGetDB( DB_SLAVE ); 03258 $res = $dbr->select( 03259 array( 'page', 'pagelinks' ), 03260 array( 'pl_namespace', 'pl_title' ), 03261 array( 03262 'pl_from' => $this->getArticleID(), 03263 'page_namespace IS NULL' 03264 ), 03265 __METHOD__, array(), 03266 array( 03267 'page' => array( 03268 'LEFT JOIN', 03269 array( 'pl_namespace=page_namespace', 'pl_title=page_title' ) 03270 ) 03271 ) 03272 ); 03273 03274 $retVal = array(); 03275 foreach ( $res as $row ) { 03276 $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title ); 03277 } 03278 return $retVal; 03279 } 03280 03281 03288 public function getSquidURLs() { 03289 $urls = array( 03290 $this->getInternalURL(), 03291 $this->getInternalURL( 'action=history' ) 03292 ); 03293 03294 $pageLang = $this->getPageLanguage(); 03295 if ( $pageLang->hasVariants() ) { 03296 $variants = $pageLang->getVariants(); 03297 foreach ( $variants as $vCode ) { 03298 $urls[] = $this->getInternalURL( '', $vCode ); 03299 } 03300 } 03301 03302 return $urls; 03303 } 03304 03308 public function purgeSquid() { 03309 global $wgUseSquid; 03310 if ( $wgUseSquid ) { 03311 $urls = $this->getSquidURLs(); 03312 $u = new SquidUpdate( $urls ); 03313 $u->doUpdate(); 03314 } 03315 } 03316 03323 public function moveNoAuth( &$nt ) { 03324 return $this->moveTo( $nt, false ); 03325 } 03326 03337 public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) { 03338 global $wgUser; 03339 03340 $errors = array(); 03341 if ( !$nt ) { 03342 // Normally we'd add this to $errors, but we'll get 03343 // lots of syntax errors if $nt is not an object 03344 return array( array( 'badtitletext' ) ); 03345 } 03346 if ( $this->equals( $nt ) ) { 03347 $errors[] = array( 'selfmove' ); 03348 } 03349 if ( !$this->isMovable() ) { 03350 $errors[] = array( 'immobile-source-namespace', $this->getNsText() ); 03351 } 03352 if ( $nt->getInterwiki() != '' ) { 03353 $errors[] = array( 'immobile-target-namespace-iw' ); 03354 } 03355 if ( !$nt->isMovable() ) { 03356 $errors[] = array( 'immobile-target-namespace', $nt->getNsText() ); 03357 } 03358 03359 $oldid = $this->getArticleID(); 03360 $newid = $nt->getArticleID(); 03361 03362 if ( strlen( $nt->getDBkey() ) < 1 ) { 03363 $errors[] = array( 'articleexists' ); 03364 } 03365 if ( ( $this->getDBkey() == '' ) || 03366 ( !$oldid ) || 03367 ( $nt->getDBkey() == '' ) ) { 03368 $errors[] = array( 'badarticleerror' ); 03369 } 03370 03371 // Image-specific checks 03372 if ( $this->getNamespace() == NS_FILE ) { 03373 $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) ); 03374 } 03375 03376 if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) { 03377 $errors[] = array( 'nonfile-cannot-move-to-file' ); 03378 } 03379 03380 if ( $auth ) { 03381 $errors = wfMergeErrorArrays( $errors, 03382 $this->getUserPermissionsErrors( 'move', $wgUser ), 03383 $this->getUserPermissionsErrors( 'edit', $wgUser ), 03384 $nt->getUserPermissionsErrors( 'move-target', $wgUser ), 03385 $nt->getUserPermissionsErrors( 'edit', $wgUser ) ); 03386 } 03387 03388 $match = EditPage::matchSummarySpamRegex( $reason ); 03389 if ( $match !== false ) { 03390 // This is kind of lame, won't display nice 03391 $errors[] = array( 'spamprotectiontext' ); 03392 } 03393 03394 $err = null; 03395 if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) { 03396 $errors[] = array( 'hookaborted', $err ); 03397 } 03398 03399 # The move is allowed only if (1) the target doesn't exist, or 03400 # (2) the target is a redirect to the source, and has no history 03401 # (so we can undo bad moves right after they're done). 03402 03403 if ( 0 != $newid ) { # Target exists; check for validity 03404 if ( !$this->isValidMoveTarget( $nt ) ) { 03405 $errors[] = array( 'articleexists' ); 03406 } 03407 } else { 03408 $tp = $nt->getTitleProtection(); 03409 $right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm']; 03410 if ( $tp and !$wgUser->isAllowed( $right ) ) { 03411 $errors[] = array( 'cantmove-titleprotected' ); 03412 } 03413 } 03414 if ( empty( $errors ) ) { 03415 return true; 03416 } 03417 return $errors; 03418 } 03419 03425 protected function validateFileMoveOperation( $nt ) { 03426 global $wgUser; 03427 03428 $errors = array(); 03429 03430 // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below 03431 03432 $file = wfLocalFile( $this ); 03433 if ( $file->exists() ) { 03434 if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) { 03435 $errors[] = array( 'imageinvalidfilename' ); 03436 } 03437 if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) { 03438 $errors[] = array( 'imagetypemismatch' ); 03439 } 03440 } 03441 03442 if ( $nt->getNamespace() != NS_FILE ) { 03443 $errors[] = array( 'imagenocrossnamespace' ); 03444 // From here we want to do checks on a file object, so if we can't 03445 // create one, we must return. 03446 return $errors; 03447 } 03448 03449 // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here 03450 03451 $destFile = wfLocalFile( $nt ); 03452 if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) { 03453 $errors[] = array( 'file-exists-sharedrepo' ); 03454 } 03455 03456 return $errors; 03457 } 03458 03470 public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) { 03471 global $wgUser; 03472 $err = $this->isValidMoveOperation( $nt, $auth, $reason ); 03473 if ( is_array( $err ) ) { 03474 // Auto-block user's IP if the account was "hard" blocked 03475 $wgUser->spreadAnyEditBlock(); 03476 return $err; 03477 } 03478 // Check suppressredirect permission 03479 if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) { 03480 $createRedirect = true; 03481 } 03482 03483 // If it is a file, move it first. 03484 // It is done before all other moving stuff is done because it's hard to revert. 03485 $dbw = wfGetDB( DB_MASTER ); 03486 if ( $this->getNamespace() == NS_FILE ) { 03487 $file = wfLocalFile( $this ); 03488 if ( $file->exists() ) { 03489 $status = $file->move( $nt ); 03490 if ( !$status->isOk() ) { 03491 return $status->getErrorsArray(); 03492 } 03493 } 03494 // Clear RepoGroup process cache 03495 RepoGroup::singleton()->clearCache( $this ); 03496 RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache 03497 } 03498 03499 $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own. 03500 $pageid = $this->getArticleID( self::GAID_FOR_UPDATE ); 03501 $protected = $this->isProtected(); 03502 03503 // Do the actual move 03504 $this->moveToInternal( $nt, $reason, $createRedirect ); 03505 03506 // Refresh the sortkey for this row. Be careful to avoid resetting 03507 // cl_timestamp, which may disturb time-based lists on some sites. 03508 $prefixes = $dbw->select( 03509 'categorylinks', 03510 array( 'cl_sortkey_prefix', 'cl_to' ), 03511 array( 'cl_from' => $pageid ), 03512 __METHOD__ 03513 ); 03514 foreach ( $prefixes as $prefixRow ) { 03515 $prefix = $prefixRow->cl_sortkey_prefix; 03516 $catTo = $prefixRow->cl_to; 03517 $dbw->update( 'categorylinks', 03518 array( 03519 'cl_sortkey' => Collation::singleton()->getSortKey( 03520 $nt->getCategorySortkey( $prefix ) ), 03521 'cl_timestamp=cl_timestamp' ), 03522 array( 03523 'cl_from' => $pageid, 03524 'cl_to' => $catTo ), 03525 __METHOD__ 03526 ); 03527 } 03528 03529 $redirid = $this->getArticleID(); 03530 03531 if ( $protected ) { 03532 # Protect the redirect title as the title used to be... 03533 $dbw->insertSelect( 'page_restrictions', 'page_restrictions', 03534 array( 03535 'pr_page' => $redirid, 03536 'pr_type' => 'pr_type', 03537 'pr_level' => 'pr_level', 03538 'pr_cascade' => 'pr_cascade', 03539 'pr_user' => 'pr_user', 03540 'pr_expiry' => 'pr_expiry' 03541 ), 03542 array( 'pr_page' => $pageid ), 03543 __METHOD__, 03544 array( 'IGNORE' ) 03545 ); 03546 # Update the protection log 03547 $log = new LogPage( 'protect' ); 03548 $comment = wfMessage( 03549 'prot_1movedto2', 03550 $this->getPrefixedText(), 03551 $nt->getPrefixedText() 03552 )->inContentLanguage()->text(); 03553 if ( $reason ) { 03554 $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason; 03555 } 03556 // @todo FIXME: $params? 03557 $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) ); 03558 } 03559 03560 # Update watchlists 03561 $oldnamespace = $this->getNamespace() & ~1; 03562 $newnamespace = $nt->getNamespace() & ~1; 03563 $oldtitle = $this->getDBkey(); 03564 $newtitle = $nt->getDBkey(); 03565 03566 if ( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) { 03567 WatchedItem::duplicateEntries( $this, $nt ); 03568 } 03569 03570 $dbw->commit( __METHOD__ ); 03571 03572 wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) ); 03573 return true; 03574 } 03575 03586 private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) { 03587 global $wgUser, $wgContLang; 03588 03589 if ( $nt->exists() ) { 03590 $moveOverRedirect = true; 03591 $logType = 'move_redir'; 03592 } else { 03593 $moveOverRedirect = false; 03594 $logType = 'move'; 03595 } 03596 03597 $redirectSuppressed = !$createRedirect; 03598 03599 $logEntry = new ManualLogEntry( 'move', $logType ); 03600 $logEntry->setPerformer( $wgUser ); 03601 $logEntry->setTarget( $this ); 03602 $logEntry->setComment( $reason ); 03603 $logEntry->setParameters( array( 03604 '4::target' => $nt->getPrefixedText(), 03605 '5::noredir' => $redirectSuppressed ? '1': '0', 03606 ) ); 03607 03608 $formatter = LogFormatter::newFromEntry( $logEntry ); 03609 $formatter->setContext( RequestContext::newExtraneousContext( $this ) ); 03610 $comment = $formatter->getPlainActionText(); 03611 if ( $reason ) { 03612 $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason; 03613 } 03614 # Truncate for whole multibyte characters. 03615 $comment = $wgContLang->truncate( $comment, 255 ); 03616 03617 $oldid = $this->getArticleID(); 03618 03619 $dbw = wfGetDB( DB_MASTER ); 03620 03621 $newpage = WikiPage::factory( $nt ); 03622 03623 if ( $moveOverRedirect ) { 03624 $newid = $nt->getArticleID(); 03625 03626 # Delete the old redirect. We don't save it to history since 03627 # by definition if we've got here it's rather uninteresting. 03628 # We have to remove it so that the next step doesn't trigger 03629 # a conflict on the unique namespace+title index... 03630 $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ ); 03631 03632 $newpage->doDeleteUpdates( $newid ); 03633 } 03634 03635 # Save a null revision in the page's history notifying of the move 03636 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true ); 03637 if ( !is_object( $nullRevision ) ) { 03638 throw new MWException( 'No valid null revision produced in ' . __METHOD__ ); 03639 } 03640 $nullRevId = $nullRevision->insertOn( $dbw ); 03641 03642 # Change the name of the target page: 03643 $dbw->update( 'page', 03644 /* SET */ array( 03645 'page_namespace' => $nt->getNamespace(), 03646 'page_title' => $nt->getDBkey(), 03647 ), 03648 /* WHERE */ array( 'page_id' => $oldid ), 03649 __METHOD__ 03650 ); 03651 03652 $this->resetArticleID( 0 ); 03653 $nt->resetArticleID( $oldid ); 03654 03655 $newpage->updateRevisionOn( $dbw, $nullRevision ); 03656 03657 wfRunHooks( 'NewRevisionFromEditComplete', 03658 array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) ); 03659 03660 $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) ); 03661 03662 if ( !$moveOverRedirect ) { 03663 WikiPage::onArticleCreate( $nt ); 03664 } 03665 03666 # Recreate the redirect, this time in the other direction. 03667 if ( $redirectSuppressed ) { 03668 WikiPage::onArticleDelete( $this ); 03669 } else { 03670 $mwRedir = MagicWord::get( 'redirect' ); 03671 $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n"; 03672 $redirectArticle = WikiPage::factory( $this ); 03673 $newid = $redirectArticle->insertOn( $dbw ); 03674 if ( $newid ) { // sanity 03675 $redirectRevision = new Revision( array( 03676 'page' => $newid, 03677 'comment' => $comment, 03678 'text' => $redirectText ) ); 03679 $redirectRevision->insertOn( $dbw ); 03680 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 ); 03681 03682 wfRunHooks( 'NewRevisionFromEditComplete', 03683 array( $redirectArticle, $redirectRevision, false, $wgUser ) ); 03684 03685 $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) ); 03686 } 03687 } 03688 03689 # Log the move 03690 $logid = $logEntry->insert(); 03691 $logEntry->publish( $logid ); 03692 } 03693 03706 public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) { 03707 global $wgMaximumMovedPages; 03708 // Check permissions 03709 if ( !$this->userCan( 'move-subpages' ) ) { 03710 return array( 'cant-move-subpages' ); 03711 } 03712 // Do the source and target namespaces support subpages? 03713 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) { 03714 return array( 'namespace-nosubpages', 03715 MWNamespace::getCanonicalName( $this->getNamespace() ) ); 03716 } 03717 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) { 03718 return array( 'namespace-nosubpages', 03719 MWNamespace::getCanonicalName( $nt->getNamespace() ) ); 03720 } 03721 03722 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 ); 03723 $retval = array(); 03724 $count = 0; 03725 foreach ( $subpages as $oldSubpage ) { 03726 $count++; 03727 if ( $count > $wgMaximumMovedPages ) { 03728 $retval[$oldSubpage->getPrefixedTitle()] = 03729 array( 'movepage-max-pages', 03730 $wgMaximumMovedPages ); 03731 break; 03732 } 03733 03734 // We don't know whether this function was called before 03735 // or after moving the root page, so check both 03736 // $this and $nt 03737 if ( $oldSubpage->getArticleID() == $this->getArticleID() || 03738 $oldSubpage->getArticleID() == $nt->getArticleID() ) 03739 { 03740 // When moving a page to a subpage of itself, 03741 // don't move it twice 03742 continue; 03743 } 03744 $newPageName = preg_replace( 03745 '#^' . preg_quote( $this->getDBkey(), '#' ) . '#', 03746 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234 03747 $oldSubpage->getDBkey() ); 03748 if ( $oldSubpage->isTalkPage() ) { 03749 $newNs = $nt->getTalkPage()->getNamespace(); 03750 } else { 03751 $newNs = $nt->getSubjectPage()->getNamespace(); 03752 } 03753 # Bug 14385: we need makeTitleSafe because the new page names may 03754 # be longer than 255 characters. 03755 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName ); 03756 03757 $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect ); 03758 if ( $success === true ) { 03759 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText(); 03760 } else { 03761 $retval[$oldSubpage->getPrefixedText()] = $success; 03762 } 03763 } 03764 return $retval; 03765 } 03766 03773 public function isSingleRevRedirect() { 03774 $dbw = wfGetDB( DB_MASTER ); 03775 # Is it a redirect? 03776 $row = $dbw->selectRow( 'page', 03777 array( 'page_is_redirect', 'page_latest', 'page_id' ), 03778 $this->pageCond(), 03779 __METHOD__, 03780 array( 'FOR UPDATE' ) 03781 ); 03782 # Cache some fields we may want 03783 $this->mArticleID = $row ? intval( $row->page_id ) : 0; 03784 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false; 03785 $this->mLatestID = $row ? intval( $row->page_latest ) : false; 03786 if ( !$this->mRedirect ) { 03787 return false; 03788 } 03789 # Does the article have a history? 03790 $row = $dbw->selectField( array( 'page', 'revision' ), 03791 'rev_id', 03792 array( 'page_namespace' => $this->getNamespace(), 03793 'page_title' => $this->getDBkey(), 03794 'page_id=rev_page', 03795 'page_latest != rev_id' 03796 ), 03797 __METHOD__, 03798 array( 'FOR UPDATE' ) 03799 ); 03800 # Return true if there was no history 03801 return ( $row === false ); 03802 } 03803 03811 public function isValidMoveTarget( $nt ) { 03812 # Is it an existing file? 03813 if ( $nt->getNamespace() == NS_FILE ) { 03814 $file = wfLocalFile( $nt ); 03815 if ( $file->exists() ) { 03816 wfDebug( __METHOD__ . ": file exists\n" ); 03817 return false; 03818 } 03819 } 03820 # Is it a redirect with no history? 03821 if ( !$nt->isSingleRevRedirect() ) { 03822 wfDebug( __METHOD__ . ": not a one-rev redirect\n" ); 03823 return false; 03824 } 03825 # Get the article text 03826 $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST ); 03827 if( !is_object( $rev ) ){ 03828 return false; 03829 } 03830 $text = $rev->getText(); 03831 # Does the redirect point to the source? 03832 # Or is it a broken self-redirect, usually caused by namespace collisions? 03833 $m = array(); 03834 if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) { 03835 $redirTitle = Title::newFromText( $m[1] ); 03836 if ( !is_object( $redirTitle ) || 03837 ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() && 03838 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) { 03839 wfDebug( __METHOD__ . ": redirect points to other page\n" ); 03840 return false; 03841 } 03842 } else { 03843 # Fail safe 03844 wfDebug( __METHOD__ . ": failsafe\n" ); 03845 return false; 03846 } 03847 return true; 03848 } 03849 03857 public function getParentCategories() { 03858 global $wgContLang; 03859 03860 $data = array(); 03861 03862 $titleKey = $this->getArticleID(); 03863 03864 if ( $titleKey === 0 ) { 03865 return $data; 03866 } 03867 03868 $dbr = wfGetDB( DB_SLAVE ); 03869 03870 $res = $dbr->select( 'categorylinks', '*', 03871 array( 03872 'cl_from' => $titleKey, 03873 ), 03874 __METHOD__, 03875 array() 03876 ); 03877 03878 if ( $dbr->numRows( $res ) > 0 ) { 03879 foreach ( $res as $row ) { 03880 // $data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$row->cl_to); 03881 $data[$wgContLang->getNSText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText(); 03882 } 03883 } 03884 return $data; 03885 } 03886 03893 public function getParentCategoryTree( $children = array() ) { 03894 $stack = array(); 03895 $parents = $this->getParentCategories(); 03896 03897 if ( $parents ) { 03898 foreach ( $parents as $parent => $current ) { 03899 if ( array_key_exists( $parent, $children ) ) { 03900 # Circular reference 03901 $stack[$parent] = array(); 03902 } else { 03903 $nt = Title::newFromText( $parent ); 03904 if ( $nt ) { 03905 $stack[$parent] = $nt->getParentCategoryTree( $children + array( $parent => 1 ) ); 03906 } 03907 } 03908 } 03909 } 03910 03911 return $stack; 03912 } 03913 03920 public function pageCond() { 03921 if ( $this->mArticleID > 0 ) { 03922 // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs 03923 return array( 'page_id' => $this->mArticleID ); 03924 } else { 03925 return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ); 03926 } 03927 } 03928 03936 public function getPreviousRevisionID( $revId, $flags = 0 ) { 03937 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE ); 03938 $revId = $db->selectField( 'revision', 'rev_id', 03939 array( 03940 'rev_page' => $this->getArticleID( $flags ), 03941 'rev_id < ' . intval( $revId ) 03942 ), 03943 __METHOD__, 03944 array( 'ORDER BY' => 'rev_id DESC' ) 03945 ); 03946 03947 if ( $revId === false ) { 03948 return false; 03949 } else { 03950 return intval( $revId ); 03951 } 03952 } 03953 03961 public function getNextRevisionID( $revId, $flags = 0 ) { 03962 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE ); 03963 $revId = $db->selectField( 'revision', 'rev_id', 03964 array( 03965 'rev_page' => $this->getArticleID( $flags ), 03966 'rev_id > ' . intval( $revId ) 03967 ), 03968 __METHOD__, 03969 array( 'ORDER BY' => 'rev_id' ) 03970 ); 03971 03972 if ( $revId === false ) { 03973 return false; 03974 } else { 03975 return intval( $revId ); 03976 } 03977 } 03978 03985 public function getFirstRevision( $flags = 0 ) { 03986 $pageId = $this->getArticleID( $flags ); 03987 if ( $pageId ) { 03988 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE ); 03989 $row = $db->selectRow( 'revision', Revision::selectFields(), 03990 array( 'rev_page' => $pageId ), 03991 __METHOD__, 03992 array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ) 03993 ); 03994 if ( $row ) { 03995 return new Revision( $row ); 03996 } 03997 } 03998 return null; 03999 } 04000 04007 public function getEarliestRevTime( $flags = 0 ) { 04008 $rev = $this->getFirstRevision( $flags ); 04009 return $rev ? $rev->getTimestamp() : null; 04010 } 04011 04017 public function isNewPage() { 04018 $dbr = wfGetDB( DB_SLAVE ); 04019 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ ); 04020 } 04021 04027 public function isBigDeletion() { 04028 global $wgDeleteRevisionsLimit; 04029 04030 if ( !$wgDeleteRevisionsLimit ) { 04031 return false; 04032 } 04033 04034 $revCount = $this->estimateRevisionCount(); 04035 return $revCount > $wgDeleteRevisionsLimit; 04036 } 04037 04043 public function estimateRevisionCount() { 04044 if ( !$this->exists() ) { 04045 return 0; 04046 } 04047 04048 if ( $this->mEstimateRevisions === null ) { 04049 $dbr = wfGetDB( DB_SLAVE ); 04050 $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*', 04051 array( 'rev_page' => $this->getArticleID() ), __METHOD__ ); 04052 } 04053 04054 return $this->mEstimateRevisions; 04055 } 04056 04065 public function countRevisionsBetween( $old, $new ) { 04066 if ( !( $old instanceof Revision ) ) { 04067 $old = Revision::newFromTitle( $this, (int)$old ); 04068 } 04069 if ( !( $new instanceof Revision ) ) { 04070 $new = Revision::newFromTitle( $this, (int)$new ); 04071 } 04072 if ( !$old || !$new ) { 04073 return 0; // nothing to compare 04074 } 04075 $dbr = wfGetDB( DB_SLAVE ); 04076 return (int)$dbr->selectField( 'revision', 'count(*)', 04077 array( 04078 'rev_page' => $this->getArticleID(), 04079 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ), 04080 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) ) 04081 ), 04082 __METHOD__ 04083 ); 04084 } 04085 04100 public function countAuthorsBetween( $old, $new, $limit, $options = array() ) { 04101 if ( !( $old instanceof Revision ) ) { 04102 $old = Revision::newFromTitle( $this, (int)$old ); 04103 } 04104 if ( !( $new instanceof Revision ) ) { 04105 $new = Revision::newFromTitle( $this, (int)$new ); 04106 } 04107 // XXX: what if Revision objects are passed in, but they don't refer to this title? 04108 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID() 04109 // in the sanity check below? 04110 if ( !$old || !$new ) { 04111 return 0; // nothing to compare 04112 } 04113 $old_cmp = '>'; 04114 $new_cmp = '<'; 04115 $options = (array) $options; 04116 if ( in_array( 'include_old', $options ) ) { 04117 $old_cmp = '>='; 04118 } 04119 if ( in_array( 'include_new', $options ) ) { 04120 $new_cmp = '<='; 04121 } 04122 if ( in_array( 'include_both', $options ) ) { 04123 $old_cmp = '>='; 04124 $new_cmp = '<='; 04125 } 04126 // No DB query needed if $old and $new are the same or successive revisions: 04127 if ( $old->getId() === $new->getId() ) { 04128 return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1; 04129 } else if ( $old->getId() === $new->getParentId() ) { 04130 if ( $old_cmp === '>' || $new_cmp === '<' ) { 04131 return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1; 04132 } 04133 return ( $old->getRawUserText() === $new->getRawUserText() ) ? 1 : 2; 04134 } 04135 $dbr = wfGetDB( DB_SLAVE ); 04136 $res = $dbr->select( 'revision', 'DISTINCT rev_user_text', 04137 array( 04138 'rev_page' => $this->getArticleID(), 04139 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ), 04140 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) ) 04141 ), __METHOD__, 04142 array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated 04143 ); 04144 return (int)$dbr->numRows( $res ); 04145 } 04146 04153 public function equals( Title $title ) { 04154 // Note: === is necessary for proper matching of number-like titles. 04155 return $this->getInterwiki() === $title->getInterwiki() 04156 && $this->getNamespace() == $title->getNamespace() 04157 && $this->getDBkey() === $title->getDBkey(); 04158 } 04159 04166 public function isSubpageOf( Title $title ) { 04167 return $this->getInterwiki() === $title->getInterwiki() 04168 && $this->getNamespace() == $title->getNamespace() 04169 && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0; 04170 } 04171 04181 public function exists() { 04182 return $this->getArticleID() != 0; 04183 } 04184 04201 public function isAlwaysKnown() { 04202 $isKnown = null; 04203 04214 wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) ); 04215 04216 if ( !is_null( $isKnown ) ) { 04217 return $isKnown; 04218 } 04219 04220 if ( $this->mInterwiki != '' ) { 04221 return true; // any interwiki link might be viewable, for all we know 04222 } 04223 04224 switch( $this->mNamespace ) { 04225 case NS_MEDIA: 04226 case NS_FILE: 04227 // file exists, possibly in a foreign repo 04228 return (bool)wfFindFile( $this ); 04229 case NS_SPECIAL: 04230 // valid special page 04231 return SpecialPageFactory::exists( $this->getDBkey() ); 04232 case NS_MAIN: 04233 // selflink, possibly with fragment 04234 return $this->mDbkeyform == ''; 04235 case NS_MEDIAWIKI: 04236 // known system message 04237 return $this->hasSourceText() !== false; 04238 default: 04239 return false; 04240 } 04241 } 04242 04254 public function isKnown() { 04255 return $this->isAlwaysKnown() || $this->exists(); 04256 } 04257 04263 public function hasSourceText() { 04264 if ( $this->exists() ) { 04265 return true; 04266 } 04267 04268 if ( $this->mNamespace == NS_MEDIAWIKI ) { 04269 // If the page doesn't exist but is a known system message, default 04270 // message content will be displayed, same for language subpages- 04271 // Use always content language to avoid loading hundreds of languages 04272 // to get the link color. 04273 global $wgContLang; 04274 list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) ); 04275 $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false ); 04276 return $message->exists(); 04277 } 04278 04279 return false; 04280 } 04281 04287 public function getDefaultMessageText() { 04288 global $wgContLang; 04289 04290 if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case 04291 return false; 04292 } 04293 04294 list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) ); 04295 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false ); 04296 04297 if ( $message->exists() ) { 04298 return $message->plain(); 04299 } else { 04300 return false; 04301 } 04302 } 04303 04309 public function invalidateCache() { 04310 if ( wfReadOnly() ) { 04311 return false; 04312 } 04313 $dbw = wfGetDB( DB_MASTER ); 04314 $success = $dbw->update( 04315 'page', 04316 array( 'page_touched' => $dbw->timestamp() ), 04317 $this->pageCond(), 04318 __METHOD__ 04319 ); 04320 HTMLFileCache::clearFileCache( $this ); 04321 return $success; 04322 } 04323 04329 public function touchLinks() { 04330 $u = new HTMLCacheUpdate( $this, 'pagelinks' ); 04331 $u->doUpdate(); 04332 04333 if ( $this->getNamespace() == NS_CATEGORY ) { 04334 $u = new HTMLCacheUpdate( $this, 'categorylinks' ); 04335 $u->doUpdate(); 04336 } 04337 } 04338 04345 public function getTouched( $db = null ) { 04346 $db = isset( $db ) ? $db : wfGetDB( DB_SLAVE ); 04347 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ ); 04348 return $touched; 04349 } 04350 04357 public function getNotificationTimestamp( $user = null ) { 04358 global $wgUser, $wgShowUpdatedMarker; 04359 // Assume current user if none given 04360 if ( !$user ) { 04361 $user = $wgUser; 04362 } 04363 // Check cache first 04364 $uid = $user->getId(); 04365 // avoid isset here, as it'll return false for null entries 04366 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) { 04367 return $this->mNotificationTimestamp[$uid]; 04368 } 04369 if ( !$uid || !$wgShowUpdatedMarker ) { 04370 return $this->mNotificationTimestamp[$uid] = false; 04371 } 04372 // Don't cache too much! 04373 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) { 04374 $this->mNotificationTimestamp = array(); 04375 } 04376 $dbr = wfGetDB( DB_SLAVE ); 04377 $this->mNotificationTimestamp[$uid] = $dbr->selectField( 'watchlist', 04378 'wl_notificationtimestamp', 04379 array( 04380 'wl_user' => $user->getId(), 04381 'wl_namespace' => $this->getNamespace(), 04382 'wl_title' => $this->getDBkey(), 04383 ), 04384 __METHOD__ 04385 ); 04386 return $this->mNotificationTimestamp[$uid]; 04387 } 04388 04395 public function getNamespaceKey( $prepend = 'nstab-' ) { 04396 global $wgContLang; 04397 // Gets the subject namespace if this title 04398 $namespace = MWNamespace::getSubject( $this->getNamespace() ); 04399 // Checks if cononical namespace name exists for namespace 04400 if ( MWNamespace::exists( $this->getNamespace() ) ) { 04401 // Uses canonical namespace name 04402 $namespaceKey = MWNamespace::getCanonicalName( $namespace ); 04403 } else { 04404 // Uses text of namespace 04405 $namespaceKey = $this->getSubjectNsText(); 04406 } 04407 // Makes namespace key lowercase 04408 $namespaceKey = $wgContLang->lc( $namespaceKey ); 04409 // Uses main 04410 if ( $namespaceKey == '' ) { 04411 $namespaceKey = 'main'; 04412 } 04413 // Changes file to image for backwards compatibility 04414 if ( $namespaceKey == 'file' ) { 04415 $namespaceKey = 'image'; 04416 } 04417 return $prepend . $namespaceKey; 04418 } 04419 04426 public function getRedirectsHere( $ns = null ) { 04427 $redirs = array(); 04428 04429 $dbr = wfGetDB( DB_SLAVE ); 04430 $where = array( 04431 'rd_namespace' => $this->getNamespace(), 04432 'rd_title' => $this->getDBkey(), 04433 'rd_from = page_id' 04434 ); 04435 if ( $this->isExternal() ) { 04436 $where['rd_interwiki'] = $this->getInterwiki(); 04437 } else { 04438 $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'; 04439 } 04440 if ( !is_null( $ns ) ) { 04441 $where['page_namespace'] = $ns; 04442 } 04443 04444 $res = $dbr->select( 04445 array( 'redirect', 'page' ), 04446 array( 'page_namespace', 'page_title' ), 04447 $where, 04448 __METHOD__ 04449 ); 04450 04451 foreach ( $res as $row ) { 04452 $redirs[] = self::newFromRow( $row ); 04453 } 04454 return $redirs; 04455 } 04456 04462 public function isValidRedirectTarget() { 04463 global $wgInvalidRedirectTargets; 04464 04465 // invalid redirect targets are stored in a global array, but explicity disallow Userlogout here 04466 if ( $this->isSpecial( 'Userlogout' ) ) { 04467 return false; 04468 } 04469 04470 foreach ( $wgInvalidRedirectTargets as $target ) { 04471 if ( $this->isSpecial( $target ) ) { 04472 return false; 04473 } 04474 } 04475 04476 return true; 04477 } 04478 04484 public function getBacklinkCache() { 04485 return BacklinkCache::get( $this ); 04486 } 04487 04493 public function canUseNoindex() { 04494 global $wgContentNamespaces, $wgExemptFromUserRobotsControl; 04495 04496 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl ) 04497 ? $wgContentNamespaces 04498 : $wgExemptFromUserRobotsControl; 04499 04500 return !in_array( $this->mNamespace, $bannedNamespaces ); 04501 04502 } 04503 04514 public function getCategorySortkey( $prefix = '' ) { 04515 $unprefixed = $this->getText(); 04516 04517 // Anything that uses this hook should only depend 04518 // on the Title object passed in, and should probably 04519 // tell the users to run updateCollations.php --force 04520 // in order to re-sort existing category relations. 04521 wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) ); 04522 if ( $prefix !== '' ) { 04523 # Separate with a line feed, so the unprefixed part is only used as 04524 # a tiebreaker when two pages have the exact same prefix. 04525 # In UCA, tab is the only character that can sort above LF 04526 # so we strip both of them from the original prefix. 04527 $prefix = strtr( $prefix, "\n\t", ' ' ); 04528 return "$prefix\n$unprefixed"; 04529 } 04530 return $unprefixed; 04531 } 04532 04541 public function getPageLanguage() { 04542 global $wgLang; 04543 if ( $this->isSpecialPage() ) { 04544 // special pages are in the user language 04545 return $wgLang; 04546 } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) { 04547 // css/js should always be LTR and is, in fact, English 04548 return wfGetLangObj( 'en' ); 04549 } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) { 04550 // Parse mediawiki messages with correct target language 04551 list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() ); 04552 return wfGetLangObj( $lang ); 04553 } 04554 global $wgContLang; 04555 // If nothing special, it should be in the wiki content language 04556 $pageLang = $wgContLang; 04557 // Hook at the end because we don't want to override the above stuff 04558 wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) ); 04559 return wfGetLangObj( $pageLang ); 04560 } 04561 04570 public function getPageViewLanguage() { 04571 $pageLang = $this->getPageLanguage(); 04572 // If this is nothing special (so the content is converted when viewed) 04573 if ( !$this->isSpecialPage() 04574 && !$this->isCssOrJsPage() && !$this->isCssJsSubpage() 04575 && $this->getNamespace() !== NS_MEDIAWIKI 04576 ) { 04577 // If the user chooses a variant, the content is actually 04578 // in a language whose code is the variant code. 04579 $variant = $pageLang->getPreferredVariant(); 04580 if ( $pageLang->getCode() !== $variant ) { 04581 $pageLang = Language::factory( $variant ); 04582 } 04583 } 04584 return $pageLang; 04585 } 04586 }