00001 <?php
00007 if ( !class_exists( 'UtfNormal' ) ) {
00008 require_once( dirname(__FILE__) . '/normal/UtfNormal.php' );
00009 }
00010
00011 define ( 'GAID_FOR_UPDATE', 1 );
00012
00019 class Title {
00022 static private $titleCache=array();
00023 static private $interwikiCache=array();
00025
00031 const CACHE_MAX = 1000;
00032
00033
00040
00041 var $mTextform = '';
00042 var $mUrlform = '';
00043 var $mDbkeyform = '';
00044 var $mUserCaseDBKey;
00045 var $mNamespace = NS_MAIN;
00046 var $mInterwiki = '';
00047 var $mFragment;
00048 var $mArticleID = -1;
00049 var $mLatestID = false;
00050 var $mRestrictions = array();
00051 var $mOldRestrictions = false;
00052 var $mCascadeRestriction;
00053 var $mRestrictionsExpiry = array();
00054 var $mHasCascadingRestrictions;
00055 var $mCascadeSources;
00056 var $mRestrictionsLoaded = false;
00057 var $mPrefixedText;
00058 # Don't change the following default, NS_MAIN is hardcoded in several
00059 # places. See bug 696.
00060 var $mDefaultNamespace = NS_MAIN;
00061 # Zero except in {{transclusion}} tags
00062 var $mWatched = null;
00063 var $mLength = -1;
00064 var $mRedirect = null;
00065 var $mNotificationTimestamp = array();
00066 var $mBacklinkCache = null;
00067
00068
00069
00074 function __construct() {}
00075
00083 public static function newFromDBkey( $key ) {
00084 $t = new Title();
00085 $t->mDbkeyform = $key;
00086 if( $t->secureAndSplit() )
00087 return $t;
00088 else
00089 return null;
00090 }
00091
00104 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
00105 if( is_object( $text ) ) {
00106 throw new MWException( 'Title::newFromText given an object' );
00107 }
00108
00117 if( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) {
00118 return Title::$titleCache[$text];
00119 }
00120
00124 $filteredText = Sanitizer::decodeCharReferences( $text );
00125
00126 $t = new Title();
00127 $t->mDbkeyform = str_replace( ' ', '_', $filteredText );
00128 $t->mDefaultNamespace = $defaultNamespace;
00129
00130 static $cachedcount = 0 ;
00131 if( $t->secureAndSplit() ) {
00132 if( $defaultNamespace == NS_MAIN ) {
00133 if( $cachedcount >= self::CACHE_MAX ) {
00134 # Avoid memory leaks on mass operations...
00135 Title::$titleCache = array();
00136 $cachedcount=0;
00137 }
00138 $cachedcount++;
00139 Title::$titleCache[$text] =& $t;
00140 }
00141 return $t;
00142 } else {
00143 $ret = null;
00144 return $ret;
00145 }
00146 }
00147
00162 public static function newFromURL( $url ) {
00163 global $wgLegalTitleChars;
00164 $t = new Title();
00165
00166 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
00167 # but some URLs used it as a space replacement and they still come
00168 # from some external search tools.
00169 if ( strpos( $wgLegalTitleChars, '+' ) === false ) {
00170 $url = str_replace( '+', ' ', $url );
00171 }
00172
00173 $t->mDbkeyform = str_replace( ' ', '_', $url );
00174 if( $t->secureAndSplit() ) {
00175 return $t;
00176 } else {
00177 return null;
00178 }
00179 }
00180
00188 public static function newFromID( $id, $flags = 0 ) {
00189 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
00190 $row = $db->selectRow( 'page', '*', array( 'page_id' => $id ), __METHOD__ );
00191 if( $row !== false ) {
00192 $title = Title::newFromRow( $row );
00193 } else {
00194 $title = null;
00195 }
00196 return $title;
00197 }
00198
00204 public static function newFromIDs( $ids ) {
00205 if ( !count( $ids ) ) {
00206 return array();
00207 }
00208 $dbr = wfGetDB( DB_SLAVE );
00209 $res = $dbr->select( 'page', array( 'page_namespace', 'page_title' ),
00210 'page_id IN (' . $dbr->makeList( $ids ) . ')', __METHOD__ );
00211
00212 $titles = array();
00213 foreach( $res as $row ) {
00214 $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
00215 }
00216 return $titles;
00217 }
00218
00224 public static function newFromRow( $row ) {
00225 $t = self::makeTitle( $row->page_namespace, $row->page_title );
00226
00227 $t->mArticleID = isset($row->page_id) ? intval($row->page_id) : -1;
00228 $t->mLength = isset($row->page_len) ? intval($row->page_len) : -1;
00229 $t->mRedirect = isset($row->page_is_redirect) ? (bool)$row->page_is_redirect : null;
00230 $t->mLatestID = isset($row->page_latest) ? $row->page_latest : false;
00231
00232 return $t;
00233 }
00234
00247 public static function &makeTitle( $ns, $title, $fragment = '' ) {
00248 $t = new Title();
00249 $t->mInterwiki = '';
00250 $t->mFragment = $fragment;
00251 $t->mNamespace = $ns = intval( $ns );
00252 $t->mDbkeyform = str_replace( ' ', '_', $title );
00253 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
00254 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
00255 $t->mTextform = str_replace( '_', ' ', $title );
00256 return $t;
00257 }
00258
00269 public static function makeTitleSafe( $ns, $title, $fragment = '' ) {
00270 $t = new Title();
00271 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment );
00272 if( $t->secureAndSplit() ) {
00273 return $t;
00274 } else {
00275 return null;
00276 }
00277 }
00278
00283 public static function newMainPage() {
00284 $title = Title::newFromText( wfMsgForContent( 'mainpage' ) );
00285
00286 if ( !$title ) {
00287 $title = Title::newFromText( 'Main Page' );
00288 }
00289 return $title;
00290 }
00291
00301 public static function newFromRedirect( $text ) {
00302 return self::newFromRedirectInternal( $text );
00303 }
00304
00314 public static function newFromRedirectRecurse( $text ) {
00315 $titles = self::newFromRedirectArray( $text );
00316 return $titles ? array_pop( $titles ) : null;
00317 }
00318
00328 public static function newFromRedirectArray( $text ) {
00329 global $wgMaxRedirects;
00330
00331 if( $wgMaxRedirects < 1 )
00332 return null;
00333 $title = self::newFromRedirectInternal( $text );
00334 if( is_null( $title ) )
00335 return null;
00336
00337 $recurse = $wgMaxRedirects;
00338 $titles = array( $title );
00339 while( --$recurse > 0 ) {
00340 if( $title->isRedirect() ) {
00341 $article = new Article( $title, 0 );
00342 $newtitle = $article->getRedirectTarget();
00343 } else {
00344 break;
00345 }
00346
00347 if( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
00348
00349 $title = $newtitle;
00350 $titles[] = $newtitle;
00351 } else {
00352 break;
00353 }
00354 }
00355 return $titles;
00356 }
00357
00365 protected static function newFromRedirectInternal( $text ) {
00366 $redir = MagicWord::get( 'redirect' );
00367 $text = trim($text);
00368 if( $redir->matchStartAndRemove( $text ) ) {
00369
00370
00371
00372 $m = array();
00373 if( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
00374
00375
00376 if( strpos( $m[1], '%' ) !== false ) {
00377
00378
00379
00380 $m[1] = urldecode( ltrim( $m[1], ':' ) );
00381 }
00382 $title = Title::newFromText( $m[1] );
00383
00384 if( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
00385 return null;
00386 }
00387 return $title;
00388 }
00389 }
00390 return null;
00391 }
00392
00393 #----------------------------------------------------------------------------
00394 # Static functions
00395 #----------------------------------------------------------------------------
00396
00403 public static function nameOf( $id ) {
00404 $dbr = wfGetDB( DB_SLAVE );
00405
00406 $s = $dbr->selectRow( 'page',
00407 array( 'page_namespace','page_title' ),
00408 array( 'page_id' => $id ),
00409 __METHOD__ );
00410 if ( $s === false ) { return null; }
00411
00412 $n = self::makeName( $s->page_namespace, $s->page_title );
00413 return $n;
00414 }
00415
00420 public static function legalChars() {
00421 global $wgLegalTitleChars;
00422 return $wgLegalTitleChars;
00423 }
00424
00434 public static function indexTitle( $ns, $title ) {
00435 global $wgContLang;
00436
00437 $lc = SearchEngine::legalSearchChars() . '&#;';
00438 $t = $wgContLang->normalizeForSearch( $title );
00439 $t = preg_replace( "/[^{$lc}]+/", ' ', $t );
00440 $t = $wgContLang->lc( $t );
00441
00442 # Handle 's, s'
00443 $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t );
00444 $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t );
00445
00446 $t = preg_replace( "/\\s+/", ' ', $t );
00447
00448 if ( $ns == NS_FILE ) {
00449 $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t );
00450 }
00451 return trim( $t );
00452 }
00453
00461 public static function makeName( $ns, $title, $fragment = '' ) {
00462 global $wgContLang;
00463
00464 $namespace = $wgContLang->getNsText( $ns );
00465 $name = $namespace == '' ? $title : "$namespace:$title";
00466 if ( strval( $fragment ) != '' ) {
00467 $name .= '#' . $fragment;
00468 }
00469 return $name;
00470 }
00471
00479 public function isLocal() {
00480 if ( $this->mInterwiki != '' ) {
00481 return Interwiki::fetch( $this->mInterwiki )->isLocal();
00482 } else {
00483 return true;
00484 }
00485 }
00486
00493 public function isTrans() {
00494 if ($this->mInterwiki == '')
00495 return false;
00496
00497 return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
00498 }
00499
00503 static function escapeFragmentForURL( $fragment ) {
00504 # Note that we don't urlencode the fragment. urlencoded Unicode
00505 # fragments appear not to work in IE (at least up to 7) or in at least
00506 # one version of Opera 9.x. The W3C validator, for one, doesn't seem
00507 # to care if they aren't encoded.
00508 return Sanitizer::escapeId( $fragment, 'noninitial' );
00509 }
00510
00511 #----------------------------------------------------------------------------
00512 # Other stuff
00513 #----------------------------------------------------------------------------
00514
00520 public function getText() { return $this->mTextform; }
00525 public function getPartialURL() { return $this->mUrlform; }
00530 public function getDBkey() { return $this->mDbkeyform; }
00535 public function getNamespace() { return $this->mNamespace; }
00540 public function getNsText() {
00541 global $wgContLang;
00542
00543 if ( $this->mInterwiki != '' ) {
00544
00545
00546
00547
00548
00549
00550 if( MWNamespace::exists( $this->mNamespace ) ) {
00551 return MWNamespace::getCanonicalName( $this->mNamespace );
00552 }
00553 }
00554 return $wgContLang->getNsText( $this->mNamespace );
00555 }
00560 function getUserCaseDBKey() {
00561 return $this->mUserCaseDBKey;
00562 }
00567 public function getSubjectNsText() {
00568 global $wgContLang;
00569 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
00570 }
00575 public function getTalkNsText() {
00576 global $wgContLang;
00577 return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) );
00578 }
00583 public function canTalk() {
00584 return( MWNamespace::canTalk( $this->mNamespace ) );
00585 }
00590 public function getInterwiki() { return $this->mInterwiki; }
00595 public function getFragment() { return $this->mFragment; }
00600 public function getFragmentForURL() {
00601 if ( $this->mFragment == '' ) {
00602 return '';
00603 } else {
00604 return '#' . Title::escapeFragmentForURL( $this->mFragment );
00605 }
00606 }
00611 public function getDefaultNamespace() { return $this->mDefaultNamespace; }
00612
00618 public function getIndexTitle() {
00619 return Title::indexTitle( $this->mNamespace, $this->mTextform );
00620 }
00621
00627 public function getPrefixedDBkey() {
00628 $s = $this->prefix( $this->mDbkeyform );
00629 $s = str_replace( ' ', '_', $s );
00630 return $s;
00631 }
00632
00638 public function getPrefixedText() {
00639 if ( empty( $this->mPrefixedText ) ) {
00640 $s = $this->prefix( $this->mTextform );
00641 $s = str_replace( '_', ' ', $s );
00642 $this->mPrefixedText = $s;
00643 }
00644 return $this->mPrefixedText;
00645 }
00646
00653 public function getFullText() {
00654 $text = $this->getPrefixedText();
00655 if( $this->mFragment != '' ) {
00656 $text .= '#' . $this->mFragment;
00657 }
00658 return $text;
00659 }
00660
00665 public function getBaseText() {
00666 if( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
00667 return $this->getText();
00668 }
00669
00670 $parts = explode( '/', $this->getText() );
00671 # Don't discard the real title if there's no subpage involved
00672 if( count( $parts ) > 1 )
00673 unset( $parts[ count( $parts ) - 1 ] );
00674 return implode( '/', $parts );
00675 }
00676
00681 public function getSubpageText() {
00682 if( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
00683 return( $this->mTextform );
00684 }
00685 $parts = explode( '/', $this->mTextform );
00686 return( $parts[ count( $parts ) - 1 ] );
00687 }
00688
00693 public function getSubpageUrlForm() {
00694 $text = $this->getSubpageText();
00695 $text = wfUrlencode( str_replace( ' ', '_', $text ) );
00696 return( $text );
00697 }
00698
00703 public function getPrefixedURL() {
00704 $s = $this->prefix( $this->mDbkeyform );
00705 $s = wfUrlencode( str_replace( ' ', '_', $s ) );
00706 return $s;
00707 }
00708
00719 public function getFullURL( $query = '', $variant = false ) {
00720 global $wgContLang, $wgServer, $wgRequest;
00721
00722 if( is_array( $query ) ) {
00723 $query = wfArrayToCGI( $query );
00724 }
00725
00726 $interwiki = Interwiki::fetch( $this->mInterwiki );
00727 if ( !$interwiki ) {
00728 $url = $this->getLocalURL( $query, $variant );
00729
00730
00731
00732 if ($wgRequest->getVal('action') != 'render') {
00733 $url = $wgServer . $url;
00734 }
00735 } else {
00736 $baseUrl = $interwiki->getURL( );
00737
00738 $namespace = wfUrlencode( $this->getNsText() );
00739 if ( $namespace != '' ) {
00740 # Can this actually happen? Interwikis shouldn't be parsed.
00741 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
00742 $namespace .= ':';
00743 }
00744 $url = str_replace( '$1', $namespace . $this->mUrlform, $baseUrl );
00745 $url = wfAppendQuery( $url, $query );
00746 }
00747
00748 # Finally, add the fragment.
00749 $url .= $this->getFragmentForURL();
00750
00751 wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
00752 return $url;
00753 }
00754
00765 public function getLocalURL( $query = '', $variant = false ) {
00766 global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
00767 global $wgVariantArticlePath, $wgContLang, $wgUser;
00768
00769 if( is_array( $query ) ) {
00770 $query = wfArrayToCGI( $query );
00771 }
00772
00773
00774 if($variant == false && $wgContLang->hasVariants() && !$wgUser->isLoggedIn()){
00775 $pref = $wgContLang->getPreferredVariant(false);
00776 if($pref != $wgContLang->getCode())
00777 $variant = $pref;
00778 }
00779
00780 if ( $this->isExternal() ) {
00781 $url = $this->getFullURL();
00782 if ( $query ) {
00783
00784
00785
00786
00787 $url .= "?$query";
00788 }
00789 } else {
00790 $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
00791 if ( $query == '' ) {
00792 if( $variant != false && $wgContLang->hasVariants() ) {
00793 if( $wgVariantArticlePath == false ) {
00794 $variantArticlePath = "$wgScript?title=$1&variant=$2";
00795 } else {
00796 $variantArticlePath = $wgVariantArticlePath;
00797 }
00798 $url = str_replace( '$2', urlencode( $variant ), $variantArticlePath );
00799 $url = str_replace( '$1', $dbkey, $url );
00800 } else {
00801 $url = str_replace( '$1', $dbkey, $wgArticlePath );
00802 }
00803 } else {
00804 global $wgActionPaths;
00805 $url = false;
00806 $matches = array();
00807 if( !empty( $wgActionPaths ) &&
00808 preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
00809 {
00810 $action = urldecode( $matches[2] );
00811 if( isset( $wgActionPaths[$action] ) ) {
00812 $query = $matches[1];
00813 if( isset( $matches[4] ) ) $query .= $matches[4];
00814 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
00815 if( $query != '' ) {
00816 $url = wfAppendQuery( $url, $query );
00817 }
00818 }
00819 }
00820 if ( $url === false ) {
00821 if ( $query == '-' ) {
00822 $query = '';
00823 }
00824 $url = "{$wgScript}?title={$dbkey}&{$query}";
00825 }
00826 }
00827
00828
00829
00830 if ($wgRequest->getVal('action') == 'render') {
00831 $url = $wgServer . $url;
00832 }
00833 }
00834 wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
00835 return $url;
00836 }
00837
00855 public function getLinkUrl( $query = array(), $variant = false ) {
00856 wfProfileIn( __METHOD__ );
00857 if( $this->isExternal() ) {
00858 $ret = $this->getFullURL( $query );
00859 } elseif( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
00860 $ret = $this->getFragmentForURL();
00861 } else {
00862 $ret = $this->getLocalURL( $query, $variant ) . $this->getFragmentForURL();
00863 }
00864 wfProfileOut( __METHOD__ );
00865 return $ret;
00866 }
00867
00874 public function escapeLocalURL( $query = '' ) {
00875 return htmlspecialchars( $this->getLocalURL( $query ) );
00876 }
00877
00885 public function escapeFullURL( $query = '' ) {
00886 return htmlspecialchars( $this->getFullURL( $query ) );
00887 }
00888
00898 public function getInternalURL( $query = '', $variant = false ) {
00899 global $wgInternalServer;
00900 $url = $wgInternalServer . $this->getLocalURL( $query, $variant );
00901 wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
00902 return $url;
00903 }
00904
00910 public function getEditURL() {
00911 if ( $this->mInterwiki != '' ) { return ''; }
00912 $s = $this->getLocalURL( 'action=edit' );
00913
00914 return $s;
00915 }
00916
00922 public function getEscapedText() {
00923 return htmlspecialchars( $this->getPrefixedText() );
00924 }
00925
00930 public function isExternal() { return ( $this->mInterwiki != '' ); }
00931
00938 public function isSemiProtected( $action = 'edit' ) {
00939 if( $this->exists() ) {
00940 $restrictions = $this->getRestrictions( $action );
00941 if( count( $restrictions ) > 0 ) {
00942 foreach( $restrictions as $restriction ) {
00943 if( strtolower( $restriction ) != 'autoconfirmed' )
00944 return false;
00945 }
00946 } else {
00947 # Not protected
00948 return false;
00949 }
00950 return true;
00951 } else {
00952 # If it doesn't exist, it can't be protected
00953 return false;
00954 }
00955 }
00956
00963 public function isProtected( $action = '' ) {
00964 global $wgRestrictionLevels;
00965
00966 $restrictionTypes = $this->getRestrictionTypes();
00967
00968 # Special pages have inherent protection
00969 if( $this->getNamespace() == NS_SPECIAL )
00970 return true;
00971
00972 # Check regular protection levels
00973 foreach( $restrictionTypes as $type ){
00974 if( $action == $type || $action == '' ) {
00975 $r = $this->getRestrictions( $type );
00976 foreach( $wgRestrictionLevels as $level ) {
00977 if( in_array( $level, $r ) && $level != '' ) {
00978 return true;
00979 }
00980 }
00981 }
00982 }
00983
00984 return false;
00985 }
00986
00991 public function isConversionTable() {
00992 if($this->getNamespace() == NS_MEDIAWIKI
00993 && strpos( $this->getText(), 'Conversiontable' ) !== false ) {
00994 return true;
00995 }
00996
00997 return false;
00998 }
00999
01004 public function userIsWatching() {
01005 global $wgUser;
01006
01007 if ( is_null( $this->mWatched ) ) {
01008 if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn()) {
01009 $this->mWatched = false;
01010 } else {
01011 $this->mWatched = $wgUser->isWatched( $this );
01012 }
01013 }
01014 return $this->mWatched;
01015 }
01016
01030 public function quickUserCan( $action ) {
01031 return $this->userCan( $action, false );
01032 }
01033
01040 public function isNamespaceProtected() {
01041 global $wgNamespaceProtection, $wgUser;
01042 if( isset( $wgNamespaceProtection[ $this->mNamespace ] ) ) {
01043 foreach( (array)$wgNamespaceProtection[ $this->mNamespace ] as $right ) {
01044 if( $right != '' && !$wgUser->isAllowed( $right ) )
01045 return true;
01046 }
01047 }
01048 return false;
01049 }
01050
01057 public function userCan( $action, $doExpensiveQueries = true ) {
01058 global $wgUser;
01059 return ($this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries, true ) === array());
01060 }
01061
01073 public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
01074 if( !StubObject::isRealObject( $user ) ) {
01075
01076 global $wgUser;
01077 $wgUser->_unstub( '', 5 );
01078 $user = $wgUser;
01079 }
01080 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
01081
01082 global $wgContLang;
01083 global $wgLang;
01084 global $wgEmailConfirmToEdit;
01085
01086 if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' ) {
01087 $errors[] = array( 'confirmedittext' );
01088 }
01089
01090
01091 if ( $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) ) {
01092 $block = $user->mBlock;
01093
01094
01095
01096
01097 $id = $user->blockedBy();
01098 $reason = $user->blockedFor();
01099 if( $reason == '' ) {
01100 $reason = wfMsg( 'blockednoreason' );
01101 }
01102 $ip = wfGetIP();
01103
01104 if ( is_numeric( $id ) ) {
01105 $name = User::whoIs( $id );
01106 } else {
01107 $name = $id;
01108 }
01109
01110 $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
01111 $blockid = $block->mId;
01112 $blockExpiry = $user->mBlock->mExpiry;
01113 $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
01114
01115 if ( $blockExpiry == 'infinity' ) {
01116
01117 $scBlockExpiryOptions = wfMsg( 'ipboptions' );
01118
01119 foreach ( explode( ',', $scBlockExpiryOptions ) as $option ) {
01120 if ( strpos( $option, ':' ) == false )
01121 continue;
01122
01123 list ($show, $value) = explode( ":", $option );
01124
01125 if ( $value == 'infinite' || $value == 'indefinite' ) {
01126 $blockExpiry = $show;
01127 break;
01128 }
01129 }
01130 } else {
01131 $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
01132 }
01133
01134 $intended = $user->mBlock->mAddress;
01135
01136 $errors[] = array( ($block->mAuto ? 'autoblockedtext' : 'blockedtext'), $link, $reason, $ip, $name,
01137 $blockid, $blockExpiry, $intended, $blockTimestamp );
01138 }
01139
01140
01141
01142 foreach( $errors as $index => $error ) {
01143 $error_key = is_array($error) ? $error[0] : $error;
01144
01145 if (in_array( $error_key, $ignoreErrors )) {
01146 unset($errors[$index]);
01147 }
01148 }
01149
01150 return $errors;
01151 }
01152
01164 private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries=true, $short=false ) {
01165 wfProfileIn( __METHOD__ );
01166
01167 $errors = array();
01168
01169
01170 if ( $action == 'move' ) {
01171 if( !$user->isAllowed( 'move-rootuserpages' )
01172 && $this->getNamespace() == NS_USER && !$this->isSubpage() )
01173 {
01174
01175 $errors[] = array( 'cant-move-user-page' );
01176 }
01177
01178
01179 if( $this->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
01180 $errors[] = array( 'movenotallowedfile' );
01181 }
01182
01183 if( !$user->isAllowed( 'move' ) ) {
01184
01185 global $wgGroupPermissions;
01186 $userCanMove = false;
01187 if ( isset( $wgGroupPermissions['user']['move'] ) ) {
01188 $userCanMove = $wgGroupPermissions['user']['move'];
01189 }
01190 $autoconfirmedCanMove = false;
01191 if ( isset( $wgGroupPermissions['autoconfirmed']['move'] ) ) {
01192 $autoconfirmedCanMove = $wgGroupPermissions['autoconfirmed']['move'];
01193 }
01194 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
01195
01196 $errors[] = array ( 'movenologintext' );
01197 } else {
01198 $errors[] = array ('movenotallowed');
01199 }
01200 }
01201 } elseif ( $action == 'create' ) {
01202 if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
01203 ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) )
01204 {
01205 $errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin');
01206 }
01207 } elseif( $action == 'move-target' ) {
01208 if( !$user->isAllowed( 'move' ) ) {
01209
01210 $errors[] = array ('movenotallowed');
01211 } elseif( !$user->isAllowed( 'move-rootuserpages' )
01212 && $this->getNamespace() == NS_USER && !$this->isSubpage() )
01213 {
01214
01215 $errors[] = array( 'cant-move-to-user-page' );
01216 }
01217 } elseif( !$user->isAllowed( $action ) ) {
01218 $return = null;
01219
01220
01221 $groups = false;
01222 if (!$short) {
01223 $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
01224 User::getGroupsWithPermission( $action ) );
01225 }
01226
01227 if( $groups ) {
01228 $return = array( 'badaccess-groups',
01229 array( implode( ', ', $groups ), count( $groups ) ) );
01230 } else {
01231 $return = array( "badaccess-group0" );
01232 }
01233 $errors[] = $return;
01234 }
01235
01236 # Short-circuit point
01237 if( $short && count($errors) > 0 ) {
01238 wfProfileOut( __METHOD__ );
01239 return $errors;
01240 }
01241
01242
01243 if( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
01244 wfProfileOut( __METHOD__ );
01245 return $result ? array() : array( array( 'badaccess-group0' ) );
01246 }
01247
01248 if( !wfRunHooks( 'getUserPermissionsErrors', array(&$this,&$user,$action,&$result) ) ) {
01249 if( is_array($result) && count($result) && !is_array($result[0]) )
01250 $errors[] = $result; # A single array representing an error
01251 else if( is_array($result) && is_array($result[0]) )
01252 $errors = array_merge( $errors, $result ); # A nested array representing multiple errors
01253 else if( $result !== '' && is_string($result) )
01254 $errors[] = array($result); # A string representing a message-id
01255 else if( $result === false )
01256 $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
01257 }
01258 # Short-circuit point
01259 if( $short && count($errors) > 0 ) {
01260 wfProfileOut( __METHOD__ );
01261 return $errors;
01262 }
01263
01264 if( $doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array(&$this,&$user,$action,&$result) ) ) {
01265 if( is_array($result) && count($result) && !is_array($result[0]) )
01266 $errors[] = $result; # A single array representing an error
01267 else if( is_array($result) && is_array($result[0]) )
01268 $errors = array_merge( $errors, $result ); # A nested array representing multiple errors
01269 else if( $result !== '' && is_string($result) )
01270 $errors[] = array($result); # A string representing a message-id
01271 else if( $result === false )
01272 $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
01273 }
01274 # Short-circuit point
01275 if( $short && count($errors) > 0 ) {
01276 wfProfileOut( __METHOD__ );
01277 return $errors;
01278 }
01279
01280 # Only 'createaccount' and 'execute' can be performed on
01281 # special pages, which don't actually exist in the DB.
01282 $specialOKActions = array( 'createaccount', 'execute' );
01283 if( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions) ) {
01284 $errors[] = array('ns-specialprotected');
01285 }
01286
01287 # Check $wgNamespaceProtection for restricted namespaces
01288 if( $this->isNamespaceProtected() ) {
01289 $ns = $this->getNamespace() == NS_MAIN ?
01290 wfMsg( 'nstab-main' ) : $this->getNsText();
01291 $errors[] = NS_MEDIAWIKI == $this->mNamespace ?
01292 array('protectedinterface') : array( 'namespaceprotected', $ns );
01293 }
01294
01295 # Protect css/js subpages of user pages
01296 # XXX: this might be better using restrictions
01297 # XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssSubpage()
01298 # and $this->userCanEditJsSubpage() from working
01299 # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
01300 if( $this->isCssSubpage() && !( $user->isAllowed('editusercssjs') || $user->isAllowed('editusercss') )
01301 && $action != 'patrol'
01302 && !preg_match('/^'.preg_quote($user->getName(), '/').'\//', $this->mTextform) )
01303 {
01304 $errors[] = array('customcssjsprotected');
01305 } else if( $this->isJsSubpage() && !( $user->isAllowed('editusercssjs') || $user->isAllowed('edituserjs') )
01306 && $action != 'patrol'
01307 && !preg_match('/^'.preg_quote($user->getName(), '/').'\//', $this->mTextform) )
01308 {
01309 $errors[] = array('customcssjsprotected');
01310 }
01311
01312 # Check against page_restrictions table requirements on this
01313 # page. The user must possess all required rights for this action.
01314 foreach( $this->getRestrictions($action) as $right ) {
01315
01316 if( $right == 'sysop' ) {
01317 $right = 'protect';
01318 }
01319 if( $right != '' && !$user->isAllowed( $right ) ) {
01320
01321 if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
01322
01323
01324 if( $this->mCascadeRestriction ) {
01325 $errors[] = array( 'protectedpagetext', $right );
01326 }
01327 } else {
01328 $errors[] = array( 'protectedpagetext', $right );
01329 }
01330 }
01331 }
01332 # Short-circuit point
01333 if( $short && count($errors) > 0 ) {
01334 wfProfileOut( __METHOD__ );
01335 return $errors;
01336 }
01337
01338 if( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
01339 # We /could/ use the protection level on the source page, but it's fairly ugly
01340 # as we have to establish a precedence hierarchy for pages included by multiple
01341 # cascade-protected pages. So just restrict it to people with 'protect' permission,
01342 # as they could remove the protection anyway.
01343 list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
01344 # Cascading protection depends on more than this page...
01345 # Several cascading protected pages may include this page...
01346 # Check each cascading level
01347 # This is only for protection restrictions, not for all actions
01348 if( $cascadingSources > 0 && isset($restrictions[$action]) ) {
01349 foreach( $restrictions[$action] as $right ) {
01350 $right = ( $right == 'sysop' ) ? 'protect' : $right;
01351 if( $right != '' && !$user->isAllowed( $right ) ) {
01352 $pages = '';
01353 foreach( $cascadingSources as $page )
01354 $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
01355 $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
01356 }
01357 }
01358 }
01359 }
01360 # Short-circuit point
01361 if( $short && count($errors) > 0 ) {
01362 wfProfileOut( __METHOD__ );
01363 return $errors;
01364 }
01365
01366 if( $action == 'protect' ) {
01367 if( $this->getUserPermissionsErrors('edit', $user) != array() ) {
01368 $errors[] = array( 'protect-cantedit' );
01369 }
01370 }
01371
01372 if( $action == 'create' ) {
01373 $title_protection = $this->getTitleProtection();
01374 if( is_array($title_protection) ) {
01375 extract($title_protection);
01376
01377 if( $pt_create_perm == 'sysop' ) {
01378 $pt_create_perm = 'protect';
01379 }
01380 if( $pt_create_perm == '' || !$user->isAllowed($pt_create_perm) ) {
01381 $errors[] = array( 'titleprotected', User::whoIs($pt_user), $pt_reason );
01382 }
01383 }
01384 } elseif( $action == 'move' ) {
01385
01386 if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
01387
01388 $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
01389 } elseif( !$this->isMovable() ) {
01390
01391 $errors[] = array( 'immobile-page' );
01392 }
01393 } elseif( $action == 'move-target' ) {
01394 if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
01395 $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
01396 } elseif( !$this->isMovable() ) {
01397 $errors[] = array( 'immobile-target-page' );
01398 }
01399 }
01400
01401 wfProfileOut( __METHOD__ );
01402 return $errors;
01403 }
01404
01410 private function getTitleProtection() {
01411
01412 if ( $this->getNamespace() < 0 ) {
01413 return false;
01414 }
01415
01416
01417 if ($this->exists()) {
01418 return false;
01419 }
01420
01421 $dbr = wfGetDB( DB_SLAVE );
01422 $res = $dbr->select( 'protected_titles', '*',
01423 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
01424 __METHOD__ );
01425
01426 if ($row = $dbr->fetchRow( $res )) {
01427 return $row;
01428 } else {
01429 return false;
01430 }
01431 }
01432
01439 public function updateTitleProtection( $create_perm, $reason, $expiry ) {
01440 global $wgUser,$wgContLang;
01441
01442 if ($create_perm == implode(',',$this->getRestrictions('create'))
01443 && $expiry == $this->mRestrictionsExpiry['create']) {
01444
01445 return true;
01446 }
01447
01448 list ($namespace, $title) = array( $this->getNamespace(), $this->getDBkey() );
01449
01450 $dbw = wfGetDB( DB_MASTER );
01451
01452 $encodedExpiry = Block::encodeExpiry($expiry, $dbw );
01453
01454 $expiry_description = '';
01455 if ( $encodedExpiry != 'infinity' ) {
01456 $expiry_description = ' (' . wfMsgForContent( 'protect-expiring',$wgContLang->timeanddate( $expiry ),
01457 $wgContLang->date( $expiry ) , $wgContLang->time( $expiry ) ).')';
01458 }
01459 else {
01460 $expiry_description .= ' (' . wfMsgForContent( 'protect-expiry-indefinite' ).')';
01461 }
01462
01463 # Update protection table
01464 if ($create_perm != '' ) {
01465 $dbw->replace( 'protected_titles', array(array('pt_namespace', 'pt_title')),
01466 array(
01467 'pt_namespace' => $namespace,
01468 'pt_title' => $title,
01469 'pt_create_perm' => $create_perm,
01470 'pt_timestamp' => Block::encodeExpiry(wfTimestampNow(), $dbw),
01471 'pt_expiry' => $encodedExpiry,
01472 'pt_user' => $wgUser->getId(),
01473 'pt_reason' => $reason,
01474 ), __METHOD__
01475 );
01476 } else {
01477 $dbw->delete( 'protected_titles', array( 'pt_namespace' => $namespace,
01478 'pt_title' => $title ), __METHOD__ );
01479 }
01480 # Update the protection log
01481 if( $dbw->affectedRows() ) {
01482 $log = new LogPage( 'protect' );
01483
01484 if( $create_perm ) {
01485 $params = array("[create=$create_perm] $expiry_description",'');
01486 $log->addEntry( ( isset( $this->mRestrictions['create'] ) && $this->mRestrictions['create'] ) ? 'modify' : 'protect', $this, trim( $reason ), $params );
01487 } else {
01488 $log->addEntry( 'unprotect', $this, $reason );
01489 }
01490 }
01491
01492 return true;
01493 }
01494
01498 public function deleteTitleProtection() {
01499 $dbw = wfGetDB( DB_MASTER );
01500
01501 $dbw->delete( 'protected_titles',
01502 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
01503 __METHOD__ );
01504 }
01505
01512 public function isMovable() {
01513 return MWNamespace::isMovable( $this->getNamespace() ) && $this->getInterwiki() == '';
01514 }
01515
01521 public function userCanRead() {
01522 global $wgUser, $wgGroupPermissions;
01523
01524 static $useShortcut = null;
01525
01526 # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
01527 if( is_null( $useShortcut ) ) {
01528 global $wgRevokePermissions;
01529 $useShortcut = true;
01530 if( empty( $wgGroupPermissions['*']['read'] ) ) {
01531 # Not a public wiki, so no shortcut
01532 $useShortcut = false;
01533 } elseif( !empty( $wgRevokePermissions ) ) {
01534
01535
01536
01537
01538
01539
01540 foreach( $wgRevokePermissions as $perms ) {
01541 if( !empty( $perms['read'] ) ) {
01542 # We might be removing the read right from the user, so no shortcut
01543 $useShortcut = false;
01544 break;
01545 }
01546 }
01547 }
01548 }
01549
01550 $result = null;
01551 wfRunHooks( 'userCan', array( &$this, &$wgUser, 'read', &$result ) );
01552 if ( $result !== null ) {
01553 return $result;
01554 }
01555
01556 # Shortcut for public wikis, allows skipping quite a bit of code
01557 if ( $useShortcut )
01558 return true;
01559
01560 if( $wgUser->isAllowed( 'read' ) ) {
01561 return true;
01562 } else {
01563 global $wgWhitelistRead;
01564
01569 if( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'Resetpass' ) ) {
01570 return true;
01571 }
01572
01576 if( !is_array($wgWhitelistRead) ) {
01577 return false;
01578 }
01579
01583 $name = $this->getPrefixedText();
01584 $dbName = $this->getPrefixedDBKey();
01585
01586 if( in_array($name,$wgWhitelistRead,true) || in_array($dbName,$wgWhitelistRead,true) )
01587 return true;
01588
01593 if( $this->getNamespace() == NS_MAIN ) {
01594 if( in_array( ':' . $name, $wgWhitelistRead ) )
01595 return true;
01596 }
01597
01602 if( $this->getNamespace() == NS_SPECIAL ) {
01603 $name = $this->getDBkey();
01604 list( $name, ) = SpecialPage::resolveAliasWithSubpage( $name );
01605 if ( $name === false ) {
01606 # Invalid special page, but we show standard login required message
01607 return false;
01608 }
01609
01610 $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
01611 if( in_array( $pure, $wgWhitelistRead, true ) )
01612 return true;
01613 }
01614
01615 }
01616 return false;
01617 }
01618
01623 public function isTalkPage() {
01624 return MWNamespace::isTalk( $this->getNamespace() );
01625 }
01626
01631 public function isSubpage() {
01632 return MWNamespace::hasSubpages( $this->mNamespace )
01633 ? strpos( $this->getText(), '/' ) !== false
01634 : false;
01635 }
01636
01641 public function hasSubpages() {
01642 if( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01643 # Duh
01644 return false;
01645 }
01646
01647 # We dynamically add a member variable for the purpose of this method
01648 # alone to cache the result. There's no point in having it hanging
01649 # around uninitialized in every Title object; therefore we only add it
01650 # if needed and don't declare it statically.
01651 if( isset( $this->mHasSubpages ) ) {
01652 return $this->mHasSubpages;
01653 }
01654
01655 $subpages = $this->getSubpages( 1 );
01656 if( $subpages instanceof TitleArray )
01657 return $this->mHasSubpages = (bool)$subpages->count();
01658 return $this->mHasSubpages = false;
01659 }
01660
01667 public function getSubpages( $limit = -1 ) {
01668 if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
01669 return array();
01670
01671 $dbr = wfGetDB( DB_SLAVE );
01672 $conds['page_namespace'] = $this->getNamespace();
01673 $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
01674 $options = array();
01675 if( $limit > -1 )
01676 $options['LIMIT'] = $limit;
01677 return $this->mSubpages = TitleArray::newFromResult(
01678 $dbr->select( 'page',
01679 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
01680 $conds,
01681 __METHOD__,
01682 $options
01683 )
01684 );
01685 }
01686
01693 public function isCssOrJsPage() {
01694 return $this->mNamespace == NS_MEDIAWIKI
01695 && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
01696 }
01697
01702 public function isCssJsSubpage() {
01703 return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
01704 }
01710 public function isValidCssJsSubpage() {
01711 if ( $this->isCssJsSubpage() ) {
01712 $skinNames = Skin::getSkinNames();
01713 return array_key_exists( $this->getSkinFromCssJsSubpage(), $skinNames );
01714 } else {
01715 return false;
01716 }
01717 }
01721 public function getSkinFromCssJsSubpage() {
01722 $subpage = explode( '/', $this->mTextform );
01723 $subpage = $subpage[ count( $subpage ) - 1 ];
01724 return( str_replace( array( '.css', '.js' ), array( '', '' ), $subpage ) );
01725 }
01730 public function isCssSubpage() {
01731 return ( NS_USER == $this->mNamespace && preg_match("/\\/.*\\.css$/", $this->mTextform ) );
01732 }
01737 public function isJsSubpage() {
01738 return ( NS_USER == $this->mNamespace && preg_match("/\\/.*\\.js$/", $this->mTextform ) );
01739 }
01747 public function userCanEditCssSubpage() {
01748 global $wgUser;
01749 return ( ( $wgUser->isAllowed('editusercssjs') && $wgUser->isAllowed('editusercss') )
01750 || preg_match('/^'.preg_quote($wgUser->getName(), '/').'\//', $this->mTextform) );
01751 }
01759 public function userCanEditJsSubpage() {
01760 global $wgUser;
01761 return ( ( $wgUser->isAllowed('editusercssjs') && $wgUser->isAllowed('edituserjs') )
01762 || preg_match('/^'.preg_quote($wgUser->getName(), '/').'\//', $this->mTextform) );
01763 }
01764
01770 public function isCascadeProtected() {
01771 list( $sources, ) = $this->getCascadeProtectionSources( false );
01772 return ( $sources > 0 );
01773 }
01774
01783 public function getCascadeProtectionSources( $get_pages = true ) {
01784 $pagerestrictions = array();
01785
01786 if ( isset( $this->mCascadeSources ) && $get_pages ) {
01787 return array( $this->mCascadeSources, $this->mCascadingRestrictions );
01788 } else if ( isset( $this->mHasCascadingRestrictions ) && !$get_pages ) {
01789 return array( $this->mHasCascadingRestrictions, $pagerestrictions );
01790 }
01791
01792 wfProfileIn( __METHOD__ );
01793
01794 $dbr = wfGetDB( DB_SLAVE );
01795
01796 if ( $this->getNamespace() == NS_FILE ) {
01797 $tables = array ('imagelinks', 'page_restrictions');
01798 $where_clauses = array(
01799 'il_to' => $this->getDBkey(),
01800 'il_from=pr_page',
01801 'pr_cascade' => 1 );
01802 } else {
01803 $tables = array ('templatelinks', 'page_restrictions');
01804 $where_clauses = array(
01805 'tl_namespace' => $this->getNamespace(),
01806 'tl_title' => $this->getDBkey(),
01807 'tl_from=pr_page',
01808 'pr_cascade' => 1 );
01809 }
01810
01811 if ( $get_pages ) {
01812 $cols = array('pr_page', 'page_namespace', 'page_title', 'pr_expiry', 'pr_type', 'pr_level' );
01813 $where_clauses[] = 'page_id=pr_page';
01814 $tables[] = 'page';
01815 } else {
01816 $cols = array( 'pr_expiry' );
01817 }
01818
01819 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
01820
01821 $sources = $get_pages ? array() : false;
01822 $now = wfTimestampNow();
01823 $purgeExpired = false;
01824
01825 foreach( $res as $row ) {
01826 $expiry = Block::decodeExpiry( $row->pr_expiry );
01827 if( $expiry > $now ) {
01828 if ($get_pages) {
01829 $page_id = $row->pr_page;
01830 $page_ns = $row->page_namespace;
01831 $page_title = $row->page_title;
01832 $sources[$page_id] = Title::makeTitle($page_ns, $page_title);
01833 # Add groups needed for each restriction type if its not already there
01834 # Make sure this restriction type still exists
01835
01836 if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
01837 $pagerestrictions[$row->pr_type] = array();
01838 }
01839
01840 if ( isset($pagerestrictions[$row->pr_type]) &&
01841 !in_array($row->pr_level, $pagerestrictions[$row->pr_type]) ) {
01842 $pagerestrictions[$row->pr_type][]=$row->pr_level;
01843 }
01844 } else {
01845 $sources = true;
01846 }
01847 } else {
01848
01849 $purgeExpired = true;
01850 }
01851 }
01852 if( $purgeExpired ) {
01853 Title::purgeExpiredRestrictions();
01854 }
01855
01856 wfProfileOut( __METHOD__ );
01857
01858 if ( $get_pages ) {
01859 $this->mCascadeSources = $sources;
01860 $this->mCascadingRestrictions = $pagerestrictions;
01861 } else {
01862 $this->mHasCascadingRestrictions = $sources;
01863 }
01864 return array( $sources, $pagerestrictions );
01865 }
01866
01867 function areRestrictionsCascading() {
01868 if (!$this->mRestrictionsLoaded) {
01869 $this->loadRestrictions();
01870 }
01871
01872 return $this->mCascadeRestriction;
01873 }
01874
01879 private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
01880 $rows = array();
01881 $dbr = wfGetDB( DB_SLAVE );
01882
01883 while( $row = $dbr->fetchObject( $res ) ) {
01884 $rows[] = $row;
01885 }
01886
01887 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
01888 }
01889
01890 public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
01891 $dbr = wfGetDB( DB_SLAVE );
01892
01893 $restrictionTypes = $this->getRestrictionTypes();
01894
01895 foreach( $restrictionTypes as $type ){
01896 $this->mRestrictions[$type] = array();
01897 $this->mRestrictionsExpiry[$type] = Block::decodeExpiry('');
01898 }
01899
01900 $this->mCascadeRestriction = false;
01901
01902 # Backwards-compatibility: also load the restrictions from the page record (old format).
01903
01904 if ( $oldFashionedRestrictions === null ) {
01905 $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
01906 array( 'page_id' => $this->getArticleId() ), __METHOD__ );
01907 }
01908
01909 if ($oldFashionedRestrictions != '') {
01910
01911 foreach( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
01912 $temp = explode( '=', trim( $restrict ) );
01913 if(count($temp) == 1) {
01914
01915 $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
01916 $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
01917 } else {
01918 $this->mRestrictions[$temp[0]] = explode( ',', trim( $temp[1] ) );
01919 }
01920 }
01921
01922 $this->mOldRestrictions = true;
01923
01924 }
01925
01926 if( count($rows) ) {
01927 # Current system - load second to make them override.
01928 $now = wfTimestampNow();
01929 $purgeExpired = false;
01930
01931 foreach( $rows as $row ) {
01932 # Cycle through all the restrictions.
01933
01934
01935
01936 if( !in_array( $row->pr_type, $restrictionTypes ) )
01937 continue;
01938
01939
01940
01941 $expiry = Block::decodeExpiry( $row->pr_expiry );
01942
01943
01944 if ( !$expiry || $expiry > $now ) {
01945 $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
01946 $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
01947
01948 $this->mCascadeRestriction |= $row->pr_cascade;
01949 } else {
01950
01951 $purgeExpired = true;
01952 }
01953 }
01954
01955 if( $purgeExpired ) {
01956 Title::purgeExpiredRestrictions();
01957 }
01958 }
01959
01960 $this->mRestrictionsLoaded = true;
01961 }
01962
01966 public function loadRestrictions( $oldFashionedRestrictions = null ) {
01967 if( !$this->mRestrictionsLoaded ) {
01968 if ($this->exists()) {
01969 $dbr = wfGetDB( DB_SLAVE );
01970
01971 $res = $dbr->select( 'page_restrictions', '*',
01972 array ( 'pr_page' => $this->getArticleId() ), __METHOD__ );
01973
01974 $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
01975 } else {
01976 $title_protection = $this->getTitleProtection();
01977
01978 if (is_array($title_protection)) {
01979 extract($title_protection);
01980
01981 $now = wfTimestampNow();
01982 $expiry = Block::decodeExpiry($pt_expiry);
01983
01984 if (!$expiry || $expiry > $now) {
01985
01986 $this->mRestrictionsExpiry['create'] = $expiry;
01987 $this->mRestrictions['create'] = explode(',', trim($pt_create_perm) );
01988 } else {
01989 Title::purgeExpiredRestrictions();
01990 }
01991 } else {
01992 $this->mRestrictionsExpiry['create'] = Block::decodeExpiry('');
01993 }
01994 $this->mRestrictionsLoaded = true;
01995 }
01996 }
01997 }
01998
02002 static function purgeExpiredRestrictions() {
02003 $dbw = wfGetDB( DB_MASTER );
02004 $dbw->delete( 'page_restrictions',
02005 array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02006 __METHOD__ );
02007
02008 $dbw->delete( 'protected_titles',
02009 array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02010 __METHOD__ );
02011 }
02012
02019 public function getRestrictions( $action ) {
02020 if( !$this->mRestrictionsLoaded ) {
02021 $this->loadRestrictions();
02022 }
02023 return isset( $this->mRestrictions[$action] )
02024 ? $this->mRestrictions[$action]
02025 : array();
02026 }
02027
02033 public function getRestrictionExpiry( $action ) {
02034 if( !$this->mRestrictionsLoaded ) {
02035 $this->loadRestrictions();
02036 }
02037 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
02038 }
02039
02044 public function isDeleted() {
02045 if( $this->getNamespace() < 0 ) {
02046 $n = 0;
02047 } else {
02048 $dbr = wfGetDB( DB_SLAVE );
02049 $n = $dbr->selectField( 'archive', 'COUNT(*)',
02050 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02051 __METHOD__
02052 );
02053 if( $this->getNamespace() == NS_FILE ) {
02054 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
02055 array( 'fa_name' => $this->getDBkey() ),
02056 __METHOD__
02057 );
02058 }
02059 }
02060 return (int)$n;
02061 }
02062
02067 public function isDeletedQuick() {
02068 if( $this->getNamespace() < 0 ) {
02069 return false;
02070 }
02071 $dbr = wfGetDB( DB_SLAVE );
02072 $deleted = (bool)$dbr->selectField( 'archive', '1',
02073 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02074 __METHOD__
02075 );
02076 if( !$deleted && $this->getNamespace() == NS_FILE ) {
02077 $deleted = (bool)$dbr->selectField( 'filearchive', '1',
02078 array( 'fa_name' => $this->getDBkey() ),
02079 __METHOD__
02080 );
02081 }
02082 return $deleted;
02083 }
02084
02092 public function getArticleID( $flags = 0 ) {
02093 if( $this->getNamespace() < 0 ) {
02094 return $this->mArticleID = 0;
02095 }
02096 $linkCache = LinkCache::singleton();
02097 if( $flags & GAID_FOR_UPDATE ) {
02098 $oldUpdate = $linkCache->forUpdate( true );
02099 $linkCache->clearLink( $this );
02100 $this->mArticleID = $linkCache->addLinkObj( $this );
02101 $linkCache->forUpdate( $oldUpdate );
02102 } else {
02103 if( -1 == $this->mArticleID ) {
02104 $this->mArticleID = $linkCache->addLinkObj( $this );
02105 }
02106 }
02107 return $this->mArticleID;
02108 }
02109
02116 public function isRedirect( $flags = 0 ) {
02117 if( !is_null($this->mRedirect) )
02118 return $this->mRedirect;
02119 # Calling getArticleID() loads the field from cache as needed
02120 if( !$this->getArticleID($flags) ) {
02121 return $this->mRedirect = false;
02122 }
02123 $linkCache = LinkCache::singleton();
02124 $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
02125
02126 return $this->mRedirect;
02127 }
02128
02135 public function getLength( $flags = 0 ) {
02136 if( $this->mLength != -1 )
02137 return $this->mLength;
02138 # Calling getArticleID() loads the field from cache as needed
02139 if( !$this->getArticleID($flags) ) {
02140 return $this->mLength = 0;
02141 }
02142 $linkCache = LinkCache::singleton();
02143 $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
02144
02145 return $this->mLength;
02146 }
02147
02153 public function getLatestRevID( $flags = 0 ) {
02154 if( $this->mLatestID !== false )
02155 return $this->mLatestID;
02156
02157 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_SLAVE);
02158 $this->mLatestID = $db->selectField( 'page', 'page_latest', $this->pageCond(), __METHOD__ );
02159 return $this->mLatestID;
02160 }
02161
02172 public function resetArticleID( $newid ) {
02173 $linkCache = LinkCache::singleton();
02174 $linkCache->clearBadLink( $this->getPrefixedDBkey() );
02175
02176 if ( $newid === false ) { $this->mArticleID = -1; }
02177 else { $this->mArticleID = intval( $newid ); }
02178 $this->mRestrictionsLoaded = false;
02179 $this->mRestrictions = array();
02180 }
02181
02186 public function invalidateCache() {
02187 if( wfReadOnly() ) {
02188 return;
02189 }
02190 $dbw = wfGetDB( DB_MASTER );
02191 $success = $dbw->update( 'page',
02192 array( 'page_touched' => $dbw->timestamp() ),
02193 $this->pageCond(),
02194 __METHOD__
02195 );
02196 HTMLFileCache::clearFileCache( $this );
02197 return $success;
02198 }
02199
02208 function prefix( $name ) {
02209 $p = '';
02210 if ( $this->mInterwiki != '' ) {
02211 $p = $this->mInterwiki . ':';
02212 }
02213 if ( 0 != $this->mNamespace ) {
02214 $p .= $this->getNsText() . ':';
02215 }
02216 return $p . $name;
02217 }
02218
02219
02220
02221
02222 static function getTitleInvalidRegex() {
02223 static $rxTc = false;
02224 if( !$rxTc ) {
02225 # Matching titles will be held as illegal.
02226 $rxTc = '/' .
02227 # Any character not allowed is forbidden...
02228 '[^' . Title::legalChars() . ']' .
02229 # URL percent encoding sequences interfere with the ability
02230 # to round-trip titles -- you can't link to them consistently.
02231 '|%[0-9A-Fa-f]{2}' .
02232 # XML/HTML character references produce similar issues.
02233 '|&[A-Za-z0-9\x80-\xff]+;' .
02234 '|&#[0-9]+;' .
02235 '|&#x[0-9A-Fa-f]+;' .
02236 '/S';
02237 }
02238
02239 return $rxTc;
02240 }
02241
02245 public static function capitalize( $text, $ns = NS_MAIN ) {
02246 global $wgContLang;
02247
02248 if ( MWNamespace::isCapitalized( $ns ) )
02249 return $wgContLang->ucfirst( $text );
02250 else
02251 return $text;
02252 }
02253
02264 private function secureAndSplit() {
02265 global $wgContLang, $wgLocalInterwiki;
02266
02267 # Initialisation
02268 $rxTc = self::getTitleInvalidRegex();
02269
02270 $this->mInterwiki = $this->mFragment = '';
02271 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
02272
02273 $dbkey = $this->mDbkeyform;
02274
02275 # Strip Unicode bidi override characters.
02276 # Sometimes they slip into cut-n-pasted page titles, where the
02277 # override chars get included in list displays.
02278 $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
02279
02280 # Clean up whitespace
02281 # Note: use of the /u option on preg_replace here will cause
02282 # input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x,
02283 # conveniently disabling them.
02284 #
02285 $dbkey = preg_replace( '/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u', '_', $dbkey );
02286 $dbkey = trim( $dbkey, '_' );
02287
02288 if ( $dbkey == '' ) {
02289 return false;
02290 }
02291
02292 if( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
02293 # Contained illegal UTF-8 sequences or forbidden Unicode chars.
02294 return false;
02295 }
02296
02297 $this->mDbkeyform = $dbkey;
02298
02299 # Initial colon indicates main namespace rather than specified default
02300 # but should not create invalid {ns,title} pairs such as {0,Project:Foo}
02301 if ( ':' == $dbkey{0} ) {
02302 $this->mNamespace = NS_MAIN;
02303 $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
02304 $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
02305 }
02306
02307 # Namespace or interwiki prefix
02308 $firstPass = true;
02309 $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
02310 do {
02311 $m = array();
02312 if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
02313 $p = $m[1];
02314 if ( $ns = $wgContLang->getNsIndex( $p ) ) {
02315 # Ordinary namespace
02316 $dbkey = $m[2];
02317 $this->mNamespace = $ns;
02318 # For Talk:X pages, check if X has a "namespace" prefix
02319 if( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
02320 if( $wgContLang->getNsIndex( $x[1] ) )
02321 return false; # Disallow Talk:File:x type titles...
02322 else if( Interwiki::isValidInterwiki( $x[1] ) )
02323 return false; # Disallow Talk:Interwiki:x type titles...
02324 }
02325 } elseif( Interwiki::isValidInterwiki( $p ) ) {
02326 if( !$firstPass ) {
02327 # Can't make a local interwiki link to an interwiki link.
02328 # That's just crazy!
02329 return false;
02330 }
02331
02332 # Interwiki link
02333 $dbkey = $m[2];
02334 $this->mInterwiki = $wgContLang->lc( $p );
02335
02336 # Redundant interwiki prefix to the local wiki
02337 if ( 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) {
02338 if( $dbkey == '' ) {
02339 # Can't have an empty self-link
02340 return false;
02341 }
02342 $this->mInterwiki = '';
02343 $firstPass = false;
02344 # Do another namespace split...
02345 continue;
02346 }
02347
02348 # If there's an initial colon after the interwiki, that also
02349 # resets the default namespace
02350 if ( $dbkey !== '' && $dbkey[0] == ':' ) {
02351 $this->mNamespace = NS_MAIN;
02352 $dbkey = substr( $dbkey, 1 );
02353 }
02354 }
02355 # If there's no recognized interwiki or namespace,
02356 # then let the colon expression be part of the title.
02357 }
02358 break;
02359 } while( true );
02360
02361 # We already know that some pages won't be in the database!
02362 #
02363 if ( $this->mInterwiki != '' || NS_SPECIAL == $this->mNamespace ) {
02364 $this->mArticleID = 0;
02365 }
02366 $fragment = strstr( $dbkey, '#' );
02367 if ( false !== $fragment ) {
02368 $this->setFragment( $fragment );
02369 $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
02370 # remove whitespace again: prevents "Foo_bar_#"
02371 # becoming "Foo_bar_"
02372 $dbkey = preg_replace( '/_*$/', '', $dbkey );
02373 }
02374
02375 # Reject illegal characters.
02376 #
02377 if( preg_match( $rxTc, $dbkey ) ) {
02378 return false;
02379 }
02380
02386 if ( strpos( $dbkey, '.' ) !== false &&
02387 ( $dbkey === '.' || $dbkey === '..' ||
02388 strpos( $dbkey, './' ) === 0 ||
02389 strpos( $dbkey, '../' ) === 0 ||
02390 strpos( $dbkey, '/./' ) !== false ||
02391 strpos( $dbkey, '/../' ) !== false ||
02392 substr( $dbkey, -2 ) == '/.' ||
02393 substr( $dbkey, -3 ) == '/..' ) )
02394 {
02395 return false;
02396 }
02397
02401 if( strpos( $dbkey, '~~~' ) !== false ) {
02402 return false;
02403 }
02404
02412 if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
02413 strlen( $dbkey ) > 512 )
02414 {
02415 return false;
02416 }
02417
02426 $this->mUserCaseDBKey = $dbkey;
02427 if( $this->mInterwiki == '') {
02428 $dbkey = self::capitalize( $dbkey, $this->mNamespace );
02429 }
02430
02436 if( $dbkey == '' &&
02437 $this->mInterwiki == '' &&
02438 $this->mNamespace != NS_MAIN ) {
02439 return false;
02440 }
02441
02442
02443
02444
02445
02446
02447 $dbkey = ($this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK) ?
02448 IP::sanitizeIP( $dbkey ) : $dbkey;
02449
02450 if ( $dbkey !== '' && ':' == $dbkey{0} ) {
02451 return false;
02452 }
02453
02454 # Fill fields
02455 $this->mDbkeyform = $dbkey;
02456 $this->mUrlform = wfUrlencode( $dbkey );
02457
02458 $this->mTextform = str_replace( '_', ' ', $dbkey );
02459
02460 return true;
02461 }
02462
02473 public function setFragment( $fragment ) {
02474 $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
02475 }
02476
02481 public function getTalkPage() {
02482 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
02483 }
02484
02491 public function getSubjectPage() {
02492
02493 $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
02494 if( $this->getNamespace() == $subjectNS ) {
02495 return $this;
02496 }
02497 return Title::makeTitle( $subjectNS, $this->getDBkey() );
02498 }
02499
02510 public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
02511 $linkCache = LinkCache::singleton();
02512
02513 if ( count( $options ) > 0 ) {
02514 $db = wfGetDB( DB_MASTER );
02515 } else {
02516 $db = wfGetDB( DB_SLAVE );
02517 }
02518
02519 $res = $db->select( array( 'page', $table ),
02520 array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect' ),
02521 array(
02522 "{$prefix}_from=page_id",
02523 "{$prefix}_namespace" => $this->getNamespace(),
02524 "{$prefix}_title" => $this->getDBkey() ),
02525 __METHOD__,
02526 $options );
02527
02528 $retVal = array();
02529 if ( $db->numRows( $res ) ) {
02530 foreach( $res as $row ) {
02531 if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) {
02532 $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect );
02533 $retVal[] = $titleObj;
02534 }
02535 }
02536 }
02537 $db->freeResult( $res );
02538 return $retVal;
02539 }
02540
02551 public function getTemplateLinksTo( $options = array() ) {
02552 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
02553 }
02554
02561 public function getBrokenLinksFrom() {
02562 if ( $this->getArticleId() == 0 ) {
02563 # All links from article ID 0 are false positives
02564 return array();
02565 }
02566
02567 $dbr = wfGetDB( DB_SLAVE );
02568 $res = $dbr->select(
02569 array( 'page', 'pagelinks' ),
02570 array( 'pl_namespace', 'pl_title' ),
02571 array(
02572 'pl_from' => $this->getArticleId(),
02573 'page_namespace IS NULL'
02574 ),
02575 __METHOD__, array(),
02576 array(
02577 'page' => array(
02578 'LEFT JOIN',
02579 array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
02580 )
02581 )
02582 );
02583
02584 $retVal = array();
02585 foreach( $res as $row ) {
02586 $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
02587 }
02588 return $retVal;
02589 }
02590
02591
02598 public function getSquidURLs() {
02599 global $wgContLang;
02600
02601 $urls = array(
02602 $this->getInternalURL(),
02603 $this->getInternalURL( 'action=history' )
02604 );
02605
02606
02607 if($wgContLang->hasVariants()){
02608 $variants = $wgContLang->getVariants();
02609 foreach ( $variants as $vCode ) {
02610 $urls[] = $this->getInternalURL( '', $vCode );
02611 }
02612 }
02613
02614 return $urls;
02615 }
02616
02620 public function purgeSquid() {
02621 global $wgUseSquid;
02622 if ( $wgUseSquid ) {
02623 $urls = $this->getSquidURLs();
02624 $u = new SquidUpdate( $urls );
02625 $u->doUpdate();
02626 }
02627 }
02628
02633 public function moveNoAuth( &$nt ) {
02634 return $this->moveTo( $nt, false );
02635 }
02636
02646 public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
02647 global $wgUser;
02648
02649 $errors = array();
02650 if( !$nt ) {
02651
02652
02653 return array(array('badtitletext'));
02654 }
02655 if( $this->equals( $nt ) ) {
02656 $errors[] = array('selfmove');
02657 }
02658 if( !$this->isMovable() ) {
02659 $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
02660 }
02661 if ( $nt->getInterwiki() != '' ) {
02662 $errors[] = array( 'immobile-target-namespace-iw' );
02663 }
02664 if ( !$nt->isMovable() ) {
02665 $errors[] = array('immobile-target-namespace', $nt->getNsText() );
02666 }
02667
02668 $oldid = $this->getArticleID();
02669 $newid = $nt->getArticleID();
02670
02671 if ( strlen( $nt->getDBkey() ) < 1 ) {
02672 $errors[] = array('articleexists');
02673 }
02674 if ( ( $this->getDBkey() == '' ) ||
02675 ( !$oldid ) ||
02676 ( $nt->getDBkey() == '' ) ) {
02677 $errors[] = array('badarticleerror');
02678 }
02679
02680
02681 if( $this->getNamespace() == NS_FILE ) {
02682 $file = wfLocalFile( $this );
02683 if( $file->exists() ) {
02684 if( $nt->getNamespace() != NS_FILE ) {
02685 $errors[] = array('imagenocrossnamespace');
02686 }
02687 if( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
02688 $errors[] = array('imageinvalidfilename');
02689 }
02690 if( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
02691 $errors[] = array('imagetypemismatch');
02692 }
02693 }
02694 $destfile = wfLocalFile( $nt );
02695 if( !$wgUser->isAllowed( 'reupload-shared' ) && !$destfile->exists() && wfFindFile( $nt ) ) {
02696 $errors[] = array( 'file-exists-sharedrepo' );
02697 }
02698
02699 }
02700
02701 if ( $auth ) {
02702 $errors = wfMergeErrorArrays( $errors,
02703 $this->getUserPermissionsErrors('move', $wgUser),
02704 $this->getUserPermissionsErrors('edit', $wgUser),
02705 $nt->getUserPermissionsErrors('move-target', $wgUser),
02706 $nt->getUserPermissionsErrors('edit', $wgUser) );
02707 }
02708
02709 $match = EditPage::matchSummarySpamRegex( $reason );
02710 if( $match !== false ) {
02711
02712 $errors[] = array('spamprotectiontext');
02713 }
02714
02715 $err = null;
02716 if( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
02717 $errors[] = array('hookaborted', $err);
02718 }
02719
02720 # The move is allowed only if (1) the target doesn't exist, or
02721 # (2) the target is a redirect to the source, and has no history
02722 # (so we can undo bad moves right after they're done).
02723
02724 if ( 0 != $newid ) { # Target exists; check for validity
02725 if ( ! $this->isValidMoveTarget( $nt ) ) {
02726 $errors[] = array('articleexists');
02727 }
02728 } else {
02729 $tp = $nt->getTitleProtection();
02730 $right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm'];
02731 if ( $tp and !$wgUser->isAllowed( $right ) ) {
02732 $errors[] = array('cantmove-titleprotected');
02733 }
02734 }
02735 if(empty($errors))
02736 return true;
02737 return $errors;
02738 }
02739
02750 public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
02751 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
02752 if( is_array( $err ) ) {
02753 return $err;
02754 }
02755
02756
02757 $dbw = wfGetDB( DB_MASTER );
02758 if( $this->getNamespace() == NS_FILE ) {
02759 $file = wfLocalFile( $this );
02760 if( $file->exists() ) {
02761 $status = $file->move( $nt );
02762 if( !$status->isOk() ) {
02763 return $status->getErrorsArray();
02764 }
02765 }
02766 }
02767
02768 $pageid = $this->getArticleID();
02769 $protected = $this->isProtected();
02770 if( $nt->exists() ) {
02771 $err = $this->moveOverExistingRedirect( $nt, $reason, $createRedirect );
02772 $pageCountChange = ($createRedirect ? 0 : -1);
02773 } else { # Target didn't exist, do normal move.
02774 $err = $this->moveToNewTitle( $nt, $reason, $createRedirect );
02775 $pageCountChange = ($createRedirect ? 1 : 0);
02776 }
02777
02778 if( is_array( $err ) ) {
02779 return $err;
02780 }
02781 $redirid = $this->getArticleID();
02782
02783 // Category memberships include a sort key which may be customized.
02784 // If it's left as the default (the page title), we need to update
02785
02786
02787
02788
02789
02790
02791
02792
02793
02794 $dbw->update( 'categorylinks',
02795 array(
02796 'cl_sortkey' => $nt->getPrefixedText(),
02797 'cl_timestamp=cl_timestamp' ),
02798 array(
02799 'cl_from' => $pageid,
02800 'cl_sortkey' => $this->getPrefixedText() ),
02801 __METHOD__ );
02802
02803 if( $protected ) {
02804 # Protect the redirect title as the title used to be...
02805 $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
02806 array(
02807 'pr_page' => $redirid,
02808 'pr_type' => 'pr_type',
02809 'pr_level' => 'pr_level',
02810 'pr_cascade' => 'pr_cascade',
02811 'pr_user' => 'pr_user',
02812 'pr_expiry' => 'pr_expiry'
02813 ),
02814 array( 'pr_page' => $pageid ),
02815 __METHOD__,
02816 array( 'IGNORE' )
02817 );
02818 # Update the protection log
02819 $log = new LogPage( 'protect' );
02820 $comment = wfMsgForContent( 'prot_1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
02821 if( $reason ) $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
02822 $log->addEntry( 'move_prot', $nt, $comment, array($this->getPrefixedText()) );
02823 }
02824
02825 # Update watchlists
02826 $oldnamespace = $this->getNamespace() & ~1;
02827 $newnamespace = $nt->getNamespace() & ~1;
02828 $oldtitle = $this->getDBkey();
02829 $newtitle = $nt->getDBkey();
02830
02831 if( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
02832 WatchedItem::duplicateEntries( $this, $nt );
02833 }
02834
02835 # Update search engine
02836 $u = new SearchUpdate( $pageid, $nt->getPrefixedDBkey() );
02837 $u->doUpdate();
02838 $u = new SearchUpdate( $redirid, $this->getPrefixedDBkey(), '' );
02839 $u->doUpdate();
02840
02841 # Update site_stats
02842 if( $this->isContentPage() && !$nt->isContentPage() ) {
02843 # No longer a content page
02844 # Not viewed, edited, removing
02845 $u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange );
02846 } elseif( !$this->isContentPage() && $nt->isContentPage() ) {
02847 # Now a content page
02848 # Not viewed, edited, adding
02849 $u = new SiteStatsUpdate( 0, 1, +1, $pageCountChange );
02850 } elseif( $pageCountChange ) {
02851 # Redirect added
02852 $u = new SiteStatsUpdate( 0, 0, 0, 1 );
02853 } else {
02854 # Nothing special
02855 $u = false;
02856 }
02857 if( $u )
02858 $u->doUpdate();
02859 # Update message cache for interface messages
02860 if( $nt->getNamespace() == NS_MEDIAWIKI ) {
02861 global $wgMessageCache;
02862
02863 # @bug 17860: old article can be deleted, if this the case,
02864 # delete it from message cache
02865 if ( $this->getArticleID() === 0 ) {
02866 $wgMessageCache->replace( $this->getDBkey(), false );
02867 } else {
02868 $oldarticle = new Article( $this );
02869 $wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() );
02870 }
02871
02872 $newarticle = new Article( $nt );
02873 $wgMessageCache->replace( $nt->getDBkey(), $newarticle->getContent() );
02874 }
02875
02876 global $wgUser;
02877 wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
02878 return true;
02879 }
02880
02891 private function moveOverExistingRedirect( &$nt, $reason = '', $createRedirect = true ) {
02892 global $wgUseSquid, $wgUser, $wgContLang;
02893
02894 $comment = wfMsgForContent( '1movedto2_redir', $this->getPrefixedText(), $nt->getPrefixedText() );
02895
02896 if ( $reason ) {
02897 $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
02898 }
02899 # Truncate for whole multibyte characters. +5 bytes for ellipsis
02900 $comment = $wgContLang->truncate( $comment, 250 );
02901
02902 $now = wfTimestampNow();
02903 $newid = $nt->getArticleID();
02904 $oldid = $this->getArticleID();
02905 $latest = $this->getLatestRevID();
02906
02907 $dbw = wfGetDB( DB_MASTER );
02908
02909 $rcts = $dbw->timestamp( $nt->getEarliestRevTime() );
02910 $newns = $nt->getNamespace();
02911 $newdbk = $nt->getDBkey();
02912
02913 # Delete the old redirect. We don't save it to history since
02914 # by definition if we've got here it's rather uninteresting.
02915 # We have to remove it so that the next step doesn't trigger
02916 # a conflict on the unique namespace+title index...
02917 $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
02918 if ( !$dbw->cascadingDeletes() ) {
02919 $dbw->delete( 'revision', array( 'rev_page' => $newid ), __METHOD__ );
02920 global $wgUseTrackbacks;
02921 if ($wgUseTrackbacks)
02922 $dbw->delete( 'trackbacks', array( 'tb_page' => $newid ), __METHOD__ );
02923 $dbw->delete( 'pagelinks', array( 'pl_from' => $newid ), __METHOD__ );
02924 $dbw->delete( 'imagelinks', array( 'il_from' => $newid ), __METHOD__ );
02925 $dbw->delete( 'categorylinks', array( 'cl_from' => $newid ), __METHOD__ );
02926 $dbw->delete( 'templatelinks', array( 'tl_from' => $newid ), __METHOD__ );
02927 $dbw->delete( 'externallinks', array( 'el_from' => $newid ), __METHOD__ );
02928 $dbw->delete( 'langlinks', array( 'll_from' => $newid ), __METHOD__ );
02929 $dbw->delete( 'redirect', array( 'rd_from' => $newid ), __METHOD__ );
02930 }
02931
02932 $dbw->delete( 'recentchanges',
02933 array( 'rc_timestamp' => $rcts, 'rc_namespace' => $newns, 'rc_title' => $newdbk, 'rc_new' => 1 ),
02934 __METHOD__
02935 );
02936
02937 # Save a null revision in the page's history notifying of the move
02938 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
02939 $nullRevId = $nullRevision->insertOn( $dbw );
02940
02941 $article = new Article( $this );
02942 wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
02943
02944 # Change the name of the target page:
02945 $dbw->update( 'page',
02946 array(
02947 'page_touched' => $dbw->timestamp($now),
02948 'page_namespace' => $nt->getNamespace(),
02949 'page_title' => $nt->getDBkey(),
02950 'page_latest' => $nullRevId,
02951 ),
02952 array( 'page_id' => $oldid ),
02953 __METHOD__
02954 );
02955 $nt->resetArticleID( $oldid );
02956
02957 # Recreate the redirect, this time in the other direction.
02958 if( $createRedirect || !$wgUser->isAllowed('suppressredirect') ) {
02959 $mwRedir = MagicWord::get( 'redirect' );
02960 $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
02961 $redirectArticle = new Article( $this );
02962 $newid = $redirectArticle->insertOn( $dbw );
02963 $redirectRevision = new Revision( array(
02964 'page' => $newid,
02965 'comment' => $comment,
02966 'text' => $redirectText ) );
02967 $redirectRevision->insertOn( $dbw );
02968 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
02969
02970 wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser) );
02971
02972 # Now, we record the link from the redirect to the new title.
02973 # It should have no other outgoing links...
02974 $dbw->delete( 'pagelinks', array( 'pl_from' => $newid ), __METHOD__ );
02975 $dbw->insert( 'pagelinks',
02976 array(
02977 'pl_from' => $newid,
02978 'pl_namespace' => $nt->getNamespace(),
02979 'pl_title' => $nt->getDBkey() ),
02980 __METHOD__ );
02981 $redirectSuppressed = false;
02982 } else {
02983 $this->resetArticleID( 0 );
02984 $redirectSuppressed = true;
02985 }
02986
02987 # Log the move
02988 $log = new LogPage( 'move' );
02989 $log->addEntry( 'move_redir', $this, $reason, array( 1 => $nt->getPrefixedText(), 2 => $redirectSuppressed ) );
02990
02991 # Purge squid
02992 if ( $wgUseSquid ) {
02993 $urls = array_merge( $nt->getSquidURLs(), $this->getSquidURLs() );
02994 $u = new SquidUpdate( $urls );
02995 $u->doUpdate();
02996 }
02997
02998 }
02999
03007 private function moveToNewTitle( &$nt, $reason = '', $createRedirect = true ) {
03008 global $wgUseSquid, $wgUser, $wgContLang;
03009
03010 $comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
03011 if ( $reason ) {
03012 $comment .= wfMsgExt( 'colon-separator',
03013 array( 'escapenoentities', 'content' ) );
03014 $comment .= $reason;
03015 }
03016 # Truncate for whole multibyte characters. +5 bytes for ellipsis
03017 $comment = $wgContLang->truncate( $comment, 250 );
03018
03019 $newid = $nt->getArticleID();
03020 $oldid = $this->getArticleID();
03021 $latest = $this->getLatestRevId();
03022
03023 $dbw = wfGetDB( DB_MASTER );
03024 $now = $dbw->timestamp();
03025
03026 # Save a null revision in the page's history notifying of the move
03027 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
03028 if ( !is_object( $nullRevision ) ) {
03029 throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
03030 }
03031 $nullRevId = $nullRevision->insertOn( $dbw );
03032
03033 $article = new Article( $this );
03034 wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
03035
03036 # Rename page entry
03037 $dbw->update( 'page',
03038 array(
03039 'page_touched' => $now,
03040 'page_namespace' => $nt->getNamespace(),
03041 'page_title' => $nt->getDBkey(),
03042 'page_latest' => $nullRevId,
03043 ),
03044 array( 'page_id' => $oldid ),
03045 __METHOD__
03046 );
03047 $nt->resetArticleID( $oldid );
03048
03049 if( $createRedirect || !$wgUser->isAllowed('suppressredirect') ) {
03050 # Insert redirect
03051 $mwRedir = MagicWord::get( 'redirect' );
03052 $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
03053 $redirectArticle = new Article( $this );
03054 $newid = $redirectArticle->insertOn( $dbw );
03055 $redirectRevision = new Revision( array(
03056 'page' => $newid,
03057 'comment' => $comment,
03058 'text' => $redirectText ) );
03059 $redirectRevision->insertOn( $dbw );
03060 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
03061
03062 wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser) );
03063
03064 # Record the just-created redirect's linking to the page
03065 $dbw->insert( 'pagelinks',
03066 array(
03067 'pl_from' => $newid,
03068 'pl_namespace' => $nt->getNamespace(),
03069 'pl_title' => $nt->getDBkey() ),
03070 __METHOD__ );
03071 $redirectSuppressed = false;
03072 } else {
03073 $this->resetArticleID( 0 );
03074 $redirectSuppressed = true;
03075 }
03076
03077 # Log the move
03078 $log = new LogPage( 'move' );
03079 $log->addEntry( 'move', $this, $reason, array( 1 => $nt->getPrefixedText(), 2 => $redirectSuppressed ) );
03080
03081 # Purge caches as per article creation
03082 Article::onArticleCreate( $nt );
03083
03084 # Purge old title from squid
03085 # The new title, and links to the new title, are purged in Article::onArticleCreate()
03086 $this->purgeSquid();
03087
03088 }
03089
03100 public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
03101 global $wgMaximumMovedPages;
03102
03103 if( !$this->userCan( 'move-subpages' ) )
03104 return array( 'cant-move-subpages' );
03105
03106 if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
03107 return array( 'namespace-nosubpages',
03108 MWNamespace::getCanonicalName( $this->getNamespace() ) );
03109 if( !MWNamespace::hasSubpages( $nt->getNamespace() ) )
03110 return array( 'namespace-nosubpages',
03111 MWNamespace::getCanonicalName( $nt->getNamespace() ) );
03112
03113 $subpages = $this->getSubpages($wgMaximumMovedPages + 1);
03114 $retval = array();
03115 $count = 0;
03116 foreach( $subpages as $oldSubpage ) {
03117 $count++;
03118 if( $count > $wgMaximumMovedPages ) {
03119 $retval[$oldSubpage->getPrefixedTitle()] =
03120 array( 'movepage-max-pages',
03121 $wgMaximumMovedPages );
03122 break;
03123 }
03124
03125
03126
03127
03128 if( $oldSubpage->getArticleId() == $this->getArticleId() ||
03129 $oldSubpage->getArticleID() == $nt->getArticleId() )
03130
03131
03132 continue;
03133 $newPageName = preg_replace(
03134 '#^'.preg_quote( $this->getDBkey(), '#' ).'#',
03135 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
03136 $oldSubpage->getDBkey() );
03137 if( $oldSubpage->isTalkPage() ) {
03138 $newNs = $nt->getTalkPage()->getNamespace();
03139 } else {
03140 $newNs = $nt->getSubjectPage()->getNamespace();
03141 }
03142 # Bug 14385: we need makeTitleSafe because the new page names may
03143 # be longer than 255 characters.
03144 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
03145
03146 $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
03147 if( $success === true ) {
03148 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
03149 } else {
03150 $retval[$oldSubpage->getPrefixedText()] = $success;
03151 }
03152 }
03153 return $retval;
03154 }
03155
03162 public function isSingleRevRedirect() {
03163 $dbw = wfGetDB( DB_MASTER );
03164 # Is it a redirect?
03165 $row = $dbw->selectRow( 'page',
03166 array( 'page_is_redirect', 'page_latest', 'page_id' ),
03167 $this->pageCond(),
03168 __METHOD__,
03169 array( 'FOR UPDATE' )
03170 );
03171 # Cache some fields we may want
03172 $this->mArticleID = $row ? intval($row->page_id) : 0;
03173 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
03174 $this->mLatestID = $row ? intval($row->page_latest) : false;
03175 if( !$this->mRedirect ) {
03176 return false;
03177 }
03178 # Does the article have a history?
03179 $row = $dbw->selectField( array( 'page', 'revision'),
03180 'rev_id',
03181 array( 'page_namespace' => $this->getNamespace(),
03182 'page_title' => $this->getDBkey(),
03183 'page_id=rev_page',
03184 'page_latest != rev_id'
03185 ),
03186 __METHOD__,
03187 array( 'FOR UPDATE' )
03188 );
03189 # Return true if there was no history
03190 return ($row === false);
03191 }
03192
03200 public function isValidMoveTarget( $nt ) {
03201 $dbw = wfGetDB( DB_MASTER );
03202 # Is it an existsing file?
03203 if( $nt->getNamespace() == NS_FILE ) {
03204 $file = wfLocalFile( $nt );
03205 if( $file->exists() ) {
03206 wfDebug( __METHOD__ . ": file exists\n" );
03207 return false;
03208 }
03209 }
03210 # Is it a redirect with no history?
03211 if( !$nt->isSingleRevRedirect() ) {
03212 wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
03213 return false;
03214 }
03215 # Get the article text
03216 $rev = Revision::newFromTitle( $nt );
03217 $text = $rev->getText();
03218 # Does the redirect point to the source?
03219 # Or is it a broken self-redirect, usually caused by namespace collisions?
03220 $m = array();
03221 if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
03222 $redirTitle = Title::newFromText( $m[1] );
03223 if( !is_object( $redirTitle ) ||
03224 ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
03225 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
03226 wfDebug( __METHOD__ . ": redirect points to other page\n" );
03227 return false;
03228 }
03229 } else {
03230 # Fail safe
03231 wfDebug( __METHOD__ . ": failsafe\n" );
03232 return false;
03233 }
03234 return true;
03235 }
03236
03242 public function isWatchable() {
03243 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
03244 }
03245
03253 public function getParentCategories() {
03254 global $wgContLang;
03255
03256 $titlekey = $this->getArticleId();
03257 $dbr = wfGetDB( DB_SLAVE );
03258 $categorylinks = $dbr->tableName( 'categorylinks' );
03259
03260 # NEW SQL
03261 $sql = "SELECT * FROM $categorylinks"
03262 ." WHERE cl_from='$titlekey'"
03263 ." AND cl_from <> '0'"
03264 ." ORDER BY cl_sortkey";
03265
03266 $res = $dbr->query( $sql );
03267
03268 if( $dbr->numRows( $res ) > 0 ) {
03269 foreach( $res as $row )
03270
03271 $data[$wgContLang->getNSText( NS_CATEGORY ).':'.$row->cl_to] = $this->getFullText();
03272 $dbr->freeResult( $res );
03273 } else {
03274 $data = array();
03275 }
03276 return $data;
03277 }
03278
03284 public function getParentCategoryTree( $children = array() ) {
03285 $stack = array();
03286 $parents = $this->getParentCategories();
03287
03288 if( $parents ) {
03289 foreach( $parents as $parent => $current ) {
03290 if ( array_key_exists( $parent, $children ) ) {
03291 # Circular reference
03292 $stack[$parent] = array();
03293 } else {
03294 $nt = Title::newFromText($parent);
03295 if ( $nt ) {
03296 $stack[$parent] = $nt->getParentCategoryTree( $children + array($parent => 1) );
03297 }
03298 }
03299 }
03300 return $stack;
03301 } else {
03302 return array();
03303 }
03304 }
03305
03306
03313 public function pageCond() {
03314 if( $this->mArticleID > 0 ) {
03315
03316 return array( 'page_id' => $this->mArticleID );
03317 } else {
03318 return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
03319 }
03320 }
03321
03329 public function getPreviousRevisionID( $revId, $flags=0 ) {
03330 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03331 return $db->selectField( 'revision', 'rev_id',
03332 array(
03333 'rev_page' => $this->getArticleId($flags),
03334 'rev_id < ' . intval( $revId )
03335 ),
03336 __METHOD__,
03337 array( 'ORDER BY' => 'rev_id DESC' )
03338 );
03339 }
03340
03348 public function getNextRevisionID( $revId, $flags=0 ) {
03349 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03350 return $db->selectField( 'revision', 'rev_id',
03351 array(
03352 'rev_page' => $this->getArticleId($flags),
03353 'rev_id > ' . intval( $revId )
03354 ),
03355 __METHOD__,
03356 array( 'ORDER BY' => 'rev_id' )
03357 );
03358 }
03359
03366 public function getFirstRevision( $flags=0 ) {
03367 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03368 $pageId = $this->getArticleId($flags);
03369 if( !$pageId ) return null;
03370 $row = $db->selectRow( 'revision', '*',
03371 array( 'rev_page' => $pageId ),
03372 __METHOD__,
03373 array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
03374 );
03375 if( !$row ) {
03376 return null;
03377 } else {
03378 return new Revision( $row );
03379 }
03380 }
03381
03387 public function isNewPage() {
03388 $dbr = wfGetDB( DB_SLAVE );
03389 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
03390 }
03391
03397 public function getEarliestRevTime() {
03398 $dbr = wfGetDB( DB_SLAVE );
03399 if( $this->exists() ) {
03400 $min = $dbr->selectField( 'revision',
03401 'MIN(rev_timestamp)',
03402 array( 'rev_page' => $this->getArticleId() ),
03403 __METHOD__ );
03404 return wfTimestampOrNull( TS_MW, $min );
03405 }
03406 return null;
03407 }
03408
03417 public function countRevisionsBetween( $old, $new ) {
03418 $dbr = wfGetDB( DB_SLAVE );
03419 return (int)$dbr->selectField( 'revision', 'count(*)',
03420 'rev_page = ' . intval( $this->getArticleId() ) .
03421 ' AND rev_id > ' . intval( $old ) .
03422 ' AND rev_id < ' . intval( $new ),
03423 __METHOD__
03424 );
03425 }
03426
03433 public function equals( Title $title ) {
03434
03435 return $this->getInterwiki() === $title->getInterwiki()
03436 && $this->getNamespace() == $title->getNamespace()
03437 && $this->getDBkey() === $title->getDBkey();
03438 }
03439
03443 public static function compare( $a, $b ) {
03444 if( $a->getNamespace() == $b->getNamespace() ) {
03445 return strcmp( $a->getText(), $b->getText() );
03446 } else {
03447 return $a->getNamespace() - $b->getNamespace();
03448 }
03449 }
03450
03456 public function __toString() {
03457 return $this->getPrefixedText();
03458 }
03459
03469 public function exists() {
03470 return $this->getArticleId() != 0;
03471 }
03472
03489 public function isAlwaysKnown() {
03490 if( $this->mInterwiki != '' ) {
03491 return true;
03492 }
03493 switch( $this->mNamespace ) {
03494 case NS_MEDIA:
03495 case NS_FILE:
03496 return wfFindFile( $this );
03497 case NS_SPECIAL:
03498 return SpecialPage::exists( $this->getDBkey() );
03499 case NS_MAIN:
03500 return $this->mDbkeyform == '';
03501 case NS_MEDIAWIKI:
03502
03503
03504
03505
03506
03507 list( $basename, ) = explode( '/', $this->mDbkeyform, 2 );
03508 return wfMsgWeirdKey( $basename );
03509 default:
03510 return false;
03511 }
03512 }
03513
03522 public function isKnown() {
03523 return $this->exists() || $this->isAlwaysKnown();
03524 }
03525
03531 public function canExist() {
03532 return $this->mNamespace >= 0 && $this->mNamespace != NS_MEDIA;
03533 }
03534
03540 public function touchLinks() {
03541 $u = new HTMLCacheUpdate( $this, 'pagelinks' );
03542 $u->doUpdate();
03543
03544 if ( $this->getNamespace() == NS_CATEGORY ) {
03545 $u = new HTMLCacheUpdate( $this, 'categorylinks' );
03546 $u->doUpdate();
03547 }
03548 }
03549
03555 public function getTouched( $db = null ) {
03556 $db = isset($db) ? $db : wfGetDB( DB_SLAVE );
03557 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
03558 return $touched;
03559 }
03560
03566 public function getNotificationTimestamp( $user = null ) {
03567 global $wgUser, $wgShowUpdatedMarker;
03568
03569 if( !$user ) $user = $wgUser;
03570
03571 $uid = $user->getId();
03572 if( isset($this->mNotificationTimestamp[$uid]) ) {
03573 return $this->mNotificationTimestamp[$uid];
03574 }
03575 if( !$uid || !$wgShowUpdatedMarker ) {
03576 return $this->mNotificationTimestamp[$uid] = false;
03577 }
03578
03579 if( count($this->mNotificationTimestamp) >= self::CACHE_MAX ) {
03580 $this->mNotificationTimestamp = array();
03581 }
03582 $dbr = wfGetDB( DB_SLAVE );
03583 $this->mNotificationTimestamp[$uid] = $dbr->selectField( 'watchlist',
03584 'wl_notificationtimestamp',
03585 array( 'wl_namespace' => $this->getNamespace(),
03586 'wl_title' => $this->getDBkey(),
03587 'wl_user' => $user->getId()
03588 ),
03589 __METHOD__
03590 );
03591 return $this->mNotificationTimestamp[$uid];
03592 }
03593
03598 public function trackbackURL() {
03599 global $wgScriptPath, $wgServer, $wgScriptExtension;
03600
03601 return "$wgServer$wgScriptPath/trackback$wgScriptExtension?article="
03602 . htmlspecialchars(urlencode($this->getPrefixedDBkey()));
03603 }
03604
03609 public function trackbackRDF() {
03610 $url = htmlspecialchars($this->getFullURL());
03611 $title = htmlspecialchars($this->getText());
03612 $tburl = $this->trackbackURL();
03613
03614
03615
03616
03617
03618
03619 return "<!--
03620 <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"
03621 xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
03622 xmlns:trackback=\"http://madskills.com/public/xml/rss/module/trackback/\">
03623 <rdf:Description
03624 rdf:about=\"$url\"
03625 dc:identifier=\"$url\"
03626 dc:title=\"$title\"
03627 trackback:ping=\"$tburl\" />
03628 </rdf:RDF>
03629 -->";
03630 }
03631
03636 public function getNamespaceKey( $prepend = 'nstab-' ) {
03637 global $wgContLang;
03638
03639 $namespace = MWNamespace::getSubject( $this->getNamespace() );
03640
03641 if ( MWNamespace::exists( $this->getNamespace() ) ) {
03642
03643 $namespaceKey = MWNamespace::getCanonicalName( $namespace );
03644 } else {
03645
03646 $namespaceKey = $this->getSubjectNsText();
03647 }
03648
03649 $namespaceKey = $wgContLang->lc( $namespaceKey );
03650
03651 if ( $namespaceKey == '' ) {
03652 $namespaceKey = 'main';
03653 }
03654
03655 if ( $namespaceKey == 'file' ) {
03656 $namespaceKey = 'image';
03657 }
03658 return $prepend . $namespaceKey;
03659 }
03660
03664 public function isSpecialPage( ) {
03665 return $this->getNamespace() == NS_SPECIAL;
03666 }
03667
03672 public function isSpecial( $name ) {
03673 if ( $this->getNamespace() == NS_SPECIAL ) {
03674 list( $thisName, ) = SpecialPage::resolveAliasWithSubpage( $this->getDBkey() );
03675 if ( $name == $thisName ) {
03676 return true;
03677 }
03678 }
03679 return false;
03680 }
03681
03686 public function fixSpecialName() {
03687 if ( $this->getNamespace() == NS_SPECIAL ) {
03688 $canonicalName = SpecialPage::resolveAlias( $this->mDbkeyform );
03689 if ( $canonicalName ) {
03690 $localName = SpecialPage::getLocalNameFor( $canonicalName );
03691 if ( $localName != $this->mDbkeyform ) {
03692 return Title::makeTitle( NS_SPECIAL, $localName );
03693 }
03694 }
03695 }
03696 return $this;
03697 }
03698
03706 public function isContentPage() {
03707 return MWNamespace::isContent( $this->getNamespace() );
03708 }
03709
03717 public function getRedirectsHere( $ns = null ) {
03718 $redirs = array();
03719
03720 $dbr = wfGetDB( DB_SLAVE );
03721 $where = array(
03722 'rd_namespace' => $this->getNamespace(),
03723 'rd_title' => $this->getDBkey(),
03724 'rd_from = page_id'
03725 );
03726 if ( !is_null($ns) ) $where['page_namespace'] = $ns;
03727
03728 $res = $dbr->select(
03729 array( 'redirect', 'page' ),
03730 array( 'page_namespace', 'page_title' ),
03731 $where,
03732 __METHOD__
03733 );
03734
03735
03736 foreach( $res as $row ) {
03737 $redirs[] = self::newFromRow( $row );
03738 }
03739 return $redirs;
03740 }
03741
03747 public function isValidRedirectTarget() {
03748 global $wgInvalidRedirectTargets;
03749
03750
03751 if( $this->isSpecial( 'Userlogout' ) ) {
03752 return false;
03753 }
03754
03755 foreach( $wgInvalidRedirectTargets as $target ) {
03756 if( $this->isSpecial( $target ) ) {
03757 return false;
03758 }
03759 }
03760
03761 return true;
03762 }
03763
03767 function getBacklinkCache() {
03768 if ( is_null( $this->mBacklinkCache ) ) {
03769 $this->mBacklinkCache = new BacklinkCache( $this );
03770 }
03771 return $this->mBacklinkCache;
03772 }
03773
03779 public function canUseNoindex(){
03780 global $wgArticleRobotPolicies, $wgContentNamespaces,
03781 $wgExemptFromUserRobotsControl;
03782
03783 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
03784 ? $wgContentNamespaces
03785 : $wgExemptFromUserRobotsControl;
03786
03787 return !in_array( $this->mNamespace, $bannedNamespaces );
03788
03789 }
03790
03791 public function getRestrictionTypes() {
03792 global $wgRestrictionTypes;
03793 $types = $this->exists() ? $wgRestrictionTypes : array('create');
03794
03795 if ( $this->getNamespace() == NS_FILE ) {
03796 $types[] = 'upload';
03797 }
03798
03799 wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
03800
03801 return $types;
03802 }
03803 }