8 if ( !class_exists(
'UtfNormal' ) ) {
9 require_once( dirname(__FILE__) .
'/normal/UtfNormal.php' );
12 define (
'GAID_FOR_UPDATE', 1 );
14 # Title::newFromTitle maintains a cache to avoid
15 # expensive re-normalization of commonly used titles.
16 # On a batch operation this can become a memory leak
17 # if not bounded. After hitting this many titles,
19 define(
'MW_TITLECACHE_MAX', 1000 );
21 # Constants for pr_cascade bitfield
22 define(
'CASCADE', 1 );
47 var
$mTextform; # Text form (spaces not underscores) of the main part
50 var
$mNamespace; # Namespace index, i.e. one of the NS_xxxx constants
52 var
$mFragment;
# Title fragment (i.e. the bit after the #)
53 var
$mArticleID; # Article ID, fetched from the link cache on demand
61 var
$mPrefixedText; # Text form including
namespace/interwiki, initialised on demand
63 # Zero except in {{transclusion}} tags
73 $this->mInterwiki = $this->mUrlform =
74 $this->mTextform = $this->mDbkeyform =
'';
75 $this->mArticleID = -1;
76 $this->mNamespace = NS_MAIN;
77 $this->mRestrictionsLoaded =
false;
78 $this->mRestrictions = array();
79 # Dont change the following, NS_MAIN is hardcoded in several place
81 $this->mDefaultNamespace = NS_MAIN;
82 $this->mWatched = NULL;
83 $this->mLatestID =
false;
84 $this->mOldRestrictions =
false;
96 $t->mDbkeyform = $key;
97 if(
$t->secureAndSplit() )
114 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
115 if( is_object( $text ) ) {
116 throw new MWException(
'Title::newFromText given an object' );
137 $t->mDbkeyform = str_replace(
' ',
'_', $filteredText );
138 $t->mDefaultNamespace = $defaultNamespace;
140 static $cachedcount = 0 ;
141 if(
$t->secureAndSplit() ) {
142 if( $defaultNamespace == NS_MAIN ) {
144 # Avoid memory leaks on mass operations...
165 global $wgLegalTitleChars;
168 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
169 # but some URLs used it as a space replacement and they still come
170 # from some external search tools.
171 if ( strpos( $wgLegalTitleChars,
'+' ) ===
false ) {
172 $url = str_replace(
'+',
' ', $url );
175 $t->mDbkeyform = str_replace(
' ',
'_', $url );
176 if(
$t->secureAndSplit() ) {
193 $fname =
'Title::newFromID';
194 $dbr = wfGetDB( DB_SLAVE );
195 $row = $dbr->selectRow(
'page', array(
'page_namespace',
'page_title' ),
196 array(
'page_id' => $id ), $fname );
197 if ( $row !==
false ) {
209 $dbr = wfGetDB( DB_SLAVE );
210 $res = $dbr->select(
'page', array(
'page_namespace',
'page_title' ),
211 'page_id IN (' . $dbr->makeList( $ids ) .
')', __METHOD__ );
214 while ( $row = $dbr->fetchObject(
$res ) ) {
235 $t->mNamespace = intval( $ns );
236 $t->mDbkeyform = str_replace(
' ',
'_',
$title );
237 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
238 $t->mUrlform = wfUrlencode(
$t->mDbkeyform );
239 $t->mTextform = str_replace(
'_',
' ',
$title );
255 if(
$t->secureAndSplit() ) {
277 $mwRedir = MagicWord::get(
'redirect' );
279 if ( $mwRedir->matchStart( $text ) ) {
281 if ( preg_match(
'/\[{2}(.*?)(?:\||\]{2})/', $text, $m ) ) {
282 # categories are escaped using : for example one can enter:
283 # #REDIRECT [[:Category:Music]]. Need to remove it.
284 if ( substr($m[1],0,1) ==
':') {
285 # We don't want to keep the ':'
286 $m[1] = substr( $m[1], 1 );
290 # Disallow redirects to Special:Userlogout
291 if ( !is_null($rt) && $rt->isSpecial(
'Userlogout' ) ) {
299 #----------------------------------------------------------------------------
301 #----------------------------------------------------------------------------
312 $fname =
'Title::nameOf';
313 $dbr = wfGetDB( DB_SLAVE );
315 $s = $dbr->selectRow(
'page', array(
'page_namespace',
'page_title' ), array(
'page_id' => $id ), $fname );
316 if ( $s ===
false ) {
return NULL; }
327 global $wgLegalTitleChars;
329 $wgLegalTitleChars =
" %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+";
331 return $wgLegalTitleChars;
346 $lc = SearchEngine::legalSearchChars() .
'&#;';
347 $t = $wgContLang->stripForSearch(
$title );
348 $t = preg_replace(
"/[^{$lc}]+/",
' ',
$t );
349 $t = $wgContLang->lc(
$t );
352 $t = preg_replace(
"/([{$lc}]+)'s( |$)/",
"\\1 \\1's ",
$t );
353 $t = preg_replace(
"/([{$lc}]+)s'( |$)/",
"\\1s ",
$t );
355 $t = preg_replace(
"/\\s+/",
' ',
$t );
357 if ( $ns == NS_IMAGE ) {
358 $t = preg_replace(
"/ (png|gif|jpg|jpeg|ogg)$/",
"",
$t );
372 $n = $wgContLang->getNsText( $ns );
373 return $n ==
'' ?
$title :
"$n:$title";
384 global $wgMemc, $wgInterwikiExpiry;
385 global $wgInterwikiCache, $wgContLang;
389 $fname =
'Title::getInterwikiLink';
391 $key = $wgContLang->lc( $key );
393 $k = wfMemcKey(
'interwiki', $key );
398 if ($wgInterwikiCache) {
402 $s = $wgMemc->get( $k );
403 # Ignore old keys with no iw_local
404 if( $s && isset( $s->iw_local ) && isset($s->iw_trans)) {
409 $dbr = wfGetDB( DB_SLAVE );
410 $res = $dbr->select(
'interwiki',
411 array(
'iw_url',
'iw_local',
'iw_trans' ),
412 array(
'iw_prefix' => $key ), $fname );
417 $s = $dbr->fetchObject(
$res );
419 # Cache non-existence: create a blank object and save it to memcached
425 $wgMemc->set( $k, $s, $wgInterwikiExpiry );
439 global $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite;
443 $db=dba_open($wgInterwikiCache,
'r',
'cdb');
445 if ($wgInterwikiScopes>=3 and !$site) {
446 $site = dba_fetch(
'__sites:' . wfWikiID(), $db);
448 $site = $wgInterwikiFallbackSite;
450 $value = dba_fetch( wfMemcKey( $key ), $db);
451 if ($value==
'' and $wgInterwikiScopes>=3) {
453 $value = dba_fetch(
"_{$site}:{$key}", $db);
455 if ($value==
'' and $wgInterwikiScopes>=2) {
457 $value = dba_fetch(
"__global:{$key}", $db);
466 list($local,$url)=explode(
' ',$value,2);
468 $s->iw_local=(int)$local;
481 if ( $this->mInterwiki !=
'' ) {
482 # Make sure key is loaded into cache
484 $k = wfMemcKey(
'interwiki', $this->mInterwiki );
498 if ($this->mInterwiki ==
'')
500 # Make sure key is loaded into cache
502 $k = wfMemcKey(
'interwiki', $this->mInterwiki );
510 $fragment = str_replace(
' ',
'_', $fragment );
512 $replaceArray = array(
516 return strtr( $fragment, $replaceArray );
519 #----------------------------------------------------------------------------
521 #----------------------------------------------------------------------------
549 global $wgContLang, $wgCanonicalNamespaceNames;
551 if (
'' != $this->mInterwiki ) {
558 if( isset( $wgCanonicalNamespaceNames[$this->mNamespace] ) ) {
562 return $wgContLang->getNsText( $this->mNamespace );
570 return $wgContLang->getNsText( Namespace::getSubject( $this->mNamespace ) );
579 return( $wgContLang->getNsText( Namespace::getTalk( $this->mNamespace ) ) );
587 return( Namespace::canTalk( $this->mNamespace ) );
605 if ( $this->mFragment ==
'' ) {
632 $s = $this->
prefix( $this->mDbkeyform );
633 $s = str_replace(
' ',
'_', $s );
643 if ( empty( $this->mPrefixedText ) ) {
644 $s = $this->
prefix( $this->mTextform );
645 $s = str_replace(
'_',
' ', $s );
646 $this->mPrefixedText = $s;
659 if(
'' != $this->mFragment ) {
670 global $wgNamespacesWithSubpages;
671 if( isset( $wgNamespacesWithSubpages[ $this->mNamespace ] ) && $wgNamespacesWithSubpages[ $this->mNamespace ] ) {
672 $parts = explode(
'/', $this->
getText() );
673 # Don't discard the real title if there's no subpage involved
674 if( count( $parts ) > 1 )
675 unset( $parts[ count( $parts ) - 1 ] );
676 return implode(
'/', $parts );
687 global $wgNamespacesWithSubpages;
688 if( isset( $wgNamespacesWithSubpages[ $this->mNamespace ] ) && $wgNamespacesWithSubpages[ $this->mNamespace ] ) {
689 $parts = explode(
'/', $this->mTextform );
690 return( $parts[ count( $parts ) - 1 ] );
692 return( $this->mTextform );
702 $text = wfUrlencode( str_replace(
' ',
'_', $text ) );
703 $text = str_replace(
'%28',
'(', str_replace(
'%29',
')', $text ) ); # Clean up the URL; per below,
this might not be safe
712 $s = $this->
prefix( $this->mDbkeyform );
713 $s = str_replace(
' ',
'_', $s );
715 $s = wfUrlencode ( $s ) ;
717 # Cleaning up URL to make it look nice -- is this safe?
718 $s = str_replace(
'%28',
'(', $s );
719 $s = str_replace(
'%29',
')', $s );
733 public function getFullURL( $query =
'', $variant =
false ) {
734 global $wgContLang, $wgServer, $wgRequest;
736 if (
'' == $this->mInterwiki ) {
737 $url = $this->getLocalUrl( $query, $variant );
741 if ($wgRequest->getVal(
'action') !=
'render') {
742 $url = $wgServer . $url;
749 # Can this actually happen? Interwikis shouldn't be parsed.
750 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
753 $url = str_replace(
'$1',
$namespace . $this->mUrlform, $baseUrl );
754 $url = wfAppendQuery( $url, $query );
757 # Finally, add the fragment.
760 wfRunHooks(
'GetFullURL', array( &$this, &$url, $query ) );
773 global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
774 global $wgVariantArticlePath, $wgContLang, $wgUser;
777 if($variant ==
false && $wgContLang->hasVariants() && !$wgUser->isLoggedIn()){
778 $pref = $wgContLang->getPreferredVariant(
false);
779 if($pref != $wgContLang->getCode())
794 if ( $query ==
'' ) {
795 if($variant!=
false && $wgContLang->hasVariants()){
796 if($wgVariantArticlePath==
false) {
797 $variantArticlePath =
"$wgScript?title=$1&variant=$2";
799 $variantArticlePath = $wgVariantArticlePath;
801 $url = str_replace(
'$2', urlencode( $variant ), $variantArticlePath );
802 $url = str_replace(
'$1', $dbkey, $url );
805 $url = str_replace(
'$1', $dbkey, $wgArticlePath );
808 global $wgActionPaths;
811 if( !empty( $wgActionPaths ) &&
812 preg_match(
'/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
814 $action = urldecode( $matches[2] );
815 if( isset( $wgActionPaths[$action] ) ) {
816 $query = $matches[1];
817 if( isset( $matches[4] ) ) $query .= $matches[4];
818 $url = str_replace(
'$1', $dbkey, $wgActionPaths[$action] );
819 if( $query !=
'' ) $url .=
'?' . $query;
822 if ( $url ===
false ) {
823 if ( $query ==
'-' ) {
826 $url =
"{$wgScript}?title={$dbkey}&{$query}";
832 if ($wgRequest->getVal(
'action') ==
'render') {
833 $url = $wgServer . $url;
836 wfRunHooks(
'GetLocalURL', array( &$this, &$url, $query ) );
847 return htmlspecialchars( $this->
getLocalURL( $query ) );
858 return htmlspecialchars( $this->
getFullURL( $query ) );
871 global $wgInternalServer;
872 $url = $wgInternalServer . $this->
getLocalURL( $query, $variant );
873 wfRunHooks(
'GetInternalURL', array( &$this, &$url, $query ) );
883 if (
'' != $this->mInterwiki ) {
return ''; }
902 public function isExternal() {
return (
'' != $this->mInterwiki ); }
913 if( count( $restrictions ) > 0 ) {
914 foreach( $restrictions as $restriction ) {
915 if( strtolower( $restriction ) !=
'autoconfirmed' )
924 # If it doesn't exist, it can't be protected
936 global $wgRestrictionLevels;
938 # Special pages have inherent protection
942 # Check regular protection levels
943 if( $action ==
'edit' || $action ==
'' ) {
945 foreach( $wgRestrictionLevels as $level ) {
946 if( in_array( $level, $r ) && $level !=
'' ) {
952 if( $action ==
'move' || $action ==
'' ) {
954 foreach( $wgRestrictionLevels as $level ) {
955 if( in_array( $level, $r ) && $level !=
'' ) {
971 if ( is_null( $this->mWatched ) ) {
972 if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn()) {
973 $this->mWatched =
false;
975 $this->mWatched = $wgUser->isWatched( $this );
994 return $this->
userCan( $action,
false );
1003 public function userCan( $action, $doExpensiveQueries =
true ) {
1004 $fname =
'Title::userCan';
1005 wfProfileIn( $fname );
1007 global $wgUser, $wgNamespaceProtection;
1010 wfRunHooks(
'userCan', array( &$this, &$wgUser, $action, &
$result ) );
1012 wfProfileOut( $fname );
1016 if( NS_SPECIAL == $this->mNamespace ) {
1017 wfProfileOut( $fname );
1021 if ( array_key_exists( $this->mNamespace, $wgNamespaceProtection ) ) {
1023 if ( !is_array($nsProt) ) $nsProt = array($nsProt);
1024 foreach( $nsProt as $right ) {
1025 if(
'' != $right && !$wgUser->isAllowed( $right ) ) {
1026 wfProfileOut( $fname );
1032 if( $this->mDbkeyform ==
'_' ) {
1033 # FIXME: Is this necessary? Shouldn't be allowed anyway...
1034 wfProfileOut( $fname );
1038 # protect css/js subpages of user pages
1039 # XXX: this might be better using restrictions
1040 # XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working
1042 && !$wgUser->isAllowed(
'editinterface')
1043 && !preg_match(
'/^'.preg_quote($wgUser->getName(),
'/').
'\//', $this->mTextform) ) {
1044 wfProfileOut( $fname );
1049 # We /could/ use the protection level on the source page, but it's fairly ugly
1050 # as we have to establish a precedence hierarchy for pages included by multiple
1051 # cascade-protected pages. So just restrict it to people with 'protect' permission,
1052 # as they could remove the protection anyway.
1054 # Cascading protection depends on more than this page...
1055 # Several cascading protected pages may include this page...
1056 # Check each cascading level
1057 # This is only for protection restrictions, not for all actions
1058 if( $cascadingSources > 0 && isset($restrictions[$action]) ) {
1059 foreach( $restrictions[$action] as $right ) {
1060 $right = ( $right ==
'sysop' ) ?
'protect' : $right;
1061 if(
'' != $right && !$wgUser->isAllowed( $right ) ) {
1062 wfProfileOut( $fname );
1071 if ( $right ==
'sysop' ) {
1074 if(
'' != $right && !$wgUser->isAllowed( $right ) ) {
1075 wfProfileOut( $fname );
1080 if( $action ==
'move' &&
1081 !( $this->
isMovable() && $wgUser->isAllowed(
'move' ) ) ) {
1082 wfProfileOut( $fname );
1086 if( $action ==
'create' ) {
1087 if( ( $this->
isTalkPage() && !$wgUser->isAllowed(
'createtalk' ) ) ||
1088 ( !$this->
isTalkPage() && !$wgUser->isAllowed(
'createpage' ) ) ) {
1089 wfProfileOut( $fname );
1094 wfProfileOut( $fname );
1104 return $this->
userCan(
'edit', $doExpensiveQueries );
1113 return $this->
userCan(
'create', $doExpensiveQueries );
1122 return $this->
userCan(
'move', $doExpensiveQueries );
1145 wfRunHooks(
'userCan', array( &$this, &$wgUser,
'read', &
$result ) );
1150 if( $wgUser->isAllowed(
'read') ) {
1153 global $wgWhitelistRead;
1165 if( $wgWhitelistRead && in_array( $name, $wgWhitelistRead ) ) {
1169 # Compatibility with old settings
1170 if( $wgWhitelistRead && $this->
getNamespace() == NS_MAIN ) {
1171 if( in_array(
':' . $name, $wgWhitelistRead ) ) {
1192 global $wgNamespacesWithSubpages;
1194 if( isset( $wgNamespacesWithSubpages[ $this->mNamespace ] ) ) {
1195 return ( strpos( $this->
getText(),
'/' ) !==
false && $wgNamespacesWithSubpages[ $this->mNamespace ] ==
true );
1206 return ( NS_USER == $this->mNamespace and preg_match(
"/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
1214 $skinNames = Skin::getSkinNames();
1224 $subpage = explode(
'/', $this->mTextform );
1225 $subpage = $subpage[ count( $subpage ) - 1 ];
1226 return( str_replace( array(
'.css',
'.js' ), array(
'',
'' ), $subpage ) );
1233 return ( NS_USER == $this->mNamespace and preg_match(
"/\\/.*\\.css$/", $this->mTextform ) );
1240 return ( NS_USER == $this->mNamespace and preg_match(
"/\\/.*\\.js$/", $this->mTextform ) );
1251 return ( $wgUser->isAllowed(
'editinterface') or preg_match(
'/^'.preg_quote($wgUser->getName(),
'/').
'\//', $this->mTextform) );
1261 return ( $sources > 0 );
1273 global $wgEnableCascadingProtection, $wgRestrictionTypes;
1275 # Define our dimension of restrictions types
1276 $pagerestrictions = array();
1277 foreach( $wgRestrictionTypes as $action )
1278 $pagerestrictions[$action] = array();
1280 if (!$wgEnableCascadingProtection)
1281 return array(
false, $pagerestrictions );
1283 if ( isset( $this->mCascadeSources ) && $get_pages ) {
1284 return array( $this->mCascadeSources, $this->mCascadingRestrictions );
1285 }
else if ( isset( $this->mHasCascadingRestrictions ) && !$get_pages ) {
1286 return array( $this->mHasCascadingRestrictions, $pagerestrictions );
1289 wfProfileIn( __METHOD__ );
1291 $dbr = wfGetDb( DB_SLAVE );
1294 $tables = array (
'imagelinks',
'page_restrictions');
1295 $where_clauses = array(
1298 'pr_cascade' => 1 );
1300 $tables = array (
'templatelinks',
'page_restrictions');
1301 $where_clauses = array(
1305 'pr_cascade' => 1 );
1309 $cols = array(
'pr_page',
'page_namespace',
'page_title',
'pr_expiry',
'pr_type',
'pr_level' );
1310 $where_clauses[] =
'page_id=pr_page';
1313 $cols = array(
'pr_expiry' );
1316 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
1318 $sources = $get_pages ? array() :
false;
1319 $now = wfTimestampNow();
1320 $purgeExpired =
false;
1322 while( $row = $dbr->fetchObject(
$res ) ) {
1323 $expiry = Block::decodeExpiry( $row->pr_expiry );
1324 if( $expiry > $now ) {
1326 $page_id = $row->pr_page;
1327 $page_ns = $row->page_namespace;
1328 $page_title = $row->page_title;
1330 # Add groups needed for each restriction type if its not already there
1331 # Make sure this restriction type still exists
1332 if ( isset($pagerestrictions[$row->pr_type]) && !in_array($row->pr_level, $pagerestrictions[$row->pr_type]) ) {
1333 $pagerestrictions[$row->pr_type][]=$row->pr_level;
1340 $purgeExpired =
true;
1343 if( $purgeExpired ) {
1347 wfProfileOut( __METHOD__ );
1350 $this->mCascadeSources = $sources;
1351 $this->mCascadingRestrictions = $pagerestrictions;
1353 $this->mHasCascadingRestrictions = $sources;
1356 return array( $sources, $pagerestrictions );
1360 if (!$this->mRestrictionsLoaded) {
1372 $dbr = wfGetDb( DB_SLAVE );
1374 $this->mRestrictions[
'edit'] = array();
1375 $this->mRestrictions[
'move'] = array();
1377 # Backwards-compatibility: also load the restrictions from the page record (old format).
1379 if ( $oldFashionedRestrictions == NULL ) {
1380 $oldFashionedRestrictions = $dbr->selectField(
'page',
'page_restrictions', array(
'page_id' => $this->getArticleId() ), __METHOD__ );
1383 if ($oldFashionedRestrictions !=
'') {
1385 foreach( explode(
':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
1386 $temp = explode(
'=', trim( $restrict ) );
1387 if(count($temp) == 1) {
1389 $this->mRestrictions[
"edit"] = explode(
',', trim( $temp[0] ) );
1390 $this->mRestrictions[
"move"] = explode(
',', trim( $temp[0] ) );
1392 $this->mRestrictions[$temp[0]] = explode(
',', trim( $temp[1] ) );
1396 $this->mOldRestrictions =
true;
1397 $this->mCascadeRestriction =
false;
1398 $this->mRestrictionsExpiry = Block::decodeExpiry(
'');
1402 if( $dbr->numRows(
$res ) ) {
1403 # Current system - load second to make them override.
1404 $now = wfTimestampNow();
1405 $purgeExpired =
false;
1407 while ($row = $dbr->fetchObject(
$res ) ) {
1408 # Cycle through all the restrictions.
1412 $expiry = Block::decodeExpiry( $row->pr_expiry );
1415 if ( !$expiry || $expiry > $now ) {
1416 $this->mRestrictionsExpiry = $expiry;
1417 $this->mRestrictions[$row->pr_type] = explode(
',', trim( $row->pr_level ) );
1419 $this->mCascadeRestriction |= $row->pr_cascade;
1422 $purgeExpired =
true;
1426 if( $purgeExpired ) {
1431 $this->mRestrictionsLoaded =
true;
1435 if( !$this->mRestrictionsLoaded ) {
1436 $dbr = wfGetDB( DB_SLAVE );
1438 $res = $dbr->select(
'page_restrictions',
'*',
1439 array (
'pr_page' => $this->getArticleId() ), __METHOD__ );
1449 $dbw = wfGetDB( DB_MASTER );
1450 $dbw->delete(
'page_restrictions',
1451 array(
'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
1463 if( !$this->mRestrictionsLoaded ) {
1466 return isset( $this->mRestrictions[$action] )
1467 ? $this->mRestrictions[$action]
1479 $fname =
'Title::isDeleted';
1483 $dbr = wfGetDB( DB_SLAVE );
1484 $n = $dbr->selectField(
'archive',
'COUNT(*)', array(
'ar_namespace' => $this->
getNamespace(),
1485 'ar_title' => $this->
getDBkey() ), $fname );
1487 $n += $dbr->selectField(
'filearchive',
'COUNT(*)',
1488 array(
'fa_name' => $this->
getDBkey() ), $fname );
1502 $linkCache =& LinkCache::singleton();
1504 $oldUpdate = $linkCache->forUpdate(
true );
1505 $this->mArticleID = $linkCache->addLinkObj( $this );
1506 $linkCache->forUpdate( $oldUpdate );
1508 if ( -1 == $this->mArticleID ) {
1509 $this->mArticleID = $linkCache->addLinkObj( $this );
1516 if ($this->mLatestID !==
false)
1519 $db = wfGetDB(DB_SLAVE);
1520 return $this->mLatestID = $db->selectField(
'revision',
1523 'Title::getLatestRevID' );
1537 $linkCache =& LinkCache::singleton();
1540 if ( 0 == $newid ) { $this->mArticleID = -1; }
1541 else { $this->mArticleID = $newid; }
1542 $this->mRestrictionsLoaded =
false;
1543 $this->mRestrictions = array();
1551 global $wgUseFileCache;
1553 if ( wfReadOnly() ) {
1557 $dbw = wfGetDB( DB_MASTER );
1560 'page_touched' => $dbw->timestamp()
1564 ),
'Title::invalidateCache'
1567 if ($wgUseFileCache) {
1568 $cache =
new HTMLFileCache($this);
1569 @unlink($cache->fileCacheName());
1585 if (
'' != $this->mInterwiki ) {
1586 $p = $this->mInterwiki .
':';
1588 if ( 0 != $this->mNamespace ) {
1605 global $wgContLang, $wgLocalInterwiki, $wgCapitalLinks;
1608 static $rxTc =
false;
1610 # % is needed as well
1614 $this->mInterwiki = $this->mFragment =
'';
1619 # Strip Unicode bidi override characters.
1620 # Sometimes they slip into cut-n-pasted page titles, where the
1621 # override chars get included in list displays.
1622 $dbkey = str_replace(
"\xE2\x80\x8E",
'', $dbkey );
1623 $dbkey = str_replace(
"\xE2\x80\x8F",
'', $dbkey );
1625 # Clean up whitespace
1627 $dbkey = preg_replace(
'/[ _]+/',
'_', $dbkey );
1628 $dbkey = trim( $dbkey,
'_' );
1630 if (
'' == $dbkey ) {
1635 # Contained illegal UTF-8 sequences or forbidden Unicode chars.
1639 $this->mDbkeyform = $dbkey;
1641 # Initial colon indicates main namespace rather than specified default
1642 # but should not create invalid {ns,title} pairs such as {0,Project:Foo}
1643 if (
':' == $dbkey{0} ) {
1644 $this->mNamespace = NS_MAIN;
1645 $dbkey = substr( $dbkey, 1 ); #
remove the colon but
continue processing
1646 $dbkey = trim( $dbkey,
'_' ); #
remove any subsequent whitespace
1649 # Namespace or interwiki prefix
1653 if ( preg_match(
"/^(.+?)_*:_*(.*)$/S", $dbkey, $m ) ) {
1655 if ( $ns = $wgContLang->getNsIndex( $p )) {
1656 # Ordinary namespace
1658 $this->mNamespace = $ns;
1661 # Can't make a local interwiki link to an interwiki link.
1662 # That's just crazy!
1668 $this->mInterwiki = $wgContLang->lc( $p );
1670 # Redundant interwiki prefix to the local wiki
1671 if ( 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) {
1672 if( $dbkey ==
'' ) {
1673 # Can't have an empty self-link
1676 $this->mInterwiki =
'';
1678 # Do another namespace split...
1682 # If there's an initial colon after the interwiki, that also
1683 # resets the default namespace
1684 if ( $dbkey !==
'' && $dbkey[0] ==
':' ) {
1685 $this->mNamespace = NS_MAIN;
1686 $dbkey = substr( $dbkey, 1 );
1689 # If there's no recognized interwiki or namespace,
1690 # then let the colon expression be part of the title.
1695 # We already know that some pages won't be in the database!
1697 if (
'' != $this->mInterwiki || NS_SPECIAL == $this->mNamespace ) {
1698 $this->mArticleID = 0;
1700 $fragment = strstr( $dbkey,
'#' );
1701 if (
false !== $fragment ) {
1703 $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
1704 # remove whitespace again: prevents "Foo_bar_#"
1705 # becoming "Foo_bar_"
1706 $dbkey = preg_replace(
'/_*$/',
'', $dbkey );
1709 # Reject illegal characters.
1711 if( preg_match( $rxTc, $dbkey ) ) {
1720 if ( strpos( $dbkey,
'.' ) !==
false &&
1721 ( $dbkey ===
'.' || $dbkey ===
'..' ||
1722 strpos( $dbkey,
'./' ) === 0 ||
1723 strpos( $dbkey,
'../' ) === 0 ||
1724 strpos( $dbkey,
'/./' ) !==
false ||
1725 strpos( $dbkey,
'/../' ) !==
false ) )
1733 if( strpos( $dbkey,
'~~~' ) !==
false ) {
1744 if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
1745 strlen( $dbkey ) > 512 )
1758 if( $wgCapitalLinks && $this->mInterwiki ==
'') {
1759 $dbkey = $wgContLang->ucfirst( $dbkey );
1768 $this->mInterwiki ==
'' &&
1769 $this->mNamespace != NS_MAIN ) {
1774 if ( $dbkey !==
'' &&
':' == $dbkey{0} ) {
1779 $this->mDbkeyform = $dbkey;
1782 $this->mTextform = str_replace(
'_',
' ', $dbkey );
1797 $this->mFragment = str_replace(
'_',
' ', substr( $fragment, 1 ) );
1828 public function getLinksTo( $options =
'', $table =
'pagelinks', $prefix =
'pl' ) {
1829 $linkCache =& LinkCache::singleton();
1832 $db = wfGetDB( DB_MASTER );
1834 $db = wfGetDB( DB_SLAVE );
1837 $res = $db->select( array(
'page', $table ),
1838 array(
'page_namespace',
'page_title',
'page_id' ),
1840 "{$prefix}_from=page_id",
1842 "{$prefix}_title" => $this->getDbKey() ),
1843 'Title::getLinksTo',
1847 if ( $db->numRows(
$res ) ) {
1848 while ( $row = $db->fetchObject(
$res ) ) {
1849 if ( $titleObj =
Title::makeTitle( $row->page_namespace, $row->page_title ) ) {
1850 $linkCache->addGoodLinkObj( $row->page_id, $titleObj );
1851 $retVal[] = $titleObj;
1855 $db->freeResult(
$res );
1870 return $this->
getLinksTo( $options,
'templatelinks',
'tl' );
1881 $db = wfGetDB( DB_MASTER );
1883 $db = wfGetDB( DB_SLAVE );
1886 $res = $db->safeQuery(
1887 "SELECT pl_namespace, pl_title
1890 ON pl_namespace=page_namespace
1891 AND pl_title=page_title
1893 AND page_namespace IS NULL
1895 $db->tableName(
'pagelinks' ),
1896 $db->tableName(
'page' ),
1897 $this->getArticleId(),
1901 if ( $db->numRows(
$res ) ) {
1902 while ( $row = $db->fetchObject(
$res ) ) {
1906 $db->freeResult(
$res );
1926 if($wgContLang->hasVariants()){
1927 $variants = $wgContLang->getVariants();
1928 foreach($variants as $vCode){
1929 if($vCode==$wgContLang->getCode())
continue;
1939 if ( $wgUseSquid ) {
1941 $u =
new SquidUpdate( $urls );
1951 return $this->
moveTo( $nt,
false );
1964 if( !$this or !$nt ) {
1965 return 'badtitletext';
1967 if( $this->
equals( $nt ) ) {
1970 if( !$this->
isMovable() || !$nt->isMovable() ) {
1971 return 'immobile_namespace';
1975 $newid = $nt->getArticleID();
1977 if ( strlen( $nt->getDBkey() ) < 1 ) {
1978 return 'articleexists';
1980 if ( (
'' == $this->
getDBkey() ) ||
1982 (
'' == $nt->getDBkey() ) ) {
1983 return 'badarticleerror';
1987 !$this->
userCan(
'edit' ) || !$nt->userCan(
'edit' ) ||
1988 !$this->
userCan(
'move' ) || !$nt->userCan(
'move' ) ) ) {
1989 return 'protectedpage';
1992 # The move is allowed only if (1) the target doesn't exist, or
1993 # (2) the target is a redirect to the source, and has no history
1994 # (so we can undo bad moves right after they're done).
1996 if ( 0 != $newid ) { # Target
exists; check
for validity
1998 return 'articleexists';
2013 if( is_string( $err ) ) {
2018 if( $nt->exists() ) {
2020 $pageCountChange = 0;
2021 }
else { # Target didn
't exist, do normal move.
2022 $this->moveToNewTitle( $nt, $reason );
2023 $pageCountChange = 1;
2025 $redirid = $this->getArticleID();
2027 # Fixing category links (those without piped 'alternate
' names) to be sorted under the new title
2028 $dbw = wfGetDB( DB_MASTER );
2029 $categorylinks = $dbw->tableName( 'categorylinks
' );
2030 $sql = "UPDATE $categorylinks SET cl_sortkey=" . $dbw->addQuotes( $nt->getPrefixedText() ) .
2031 " WHERE cl_from=" . $dbw->addQuotes( $pageid ) .
2032 " AND cl_sortkey=" . $dbw->addQuotes( $this->getPrefixedText() );
2033 $dbw->query( $sql, 'SpecialMovepage::doSubmit
' );
2037 $oldnamespace = $this->getNamespace() & ~1;
2038 $newnamespace = $nt->getNamespace() & ~1;
2039 $oldtitle = $this->getDBkey();
2040 $newtitle = $nt->getDBkey();
2042 if( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
2043 WatchedItem::duplicateEntries( $this, $nt );
2046 # Update search engine
2047 $u = new SearchUpdate( $pageid, $nt->getPrefixedDBkey() );
2049 $u = new SearchUpdate( $redirid, $this->getPrefixedDBkey(), '' );
2053 if( $this->isContentPage() && !$nt->isContentPage() ) {
2054 # No longer a content page
2055 # Not viewed, edited, removing
2056 $u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange );
2057 } elseif( !$this->isContentPage() && $nt->isContentPage() ) {
2058 # Now a content page
2059 # Not viewed, edited, adding
2060 $u = new SiteStatsUpdate( 0, 1, +1, $pageCountChange );
2061 } elseif( $pageCountChange ) {
2063 $u = new SiteStatsUpdate( 0, 0, 0, 1 );
2072 wfRunHooks( 'TitleMoveComplete
', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
2083 private function moveOverExistingRedirect( &$nt, $reason = '' ) {
2086 $comment = wfMsgForContent( '1movedto2_redir
', $this->getPrefixedText(), $nt->getPrefixedText() );
2089 $comment .= ": $reason";
2092 $now = wfTimestampNow();
2093 $newid = $nt->getArticleID();
2094 $oldid = $this->getArticleID();
2095 $dbw = wfGetDB( DB_MASTER );
2096 $linkCache =& LinkCache::singleton();
2098 # Delete the old redirect. We don't save it to history since
2099 # by definition if we've got here it's rather uninteresting.
2100 # We have to remove it so that the next step doesn't trigger
2101 # a conflict on the unique namespace+title index...
2102 $dbw->delete(
'page', array(
'page_id' => $newid ), $fname );
2104 # Save a null revision in the page's history notifying of the move
2105 $nullRevision = Revision::newNullRevision( $dbw, $oldid,
$comment,
true );
2106 $nullRevId = $nullRevision->insertOn( $dbw );
2108 # Change the name of the target page:
2109 $dbw->update(
'page',
2111 'page_touched' => $dbw->timestamp($now),
2112 'page_namespace' => $nt->getNamespace(),
2113 'page_title' => $nt->getDBkey(),
2114 'page_latest' => $nullRevId,
2116 array(
'page_id' => $oldid ),
2119 $linkCache->clearLink( $nt->getPrefixedDBkey() );
2121 # Recreate the redirect, this time in the other direction.
2122 $mwRedir = MagicWord::get(
'redirect' );
2123 $redirectText = $mwRedir->getSynonym( 0 ) .
' [[' . $nt->getPrefixedText() .
"]]\n";
2124 $redirectArticle =
new Article( $this );
2125 $newid = $redirectArticle->insertOn( $dbw );
2126 $redirectRevision =
new Revision( array(
2129 'text' => $redirectText ) );
2130 $redirectRevision->insertOn( $dbw );
2131 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
2135 $log =
new LogPage(
'move' );
2136 $log->addEntry(
'move_redir', $this, $reason, array( 1 => $nt->getPrefixedText() ) );
2138 # Now, we record the link from the redirect to the new title.
2139 # It should have no other outgoing links...
2140 $dbw->delete(
'pagelinks', array(
'pl_from' => $newid ), $fname );
2141 $dbw->insert(
'pagelinks',
2143 'pl_from' => $newid,
2144 'pl_namespace' => $nt->getNamespace(),
2145 'pl_title' => $nt->getDbKey() ),
2149 if ( $wgUseSquid ) {
2150 $urls = array_merge( $nt->getSquidURLs(), $this->
getSquidURLs() );
2151 $u =
new SquidUpdate( $urls );
2162 $fname =
'MovePageForm::moveToNewTitle';
2168 $newid = $nt->getArticleID();
2170 $dbw = wfGetDB( DB_MASTER );
2171 $now = $dbw->timestamp();
2172 $linkCache =& LinkCache::singleton();
2174 # Save a null revision in the page's history notifying of the move
2175 $nullRevision = Revision::newNullRevision( $dbw, $oldid,
$comment,
true );
2176 $nullRevId = $nullRevision->insertOn( $dbw );
2179 $dbw->update(
'page',
2181 'page_touched' => $now,
2182 'page_namespace' => $nt->getNamespace(),
2183 'page_title' => $nt->getDBkey(),
2184 'page_latest' => $nullRevId,
2186 array(
'page_id' => $oldid ),
2190 $linkCache->clearLink( $nt->getPrefixedDBkey() );
2193 $mwRedir = MagicWord::get(
'redirect' );
2194 $redirectText = $mwRedir->getSynonym( 0 ) .
' [[' . $nt->getPrefixedText() .
"]]\n";
2195 $redirectArticle =
new Article( $this );
2196 $newid = $redirectArticle->insertOn( $dbw );
2197 $redirectRevision =
new Revision( array(
2200 'text' => $redirectText ) );
2201 $redirectRevision->insertOn( $dbw );
2202 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
2206 $log =
new LogPage(
'move' );
2207 $log->addEntry(
'move', $this, $reason, array( 1 => $nt->getPrefixedText()) );
2209 # Purge caches as per article creation
2210 Article::onArticleCreate( $nt );
2212 # Record the just-created redirect's linking to the page
2213 $dbw->insert(
'pagelinks',
2215 'pl_from' => $newid,
2216 'pl_namespace' => $nt->getNamespace(),
2217 'pl_title' => $nt->getDBkey() ),
2220 # Purge old title from squid
2221 # The new title, and links to the new title, are purged in Article::onArticleCreate()
2233 $fname =
'Title::isValidMoveTarget';
2234 $dbw = wfGetDB( DB_MASTER );
2237 $id = $nt->getArticleID();
2238 $obj = $dbw->selectRow( array(
'page',
'revision',
'text'),
2239 array(
'page_is_redirect',
'old_text',
'old_flags' ),
2240 array(
'page_id' => $id,
'page_latest=rev_id',
'rev_text_id=old_id' ),
2241 $fname,
'FOR UPDATE' );
2243 if ( !$obj || 0 == $obj->page_is_redirect ) {
2245 wfDebug( __METHOD__ .
": not a redirect\n" );
2248 $text = Revision::getRevisionText( $obj );
2250 # Does the redirect point to the source?
2251 # Or is it a broken self-redirect, usually caused by namespace collisions?
2253 if ( preg_match(
"/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
2255 if( !is_object( $redirTitle ) ||
2257 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
2258 wfDebug( __METHOD__ .
": redirect points to other page\n" );
2263 wfDebug( __METHOD__ .
": failsafe\n" );
2267 # Does the article have a history?
2268 $row = $dbw->selectRow( array(
'page',
'revision'),
2270 array(
'page_namespace' => $nt->getNamespace(),
2271 'page_title' => $nt->getDBkey(),
2272 'page_id=rev_page AND page_latest != rev_id'
2273 ), $fname,
'FOR UPDATE'
2276 # Return true if there was no history
2277 return $row ===
false;
2290 $titlekey = $this->getArticleId();
2291 $dbr = wfGetDB( DB_SLAVE );
2292 $categorylinks = $dbr->tableName(
'categorylinks' );
2295 $sql =
"SELECT * FROM $categorylinks"
2296 .
" WHERE cl_from='$titlekey'"
2297 .
" AND cl_from <> '0'"
2298 .
" ORDER BY cl_sortkey";
2300 $res = $dbr->query ( $sql ) ;
2302 if($dbr->numRows(
$res) > 0) {
2303 while ( $x = $dbr->fetchObject (
$res ) )
2305 $data[$wgContLang->getNSText ( NS_CATEGORY ).
':'.$x->cl_to] = $this->
getFullText();
2306 $dbr->freeResult (
$res ) ;
2321 if($parents !=
'') {
2322 foreach($parents as $parent => $current) {
2323 if ( array_key_exists( $parent, $children ) ) {
2324 # Circular reference
2325 $stack[$parent] = array();
2329 $stack[$parent] = $nt->getParentCategoryTree( $children + array($parent => 1) );
2347 return array(
'page_namespace' => $this->mNamespace,
'page_title' => $this->mDbkeyform );
2357 $dbr = wfGetDB( DB_SLAVE );
2358 return $dbr->selectField(
'revision',
'rev_id',
2359 'rev_page=' . intval( $this->getArticleId() ) .
2360 ' AND rev_id<' . intval(
$revision ) .
' ORDER BY rev_id DESC' );
2370 $dbr = wfGetDB( DB_SLAVE );
2371 return $dbr->selectField(
'revision',
'rev_id',
2372 'rev_page=' . intval( $this->getArticleId() ) .
2373 ' AND rev_id>' . intval(
$revision ) .
' ORDER BY rev_id' );
2384 $dbr = wfGetDB( DB_SLAVE );
2385 return $dbr->selectField(
'revision',
'count(*)',
2386 'rev_page = ' . intval( $this->getArticleId() ) .
2387 ' AND rev_id > ' . intval( $old ) .
2388 ' AND rev_id < ' . intval( $new ) );
2401 && $this->getDbkey() ===
$title->getDbkey();
2409 return $this->getArticleId() != 0;
2420 || NS_SPECIAL == $this->mNamespace;
2429 $u =
new HTMLCacheUpdate( $this,
'pagelinks' );
2433 $u =
new HTMLCacheUpdate( $this,
'categorylinks' );
2442 $dbr = wfGetDB( DB_SLAVE );
2443 $touched = $dbr->selectField(
'page',
'page_touched',
2453 global $wgTitle, $wgScriptPath, $wgServer;
2455 return "$wgServer$wgScriptPath/trackback.php?article="
2456 . htmlspecialchars(urlencode($wgTitle->getPrefixedDBkey()));
2460 $url = htmlspecialchars($this->
getFullURL());
2465 <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"
2466 xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
2467 xmlns:trackback=\"http://madskills.com/public/xml/rss/module/trackback/\">
2470 dc:identifier=\"$url\"
2472 trackback:ping=\"$tburl\" />
2485 return 'nstab-main';
2488 return 'nstab-user';
2490 return 'nstab-media';
2492 return 'nstab-special';
2494 case NS_PROJECT_TALK:
2495 return 'nstab-project';
2498 return 'nstab-image';
2500 case NS_MEDIAWIKI_TALK:
2501 return 'nstab-mediawiki';
2503 case NS_TEMPLATE_TALK:
2504 return 'nstab-template';
2507 return 'nstab-help';
2509 case NS_CATEGORY_TALK:
2510 return 'nstab-category';
2522 list( $thisName, ) = SpecialPage::resolveAliasWithSubpage( $this->
getDBkey() );
2523 if ( $name == $thisName ) {
2536 $canonicalName = SpecialPage::resolveAlias( $this->mDbkeyform );
2537 if ( $canonicalName ) {
2538 $localName = SpecialPage::getLocalNameFor( $canonicalName );
2539 if ( $localName != $this->mDbkeyform ) {