MediaWiki REL1_34
Title.php
Go to the documentation of this file.
1<?php
27use Wikimedia\Assert\Assert;
33
42class Title implements LinkTarget, IDBAccessObject {
44 private static $titleCache = null;
45
51 const CACHE_MAX = 1000;
52
58 const GAID_FOR_UPDATE = 512;
59
67 const NEW_CLONE = 'clone';
68
74 // @{
75
77 public $mTextform = '';
79 public $mUrlform = '';
81 public $mDbkeyform = '';
83 protected $mUserCaseDBKey;
87 public $mInterwiki = '';
89 private $mLocalInterwiki = false;
91 public $mFragment = '';
92
94 public $mArticleID = -1;
95
97 protected $mLatestID = false;
98
103 private $mContentModel = false;
104
109 private $mForcedContentModel = false;
110
113
115 public $mRestrictions = [];
116
123 protected $mOldRestrictions = false;
124
127
130
132 protected $mRestrictionsExpiry = [];
133
136
139
141 public $mRestrictionsLoaded = false;
142
151 public $prefixedText = null;
152
155
162
164 protected $mLength = -1;
165
167 public $mRedirect = null;
168
171
174
177
181 private $mDbPageLanguage = false;
182
184 private $mTitleValue = null;
185
187 private $mIsBigDeletion = null;
188 // @}
189
198 private static function getTitleFormatter() {
199 return MediaWikiServices::getInstance()->getTitleFormatter();
200 }
201
210 private static function getInterwikiLookup() {
211 return MediaWikiServices::getInstance()->getInterwikiLookup();
212 }
213
217 function __construct() {
218 }
219
228 public static function newFromDBkey( $key ) {
229 $t = new self();
230 $t->mDbkeyform = $key;
231
232 try {
233 $t->secureAndSplit();
234 return $t;
235 } catch ( MalformedTitleException $ex ) {
236 return null;
237 }
238 }
239
253 public static function newFromTitleValue( TitleValue $titleValue, $forceClone = '' ) {
254 return self::newFromLinkTarget( $titleValue, $forceClone );
255 }
256
268 public static function newFromLinkTarget( LinkTarget $linkTarget, $forceClone = '' ) {
269 if ( $linkTarget instanceof Title ) {
270 // Special case if it's already a Title object
271 if ( $forceClone === self::NEW_CLONE ) {
272 return clone $linkTarget;
273 } else {
274 return $linkTarget;
275 }
276 }
277 return self::makeTitle(
278 $linkTarget->getNamespace(),
279 $linkTarget->getText(),
280 $linkTarget->getFragment(),
281 $linkTarget->getInterwiki()
282 );
283 }
284
292 public static function castFromLinkTarget( $linkTarget ) {
293 return $linkTarget ? self::newFromLinkTarget( $linkTarget ) : null;
294 }
295
316 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
317 // DWIM: Integers can be passed in here when page titles are used as array keys.
318 if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
319 throw new InvalidArgumentException( '$text must be a string.' );
320 }
321 if ( $text === null ) {
322 return null;
323 }
324
325 try {
326 return self::newFromTextThrow( (string)$text, $defaultNamespace );
327 } catch ( MalformedTitleException $ex ) {
328 return null;
329 }
330 }
331
353 public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
354 if ( is_object( $text ) ) {
355 throw new MWException( '$text must be a string, given an object' );
356 } elseif ( $text === null ) {
357 // Legacy code relies on MalformedTitleException being thrown in this case
358 // (happens when URL with no title in it is parsed). TODO fix
359 throw new MalformedTitleException( 'title-invalid-empty' );
360 }
361
362 $titleCache = self::getTitleCache();
363
364 // Wiki pages often contain multiple links to the same page.
365 // Title normalization and parsing can become expensive on pages with many
366 // links, so we can save a little time by caching them.
367 // In theory these are value objects and won't get changed...
368 if ( $defaultNamespace == NS_MAIN ) {
369 $t = $titleCache->get( $text );
370 if ( $t ) {
371 return $t;
372 }
373 }
374
375 // Convert things like &eacute; &#257; or &#x3017; into normalized (T16952) text
376 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
377
378 $t = new Title();
379 $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
380 $t->mDefaultNamespace = (int)$defaultNamespace;
381
382 $t->secureAndSplit();
383 if ( $defaultNamespace == NS_MAIN ) {
384 $titleCache->set( $text, $t );
385 }
386 return $t;
387 }
388
404 public static function newFromURL( $url ) {
405 $t = new Title();
406
407 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
408 # but some URLs used it as a space replacement and they still come
409 # from some external search tools.
410 if ( strpos( self::legalChars(), '+' ) === false ) {
411 $url = strtr( $url, '+', ' ' );
412 }
413
414 $t->mDbkeyform = strtr( $url, ' ', '_' );
415
416 try {
417 $t->secureAndSplit();
418 return $t;
419 } catch ( MalformedTitleException $ex ) {
420 return null;
421 }
422 }
423
427 private static function getTitleCache() {
428 if ( self::$titleCache === null ) {
429 self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
430 }
431 return self::$titleCache;
432 }
433
441 protected static function getSelectFields() {
443
444 $fields = [
445 'page_namespace', 'page_title', 'page_id',
446 'page_len', 'page_is_redirect', 'page_latest',
447 ];
448
450 $fields[] = 'page_content_model';
451 }
452
453 if ( $wgPageLanguageUseDB ) {
454 $fields[] = 'page_lang';
455 }
456
457 return $fields;
458 }
459
467 public static function newFromID( $id, $flags = 0 ) {
468 $flags |= ( $flags & self::GAID_FOR_UPDATE ) ? self::READ_LATEST : 0; // b/c
469 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
470 $row = wfGetDB( $index )->selectRow(
471 'page',
472 self::getSelectFields(),
473 [ 'page_id' => $id ],
474 __METHOD__,
475 $options
476 );
477 if ( $row !== false ) {
478 $title = self::newFromRow( $row );
479 } else {
480 $title = null;
481 }
482
483 return $title;
484 }
485
492 public static function newFromIDs( $ids ) {
493 if ( !count( $ids ) ) {
494 return [];
495 }
497
498 $res = $dbr->select(
499 'page',
500 self::getSelectFields(),
501 [ 'page_id' => $ids ],
502 __METHOD__
503 );
504
505 $titles = [];
506 foreach ( $res as $row ) {
507 $titles[] = self::newFromRow( $row );
508 }
509 return $titles;
510 }
511
518 public static function newFromRow( $row ) {
519 $t = self::makeTitle( $row->page_namespace, $row->page_title );
520 $t->loadFromRow( $row );
521 return $t;
522 }
523
530 public function loadFromRow( $row ) {
531 if ( $row ) { // page found
532 if ( isset( $row->page_id ) ) {
533 $this->mArticleID = (int)$row->page_id;
534 }
535 if ( isset( $row->page_len ) ) {
536 $this->mLength = (int)$row->page_len;
537 }
538 if ( isset( $row->page_is_redirect ) ) {
539 $this->mRedirect = (bool)$row->page_is_redirect;
540 }
541 if ( isset( $row->page_latest ) ) {
542 $this->mLatestID = (int)$row->page_latest;
543 }
544 if ( isset( $row->page_content_model ) ) {
545 $this->lazyFillContentModel( $row->page_content_model );
546 } else {
547 $this->lazyFillContentModel( false ); // lazily-load getContentModel()
548 }
549 if ( isset( $row->page_lang ) ) {
550 $this->mDbPageLanguage = (string)$row->page_lang;
551 }
552 if ( isset( $row->page_restrictions ) ) {
553 $this->mOldRestrictions = $row->page_restrictions;
554 }
555 } else { // page not found
556 $this->mArticleID = 0;
557 $this->mLength = 0;
558 $this->mRedirect = false;
559 $this->mLatestID = 0;
560 $this->lazyFillContentModel( false ); // lazily-load getContentModel()
561 }
562 }
563
586 public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
587 $t = new Title();
588 $t->mInterwiki = $interwiki;
589 $t->mFragment = $fragment;
590 $t->mNamespace = $ns = (int)$ns;
591 $t->mDbkeyform = strtr( $title, ' ', '_' );
592 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
593 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
594 $t->mTextform = strtr( $title, '_', ' ' );
595 return $t;
596 }
597
613 public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
614 // NOTE: ideally, this would just call makeTitle() and then isValid(),
615 // but presently, that means more overhead on a potential performance hotspot.
616
617 if ( !MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $ns ) ) {
618 return null;
619 }
620
621 $t = new Title();
622 $t->mDbkeyform = self::makeName( $ns, $title, $fragment, $interwiki, true );
623
624 try {
625 $t->secureAndSplit();
626 return $t;
627 } catch ( MalformedTitleException $ex ) {
628 return null;
629 }
630 }
631
649 public static function newMainPage( MessageLocalizer $localizer = null ) {
650 if ( $localizer ) {
651 $msg = $localizer->msg( 'mainpage' );
652 } else {
653 $msg = wfMessage( 'mainpage' );
654 }
655
656 $title = self::newFromText( $msg->inContentLanguage()->text() );
657
658 // Every page renders at least one link to the Main Page (e.g. sidebar).
659 // If the localised value is invalid, don't produce fatal errors that
660 // would make the wiki inaccessible (and hard to fix the invalid message).
661 // Gracefully fallback...
662 if ( !$title ) {
663 $title = self::newFromText( 'Main Page' );
664 }
665 return $title;
666 }
667
674 public static function nameOf( $id ) {
676
677 $s = $dbr->selectRow(
678 'page',
679 [ 'page_namespace', 'page_title' ],
680 [ 'page_id' => $id ],
681 __METHOD__
682 );
683 if ( $s === false ) {
684 return null;
685 }
686
687 return self::makeName( $s->page_namespace, $s->page_title );
688 }
689
695 public static function legalChars() {
696 global $wgLegalTitleChars;
697 return $wgLegalTitleChars;
698 }
699
709 public static function convertByteClassToUnicodeClass( $byteClass ) {
710 $length = strlen( $byteClass );
711 // Input token queue
712 $x0 = $x1 = $x2 = '';
713 // Decoded queue
714 $d0 = $d1 = $d2 = '';
715 // Decoded integer codepoints
716 $ord0 = $ord1 = $ord2 = 0;
717 // Re-encoded queue
718 $r0 = $r1 = $r2 = '';
719 // Output
720 $out = '';
721 // Flags
722 $allowUnicode = false;
723 for ( $pos = 0; $pos < $length; $pos++ ) {
724 // Shift the queues down
725 $x2 = $x1;
726 $x1 = $x0;
727 $d2 = $d1;
728 $d1 = $d0;
729 $ord2 = $ord1;
730 $ord1 = $ord0;
731 $r2 = $r1;
732 $r1 = $r0;
733 // Load the current input token and decoded values
734 $inChar = $byteClass[$pos];
735 if ( $inChar == '\\' ) {
736 if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
737 $x0 = $inChar . $m[0];
738 $d0 = chr( hexdec( $m[1] ) );
739 $pos += strlen( $m[0] );
740 } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
741 $x0 = $inChar . $m[0];
742 $d0 = chr( octdec( $m[0] ) );
743 $pos += strlen( $m[0] );
744 } elseif ( $pos + 1 >= $length ) {
745 $x0 = $d0 = '\\';
746 } else {
747 $d0 = $byteClass[$pos + 1];
748 $x0 = $inChar . $d0;
749 $pos += 1;
750 }
751 } else {
752 $x0 = $d0 = $inChar;
753 }
754 $ord0 = ord( $d0 );
755 // Load the current re-encoded value
756 if ( $ord0 < 32 || $ord0 == 0x7f ) {
757 $r0 = sprintf( '\x%02x', $ord0 );
758 } elseif ( $ord0 >= 0x80 ) {
759 // Allow unicode if a single high-bit character appears
760 $r0 = sprintf( '\x%02x', $ord0 );
761 $allowUnicode = true;
762 // @phan-suppress-next-line PhanParamSuspiciousOrder false positive
763 } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
764 $r0 = '\\' . $d0;
765 } else {
766 $r0 = $d0;
767 }
768 // Do the output
769 if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
770 // Range
771 if ( $ord2 > $ord0 ) {
772 // Empty range
773 } elseif ( $ord0 >= 0x80 ) {
774 // Unicode range
775 $allowUnicode = true;
776 if ( $ord2 < 0x80 ) {
777 // Keep the non-unicode section of the range
778 $out .= "$r2-\\x7F";
779 }
780 } else {
781 // Normal range
782 $out .= "$r2-$r0";
783 }
784 // Reset state to the initial value
785 $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
786 } elseif ( $ord2 < 0x80 ) {
787 // ASCII character
788 $out .= $r2;
789 }
790 }
791 if ( $ord1 < 0x80 ) {
792 $out .= $r1;
793 }
794 if ( $ord0 < 0x80 ) {
795 $out .= $r0;
796 }
797 if ( $allowUnicode ) {
798 $out .= '\u0080-\uFFFF';
799 }
800 return $out;
801 }
802
814 public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
815 $canonicalNamespace = false
816 ) {
817 if ( $canonicalNamespace ) {
818 $namespace = MediaWikiServices::getInstance()->getNamespaceInfo()->
819 getCanonicalName( $ns );
820 } else {
821 $namespace = MediaWikiServices::getInstance()->getContentLanguage()->getNsText( $ns );
822 }
823 $name = $namespace == '' ? $title : "$namespace:$title";
824 if ( strval( $interwiki ) != '' ) {
825 $name = "$interwiki:$name";
826 }
827 if ( strval( $fragment ) != '' ) {
828 $name .= '#' . $fragment;
829 }
830 return $name;
831 }
832
841 public static function compare( LinkTarget $a, LinkTarget $b ) {
842 return $a->getNamespace() <=> $b->getNamespace()
843 ?: strcmp( $a->getText(), $b->getText() );
844 }
845
863 public function isValid() {
864 $services = MediaWikiServices::getInstance();
865 if ( !$services->getNamespaceInfo()->exists( $this->mNamespace ) ) {
866 return false;
867 }
868
869 try {
870 $services->getTitleParser()->parseTitle( $this->mDbkeyform, $this->mNamespace );
871 } catch ( MalformedTitleException $ex ) {
872 return false;
873 }
874
875 try {
876 // Title value applies basic syntax checks. Should perhaps be moved elsewhere.
877 new TitleValue(
878 $this->mNamespace,
879 $this->mDbkeyform,
880 $this->mFragment,
881 $this->mInterwiki
882 );
883 } catch ( InvalidArgumentException $ex ) {
884 return false;
885 }
886
887 return true;
888 }
889
897 public function isLocal() {
898 if ( $this->isExternal() ) {
899 $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
900 if ( $iw ) {
901 return $iw->isLocal();
902 }
903 }
904 return true;
905 }
906
912 public function isExternal() {
913 return $this->mInterwiki !== '';
914 }
915
923 public function getInterwiki() {
924 return $this->mInterwiki;
925 }
926
932 public function wasLocalInterwiki() {
933 return $this->mLocalInterwiki;
934 }
935
942 public function isTrans() {
943 if ( !$this->isExternal() ) {
944 return false;
945 }
946
947 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
948 }
949
955 public function getTransWikiID() {
956 if ( !$this->isExternal() ) {
957 return false;
958 }
959
960 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
961 }
962
972 public function getTitleValue() {
973 if ( $this->mTitleValue === null ) {
974 try {
975 $this->mTitleValue = new TitleValue(
976 $this->mNamespace,
977 $this->mDbkeyform,
978 $this->mFragment,
979 $this->mInterwiki
980 );
981 } catch ( InvalidArgumentException $ex ) {
982 wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
983 $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
984 }
985 }
986
987 return $this->mTitleValue;
988 }
989
995 public function getText() {
996 return $this->mTextform;
997 }
998
1004 public function getPartialURL() {
1005 return $this->mUrlform;
1006 }
1007
1013 public function getDBkey() {
1014 return $this->mDbkeyform;
1015 }
1016
1023 function getUserCaseDBKey() {
1024 if ( !is_null( $this->mUserCaseDBKey ) ) {
1025 return $this->mUserCaseDBKey;
1026 } else {
1027 // If created via makeTitle(), $this->mUserCaseDBKey is not set.
1028 return $this->mDbkeyform;
1029 }
1030 }
1031
1037 public function getNamespace() {
1038 return $this->mNamespace;
1039 }
1040
1049 public function getContentModel( $flags = 0 ) {
1050 if ( $this->mForcedContentModel ) {
1051 if ( !$this->mContentModel ) {
1052 throw new RuntimeException( 'Got out of sync; an empty model is being forced' );
1053 }
1054 // Content model is locked to the currently loaded one
1055 return $this->mContentModel;
1056 }
1057
1058 if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
1059 $this->lazyFillContentModel( $this->loadFieldFromDB( 'page_content_model', $flags ) );
1060 } elseif (
1061 ( !$this->mContentModel || $flags & self::GAID_FOR_UPDATE ) &&
1062 $this->getArticleID( $flags )
1063 ) {
1064 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1065 $linkCache->addLinkObj( $this ); # in case we already had an article ID
1066 $this->lazyFillContentModel( $linkCache->getGoodLinkFieldObj( $this, 'model' ) );
1067 }
1068
1069 if ( !$this->mContentModel ) {
1070 $this->lazyFillContentModel( ContentHandler::getDefaultModelFor( $this ) );
1071 }
1072
1073 return $this->mContentModel;
1074 }
1075
1082 public function hasContentModel( $id ) {
1083 return $this->getContentModel() == $id;
1084 }
1085
1099 public function setContentModel( $model ) {
1100 if ( (string)$model === '' ) {
1101 throw new InvalidArgumentException( "Missing CONTENT_MODEL_* constant" );
1102 }
1103
1104 $this->mContentModel = $model;
1105 $this->mForcedContentModel = true;
1106 }
1107
1113 private function lazyFillContentModel( $model ) {
1114 if ( !$this->mForcedContentModel ) {
1115 $this->mContentModel = ( $model === false ) ? false : (string)$model;
1116 }
1117 }
1118
1124 public function getNsText() {
1125 if ( $this->isExternal() ) {
1126 // This probably shouldn't even happen, except for interwiki transclusion.
1127 // If possible, use the canonical name for the foreign namespace.
1128 $nsText = MediaWikiServices::getInstance()->getNamespaceInfo()->
1129 getCanonicalName( $this->mNamespace );
1130 if ( $nsText !== false ) {
1131 return $nsText;
1132 }
1133 }
1134
1135 try {
1136 $formatter = self::getTitleFormatter();
1137 return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
1138 } catch ( InvalidArgumentException $ex ) {
1139 wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
1140 return false;
1141 }
1142 }
1143
1149 public function getSubjectNsText() {
1150 $services = MediaWikiServices::getInstance();
1151 return $services->getContentLanguage()->
1152 getNsText( $services->getNamespaceInfo()->getSubject( $this->mNamespace ) );
1153 }
1154
1160 public function getTalkNsText() {
1161 $services = MediaWikiServices::getInstance();
1162 return $services->getContentLanguage()->
1163 getNsText( $services->getNamespaceInfo()->getTalk( $this->mNamespace ) );
1164 }
1165
1177 public function canHaveTalkPage() {
1178 return MediaWikiServices::getInstance()->getNamespaceInfo()->canHaveTalkPage( $this );
1179 }
1180
1186 public function canExist() {
1187 return $this->mNamespace >= NS_MAIN;
1188 }
1189
1198 public function isWatchable() {
1199 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
1200 return $this->getText() !== '' && !$this->isExternal() &&
1201 $nsInfo->isWatchable( $this->mNamespace );
1202 }
1203
1209 public function isSpecialPage() {
1210 return $this->mNamespace == NS_SPECIAL;
1211 }
1212
1219 public function isSpecial( $name ) {
1220 if ( $this->isSpecialPage() ) {
1221 list( $thisName, /* $subpage */ ) =
1222 MediaWikiServices::getInstance()->getSpecialPageFactory()->
1223 resolveAlias( $this->mDbkeyform );
1224 if ( $name == $thisName ) {
1225 return true;
1226 }
1227 }
1228 return false;
1229 }
1230
1237 public function fixSpecialName() {
1238 if ( $this->isSpecialPage() ) {
1239 $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
1240 list( $canonicalName, $par ) = $spFactory->resolveAlias( $this->mDbkeyform );
1241 if ( $canonicalName ) {
1242 $localName = $spFactory->getLocalNameFor( $canonicalName, $par );
1243 if ( $localName != $this->mDbkeyform ) {
1244 return self::makeTitle( NS_SPECIAL, $localName );
1245 }
1246 }
1247 }
1248 return $this;
1249 }
1250
1261 public function inNamespace( $ns ) {
1262 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1263 equals( $this->mNamespace, $ns );
1264 }
1265
1273 public function inNamespaces( ...$namespaces ) {
1274 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1275 $namespaces = $namespaces[0];
1276 }
1277
1278 foreach ( $namespaces as $ns ) {
1279 if ( $this->inNamespace( $ns ) ) {
1280 return true;
1281 }
1282 }
1283
1284 return false;
1285 }
1286
1300 public function hasSubjectNamespace( $ns ) {
1301 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1302 subjectEquals( $this->mNamespace, $ns );
1303 }
1304
1312 public function isContentPage() {
1313 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1314 isContent( $this->mNamespace );
1315 }
1316
1323 public function isMovable() {
1324 if (
1325 !MediaWikiServices::getInstance()->getNamespaceInfo()->
1326 isMovable( $this->mNamespace ) || $this->isExternal()
1327 ) {
1328 // Interwiki title or immovable namespace. Hooks don't get to override here
1329 return false;
1330 }
1331
1332 $result = true;
1333 Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1334 return $result;
1335 }
1336
1347 public function isMainPage() {
1348 return $this->equals( self::newMainPage() );
1349 }
1350
1356 public function isSubpage() {
1357 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1358 hasSubpages( $this->mNamespace )
1359 ? strpos( $this->getText(), '/' ) !== false
1360 : false;
1361 }
1362
1368 public function isConversionTable() {
1369 // @todo ConversionTable should become a separate content model.
1370
1371 return $this->mNamespace == NS_MEDIAWIKI &&
1372 strpos( $this->getText(), 'Conversiontable/' ) === 0;
1373 }
1374
1380 public function isWikitextPage() {
1381 return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1382 }
1383
1398 public function isSiteConfigPage() {
1399 return (
1400 $this->isSiteCssConfigPage()
1401 || $this->isSiteJsonConfigPage()
1402 || $this->isSiteJsConfigPage()
1403 );
1404 }
1405
1412 public function isUserConfigPage() {
1413 return (
1414 $this->isUserCssConfigPage()
1415 || $this->isUserJsonConfigPage()
1416 || $this->isUserJsConfigPage()
1417 );
1418 }
1419
1426 public function getSkinFromConfigSubpage() {
1427 $subpage = explode( '/', $this->mTextform );
1428 $subpage = $subpage[count( $subpage ) - 1];
1429 $lastdot = strrpos( $subpage, '.' );
1430 if ( $lastdot === false ) {
1431 return $subpage; # Never happens: only called for names ending in '.css'/'.json'/'.js'
1432 }
1433 return substr( $subpage, 0, $lastdot );
1434 }
1435
1442 public function isUserCssConfigPage() {
1443 return (
1444 NS_USER == $this->mNamespace
1445 && $this->isSubpage()
1447 );
1448 }
1449
1456 public function isUserJsonConfigPage() {
1457 return (
1458 NS_USER == $this->mNamespace
1459 && $this->isSubpage()
1461 );
1462 }
1463
1470 public function isUserJsConfigPage() {
1471 return (
1472 NS_USER == $this->mNamespace
1473 && $this->isSubpage()
1475 );
1476 }
1477
1484 public function isSiteCssConfigPage() {
1485 return (
1486 NS_MEDIAWIKI == $this->mNamespace
1487 && (
1489 // paranoia - a MediaWiki: namespace page with mismatching extension and content
1490 // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1491 || substr( $this->mDbkeyform, -4 ) === '.css'
1492 )
1493 );
1494 }
1495
1502 public function isSiteJsonConfigPage() {
1503 return (
1504 NS_MEDIAWIKI == $this->mNamespace
1505 && (
1507 // paranoia - a MediaWiki: namespace page with mismatching extension and content
1508 // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1509 || substr( $this->mDbkeyform, -5 ) === '.json'
1510 )
1511 );
1512 }
1513
1520 public function isSiteJsConfigPage() {
1521 return (
1522 NS_MEDIAWIKI == $this->mNamespace
1523 && (
1525 // paranoia - a MediaWiki: namespace page with mismatching extension and content
1526 // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1527 || substr( $this->mDbkeyform, -3 ) === '.js'
1528 )
1529 );
1530 }
1531
1538 public function isRawHtmlMessage() {
1539 global $wgRawHtmlMessages;
1540
1541 if ( !$this->inNamespace( NS_MEDIAWIKI ) ) {
1542 return false;
1543 }
1544 $message = lcfirst( $this->getRootTitle()->getDBkey() );
1545 return in_array( $message, $wgRawHtmlMessages, true );
1546 }
1547
1553 public function isTalkPage() {
1554 return MediaWikiServices::getInstance()->getNamespaceInfo()->
1555 isTalk( $this->mNamespace );
1556 }
1557
1569 public function getTalkPage() {
1570 // NOTE: The equivalent code in NamespaceInfo is less lenient about producing invalid titles.
1571 // Instead of failing on invalid titles, let's just log the issue for now.
1572 // See the discussion on T227817.
1573
1574 // Is this the same title?
1575 $talkNS = MediaWikiServices::getInstance()->getNamespaceInfo()->getTalk( $this->mNamespace );
1576 if ( $this->mNamespace == $talkNS ) {
1577 return $this;
1578 }
1579
1580 $title = self::makeTitle( $talkNS, $this->mDbkeyform );
1581
1582 $this->warnIfPageCannotExist( $title, __METHOD__ );
1583
1584 return $title;
1585 // TODO: replace the above with the code below:
1586 // return self::castFromLinkTarget(
1587 // MediaWikiServices::getInstance()->getNamespaceInfo()->getTalkPage( $this ) );
1588 }
1589
1599 public function getTalkPageIfDefined() {
1600 if ( !$this->canHaveTalkPage() ) {
1601 return null;
1602 }
1603
1604 return $this->getTalkPage();
1605 }
1606
1614 public function getSubjectPage() {
1615 // Is this the same title?
1616 $subjectNS = MediaWikiServices::getInstance()->getNamespaceInfo()
1617 ->getSubject( $this->mNamespace );
1618 if ( $this->mNamespace == $subjectNS ) {
1619 return $this;
1620 }
1621 // NOTE: The equivalent code in NamespaceInfo is less lenient about producing invalid titles.
1622 // Instead of failing on invalid titles, let's just log the issue for now.
1623 // See the discussion on T227817.
1624 $title = self::makeTitle( $subjectNS, $this->mDbkeyform );
1625
1626 $this->warnIfPageCannotExist( $title, __METHOD__ );
1627
1628 return $title;
1629 // TODO: replace the above with the code below:
1630 // return self::castFromLinkTarget(
1631 // MediaWikiServices::getInstance()->getNamespaceInfo()->getSubjectPage( $this ) );
1632 }
1633
1640 private function warnIfPageCannotExist( Title $title, $method ) {
1641 if ( $this->getText() == '' ) {
1643 $method . ': called on empty title ' . $this->getFullText() . ', returning '
1644 . $title->getFullText()
1645 );
1646
1647 return true;
1648 }
1649
1650 if ( $this->getInterwiki() !== '' ) {
1652 $method . ': called on interwiki title ' . $this->getFullText() . ', returning '
1653 . $title->getFullText()
1654 );
1655
1656 return true;
1657 }
1658
1659 return false;
1660 }
1661
1671 public function getOtherPage() {
1672 // NOTE: Depend on the methods in this class instead of their equivalent in NamespaceInfo,
1673 // until their semantics has become exactly the same.
1674 // See the discussion on T227817.
1675 if ( $this->isSpecialPage() ) {
1676 throw new MWException( 'Special pages cannot have other pages' );
1677 }
1678 if ( $this->isTalkPage() ) {
1679 return $this->getSubjectPage();
1680 } else {
1681 if ( !$this->canHaveTalkPage() ) {
1682 throw new MWException( "{$this->getPrefixedText()} does not have an other page" );
1683 }
1684 return $this->getTalkPage();
1685 }
1686 // TODO: replace the above with the code below:
1687 // return self::castFromLinkTarget(
1688 // MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociatedPage( $this ) );
1689 }
1690
1696 public function getDefaultNamespace() {
1697 return $this->mDefaultNamespace;
1698 }
1699
1707 public function getFragment() {
1708 return $this->mFragment;
1709 }
1710
1717 public function hasFragment() {
1718 return $this->mFragment !== '';
1719 }
1720
1726 public function getFragmentForURL() {
1727 if ( !$this->hasFragment() ) {
1728 return '';
1729 } elseif ( $this->isExternal() ) {
1730 // Note: If the interwiki is unknown, it's treated as a namespace on the local wiki,
1731 // so we treat it like a local interwiki.
1732 $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1733 if ( $interwiki && !$interwiki->isLocal() ) {
1734 return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->mFragment );
1735 }
1736 }
1737
1738 return '#' . Sanitizer::escapeIdForLink( $this->mFragment );
1739 }
1740
1753 public function setFragment( $fragment ) {
1754 $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1755 }
1756
1764 public function createFragmentTarget( $fragment ) {
1765 return self::makeTitle(
1766 $this->mNamespace,
1767 $this->getText(),
1768 $fragment,
1769 $this->mInterwiki
1770 );
1771 }
1772
1780 private function prefix( $name ) {
1781 $p = '';
1782 if ( $this->isExternal() ) {
1783 $p = $this->mInterwiki . ':';
1784 }
1785
1786 if ( $this->mNamespace != 0 ) {
1787 $nsText = $this->getNsText();
1788
1789 if ( $nsText === false ) {
1790 // See T165149. Awkward, but better than erroneously linking to the main namespace.
1791 $nsText = MediaWikiServices::getInstance()->getContentLanguage()->
1792 getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}";
1793 }
1794
1795 $p .= $nsText . ':';
1796 }
1797 return $p . $name;
1798 }
1799
1806 public function getPrefixedDBkey() {
1807 $s = $this->prefix( $this->mDbkeyform );
1808 $s = strtr( $s, ' ', '_' );
1809 return $s;
1810 }
1811
1818 public function getPrefixedText() {
1819 if ( $this->prefixedText === null ) {
1820 $s = $this->prefix( $this->mTextform );
1821 $s = strtr( $s, '_', ' ' );
1822 $this->prefixedText = $s;
1823 }
1824 return $this->prefixedText;
1825 }
1826
1832 public function __toString() {
1833 return $this->getPrefixedText();
1834 }
1835
1842 public function getFullText() {
1843 $text = $this->getPrefixedText();
1844 if ( $this->hasFragment() ) {
1845 $text .= '#' . $this->mFragment;
1846 }
1847 return $text;
1848 }
1849
1865 public function getRootText() {
1866 if (
1867 !MediaWikiServices::getInstance()->getNamespaceInfo()->
1868 hasSubpages( $this->mNamespace )
1869 || strtok( $this->getText(), '/' ) === false
1870 ) {
1871 return $this->getText();
1872 }
1873
1874 return strtok( $this->getText(), '/' );
1875 }
1876
1889 public function getRootTitle() {
1890 $title = self::makeTitleSafe( $this->mNamespace, $this->getRootText() );
1891 Assert::postcondition(
1892 $title !== null,
1893 'makeTitleSafe() should always return a Title for the text returned by getRootText().'
1894 );
1895 return $title;
1896 }
1897
1912 public function getBaseText() {
1913 $text = $this->getText();
1914 if (
1915 !MediaWikiServices::getInstance()->getNamespaceInfo()->
1916 hasSubpages( $this->mNamespace )
1917 ) {
1918 return $text;
1919 }
1920
1921 $lastSlashPos = strrpos( $text, '/' );
1922 // Don't discard the real title if there's no subpage involved
1923 if ( $lastSlashPos === false ) {
1924 return $text;
1925 }
1926
1927 return substr( $text, 0, $lastSlashPos );
1928 }
1929
1942 public function getBaseTitle() {
1943 $title = self::makeTitleSafe( $this->mNamespace, $this->getBaseText() );
1944 Assert::postcondition(
1945 $title !== null,
1946 'makeTitleSafe() should always return a Title for the text returned by getBaseText().'
1947 );
1948 return $title;
1949 }
1950
1962 public function getSubpageText() {
1963 if (
1964 !MediaWikiServices::getInstance()->getNamespaceInfo()->
1965 hasSubpages( $this->mNamespace )
1966 ) {
1967 return $this->mTextform;
1968 }
1969 $parts = explode( '/', $this->mTextform );
1970 return $parts[count( $parts ) - 1];
1971 }
1972
1986 public function getSubpage( $text ) {
1987 return self::makeTitleSafe(
1988 $this->mNamespace,
1989 $this->getText() . '/' . $text,
1990 '',
1991 $this->mInterwiki
1992 );
1993 }
1994
2000 public function getSubpageUrlForm() {
2001 $text = $this->getSubpageText();
2002 $text = wfUrlencode( strtr( $text, ' ', '_' ) );
2003 return $text;
2004 }
2005
2011 public function getPrefixedURL() {
2012 $s = $this->prefix( $this->mDbkeyform );
2013 $s = wfUrlencode( strtr( $s, ' ', '_' ) );
2014 return $s;
2015 }
2016
2030 private static function fixUrlQueryArgs( $query, $query2 = false ) {
2031 if ( $query2 !== false ) {
2032 wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
2033 "method called with a second parameter is deprecated. Add your " .
2034 "parameter to an array passed as the first parameter.", "1.19" );
2035 }
2036 if ( is_array( $query ) ) {
2037 $query = wfArrayToCgi( $query );
2038 }
2039 if ( $query2 ) {
2040 if ( is_string( $query2 ) ) {
2041 // $query2 is a string, we will consider this to be
2042 // a deprecated $variant argument and add it to the query
2043 $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
2044 } else {
2045 $query2 = wfArrayToCgi( $query2 );
2046 }
2047 // If we have $query content add a & to it first
2048 if ( $query ) {
2049 $query .= '&';
2050 }
2051 // Now append the queries together
2052 $query .= $query2;
2053 }
2054 return $query;
2055 }
2056
2068 public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
2069 $query = self::fixUrlQueryArgs( $query, $query2 );
2070
2071 # Hand off all the decisions on urls to getLocalURL
2072 $url = $this->getLocalURL( $query );
2073
2074 # Expand the url to make it a full url. Note that getLocalURL has the
2075 # potential to output full urls for a variety of reasons, so we use
2076 # wfExpandUrl instead of simply prepending $wgServer
2077 $url = wfExpandUrl( $url, $proto );
2078
2079 # Finally, add the fragment.
2080 $url .= $this->getFragmentForURL();
2081 // Avoid PHP 7.1 warning from passing $this by reference
2082 $titleRef = $this;
2083 Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
2084 return $url;
2085 }
2086
2103 public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
2104 $target = $this;
2105 if ( $this->isExternal() ) {
2106 $target = SpecialPage::getTitleFor(
2107 'GoToInterwiki',
2108 $this->getPrefixedDBkey()
2109 );
2110 }
2111 return $target->getFullURL( $query, false, $proto );
2112 }
2113
2137 public function getLocalURL( $query = '', $query2 = false ) {
2139
2140 $query = self::fixUrlQueryArgs( $query, $query2 );
2141
2142 $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
2143 if ( $interwiki ) {
2144 $namespace = $this->getNsText();
2145 if ( $namespace != '' ) {
2146 # Can this actually happen? Interwikis shouldn't be parsed.
2147 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
2148 $namespace .= ':';
2149 }
2150 $url = $interwiki->getURL( $namespace . $this->mDbkeyform );
2151 $url = wfAppendQuery( $url, $query );
2152 } else {
2153 $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
2154 if ( $query == '' ) {
2155 $url = str_replace( '$1', $dbkey, $wgArticlePath );
2156 // Avoid PHP 7.1 warning from passing $this by reference
2157 $titleRef = $this;
2158 Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
2159 } else {
2161 $url = false;
2162 $matches = [];
2163
2165
2166 if ( $articlePaths
2167 && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
2168 ) {
2169 $action = urldecode( $matches[2] );
2170 if ( isset( $articlePaths[$action] ) ) {
2171 $query = $matches[1];
2172 if ( isset( $matches[4] ) ) {
2173 $query .= $matches[4];
2174 }
2175 $url = str_replace( '$1', $dbkey, $articlePaths[$action] );
2176 if ( $query != '' ) {
2177 $url = wfAppendQuery( $url, $query );
2178 }
2179 }
2180 }
2181
2182 if ( $url === false
2184 && preg_match( '/^variant=([^&]*)$/', $query, $matches )
2185 && $this->getPageLanguage()->equals(
2186 MediaWikiServices::getInstance()->getContentLanguage() )
2187 && $this->getPageLanguage()->hasVariants()
2188 ) {
2189 $variant = urldecode( $matches[1] );
2190 if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
2191 // Only do the variant replacement if the given variant is a valid
2192 // variant for the page's language.
2193 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
2194 $url = str_replace( '$1', $dbkey, $url );
2195 }
2196 }
2197
2198 if ( $url === false ) {
2199 if ( $query == '-' ) {
2200 $query = '';
2201 }
2202 $url = "{$wgScript}?title={$dbkey}&{$query}";
2203 }
2204 }
2205 // Avoid PHP 7.1 warning from passing $this by reference
2206 $titleRef = $this;
2207 Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
2208
2209 // @todo FIXME: This causes breakage in various places when we
2210 // actually expected a local URL and end up with dupe prefixes.
2211 if ( $wgRequest->getVal( 'action' ) == 'render' ) {
2212 $url = $wgServer . $url;
2213 }
2214 }
2215
2216 if ( $wgMainPageIsDomainRoot && $this->isMainPage() && $query === '' ) {
2217 return '/';
2218 }
2219
2220 // Avoid PHP 7.1 warning from passing $this by reference
2221 $titleRef = $this;
2222 Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
2223 return $url;
2224 }
2225
2243 public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
2244 if ( $this->isExternal() || $proto !== false ) {
2245 $ret = $this->getFullURL( $query, $query2, $proto );
2246 } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
2247 $ret = $this->getFragmentForURL();
2248 } else {
2249 $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
2250 }
2251 return $ret;
2252 }
2253
2268 public function getInternalURL( $query = '', $query2 = false ) {
2270 $query = self::fixUrlQueryArgs( $query, $query2 );
2271 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
2272 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
2273 // Avoid PHP 7.1 warning from passing $this by reference
2274 $titleRef = $this;
2275 Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
2276 return $url;
2277 }
2278
2292 public function getCanonicalURL( $query = '', $query2 = false ) {
2293 $query = self::fixUrlQueryArgs( $query, $query2 );
2294 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
2295 // Avoid PHP 7.1 warning from passing $this by reference
2296 $titleRef = $this;
2297 Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
2298 return $url;
2299 }
2300
2306 public function getEditURL() {
2307 if ( $this->isExternal() ) {
2308 return '';
2309 }
2310 $s = $this->getLocalURL( 'action=edit' );
2311
2312 return $s;
2313 }
2314
2335 public function quickUserCan( $action, $user = null ) {
2336 return $this->userCan( $action, $user, false );
2337 }
2338
2354 public function userCan( $action, $user = null, $rigor = PermissionManager::RIGOR_SECURE ) {
2355 if ( !$user instanceof User ) {
2356 global $wgUser;
2357 $user = $wgUser;
2358 }
2359
2360 // TODO: this is for b/c, eventually will be removed
2361 if ( $rigor === true ) {
2362 $rigor = PermissionManager::RIGOR_SECURE; // b/c
2363 } elseif ( $rigor === false ) {
2364 $rigor = PermissionManager::RIGOR_QUICK; // b/c
2365 }
2366
2367 return MediaWikiServices::getInstance()->getPermissionManager()
2368 ->userCan( $action, $user, $this, $rigor );
2369 }
2370
2393 $action, $user, $rigor = PermissionManager::RIGOR_SECURE, $ignoreErrors = []
2394 ) {
2395 // TODO: this is for b/c, eventually will be removed
2396 if ( $rigor === true ) {
2397 $rigor = PermissionManager::RIGOR_SECURE; // b/c
2398 } elseif ( $rigor === false ) {
2399 $rigor = PermissionManager::RIGOR_QUICK; // b/c
2400 }
2401
2402 return MediaWikiServices::getInstance()->getPermissionManager()
2403 ->getPermissionErrors( $action, $user, $this, $rigor, $ignoreErrors );
2404 }
2405
2413 public static function getFilteredRestrictionTypes( $exists = true ) {
2414 global $wgRestrictionTypes;
2415 $types = $wgRestrictionTypes;
2416 if ( $exists ) {
2417 # Remove the create restriction for existing titles
2418 $types = array_diff( $types, [ 'create' ] );
2419 } else {
2420 # Only the create and upload restrictions apply to non-existing titles
2421 $types = array_intersect( $types, [ 'create', 'upload' ] );
2422 }
2423 return $types;
2424 }
2425
2431 public function getRestrictionTypes() {
2432 if ( $this->isSpecialPage() ) {
2433 return [];
2434 }
2435
2436 $types = self::getFilteredRestrictionTypes( $this->exists() );
2437
2438 if ( $this->mNamespace != NS_FILE ) {
2439 # Remove the upload restriction for non-file titles
2440 $types = array_diff( $types, [ 'upload' ] );
2441 }
2442
2443 Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2444
2445 wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2446 $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2447
2448 return $types;
2449 }
2450
2458 public function getTitleProtection() {
2459 $protection = $this->getTitleProtectionInternal();
2460 if ( $protection ) {
2461 if ( $protection['permission'] == 'sysop' ) {
2462 $protection['permission'] = 'editprotected'; // B/C
2463 }
2464 if ( $protection['permission'] == 'autoconfirmed' ) {
2465 $protection['permission'] = 'editsemiprotected'; // B/C
2466 }
2467 }
2468 return $protection;
2469 }
2470
2481 protected function getTitleProtectionInternal() {
2482 // Can't protect pages in special namespaces
2483 if ( $this->mNamespace < 0 ) {
2484 return false;
2485 }
2486
2487 // Can't protect pages that exist.
2488 if ( $this->exists() ) {
2489 return false;
2490 }
2491
2492 if ( $this->mTitleProtection === null ) {
2493 $dbr = wfGetDB( DB_REPLICA );
2494 $commentStore = CommentStore::getStore();
2495 $commentQuery = $commentStore->getJoin( 'pt_reason' );
2496 $res = $dbr->select(
2497 [ 'protected_titles' ] + $commentQuery['tables'],
2498 [
2499 'user' => 'pt_user',
2500 'expiry' => 'pt_expiry',
2501 'permission' => 'pt_create_perm'
2502 ] + $commentQuery['fields'],
2503 [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2504 __METHOD__,
2505 [],
2506 $commentQuery['joins']
2507 );
2508
2509 // fetchRow returns false if there are no rows.
2510 $row = $dbr->fetchRow( $res );
2511 if ( $row ) {
2512 $this->mTitleProtection = [
2513 'user' => $row['user'],
2514 'expiry' => $dbr->decodeExpiry( $row['expiry'] ),
2515 'permission' => $row['permission'],
2516 'reason' => $commentStore->getComment( 'pt_reason', $row )->text,
2517 ];
2518 } else {
2519 $this->mTitleProtection = false;
2520 }
2521 }
2522 return $this->mTitleProtection;
2523 }
2524
2528 public function deleteTitleProtection() {
2529 $dbw = wfGetDB( DB_MASTER );
2530
2531 $dbw->delete(
2532 'protected_titles',
2533 [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2534 __METHOD__
2535 );
2536 $this->mTitleProtection = false;
2537 }
2538
2546 public function isSemiProtected( $action = 'edit' ) {
2548
2549 $restrictions = $this->getRestrictions( $action );
2551 if ( !$restrictions || !$semi ) {
2552 // Not protected, or all protection is full protection
2553 return false;
2554 }
2555
2556 // Remap autoconfirmed to editsemiprotected for BC
2557 foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2558 $semi[$key] = 'editsemiprotected';
2559 }
2560 foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2561 $restrictions[$key] = 'editsemiprotected';
2562 }
2563
2564 return !array_diff( $restrictions, $semi );
2565 }
2566
2574 public function isProtected( $action = '' ) {
2575 global $wgRestrictionLevels;
2576
2577 $restrictionTypes = $this->getRestrictionTypes();
2578
2579 # Special pages have inherent protection
2580 if ( $this->isSpecialPage() ) {
2581 return true;
2582 }
2583
2584 # Check regular protection levels
2585 foreach ( $restrictionTypes as $type ) {
2586 if ( $action == $type || $action == '' ) {
2587 $r = $this->getRestrictions( $type );
2588 foreach ( $wgRestrictionLevels as $level ) {
2589 if ( in_array( $level, $r ) && $level != '' ) {
2590 return true;
2591 }
2592 }
2593 }
2594 }
2595
2596 return false;
2597 }
2598
2607 public function isNamespaceProtected( User $user ) {
2609
2610 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2611 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
2612 foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2613 if ( !$permissionManager->userHasRight( $user, $right ) ) {
2614 return true;
2615 }
2616 }
2617 }
2618 return false;
2619 }
2620
2626 public function isCascadeProtected() {
2627 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2628 return ( $sources > 0 );
2629 }
2630
2640 public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2641 return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2642 }
2643
2657 public function getCascadeProtectionSources( $getPages = true ) {
2658 $pagerestrictions = [];
2659
2660 if ( $this->mCascadeSources !== null && $getPages ) {
2661 return [ $this->mCascadeSources, $this->mCascadingRestrictions ];
2662 } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2663 return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2664 }
2665
2666 $dbr = wfGetDB( DB_REPLICA );
2667
2668 if ( $this->mNamespace == NS_FILE ) {
2669 $tables = [ 'imagelinks', 'page_restrictions' ];
2670 $where_clauses = [
2671 'il_to' => $this->mDbkeyform,
2672 'il_from=pr_page',
2673 'pr_cascade' => 1
2674 ];
2675 } else {
2676 $tables = [ 'templatelinks', 'page_restrictions' ];
2677 $where_clauses = [
2678 'tl_namespace' => $this->mNamespace,
2679 'tl_title' => $this->mDbkeyform,
2680 'tl_from=pr_page',
2681 'pr_cascade' => 1
2682 ];
2683 }
2684
2685 if ( $getPages ) {
2686 $cols = [ 'pr_page', 'page_namespace', 'page_title',
2687 'pr_expiry', 'pr_type', 'pr_level' ];
2688 $where_clauses[] = 'page_id=pr_page';
2689 $tables[] = 'page';
2690 } else {
2691 $cols = [ 'pr_expiry' ];
2692 }
2693
2694 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2695
2696 $sources = $getPages ? [] : false;
2697 $now = wfTimestampNow();
2698
2699 foreach ( $res as $row ) {
2700 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2701 if ( $expiry > $now ) {
2702 if ( $getPages ) {
2703 $page_id = $row->pr_page;
2704 $page_ns = $row->page_namespace;
2705 $page_title = $row->page_title;
2706 $sources[$page_id] = self::makeTitle( $page_ns, $page_title );
2707 # Add groups needed for each restriction type if its not already there
2708 # Make sure this restriction type still exists
2709
2710 if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2711 $pagerestrictions[$row->pr_type] = [];
2712 }
2713
2714 if (
2715 isset( $pagerestrictions[$row->pr_type] )
2716 && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2717 ) {
2718 $pagerestrictions[$row->pr_type][] = $row->pr_level;
2719 }
2720 } else {
2721 $sources = true;
2722 }
2723 }
2724 }
2725
2726 if ( $getPages ) {
2727 $this->mCascadeSources = $sources;
2728 $this->mCascadingRestrictions = $pagerestrictions;
2729 } else {
2730 $this->mHasCascadingRestrictions = $sources;
2731 }
2732
2733 return [ $sources, $pagerestrictions ];
2734 }
2735
2743 public function areRestrictionsLoaded() {
2744 return $this->mRestrictionsLoaded;
2745 }
2746
2756 public function getRestrictions( $action ) {
2757 if ( !$this->mRestrictionsLoaded ) {
2758 $this->loadRestrictions();
2759 }
2760 return $this->mRestrictions[$action] ?? [];
2761 }
2762
2770 public function getAllRestrictions() {
2771 if ( !$this->mRestrictionsLoaded ) {
2772 $this->loadRestrictions();
2773 }
2774 return $this->mRestrictions;
2775 }
2776
2784 public function getRestrictionExpiry( $action ) {
2785 if ( !$this->mRestrictionsLoaded ) {
2786 $this->loadRestrictions();
2787 }
2788 return $this->mRestrictionsExpiry[$action] ?? false;
2789 }
2790
2797 if ( !$this->mRestrictionsLoaded ) {
2798 $this->loadRestrictions();
2799 }
2800
2801 return $this->mCascadeRestriction;
2802 }
2803
2815 public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2816 // This function will only read rows from a table that we migrated away
2817 // from before adding READ_LATEST support to loadRestrictions, so we
2818 // don't need to support reading from DB_MASTER here.
2819 $dbr = wfGetDB( DB_REPLICA );
2820
2821 $restrictionTypes = $this->getRestrictionTypes();
2822
2823 foreach ( $restrictionTypes as $type ) {
2824 $this->mRestrictions[$type] = [];
2825 $this->mRestrictionsExpiry[$type] = 'infinity';
2826 }
2827
2828 $this->mCascadeRestriction = false;
2829
2830 # Backwards-compatibility: also load the restrictions from the page record (old format).
2831 if ( $oldFashionedRestrictions !== null ) {
2832 $this->mOldRestrictions = $oldFashionedRestrictions;
2833 }
2834
2835 if ( $this->mOldRestrictions === false ) {
2836 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
2837 $linkCache->addLinkObj( $this ); # in case we already had an article ID
2838 $this->mOldRestrictions = $linkCache->getGoodLinkFieldObj( $this, 'restrictions' );
2839 }
2840
2841 if ( $this->mOldRestrictions != '' ) {
2842 foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2843 $temp = explode( '=', trim( $restrict ) );
2844 if ( count( $temp ) == 1 ) {
2845 // old old format should be treated as edit/move restriction
2846 $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2847 $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2848 } else {
2849 $restriction = trim( $temp[1] );
2850 if ( $restriction != '' ) { // some old entries are empty
2851 $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2852 }
2853 }
2854 }
2855 }
2856
2857 if ( count( $rows ) ) {
2858 # Current system - load second to make them override.
2859 $now = wfTimestampNow();
2860
2861 # Cycle through all the restrictions.
2862 foreach ( $rows as $row ) {
2863 // Don't take care of restrictions types that aren't allowed
2864 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2865 continue;
2866 }
2867
2868 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2869
2870 // Only apply the restrictions if they haven't expired!
2871 if ( !$expiry || $expiry > $now ) {
2872 $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2873 $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2874
2875 $this->mCascadeRestriction |= $row->pr_cascade;
2876 }
2877 }
2878 }
2879
2880 $this->mRestrictionsLoaded = true;
2881 }
2882
2893 public function loadRestrictions( $oldFashionedRestrictions = null, $flags = 0 ) {
2894 $readLatest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
2895 if ( $this->mRestrictionsLoaded && !$readLatest ) {
2896 return;
2897 }
2898
2899 $id = $this->getArticleID( $flags );
2900 if ( $id ) {
2901 $fname = __METHOD__;
2902 $loadRestrictionsFromDb = function ( IDatabase $dbr ) use ( $fname, $id ) {
2903 return iterator_to_array(
2904 $dbr->select(
2905 'page_restrictions',
2906 [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
2907 [ 'pr_page' => $id ],
2908 $fname
2909 )
2910 );
2911 };
2912
2913 if ( $readLatest ) {
2914 $dbr = wfGetDB( DB_MASTER );
2915 $rows = $loadRestrictionsFromDb( $dbr );
2916 } else {
2917 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2918 $rows = $cache->getWithSetCallback(
2919 // Page protections always leave a new null revision
2920 $cache->makeKey( 'page-restrictions', 'v1', $id, $this->getLatestRevID() ),
2921 $cache::TTL_DAY,
2922 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
2923 $dbr = wfGetDB( DB_REPLICA );
2924
2925 $setOpts += Database::getCacheSetOptions( $dbr );
2926 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2927 if ( $lb->hasOrMadeRecentMasterChanges() ) {
2928 // @TODO: cleanup Title cache and caller assumption mess in general
2929 $ttl = WANObjectCache::TTL_UNCACHEABLE;
2930 }
2931
2932 return $loadRestrictionsFromDb( $dbr );
2933 }
2934 );
2935 }
2936
2937 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2938 } else {
2939 $title_protection = $this->getTitleProtectionInternal();
2940
2941 if ( $title_protection ) {
2942 $now = wfTimestampNow();
2943 $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
2944
2945 if ( !$expiry || $expiry > $now ) {
2946 // Apply the restrictions
2947 $this->mRestrictionsExpiry['create'] = $expiry;
2948 $this->mRestrictions['create'] =
2949 explode( ',', trim( $title_protection['permission'] ) );
2950 } else { // Get rid of the old restrictions
2951 $this->mTitleProtection = false;
2952 }
2953 } else {
2954 $this->mRestrictionsExpiry['create'] = 'infinity';
2955 }
2956 $this->mRestrictionsLoaded = true;
2957 }
2958 }
2959
2964 public function flushRestrictions() {
2965 $this->mRestrictionsLoaded = false;
2966 $this->mTitleProtection = null;
2967 }
2968
2974 static function purgeExpiredRestrictions() {
2975 if ( wfReadOnly() ) {
2976 return;
2977 }
2978
2979 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
2980 wfGetDB( DB_MASTER ),
2981 __METHOD__,
2982 function ( IDatabase $dbw, $fname ) {
2983 $config = MediaWikiServices::getInstance()->getMainConfig();
2984 $ids = $dbw->selectFieldValues(
2985 'page_restrictions',
2986 'pr_id',
2987 [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
2988 $fname,
2989 [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
2990 );
2991 if ( $ids ) {
2992 $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
2993 }
2994 }
2995 ) );
2996
2997 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
2998 wfGetDB( DB_MASTER ),
2999 __METHOD__,
3000 function ( IDatabase $dbw, $fname ) {
3001 $dbw->delete(
3002 'protected_titles',
3003 [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3004 $fname
3005 );
3006 }
3007 ) );
3008 }
3009
3015 public function hasSubpages() {
3016 if (
3017 !MediaWikiServices::getInstance()->getNamespaceInfo()->
3018 hasSubpages( $this->mNamespace )
3019 ) {
3020 # Duh
3021 return false;
3022 }
3023
3024 # We dynamically add a member variable for the purpose of this method
3025 # alone to cache the result. There's no point in having it hanging
3026 # around uninitialized in every Title object; therefore we only add it
3027 # if needed and don't declare it statically.
3028 if ( $this->mHasSubpages === null ) {
3029 $this->mHasSubpages = false;
3030 $subpages = $this->getSubpages( 1 );
3031 if ( $subpages instanceof TitleArray ) {
3032 $this->mHasSubpages = (bool)$subpages->current();
3033 }
3034 }
3035
3036 return $this->mHasSubpages;
3037 }
3038
3046 public function getSubpages( $limit = -1 ) {
3047 if (
3048 !MediaWikiServices::getInstance()->getNamespaceInfo()->
3049 hasSubpages( $this->mNamespace )
3050 ) {
3051 return [];
3052 }
3053
3054 $dbr = wfGetDB( DB_REPLICA );
3055 $conds = [ 'page_namespace' => $this->mNamespace ];
3056 $conds[] = 'page_title ' . $dbr->buildLike( $this->mDbkeyform . '/', $dbr->anyString() );
3057 $options = [];
3058 if ( $limit > -1 ) {
3059 $options['LIMIT'] = $limit;
3060 }
3062 $dbr->select( 'page',
3063 [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3064 $conds,
3065 __METHOD__,
3066 $options
3067 )
3068 );
3069 }
3070
3076 public function isDeleted() {
3077 if ( $this->mNamespace < 0 ) {
3078 $n = 0;
3079 } else {
3080 $dbr = wfGetDB( DB_REPLICA );
3081
3082 $n = $dbr->selectField( 'archive', 'COUNT(*)',
3083 [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
3084 __METHOD__
3085 );
3086 if ( $this->mNamespace == NS_FILE ) {
3087 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3088 [ 'fa_name' => $this->mDbkeyform ],
3089 __METHOD__
3090 );
3091 }
3092 }
3093 return (int)$n;
3094 }
3095
3101 public function isDeletedQuick() {
3102 if ( $this->mNamespace < 0 ) {
3103 return false;
3104 }
3105 $dbr = wfGetDB( DB_REPLICA );
3106 $deleted = (bool)$dbr->selectField( 'archive', '1',
3107 [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
3108 __METHOD__
3109 );
3110 if ( !$deleted && $this->mNamespace == NS_FILE ) {
3111 $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3112 [ 'fa_name' => $this->mDbkeyform ],
3113 __METHOD__
3114 );
3115 }
3116 return $deleted;
3117 }
3118
3126 public function getArticleID( $flags = 0 ) {
3127 if ( $this->mNamespace < 0 ) {
3128 $this->mArticleID = 0;
3129
3130 return $this->mArticleID;
3131 }
3132
3133 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3134 if ( $flags & self::GAID_FOR_UPDATE ) {
3135 $oldUpdate = $linkCache->forUpdate( true );
3136 $linkCache->clearLink( $this );
3137 $this->mArticleID = $linkCache->addLinkObj( $this );
3138 $linkCache->forUpdate( $oldUpdate );
3139 } elseif ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
3140 $this->mArticleID = (int)$this->loadFieldFromDB( 'page_id', $flags );
3141 } elseif ( $this->mArticleID == -1 ) {
3142 $this->mArticleID = $linkCache->addLinkObj( $this );
3143 }
3144
3145 return $this->mArticleID;
3146 }
3147
3155 public function isRedirect( $flags = 0 ) {
3156 if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
3157 $this->mRedirect = (bool)$this->loadFieldFromDB( 'page_is_redirect', $flags );
3158 } else {
3159 if ( $this->mRedirect !== null ) {
3160 return $this->mRedirect;
3161 } elseif ( !$this->getArticleID( $flags ) ) {
3162 $this->mRedirect = false;
3163
3164 return $this->mRedirect;
3165 }
3166
3167 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3168 $linkCache->addLinkObj( $this ); // in case we already had an article ID
3169 // Note that LinkCache returns null if it thinks the page does not exist;
3170 // always trust the state of LinkCache over that of this Title instance.
3171 $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3172 }
3173
3174 return $this->mRedirect;
3175 }
3176
3184 public function getLength( $flags = 0 ) {
3185 if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
3186 $this->mLength = (int)$this->loadFieldFromDB( 'page_len', $flags );
3187 } else {
3188 if ( $this->mLength != -1 ) {
3189 return $this->mLength;
3190 } elseif ( !$this->getArticleID( $flags ) ) {
3191 $this->mLength = 0;
3192 return $this->mLength;
3193 }
3194
3195 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3196 $linkCache->addLinkObj( $this ); // in case we already had an article ID
3197 // Note that LinkCache returns null if it thinks the page does not exist;
3198 // always trust the state of LinkCache over that of this Title instance.
3199 $this->mLength = (int)$linkCache->getGoodLinkFieldObj( $this, 'length' );
3200 }
3201
3202 return $this->mLength;
3203 }
3204
3211 public function getLatestRevID( $flags = 0 ) {
3212 if ( DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ) ) {
3213 $this->mLatestID = (int)$this->loadFieldFromDB( 'page_latest', $flags );
3214 } else {
3215 if ( $this->mLatestID !== false ) {
3216 return (int)$this->mLatestID;
3217 } elseif ( !$this->getArticleID( $flags ) ) {
3218 $this->mLatestID = 0;
3219
3220 return $this->mLatestID;
3221 }
3222
3223 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3224 $linkCache->addLinkObj( $this ); // in case we already had an article ID
3225 // Note that LinkCache returns null if it thinks the page does not exist;
3226 // always trust the state of LinkCache over that of this Title instance.
3227 $this->mLatestID = (int)$linkCache->getGoodLinkFieldObj( $this, 'revision' );
3228 }
3229
3230 return $this->mLatestID;
3231 }
3232
3243 public function resetArticleID( $id ) {
3244 if ( $id === false ) {
3245 $this->mArticleID = -1;
3246 } else {
3247 $this->mArticleID = (int)$id;
3248 }
3249 $this->mRestrictionsLoaded = false;
3250 $this->mRestrictions = [];
3251 $this->mOldRestrictions = false;
3252 $this->mRedirect = null;
3253 $this->mLength = -1;
3254 $this->mLatestID = false;
3255 $this->mContentModel = false;
3256 $this->mForcedContentModel = false;
3257 $this->mEstimateRevisions = null;
3258 $this->mPageLanguage = null;
3259 $this->mDbPageLanguage = false;
3260 $this->mIsBigDeletion = null;
3261
3262 MediaWikiServices::getInstance()->getLinkCache()->clearLink( $this );
3263 }
3264
3265 public static function clearCaches() {
3266 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3267 $linkCache->clear();
3268
3269 $titleCache = self::getTitleCache();
3270 $titleCache->clear();
3271 }
3272
3280 public static function capitalize( $text, $ns = NS_MAIN ) {
3281 $services = MediaWikiServices::getInstance();
3282 if ( $services->getNamespaceInfo()->isCapitalized( $ns ) ) {
3283 return $services->getContentLanguage()->ucfirst( $text );
3284 } else {
3285 return $text;
3286 }
3287 }
3288
3301 private function secureAndSplit() {
3302 // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3303 // the parsing code with Title, while avoiding massive refactoring.
3304 // @todo: get rid of secureAndSplit, refactor parsing code.
3305 // @note: getTitleParser() returns a TitleParser implementation which does not have a
3306 // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3308 $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3309 '@phan-var MediaWikiTitleCodec $titleCodec';
3310 // MalformedTitleException can be thrown here
3311 $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace );
3312
3313 # Fill fields
3314 $this->setFragment( '#' . $parts['fragment'] );
3315 $this->mInterwiki = $parts['interwiki'];
3316 $this->mLocalInterwiki = $parts['local_interwiki'];
3317 $this->mNamespace = $parts['namespace'];
3318 $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3319
3320 $this->mDbkeyform = $parts['dbkey'];
3321 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3322 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3323
3324 # We already know that some pages won't be in the database!
3325 if ( $this->isExternal() || $this->isSpecialPage() ) {
3326 $this->mArticleID = 0;
3327 }
3328
3329 return true;
3330 }
3331
3344 public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3345 if ( count( $options ) > 0 ) {
3346 $db = wfGetDB( DB_MASTER );
3347 } else {
3348 $db = wfGetDB( DB_REPLICA );
3349 }
3350
3351 $res = $db->select(
3352 [ 'page', $table ],
3353 self::getSelectFields(),
3354 [
3355 "{$prefix}_from=page_id",
3356 "{$prefix}_namespace" => $this->mNamespace,
3357 "{$prefix}_title" => $this->mDbkeyform ],
3358 __METHOD__,
3359 $options
3360 );
3361
3362 $retVal = [];
3363 if ( $res->numRows() ) {
3364 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3365 foreach ( $res as $row ) {
3366 $titleObj = self::makeTitle( $row->page_namespace, $row->page_title );
3367 if ( $titleObj ) {
3368 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3369 $retVal[] = $titleObj;
3370 }
3371 }
3372 }
3373 return $retVal;
3374 }
3375
3386 public function getTemplateLinksTo( $options = [] ) {
3387 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3388 }
3389
3402 public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3403 $id = $this->getArticleID();
3404
3405 # If the page doesn't exist; there can't be any link from this page
3406 if ( !$id ) {
3407 return [];
3408 }
3409
3410 $db = wfGetDB( DB_REPLICA );
3411
3412 $blNamespace = "{$prefix}_namespace";
3413 $blTitle = "{$prefix}_title";
3414
3415 $pageQuery = WikiPage::getQueryInfo();
3416 $res = $db->select(
3417 [ $table, 'nestpage' => $pageQuery['tables'] ],
3418 array_merge(
3419 [ $blNamespace, $blTitle ],
3420 $pageQuery['fields']
3421 ),
3422 [ "{$prefix}_from" => $id ],
3423 __METHOD__,
3424 $options,
3425 [ 'nestpage' => [
3426 'LEFT JOIN',
3427 [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3428 ] ] + $pageQuery['joins']
3429 );
3430
3431 $retVal = [];
3432 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3433 foreach ( $res as $row ) {
3434 if ( $row->page_id ) {
3435 $titleObj = self::newFromRow( $row );
3436 } else {
3437 $titleObj = self::makeTitle( $row->$blNamespace, $row->$blTitle );
3438 $linkCache->addBadLinkObj( $titleObj );
3439 }
3440 $retVal[] = $titleObj;
3441 }
3442
3443 return $retVal;
3444 }
3445
3456 public function getTemplateLinksFrom( $options = [] ) {
3457 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3458 }
3459
3468 public function getBrokenLinksFrom() {
3469 if ( $this->getArticleID() == 0 ) {
3470 # All links from article ID 0 are false positives
3471 return [];
3472 }
3473
3474 $dbr = wfGetDB( DB_REPLICA );
3475 $res = $dbr->select(
3476 [ 'page', 'pagelinks' ],
3477 [ 'pl_namespace', 'pl_title' ],
3478 [
3479 'pl_from' => $this->getArticleID(),
3480 'page_namespace IS NULL'
3481 ],
3482 __METHOD__, [],
3483 [
3484 'page' => [
3485 'LEFT JOIN',
3486 [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3487 ]
3488 ]
3489 );
3490
3491 $retVal = [];
3492 foreach ( $res as $row ) {
3493 $retVal[] = self::makeTitle( $row->pl_namespace, $row->pl_title );
3494 }
3495 return $retVal;
3496 }
3497
3504 public function getCdnUrls() {
3505 $urls = [
3506 $this->getInternalURL(),
3507 $this->getInternalURL( 'action=history' )
3508 ];
3509
3510 $pageLang = $this->getPageLanguage();
3511 if ( $pageLang->hasVariants() ) {
3512 $variants = $pageLang->getVariants();
3513 foreach ( $variants as $vCode ) {
3514 $urls[] = $this->getInternalURL( $vCode );
3515 }
3516 }
3517
3518 // If we are looking at a css/js user subpage, purge the action=raw.
3519 if ( $this->isUserJsConfigPage() ) {
3520 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3521 } elseif ( $this->isUserJsonConfigPage() ) {
3522 $urls[] = $this->getInternalURL( 'action=raw&ctype=application/json' );
3523 } elseif ( $this->isUserCssConfigPage() ) {
3524 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3525 }
3526
3527 Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3528 return $urls;
3529 }
3530
3534 public function purgeSquid() {
3535 DeferredUpdates::addUpdate(
3536 new CdnCacheUpdate( $this->getCdnUrls() ),
3537 DeferredUpdates::PRESEND
3538 );
3539 }
3540
3551 public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3552 wfDeprecated( __METHOD__, '1.25' );
3553
3554 global $wgUser;
3555
3556 if ( !( $nt instanceof Title ) ) {
3557 // Normally we'd add this to $errors, but we'll get
3558 // lots of syntax errors if $nt is not an object
3559 return [ [ 'badtitletext' ] ];
3560 }
3561
3562 $mp = MediaWikiServices::getInstance()->getMovePageFactory()->newMovePage( $this, $nt );
3563 $errors = $mp->isValidMove()->getErrorsArray();
3564 if ( $auth ) {
3565 $errors = wfMergeErrorArrays(
3566 $errors,
3567 $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3568 );
3569 }
3570
3571 return $errors ?: true;
3572 }
3573
3587 public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true,
3588 array $changeTags = []
3589 ) {
3590 wfDeprecated( __METHOD__, '1.25' );
3591
3592 global $wgUser;
3593
3594 $mp = MediaWikiServices::getInstance()->getMovePageFactory()->newMovePage( $this, $nt );
3595 $method = $auth ? 'moveIfAllowed' : 'move';
3597 $status = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
3598 if ( $status->isOK() ) {
3599 return true;
3600 } else {
3601 return $status->getErrorsArray();
3602 }
3603 }
3604
3620 public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true,
3621 array $changeTags = []
3622 ) {
3623 wfDeprecated( __METHOD__, '1.34' );
3624
3625 global $wgUser;
3626
3627 $mp = new MovePage( $this, $nt );
3628 $method = $auth ? 'moveSubpagesIfAllowed' : 'moveSubpages';
3630 $result = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
3631
3632 if ( !$result->isOK() ) {
3633 return $result->getErrorsArray();
3634 }
3635
3636 $retval = [];
3637 foreach ( $result->getValue() as $key => $status ) {
3639 if ( $status->isOK() ) {
3640 $retval[$key] = $status->getValue();
3641 } else {
3642 $retval[$key] = $status->getErrorsArray();
3643 }
3644 }
3645 return $retval;
3646 }
3647
3655 public function isSingleRevRedirect() {
3657
3658 $dbw = wfGetDB( DB_MASTER );
3659
3660 # Is it a redirect?
3661 $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3662 if ( $wgContentHandlerUseDB ) {
3663 $fields[] = 'page_content_model';
3664 }
3665
3666 $row = $dbw->selectRow( 'page',
3667 $fields,
3668 $this->pageCond(),
3669 __METHOD__,
3670 [ 'FOR UPDATE' ]
3671 );
3672 # Cache some fields we may want
3673 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3674 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3675 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3676 $this->mContentModel = $row && isset( $row->page_content_model )
3677 ? strval( $row->page_content_model )
3678 : false;
3679
3680 if ( !$this->mRedirect ) {
3681 return false;
3682 }
3683 # Does the article have a history?
3684 $row = $dbw->selectField( [ 'page', 'revision' ],
3685 'rev_id',
3686 [ 'page_namespace' => $this->mNamespace,
3687 'page_title' => $this->mDbkeyform,
3688 'page_id=rev_page',
3689 'page_latest != rev_id'
3690 ],
3691 __METHOD__,
3692 [ 'FOR UPDATE' ]
3693 );
3694 # Return true if there was no history
3695 return ( $row === false );
3696 }
3697
3706 public function isValidMoveTarget( $nt ) {
3707 wfDeprecated( __METHOD__, '1.25' );
3708
3709 # Is it an existing file?
3710 if ( $nt->getNamespace() == NS_FILE ) {
3711 $file = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
3712 ->newFile( $nt );
3713 $file->load( File::READ_LATEST );
3714 if ( $file->exists() ) {
3715 wfDebug( __METHOD__ . ": file exists\n" );
3716 return false;
3717 }
3718 }
3719 # Is it a redirect with no history?
3720 if ( !$nt->isSingleRevRedirect() ) {
3721 wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3722 return false;
3723 }
3724 # Get the article text
3725 $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
3726 if ( !is_object( $rev ) ) {
3727 return false;
3728 }
3729 $content = $rev->getContent();
3730 # Does the redirect point to the source?
3731 # Or is it a broken self-redirect, usually caused by namespace collisions?
3732 $redirTitle = $content ? $content->getRedirectTarget() : null;
3733
3734 if ( $redirTitle ) {
3735 if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3736 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3737 wfDebug( __METHOD__ . ": redirect points to other page\n" );
3738 return false;
3739 } else {
3740 return true;
3741 }
3742 } else {
3743 # Fail safe (not a redirect after all. strange.)
3744 wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3745 " is a redirect, but it doesn't contain a valid redirect.\n" );
3746 return false;
3747 }
3748 }
3749
3757 public function getParentCategories() {
3758 $data = [];
3759
3760 $titleKey = $this->getArticleID();
3761
3762 if ( $titleKey === 0 ) {
3763 return $data;
3764 }
3765
3766 $dbr = wfGetDB( DB_REPLICA );
3767
3768 $res = $dbr->select(
3769 'categorylinks',
3770 'cl_to',
3771 [ 'cl_from' => $titleKey ],
3772 __METHOD__
3773 );
3774
3775 if ( $res->numRows() > 0 ) {
3776 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3777 foreach ( $res as $row ) {
3778 // $data[] = Title::newFromText( $contLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3779 $data[$contLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] =
3780 $this->getFullText();
3781 }
3782 }
3783 return $data;
3784 }
3785
3792 public function getParentCategoryTree( $children = [] ) {
3793 $stack = [];
3794 $parents = $this->getParentCategories();
3795
3796 if ( $parents ) {
3797 foreach ( $parents as $parent => $current ) {
3798 if ( array_key_exists( $parent, $children ) ) {
3799 # Circular reference
3800 $stack[$parent] = [];
3801 } else {
3802 $nt = self::newFromText( $parent );
3803 if ( $nt ) {
3804 $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3805 }
3806 }
3807 }
3808 }
3809
3810 return $stack;
3811 }
3812
3819 public function pageCond() {
3820 if ( $this->mArticleID > 0 ) {
3821 // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3822 return [ 'page_id' => $this->mArticleID ];
3823 } else {
3824 return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3825 }
3826 }
3827
3835 private function getRelativeRevisionID( $revId, $flags, $dir ) {
3836 $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3837 $rev = $rl->getRevisionById( $revId, $flags );
3838 if ( !$rev ) {
3839 return false;
3840 }
3841
3842 $oldRev = ( $dir === 'next' )
3843 ? $rl->getNextRevision( $rev, $flags )
3844 : $rl->getPreviousRevision( $rev, $flags );
3845
3846 return $oldRev ? $oldRev->getId() : false;
3847 }
3848
3857 public function getPreviousRevisionID( $revId, $flags = 0 ) {
3858 return $this->getRelativeRevisionID( $revId, $flags, 'prev' );
3859 }
3860
3869 public function getNextRevisionID( $revId, $flags = 0 ) {
3870 return $this->getRelativeRevisionID( $revId, $flags, 'next' );
3871 }
3872
3879 public function getFirstRevision( $flags = 0 ) {
3880 $pageId = $this->getArticleID( $flags );
3881 if ( $pageId ) {
3882 $flags |= ( $flags & self::GAID_FOR_UPDATE ) ? self::READ_LATEST : 0; // b/c
3883 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
3885 $row = wfGetDB( $index )->selectRow(
3886 $revQuery['tables'], $revQuery['fields'],
3887 [ 'rev_page' => $pageId ],
3888 __METHOD__,
3889 array_merge(
3890 [
3891 'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
3892 'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
3893 ],
3894 $options
3895 ),
3896 $revQuery['joins']
3897 );
3898 if ( $row ) {
3899 return new Revision( $row, 0, $this );
3900 }
3901 }
3902 return null;
3903 }
3904
3911 public function getEarliestRevTime( $flags = 0 ) {
3912 $rev = $this->getFirstRevision( $flags );
3913 return $rev ? $rev->getTimestamp() : null;
3914 }
3915
3921 public function isNewPage() {
3922 $dbr = wfGetDB( DB_REPLICA );
3923 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
3924 }
3925
3931 public function isBigDeletion() {
3933
3934 if ( !$wgDeleteRevisionsLimit ) {
3935 return false;
3936 }
3937
3938 if ( $this->mIsBigDeletion === null ) {
3939 $dbr = wfGetDB( DB_REPLICA );
3940
3941 $revCount = $dbr->selectRowCount(
3942 'revision',
3943 '1',
3944 [ 'rev_page' => $this->getArticleID() ],
3945 __METHOD__,
3946 [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
3947 );
3948
3949 $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
3950 }
3951
3952 return $this->mIsBigDeletion;
3953 }
3954
3960 public function estimateRevisionCount() {
3961 if ( !$this->exists() ) {
3962 return 0;
3963 }
3964
3965 if ( $this->mEstimateRevisions === null ) {
3966 $dbr = wfGetDB( DB_REPLICA );
3967 $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
3968 [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
3969 }
3970
3971 return $this->mEstimateRevisions;
3972 }
3973
3983 public function countRevisionsBetween( $old, $new, $max = null ) {
3984 if ( !( $old instanceof Revision ) ) {
3985 $old = Revision::newFromTitle( $this, (int)$old );
3986 }
3987 if ( !( $new instanceof Revision ) ) {
3988 $new = Revision::newFromTitle( $this, (int)$new );
3989 }
3990 if ( !$old || !$new ) {
3991 return 0; // nothing to compare
3992 }
3993 $dbr = wfGetDB( DB_REPLICA );
3994 $conds = [
3995 'rev_page' => $this->getArticleID(),
3996 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
3997 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
3998 ];
3999 if ( $max !== null ) {
4000 return $dbr->selectRowCount( 'revision', '1',
4001 $conds,
4002 __METHOD__,
4003 [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4004 );
4005 } else {
4006 return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4007 }
4008 }
4009
4026 public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4027 if ( !( $old instanceof Revision ) ) {
4028 $old = Revision::newFromTitle( $this, (int)$old );
4029 }
4030 if ( !( $new instanceof Revision ) ) {
4031 $new = Revision::newFromTitle( $this, (int)$new );
4032 }
4033 // XXX: what if Revision objects are passed in, but they don't refer to this title?
4034 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4035 // in the sanity check below?
4036 if ( !$old || !$new ) {
4037 return null; // nothing to compare
4038 }
4039 $authors = [];
4040 $old_cmp = '>';
4041 $new_cmp = '<';
4042 $options = (array)$options;
4043 if ( in_array( 'include_old', $options ) ) {
4044 $old_cmp = '>=';
4045 }
4046 if ( in_array( 'include_new', $options ) ) {
4047 $new_cmp = '<=';
4048 }
4049 if ( in_array( 'include_both', $options ) ) {
4050 $old_cmp = '>=';
4051 $new_cmp = '<=';
4052 }
4053 // No DB query needed if $old and $new are the same or successive revisions:
4054 if ( $old->getId() === $new->getId() ) {
4055 return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4056 [] :
4057 [ $old->getUserText( RevisionRecord::RAW ) ];
4058 } elseif ( $old->getId() === $new->getParentId() ) {
4059 if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4060 $authors[] = $oldUserText = $old->getUserText( RevisionRecord::RAW );
4061 $newUserText = $new->getUserText( RevisionRecord::RAW );
4062 if ( $oldUserText != $newUserText ) {
4063 $authors[] = $newUserText;
4064 }
4065 } elseif ( $old_cmp === '>=' ) {
4066 $authors[] = $old->getUserText( RevisionRecord::RAW );
4067 } elseif ( $new_cmp === '<=' ) {
4068 $authors[] = $new->getUserText( RevisionRecord::RAW );
4069 }
4070 return $authors;
4071 }
4072 $dbr = wfGetDB( DB_REPLICA );
4074 $authors = $dbr->selectFieldValues(
4075 $revQuery['tables'],
4076 $revQuery['fields']['rev_user_text'],
4077 [
4078 'rev_page' => $this->getArticleID(),
4079 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4080 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4081 ], __METHOD__,
4082 [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated
4083 $revQuery['joins']
4084 );
4085 return $authors;
4086 }
4087
4102 public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4103 $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4104 return $authors ? count( $authors ) : 0;
4105 }
4106
4113 public function equals( LinkTarget $title ) {
4114 // Note: === is necessary for proper matching of number-like titles.
4115 return $this->mInterwiki === $title->getInterwiki()
4116 && $this->mNamespace == $title->getNamespace()
4117 && $this->mDbkeyform === $title->getDBkey();
4118 }
4119
4126 public function isSubpageOf( Title $title ) {
4127 return $this->mInterwiki === $title->mInterwiki
4128 && $this->mNamespace == $title->mNamespace
4129 && strpos( $this->mDbkeyform, $title->mDbkeyform . '/' ) === 0;
4130 }
4131
4142 public function exists( $flags = 0 ) {
4143 $exists = $this->getArticleID( $flags ) != 0;
4144 Hooks::run( 'TitleExists', [ $this, &$exists ] );
4145 return $exists;
4146 }
4147
4164 public function isAlwaysKnown() {
4165 $isKnown = null;
4166
4177 Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4178
4179 if ( !is_null( $isKnown ) ) {
4180 return $isKnown;
4181 }
4182
4183 if ( $this->isExternal() ) {
4184 return true; // any interwiki link might be viewable, for all we know
4185 }
4186
4187 $services = MediaWikiServices::getInstance();
4188 switch ( $this->mNamespace ) {
4189 case NS_MEDIA:
4190 case NS_FILE:
4191 // file exists, possibly in a foreign repo
4192 return (bool)$services->getRepoGroup()->findFile( $this );
4193 case NS_SPECIAL:
4194 // valid special page
4195 return $services->getSpecialPageFactory()->exists( $this->mDbkeyform );
4196 case NS_MAIN:
4197 // selflink, possibly with fragment
4198 return $this->mDbkeyform == '';
4199 case NS_MEDIAWIKI:
4200 // known system message
4201 return $this->hasSourceText() !== false;
4202 default:
4203 return false;
4204 }
4205 }
4206
4218 public function isKnown() {
4219 return $this->isAlwaysKnown() || $this->exists();
4220 }
4221
4227 public function hasSourceText() {
4228 if ( $this->exists() ) {
4229 return true;
4230 }
4231
4232 if ( $this->mNamespace == NS_MEDIAWIKI ) {
4233 // If the page doesn't exist but is a known system message, default
4234 // message content will be displayed, same for language subpages-
4235 // Use always content language to avoid loading hundreds of languages
4236 // to get the link color.
4237 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
4238 list( $name, ) = MessageCache::singleton()->figureMessage(
4239 $contLang->lcfirst( $this->getText() )
4240 );
4241 $message = wfMessage( $name )->inLanguage( $contLang )->useDatabase( false );
4242 return $message->exists();
4243 }
4244
4245 return false;
4246 }
4247
4285 public function getDefaultMessageText() {
4286 if ( $this->mNamespace != NS_MEDIAWIKI ) { // Just in case
4287 return false;
4288 }
4289
4290 list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4291 MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $this->getText() )
4292 );
4293 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4294
4295 if ( $message->exists() ) {
4296 return $message->plain();
4297 } else {
4298 return false;
4299 }
4300 }
4301
4308 public function invalidateCache( $purgeTime = null ) {
4309 if ( wfReadOnly() ) {
4310 return false;
4311 } elseif ( $this->mArticleID === 0 ) {
4312 return true; // avoid gap locking if we know it's not there
4313 }
4314
4315 $dbw = wfGetDB( DB_MASTER );
4316 $dbw->onTransactionPreCommitOrIdle(
4317 function () use ( $dbw ) {
4318 ResourceLoaderWikiModule::invalidateModuleCache(
4319 $this, null, null, $dbw->getDomainID() );
4320 },
4321 __METHOD__
4322 );
4323
4324 $conds = $this->pageCond();
4325 DeferredUpdates::addUpdate(
4326 new AutoCommitUpdate(
4327 $dbw,
4328 __METHOD__,
4329 function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4330 $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4331 $dbw->update(
4332 'page',
4333 [ 'page_touched' => $dbTimestamp ],
4334 $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4335 $fname
4336 );
4337 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4338 }
4339 ),
4340 DeferredUpdates::PRESEND
4341 );
4342
4343 return true;
4344 }
4345
4351 public function touchLinks() {
4352 $jobs = [];
4354 $this,
4355 'pagelinks',
4356 [ 'causeAction' => 'page-touch' ]
4357 );
4358 if ( $this->mNamespace == NS_CATEGORY ) {
4360 $this,
4361 'categorylinks',
4362 [ 'causeAction' => 'category-touch' ]
4363 );
4364 }
4365
4366 JobQueueGroup::singleton()->lazyPush( $jobs );
4367 }
4368
4375 public function getTouched( $db = null ) {
4376 if ( $db === null ) {
4377 $db = wfGetDB( DB_REPLICA );
4378 }
4379 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4380 return $touched;
4381 }
4382
4389 public function getNotificationTimestamp( $user = null ) {
4390 global $wgUser;
4391
4392 // Assume current user if none given
4393 if ( !$user ) {
4394 $user = $wgUser;
4395 }
4396 // Check cache first
4397 $uid = $user->getId();
4398 if ( !$uid ) {
4399 return false;
4400 }
4401 // avoid isset here, as it'll return false for null entries
4402 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4403 return $this->mNotificationTimestamp[$uid];
4404 }
4405 // Don't cache too much!
4406 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4407 $this->mNotificationTimestamp = [];
4408 }
4409
4410 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4411 $watchedItem = $store->getWatchedItem( $user, $this );
4412 if ( $watchedItem ) {
4413 $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4414 } else {
4415 $this->mNotificationTimestamp[$uid] = false;
4416 }
4417
4418 return $this->mNotificationTimestamp[$uid];
4419 }
4420
4427 public function getNamespaceKey( $prepend = 'nstab-' ) {
4428 // Gets the subject namespace of this title
4429 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
4430 $subjectNS = $nsInfo->getSubject( $this->mNamespace );
4431 // Prefer canonical namespace name for HTML IDs
4432 $namespaceKey = $nsInfo->getCanonicalName( $subjectNS );
4433 if ( $namespaceKey === false ) {
4434 // Fallback to localised text
4435 $namespaceKey = $this->getSubjectNsText();
4436 }
4437 // Makes namespace key lowercase
4438 $namespaceKey = MediaWikiServices::getInstance()->getContentLanguage()->lc( $namespaceKey );
4439 // Uses main
4440 if ( $namespaceKey == '' ) {
4441 $namespaceKey = 'main';
4442 }
4443 // Changes file to image for backwards compatibility
4444 if ( $namespaceKey == 'file' ) {
4445 $namespaceKey = 'image';
4446 }
4447 return $prepend . $namespaceKey;
4448 }
4449
4456 public function getRedirectsHere( $ns = null ) {
4457 $redirs = [];
4458
4459 $dbr = wfGetDB( DB_REPLICA );
4460 $where = [
4461 'rd_namespace' => $this->mNamespace,
4462 'rd_title' => $this->mDbkeyform,
4463 'rd_from = page_id'
4464 ];
4465 if ( $this->isExternal() ) {
4466 $where['rd_interwiki'] = $this->mInterwiki;
4467 } else {
4468 $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4469 }
4470 if ( !is_null( $ns ) ) {
4471 $where['page_namespace'] = $ns;
4472 }
4473
4474 $res = $dbr->select(
4475 [ 'redirect', 'page' ],
4476 [ 'page_namespace', 'page_title' ],
4477 $where,
4478 __METHOD__
4479 );
4480
4481 foreach ( $res as $row ) {
4482 $redirs[] = self::newFromRow( $row );
4483 }
4484 return $redirs;
4485 }
4486
4492 public function isValidRedirectTarget() {
4494
4495 if ( $this->isSpecialPage() ) {
4496 // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4497 if ( $this->isSpecial( 'Userlogout' ) ) {
4498 return false;
4499 }
4500
4501 foreach ( $wgInvalidRedirectTargets as $target ) {
4502 if ( $this->isSpecial( $target ) ) {
4503 return false;
4504 }
4505 }
4506 }
4507
4508 return true;
4509 }
4510
4516 public function getBacklinkCache() {
4517 return BacklinkCache::get( $this );
4518 }
4519
4525 public function canUseNoindex() {
4527
4528 $bannedNamespaces = $wgExemptFromUserRobotsControl ??
4529 MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces();
4530
4531 return !in_array( $this->mNamespace, $bannedNamespaces );
4532 }
4533
4544 public function getCategorySortkey( $prefix = '' ) {
4545 $unprefixed = $this->getText();
4546
4547 // Anything that uses this hook should only depend
4548 // on the Title object passed in, and should probably
4549 // tell the users to run updateCollations.php --force
4550 // in order to re-sort existing category relations.
4551 Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4552 if ( $prefix !== '' ) {
4553 # Separate with a line feed, so the unprefixed part is only used as
4554 # a tiebreaker when two pages have the exact same prefix.
4555 # In UCA, tab is the only character that can sort above LF
4556 # so we strip both of them from the original prefix.
4557 $prefix = strtr( $prefix, "\n\t", ' ' );
4558 return "$prefix\n$unprefixed";
4559 }
4560 return $unprefixed;
4561 }
4562
4570 private function getDbPageLanguageCode() {
4571 global $wgPageLanguageUseDB;
4572
4573 // check, if the page language could be saved in the database, and if so and
4574 // the value is not requested already, lookup the page language using LinkCache
4575 if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4576 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
4577 $linkCache->addLinkObj( $this );
4578 $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4579 }
4580
4581 return $this->mDbPageLanguage;
4582 }
4583
4592 public function getPageLanguage() {
4593 global $wgLang, $wgLanguageCode;
4594 if ( $this->isSpecialPage() ) {
4595 // special pages are in the user language
4596 return $wgLang;
4597 }
4598
4599 // Checking if DB language is set
4600 $dbPageLanguage = $this->getDbPageLanguageCode();
4601 if ( $dbPageLanguage ) {
4602 return wfGetLangObj( $dbPageLanguage );
4603 }
4604
4605 if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4606 // Note that this may depend on user settings, so the cache should
4607 // be only per-request.
4608 // NOTE: ContentHandler::getPageLanguage() may need to load the
4609 // content to determine the page language!
4610 // Checking $wgLanguageCode hasn't changed for the benefit of unit
4611 // tests.
4612 $contentHandler = ContentHandler::getForTitle( $this );
4613 $langObj = $contentHandler->getPageLanguage( $this );
4614 $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4615 } else {
4616 $langObj = Language::factory( $this->mPageLanguage[0] );
4617 }
4618
4619 return $langObj;
4620 }
4621
4630 public function getPageViewLanguage() {
4631 global $wgLang;
4632
4633 if ( $this->isSpecialPage() ) {
4634 // If the user chooses a variant, the content is actually
4635 // in a language whose code is the variant code.
4636 $variant = $wgLang->getPreferredVariant();
4637 if ( $wgLang->getCode() !== $variant ) {
4638 return Language::factory( $variant );
4639 }
4640
4641 return $wgLang;
4642 }
4643
4644 // Checking if DB language is set
4645 $dbPageLanguage = $this->getDbPageLanguageCode();
4646 if ( $dbPageLanguage ) {
4647 $pageLang = wfGetLangObj( $dbPageLanguage );
4648 $variant = $pageLang->getPreferredVariant();
4649 if ( $pageLang->getCode() !== $variant ) {
4650 $pageLang = Language::factory( $variant );
4651 }
4652
4653 return $pageLang;
4654 }
4655
4656 // @note Can't be cached persistently, depends on user settings.
4657 // @note ContentHandler::getPageViewLanguage() may need to load the
4658 // content to determine the page language!
4659 $contentHandler = ContentHandler::getForTitle( $this );
4660 $pageLang = $contentHandler->getPageViewLanguage( $this );
4661 return $pageLang;
4662 }
4663
4674 public function getEditNotices( $oldid = 0 ) {
4675 $notices = [];
4676
4677 // Optional notice for the entire namespace
4678 $editnotice_ns = 'editnotice-' . $this->mNamespace;
4679 $msg = wfMessage( $editnotice_ns );
4680 if ( $msg->exists() ) {
4681 $html = $msg->parseAsBlock();
4682 // Edit notices may have complex logic, but output nothing (T91715)
4683 if ( trim( $html ) !== '' ) {
4684 $notices[$editnotice_ns] = Html::rawElement(
4685 'div',
4686 [ 'class' => [
4687 'mw-editnotice',
4688 'mw-editnotice-namespace',
4689 Sanitizer::escapeClass( "mw-$editnotice_ns" )
4690 ] ],
4691 $html
4692 );
4693 }
4694 }
4695
4696 if (
4697 MediaWikiServices::getInstance()->getNamespaceInfo()->
4698 hasSubpages( $this->mNamespace )
4699 ) {
4700 // Optional notice for page itself and any parent page
4701 $editnotice_base = $editnotice_ns;
4702 foreach ( explode( '/', $this->mDbkeyform ) as $part ) {
4703 $editnotice_base .= '-' . $part;
4704 $msg = wfMessage( $editnotice_base );
4705 if ( $msg->exists() ) {
4706 $html = $msg->parseAsBlock();
4707 if ( trim( $html ) !== '' ) {
4708 $notices[$editnotice_base] = Html::rawElement(
4709 'div',
4710 [ 'class' => [
4711 'mw-editnotice',
4712 'mw-editnotice-base',
4713 Sanitizer::escapeClass( "mw-$editnotice_base" )
4714 ] ],
4715 $html
4716 );
4717 }
4718 }
4719 }
4720 } else {
4721 // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4722 $editnoticeText = $editnotice_ns . '-' . strtr( $this->mDbkeyform, '/', '-' );
4723 $msg = wfMessage( $editnoticeText );
4724 if ( $msg->exists() ) {
4725 $html = $msg->parseAsBlock();
4726 if ( trim( $html ) !== '' ) {
4727 $notices[$editnoticeText] = Html::rawElement(
4728 'div',
4729 [ 'class' => [
4730 'mw-editnotice',
4731 'mw-editnotice-page',
4732 Sanitizer::escapeClass( "mw-$editnoticeText" )
4733 ] ],
4734 $html
4735 );
4736 }
4737 }
4738 }
4739
4740 Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4741 return $notices;
4742 }
4743
4748 private function loadFieldFromDB( $field, $flags ) {
4749 if ( !in_array( $field, self::getSelectFields(), true ) ) {
4750 return false; // field does not exist
4751 }
4752
4753 $flags |= ( $flags & self::GAID_FOR_UPDATE ) ? self::READ_LATEST : 0; // b/c
4754 list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
4755
4756 return wfGetDB( $index )->selectField(
4757 'page',
4758 $field,
4759 $this->pageCond(),
4760 __METHOD__,
4761 $options
4762 );
4763 }
4764
4768 public function __sleep() {
4769 return [
4770 'mNamespace',
4771 'mDbkeyform',
4772 'mFragment',
4773 'mInterwiki',
4774 'mLocalInterwiki',
4775 'mUserCaseDBKey',
4776 'mDefaultNamespace',
4777 ];
4778 }
4779
4780 public function __wakeup() {
4781 $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4782 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4783 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4784 }
4785
4786}
$wgRestrictionLevels
Rights which can be required for each protection level (via action=protect)
$wgExemptFromUserRobotsControl
An array of namespace keys in which the INDEX/__NOINDEX__ magic words will not function,...
$wgLegalTitleChars
Allowed title characters – regex character class Don't change this unless you know what you're doing.
bool $wgPageLanguageUseDB
Enable page language feature Allows setting page language in database.
bool $wgMainPageIsDomainRoot
Option to whether serve the main page as the domain root.
$wgSemiprotectedRestrictionLevels
Restriction levels that should be considered "semiprotected".
$wgLanguageCode
Site language code.
$wgScript
The URL path to index.php.
$wgInternalServer
Internal server name as known to CDN, if different.
$wgInvalidRedirectTargets
Array of invalid page redirect targets.
$wgNamespaceProtection
Set the minimum permissions required to edit pages in each namespace.
$wgRestrictionTypes
Set of available actions that can be restricted via action=protect You probably shouldn't change this...
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
$wgServer
URL of the server.
string[] $wgRawHtmlMessages
List of messages which might contain raw HTML.
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility.
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfMergeErrorArrays(... $args)
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:751
$wgLang
Definition Setup.php:880
Deferrable Update for closure/callback updates via IDatabase::doAtomicSection()
Deferrable Update for closure/callback updates that should use auto-commit mode.
static get(Title $title)
Create a new BacklinkCache or reuse any existing one.
Handles purging the appropriate CDN objects given a list of URLs or Title instances.
static newForBacklinks(Title $title, $table, $params=[])
MediaWiki exception.
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Handles a simple LRU key/value map with a maximum number of entries.
set( $key, $value, $rank=self::RANK_TOP)
Set a key/value pair.
get( $key, $maxAge=INF, $default=null)
Get the value for a key.
clear( $keys=null)
Clear one or several cache entries, or all cache entries.
MediaWikiServices is the service locator for the application scope of MediaWiki.
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Page revision base class.
Handles the backend logic of moving a page from one title to another.
Definition MovePage.php:36
static getActionPaths(array $actionPaths, $articlePath)
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object.
Definition Revision.php:315
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition Revision.php:138
The TitleArray class only exists to provide the newFromResult method at pre- sent.
static newFromResult( $res)
Represents a page (or page fragment) title within MediaWiki.
Represents a title within MediaWiki.
Definition Title.php:42
string $mInterwiki
Interwiki prefix.
Definition Title.php:87
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition Title.php:467
getSubpages( $limit=-1)
Get all subpages of this page.
Definition Title.php:3046
array null $mPageLanguage
The (string) language code of the page's language and content code.
Definition Title.php:176
isWatchable()
Can this title be added to a user's watchlist?
Definition Title.php:1198
getTalkPageIfDefined()
Get a Title object associated with the talk page of this article, if such a talk page can exist.
Definition Title.php:1599
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1037
estimateRevisionCount()
Get the approximate revision count of this page.
Definition Title.php:3960
__wakeup()
Text form (spaces not underscores) of the main part.
Definition Title.php:4780
static newFromDBkey( $key)
Create a new Title from a prefixed DB key.
Definition Title.php:228
isProtected( $action='')
Does the title correspond to a protected article?
Definition Title.php:2574
getUserPermissionsErrors( $action, $user, $rigor=PermissionManager::RIGOR_SECURE, $ignoreErrors=[])
Can $user perform $action on this page?
Definition Title.php:2392
const NEW_CLONE
Flag for use with factory methods like newFromLinkTarget() that have a $forceClone parameter.
Definition Title.php:67
getTitleProtectionInternal()
Fetch title protection settings.
Definition Title.php:2481
getLinkURL( $query='', $query2=false, $proto=false)
Get a URL that's the simplest URL that will be valid to link, locally, to the current Title.
Definition Title.php:2243
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition Title.php:138
isSingleRevRedirect()
Locks the page row and check if this page is single revision redirect.
Definition Title.php:3655
wasLocalInterwiki()
Was this a local interwiki link?
Definition Title.php:932
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition Title.php:2268
purgeSquid()
Purge all applicable CDN URLs.
Definition Title.php:3534
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition Title.php:2068
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition Title.php:2756
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition Title.php:268
isKnown()
Does this title refer to a page that can (or might) be meaningfully viewed? In particular,...
Definition Title.php:4218
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition Title.php:112
getBacklinkCache()
Get a backlink cache object.
Definition Title.php:4516
static getInterwikiLookup()
B/C kludge: provide an InterwikiLookup for use by Title.
Definition Title.php:210
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition Title.php:198
inNamespace( $ns)
Returns true if the title is inside the specified namespace.
Definition Title.php:1261
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition Title.php:3101
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition Title.php:3280
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition Title.php:1569
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition Title.php:3301
isSiteJsonConfigPage()
Is this a sitewide JSON "config" page?
Definition Title.php:1502
isSiteCssConfigPage()
Is this a sitewide CSS "config" page?
Definition Title.php:1484
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition Title.php:2770
hasContentModel( $id)
Convenience method for checking a title's content model name.
Definition Title.php:1082
static clearCaches()
Text form (spaces not underscores) of the main part.
Definition Title.php:3265
createFragmentTarget( $fragment)
Creates a new Title for a different fragment of the same page.
Definition Title.php:1764
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition Title.php:1696
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move a title to a new location.
Definition Title.php:3587
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition Title.php:1368
getFragment()
Get the Title fragment (i.e.
Definition Title.php:1707
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not.
Definition Title.php:2626
static getFilteredRestrictionTypes( $exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition Title.php:2413
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition Title.php:2011
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition Title.php:1380
getTalkNsText()
Get the namespace text of the talk page.
Definition Title.php:1160
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition Title.php:2796
hasFragment()
Check if a Title fragment is set.
Definition Title.php:1717
static nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition Title.php:674
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition Title.php:1219
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition Title.php:4456
getLength( $flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition Title.php:3184
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition Title.php:170
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition Title.php:3551
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition Title.php:1842
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition Title.php:2743
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition Title.php:4525
exists( $flags=0)
Check if page exists.
Definition Title.php:4142
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition Title.php:404
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,...
Definition Title.php:353
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition Title.php:897
int $mLength
The page length, 0 for special pages.
Definition Title.php:164
loadFromRow( $row)
Load Title object fields from a DB row.
Definition Title.php:530
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition Title.php:4592
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition Title.php:89
getUserCaseDBKey()
Get the DB key with the initial letter case as specified by the user.
Definition Title.php:1023
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable.
Definition Title.php:1323
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles.
Definition Title.php:51
getRestrictionExpiry( $action)
Get the expiry time for the restriction against a given action.
Definition Title.php:2784
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition Title.php:1614
getTemplateLinksTo( $options=[])
Get an array of Title objects using this Title as a template Also stores the IDs in the link cache.
Definition Title.php:3386
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias,...
Definition Title.php:1237
getRestrictionTypes()
Returns restriction types for the current Title.
Definition Title.php:2431
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition Title.php:695
__toString()
Return a string representation of this title.
Definition Title.php:1832
hasSubjectNamespace( $ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition Title.php:1300
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition Title.php:2546
lazyFillContentModel( $model)
If the content model field is not frozen then update it with a retreived value.
Definition Title.php:1113
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1806
areCascadeProtectionSourcesLoaded( $getPages=true)
Determines whether cascading protection sources have already been loaded from the database.
Definition Title.php:2640
getPreviousRevisionID( $revId, $flags=0)
Get the revision ID of the previous revision.
Definition Title.php:3857
getNsText()
Get the namespace text.
Definition Title.php:1124
canExist()
Is this in a namespace that allows actual pages?
Definition Title.php:1186
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition Title.php:2974
getDefaultMessageText()
Get the default (plain) message contents for an page that overrides an interface message key.
Definition Title.php:4285
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition Title.php:4570
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition Title.php:3983
bool $mForcedContentModel
If a content model was forced via setContentModel() this will be true to avoid having other code path...
Definition Title.php:109
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition Title.php:4389
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition Title.php:942
isRawHtmlMessage()
Is this a message which can contain raw HTML?
Definition Title.php:1538
isSiteJsConfigPage()
Is this a sitewide JS "config" page?
Definition Title.php:1520
isNewPage()
Check if this is a new page.
Definition Title.php:3921
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition Title.php:4351
isExternal()
Is this Title interwiki?
Definition Title.php:912
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition Title.php:141
isMainPage()
Is this the mainpage?
Definition Title.php:1347
isUserConfigPage()
Is this a "config" (.css, .json, or .js) sub-page of a user page?
Definition Title.php:1412
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition Title.php:1726
string bool null $mDbPageLanguage
The page language code from the database, null if not saved in the database or false if not loaded,...
Definition Title.php:181
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition Title.php:4026
loadRestrictions( $oldFashionedRestrictions=null, $flags=0)
Load restrictions from the page_restrictions table.
Definition Title.php:2893
isSpecialPage()
Returns true if this is a special page.
Definition Title.php:1209
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition Title.php:2607
isUserJsConfigPage()
Is this a JS "config" sub-page of a user page?
Definition Title.php:1470
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition Title.php:2000
canHaveTalkPage()
Can this title have a corresponding talk page?
Definition Title.php:1177
isTalkPage()
Is this a talk page of some sort?
Definition Title.php:1553
getRootTitle()
Get the root page name title, i.e.
Definition Title.php:1889
bool int $mLatestID
ID of most recent revision.
Definition Title.php:97
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page.
Definition Title.php:3468
getDBkey()
Get the main part with underscores.
Definition Title.php:1013
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object.
Definition Title.php:1780
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition Title.php:3911
string $mFragment
Title fragment (i.e.
Definition Title.php:91
getRootText()
Get the root page name text without a namespace, i.e.
Definition Title.php:1865
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition Title.php:2103
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition Title.php:649
bool string $mContentModel
ID of the page's content model, i.e.
Definition Title.php:103
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition Title.php:3211
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition Title.php:709
userCan( $action, $user=null, $rigor=PermissionManager::RIGOR_SECURE)
Can $user perform $action on this page?
Definition Title.php:2354
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition Title.php:4492
getLinksFrom( $options=[], $table='pagelinks', $prefix='pl')
Get an array of Title objects linked from this Title Also stores the IDs in the link cache.
Definition Title.php:3402
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition Title.php:814
getLinksTo( $options=[], $table='pagelinks', $prefix='pl')
Get an array of Title objects linking to this Title Also stores the IDs in the link cache.
Definition Title.php:3344
string null $prefixedText
Text form including namespace/interwiki, initialised on demand.
Definition Title.php:151
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition Title.php:135
getPartialURL()
Get the URL-encoded form of the main part.
Definition Title.php:1004
getBaseText()
Get the base page name without a namespace, i.e.
Definition Title.php:1912
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page,...
Definition Title.php:1312
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:995
getTouched( $db=null)
Get the last touched timestamp.
Definition Title.php:4375
getTitleValue()
Get a TitleValue object representing this Title.
Definition Title.php:972
static MapCacheLRU null $titleCache
Definition Title.php:44
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition Title.php:3819
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition Title.php:126
string $mUrlform
URL-encoded form of the main part.
Definition Title.php:79
static castFromLinkTarget( $linkTarget)
Same as newFromLinkTarget, but if passed null, returns null.
Definition Title.php:292
getFirstRevision( $flags=0)
Get the first revision of the page.
Definition Title.php:3879
string $mTextform
Text form (spaces not underscores) of the main part.
Definition Title.php:77
getOtherPage()
Get the other title for this page, if this is a subject page get the talk page, if it is a subject pa...
Definition Title.php:1671
static newFromIDs( $ids)
Make an array of titles from an array of IDs.
Definition Title.php:492
quickUserCan( $action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition Title.php:2335
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
Definition Title.php:441
__construct()
Definition Title.php:217
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition Title.php:4126
getBaseTitle()
Get the base page name title, i.e.
Definition Title.php:1942
getParentCategoryTree( $children=[])
Get a tree of parent categories.
Definition Title.php:3792
bool $mHasSubpages
Whether a page has any subpages.
Definition Title.php:173
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition Title.php:3869
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition Title.php:132
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition Title.php:2815
static fixUrlQueryArgs( $query, $query2=false)
Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args get{Canonical,...
Definition Title.php:2030
isUserJsonConfigPage()
Is this a JSON "config" sub-page of a user page?
Definition Title.php:1456
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition Title.php:3706
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition Title.php:3155
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition Title.php:316
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition Title.php:4308
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition Title.php:129
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:3126
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition Title.php:1149
int $mNamespace
Namespace index, i.e.
Definition Title.php:85
warnIfPageCannotExist(Title $title, $method)
Definition Title.php:1640
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:613
null $mRedirect
Is the article at this title a redirect?
Definition Title.php:167
countAuthorsBetween( $old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition Title.php:4102
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition Title.php:841
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition Title.php:2292
isDeleted()
Is there a version of this page in the deletion archive?
Definition Title.php:3076
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition Title.php:4630
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define().
Definition Title.php:58
getSkinFromConfigSubpage()
Trim down a .css, .json, or .js subpage title to get the corresponding skin name.
Definition Title.php:1426
array $mRestrictions
Array of groups allowed to edit this article.
Definition Title.php:115
isSiteConfigPage()
Could this MediaWiki namespace page contain custom CSS, JSON, or JavaScript for the global UI.
Definition Title.php:1398
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition Title.php:161
static newFromTitleValue(TitleValue $titleValue, $forceClone='')
Returns a Title given a TitleValue.
Definition Title.php:253
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move this page's subpages to be subpages of $nt.
Definition Title.php:3620
getRelativeRevisionID( $revId, $flags, $dir)
Get next/previous revision ID relative to another revision ID.
Definition Title.php:3835
deleteTitleProtection()
Remove any title protection due to page existing.
Definition Title.php:2528
getSubpage( $text)
Get the title for a subpage of the current page.
Definition Title.php:1986
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition Title.php:2458
getEditURL()
Get the edit URL for this Title.
Definition Title.php:2306
loadFieldFromDB( $field, $flags)
Definition Title.php:4748
getParentCategories()
Get categories to which this Title belongs and return an array of categories' names.
Definition Title.php:3757
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:586
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition Title.php:94
static getTitleCache()
Definition Title.php:427
getTemplateLinksFrom( $options=[])
Get an array of Title objects used on this Title as a template Also stores the IDs in the link cache.
Definition Title.php:3456
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition Title.php:955
isSubpage()
Is this a subpage?
Definition Title.php:1356
isValid()
Returns true if the title is valid, false if it is invalid.
Definition Title.php:863
resetArticleID( $id)
Inject a page ID, reset DB-loaded fields, and clear the link cache for this title.
Definition Title.php:3243
setFragment( $fragment)
Set the fragment for this title.
Definition Title.php:1753
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition Title.php:2137
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:1049
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition Title.php:3931
bool null $mIsBigDeletion
Would deleting this page be a big deletion?
Definition Title.php:187
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition Title.php:3504
TitleValue null $mTitleValue
A corresponding TitleValue object.
Definition Title.php:184
equals(LinkTarget $title)
Compare with another title.
Definition Title.php:4113
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition Title.php:83
getInterwiki()
Get the interwiki prefix.
Definition Title.php:923
getEditNotices( $oldid=0)
Get a list of rendered edit notices for this page.
Definition Title.php:4674
__sleep()
Definition Title.php:4768
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition Title.php:1099
getCascadeProtectionSources( $getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition Title.php:2657
mixed $mTitleProtection
Cached value for getTitleProtection (create protection)
Definition Title.php:154
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition Title.php:1962
string $mDbkeyform
Main part with underscores.
Definition Title.php:81
hasSourceText()
Does this page have source text?
Definition Title.php:4227
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition Title.php:2964
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1818
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition Title.php:3015
string bool $mOldRestrictions
Comma-separated set of permission keys indicating who can move or edit the page from the page table,...
Definition Title.php:123
inNamespaces(... $namespaces)
Returns true if the title is inside one of the specified namespaces.
Definition Title.php:1273
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition Title.php:4164
getNamespaceKey( $prepend='nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition Title.php:4427
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition Title.php:4544
static newFromRow( $row)
Make a Title object from a DB row.
Definition Title.php:518
isUserCssConfigPage()
Is this a CSS "config" sub-page of a user page?
Definition Title.php:1442
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
Relational database abstraction object.
Definition Database.php:49
const PROTO_CANONICAL
Definition Defines.php:212
const NS_USER
Definition Defines.php:71
const CONTENT_MODEL_CSS
Definition Defines.php:226
const NS_FILE
Definition Defines.php:75
const PROTO_CURRENT
Definition Defines.php:211
const NS_MAIN
Definition Defines.php:69
const NS_MEDIAWIKI
Definition Defines.php:77
const NS_SPECIAL
Definition Defines.php:58
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:224
const CONTENT_MODEL_JSON
Definition Defines.php:228
const PROTO_HTTP
Definition Defines.php:208
const NS_MEDIA
Definition Defines.php:57
const PROTO_RELATIVE
Definition Defines.php:210
const NS_CATEGORY
Definition Defines.php:83
const CONTENT_MODEL_JAVASCRIPT
Definition Defines.php:225
$wgActionPaths
Definition img_auth.php:48
$wgArticlePath
Definition img_auth.php:47
Interface for database access objects.
Service interface for looking up Interwiki records.
getInterwiki()
The interwiki component of this LinkTarget.
getFragment()
Get the link fragment (i.e.
getNamespace()
Get the namespace index.
getText()
Returns the link in text form, without namespace prefix or fragment.
Interface for localizing messages in MediaWiki.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26
$content
Definition router.php:78
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!isset( $args[0])) $lang