00001 <?php
00007 if ( !class_exists( 'UtfNormal' ) ) {
00008 require_once( dirname(__FILE__) . '/normal/UtfNormal.php' );
00009 }
00010
00011 define ( 'GAID_FOR_UPDATE', 1 );
00012
00013
00017 define( 'CASCADE', 1 );
00018
00025 class Title {
00028 static private $titleCache=array();
00029 static private $interwikiCache=array();
00031
00037 const CACHE_MAX = 1000;
00038
00039
00046
00047 var $mTextform = '';
00048 var $mUrlform = '';
00049 var $mDbkeyform = '';
00050 var $mUserCaseDBKey;
00051 var $mNamespace = NS_MAIN;
00052 var $mInterwiki = '';
00053 var $mFragment;
00054 var $mArticleID = -1;
00055 var $mLatestID = false;
00056 var $mRestrictions = array();
00057 var $mOldRestrictions = false;
00058 var $mCascadeRestriction;
00059 var $mRestrictionsExpiry = array();
00060 var $mHasCascadingRestrictions;
00061 var $mCascadeSources;
00062 var $mRestrictionsLoaded = false;
00063 var $mPrefixedText;
00064 # Don't change the following default, NS_MAIN is hardcoded in several
00065 # places. See bug 696.
00066 var $mDefaultNamespace = NS_MAIN;
00067 # Zero except in {{transclusion}} tags
00068 var $mWatched = null;
00069 var $mLength = -1;
00070 var $mRedirect = null;
00071 var $mNotificationTimestamp = array();
00072 var $mBacklinkCache = null;
00073
00074
00075
00080 function __construct() {}
00081
00089 public static function newFromDBkey( $key ) {
00090 $t = new Title();
00091 $t->mDbkeyform = $key;
00092 if( $t->secureAndSplit() )
00093 return $t;
00094 else
00095 return NULL;
00096 }
00097
00110 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
00111 if( is_object( $text ) ) {
00112 throw new MWException( 'Title::newFromText given an object' );
00113 }
00114
00123 if( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) {
00124 return Title::$titleCache[$text];
00125 }
00126
00130 $filteredText = Sanitizer::decodeCharReferences( $text );
00131
00132 $t = new Title();
00133 $t->mDbkeyform = str_replace( ' ', '_', $filteredText );
00134 $t->mDefaultNamespace = $defaultNamespace;
00135
00136 static $cachedcount = 0 ;
00137 if( $t->secureAndSplit() ) {
00138 if( $defaultNamespace == NS_MAIN ) {
00139 if( $cachedcount >= self::CACHE_MAX ) {
00140 # Avoid memory leaks on mass operations...
00141 Title::$titleCache = array();
00142 $cachedcount=0;
00143 }
00144 $cachedcount++;
00145 Title::$titleCache[$text] =& $t;
00146 }
00147 return $t;
00148 } else {
00149 $ret = NULL;
00150 return $ret;
00151 }
00152 }
00153
00160 public static function newFromURL( $url ) {
00161 global $wgLegalTitleChars;
00162 $t = new Title();
00163
00164 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
00165 # but some URLs used it as a space replacement and they still come
00166 # from some external search tools.
00167 if ( strpos( $wgLegalTitleChars, '+' ) === false ) {
00168 $url = str_replace( '+', ' ', $url );
00169 }
00170
00171 $t->mDbkeyform = str_replace( ' ', '_', $url );
00172 if( $t->secureAndSplit() ) {
00173 return $t;
00174 } else {
00175 return NULL;
00176 }
00177 }
00178
00189 public static function newFromID( $id, $flags = 0 ) {
00190 $fname = 'Title::newFromID';
00191 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
00192 $row = $db->selectRow( 'page', array( 'page_namespace', 'page_title' ),
00193 array( 'page_id' => $id ), $fname );
00194 if ( $row !== false ) {
00195 $title = Title::makeTitle( $row->page_namespace, $row->page_title );
00196 } else {
00197 $title = NULL;
00198 }
00199 return $title;
00200 }
00201
00207 public static function newFromIDs( $ids ) {
00208 if ( !count( $ids ) ) {
00209 return array();
00210 }
00211 $dbr = wfGetDB( DB_SLAVE );
00212 $res = $dbr->select( 'page', array( 'page_namespace', 'page_title' ),
00213 'page_id IN (' . $dbr->makeList( $ids ) . ')', __METHOD__ );
00214
00215 $titles = array();
00216 foreach( $res as $row ) {
00217 $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
00218 }
00219 return $titles;
00220 }
00221
00227 public static function newFromRow( $row ) {
00228 $t = self::makeTitle( $row->page_namespace, $row->page_title );
00229
00230 $t->mArticleID = isset($row->page_id) ? intval($row->page_id) : -1;
00231 $t->mLength = isset($row->page_len) ? intval($row->page_len) : -1;
00232 $t->mRedirect = isset($row->page_is_redirect) ? (bool)$row->page_is_redirect : NULL;
00233 $t->mLatestID = isset($row->page_latest) ? $row->page_latest : false;
00234
00235 return $t;
00236 }
00237
00250 public static function &makeTitle( $ns, $title, $fragment = '' ) {
00251 $t = new Title();
00252 $t->mInterwiki = '';
00253 $t->mFragment = $fragment;
00254 $t->mNamespace = $ns = intval( $ns );
00255 $t->mDbkeyform = str_replace( ' ', '_', $title );
00256 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
00257 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
00258 $t->mTextform = str_replace( '_', ' ', $title );
00259 return $t;
00260 }
00261
00272 public static function makeTitleSafe( $ns, $title, $fragment = '' ) {
00273 $t = new Title();
00274 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment );
00275 if( $t->secureAndSplit() ) {
00276 return $t;
00277 } else {
00278 return NULL;
00279 }
00280 }
00281
00286 public static function newMainPage() {
00287 $title = Title::newFromText( wfMsgForContent( 'mainpage' ) );
00288
00289 if ( !$title ) {
00290 $title = Title::newFromText( 'Main Page' );
00291 }
00292 return $title;
00293 }
00294
00304 public static function newFromRedirect( $text ) {
00305 return self::newFromRedirectInternal( $text );
00306 }
00307
00317 public static function newFromRedirectRecurse( $text ) {
00318 $titles = self::newFromRedirectArray( $text );
00319 return $titles ? array_pop( $titles ) : null;
00320 }
00321
00331 public static function newFromRedirectArray( $text ) {
00332 global $wgMaxRedirects;
00333
00334 if( $wgMaxRedirects < 1 )
00335 return null;
00336 $title = self::newFromRedirectInternal( $text );
00337 if( is_null( $title ) )
00338 return null;
00339
00340 $recurse = $wgMaxRedirects;
00341 $titles = array( $title );
00342 while( --$recurse > 0 ) {
00343 if( $title->isRedirect() ) {
00344 $article = new Article( $title, 0 );
00345 $newtitle = $article->getRedirectTarget();
00346 } else {
00347 break;
00348 }
00349
00350 if( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
00351
00352 $title = $newtitle;
00353 $titles[] = $newtitle;
00354 } else {
00355 break;
00356 }
00357 }
00358 return $titles;
00359 }
00360
00368 protected static function newFromRedirectInternal( $text ) {
00369 $redir = MagicWord::get( 'redirect' );
00370 $text = trim($text);
00371 if( $redir->matchStartAndRemove( $text ) ) {
00372
00373
00374
00375 $m = array();
00376 if( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
00377
00378
00379 if( strpos( $m[1], '%' ) !== false ) {
00380
00381
00382
00383 $m[1] = urldecode( ltrim( $m[1], ':' ) );
00384 }
00385 $title = Title::newFromText( $m[1] );
00386
00387 if( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
00388 return null;
00389 }
00390 return $title;
00391 }
00392 }
00393 return null;
00394 }
00395
00396 #----------------------------------------------------------------------------
00397 # Static functions
00398 #----------------------------------------------------------------------------
00399
00406 public static function nameOf( $id ) {
00407 $dbr = wfGetDB( DB_SLAVE );
00408
00409 $s = $dbr->selectRow( 'page',
00410 array( 'page_namespace','page_title' ),
00411 array( 'page_id' => $id ),
00412 __METHOD__ );
00413 if ( $s === false ) { return NULL; }
00414
00415 $n = self::makeName( $s->page_namespace, $s->page_title );
00416 return $n;
00417 }
00418
00423 public static function legalChars() {
00424 global $wgLegalTitleChars;
00425 return $wgLegalTitleChars;
00426 }
00427
00437 public static function indexTitle( $ns, $title ) {
00438 global $wgContLang;
00439
00440 $lc = SearchEngine::legalSearchChars() . '&#;';
00441 $t = $wgContLang->stripForSearch( $title );
00442 $t = preg_replace( "/[^{$lc}]+/", ' ', $t );
00443 $t = $wgContLang->lc( $t );
00444
00445 # Handle 's, s'
00446 $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t );
00447 $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t );
00448
00449 $t = preg_replace( "/\\s+/", ' ', $t );
00450
00451 if ( $ns == NS_FILE ) {
00452 $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t );
00453 }
00454 return trim( $t );
00455 }
00456
00457
00458
00459
00460
00461
00462
00463
00464 public static function makeName( $ns, $title, $fragment = '' ) {
00465 global $wgContLang;
00466
00467 $namespace = $wgContLang->getNsText( $ns );
00468 $name = $namespace == '' ? $title : "$namespace:$title";
00469 if ( strval( $fragment ) != '' ) {
00470 $name .= '#' . $fragment;
00471 }
00472 return $name;
00473 }
00474
00483 public function getInterwikiLink( $key ) {
00484 return Interwiki::fetch( $key )->getURL( );
00485 }
00486
00494 public function isLocal() {
00495 if ( $this->mInterwiki != '' ) {
00496 return Interwiki::fetch( $this->mInterwiki )->isLocal();
00497 } else {
00498 return true;
00499 }
00500 }
00501
00508 public function isTrans() {
00509 if ($this->mInterwiki == '')
00510 return false;
00511
00512 return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
00513 }
00514
00518 static function escapeFragmentForURL( $fragment ) {
00519 global $wgEnforceHtmlIds;
00520 # Note that we don't urlencode the fragment. urlencoded Unicode
00521 # fragments appear not to work in IE (at least up to 7) or in at least
00522 # one version of Opera 9.x. The W3C validator, for one, doesn't seem
00523 # to care if they aren't encoded.
00524 return Sanitizer::escapeId( $fragment,
00525 $wgEnforceHtmlIds ? 'noninitial' : 'xml' );
00526 }
00527
00528 #----------------------------------------------------------------------------
00529 # Other stuff
00530 #----------------------------------------------------------------------------
00531
00537 public function getText() { return $this->mTextform; }
00542 public function getPartialURL() { return $this->mUrlform; }
00547 public function getDBkey() { return $this->mDbkeyform; }
00552 public function getNamespace() { return $this->mNamespace; }
00557 public function getNsText() {
00558 global $wgContLang, $wgCanonicalNamespaceNames;
00559
00560 if ( '' != $this->mInterwiki ) {
00561
00562
00563
00564
00565
00566
00567 if( isset( $wgCanonicalNamespaceNames[$this->mNamespace] ) ) {
00568 return $wgCanonicalNamespaceNames[$this->mNamespace];
00569 }
00570 }
00571 return $wgContLang->getNsText( $this->mNamespace );
00572 }
00577 function getUserCaseDBKey() {
00578 return $this->mUserCaseDBKey;
00579 }
00584 public function getSubjectNsText() {
00585 global $wgContLang;
00586 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
00587 }
00592 public function getTalkNsText() {
00593 global $wgContLang;
00594 return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) );
00595 }
00600 public function canTalk() {
00601 return( MWNamespace::canTalk( $this->mNamespace ) );
00602 }
00607 public function getInterwiki() { return $this->mInterwiki; }
00612 public function getFragment() { return $this->mFragment; }
00617 public function getFragmentForURL() {
00618 if ( $this->mFragment == '' ) {
00619 return '';
00620 } else {
00621 return '#' . Title::escapeFragmentForURL( $this->mFragment );
00622 }
00623 }
00628 public function getDefaultNamespace() { return $this->mDefaultNamespace; }
00629
00635 public function getIndexTitle() {
00636 return Title::indexTitle( $this->mNamespace, $this->mTextform );
00637 }
00638
00644 public function getPrefixedDBkey() {
00645 $s = $this->prefix( $this->mDbkeyform );
00646 $s = str_replace( ' ', '_', $s );
00647 return $s;
00648 }
00649
00655 public function getPrefixedText() {
00656 if ( empty( $this->mPrefixedText ) ) {
00657 $s = $this->prefix( $this->mTextform );
00658 $s = str_replace( '_', ' ', $s );
00659 $this->mPrefixedText = $s;
00660 }
00661 return $this->mPrefixedText;
00662 }
00663
00670 public function getFullText() {
00671 $text = $this->getPrefixedText();
00672 if( '' != $this->mFragment ) {
00673 $text .= '#' . $this->mFragment;
00674 }
00675 return $text;
00676 }
00677
00682 public function getBaseText() {
00683 if( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
00684 return $this->getText();
00685 }
00686
00687 $parts = explode( '/', $this->getText() );
00688 # Don't discard the real title if there's no subpage involved
00689 if( count( $parts ) > 1 )
00690 unset( $parts[ count( $parts ) - 1 ] );
00691 return implode( '/', $parts );
00692 }
00693
00698 public function getSubpageText() {
00699 if( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
00700 return( $this->mTextform );
00701 }
00702 $parts = explode( '/', $this->mTextform );
00703 return( $parts[ count( $parts ) - 1 ] );
00704 }
00705
00710 public function getSubpageUrlForm() {
00711 $text = $this->getSubpageText();
00712 $text = wfUrlencode( str_replace( ' ', '_', $text ) );
00713 return( $text );
00714 }
00715
00720 public function getPrefixedURL() {
00721 $s = $this->prefix( $this->mDbkeyform );
00722 $s = wfUrlencode( str_replace( ' ', '_', $s ) );
00723 return $s;
00724 }
00725
00736 public function getFullURL( $query = '', $variant = false ) {
00737 global $wgContLang, $wgServer, $wgRequest;
00738
00739 if( is_array( $query ) ) {
00740 $query = wfArrayToCGI( $query );
00741 }
00742
00743 $interwiki = Interwiki::fetch( $this->mInterwiki );
00744 if ( !$interwiki ) {
00745 $url = $this->getLocalUrl( $query, $variant );
00746
00747
00748
00749 if ($wgRequest->getVal('action') != 'render') {
00750 $url = $wgServer . $url;
00751 }
00752 } else {
00753 $baseUrl = $interwiki->getURL( );
00754
00755 $namespace = wfUrlencode( $this->getNsText() );
00756 if ( '' != $namespace ) {
00757 # Can this actually happen? Interwikis shouldn't be parsed.
00758 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
00759 $namespace .= ':';
00760 }
00761 $url = str_replace( '$1', $namespace . $this->mUrlform, $baseUrl );
00762 $url = wfAppendQuery( $url, $query );
00763 }
00764
00765 # Finally, add the fragment.
00766 $url .= $this->getFragmentForURL();
00767
00768 wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
00769 return $url;
00770 }
00771
00782 public function getLocalURL( $query = '', $variant = false ) {
00783 global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
00784 global $wgVariantArticlePath, $wgContLang, $wgUser;
00785
00786 if( is_array( $query ) ) {
00787 $query = wfArrayToCGI( $query );
00788 }
00789
00790
00791 if($variant == false && $wgContLang->hasVariants() && !$wgUser->isLoggedIn()){
00792 $pref = $wgContLang->getPreferredVariant(false);
00793 if($pref != $wgContLang->getCode())
00794 $variant = $pref;
00795 }
00796
00797 if ( $this->isExternal() ) {
00798 $url = $this->getFullURL();
00799 if ( $query ) {
00800
00801
00802
00803
00804 $url .= "?$query";
00805 }
00806 } else {
00807 $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
00808 if ( $query == '' ) {
00809 if( $variant != false && $wgContLang->hasVariants() ) {
00810 if( $wgVariantArticlePath == false ) {
00811 $variantArticlePath = "$wgScript?title=$1&variant=$2";
00812 } else {
00813 $variantArticlePath = $wgVariantArticlePath;
00814 }
00815 $url = str_replace( '$2', urlencode( $variant ), $variantArticlePath );
00816 $url = str_replace( '$1', $dbkey, $url );
00817 } else {
00818 $url = str_replace( '$1', $dbkey, $wgArticlePath );
00819 }
00820 } else {
00821 global $wgActionPaths;
00822 $url = false;
00823 $matches = array();
00824 if( !empty( $wgActionPaths ) &&
00825 preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
00826 {
00827 $action = urldecode( $matches[2] );
00828 if( isset( $wgActionPaths[$action] ) ) {
00829 $query = $matches[1];
00830 if( isset( $matches[4] ) ) $query .= $matches[4];
00831 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
00832 if( $query != '' ) {
00833 $url = wfAppendQuery( $url, $query );
00834 }
00835 }
00836 }
00837 if ( $url === false ) {
00838 if ( $query == '-' ) {
00839 $query = '';
00840 }
00841 $url = "{$wgScript}?title={$dbkey}&{$query}";
00842 }
00843 }
00844
00845
00846
00847 if ($wgRequest->getVal('action') == 'render') {
00848 $url = $wgServer . $url;
00849 }
00850 }
00851 wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
00852 return $url;
00853 }
00854
00869 public function getLinkUrl( $query = array(), $variant = false ) {
00870 wfProfileIn( __METHOD__ );
00871 if( !is_array( $query ) ) {
00872 wfProfileOut( __METHOD__ );
00873 throw new MWException( 'Title::getLinkUrl passed a non-array for '.
00874 '$query' );
00875 }
00876 if( $this->isExternal() ) {
00877 $ret = $this->getFullURL( $query );
00878 } elseif( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
00879 $ret = $this->getFragmentForURL();
00880 } else {
00881 $ret = $this->getLocalURL( $query, $variant ) . $this->getFragmentForURL();
00882 }
00883 wfProfileOut( __METHOD__ );
00884 return $ret;
00885 }
00886
00893 public function escapeLocalURL( $query = '' ) {
00894 return htmlspecialchars( $this->getLocalURL( $query ) );
00895 }
00896
00904 public function escapeFullURL( $query = '' ) {
00905 return htmlspecialchars( $this->getFullURL( $query ) );
00906 }
00907
00917 public function getInternalURL( $query = '', $variant = false ) {
00918 global $wgInternalServer;
00919 $url = $wgInternalServer . $this->getLocalURL( $query, $variant );
00920 wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
00921 return $url;
00922 }
00923
00929 public function getEditURL() {
00930 if ( '' != $this->mInterwiki ) { return ''; }
00931 $s = $this->getLocalURL( 'action=edit' );
00932
00933 return $s;
00934 }
00935
00941 public function getEscapedText() {
00942 return htmlspecialchars( $this->getPrefixedText() );
00943 }
00944
00949 public function isExternal() { return ( '' != $this->mInterwiki ); }
00950
00957 public function isSemiProtected( $action = 'edit' ) {
00958 if( $this->exists() ) {
00959 $restrictions = $this->getRestrictions( $action );
00960 if( count( $restrictions ) > 0 ) {
00961 foreach( $restrictions as $restriction ) {
00962 if( strtolower( $restriction ) != 'autoconfirmed' )
00963 return false;
00964 }
00965 } else {
00966 # Not protected
00967 return false;
00968 }
00969 return true;
00970 } else {
00971 # If it doesn't exist, it can't be protected
00972 return false;
00973 }
00974 }
00975
00982 public function isProtected( $action = '' ) {
00983 global $wgRestrictionLevels, $wgRestrictionTypes;
00984
00985 # Special pages have inherent protection
00986 if( $this->getNamespace() == NS_SPECIAL )
00987 return true;
00988
00989 # Check regular protection levels
00990 foreach( $wgRestrictionTypes as $type ){
00991 if( $action == $type || $action == '' ) {
00992 $r = $this->getRestrictions( $type );
00993 foreach( $wgRestrictionLevels as $level ) {
00994 if( in_array( $level, $r ) && $level != '' ) {
00995 return true;
00996 }
00997 }
00998 }
00999 }
01000
01001 return false;
01002 }
01003
01008 public function userIsWatching() {
01009 global $wgUser;
01010
01011 if ( is_null( $this->mWatched ) ) {
01012 if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn()) {
01013 $this->mWatched = false;
01014 } else {
01015 $this->mWatched = $wgUser->isWatched( $this );
01016 }
01017 }
01018 return $this->mWatched;
01019 }
01020
01033 public function quickUserCan( $action ) {
01034 return $this->userCan( $action, false );
01035 }
01036
01043 public function isNamespaceProtected() {
01044 global $wgNamespaceProtection, $wgUser;
01045 if( isset( $wgNamespaceProtection[ $this->mNamespace ] ) ) {
01046 foreach( (array)$wgNamespaceProtection[ $this->mNamespace ] as $right ) {
01047 if( $right != '' && !$wgUser->isAllowed( $right ) )
01048 return true;
01049 }
01050 }
01051 return false;
01052 }
01053
01060 public function userCan( $action, $doExpensiveQueries = true ) {
01061 global $wgUser;
01062 return ($this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries, true ) === array());
01063 }
01064
01076 public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
01077 if( !StubObject::isRealObject( $user ) ) {
01078
01079 global $wgUser;
01080 $wgUser->_unstub( '', 5 );
01081 $user = $wgUser;
01082 }
01083 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
01084
01085 global $wgContLang;
01086 global $wgLang;
01087 global $wgEmailConfirmToEdit;
01088
01089 if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' ) {
01090 $errors[] = array( 'confirmedittext' );
01091 }
01092
01093
01094 if ( $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) ) {
01095 $block = $user->mBlock;
01096
01097
01098
01099
01100 $id = $user->blockedBy();
01101 $reason = $user->blockedFor();
01102 if( $reason == '' ) {
01103 $reason = wfMsg( 'blockednoreason' );
01104 }
01105 $ip = wfGetIP();
01106
01107 if ( is_numeric( $id ) ) {
01108 $name = User::whoIs( $id );
01109 } else {
01110 $name = $id;
01111 }
01112
01113 $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
01114 $blockid = $block->mId;
01115 $blockExpiry = $user->mBlock->mExpiry;
01116 $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
01117
01118 if ( $blockExpiry == 'infinity' ) {
01119
01120 $scBlockExpiryOptions = wfMsg( 'ipboptions' );
01121
01122 foreach ( explode( ',', $scBlockExpiryOptions ) as $option ) {
01123 if ( strpos( $option, ':' ) == false )
01124 continue;
01125
01126 list ($show, $value) = explode( ":", $option );
01127
01128 if ( $value == 'infinite' || $value == 'indefinite' ) {
01129 $blockExpiry = $show;
01130 break;
01131 }
01132 }
01133 } else {
01134 $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
01135 }
01136
01137 $intended = $user->mBlock->mAddress;
01138
01139 $errors[] = array( ($block->mAuto ? 'autoblockedtext' : 'blockedtext'), $link, $reason, $ip, $name,
01140 $blockid, $blockExpiry, $intended, $blockTimestamp );
01141 }
01142
01143
01144
01145 foreach( $errors as $index => $error ) {
01146 $error_key = is_array($error) ? $error[0] : $error;
01147
01148 if (in_array( $error_key, $ignoreErrors )) {
01149 unset($errors[$index]);
01150 }
01151 }
01152
01153 return $errors;
01154 }
01155
01167 private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries=true, $short=false ) {
01168 wfProfileIn( __METHOD__ );
01169
01170 $errors = array();
01171
01172
01173 if ( $action == 'move' ) {
01174 if( !$user->isAllowed( 'move-rootuserpages' )
01175 && $this->getNamespace() == NS_USER && !$this->isSubpage() )
01176 {
01177
01178 $errors[] = array( 'cant-move-user-page' );
01179 }
01180
01181
01182 if( $this->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
01183 $errors[] = array( 'movenotallowedfile' );
01184 }
01185
01186 if( !$user->isAllowed( 'move' ) ) {
01187
01188 $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
01189 }
01190 } elseif ( $action == 'create' ) {
01191 if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
01192 ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) )
01193 {
01194 $errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin');
01195 }
01196 } elseif( $action == 'move-target' ) {
01197 if( !$user->isAllowed( 'move' ) ) {
01198
01199 $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
01200 } elseif( !$user->isAllowed( 'move-rootuserpages' )
01201 && $this->getNamespace() == NS_USER && !$this->isSubpage() )
01202 {
01203
01204 $errors[] = array( 'cant-move-to-user-page' );
01205 }
01206 } elseif( !$user->isAllowed( $action ) ) {
01207 $return = null;
01208 $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
01209 User::getGroupsWithPermission( $action ) );
01210 if( $groups ) {
01211 $return = array( 'badaccess-groups',
01212 array( implode( ', ', $groups ), count( $groups ) ) );
01213 } else {
01214 $return = array( "badaccess-group0" );
01215 }
01216 $errors[] = $return;
01217 }
01218
01219 # Short-circuit point
01220 if( $short && count($errors) > 0 ) {
01221 wfProfileOut( __METHOD__ );
01222 return $errors;
01223 }
01224
01225
01226 if( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
01227 wfProfileOut( __METHOD__ );
01228 return $result ? array() : array( array( 'badaccess-group0' ) );
01229 }
01230
01231 if( !wfRunHooks( 'getUserPermissionsErrors', array(&$this,&$user,$action,&$result) ) ) {
01232 if( is_array($result) && count($result) && !is_array($result[0]) )
01233 $errors[] = $result; # A single array representing an error
01234 else if( is_array($result) && is_array($result[0]) )
01235 $errors = array_merge( $errors, $result ); # A nested array representing multiple errors
01236 else if( $result !== '' && is_string($result) )
01237 $errors[] = array($result); # A string representing a message-id
01238 else if( $result === false )
01239 $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
01240 }
01241 # Short-circuit point
01242 if( $short && count($errors) > 0 ) {
01243 wfProfileOut( __METHOD__ );
01244 return $errors;
01245 }
01246
01247 if( $doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array(&$this,&$user,$action,&$result) ) ) {
01248 if( is_array($result) && count($result) && !is_array($result[0]) )
01249 $errors[] = $result; # A single array representing an error
01250 else if( is_array($result) && is_array($result[0]) )
01251 $errors = array_merge( $errors, $result ); # A nested array representing multiple errors
01252 else if( $result !== '' && is_string($result) )
01253 $errors[] = array($result); # A string representing a message-id
01254 else if( $result === false )
01255 $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
01256 }
01257 # Short-circuit point
01258 if( $short && count($errors) > 0 ) {
01259 wfProfileOut( __METHOD__ );
01260 return $errors;
01261 }
01262
01263 # Only 'createaccount' and 'execute' can be performed on
01264 # special pages, which don't actually exist in the DB.
01265 $specialOKActions = array( 'createaccount', 'execute' );
01266 if( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions) ) {
01267 $errors[] = array('ns-specialprotected');
01268 }
01269
01270 # Check $wgNamespaceProtection for restricted namespaces
01271 if( $this->isNamespaceProtected() ) {
01272 $ns = $this->getNamespace() == NS_MAIN ?
01273 wfMsg( 'nstab-main' ) : $this->getNsText();
01274 $errors[] = NS_MEDIAWIKI == $this->mNamespace ?
01275 array('protectedinterface') : array( 'namespaceprotected', $ns );
01276 }
01277
01278 # Protect css/js subpages of user pages
01279 # XXX: this might be better using restrictions
01280 # XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working
01281 if( $this->isCssJsSubpage() && !$user->isAllowed('editusercssjs')
01282 && !preg_match('/^'.preg_quote($user->getName(), '/').'\//', $this->mTextform) )
01283 {
01284 $errors[] = array('customcssjsprotected');
01285 }
01286
01287 # Check against page_restrictions table requirements on this
01288 # page. The user must possess all required rights for this action.
01289 foreach( $this->getRestrictions($action) as $right ) {
01290
01291 if( $right == 'sysop' ) {
01292 $right = 'protect';
01293 }
01294 if( '' != $right && !$user->isAllowed( $right ) ) {
01295
01296 if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
01297
01298
01299 if( $this->mCascadeRestriction ) {
01300 $errors[] = array( 'protectedpagetext', $right );
01301 }
01302 } else {
01303 $errors[] = array( 'protectedpagetext', $right );
01304 }
01305 }
01306 }
01307 # Short-circuit point
01308 if( $short && count($errors) > 0 ) {
01309 wfProfileOut( __METHOD__ );
01310 return $errors;
01311 }
01312
01313 if( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
01314 # We /could/ use the protection level on the source page, but it's fairly ugly
01315 # as we have to establish a precedence hierarchy for pages included by multiple
01316 # cascade-protected pages. So just restrict it to people with 'protect' permission,
01317 # as they could remove the protection anyway.
01318 list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
01319 # Cascading protection depends on more than this page...
01320 # Several cascading protected pages may include this page...
01321 # Check each cascading level
01322 # This is only for protection restrictions, not for all actions
01323 if( $cascadingSources > 0 && isset($restrictions[$action]) ) {
01324 foreach( $restrictions[$action] as $right ) {
01325 $right = ( $right == 'sysop' ) ? 'protect' : $right;
01326 if( '' != $right && !$user->isAllowed( $right ) ) {
01327 $pages = '';
01328 foreach( $cascadingSources as $page )
01329 $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
01330 $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
01331 }
01332 }
01333 }
01334 }
01335 # Short-circuit point
01336 if( $short && count($errors) > 0 ) {
01337 wfProfileOut( __METHOD__ );
01338 return $errors;
01339 }
01340
01341 if( $action == 'protect' ) {
01342 if( $this->getUserPermissionsErrors('edit', $user) != array() ) {
01343 $errors[] = array( 'protect-cantedit' );
01344 }
01345 }
01346
01347 if( $action == 'create' ) {
01348 $title_protection = $this->getTitleProtection();
01349 if( is_array($title_protection) ) {
01350 extract($title_protection);
01351
01352 if( $pt_create_perm == 'sysop' ) {
01353 $pt_create_perm = 'protect';
01354 }
01355 if( $pt_create_perm == '' || !$user->isAllowed($pt_create_perm) ) {
01356 $errors[] = array( 'titleprotected', User::whoIs($pt_user), $pt_reason );
01357 }
01358 }
01359 } elseif( $action == 'move' ) {
01360
01361 if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
01362
01363 $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
01364 } elseif( !$this->isMovable() ) {
01365
01366 $errors[] = array( 'immobile-page' );
01367 }
01368 } elseif( $action == 'move-target' ) {
01369 if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
01370 $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
01371 } elseif( !$this->isMovable() ) {
01372 $errors[] = array( 'immobile-target-page' );
01373 }
01374 }
01375
01376 wfProfileOut( __METHOD__ );
01377 return $errors;
01378 }
01379
01385 private function getTitleProtection() {
01386
01387 if ( $this->getNamespace() < 0 ) {
01388 return false;
01389 }
01390
01391 $dbr = wfGetDB( DB_SLAVE );
01392 $res = $dbr->select( 'protected_titles', '*',
01393 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
01394 __METHOD__ );
01395
01396 if ($row = $dbr->fetchRow( $res )) {
01397 return $row;
01398 } else {
01399 return false;
01400 }
01401 }
01402
01409 public function updateTitleProtection( $create_perm, $reason, $expiry ) {
01410 global $wgUser,$wgContLang;
01411
01412 if ($create_perm == implode(',',$this->getRestrictions('create'))
01413 && $expiry == $this->mRestrictionsExpiry['create']) {
01414
01415 return true;
01416 }
01417
01418 list ($namespace, $title) = array( $this->getNamespace(), $this->getDBkey() );
01419
01420 $dbw = wfGetDB( DB_MASTER );
01421
01422 $encodedExpiry = Block::encodeExpiry($expiry, $dbw );
01423
01424 $expiry_description = '';
01425 if ( $encodedExpiry != 'infinity' ) {
01426 $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ) , $wgContLang->date( $expiry ) , $wgContLang->time( $expiry ) ).')';
01427 }
01428 else {
01429 $expiry_description .= ' (' . wfMsgForContent( 'protect-expiry-indefinite' ).')';
01430 }
01431
01432 # Update protection table
01433 if ($create_perm != '' ) {
01434 $dbw->replace( 'protected_titles', array(array('pt_namespace', 'pt_title')),
01435 array( 'pt_namespace' => $namespace, 'pt_title' => $title
01436 , 'pt_create_perm' => $create_perm
01437 , 'pt_timestamp' => Block::encodeExpiry(wfTimestampNow(), $dbw)
01438 , 'pt_expiry' => $encodedExpiry
01439 , 'pt_user' => $wgUser->getId(), 'pt_reason' => $reason ), __METHOD__ );
01440 } else {
01441 $dbw->delete( 'protected_titles', array( 'pt_namespace' => $namespace,
01442 'pt_title' => $title ), __METHOD__ );
01443 }
01444 # Update the protection log
01445 $log = new LogPage( 'protect' );
01446
01447 if( $create_perm ) {
01448 $params = array("[create=$create_perm] $expiry_description",'');
01449 $log->addEntry( $this->mRestrictions['create'] ? 'modify' : 'protect', $this, trim( $reason ), $params );
01450 } else {
01451 $log->addEntry( 'unprotect', $this, $reason );
01452 }
01453
01454 return true;
01455 }
01456
01460 public function deleteTitleProtection() {
01461 $dbw = wfGetDB( DB_MASTER );
01462
01463 $dbw->delete( 'protected_titles',
01464 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
01465 __METHOD__ );
01466 }
01467
01473 public function userCanEdit( $doExpensiveQueries = true ) {
01474 return $this->userCan( 'edit', $doExpensiveQueries );
01475 }
01476
01482 public function userCanCreate( $doExpensiveQueries = true ) {
01483 return $this->userCan( 'create', $doExpensiveQueries );
01484 }
01485
01491 public function userCanMove( $doExpensiveQueries = true ) {
01492 return $this->userCan( 'move', $doExpensiveQueries );
01493 }
01494
01501 public function isMovable() {
01502 return MWNamespace::isMovable( $this->getNamespace() ) && $this->getInterwiki() == '';
01503 }
01504
01510 public function userCanRead() {
01511 global $wgUser, $wgGroupPermissions;
01512
01513 $result = null;
01514 wfRunHooks( 'userCan', array( &$this, &$wgUser, 'read', &$result ) );
01515 if ( $result !== null ) {
01516 return $result;
01517 }
01518
01519 # Shortcut for public wikis, allows skipping quite a bit of code
01520 if ( !empty( $wgGroupPermissions['*']['read'] ) )
01521 return true;
01522
01523 if( $wgUser->isAllowed( 'read' ) ) {
01524 return true;
01525 } else {
01526 global $wgWhitelistRead;
01527
01532 if( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'Resetpass' ) ) {
01533 return true;
01534 }
01535
01539 if( !is_array($wgWhitelistRead) ) {
01540 return false;
01541 }
01542
01546 $name = $this->getPrefixedText();
01547 $dbName = $this->getPrefixedDBKey();
01548
01549 if( in_array($name,$wgWhitelistRead,true) || in_array($dbName,$wgWhitelistRead,true) )
01550 return true;
01551
01556 if( $this->getNamespace() == NS_MAIN ) {
01557 if( in_array( ':' . $name, $wgWhitelistRead ) )
01558 return true;
01559 }
01560
01565 if( $this->getNamespace() == NS_SPECIAL ) {
01566 $name = $this->getDBkey();
01567 list( $name, ) = SpecialPage::resolveAliasWithSubpage( $name );
01568 if ( $name === false ) {
01569 # Invalid special page, but we show standard login required message
01570 return false;
01571 }
01572
01573 $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
01574 if( in_array( $pure, $wgWhitelistRead, true ) )
01575 return true;
01576 }
01577
01578 }
01579 return false;
01580 }
01581
01586 public function isTalkPage() {
01587 return MWNamespace::isTalk( $this->getNamespace() );
01588 }
01589
01594 public function isSubpage() {
01595 return MWNamespace::hasSubpages( $this->mNamespace )
01596 ? strpos( $this->getText(), '/' ) !== false
01597 : false;
01598 }
01599
01604 public function hasSubpages() {
01605 if( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01606 # Duh
01607 return false;
01608 }
01609
01610 # We dynamically add a member variable for the purpose of this method
01611 # alone to cache the result. There's no point in having it hanging
01612 # around uninitialized in every Title object; therefore we only add it
01613 # if needed and don't declare it statically.
01614 if( isset( $this->mHasSubpages ) ) {
01615 return $this->mHasSubpages;
01616 }
01617
01618 $subpages = $this->getSubpages( 1 );
01619 if( $subpages instanceof TitleArray )
01620 return $this->mHasSubpages = (bool)$subpages->count();
01621 return $this->mHasSubpages = false;
01622 }
01623
01630 public function getSubpages( $limit = -1 ) {
01631 if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
01632 return array();
01633
01634 $dbr = wfGetDB( DB_SLAVE );
01635 $conds['page_namespace'] = $this->getNamespace();
01636 $conds[] = 'page_title LIKE ' . $dbr->addQuotes(
01637 $dbr->escapeLike( $this->getDBkey() ) . '/%' );
01638 $options = array();
01639 if( $limit > -1 )
01640 $options['LIMIT'] = $limit;
01641 return $this->mSubpages = TitleArray::newFromResult(
01642 $dbr->select( 'page',
01643 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
01644 $conds,
01645 __METHOD__,
01646 $options
01647 )
01648 );
01649 }
01650
01657 public function isCssOrJsPage() {
01658 return $this->mNamespace == NS_MEDIAWIKI
01659 && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
01660 }
01661
01666 public function isCssJsSubpage() {
01667 return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
01668 }
01674 public function isValidCssJsSubpage() {
01675 if ( $this->isCssJsSubpage() ) {
01676 $skinNames = Skin::getSkinNames();
01677 return array_key_exists( $this->getSkinFromCssJsSubpage(), $skinNames );
01678 } else {
01679 return false;
01680 }
01681 }
01685 public function getSkinFromCssJsSubpage() {
01686 $subpage = explode( '/', $this->mTextform );
01687 $subpage = $subpage[ count( $subpage ) - 1 ];
01688 return( str_replace( array( '.css', '.js' ), array( '', '' ), $subpage ) );
01689 }
01694 public function isCssSubpage() {
01695 return ( NS_USER == $this->mNamespace && preg_match("/\\/.*\\.css$/", $this->mTextform ) );
01696 }
01701 public function isJsSubpage() {
01702 return ( NS_USER == $this->mNamespace && preg_match("/\\/.*\\.js$/", $this->mTextform ) );
01703 }
01711 public function userCanEditCssJsSubpage() {
01712 global $wgUser;
01713 return ( $wgUser->isAllowed('editusercssjs') || preg_match('/^'.preg_quote($wgUser->getName(), '/').'\//', $this->mTextform) );
01714 }
01715
01721 public function isCascadeProtected() {
01722 list( $sources, ) = $this->getCascadeProtectionSources( false );
01723 return ( $sources > 0 );
01724 }
01725
01734 public function getCascadeProtectionSources( $get_pages = true ) {
01735 global $wgRestrictionTypes;
01736
01737 # Define our dimension of restrictions types
01738 $pagerestrictions = array();
01739 foreach( $wgRestrictionTypes as $action )
01740 $pagerestrictions[$action] = array();
01741
01742 if ( isset( $this->mCascadeSources ) && $get_pages ) {
01743 return array( $this->mCascadeSources, $this->mCascadingRestrictions );
01744 } else if ( isset( $this->mHasCascadingRestrictions ) && !$get_pages ) {
01745 return array( $this->mHasCascadingRestrictions, $pagerestrictions );
01746 }
01747
01748 wfProfileIn( __METHOD__ );
01749
01750 $dbr = wfGetDB( DB_SLAVE );
01751
01752 if ( $this->getNamespace() == NS_FILE ) {
01753 $tables = array ('imagelinks', 'page_restrictions');
01754 $where_clauses = array(
01755 'il_to' => $this->getDBkey(),
01756 'il_from=pr_page',
01757 'pr_cascade' => 1 );
01758 } else {
01759 $tables = array ('templatelinks', 'page_restrictions');
01760 $where_clauses = array(
01761 'tl_namespace' => $this->getNamespace(),
01762 'tl_title' => $this->getDBkey(),
01763 'tl_from=pr_page',
01764 'pr_cascade' => 1 );
01765 }
01766
01767 if ( $get_pages ) {
01768 $cols = array('pr_page', 'page_namespace', 'page_title', 'pr_expiry', 'pr_type', 'pr_level' );
01769 $where_clauses[] = 'page_id=pr_page';
01770 $tables[] = 'page';
01771 } else {
01772 $cols = array( 'pr_expiry' );
01773 }
01774
01775 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
01776
01777 $sources = $get_pages ? array() : false;
01778 $now = wfTimestampNow();
01779 $purgeExpired = false;
01780
01781 foreach( $res as $row ) {
01782 $expiry = Block::decodeExpiry( $row->pr_expiry );
01783 if( $expiry > $now ) {
01784 if ($get_pages) {
01785 $page_id = $row->pr_page;
01786 $page_ns = $row->page_namespace;
01787 $page_title = $row->page_title;
01788 $sources[$page_id] = Title::makeTitle($page_ns, $page_title);
01789 # Add groups needed for each restriction type if its not already there
01790 # Make sure this restriction type still exists
01791 if ( isset($pagerestrictions[$row->pr_type]) && !in_array($row->pr_level, $pagerestrictions[$row->pr_type]) ) {
01792 $pagerestrictions[$row->pr_type][]=$row->pr_level;
01793 }
01794 } else {
01795 $sources = true;
01796 }
01797 } else {
01798
01799 $purgeExpired = true;
01800 }
01801 }
01802 if( $purgeExpired ) {
01803 Title::purgeExpiredRestrictions();
01804 }
01805
01806 wfProfileOut( __METHOD__ );
01807
01808 if ( $get_pages ) {
01809 $this->mCascadeSources = $sources;
01810 $this->mCascadingRestrictions = $pagerestrictions;
01811 } else {
01812 $this->mHasCascadingRestrictions = $sources;
01813 }
01814 return array( $sources, $pagerestrictions );
01815 }
01816
01817 function areRestrictionsCascading() {
01818 if (!$this->mRestrictionsLoaded) {
01819 $this->loadRestrictions();
01820 }
01821
01822 return $this->mCascadeRestriction;
01823 }
01824
01829 private function loadRestrictionsFromRow( $res, $oldFashionedRestrictions = NULL ) {
01830 global $wgRestrictionTypes;
01831 $dbr = wfGetDB( DB_SLAVE );
01832
01833 foreach( $wgRestrictionTypes as $type ){
01834 $this->mRestrictions[$type] = array();
01835 $this->mRestrictionsExpiry[$type] = Block::decodeExpiry('');
01836 }
01837
01838 $this->mCascadeRestriction = false;
01839
01840 # Backwards-compatibility: also load the restrictions from the page record (old format).
01841
01842 if ( $oldFashionedRestrictions === NULL ) {
01843 $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
01844 array( 'page_id' => $this->getArticleId() ), __METHOD__ );
01845 }
01846
01847 if ($oldFashionedRestrictions != '') {
01848
01849 foreach( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
01850 $temp = explode( '=', trim( $restrict ) );
01851 if(count($temp) == 1) {
01852
01853 $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
01854 $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
01855 } else {
01856 $this->mRestrictions[$temp[0]] = explode( ',', trim( $temp[1] ) );
01857 }
01858 }
01859
01860 $this->mOldRestrictions = true;
01861
01862 }
01863
01864 if( $dbr->numRows( $res ) ) {
01865 # Current system - load second to make them override.
01866 $now = wfTimestampNow();
01867 $purgeExpired = false;
01868
01869 foreach( $res as $row ) {
01870 # Cycle through all the restrictions.
01871
01872
01873 if( !in_array( $row->pr_type, $wgRestrictionTypes ) )
01874 continue;
01875
01876
01877
01878 $expiry = Block::decodeExpiry( $row->pr_expiry );
01879
01880
01881 if ( !$expiry || $expiry > $now ) {
01882 $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
01883 $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
01884
01885 $this->mCascadeRestriction |= $row->pr_cascade;
01886 } else {
01887
01888 $purgeExpired = true;
01889 }
01890 }
01891
01892 if( $purgeExpired ) {
01893 Title::purgeExpiredRestrictions();
01894 }
01895 }
01896
01897 $this->mRestrictionsLoaded = true;
01898 }
01899
01903 public function loadRestrictions( $oldFashionedRestrictions = NULL ) {
01904 if( !$this->mRestrictionsLoaded ) {
01905 if ($this->exists()) {
01906 $dbr = wfGetDB( DB_SLAVE );
01907
01908 $res = $dbr->select( 'page_restrictions', '*',
01909 array ( 'pr_page' => $this->getArticleId() ), __METHOD__ );
01910
01911 $this->loadRestrictionsFromRow( $res, $oldFashionedRestrictions );
01912 } else {
01913 $title_protection = $this->getTitleProtection();
01914
01915 if (is_array($title_protection)) {
01916 extract($title_protection);
01917
01918 $now = wfTimestampNow();
01919 $expiry = Block::decodeExpiry($pt_expiry);
01920
01921 if (!$expiry || $expiry > $now) {
01922
01923 $this->mRestrictionsExpiry['create'] = $expiry;
01924 $this->mRestrictions['create'] = explode(',', trim($pt_create_perm) );
01925 } else {
01926 Title::purgeExpiredRestrictions();
01927 }
01928 } else {
01929 $this->mRestrictionsExpiry['create'] = Block::decodeExpiry('');
01930 }
01931 $this->mRestrictionsLoaded = true;
01932 }
01933 }
01934 }
01935
01939 static function purgeExpiredRestrictions() {
01940 $dbw = wfGetDB( DB_MASTER );
01941 $dbw->delete( 'page_restrictions',
01942 array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
01943 __METHOD__ );
01944
01945 $dbw->delete( 'protected_titles',
01946 array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
01947 __METHOD__ );
01948 }
01949
01956 public function getRestrictions( $action ) {
01957 if( !$this->mRestrictionsLoaded ) {
01958 $this->loadRestrictions();
01959 }
01960 return isset( $this->mRestrictions[$action] )
01961 ? $this->mRestrictions[$action]
01962 : array();
01963 }
01964
01970 public function getRestrictionExpiry( $action ) {
01971 if( !$this->mRestrictionsLoaded ) {
01972 $this->loadRestrictions();
01973 }
01974 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
01975 }
01976
01981 public function isDeleted() {
01982 if( $this->getNamespace() < 0 ) {
01983 $n = 0;
01984 } else {
01985 $dbr = wfGetDB( DB_SLAVE );
01986 $n = $dbr->selectField( 'archive', 'COUNT(*)',
01987 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
01988 __METHOD__
01989 );
01990 if( $this->getNamespace() == NS_FILE ) {
01991 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
01992 array( 'fa_name' => $this->getDBkey() ),
01993 __METHOD__
01994 );
01995 }
01996 }
01997 return (int)$n;
01998 }
01999
02004 public function isDeletedQuick() {
02005 if( $this->getNamespace() < 0 ) {
02006 return false;
02007 }
02008 $dbr = wfGetDB( DB_SLAVE );
02009 $deleted = (bool)$dbr->selectField( 'archive', '1',
02010 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02011 __METHOD__
02012 );
02013 if( !$deleted && $this->getNamespace() == NS_FILE ) {
02014 $deleted = (bool)$dbr->selectField( 'filearchive', '1',
02015 array( 'fa_name' => $this->getDBkey() ),
02016 __METHOD__
02017 );
02018 }
02019 return $deleted;
02020 }
02021
02029 public function getArticleID( $flags = 0 ) {
02030 if( $this->getNamespace() < 0 ) {
02031 return $this->mArticleID = 0;
02032 }
02033 $linkCache = LinkCache::singleton();
02034 if( $flags & GAID_FOR_UPDATE ) {
02035 $oldUpdate = $linkCache->forUpdate( true );
02036 $linkCache->clearLink( $this );
02037 $this->mArticleID = $linkCache->addLinkObj( $this );
02038 $linkCache->forUpdate( $oldUpdate );
02039 } else {
02040 if( -1 == $this->mArticleID ) {
02041 $this->mArticleID = $linkCache->addLinkObj( $this );
02042 }
02043 }
02044 return $this->mArticleID;
02045 }
02046
02053 public function isRedirect( $flags = 0 ) {
02054 if( !is_null($this->mRedirect) )
02055 return $this->mRedirect;
02056 # Calling getArticleID() loads the field from cache as needed
02057 if( !$this->getArticleID($flags) ) {
02058 return $this->mRedirect = false;
02059 }
02060 $linkCache = LinkCache::singleton();
02061 $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
02062
02063 return $this->mRedirect;
02064 }
02065
02072 public function getLength( $flags = 0 ) {
02073 if( $this->mLength != -1 )
02074 return $this->mLength;
02075 # Calling getArticleID() loads the field from cache as needed
02076 if( !$this->getArticleID($flags) ) {
02077 return $this->mLength = 0;
02078 }
02079 $linkCache = LinkCache::singleton();
02080 $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
02081
02082 return $this->mLength;
02083 }
02084
02090 public function getLatestRevID( $flags = 0 ) {
02091 if( $this->mLatestID !== false )
02092 return $this->mLatestID;
02093
02094 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_SLAVE);
02095 $this->mLatestID = $db->selectField( 'page', 'page_latest', $this->pageCond(), __METHOD__ );
02096 return $this->mLatestID;
02097 }
02098
02109 public function resetArticleID( $newid ) {
02110 $linkCache = LinkCache::singleton();
02111 $linkCache->clearBadLink( $this->getPrefixedDBkey() );
02112
02113 if ( $newid === false ) { $this->mArticleID = -1; }
02114 else { $this->mArticleID = $newid; }
02115 $this->mRestrictionsLoaded = false;
02116 $this->mRestrictions = array();
02117 }
02118
02123 public function invalidateCache() {
02124 if( wfReadOnly() ) {
02125 return;
02126 }
02127 $dbw = wfGetDB( DB_MASTER );
02128 $success = $dbw->update( 'page',
02129 array( 'page_touched' => $dbw->timestamp() ),
02130 $this->pageCond(),
02131 __METHOD__
02132 );
02133 HTMLFileCache::clearFileCache( $this );
02134 return $success;
02135 }
02136
02145 function prefix( $name ) {
02146 $p = '';
02147 if ( '' != $this->mInterwiki ) {
02148 $p = $this->mInterwiki . ':';
02149 }
02150 if ( 0 != $this->mNamespace ) {
02151 $p .= $this->getNsText() . ':';
02152 }
02153 return $p . $name;
02154 }
02155
02166 private function secureAndSplit() {
02167 global $wgContLang, $wgLocalInterwiki, $wgCapitalLinks;
02168
02169 # Initialisation
02170 static $rxTc = false;
02171 if( !$rxTc ) {
02172 # Matching titles will be held as illegal.
02173 $rxTc = '/' .
02174 # Any character not allowed is forbidden...
02175 '[^' . Title::legalChars() . ']' .
02176 # URL percent encoding sequences interfere with the ability
02177 # to round-trip titles -- you can't link to them consistently.
02178 '|%[0-9A-Fa-f]{2}' .
02179 # XML/HTML character references produce similar issues.
02180 '|&[A-Za-z0-9\x80-\xff]+;' .
02181 '|&#[0-9]+;' .
02182 '|&#x[0-9A-Fa-f]+;' .
02183 '/S';
02184 }
02185
02186 $this->mInterwiki = $this->mFragment = '';
02187 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
02188
02189 $dbkey = $this->mDbkeyform;
02190
02191 # Strip Unicode bidi override characters.
02192 # Sometimes they slip into cut-n-pasted page titles, where the
02193 # override chars get included in list displays.
02194 $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
02195
02196 # Clean up whitespace
02197 #
02198 $dbkey = preg_replace( '/[ _]+/', '_', $dbkey );
02199 $dbkey = trim( $dbkey, '_' );
02200
02201 if ( '' == $dbkey ) {
02202 return false;
02203 }
02204
02205 if( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
02206 # Contained illegal UTF-8 sequences or forbidden Unicode chars.
02207 return false;
02208 }
02209
02210 $this->mDbkeyform = $dbkey;
02211
02212 # Initial colon indicates main namespace rather than specified default
02213 # but should not create invalid {ns,title} pairs such as {0,Project:Foo}
02214 if ( ':' == $dbkey{0} ) {
02215 $this->mNamespace = NS_MAIN;
02216 $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
02217 $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
02218 }
02219
02220 # Namespace or interwiki prefix
02221 $firstPass = true;
02222 $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
02223 do {
02224 $m = array();
02225 if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
02226 $p = $m[1];
02227 if ( $ns = $wgContLang->getNsIndex( $p ) ) {
02228 # Ordinary namespace
02229 $dbkey = $m[2];
02230 $this->mNamespace = $ns;
02231 # For Talk:X pages, check if X has a "namespace" prefix
02232 if( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
02233 if( $wgContLang->getNsIndex( $x[1] ) )
02234 return false; # Disallow Talk:File:x type titles...
02235 else if( Interwiki::isValidInterwiki( $x[1] ) )
02236 return false; # Disallow Talk:Interwiki:x type titles...
02237 }
02238 } elseif( Interwiki::isValidInterwiki( $p ) ) {
02239 if( !$firstPass ) {
02240 # Can't make a local interwiki link to an interwiki link.
02241 # That's just crazy!
02242 return false;
02243 }
02244
02245 # Interwiki link
02246 $dbkey = $m[2];
02247 $this->mInterwiki = $wgContLang->lc( $p );
02248
02249 # Redundant interwiki prefix to the local wiki
02250 if ( 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) {
02251 if( $dbkey == '' ) {
02252 # Can't have an empty self-link
02253 return false;
02254 }
02255 $this->mInterwiki = '';
02256 $firstPass = false;
02257 # Do another namespace split...
02258 continue;
02259 }
02260
02261 # If there's an initial colon after the interwiki, that also
02262 # resets the default namespace
02263 if ( $dbkey !== '' && $dbkey[0] == ':' ) {
02264 $this->mNamespace = NS_MAIN;
02265 $dbkey = substr( $dbkey, 1 );
02266 }
02267 }
02268 # If there's no recognized interwiki or namespace,
02269 # then let the colon expression be part of the title.
02270 }
02271 break;
02272 } while( true );
02273
02274 # We already know that some pages won't be in the database!
02275 #
02276 if ( '' != $this->mInterwiki || NS_SPECIAL == $this->mNamespace ) {
02277 $this->mArticleID = 0;
02278 }
02279 $fragment = strstr( $dbkey, '#' );
02280 if ( false !== $fragment ) {
02281 $this->setFragment( $fragment );
02282 $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
02283 # remove whitespace again: prevents "Foo_bar_#"
02284 # becoming "Foo_bar_"
02285 $dbkey = preg_replace( '/_*$/', '', $dbkey );
02286 }
02287
02288 # Reject illegal characters.
02289 #
02290 if( preg_match( $rxTc, $dbkey ) ) {
02291 return false;
02292 }
02293
02299 if ( strpos( $dbkey, '.' ) !== false &&
02300 ( $dbkey === '.' || $dbkey === '..' ||
02301 strpos( $dbkey, './' ) === 0 ||
02302 strpos( $dbkey, '../' ) === 0 ||
02303 strpos( $dbkey, '/./' ) !== false ||
02304 strpos( $dbkey, '/../' ) !== false ||
02305 substr( $dbkey, -2 ) == '/.' ||
02306 substr( $dbkey, -3 ) == '/..' ) )
02307 {
02308 return false;
02309 }
02310
02314 if( strpos( $dbkey, '~~~' ) !== false ) {
02315 return false;
02316 }
02317
02325 if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
02326 strlen( $dbkey ) > 512 )
02327 {
02328 return false;
02329 }
02330
02339 $this->mUserCaseDBKey = $dbkey;
02340 if( $wgCapitalLinks && $this->mInterwiki == '') {
02341 $dbkey = $wgContLang->ucfirst( $dbkey );
02342 }
02343
02349 if( $dbkey == '' &&
02350 $this->mInterwiki == '' &&
02351 $this->mNamespace != NS_MAIN ) {
02352 return false;
02353 }
02354
02355
02356
02357
02358
02359
02360 $dbkey = ($this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK) ?
02361 IP::sanitizeIP( $dbkey ) : $dbkey;
02362
02363 if ( $dbkey !== '' && ':' == $dbkey{0} ) {
02364 return false;
02365 }
02366
02367 # Fill fields
02368 $this->mDbkeyform = $dbkey;
02369 $this->mUrlform = wfUrlencode( $dbkey );
02370
02371 $this->mTextform = str_replace( '_', ' ', $dbkey );
02372
02373 return true;
02374 }
02375
02386 public function setFragment( $fragment ) {
02387 $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
02388 }
02389
02394 public function getTalkPage() {
02395 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
02396 }
02397
02404 public function getSubjectPage() {
02405
02406 $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
02407 if( $this->getNamespace() == $subjectNS ) {
02408 return $this;
02409 }
02410 return Title::makeTitle( $subjectNS, $this->getDBkey() );
02411 }
02412
02423 public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
02424 $linkCache = LinkCache::singleton();
02425
02426 if ( count( $options ) > 0 ) {
02427 $db = wfGetDB( DB_MASTER );
02428 } else {
02429 $db = wfGetDB( DB_SLAVE );
02430 }
02431
02432 $res = $db->select( array( 'page', $table ),
02433 array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect' ),
02434 array(
02435 "{$prefix}_from=page_id",
02436 "{$prefix}_namespace" => $this->getNamespace(),
02437 "{$prefix}_title" => $this->getDBkey() ),
02438 __METHOD__,
02439 $options );
02440
02441 $retVal = array();
02442 if ( $db->numRows( $res ) ) {
02443 foreach( $res as $row ) {
02444 if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) {
02445 $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect );
02446 $retVal[] = $titleObj;
02447 }
02448 }
02449 }
02450 $db->freeResult( $res );
02451 return $retVal;
02452 }
02453
02464 public function getTemplateLinksTo( $options = array() ) {
02465 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
02466 }
02467
02474 public function getBrokenLinksFrom() {
02475 if ( $this->getArticleId() == 0 ) {
02476 # All links from article ID 0 are false positives
02477 return array();
02478 }
02479
02480 $dbr = wfGetDB( DB_SLAVE );
02481 $res = $dbr->select(
02482 array( 'page', 'pagelinks' ),
02483 array( 'pl_namespace', 'pl_title' ),
02484 array(
02485 'pl_from' => $this->getArticleId(),
02486 'page_namespace IS NULL'
02487 ),
02488 __METHOD__, array(),
02489 array(
02490 'page' => array(
02491 'LEFT JOIN',
02492 array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
02493 )
02494 )
02495 );
02496
02497 $retVal = array();
02498 foreach( $res as $row ) {
02499 $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
02500 }
02501 return $retVal;
02502 }
02503
02504
02511 public function getSquidURLs() {
02512 global $wgContLang;
02513
02514 $urls = array(
02515 $this->getInternalURL(),
02516 $this->getInternalURL( 'action=history' )
02517 );
02518
02519
02520 if($wgContLang->hasVariants()){
02521 $variants = $wgContLang->getVariants();
02522 foreach($variants as $vCode){
02523 if($vCode==$wgContLang->getCode()) continue;
02524 $urls[] = $this->getInternalURL('',$vCode);
02525 }
02526 }
02527
02528 return $urls;
02529 }
02530
02534 public function purgeSquid() {
02535 global $wgUseSquid;
02536 if ( $wgUseSquid ) {
02537 $urls = $this->getSquidURLs();
02538 $u = new SquidUpdate( $urls );
02539 $u->doUpdate();
02540 }
02541 }
02542
02547 public function moveNoAuth( &$nt ) {
02548 return $this->moveTo( $nt, false );
02549 }
02550
02560 public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
02561 global $wgUser;
02562
02563 $errors = array();
02564 if( !$nt ) {
02565
02566
02567 return array(array('badtitletext'));
02568 }
02569 if( $this->equals( $nt ) ) {
02570 $errors[] = array('selfmove');
02571 }
02572 if( !$this->isMovable() ) {
02573 $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
02574 }
02575 if ( $nt->getInterwiki() != '' ) {
02576 $errors[] = array( 'immobile-target-namespace-iw' );
02577 }
02578 if ( !$nt->isMovable() ) {
02579 $errors[] = array('immobile-target-namespace', $nt->getNsText() );
02580 }
02581
02582 $oldid = $this->getArticleID();
02583 $newid = $nt->getArticleID();
02584
02585 if ( strlen( $nt->getDBkey() ) < 1 ) {
02586 $errors[] = array('articleexists');
02587 }
02588 if ( ( '' == $this->getDBkey() ) ||
02589 ( !$oldid ) ||
02590 ( '' == $nt->getDBkey() ) ) {
02591 $errors[] = array('badarticleerror');
02592 }
02593
02594
02595 if( $this->getNamespace() == NS_FILE ) {
02596 $file = wfLocalFile( $this );
02597 if( $file->exists() ) {
02598 if( $nt->getNamespace() != NS_FILE ) {
02599 $errors[] = array('imagenocrossnamespace');
02600 }
02601 if( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
02602 $errors[] = array('imageinvalidfilename');
02603 }
02604 if( !File::checkExtensionCompatibility( $file, $nt->getDBKey() ) ) {
02605 $errors[] = array('imagetypemismatch');
02606 }
02607 }
02608 }
02609
02610 if ( $auth ) {
02611 $errors = wfMergeErrorArrays( $errors,
02612 $this->getUserPermissionsErrors('move', $wgUser),
02613 $this->getUserPermissionsErrors('edit', $wgUser),
02614 $nt->getUserPermissionsErrors('move-target', $wgUser),
02615 $nt->getUserPermissionsErrors('edit', $wgUser) );
02616 }
02617
02618 $match = EditPage::matchSummarySpamRegex( $reason );
02619 if( $match !== false ) {
02620
02621 $errors[] = array('spamprotectiontext');
02622 }
02623
02624 $err = null;
02625 if( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
02626 $errors[] = array('hookaborted', $err);
02627 }
02628
02629 # The move is allowed only if (1) the target doesn't exist, or
02630 # (2) the target is a redirect to the source, and has no history
02631 # (so we can undo bad moves right after they're done).
02632
02633 if ( 0 != $newid ) { # Target exists; check for validity
02634 if ( ! $this->isValidMoveTarget( $nt ) ) {
02635 $errors[] = array('articleexists');
02636 }
02637 } else {
02638 $tp = $nt->getTitleProtection();
02639 $right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm'];
02640 if ( $tp and !$wgUser->isAllowed( $right ) ) {
02641 $errors[] = array('cantmove-titleprotected');
02642 }
02643 }
02644 if(empty($errors))
02645 return true;
02646 return $errors;
02647 }
02648
02659 public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
02660 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
02661 if( is_array( $err ) ) {
02662 return $err;
02663 }
02664
02665 $pageid = $this->getArticleID();
02666 $protected = $this->isProtected();
02667 if( $nt->exists() ) {
02668 $err = $this->moveOverExistingRedirect( $nt, $reason, $createRedirect );
02669 $pageCountChange = ($createRedirect ? 0 : -1);
02670 } else { # Target didn't exist, do normal move.
02671 $err = $this->moveToNewTitle( $nt, $reason, $createRedirect );
02672 $pageCountChange = ($createRedirect ? 1 : 0);
02673 }
02674
02675 if( is_array( $err ) ) {
02676 return $err;
02677 }
02678 $redirid = $this->getArticleID();
02679
02680 // Category memberships include a sort key which may be customized.
02681 // If it's left as the default (the page title), we need to update
02682
02683
02684
02685
02686
02687
02688
02689
02690
02691 $dbw = wfGetDB( DB_MASTER );
02692 $dbw->update( 'categorylinks',
02693 array(
02694 'cl_sortkey' => $nt->getPrefixedText(),
02695 'cl_timestamp=cl_timestamp' ),
02696 array(
02697 'cl_from' => $pageid,
02698 'cl_sortkey' => $this->getPrefixedText() ),
02699 __METHOD__ );
02700
02701 if( $protected ) {
02702 # Protect the redirect title as the title used to be...
02703 $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
02704 array(
02705 'pr_page' => $redirid,
02706 'pr_type' => 'pr_type',
02707 'pr_level' => 'pr_level',
02708 'pr_cascade' => 'pr_cascade',
02709 'pr_user' => 'pr_user',
02710 'pr_expiry' => 'pr_expiry'
02711 ),
02712 array( 'pr_page' => $pageid ),
02713 __METHOD__,
02714 array( 'IGNORE' )
02715 );
02716 # Update the protection log
02717 $log = new LogPage( 'protect' );
02718 $comment = wfMsgForContent( 'prot_1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
02719 if( $reason ) $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
02720 $log->addEntry( 'move_prot', $nt, $comment, array($this->getPrefixedText()) );
02721 }
02722
02723 # Update watchlists
02724 $oldnamespace = $this->getNamespace() & ~1;
02725 $newnamespace = $nt->getNamespace() & ~1;
02726 $oldtitle = $this->getDBkey();
02727 $newtitle = $nt->getDBkey();
02728
02729 if( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
02730 WatchedItem::duplicateEntries( $this, $nt );
02731 }
02732
02733 # Update search engine
02734 $u = new SearchUpdate( $pageid, $nt->getPrefixedDBkey() );
02735 $u->doUpdate();
02736 $u = new SearchUpdate( $redirid, $this->getPrefixedDBkey(), '' );
02737 $u->doUpdate();
02738
02739 # Update site_stats
02740 if( $this->isContentPage() && !$nt->isContentPage() ) {
02741 # No longer a content page
02742 # Not viewed, edited, removing
02743 $u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange );
02744 } elseif( !$this->isContentPage() && $nt->isContentPage() ) {
02745 # Now a content page
02746 # Not viewed, edited, adding
02747 $u = new SiteStatsUpdate( 0, 1, +1, $pageCountChange );
02748 } elseif( $pageCountChange ) {
02749 # Redirect added
02750 $u = new SiteStatsUpdate( 0, 0, 0, 1 );
02751 } else {
02752 # Nothing special
02753 $u = false;
02754 }
02755 if( $u )
02756 $u->doUpdate();
02757 # Update message cache for interface messages
02758 if( $nt->getNamespace() == NS_MEDIAWIKI ) {
02759 global $wgMessageCache;
02760
02761 # @bug 17860: old article can be deleted, if this the case,
02762 # delete it from message cache
02763 if ( $this->getArticleID === 0 ) {
02764 $wgMessageCache->replace( $this->getDBkey(), false );
02765 } else {
02766 $oldarticle = new Article( $this );
02767 $wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() );
02768 }
02769
02770 $newarticle = new Article( $nt );
02771 $wgMessageCache->replace( $nt->getDBkey(), $newarticle->getContent() );
02772 }
02773
02774 global $wgUser;
02775 wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
02776 return true;
02777 }
02778
02789 private function moveOverExistingRedirect( &$nt, $reason = '', $createRedirect = true ) {
02790 global $wgUseSquid, $wgUser;
02791 $fname = 'Title::moveOverExistingRedirect';
02792 $comment = wfMsgForContent( '1movedto2_redir', $this->getPrefixedText(), $nt->getPrefixedText() );
02793
02794 if ( $reason ) {
02795 $comment .= ": $reason";
02796 }
02797
02798 $now = wfTimestampNow();
02799 $newid = $nt->getArticleID();
02800 $oldid = $this->getArticleID();
02801 $latest = $this->getLatestRevID();
02802
02803 $dbw = wfGetDB( DB_MASTER );
02804
02805 # Delete the old redirect. We don't save it to history since
02806 # by definition if we've got here it's rather uninteresting.
02807 # We have to remove it so that the next step doesn't trigger
02808 # a conflict on the unique namespace+title index...
02809 $dbw->delete( 'page', array( 'page_id' => $newid ), $fname );
02810 if ( !$dbw->cascadingDeletes() ) {
02811 $dbw->delete( 'revision', array( 'rev_page' => $newid ), __METHOD__ );
02812 global $wgUseTrackbacks;
02813 if ($wgUseTrackbacks)
02814 $dbw->delete( 'trackbacks', array( 'tb_page' => $newid ), __METHOD__ );
02815 $dbw->delete( 'pagelinks', array( 'pl_from' => $newid ), __METHOD__ );
02816 $dbw->delete( 'imagelinks', array( 'il_from' => $newid ), __METHOD__ );
02817 $dbw->delete( 'categorylinks', array( 'cl_from' => $newid ), __METHOD__ );
02818 $dbw->delete( 'templatelinks', array( 'tl_from' => $newid ), __METHOD__ );
02819 $dbw->delete( 'externallinks', array( 'el_from' => $newid ), __METHOD__ );
02820 $dbw->delete( 'langlinks', array( 'll_from' => $newid ), __METHOD__ );
02821 $dbw->delete( 'redirect', array( 'rd_from' => $newid ), __METHOD__ );
02822 }
02823
02824 # Save a null revision in the page's history notifying of the move
02825 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
02826 $nullRevId = $nullRevision->insertOn( $dbw );
02827
02828 $article = new Article( $this );
02829 wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
02830
02831 # Change the name of the target page:
02832 $dbw->update( 'page',
02833 array(
02834 'page_touched' => $dbw->timestamp($now),
02835 'page_namespace' => $nt->getNamespace(),
02836 'page_title' => $nt->getDBkey(),
02837 'page_latest' => $nullRevId,
02838 ),
02839 array( 'page_id' => $oldid ),
02840 $fname
02841 );
02842 $nt->resetArticleID( $oldid );
02843
02844 # Recreate the redirect, this time in the other direction.
02845 if( $createRedirect || !$wgUser->isAllowed('suppressredirect') ) {
02846 $mwRedir = MagicWord::get( 'redirect' );
02847 $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
02848 $redirectArticle = new Article( $this );
02849 $newid = $redirectArticle->insertOn( $dbw );
02850 $redirectRevision = new Revision( array(
02851 'page' => $newid,
02852 'comment' => $comment,
02853 'text' => $redirectText ) );
02854 $redirectRevision->insertOn( $dbw );
02855 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
02856
02857 wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser) );
02858
02859 # Now, we record the link from the redirect to the new title.
02860 # It should have no other outgoing links...
02861 $dbw->delete( 'pagelinks', array( 'pl_from' => $newid ), $fname );
02862 $dbw->insert( 'pagelinks',
02863 array(
02864 'pl_from' => $newid,
02865 'pl_namespace' => $nt->getNamespace(),
02866 'pl_title' => $nt->getDBkey() ),
02867 $fname );
02868 $redirectSuppressed = false;
02869 } else {
02870 $this->resetArticleID( 0 );
02871 $redirectSuppressed = true;
02872 }
02873
02874 # Move an image if this is a file
02875 if( $this->getNamespace() == NS_FILE ) {
02876 $file = wfLocalFile( $this );
02877 if( $file->exists() ) {
02878 $status = $file->move( $nt );
02879 if( !$status->isOk() ) {
02880 $dbw->rollback();
02881 return $status->getErrorsArray();
02882 }
02883 }
02884 }
02885
02886 # Log the move
02887 $log = new LogPage( 'move' );
02888 $log->addEntry( 'move_redir', $this, $reason, array( 1 => $nt->getPrefixedText(), 2 => $redirectSuppressed ) );
02889
02890 # Purge squid
02891 if ( $wgUseSquid ) {
02892 $urls = array_merge( $nt->getSquidURLs(), $this->getSquidURLs() );
02893 $u = new SquidUpdate( $urls );
02894 $u->doUpdate();
02895 }
02896
02897 }
02898
02906 private function moveToNewTitle( &$nt, $reason = '', $createRedirect = true ) {
02907 global $wgUseSquid, $wgUser;
02908 $fname = 'MovePageForm::moveToNewTitle';
02909 $comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
02910 if ( $reason ) {
02911 $comment .= wfMsgExt( 'colon-separator',
02912 array( 'escapenoentities', 'content' ) );
02913 $comment .= $reason;
02914 }
02915
02916 $newid = $nt->getArticleID();
02917 $oldid = $this->getArticleID();
02918 $latest = $this->getLatestRevId();
02919
02920 $dbw = wfGetDB( DB_MASTER );
02921 $now = $dbw->timestamp();
02922
02923 # Save a null revision in the page's history notifying of the move
02924 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
02925 $nullRevId = $nullRevision->insertOn( $dbw );
02926
02927 $article = new Article( $this );
02928 wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
02929
02930 # Rename page entry
02931 $dbw->update( 'page',
02932 array(
02933 'page_touched' => $now,
02934 'page_namespace' => $nt->getNamespace(),
02935 'page_title' => $nt->getDBkey(),
02936 'page_latest' => $nullRevId,
02937 ),
02938 array( 'page_id' => $oldid ),
02939 $fname
02940 );
02941 $nt->resetArticleID( $oldid );
02942
02943 if( $createRedirect || !$wgUser->isAllowed('suppressredirect') ) {
02944 # Insert redirect
02945 $mwRedir = MagicWord::get( 'redirect' );
02946 $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
02947 $redirectArticle = new Article( $this );
02948 $newid = $redirectArticle->insertOn( $dbw );
02949 $redirectRevision = new Revision( array(
02950 'page' => $newid,
02951 'comment' => $comment,
02952 'text' => $redirectText ) );
02953 $redirectRevision->insertOn( $dbw );
02954 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
02955
02956 wfRunHooks( 'NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser) );
02957
02958 # Record the just-created redirect's linking to the page
02959 $dbw->insert( 'pagelinks',
02960 array(
02961 'pl_from' => $newid,
02962 'pl_namespace' => $nt->getNamespace(),
02963 'pl_title' => $nt->getDBkey() ),
02964 $fname );
02965 $redirectSuppressed = false;
02966 } else {
02967 $this->resetArticleID( 0 );
02968 $redirectSuppressed = true;
02969 }
02970
02971 # Move an image if this is a file
02972 if( $this->getNamespace() == NS_FILE ) {
02973 $file = wfLocalFile( $this );
02974 if( $file->exists() ) {
02975 $status = $file->move( $nt );
02976 if( !$status->isOk() ) {
02977 $dbw->rollback();
02978 return $status->getErrorsArray();
02979 }
02980 }
02981 }
02982
02983 # Log the move
02984 $log = new LogPage( 'move' );
02985 $log->addEntry( 'move', $this, $reason, array( 1 => $nt->getPrefixedText(), 2 => $redirectSuppressed ) );
02986
02987 # Purge caches as per article creation
02988 Article::onArticleCreate( $nt );
02989
02990 # Purge old title from squid
02991 # The new title, and links to the new title, are purged in Article::onArticleCreate()
02992 $this->purgeSquid();
02993
02994 }
02995
03006 public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
03007 global $wgUser, $wgMaximumMovedPages;
03008
03009 if( !$this->userCan( 'move-subpages' ) )
03010 return array( 'cant-move-subpages' );
03011
03012 if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
03013 return array( 'namespace-nosubpages',
03014 MWNamespace::getCanonicalName( $this->getNamespace() ) );
03015 if( !MWNamespace::hasSubpages( $nt->getNamespace() ) )
03016 return array( 'namespace-nosubpages',
03017 MWNamespace::getCanonicalName( $nt->getNamespace() ) );
03018
03019 $subpages = $this->getSubpages($wgMaximumMovedPages + 1);
03020 $retval = array();
03021 $count = 0;
03022 foreach( $subpages as $oldSubpage ) {
03023 $count++;
03024 if( $count > $wgMaximumMovedPages ) {
03025 $retval[$oldSubpage->getPrefixedTitle()] =
03026 array( 'movepage-max-pages',
03027 $wgMaximumMovedPages );
03028 break;
03029 }
03030
03031 if( $oldSubpage->getArticleId() == $this->getArticleId() )
03032
03033
03034 continue;
03035 $newPageName = preg_replace(
03036 '#^'.preg_quote( $this->getDBKey(), '#' ).'#',
03037 $nt->getDBKey(), $oldSubpage->getDBKey() );
03038 if( $oldSubpage->isTalkPage() ) {
03039 $newNs = $nt->getTalkPage()->getNamespace();
03040 } else {
03041 $newNs = $nt->getSubjectPage()->getNamespace();
03042 }
03043 # Bug 14385: we need makeTitleSafe because the new page names may
03044 # be longer than 255 characters.
03045 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
03046
03047 $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
03048 if( $success === true ) {
03049 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
03050 } else {
03051 $retval[$oldSubpage->getPrefixedText()] = $success;
03052 }
03053 }
03054 return $retval;
03055 }
03056
03063 public function isSingleRevRedirect() {
03064 $dbw = wfGetDB( DB_MASTER );
03065 # Is it a redirect?
03066 $row = $dbw->selectRow( 'page',
03067 array( 'page_is_redirect', 'page_latest', 'page_id' ),
03068 $this->pageCond(),
03069 __METHOD__,
03070 array( 'FOR UPDATE' )
03071 );
03072 # Cache some fields we may want
03073 $this->mArticleID = $row ? intval($row->page_id) : 0;
03074 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
03075 $this->mLatestID = $row ? intval($row->page_latest) : false;
03076 if( !$this->mRedirect ) {
03077 return false;
03078 }
03079 # Does the article have a history?
03080 $row = $dbw->selectField( array( 'page', 'revision'),
03081 'rev_id',
03082 array( 'page_namespace' => $this->getNamespace(),
03083 'page_title' => $this->getDBkey(),
03084 'page_id=rev_page',
03085 'page_latest != rev_id'
03086 ),
03087 __METHOD__,
03088 array( 'FOR UPDATE' )
03089 );
03090 # Return true if there was no history
03091 return ($row === false);
03092 }
03093
03101 public function isValidMoveTarget( $nt ) {
03102 $dbw = wfGetDB( DB_MASTER );
03103 # Is it an existsing file?
03104 if( $nt->getNamespace() == NS_FILE ) {
03105 $file = wfLocalFile( $nt );
03106 if( $file->exists() ) {
03107 wfDebug( __METHOD__ . ": file exists\n" );
03108 return false;
03109 }
03110 }
03111 # Is it a redirect with no history?
03112 if( !$nt->isSingleRevRedirect() ) {
03113 wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
03114 return false;
03115 }
03116 # Get the article text
03117 $rev = Revision::newFromTitle( $nt );
03118 $text = $rev->getText();
03119 # Does the redirect point to the source?
03120 # Or is it a broken self-redirect, usually caused by namespace collisions?
03121 $m = array();
03122 if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
03123 $redirTitle = Title::newFromText( $m[1] );
03124 if( !is_object( $redirTitle ) ||
03125 ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
03126 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
03127 wfDebug( __METHOD__ . ": redirect points to other page\n" );
03128 return false;
03129 }
03130 } else {
03131 # Fail safe
03132 wfDebug( __METHOD__ . ": failsafe\n" );
03133 return false;
03134 }
03135 return true;
03136 }
03137
03143 public function isWatchable() {
03144 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
03145 }
03146
03154 public function getParentCategories() {
03155 global $wgContLang;
03156
03157 $titlekey = $this->getArticleId();
03158 $dbr = wfGetDB( DB_SLAVE );
03159 $categorylinks = $dbr->tableName( 'categorylinks' );
03160
03161 # NEW SQL
03162 $sql = "SELECT * FROM $categorylinks"
03163 ." WHERE cl_from='$titlekey'"
03164 ." AND cl_from <> '0'"
03165 ." ORDER BY cl_sortkey";
03166
03167 $res = $dbr->query( $sql );
03168
03169 if( $dbr->numRows( $res ) > 0 ) {
03170 foreach( $res as $row )
03171
03172 $data[$wgContLang->getNSText( NS_CATEGORY ).':'.$row->cl_to] = $this->getFullText();
03173 $dbr->freeResult( $res );
03174 } else {
03175 $data = array();
03176 }
03177 return $data;
03178 }
03179
03185 public function getParentCategoryTree( $children = array() ) {
03186 $stack = array();
03187 $parents = $this->getParentCategories();
03188
03189 if( $parents ) {
03190 foreach( $parents as $parent => $current ) {
03191 if ( array_key_exists( $parent, $children ) ) {
03192 # Circular reference
03193 $stack[$parent] = array();
03194 } else {
03195 $nt = Title::newFromText($parent);
03196 if ( $nt ) {
03197 $stack[$parent] = $nt->getParentCategoryTree( $children + array($parent => 1) );
03198 }
03199 }
03200 }
03201 return $stack;
03202 } else {
03203 return array();
03204 }
03205 }
03206
03207
03214 public function pageCond() {
03215 if( $this->mArticleID > 0 ) {
03216
03217 return array( 'page_id' => $this->mArticleID );
03218 } else {
03219 return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
03220 }
03221 }
03222
03230 public function getPreviousRevisionID( $revId, $flags=0 ) {
03231 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03232 return $db->selectField( 'revision', 'rev_id',
03233 array(
03234 'rev_page' => $this->getArticleId($flags),
03235 'rev_id < ' . intval( $revId )
03236 ),
03237 __METHOD__,
03238 array( 'ORDER BY' => 'rev_id DESC' )
03239 );
03240 }
03241
03249 public function getNextRevisionID( $revId, $flags=0 ) {
03250 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03251 return $db->selectField( 'revision', 'rev_id',
03252 array(
03253 'rev_page' => $this->getArticleId($flags),
03254 'rev_id > ' . intval( $revId )
03255 ),
03256 __METHOD__,
03257 array( 'ORDER BY' => 'rev_id' )
03258 );
03259 }
03260
03267 public function getFirstRevision( $flags=0 ) {
03268 $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03269 $pageId = $this->getArticleId($flags);
03270 if( !$pageId ) return NULL;
03271 $row = $db->selectRow( 'revision', '*',
03272 array( 'rev_page' => $pageId ),
03273 __METHOD__,
03274 array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
03275 );
03276 if( !$row ) {
03277 return NULL;
03278 } else {
03279 return new Revision( $row );
03280 }
03281 }
03282
03288 public function isNewPage() {
03289 $dbr = wfGetDB( DB_SLAVE );
03290 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
03291 }
03292
03298 public function getEarliestRevTime() {
03299 $dbr = wfGetDB( DB_SLAVE );
03300 if( $this->exists() ) {
03301 $min = $dbr->selectField( 'revision',
03302 'MIN(rev_timestamp)',
03303 array( 'rev_page' => $this->getArticleId() ),
03304 __METHOD__ );
03305 return wfTimestampOrNull( TS_MW, $min );
03306 }
03307 return null;
03308 }
03309
03318 public function countRevisionsBetween( $old, $new ) {
03319 $dbr = wfGetDB( DB_SLAVE );
03320 return $dbr->selectField( 'revision', 'count(*)',
03321 'rev_page = ' . intval( $this->getArticleId() ) .
03322 ' AND rev_id > ' . intval( $old ) .
03323 ' AND rev_id < ' . intval( $new ),
03324 __METHOD__
03325 );
03326 }
03327
03334 public function equals( Title $title ) {
03335
03336 return $this->getInterwiki() === $title->getInterwiki()
03337 && $this->getNamespace() == $title->getNamespace()
03338 && $this->getDBkey() === $title->getDBkey();
03339 }
03340
03344 public static function compare( $a, $b ) {
03345 if( $a->getNamespace() == $b->getNamespace() ) {
03346 return strcmp( $a->getText(), $b->getText() );
03347 } else {
03348 return $a->getNamespace() - $b->getNamespace();
03349 }
03350 }
03351
03357 public function __toString() {
03358 return $this->getPrefixedText();
03359 }
03360
03370 public function exists() {
03371 return $this->getArticleId() != 0;
03372 }
03373
03390 public function isAlwaysKnown() {
03391 if( $this->mInterwiki != '' ) {
03392 return true;
03393 }
03394 switch( $this->mNamespace ) {
03395 case NS_MEDIA:
03396 case NS_FILE:
03397 return wfFindFile( $this );
03398 case NS_SPECIAL:
03399 return SpecialPage::exists( $this->getDBKey() );
03400 case NS_MAIN:
03401 return $this->mDbkeyform == '';
03402 case NS_MEDIAWIKI:
03403
03404
03405
03406
03407
03408 list( $basename, ) = explode( '/', $this->mDbkeyform, 2 );
03409 return wfMsgWeirdKey( $basename );
03410 default:
03411 return false;
03412 }
03413 }
03414
03423 public function isKnown() {
03424 return $this->exists() || $this->isAlwaysKnown();
03425 }
03426
03432 public function canExist() {
03433 return $this->mNamespace >= 0 && $this->mNamespace != NS_MEDIA;
03434 }
03435
03441 public function touchLinks() {
03442 $u = new HTMLCacheUpdate( $this, 'pagelinks' );
03443 $u->doUpdate();
03444
03445 if ( $this->getNamespace() == NS_CATEGORY ) {
03446 $u = new HTMLCacheUpdate( $this, 'categorylinks' );
03447 $u->doUpdate();
03448 }
03449 }
03450
03456 public function getTouched( $db = NULL ) {
03457 $db = isset($db) ? $db : wfGetDB( DB_SLAVE );
03458 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
03459 return $touched;
03460 }
03461
03467 public function getNotificationTimestamp( $user = NULL ) {
03468 global $wgUser, $wgShowUpdatedMarker;
03469
03470 if( !$user ) $user = $wgUser;
03471
03472 $uid = $user->getId();
03473 if( isset($this->mNotificationTimestamp[$uid]) ) {
03474 return $this->mNotificationTimestamp[$uid];
03475 }
03476 if( !$uid || !$wgShowUpdatedMarker ) {
03477 return $this->mNotificationTimestamp[$uid] = false;
03478 }
03479
03480 if( count($this->mNotificationTimestamp) >= self::CACHE_MAX ) {
03481 $this->mNotificationTimestamp = array();
03482 }
03483 $dbr = wfGetDB( DB_SLAVE );
03484 $this->mNotificationTimestamp[$uid] = $dbr->selectField( 'watchlist',
03485 'wl_notificationtimestamp',
03486 array( 'wl_namespace' => $this->getNamespace(),
03487 'wl_title' => $this->getDBkey(),
03488 'wl_user' => $user->getId()
03489 ),
03490 __METHOD__
03491 );
03492 return $this->mNotificationTimestamp[$uid];
03493 }
03494
03499 public function trackbackURL() {
03500 global $wgScriptPath, $wgServer, $wgScriptExtension;
03501
03502 return "$wgServer$wgScriptPath/trackback$wgScriptExtension?article="
03503 . htmlspecialchars(urlencode($this->getPrefixedDBkey()));
03504 }
03505
03510 public function trackbackRDF() {
03511 $url = htmlspecialchars($this->getFullURL());
03512 $title = htmlspecialchars($this->getText());
03513 $tburl = $this->trackbackURL();
03514
03515
03516
03517
03518
03519
03520 return "<!--
03521 <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"
03522 xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
03523 xmlns:trackback=\"http://madskills.com/public/xml/rss/module/trackback/\">
03524 <rdf:Description
03525 rdf:about=\"$url\"
03526 dc:identifier=\"$url\"
03527 dc:title=\"$title\"
03528 trackback:ping=\"$tburl\" />
03529 </rdf:RDF>
03530 -->";
03531 }
03532
03537 public function getNamespaceKey() {
03538 global $wgContLang;
03539 switch ($this->getNamespace()) {
03540 case NS_MAIN:
03541 case NS_TALK:
03542 return 'nstab-main';
03543 case NS_USER:
03544 case NS_USER_TALK:
03545 return 'nstab-user';
03546 case NS_MEDIA:
03547 return 'nstab-media';
03548 case NS_SPECIAL:
03549 return 'nstab-special';
03550 case NS_PROJECT:
03551 case NS_PROJECT_TALK:
03552 return 'nstab-project';
03553 case NS_FILE:
03554 case NS_FILE_TALK:
03555 return 'nstab-image';
03556 case NS_MEDIAWIKI:
03557 case NS_MEDIAWIKI_TALK:
03558 return 'nstab-mediawiki';
03559 case NS_TEMPLATE:
03560 case NS_TEMPLATE_TALK:
03561 return 'nstab-template';
03562 case NS_HELP:
03563 case NS_HELP_TALK:
03564 return 'nstab-help';
03565 case NS_CATEGORY:
03566 case NS_CATEGORY_TALK:
03567 return 'nstab-category';
03568 default:
03569 return 'nstab-' . $wgContLang->lc( $this->getSubjectNsText() );
03570 }
03571 }
03572
03577 public function isSpecial( $name ) {
03578 if ( $this->getNamespace() == NS_SPECIAL ) {
03579 list( $thisName, ) = SpecialPage::resolveAliasWithSubpage( $this->getDBkey() );
03580 if ( $name == $thisName ) {
03581 return true;
03582 }
03583 }
03584 return false;
03585 }
03586
03591 public function fixSpecialName() {
03592 if ( $this->getNamespace() == NS_SPECIAL ) {
03593 $canonicalName = SpecialPage::resolveAlias( $this->mDbkeyform );
03594 if ( $canonicalName ) {
03595 $localName = SpecialPage::getLocalNameFor( $canonicalName );
03596 if ( $localName != $this->mDbkeyform ) {
03597 return Title::makeTitle( NS_SPECIAL, $localName );
03598 }
03599 }
03600 }
03601 return $this;
03602 }
03603
03611 public function isContentPage() {
03612 return MWNamespace::isContent( $this->getNamespace() );
03613 }
03614
03622 public function getRedirectsHere( $ns = null ) {
03623 $redirs = array();
03624
03625 $dbr = wfGetDB( DB_SLAVE );
03626 $where = array(
03627 'rd_namespace' => $this->getNamespace(),
03628 'rd_title' => $this->getDBkey(),
03629 'rd_from = page_id'
03630 );
03631 if ( !is_null($ns) ) $where['page_namespace'] = $ns;
03632
03633 $res = $dbr->select(
03634 array( 'redirect', 'page' ),
03635 array( 'page_namespace', 'page_title' ),
03636 $where,
03637 __METHOD__
03638 );
03639
03640
03641 foreach( $res as $row ) {
03642 $redirs[] = self::newFromRow( $row );
03643 }
03644 return $redirs;
03645 }
03646
03652 public function isValidRedirectTarget() {
03653 global $wgInvalidRedirectTargets;
03654
03655
03656 if( $this->isSpecial( 'Userlogout' ) ) {
03657 return false;
03658 }
03659
03660 foreach( $wgInvalidRedirectTargets as $target ) {
03661 if( $this->isSpecial( $target ) ) {
03662 return false;
03663 }
03664 }
03665
03666 return true;
03667 }
03668
03672 function getBacklinkCache() {
03673 if ( is_null( $this->mBacklinkCache ) ) {
03674 $this->mBacklinkCache = new BacklinkCache( $this );
03675 }
03676 return $this->mBacklinkCache;
03677 }
03678 }