33 use Wikimedia\Assert\Assert;
34 use Wikimedia\Assert\PreconditionException;
203 return MediaWikiServices::getInstance()->getLanguageConverterFactory()
204 ->getLanguageConverter( $language );
224 return MediaWikiServices::getInstance()->getTitleFormatter();
236 return MediaWikiServices::getInstance()->getInterwikiLookup();
254 $t->secureAndSplit( $key );
290 if ( $linkTarget instanceof
Title ) {
292 if ( $forceClone === self::NEW_CLONE ) {
293 return clone $linkTarget;
327 if ( !$pageIdentity ) {
331 if ( $pageIdentity instanceof
Title ) {
332 return $pageIdentity;
335 $pageIdentity->
assertWiki( PageIdentity::LOCAL );
363 if ( $text !==
null && !is_string( $text ) && !is_int( $text ) ) {
364 throw new InvalidArgumentException(
'$text must be a string.' );
366 if ( $text ===
null ) {
397 if ( is_object( $text ) ) {
398 throw new MWException(
'$text must be a string, given an object' );
399 } elseif ( $text ===
null ) {
411 if ( $defaultNamespace ===
NS_MAIN ) {
422 $dbKeyForm = strtr( $filteredText,
' ',
'_' );
424 $t->secureAndSplit( $dbKeyForm, (
int)$defaultNamespace );
425 if ( $defaultNamespace ===
NS_MAIN ) {
449 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
450 # but some URLs used it as a space replacement and they still come
451 # from some external search tools.
452 if ( strpos( self::legalChars(),
'+' ) ===
false ) {
453 $url = strtr( $url,
'+',
' ' );
456 $dbKeyForm = strtr( $url,
' ',
'_' );
459 $t->secureAndSplit( $dbKeyForm );
470 if ( self::$titleCache ===
null ) {
471 self::$titleCache =
new MapCacheLRU( self::CACHE_MAX );
486 'page_namespace',
'page_title',
'page_id',
487 'page_len',
'page_is_redirect',
'page_latest',
488 'page_content_model',
492 $fields[] =
'page_lang';
508 $row =
wfGetDB( $index )->selectRow(
510 self::getSelectFields(),
511 [
'page_id' => $id ],
515 if ( $row !==
false ) {
531 if ( !count( $ids ) ) {
538 self::getSelectFields(),
539 [
'page_id' => $ids ],
544 foreach (
$res as $row ) {
558 $t->loadFromRow( $row );
570 if ( isset( $row->page_id ) ) {
571 $this->mArticleID = (int)$row->page_id;
573 if ( isset( $row->page_len ) ) {
574 $this->mLength = (int)$row->page_len;
576 if ( isset( $row->page_is_redirect ) ) {
577 $this->mRedirect = (bool)$row->page_is_redirect;
579 if ( isset( $row->page_latest ) ) {
580 $this->mLatestID = (int)$row->page_latest;
582 if ( isset( $row->page_content_model ) ) {
587 if ( isset( $row->page_lang ) ) {
588 $this->mDbPageLanguage = (string)$row->page_lang;
590 if ( isset( $row->page_restrictions ) ) {
591 $this->mOldRestrictions = $row->page_restrictions;
594 $this->mArticleID = 0;
596 $this->mRedirect =
false;
597 $this->mLatestID = 0;
626 $t->mInterwiki = $interwiki;
627 $t->mFragment = $fragment;
628 $t->mNamespace = $ns = (int)$ns;
629 $t->mDbkeyform = strtr(
$title,
' ',
'_' );
630 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
632 $t->mTextform = strtr(
$title,
'_',
' ' );
654 if ( !MediaWikiServices::getInstance()->getNamespaceInfo()->
exists( $ns ) ) {
662 $t->secureAndSplit( $dbKeyForm );
688 $msg = $localizer->msg(
'mainpage' );
719 [
'page_namespace',
'page_title' ],
720 [
'page_id' => $id ],
723 if (
$s ===
false ) {
750 $length = strlen( $byteClass );
752 $x0 = $x1 = $x2 =
'';
756 $ord0 = $ord1 = $ord2 = 0;
758 $r0 = $r1 = $r2 =
'';
762 $allowUnicode =
false;
763 for ( $pos = 0; $pos < $length; $pos++ ) {
773 $inChar = $byteClass[$pos];
774 if ( $inChar ===
'\\' ) {
775 if ( preg_match(
'/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
776 $x0 = $inChar . $m[0];
777 $d0 = chr( hexdec( $m[1] ) );
778 $pos += strlen( $m[0] );
779 } elseif ( preg_match(
'/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
780 $x0 = $inChar . $m[0];
781 $d0 = chr( octdec( $m[0] ) );
782 $pos += strlen( $m[0] );
783 } elseif ( $pos + 1 >= $length ) {
786 $d0 = $byteClass[$pos + 1];
795 if ( $ord0 < 32 || $ord0 == 0x7f ) {
796 $r0 = sprintf(
'\x%02x', $ord0 );
797 } elseif ( $ord0 >= 0x80 ) {
799 $r0 = sprintf(
'\x%02x', $ord0 );
800 $allowUnicode =
true;
802 } elseif ( strpos(
'-\\[]^', $d0 ) !==
false ) {
808 if ( $x0 !==
'' && $x1 ===
'-' && $x2 !==
'' ) {
810 if ( $ord2 > $ord0 ) {
812 } elseif ( $ord0 >= 0x80 ) {
814 $allowUnicode =
true;
815 if ( $ord2 < 0x80 ) {
824 $x0 = $x1 = $d0 = $d1 = $r0 = $r1 =
'';
825 } elseif ( $ord2 < 0x80 ) {
831 if ( $ord1 < 0x80 ) {
834 if ( $ord0 < 0x80 ) {
837 if ( $allowUnicode ) {
838 $out .=
'\u0080-\uFFFF';
855 $canonicalNamespace =
false
857 if ( $canonicalNamespace ) {
858 $namespace = MediaWikiServices::getInstance()->getNamespaceInfo()->
859 getCanonicalName( $ns );
861 $namespace = MediaWikiServices::getInstance()->getContentLanguage()->getNsText( $ns );
863 $name = $namespace ==
'' ?
$title :
"$namespace:$title";
864 if ( strval( $interwiki ) !=
'' ) {
865 $name =
"$interwiki:$name";
867 if ( strval( $fragment ) !=
'' ) {
868 $name .=
'#' . $fragment;
903 if ( $this->mIsValid !==
null ) {
909 $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
911 '@phan-var MediaWikiTitleCodec $titleCodec';
912 $parts = $titleCodec->splitTitleString( $text, $this->mNamespace );
916 if ( $parts[
'fragment'] !== $this->mFragment
917 || $parts[
'interwiki'] !== $this->mInterwiki
918 || $parts[
'local_interwiki'] !== $this->mLocalInterwiki
919 || $parts[
'namespace'] !== $this->mNamespace
920 || $parts[
'dbkey'] !== $this->mDbkeyform
922 $this->mIsValid =
false;
926 $this->mIsValid =
false;
930 $this->mIsValid =
true;
945 return $iw->isLocal();
957 return $this->mInterwiki !==
'';
1017 if ( $this->mTitleValue ===
null ) {
1025 }
catch ( InvalidArgumentException $ex ) {
1026 wfDebug( __METHOD__ .
': Can\'t create a TitleValue for [[' .
1079 if ( $this->mForcedContentModel ) {
1080 if ( !$this->mContentModel ) {
1081 throw new RuntimeException(
'Got out of sync; an empty model is being forced' );
1090 ( !$this->mContentModel || $flags & self::GAID_FOR_UPDATE ) &&
1093 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1094 $linkCache->addLinkObj( $this ); # in
case we already had an article ID
1098 if ( !$this->mContentModel ) {
1129 if ( (
string)$model ===
'' ) {
1130 throw new InvalidArgumentException(
"Missing CONTENT_MODEL_* constant" );
1133 $this->mContentModel = $model;
1134 $this->mForcedContentModel =
true;
1143 if ( !$this->mForcedContentModel ) {
1144 $this->mContentModel = ( $model === false ) ?
false : (
string)$model;
1157 $nsText = MediaWikiServices::getInstance()->getNamespaceInfo()->
1158 getCanonicalName( $this->mNamespace );
1159 if ( $nsText !==
false ) {
1166 return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
1167 }
catch ( InvalidArgumentException $ex ) {
1168 wfDebug( __METHOD__ .
': ' . $ex->getMessage() );
1179 $services = MediaWikiServices::getInstance();
1180 return $services->getContentLanguage()->
1181 getNsText( $services->getNamespaceInfo()->getSubject( $this->mNamespace ) );
1190 $services = MediaWikiServices::getInstance();
1191 return $services->getContentLanguage()->
1192 getNsText( $services->getNamespaceInfo()->getTalk( $this->mNamespace ) );
1207 return MediaWikiServices::getInstance()->getNamespaceInfo()->canHaveTalkPage( $this );
1224 if ( $this->mArticleID > 0 ) {
1231 if ( $this->mIsValid ===
false ) {
1246 if ( $this->
getText() ===
'' ) {
1265 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
1267 $nsInfo->isWatchable( $this->mNamespace );
1287 list( $thisName, ) =
1288 MediaWikiServices::getInstance()->getSpecialPageFactory()->
1289 resolveAlias( $this->mDbkeyform );
1290 if ( $name == $thisName ) {
1305 $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
1306 list( $canonicalName, $par ) = $spFactory->resolveAlias( $this->mDbkeyform );
1307 if ( $canonicalName ) {
1308 $localName = $spFactory->getLocalNameFor( $canonicalName, $par );
1309 if ( $localName != $this->mDbkeyform ) {
1328 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1329 equals( $this->mNamespace, $ns );
1340 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1341 $namespaces = $namespaces[0];
1344 foreach ( $namespaces as $ns ) {
1367 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1368 subjectEquals( $this->mNamespace, $ns );
1379 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1380 isContent( $this->mNamespace );
1391 !MediaWikiServices::getInstance()->getNamespaceInfo()->
1414 return $this->
equals( self::newMainPage() );
1423 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1425 ? strpos( $this->
getText(),
'/' ) !== false
1438 strpos( $this->
getText(),
'Conversiontable/' ) === 0;
1493 $subpage = explode(
'/', $this->mTextform );
1494 $subpage = $subpage[count( $subpage ) - 1];
1495 $lastdot = strrpos( $subpage,
'.' );
1496 if ( $lastdot ===
false ) {
1497 return $subpage; # Never happens: only called
for names ending in
'.css'/
'.json'/
'.js'
1499 return substr( $subpage, 0, $lastdot );
1557 || substr( $this->mDbkeyform, -4 ) ===
'.css'
1575 || substr( $this->mDbkeyform, -5 ) ===
'.json'
1593 || substr( $this->mDbkeyform, -3 ) ===
'.js'
1620 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1621 isTalk( $this->mNamespace );
1641 $talkNS = MediaWikiServices::getInstance()->getNamespaceInfo()->getTalk( $this->mNamespace );
1642 if ( $this->mNamespace == $talkNS ) {
1682 $subjectNS = MediaWikiServices::getInstance()->getNamespaceInfo()
1683 ->getSubject( $this->mNamespace );
1684 if ( $this->mNamespace == $subjectNS ) {
1707 if ( $this->
getText() ==
'' ) {
1709 $method .
': called on empty title ' . $this->
getFullText() .
', returning '
1718 $method .
': called on interwiki title ' . $this->
getFullText() .
', returning '
1742 throw new MWException(
'Special pages cannot have other pages' );
1748 throw new MWException(
"{$this->getPrefixedText()} does not have an other page" );
1784 return $this->mFragment !==
'';
1799 if ( $interwiki && !$interwiki->isLocal() ) {
1820 $this->mFragment = strtr( substr( $fragment, 1 ),
'_',
' ' );
1849 $p = $this->mInterwiki .
':';
1852 if ( $this->mNamespace != 0 ) {
1855 if ( $nsText ===
false ) {
1857 $nsText = MediaWikiServices::getInstance()->getContentLanguage()->
1861 $p .= $nsText .
':';
1873 $s = $this->
prefix( $this->mDbkeyform );
1874 $s = strtr(
$s,
' ',
'_' );
1885 if ( $this->prefixedText ===
null ) {
1886 $s = $this->
prefix( $this->mTextform );
1887 $s = strtr(
$s,
'_',
' ' );
1888 $this->prefixedText =
$s;
1931 $top = strlen( $text ) - 1;
1934 while ( $bottom < $top && $text[$bottom] ===
'/' ) {
1938 if ( $top < $bottom ) {
1944 while ( $idx <= $top && $text[$idx] !==
'/' ) {
1949 while ( $idx > $bottom && $text[$idx] !==
'/' ) {
1954 if ( $idx < $bottom || $idx > $top ) {
1970 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1997 if ( $firstSlashPos ===
false ) {
2001 return substr( $text, 0, $firstSlashPos );
2018 Assert::postcondition(
2020 'makeTitleSafe() should always return a Title for the text returned by getRootText().'
2047 if ( $lastSlashPos ===
false ) {
2051 return substr( $text, 0, $lastSlashPos );
2068 Assert::postcondition(
2070 'makeTitleSafe() should always return a Title for the text returned by getBaseText().'
2093 if ( $lastSlashPos ===
false ) {
2097 return substr( $text, $lastSlashPos + 1 );
2116 $this->
getText() .
'/' . $text,
2139 $s = $this->
prefix( $this->mDbkeyform );
2158 if ( $query2 !==
false ) {
2159 wfDeprecatedMsg(
"Title::get{Canonical,Full,Link,Local,Internal}URL " .
2160 "method called with a second parameter is deprecated since MediaWiki 1.19. " .
2161 "Add your parameter to an array passed as the first parameter.",
"1.19" );
2163 if ( is_array( $query ) ) {
2167 if ( is_string( $query2 ) ) {
2198 # Hand off all the decisions on urls to getLocalURL
2201 # Expand the url to make it a full url. Note that getLocalURL has the
2202 # potential to output full urls for a variety of reasons, so we use
2203 # wfExpandUrl instead of simply prepending $wgServer
2206 # Finally, add the fragment.
2236 return $target->getFullURL( $query,
false, $proto );
2270 if ( $namespace !=
'' ) {
2271 # Can this actually happen? Interwikis shouldn't be parsed.
2272 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
2275 $url = $interwiki->getURL( $namespace . $this->mDbkeyform );
2279 if ( $query ==
'' ) {
2290 && preg_match(
'/^(.*&|)action=([^&]*)(&(.*)|)$/', $query,
$matches )
2292 $action = urldecode(
$matches[2] );
2293 if ( isset( $articlePaths[$action] ) ) {
2298 $url = str_replace(
'$1', $dbkey, $articlePaths[$action] );
2299 if ( $query !=
'' ) {
2307 && preg_match(
'/^variant=([^&]*)$/', $query,
$matches )
2309 MediaWikiServices::getInstance()->getContentLanguage() )
2312 $variant = urldecode(
$matches[1] );
2317 $url = str_replace(
'$1', $dbkey, $url );
2321 if ( $url ===
false ) {
2322 if ( $query ==
'-' ) {
2325 $url =
"{$wgScript}?title={$dbkey}&{$query}";
2328 Hooks::runner()->onGetLocalURL__Internal( $this, $url, $query );
2332 if (
$wgRequest->getVal(
'action' ) ==
'render' ) {
2333 LoggerFactory::getInstance(
'T263581' )
2335 "Title::getLocalURL called from render action",
2338 'exception' =>
new Exception()
2370 public function getLinkURL( $query =
'', $query2 =
false, $proto =
false ) {
2371 if ( $this->
isExternal() || $proto !==
false ) {
2372 $ret = $this->
getFullURL( $query, $query2, $proto );
2449 # Remove the create restriction for existing titles
2450 $types = array_diff( $types, [
'create' ] );
2452 # Only the create and upload restrictions apply to non-existing titles
2453 $types = array_intersect( $types, [
'create',
'upload' ] );
2470 if ( $this->mNamespace !==
NS_FILE ) {
2471 # Remove the upload restriction for non-file titles
2472 $types = array_diff( $types, [
'upload' ] );
2475 Hooks::runner()->onTitleGetRestrictionTypes( $this, $types );
2477 wfDebug( __METHOD__ .
': applicable restrictions to [[' .
2478 $this->
getPrefixedText() .
']] are {' . implode(
',', $types ) .
"}" );
2492 if ( $protection ) {
2493 if ( $protection[
'permission'] ==
'sysop' ) {
2494 $protection[
'permission'] =
'editprotected';
2496 if ( $protection[
'permission'] ==
'autoconfirmed' ) {
2497 $protection[
'permission'] =
'editsemiprotected';
2515 if ( $this->mNamespace < 0 ) {
2524 if ( $this->mTitleProtection ===
null ) {
2527 $commentQuery = $commentStore->getJoin(
'pt_reason' );
2529 [
'protected_titles' ] + $commentQuery[
'tables'],
2531 'user' =>
'pt_user',
2532 'expiry' =>
'pt_expiry',
2533 'permission' =>
'pt_create_perm'
2534 ] + $commentQuery[
'fields'],
2535 [
'pt_namespace' => $this->mNamespace,
'pt_title' => $this->mDbkeyform ],
2538 $commentQuery[
'joins']
2544 $this->mTitleProtection = [
2545 'user' => $row[
'user'],
2546 'expiry' =>
$dbr->decodeExpiry( $row[
'expiry'] ),
2547 'permission' => $row[
'permission'],
2548 'reason' => $commentStore->getComment(
'pt_reason', $row )->text,
2551 $this->mTitleProtection =
false;
2565 [
'pt_namespace' => $this->mNamespace,
'pt_title' => $this->mDbkeyform ],
2568 $this->mTitleProtection =
false;
2583 if ( !$restrictions || !$semi ) {
2589 foreach ( array_keys( $semi,
'autoconfirmed' ) as $key ) {
2590 $semi[$key] =
'editsemiprotected';
2592 foreach ( array_keys( $restrictions,
'autoconfirmed' ) as $key ) {
2593 $restrictions[$key] =
'editsemiprotected';
2596 return !array_diff( $restrictions, $semi );
2611 # Special pages have inherent protection
2616 # Check regular protection levels
2617 foreach ( $restrictionTypes as
$type ) {
2618 if ( $action ==
$type || $action ==
'' ) {
2621 if ( in_array( $level, $r ) && $level !=
'' ) {
2643 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
2645 if ( !$permissionManager->userHasRight( $user, $right ) ) {
2660 return $isCascadeProtected;
2673 return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !==
null;
2690 $pagerestrictions = [];
2692 if ( $this->mCascadeSources !==
null && $getPages ) {
2694 } elseif ( $this->mHasCascadingRestrictions !==
null && !$getPages ) {
2700 if ( $this->mNamespace ===
NS_FILE ) {
2701 $tables = [
'imagelinks',
'page_restrictions' ];
2708 $tables = [
'templatelinks',
'page_restrictions' ];
2718 $cols = [
'pr_page',
'page_namespace',
'page_title',
2719 'pr_expiry',
'pr_type',
'pr_level' ];
2720 $where_clauses[] =
'page_id=pr_page';
2723 $cols = [
'pr_expiry' ];
2726 $res =
$dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2728 $sources = $getPages ? [] :
false;
2731 foreach (
$res as $row ) {
2732 $expiry =
$dbr->decodeExpiry( $row->pr_expiry );
2733 if ( $expiry > $now ) {
2735 $page_id = $row->pr_page;
2736 $page_ns = $row->page_namespace;
2737 $page_title = $row->page_title;
2739 # Add groups needed for each restriction type if its not already there
2740 # Make sure this restriction type still exists
2742 if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2743 $pagerestrictions[$row->pr_type] = [];
2747 isset( $pagerestrictions[$row->pr_type] )
2748 && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2750 $pagerestrictions[$row->pr_type][] = $row->pr_level;
2759 $this->mCascadeSources = $sources;
2760 $this->mCascadingRestrictions = $pagerestrictions;
2762 $this->mHasCascadingRestrictions = $sources;
2765 return [ $sources, $pagerestrictions ];
2789 if ( !$this->mRestrictionsLoaded ) {
2792 return $this->mRestrictions[$action] ?? [];
2803 if ( !$this->mRestrictionsLoaded ) {
2817 if ( !$this->mRestrictionsLoaded ) {
2820 return $this->mRestrictionsExpiry[$action] ??
false;
2829 if ( !$this->mRestrictionsLoaded ) {
2855 foreach ( $restrictionTypes as
$type ) {
2856 $this->mRestrictions[
$type] = [];
2857 $this->mRestrictionsExpiry[
$type] =
'infinity';
2860 $this->mCascadeRestriction =
false;
2862 # Backwards-compatibility: also load the restrictions from the page record (old format).
2863 if ( $oldFashionedRestrictions !==
null ) {
2864 $this->mOldRestrictions = $oldFashionedRestrictions;
2867 if ( $this->mOldRestrictions ===
false ) {
2868 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
2869 $linkCache->addLinkObj( $this ); # in
case we already had an article ID
2870 $this->mOldRestrictions = $linkCache->getGoodLinkFieldObj( $this,
'restrictions' );
2873 if ( $this->mOldRestrictions !=
'' ) {
2874 foreach ( explode(
':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2875 $temp = explode(
'=', trim( $restrict ) );
2876 if ( count( $temp ) == 1 ) {
2878 $this->mRestrictions[
'edit'] = explode(
',', trim( $temp[0] ) );
2879 $this->mRestrictions[
'move'] = explode(
',', trim( $temp[0] ) );
2881 $restriction = trim( $temp[1] );
2882 if ( $restriction !=
'' ) {
2883 $this->mRestrictions[$temp[0]] = explode(
',', $restriction );
2889 if ( count( $rows ) ) {
2890 # Current system - load second to make them override.
2893 # Cycle through all the restrictions.
2894 foreach ( $rows as $row ) {
2896 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2900 $expiry =
$dbr->decodeExpiry( $row->pr_expiry );
2903 if ( !$expiry || $expiry > $now ) {
2904 $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2905 $this->mRestrictions[$row->pr_type] = explode(
',', trim( $row->pr_level ) );
2907 $this->mCascadeRestriction = $this->mCascadeRestriction || $row->pr_cascade;
2912 $this->mRestrictionsLoaded =
true;
2927 if ( $this->mRestrictionsLoaded && !$readLatest ) {
2933 $fname = __METHOD__;
2934 $loadRestrictionsFromDb =
function (
IDatabase $dbr ) use ( $fname, $id ) {
2935 return iterator_to_array(
2937 'page_restrictions',
2938 [
'pr_type',
'pr_expiry',
'pr_level',
'pr_cascade' ],
2939 [
'pr_page' => $id ],
2945 if ( $readLatest ) {
2947 $rows = $loadRestrictionsFromDb(
$dbr );
2949 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2950 $rows =
$cache->getWithSetCallback(
2952 $cache->makeKey(
'page-restrictions',
'v1', $id, $this->getLatestRevID() ),
2954 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
2957 $setOpts += Database::getCacheSetOptions(
$dbr );
2958 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2959 if ( $lb->hasOrMadeRecentMasterChanges() ) {
2961 $ttl = WANObjectCache::TTL_UNCACHEABLE;
2964 return $loadRestrictionsFromDb(
$dbr );
2973 if ( $title_protection ) {
2977 if ( !$expiry || $expiry > $now ) {
2979 $this->mRestrictionsExpiry[
'create'] = $expiry;
2980 $this->mRestrictions[
'create'] =
2981 explode(
',', trim( $title_protection[
'permission'] ) );
2983 $this->mTitleProtection =
false;
2986 $this->mRestrictionsExpiry[
'create'] =
'infinity';
2988 $this->mRestrictionsLoaded =
true;
2997 $this->mRestrictionsLoaded =
false;
2998 $this->mTitleProtection =
null;
3015 $config = MediaWikiServices::getInstance()->getMainConfig();
3017 'page_restrictions',
3021 [
'LIMIT' => $config->get(
'UpdateRowsPerQuery' ) ]
3024 $dbw->
delete(
'page_restrictions', [
'pr_id' => $ids ], $fname );
3049 !MediaWikiServices::getInstance()->getNamespaceInfo()->
3056 # We dynamically add a member variable for the purpose of this method
3057 # alone to cache the result. There's no point in having it hanging
3058 # around uninitialized in every Title object; therefore we only add it
3059 # if needed and don't declare it statically.
3060 if ( $this->mHasSubpages ===
null ) {
3061 $this->mHasSubpages =
false;
3064 $this->mHasSubpages = (bool)$subpages->current();
3080 !MediaWikiServices::getInstance()->getNamespaceInfo()->
3088 $conds[] =
'page_title ' .
$dbr->buildLike( $this->mDbkeyform .
'/',
$dbr->anyString() );
3090 if ( $limit > -1 ) {
3091 $options[
'LIMIT'] = $limit;
3094 $dbr->select(
'page',
3095 [
'page_id',
'page_namespace',
'page_title',
'page_is_redirect' ],
3120 if ( $this->mNamespace < 0 ) {
3125 $n =
$dbr->selectField(
'archive',
'COUNT(*)',
3126 [
'ar_namespace' => $this->mNamespace,
'ar_title' => $this->mDbkeyform ],
3129 if ( $this->mNamespace ===
NS_FILE ) {
3130 $n +=
$dbr->selectField(
'filearchive',
'COUNT(*)',
3131 [
'fa_name' => $this->mDbkeyform ],
3156 if ( $this->mNamespace < 0 ) {
3160 $deleted = (bool)
$dbr->selectField(
'archive',
'1',
3161 [
'ar_namespace' => $this->mNamespace,
'ar_title' => $this->mDbkeyform ],
3164 if ( !$deleted && $this->mNamespace ===
NS_FILE ) {
3165 $deleted = (bool)
$dbr->selectField(
'filearchive',
'1',
3166 [
'fa_name' => $this->mDbkeyform ],
3181 if ( $this->mNamespace < 0 ) {
3182 $this->mArticleID = 0;
3187 if ( $flags & self::GAID_FOR_UPDATE ) {
3188 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3189 $oldUpdate = $linkCache->forUpdate(
true );
3190 $linkCache->clearLink( $this );
3191 $this->mArticleID = $linkCache->addLinkObj( $this );
3192 $linkCache->forUpdate( $oldUpdate );
3197 $this->mArticleID = -1;
3199 } elseif ( $this->mArticleID == -1 ) {
3200 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3201 $this->mArticleID = $linkCache->addLinkObj( $this );
3216 $this->mRedirect = (bool)$this->
loadFieldFromDB(
'page_is_redirect', $flags );
3217 } elseif ( $this->mRedirect ===
null ) {
3219 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3220 $linkCache->addLinkObj( $this );
3223 $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this,
'redirect' );
3225 $this->mRedirect =
false;
3243 if ( $this->mLength != -1 ) {
3250 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3251 $linkCache->addLinkObj( $this );
3254 $this->mLength = (int)$linkCache->getGoodLinkFieldObj( $this,
'length' );
3268 $this->mLatestID = (int)$this->
loadFieldFromDB(
'page_latest', $flags );
3270 if ( $this->mLatestID !==
false ) {
3273 $this->mLatestID = 0;
3278 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3279 $linkCache->addLinkObj( $this );
3282 $this->mLatestID = (int)$linkCache->getGoodLinkFieldObj( $this,
'revision' );
3302 if ( $id ===
false ) {
3303 $this->mArticleID = -1;
3305 $this->mArticleID = (int)$id;
3307 $this->mRestrictionsLoaded =
false;
3308 $this->mRestrictions = [];
3309 $this->mOldRestrictions =
false;
3310 $this->mRedirect =
null;
3311 $this->mLength = -1;
3312 $this->mLatestID =
false;
3313 $this->mContentModel =
false;
3314 $this->mForcedContentModel =
false;
3315 $this->mEstimateRevisions =
null;
3316 $this->mPageLanguage =
null;
3317 $this->mDbPageLanguage =
false;
3318 $this->mIsBigDeletion =
null;
3320 MediaWikiServices::getInstance()->getLinkCache()->clearLink( $this );
3324 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3325 $linkCache->clear();
3339 $services = MediaWikiServices::getInstance();
3340 if ( $services->getNamespaceInfo()->isCapitalized( $ns ) ) {
3341 return $services->getContentLanguage()->ucfirst( $text );
3364 if ( $defaultNamespace ===
null ) {
3374 $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3375 '@phan-var MediaWikiTitleCodec $titleCodec';
3377 $parts = $titleCodec->splitTitleString( $text, $defaultNamespace );
3381 $this->mInterwiki = $parts[
'interwiki'];
3382 $this->mLocalInterwiki = $parts[
'local_interwiki'];
3383 $this->mNamespace = $parts[
'namespace'];
3384 $this->mDefaultNamespace = $defaultNamespace;
3386 $this->mDbkeyform = $parts[
'dbkey'];
3387 $this->mUrlform =
wfUrlencode( $this->mDbkeyform );
3388 $this->mTextform = strtr( $this->mDbkeyform,
'_',
' ' );
3391 $this->mIsValid =
true;
3393 # We already know that some pages won't be in the database!
3395 $this->mArticleID = 0;
3411 public function getLinksTo( $options = [], $table =
'pagelinks', $prefix =
'pl' ) {
3412 if ( count( $options ) > 0 ) {
3420 self::getSelectFields(),
3422 "{$prefix}_from=page_id",
3423 "{$prefix}_namespace" => $this->mNamespace,
3424 "{$prefix}_title" => $this->mDbkeyform ],
3430 if (
$res->numRows() ) {
3431 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3432 foreach (
$res as $row ) {
3433 $titleObj =
self::makeTitle( $row->page_namespace, $row->page_title );
3435 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3436 $retVal[] = $titleObj;
3454 return $this->
getLinksTo( $options,
'templatelinks',
'tl' );
3469 public function getLinksFrom( $options = [], $table =
'pagelinks', $prefix =
'pl' ) {
3472 # If the page doesn't exist; there can't be any link from this page
3479 $blNamespace =
"{$prefix}_namespace";
3480 $blTitle =
"{$prefix}_title";
3484 [ $table,
'nestpage' => $pageQuery[
'tables'] ],
3486 [ $blNamespace, $blTitle ],
3487 $pageQuery[
'fields']
3489 [
"{$prefix}_from" => $id ],
3494 [
"page_namespace=$blNamespace",
"page_title=$blTitle" ]
3495 ] ] + $pageQuery[
'joins']
3499 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3500 foreach (
$res as $row ) {
3501 if ( $row->page_id ) {
3505 $linkCache->addBadLinkObj( $titleObj );
3507 $retVal[] = $titleObj;
3524 return $this->
getLinksFrom( $options,
'templatelinks',
'tl' );
3537 # All links from article ID 0 are false positives
3543 [
'page',
'pagelinks' ],
3544 [
'pl_namespace',
'pl_title' ],
3547 'page_namespace IS NULL'
3553 [
'pl_namespace=page_namespace',
'pl_title=page_title' ]
3559 foreach (
$res as $row ) {
3572 $htmlCache = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3573 return $htmlCache->getUrls( $this );
3581 $htmlCache = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
3582 $htmlCache->purgeTitleUrls( $this, $htmlCache::PURGE_INTENT_TXROUND_REFLECTED );
3594 $dbw->startAtomic( __METHOD__ );
3596 $row = $dbw->selectRow(
3598 self::getSelectFields(),
3606 if ( $this->mRedirect && $this->mLatestID ) {
3607 $isSingleRevRedirect = !$dbw->selectField(
3610 [
'rev_page' => $this->mArticleID,
'rev_id != ' . (
int)$this->mLatestID ],
3615 $isSingleRevRedirect =
false;
3618 $dbw->endAtomic( __METHOD__ );
3620 return $isSingleRevRedirect;
3635 if ( $titleKey === 0 ) {
3644 [
'cl_from' => $titleKey ],
3648 if (
$res->numRows() > 0 ) {
3649 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3650 foreach (
$res as $row ) {
3652 $data[$contLang->getNsText(
NS_CATEGORY ) .
':' . $row->cl_to] =
3670 foreach ( $parents as $parent => $current ) {
3671 if ( array_key_exists( $parent, $children ) ) {
3672 # Circular reference
3673 $stack[$parent] = [];
3677 $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3693 if ( $this->mArticleID > 0 ) {
3709 $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3710 $rev = $rl->getRevisionById( $revId, $flags );
3715 $oldRev = ( $dir ===
'next' )
3716 ? $rl->getNextRevision( $rev, $flags )
3717 : $rl->getPreviousRevision( $rev, $flags );
3719 return $oldRev ? $oldRev->getId() :
false;
3756 $rev = MediaWikiServices::getInstance()
3757 ->getRevisionLookup()
3758 ->getFirstRevision( $this, $flags );
3759 return $rev ?
new Revision( $rev ) :
null;
3770 $rev = MediaWikiServices::getInstance()
3771 ->getRevisionLookup()
3772 ->getFirstRevision( $this, $flags );
3773 return $rev ? $rev->getTimestamp() :
null;
3783 return (
bool)
$dbr->selectField(
'page',
'page_is_new', $this->
pageCond(), __METHOD__ );
3798 if ( $this->mIsBigDeletion ===
null ) {
3801 $revCount =
$dbr->selectRowCount(
3821 if ( !$this->
exists() ) {
3825 if ( $this->mEstimateRevisions ===
null ) {
3827 $this->mEstimateRevisions =
$dbr->estimateRowCount(
'revision',
'*',
3847 if ( !( $old instanceof
Revision ) ) {
3850 if ( !( $new instanceof
Revision ) ) {
3853 if ( !$old || !$new ) {
3856 return MediaWikiServices::getInstance()
3857 ->getRevisionStore()
3858 ->countRevisionsBetween(
3860 $old->getRevisionRecord(),
3861 $new->getRevisionRecord(),
3885 if ( !( $old instanceof
Revision ) ) {
3888 if ( !( $new instanceof
Revision ) ) {
3892 $users = MediaWikiServices::getInstance()
3893 ->getRevisionStore()
3894 ->getAuthorsBetween(
3896 $old->getRevisionRecord(),
3897 $new->getRevisionRecord(),
3905 }
catch ( InvalidArgumentException $e ) {
3929 return $authors ? count( $authors ) : 0;
3940 return $this->mInterwiki ===
$title->getInterwiki()
3941 && $this->mNamespace ==
$title->getNamespace()
3942 && $this->mDbkeyform ===
$title->getDBkey();
3952 return $this->mInterwiki ===
$title->mInterwiki
3953 && $this->mNamespace ==
$title->mNamespace
3954 && strpos( $this->mDbkeyform,
$title->mDbkeyform .
'/' ) === 0;
4004 if ( $isKnown !==
null ) {
4012 $services = MediaWikiServices::getInstance();
4013 switch ( $this->mNamespace ) {
4017 return (
bool)$services->getRepoGroup()->findFile( $this );
4020 return $services->getSpecialPageFactory()->exists( $this->mDbkeyform );
4023 return $this->mDbkeyform ==
'';
4058 $services = MediaWikiServices::getInstance();
4063 $contLang = $services->getContentLanguage();
4064 list( $name, ) = $services->getMessageCache()->figureMessage(
4065 $contLang->lcfirst( $this->getText() )
4067 $message =
wfMessage( $name )->inLanguage( $contLang )->useDatabase(
false );
4068 return $message->exists();
4116 list( $name,
$lang ) = MediaWikiServices::getInstance()->getMessageCache()->figureMessage(
4117 MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $this->
getText() )
4119 $message =
wfMessage( $name )->inLanguage(
$lang )->useDatabase(
false );
4121 if ( $message->exists() ) {
4122 return $message->plain();
4138 if ( $this->mArticleID === 0 ) {
4148 function (
IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4149 $dbTimestamp = $dbw->
timestamp( $purgeTime ?: time() );
4152 [
'page_touched' => $dbTimestamp ],
4153 $conds + [
'page_touched < ' . $dbw->
addQuotes( $dbTimestamp ) ],
4157 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4163 DeferredUpdates::PRESEND
4179 [
'causeAction' =>
'page-touch' ]
4185 [
'causeAction' =>
'category-touch' ]
4199 if ( $db ===
null ) {
4202 $touched = $db->selectField(
'page',
'page_touched', $this->
pageCond(), __METHOD__ );
4215 return MediaWikiServices::getInstance()
4216 ->getWatchlistNotificationManager()
4217 ->getTitleNotificationTimestamp( $user, $this );
4228 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
4229 $subjectNS = $nsInfo->getSubject( $this->mNamespace );
4231 $namespaceKey = $nsInfo->getCanonicalName( $subjectNS );
4232 if ( $namespaceKey ===
false ) {
4237 $namespaceKey = MediaWikiServices::getInstance()->getContentLanguage()->lc( $namespaceKey );
4239 if ( $namespaceKey ==
'' ) {
4240 $namespaceKey =
'main';
4243 if ( $namespaceKey ==
'file' ) {
4244 $namespaceKey =
'image';
4246 return $prepend . $namespaceKey;
4267 $where[] =
'rd_interwiki = ' .
$dbr->addQuotes(
'' ) .
' OR rd_interwiki IS NULL';
4269 if ( $ns !==
null ) {
4270 $where[
'page_namespace'] = $ns;
4274 [
'redirect',
'page' ],
4275 [
'page_namespace',
'page_title' ],
4280 foreach (
$res as $row ) {
4296 if ( $this->
isSpecial(
'Userlogout' ) ) {
4300 foreach ( $wgInvalidRedirectTargets as $target ) {
4328 MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces();
4330 return !in_array( $this->mNamespace, $bannedNamespaces );
4344 $unprefixed = $this->
getText();
4351 if ( $prefix !==
'' ) {
4352 # Separate with a line feed, so the unprefixed part is only used as
4353 # a tiebreaker when two pages have the exact same prefix.
4354 # In UCA, tab is the only character that can sort above LF
4355 # so we strip both of them from the original prefix.
4356 $prefix = strtr( $prefix,
"\n\t",
' ' );
4357 return "$prefix\n$unprefixed";
4375 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
4376 $linkCache->addLinkObj( $this );
4377 $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this,
'lang' );
4400 if ( $dbPageLanguage ) {
4404 if ( !$this->mPageLanguage || $this->mPageLanguage[1] !==
$wgLanguageCode ) {
4411 $contentHandler = MediaWikiServices::getInstance()
4412 ->getContentHandlerFactory()
4414 $langObj = $contentHandler->getPageLanguage( $this );
4417 $langObj = MediaWikiServices::getInstance()->getLanguageFactory()
4418 ->getLanguage( $this->mPageLanguage[0] );
4439 if (
$wgLang->getCode() !== $variant ) {
4440 return MediaWikiServices::getInstance()->getLanguageFactory()
4441 ->getLanguage( $variant );
4449 if ( $dbPageLanguage ) {
4452 if ( $pageLang->getCode() !== $variant ) {
4453 $pageLang = MediaWikiServices::getInstance()->getLanguageFactory()
4454 ->getLanguage( $variant );
4463 $contentHandler = MediaWikiServices::getInstance()
4464 ->getContentHandlerFactory()
4466 $pageLang = $contentHandler->getPageViewLanguage( $this );
4486 if ( $msg->exists() ) {
4487 $html = $msg->parseAsBlock();
4489 if ( trim( $html ) !==
'' ) {
4494 'mw-editnotice-namespace',
4503 MediaWikiServices::getInstance()->getNamespaceInfo()->
4507 $editnotice_base = $editnotice_ns;
4508 foreach ( explode(
'/', $this->mDbkeyform ) as $part ) {
4509 $editnotice_base .=
'-' . $part;
4511 if ( $msg->exists() ) {
4512 $html = $msg->parseAsBlock();
4513 if ( trim( $html ) !==
'' ) {
4518 'mw-editnotice-base',
4528 $editnoticeText = $editnotice_ns .
'-' . strtr( $this->mDbkeyform,
'/',
'-' );
4530 if ( $msg->exists() ) {
4531 $html = $msg->parseAsBlock();
4532 if ( trim( $html ) !==
'' ) {
4537 'mw-editnotice-page',
4546 Hooks::runner()->onTitleGetEditNotices( $this, $oldid, $notices );
4556 if ( !in_array( $field, self::getSelectFields(),
true ) ) {
4563 return wfGetDB( $index )->selectField(
4582 'mDefaultNamespace',
4587 $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4588 $this->mUrlform =
wfUrlencode( $this->mDbkeyform );
4589 $this->mTextform = strtr( $this->mDbkeyform,
'_',
' ' );
4602 return PageIdentity::LOCAL;
4613 if ( $wikiId !== PageIdentity::LOCAL ) {
4614 throw new PreconditionException(
4615 "Expected this PageIdentity to belong to $wikiId, "
4616 .
'but it belongs to the local wiki'
4637 public function getId( $wikiId = PageIdentity::LOCAL ) {
4656 Assert::precondition(
4658 'This Title instance does not represent a proper page, but merely a link target.'
4661 Assert::precondition(
4663 'This Title instance represents a fragment link.'