MediaWiki  master
Title.php
Go to the documentation of this file.
1 <?php
31 
40 class Title implements LinkTarget, IDBAccessObject {
42  private static $titleCache = null;
43 
49  const CACHE_MAX = 1000;
50 
55  const GAID_FOR_UPDATE = 1;
56 
64  const NEW_CLONE = 'clone';
65 
71  // @{
72 
74  public $mTextform = '';
75 
77  public $mUrlform = '';
78 
80  public $mDbkeyform = '';
81 
83  protected $mUserCaseDBKey;
84 
86  public $mNamespace = NS_MAIN;
87 
89  public $mInterwiki = '';
90 
92  private $mLocalInterwiki = false;
93 
95  public $mFragment = '';
96 
98  public $mArticleID = -1;
99 
101  protected $mLatestID = false;
102 
107  private $mContentModel = false;
108 
113  private $mForcedContentModel = false;
114 
117 
119  public $mRestrictions = [];
120 
127  protected $mOldRestrictions = false;
128 
131 
134 
136  protected $mRestrictionsExpiry = [];
137 
140 
143 
145  public $mRestrictionsLoaded = false;
146 
155  public $prefixedText = null;
156 
159 
166 
168  protected $mLength = -1;
169 
171  public $mRedirect = null;
172 
175 
177  private $mHasSubpages;
178 
180  private $mPageLanguage = false;
181 
184  private $mDbPageLanguage = false;
185 
187  private $mTitleValue = null;
188 
191  // @}
192 
201  private static function getTitleFormatter() {
202  return MediaWikiServices::getInstance()->getTitleFormatter();
203  }
204 
213  private static function getInterwikiLookup() {
214  return MediaWikiServices::getInstance()->getInterwikiLookup();
215  }
216 
220  function __construct() {
221  }
222 
231  public static function newFromDBkey( $key ) {
232  $t = new self();
233  $t->mDbkeyform = $key;
234 
235  try {
236  $t->secureAndSplit();
237  return $t;
238  } catch ( MalformedTitleException $ex ) {
239  return null;
240  }
241  }
242 
256  public static function newFromTitleValue( TitleValue $titleValue, $forceClone = '' ) {
257  return self::newFromLinkTarget( $titleValue, $forceClone );
258  }
259 
271  public static function newFromLinkTarget( LinkTarget $linkTarget, $forceClone = '' ) {
272  if ( $linkTarget instanceof Title ) {
273  // Special case if it's already a Title object
274  if ( $forceClone === self::NEW_CLONE ) {
275  return clone $linkTarget;
276  } else {
277  return $linkTarget;
278  }
279  }
280  return self::makeTitle(
281  $linkTarget->getNamespace(),
282  $linkTarget->getText(),
283  $linkTarget->getFragment(),
284  $linkTarget->getInterwiki()
285  );
286  }
287 
295  public static function castFromLinkTarget( $linkTarget ) {
296  return $linkTarget ? self::newFromLinkTarget( $linkTarget ) : null;
297  }
298 
319  public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
320  // DWIM: Integers can be passed in here when page titles are used as array keys.
321  if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
322  throw new InvalidArgumentException( '$text must be a string.' );
323  }
324  if ( $text === null ) {
325  return null;
326  }
327 
328  try {
329  return self::newFromTextThrow( (string)$text, $defaultNamespace );
330  } catch ( MalformedTitleException $ex ) {
331  return null;
332  }
333  }
334 
356  public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
357  if ( is_object( $text ) ) {
358  throw new MWException( '$text must be a string, given an object' );
359  } elseif ( $text === null ) {
360  // Legacy code relies on MalformedTitleException being thrown in this case
361  // (happens when URL with no title in it is parsed). TODO fix
362  throw new MalformedTitleException( 'title-invalid-empty' );
363  }
364 
365  $titleCache = self::getTitleCache();
366 
367  // Wiki pages often contain multiple links to the same page.
368  // Title normalization and parsing can become expensive on pages with many
369  // links, so we can save a little time by caching them.
370  // In theory these are value objects and won't get changed...
371  if ( $defaultNamespace == NS_MAIN ) {
372  $t = $titleCache->get( $text );
373  if ( $t ) {
374  return $t;
375  }
376  }
377 
378  // Convert things like &eacute; &#257; or &#x3017; into normalized (T16952) text
379  $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
380 
381  $t = new Title();
382  $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
383  $t->mDefaultNamespace = (int)$defaultNamespace;
384 
385  $t->secureAndSplit();
386  if ( $defaultNamespace == NS_MAIN ) {
387  $titleCache->set( $text, $t );
388  }
389  return $t;
390  }
391 
407  public static function newFromURL( $url ) {
408  $t = new Title();
409 
410  # For compatibility with old buggy URLs. "+" is usually not valid in titles,
411  # but some URLs used it as a space replacement and they still come
412  # from some external search tools.
413  if ( strpos( self::legalChars(), '+' ) === false ) {
414  $url = strtr( $url, '+', ' ' );
415  }
416 
417  $t->mDbkeyform = strtr( $url, ' ', '_' );
418 
419  try {
420  $t->secureAndSplit();
421  return $t;
422  } catch ( MalformedTitleException $ex ) {
423  return null;
424  }
425  }
426 
430  private static function getTitleCache() {
431  if ( self::$titleCache === null ) {
432  self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
433  }
434  return self::$titleCache;
435  }
436 
444  protected static function getSelectFields() {
446 
447  $fields = [
448  'page_namespace', 'page_title', 'page_id',
449  'page_len', 'page_is_redirect', 'page_latest',
450  ];
451 
452  if ( $wgContentHandlerUseDB ) {
453  $fields[] = 'page_content_model';
454  }
455 
456  if ( $wgPageLanguageUseDB ) {
457  $fields[] = 'page_lang';
458  }
459 
460  return $fields;
461  }
462 
470  public static function newFromID( $id, $flags = 0 ) {
471  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
472  $row = $db->selectRow(
473  'page',
474  self::getSelectFields(),
475  [ 'page_id' => $id ],
476  __METHOD__
477  );
478  if ( $row !== false ) {
479  $title = self::newFromRow( $row );
480  } else {
481  $title = null;
482  }
483 
484  return $title;
485  }
486 
493  public static function newFromIDs( $ids ) {
494  if ( !count( $ids ) ) {
495  return [];
496  }
497  $dbr = wfGetDB( DB_REPLICA );
498 
499  $res = $dbr->select(
500  'page',
501  self::getSelectFields(),
502  [ 'page_id' => $ids ],
503  __METHOD__
504  );
505 
506  $titles = [];
507  foreach ( $res as $row ) {
508  $titles[] = self::newFromRow( $row );
509  }
510  return $titles;
511  }
512 
519  public static function newFromRow( $row ) {
520  $t = self::makeTitle( $row->page_namespace, $row->page_title );
521  $t->loadFromRow( $row );
522  return $t;
523  }
524 
531  public function loadFromRow( $row ) {
532  if ( $row ) { // page found
533  if ( isset( $row->page_id ) ) {
534  $this->mArticleID = (int)$row->page_id;
535  }
536  if ( isset( $row->page_len ) ) {
537  $this->mLength = (int)$row->page_len;
538  }
539  if ( isset( $row->page_is_redirect ) ) {
540  $this->mRedirect = (bool)$row->page_is_redirect;
541  }
542  if ( isset( $row->page_latest ) ) {
543  $this->mLatestID = (int)$row->page_latest;
544  }
545  if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
546  $this->mContentModel = (string)$row->page_content_model;
547  } elseif ( !$this->mForcedContentModel ) {
548  $this->mContentModel = false; # initialized lazily in getContentModel()
549  }
550  if ( isset( $row->page_lang ) ) {
551  $this->mDbPageLanguage = (string)$row->page_lang;
552  }
553  if ( isset( $row->page_restrictions ) ) {
554  $this->mOldRestrictions = $row->page_restrictions;
555  }
556  } else { // page not found
557  $this->mArticleID = 0;
558  $this->mLength = 0;
559  $this->mRedirect = false;
560  $this->mLatestID = 0;
561  if ( !$this->mForcedContentModel ) {
562  $this->mContentModel = false; # initialized lazily in getContentModel()
563  }
564  }
565  }
566 
589  public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
590  $t = new Title();
591  $t->mInterwiki = $interwiki;
592  $t->mFragment = $fragment;
593  $t->mNamespace = $ns = (int)$ns;
594  $t->mDbkeyform = strtr( $title, ' ', '_' );
595  $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
596  $t->mUrlform = wfUrlencode( $t->mDbkeyform );
597  $t->mTextform = strtr( $title, '_', ' ' );
598  $t->mContentModel = false; # initialized lazily in getContentModel()
599  return $t;
600  }
601 
617  public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
618  // NOTE: ideally, this would just call makeTitle() and then isValid(),
619  // but presently, that means more overhead on a potential performance hotspot.
620 
621  if ( !MWNamespace::exists( $ns ) ) {
622  return null;
623  }
624 
625  $t = new Title();
626  $t->mDbkeyform = self::makeName( $ns, $title, $fragment, $interwiki, true );
627 
628  try {
629  $t->secureAndSplit();
630  return $t;
631  } catch ( MalformedTitleException $ex ) {
632  return null;
633  }
634  }
635 
653  public static function newMainPage( MessageLocalizer $localizer = null ) {
654  if ( $localizer ) {
655  $msg = $localizer->msg( 'mainpage' );
656  } else {
657  $msg = wfMessage( 'mainpage' );
658  }
659 
660  $title = self::newFromText( $msg->inContentLanguage()->text() );
661 
662  // Every page renders at least one link to the Main Page (e.g. sidebar).
663  // If the localised value is invalid, don't produce fatal errors that
664  // would make the wiki inaccessible (and hard to fix the invalid message).
665  // Gracefully fallback...
666  if ( !$title ) {
667  $title = self::newFromText( 'Main Page' );
668  }
669  return $title;
670  }
671 
678  public static function nameOf( $id ) {
679  $dbr = wfGetDB( DB_REPLICA );
680 
681  $s = $dbr->selectRow(
682  'page',
683  [ 'page_namespace', 'page_title' ],
684  [ 'page_id' => $id ],
685  __METHOD__
686  );
687  if ( $s === false ) {
688  return null;
689  }
690 
691  $n = self::makeName( $s->page_namespace, $s->page_title );
692  return $n;
693  }
694 
700  public static function legalChars() {
701  global $wgLegalTitleChars;
702  return $wgLegalTitleChars;
703  }
704 
714  public static function convertByteClassToUnicodeClass( $byteClass ) {
715  $length = strlen( $byteClass );
716  // Input token queue
717  $x0 = $x1 = $x2 = '';
718  // Decoded queue
719  $d0 = $d1 = $d2 = '';
720  // Decoded integer codepoints
721  $ord0 = $ord1 = $ord2 = 0;
722  // Re-encoded queue
723  $r0 = $r1 = $r2 = '';
724  // Output
725  $out = '';
726  // Flags
727  $allowUnicode = false;
728  for ( $pos = 0; $pos < $length; $pos++ ) {
729  // Shift the queues down
730  $x2 = $x1;
731  $x1 = $x0;
732  $d2 = $d1;
733  $d1 = $d0;
734  $ord2 = $ord1;
735  $ord1 = $ord0;
736  $r2 = $r1;
737  $r1 = $r0;
738  // Load the current input token and decoded values
739  $inChar = $byteClass[$pos];
740  if ( $inChar == '\\' ) {
741  if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
742  $x0 = $inChar . $m[0];
743  $d0 = chr( hexdec( $m[1] ) );
744  $pos += strlen( $m[0] );
745  } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
746  $x0 = $inChar . $m[0];
747  $d0 = chr( octdec( $m[0] ) );
748  $pos += strlen( $m[0] );
749  } elseif ( $pos + 1 >= $length ) {
750  $x0 = $d0 = '\\';
751  } else {
752  $d0 = $byteClass[$pos + 1];
753  $x0 = $inChar . $d0;
754  $pos += 1;
755  }
756  } else {
757  $x0 = $d0 = $inChar;
758  }
759  $ord0 = ord( $d0 );
760  // Load the current re-encoded value
761  if ( $ord0 < 32 || $ord0 == 0x7f ) {
762  $r0 = sprintf( '\x%02x', $ord0 );
763  } elseif ( $ord0 >= 0x80 ) {
764  // Allow unicode if a single high-bit character appears
765  $r0 = sprintf( '\x%02x', $ord0 );
766  $allowUnicode = true;
767  // @phan-suppress-next-line PhanParamSuspiciousOrder false positive
768  } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
769  $r0 = '\\' . $d0;
770  } else {
771  $r0 = $d0;
772  }
773  // Do the output
774  if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
775  // Range
776  if ( $ord2 > $ord0 ) {
777  // Empty range
778  } elseif ( $ord0 >= 0x80 ) {
779  // Unicode range
780  $allowUnicode = true;
781  if ( $ord2 < 0x80 ) {
782  // Keep the non-unicode section of the range
783  $out .= "$r2-\\x7F";
784  }
785  } else {
786  // Normal range
787  $out .= "$r2-$r0";
788  }
789  // Reset state to the initial value
790  $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
791  } elseif ( $ord2 < 0x80 ) {
792  // ASCII character
793  $out .= $r2;
794  }
795  }
796  if ( $ord1 < 0x80 ) {
797  $out .= $r1;
798  }
799  if ( $ord0 < 0x80 ) {
800  $out .= $r0;
801  }
802  if ( $allowUnicode ) {
803  $out .= '\u0080-\uFFFF';
804  }
805  return $out;
806  }
807 
819  public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
820  $canonicalNamespace = false
821  ) {
822  if ( $canonicalNamespace ) {
823  $namespace = MWNamespace::getCanonicalName( $ns );
824  } else {
825  $namespace = MediaWikiServices::getInstance()->getContentLanguage()->getNsText( $ns );
826  }
827  $name = $namespace == '' ? $title : "$namespace:$title";
828  if ( strval( $interwiki ) != '' ) {
829  $name = "$interwiki:$name";
830  }
831  if ( strval( $fragment ) != '' ) {
832  $name .= '#' . $fragment;
833  }
834  return $name;
835  }
836 
845  public static function compare( LinkTarget $a, LinkTarget $b ) {
846  return $a->getNamespace() <=> $b->getNamespace()
847  ?: strcmp( $a->getText(), $b->getText() );
848  }
849 
864  public function isValid() {
865  if ( !MWNamespace::exists( $this->mNamespace ) ) {
866  return false;
867  }
868 
869  try {
870  $parser = MediaWikiServices::getInstance()->getTitleParser();
871  $parser->parseTitle( $this->mDbkeyform, $this->mNamespace );
872  return true;
873  } catch ( MalformedTitleException $ex ) {
874  return false;
875  }
876  }
877 
885  public function isLocal() {
886  if ( $this->isExternal() ) {
887  $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
888  if ( $iw ) {
889  return $iw->isLocal();
890  }
891  }
892  return true;
893  }
894 
900  public function isExternal() {
901  return $this->mInterwiki !== '';
902  }
903 
911  public function getInterwiki() {
912  return $this->mInterwiki;
913  }
914 
920  public function wasLocalInterwiki() {
921  return $this->mLocalInterwiki;
922  }
923 
930  public function isTrans() {
931  if ( !$this->isExternal() ) {
932  return false;
933  }
934 
935  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
936  }
937 
943  public function getTransWikiID() {
944  if ( !$this->isExternal() ) {
945  return false;
946  }
947 
948  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
949  }
950 
960  public function getTitleValue() {
961  if ( $this->mTitleValue === null ) {
962  try {
963  $this->mTitleValue = new TitleValue(
964  $this->mNamespace,
965  $this->mDbkeyform,
966  $this->mFragment,
967  $this->mInterwiki
968  );
969  } catch ( InvalidArgumentException $ex ) {
970  wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
971  $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
972  }
973  }
974 
975  return $this->mTitleValue;
976  }
977 
983  public function getText() {
984  return $this->mTextform;
985  }
986 
992  public function getPartialURL() {
993  return $this->mUrlform;
994  }
995 
1001  public function getDBkey() {
1002  return $this->mDbkeyform;
1003  }
1004 
1011  function getUserCaseDBKey() {
1012  if ( !is_null( $this->mUserCaseDBKey ) ) {
1013  return $this->mUserCaseDBKey;
1014  } else {
1015  // If created via makeTitle(), $this->mUserCaseDBKey is not set.
1016  return $this->mDbkeyform;
1017  }
1018  }
1019 
1025  public function getNamespace() {
1026  return $this->mNamespace;
1027  }
1028 
1037  public function getContentModel( $flags = 0 ) {
1038  if ( !$this->mForcedContentModel
1039  && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE )
1040  && $this->getArticleID( $flags )
1041  ) {
1042  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1043  $linkCache->addLinkObj( $this ); # in case we already had an article ID
1044  $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
1045  }
1046 
1047  if ( !$this->mContentModel ) {
1048  $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
1049  }
1050 
1051  return $this->mContentModel;
1052  }
1053 
1060  public function hasContentModel( $id ) {
1061  return $this->getContentModel() == $id;
1062  }
1063 
1075  public function setContentModel( $model ) {
1076  $this->mContentModel = $model;
1077  $this->mForcedContentModel = true;
1078  }
1079 
1085  public function getNsText() {
1086  if ( $this->isExternal() ) {
1087  // This probably shouldn't even happen, except for interwiki transclusion.
1088  // If possible, use the canonical name for the foreign namespace.
1089  $nsText = MWNamespace::getCanonicalName( $this->mNamespace );
1090  if ( $nsText !== false ) {
1091  return $nsText;
1092  }
1093  }
1094 
1095  try {
1096  $formatter = self::getTitleFormatter();
1097  return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
1098  } catch ( InvalidArgumentException $ex ) {
1099  wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
1100  return false;
1101  }
1102  }
1103 
1109  public function getSubjectNsText() {
1110  return MediaWikiServices::getInstance()->getContentLanguage()->
1111  getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1112  }
1113 
1119  public function getTalkNsText() {
1120  return MediaWikiServices::getInstance()->getContentLanguage()->
1121  getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1122  }
1123 
1132  public function canHaveTalkPage() {
1133  return MWNamespace::hasTalkNamespace( $this->mNamespace );
1134  }
1135 
1141  public function canExist() {
1142  return $this->mNamespace >= NS_MAIN;
1143  }
1144 
1150  public function isWatchable() {
1151  return !$this->isExternal() && MWNamespace::isWatchable( $this->mNamespace );
1152  }
1153 
1159  public function isSpecialPage() {
1160  return $this->mNamespace == NS_SPECIAL;
1161  }
1162 
1169  public function isSpecial( $name ) {
1170  if ( $this->isSpecialPage() ) {
1171  list( $thisName, /* $subpage */ ) =
1172  MediaWikiServices::getInstance()->getSpecialPageFactory()->
1173  resolveAlias( $this->mDbkeyform );
1174  if ( $name == $thisName ) {
1175  return true;
1176  }
1177  }
1178  return false;
1179  }
1180 
1187  public function fixSpecialName() {
1188  if ( $this->isSpecialPage() ) {
1189  $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
1190  list( $canonicalName, $par ) = $spFactory->resolveAlias( $this->mDbkeyform );
1191  if ( $canonicalName ) {
1192  $localName = $spFactory->getLocalNameFor( $canonicalName, $par );
1193  if ( $localName != $this->mDbkeyform ) {
1194  return self::makeTitle( NS_SPECIAL, $localName );
1195  }
1196  }
1197  }
1198  return $this;
1199  }
1200 
1211  public function inNamespace( $ns ) {
1212  return MWNamespace::equals( $this->mNamespace, $ns );
1213  }
1214 
1222  public function inNamespaces( /* ... */ ) {
1223  $namespaces = func_get_args();
1224  if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1225  $namespaces = $namespaces[0];
1226  }
1227 
1228  foreach ( $namespaces as $ns ) {
1229  if ( $this->inNamespace( $ns ) ) {
1230  return true;
1231  }
1232  }
1233 
1234  return false;
1235  }
1236 
1250  public function hasSubjectNamespace( $ns ) {
1251  return MWNamespace::subjectEquals( $this->mNamespace, $ns );
1252  }
1253 
1261  public function isContentPage() {
1262  return MWNamespace::isContent( $this->mNamespace );
1263  }
1264 
1271  public function isMovable() {
1272  if ( !MWNamespace::isMovable( $this->mNamespace ) || $this->isExternal() ) {
1273  // Interwiki title or immovable namespace. Hooks don't get to override here
1274  return false;
1275  }
1276 
1277  $result = true;
1278  Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1279  return $result;
1280  }
1281 
1292  public function isMainPage() {
1293  return $this->equals( self::newMainPage() );
1294  }
1295 
1301  public function isSubpage() {
1302  return MWNamespace::hasSubpages( $this->mNamespace )
1303  ? strpos( $this->getText(), '/' ) !== false
1304  : false;
1305  }
1306 
1312  public function isConversionTable() {
1313  // @todo ConversionTable should become a separate content model.
1314 
1315  return $this->mNamespace == NS_MEDIAWIKI &&
1316  strpos( $this->getText(), 'Conversiontable/' ) === 0;
1317  }
1318 
1324  public function isWikitextPage() {
1325  return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1326  }
1327 
1342  public function isSiteConfigPage() {
1343  return (
1344  $this->isSiteCssConfigPage()
1345  || $this->isSiteJsonConfigPage()
1346  || $this->isSiteJsConfigPage()
1347  );
1348  }
1349 
1356  public function isUserConfigPage() {
1357  return (
1358  $this->isUserCssConfigPage()
1359  || $this->isUserJsonConfigPage()
1360  || $this->isUserJsConfigPage()
1361  );
1362  }
1363 
1370  public function getSkinFromConfigSubpage() {
1371  $subpage = explode( '/', $this->mTextform );
1372  $subpage = $subpage[count( $subpage ) - 1];
1373  $lastdot = strrpos( $subpage, '.' );
1374  if ( $lastdot === false ) {
1375  return $subpage; # Never happens: only called for names ending in '.css'/'.json'/'.js'
1376  }
1377  return substr( $subpage, 0, $lastdot );
1378  }
1379 
1386  public function isUserCssConfigPage() {
1387  return (
1388  NS_USER == $this->mNamespace
1389  && $this->isSubpage()
1390  && $this->hasContentModel( CONTENT_MODEL_CSS )
1391  );
1392  }
1393 
1400  public function isUserJsonConfigPage() {
1401  return (
1402  NS_USER == $this->mNamespace
1403  && $this->isSubpage()
1404  && $this->hasContentModel( CONTENT_MODEL_JSON )
1405  );
1406  }
1407 
1414  public function isUserJsConfigPage() {
1415  return (
1416  NS_USER == $this->mNamespace
1417  && $this->isSubpage()
1419  );
1420  }
1421 
1428  public function isSiteCssConfigPage() {
1429  return (
1430  NS_MEDIAWIKI == $this->mNamespace
1431  && (
1433  // paranoia - a MediaWiki: namespace page with mismatching extension and content
1434  // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1435  || substr( $this->mDbkeyform, -4 ) === '.css'
1436  )
1437  );
1438  }
1439 
1446  public function isSiteJsonConfigPage() {
1447  return (
1448  NS_MEDIAWIKI == $this->mNamespace
1449  && (
1451  // paranoia - a MediaWiki: namespace page with mismatching extension and content
1452  // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1453  || substr( $this->mDbkeyform, -5 ) === '.json'
1454  )
1455  );
1456  }
1457 
1464  public function isSiteJsConfigPage() {
1465  return (
1466  NS_MEDIAWIKI == $this->mNamespace
1467  && (
1469  // paranoia - a MediaWiki: namespace page with mismatching extension and content
1470  // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1471  || substr( $this->mDbkeyform, -3 ) === '.js'
1472  )
1473  );
1474  }
1475 
1482  public function isRawHtmlMessage() {
1483  global $wgRawHtmlMessages;
1484 
1485  if ( !$this->inNamespace( NS_MEDIAWIKI ) ) {
1486  return false;
1487  }
1488  $message = lcfirst( $this->getRootTitle()->getDBkey() );
1489  return in_array( $message, $wgRawHtmlMessages, true );
1490  }
1491 
1497  public function isTalkPage() {
1498  return MWNamespace::isTalk( $this->mNamespace );
1499  }
1500 
1506  public function getTalkPage() {
1507  return self::makeTitle( MWNamespace::getTalk( $this->mNamespace ), $this->mDbkeyform );
1508  }
1509 
1519  public function getTalkPageIfDefined() {
1520  if ( !$this->canHaveTalkPage() ) {
1521  return null;
1522  }
1523 
1524  return $this->getTalkPage();
1525  }
1526 
1533  public function getSubjectPage() {
1534  // Is this the same title?
1535  $subjectNS = MWNamespace::getSubject( $this->mNamespace );
1536  if ( $this->mNamespace == $subjectNS ) {
1537  return $this;
1538  }
1539  return self::makeTitle( $subjectNS, $this->mDbkeyform );
1540  }
1541 
1550  public function getOtherPage() {
1551  if ( $this->isSpecialPage() ) {
1552  throw new MWException( 'Special pages cannot have other pages' );
1553  }
1554  if ( $this->isTalkPage() ) {
1555  return $this->getSubjectPage();
1556  } else {
1557  if ( !$this->canHaveTalkPage() ) {
1558  throw new MWException( "{$this->getPrefixedText()} does not have an other page" );
1559  }
1560  return $this->getTalkPage();
1561  }
1562  }
1563 
1569  public function getDefaultNamespace() {
1570  return $this->mDefaultNamespace;
1571  }
1572 
1580  public function getFragment() {
1581  return $this->mFragment;
1582  }
1583 
1590  public function hasFragment() {
1591  return $this->mFragment !== '';
1592  }
1593 
1599  public function getFragmentForURL() {
1600  if ( !$this->hasFragment() ) {
1601  return '';
1602  } elseif ( $this->isExternal() ) {
1603  // Note: If the interwiki is unknown, it's treated as a namespace on the local wiki,
1604  // so we treat it like a local interwiki.
1605  $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1606  if ( $interwiki && !$interwiki->isLocal() ) {
1607  return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->mFragment );
1608  }
1609  }
1610 
1611  return '#' . Sanitizer::escapeIdForLink( $this->mFragment );
1612  }
1613 
1626  public function setFragment( $fragment ) {
1627  $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1628  }
1629 
1637  public function createFragmentTarget( $fragment ) {
1638  return self::makeTitle(
1639  $this->mNamespace,
1640  $this->getText(),
1641  $fragment,
1642  $this->mInterwiki
1643  );
1644  }
1645 
1653  private function prefix( $name ) {
1654  $p = '';
1655  if ( $this->isExternal() ) {
1656  $p = $this->mInterwiki . ':';
1657  }
1658 
1659  if ( $this->mNamespace != 0 ) {
1660  $nsText = $this->getNsText();
1661 
1662  if ( $nsText === false ) {
1663  // See T165149. Awkward, but better than erroneously linking to the main namespace.
1664  $nsText = MediaWikiServices::getInstance()->getContentLanguage()->
1665  getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}";
1666  }
1667 
1668  $p .= $nsText . ':';
1669  }
1670  return $p . $name;
1671  }
1672 
1679  public function getPrefixedDBkey() {
1680  $s = $this->prefix( $this->mDbkeyform );
1681  $s = strtr( $s, ' ', '_' );
1682  return $s;
1683  }
1684 
1691  public function getPrefixedText() {
1692  if ( $this->prefixedText === null ) {
1693  $s = $this->prefix( $this->mTextform );
1694  $s = strtr( $s, '_', ' ' );
1695  $this->prefixedText = $s;
1696  }
1697  return $this->prefixedText;
1698  }
1699 
1705  public function __toString() {
1706  return $this->getPrefixedText();
1707  }
1708 
1715  public function getFullText() {
1716  $text = $this->getPrefixedText();
1717  if ( $this->hasFragment() ) {
1718  $text .= '#' . $this->mFragment;
1719  }
1720  return $text;
1721  }
1722 
1735  public function getRootText() {
1736  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1737  return $this->getText();
1738  }
1739 
1740  return strtok( $this->getText(), '/' );
1741  }
1742 
1755  public function getRootTitle() {
1756  return self::makeTitle( $this->mNamespace, $this->getRootText() );
1757  }
1758 
1770  public function getBaseText() {
1771  $text = $this->getText();
1772  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1773  return $text;
1774  }
1775 
1776  $lastSlashPos = strrpos( $text, '/' );
1777  // Don't discard the real title if there's no subpage involved
1778  if ( $lastSlashPos === false ) {
1779  return $text;
1780  }
1781 
1782  return substr( $text, 0, $lastSlashPos );
1783  }
1784 
1797  public function getBaseTitle() {
1798  return self::makeTitle( $this->mNamespace, $this->getBaseText() );
1799  }
1800 
1812  public function getSubpageText() {
1813  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1814  return $this->mTextform;
1815  }
1816  $parts = explode( '/', $this->mTextform );
1817  return $parts[count( $parts ) - 1];
1818  }
1819 
1833  public function getSubpage( $text ) {
1834  return self::makeTitleSafe( $this->mNamespace, $this->getText() . '/' . $text );
1835  }
1836 
1842  public function getSubpageUrlForm() {
1843  $text = $this->getSubpageText();
1844  $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1845  return $text;
1846  }
1847 
1853  public function getPrefixedURL() {
1854  $s = $this->prefix( $this->mDbkeyform );
1855  $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1856  return $s;
1857  }
1858 
1872  private static function fixUrlQueryArgs( $query, $query2 = false ) {
1873  if ( $query2 !== false ) {
1874  wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1875  "method called with a second parameter is deprecated. Add your " .
1876  "parameter to an array passed as the first parameter.", "1.19" );
1877  }
1878  if ( is_array( $query ) ) {
1879  $query = wfArrayToCgi( $query );
1880  }
1881  if ( $query2 ) {
1882  if ( is_string( $query2 ) ) {
1883  // $query2 is a string, we will consider this to be
1884  // a deprecated $variant argument and add it to the query
1885  $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1886  } else {
1887  $query2 = wfArrayToCgi( $query2 );
1888  }
1889  // If we have $query content add a & to it first
1890  if ( $query ) {
1891  $query .= '&';
1892  }
1893  // Now append the queries together
1894  $query .= $query2;
1895  }
1896  return $query;
1897  }
1898 
1910  public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1911  $query = self::fixUrlQueryArgs( $query, $query2 );
1912 
1913  # Hand off all the decisions on urls to getLocalURL
1914  $url = $this->getLocalURL( $query );
1915 
1916  # Expand the url to make it a full url. Note that getLocalURL has the
1917  # potential to output full urls for a variety of reasons, so we use
1918  # wfExpandUrl instead of simply prepending $wgServer
1919  $url = wfExpandUrl( $url, $proto );
1920 
1921  # Finally, add the fragment.
1922  $url .= $this->getFragmentForURL();
1923  // Avoid PHP 7.1 warning from passing $this by reference
1924  $titleRef = $this;
1925  Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
1926  return $url;
1927  }
1928 
1945  public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1946  $target = $this;
1947  if ( $this->isExternal() ) {
1948  $target = SpecialPage::getTitleFor(
1949  'GoToInterwiki',
1950  $this->getPrefixedDBkey()
1951  );
1952  }
1953  return $target->getFullURL( $query, false, $proto );
1954  }
1955 
1979  public function getLocalURL( $query = '', $query2 = false ) {
1981 
1982  $query = self::fixUrlQueryArgs( $query, $query2 );
1983 
1984  $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1985  if ( $interwiki ) {
1986  $namespace = $this->getNsText();
1987  if ( $namespace != '' ) {
1988  # Can this actually happen? Interwikis shouldn't be parsed.
1989  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1990  $namespace .= ':';
1991  }
1992  $url = $interwiki->getURL( $namespace . $this->mDbkeyform );
1993  $url = wfAppendQuery( $url, $query );
1994  } else {
1995  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1996  if ( $query == '' ) {
1997  $url = str_replace( '$1', $dbkey, $wgArticlePath );
1998  // Avoid PHP 7.1 warning from passing $this by reference
1999  $titleRef = $this;
2000  Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
2001  } else {
2003  $url = false;
2004  $matches = [];
2005 
2006  if ( !empty( $wgActionPaths )
2007  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
2008  ) {
2009  $action = urldecode( $matches[2] );
2010  if ( isset( $wgActionPaths[$action] ) ) {
2011  $query = $matches[1];
2012  if ( isset( $matches[4] ) ) {
2013  $query .= $matches[4];
2014  }
2015  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
2016  if ( $query != '' ) {
2017  $url = wfAppendQuery( $url, $query );
2018  }
2019  }
2020  }
2021 
2022  if ( $url === false
2023  && $wgVariantArticlePath
2024  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
2025  && $this->getPageLanguage()->equals(
2026  MediaWikiServices::getInstance()->getContentLanguage() )
2027  && $this->getPageLanguage()->hasVariants()
2028  ) {
2029  $variant = urldecode( $matches[1] );
2030  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
2031  // Only do the variant replacement if the given variant is a valid
2032  // variant for the page's language.
2033  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
2034  $url = str_replace( '$1', $dbkey, $url );
2035  }
2036  }
2037 
2038  if ( $url === false ) {
2039  if ( $query == '-' ) {
2040  $query = '';
2041  }
2042  $url = "{$wgScript}?title={$dbkey}&{$query}";
2043  }
2044  }
2045  // Avoid PHP 7.1 warning from passing $this by reference
2046  $titleRef = $this;
2047  Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
2048 
2049  // @todo FIXME: This causes breakage in various places when we
2050  // actually expected a local URL and end up with dupe prefixes.
2051  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
2052  $url = $wgServer . $url;
2053  }
2054  }
2055  // Avoid PHP 7.1 warning from passing $this by reference
2056  $titleRef = $this;
2057  Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
2058  return $url;
2059  }
2060 
2078  public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
2079  if ( $this->isExternal() || $proto !== false ) {
2080  $ret = $this->getFullURL( $query, $query2, $proto );
2081  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
2082  $ret = $this->getFragmentForURL();
2083  } else {
2084  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
2085  }
2086  return $ret;
2087  }
2088 
2103  public function getInternalURL( $query = '', $query2 = false ) {
2104  global $wgInternalServer, $wgServer;
2105  $query = self::fixUrlQueryArgs( $query, $query2 );
2106  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
2107  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
2108  // Avoid PHP 7.1 warning from passing $this by reference
2109  $titleRef = $this;
2110  Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
2111  return $url;
2112  }
2113 
2127  public function getCanonicalURL( $query = '', $query2 = false ) {
2128  $query = self::fixUrlQueryArgs( $query, $query2 );
2129  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
2130  // Avoid PHP 7.1 warning from passing $this by reference
2131  $titleRef = $this;
2132  Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
2133  return $url;
2134  }
2135 
2141  public function getEditURL() {
2142  if ( $this->isExternal() ) {
2143  return '';
2144  }
2145  $s = $this->getLocalURL( 'action=edit' );
2146 
2147  return $s;
2148  }
2149 
2170  public function quickUserCan( $action, $user = null ) {
2171  return $this->userCan( $action, $user, false );
2172  }
2173 
2189  public function userCan( $action, $user = null, $rigor = PermissionManager::RIGOR_SECURE ) {
2190  if ( !$user instanceof User ) {
2191  global $wgUser;
2192  $user = $wgUser;
2193  }
2194 
2195  // TODO: this is for b/c, eventually will be removed
2196  if ( $rigor === true ) {
2197  $rigor = PermissionManager::RIGOR_SECURE; // b/c
2198  } elseif ( $rigor === false ) {
2199  $rigor = PermissionManager::RIGOR_QUICK; // b/c
2200  }
2201 
2202  return MediaWikiServices::getInstance()->getPermissionManager()
2203  ->userCan( $action, $user, $this, $rigor );
2204  }
2205 
2227  public function getUserPermissionsErrors(
2228  $action, $user, $rigor = PermissionManager::RIGOR_SECURE, $ignoreErrors = []
2229  ) {
2230  // TODO: this is for b/c, eventually will be removed
2231  if ( $rigor === true ) {
2232  $rigor = PermissionManager::RIGOR_SECURE; // b/c
2233  } elseif ( $rigor === false ) {
2234  $rigor = PermissionManager::RIGOR_QUICK; // b/c
2235  }
2236 
2237  return MediaWikiServices::getInstance()->getPermissionManager()
2238  ->getPermissionErrors( $action, $user, $this, $rigor, $ignoreErrors );
2239  }
2240 
2249  private function resultToError( $errors, $result ) {
2250  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2251  // A single array representing an error
2252  $errors[] = $result;
2253  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2254  // A nested array representing multiple errors
2255  $errors = array_merge( $errors, $result );
2256  } elseif ( $result !== '' && is_string( $result ) ) {
2257  // A string representing a message-id
2258  $errors[] = [ $result ];
2259  } elseif ( $result instanceof MessageSpecifier ) {
2260  // A message specifier representing an error
2261  $errors[] = [ $result ];
2262  } elseif ( $result === false ) {
2263  // a generic "We don't want them to do that"
2264  $errors[] = [ 'badaccess-group0' ];
2265  }
2266  return $errors;
2267  }
2268 
2276  public static function getFilteredRestrictionTypes( $exists = true ) {
2277  global $wgRestrictionTypes;
2278  $types = $wgRestrictionTypes;
2279  if ( $exists ) {
2280  # Remove the create restriction for existing titles
2281  $types = array_diff( $types, [ 'create' ] );
2282  } else {
2283  # Only the create and upload restrictions apply to non-existing titles
2284  $types = array_intersect( $types, [ 'create', 'upload' ] );
2285  }
2286  return $types;
2287  }
2288 
2294  public function getRestrictionTypes() {
2295  if ( $this->isSpecialPage() ) {
2296  return [];
2297  }
2298 
2299  $types = self::getFilteredRestrictionTypes( $this->exists() );
2300 
2301  if ( $this->mNamespace != NS_FILE ) {
2302  # Remove the upload restriction for non-file titles
2303  $types = array_diff( $types, [ 'upload' ] );
2304  }
2305 
2306  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2307 
2308  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2309  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2310 
2311  return $types;
2312  }
2313 
2321  public function getTitleProtection() {
2322  $protection = $this->getTitleProtectionInternal();
2323  if ( $protection ) {
2324  if ( $protection['permission'] == 'sysop' ) {
2325  $protection['permission'] = 'editprotected'; // B/C
2326  }
2327  if ( $protection['permission'] == 'autoconfirmed' ) {
2328  $protection['permission'] = 'editsemiprotected'; // B/C
2329  }
2330  }
2331  return $protection;
2332  }
2333 
2344  protected function getTitleProtectionInternal() {
2345  // Can't protect pages in special namespaces
2346  if ( $this->mNamespace < 0 ) {
2347  return false;
2348  }
2349 
2350  // Can't protect pages that exist.
2351  if ( $this->exists() ) {
2352  return false;
2353  }
2354 
2355  if ( $this->mTitleProtection === null ) {
2356  $dbr = wfGetDB( DB_REPLICA );
2357  $commentStore = CommentStore::getStore();
2358  $commentQuery = $commentStore->getJoin( 'pt_reason' );
2359  $res = $dbr->select(
2360  [ 'protected_titles' ] + $commentQuery['tables'],
2361  [
2362  'user' => 'pt_user',
2363  'expiry' => 'pt_expiry',
2364  'permission' => 'pt_create_perm'
2365  ] + $commentQuery['fields'],
2366  [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2367  __METHOD__,
2368  [],
2369  $commentQuery['joins']
2370  );
2371 
2372  // fetchRow returns false if there are no rows.
2373  $row = $dbr->fetchRow( $res );
2374  if ( $row ) {
2375  $this->mTitleProtection = [
2376  'user' => $row['user'],
2377  'expiry' => $dbr->decodeExpiry( $row['expiry'] ),
2378  'permission' => $row['permission'],
2379  'reason' => $commentStore->getComment( 'pt_reason', $row )->text,
2380  ];
2381  } else {
2382  $this->mTitleProtection = false;
2383  }
2384  }
2385  return $this->mTitleProtection;
2386  }
2387 
2391  public function deleteTitleProtection() {
2392  $dbw = wfGetDB( DB_MASTER );
2393 
2394  $dbw->delete(
2395  'protected_titles',
2396  [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2397  __METHOD__
2398  );
2399  $this->mTitleProtection = false;
2400  }
2401 
2409  public function isSemiProtected( $action = 'edit' ) {
2411 
2412  $restrictions = $this->getRestrictions( $action );
2414  if ( !$restrictions || !$semi ) {
2415  // Not protected, or all protection is full protection
2416  return false;
2417  }
2418 
2419  // Remap autoconfirmed to editsemiprotected for BC
2420  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2421  $semi[$key] = 'editsemiprotected';
2422  }
2423  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2424  $restrictions[$key] = 'editsemiprotected';
2425  }
2426 
2427  return !array_diff( $restrictions, $semi );
2428  }
2429 
2437  public function isProtected( $action = '' ) {
2438  global $wgRestrictionLevels;
2439 
2440  $restrictionTypes = $this->getRestrictionTypes();
2441 
2442  # Special pages have inherent protection
2443  if ( $this->isSpecialPage() ) {
2444  return true;
2445  }
2446 
2447  # Check regular protection levels
2448  foreach ( $restrictionTypes as $type ) {
2449  if ( $action == $type || $action == '' ) {
2450  $r = $this->getRestrictions( $type );
2451  foreach ( $wgRestrictionLevels as $level ) {
2452  if ( in_array( $level, $r ) && $level != '' ) {
2453  return true;
2454  }
2455  }
2456  }
2457  }
2458 
2459  return false;
2460  }
2461 
2469  public function isNamespaceProtected( User $user ) {
2470  global $wgNamespaceProtection;
2471 
2472  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2473  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2474  if ( $right != '' && !$user->isAllowed( $right ) ) {
2475  return true;
2476  }
2477  }
2478  }
2479  return false;
2480  }
2481 
2487  public function isCascadeProtected() {
2488  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2489  return ( $sources > 0 );
2490  }
2491 
2501  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2502  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2503  }
2504 
2518  public function getCascadeProtectionSources( $getPages = true ) {
2519  $pagerestrictions = [];
2520 
2521  if ( $this->mCascadeSources !== null && $getPages ) {
2523  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2524  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2525  }
2526 
2527  $dbr = wfGetDB( DB_REPLICA );
2528 
2529  if ( $this->mNamespace == NS_FILE ) {
2530  $tables = [ 'imagelinks', 'page_restrictions' ];
2531  $where_clauses = [
2532  'il_to' => $this->mDbkeyform,
2533  'il_from=pr_page',
2534  'pr_cascade' => 1
2535  ];
2536  } else {
2537  $tables = [ 'templatelinks', 'page_restrictions' ];
2538  $where_clauses = [
2539  'tl_namespace' => $this->mNamespace,
2540  'tl_title' => $this->mDbkeyform,
2541  'tl_from=pr_page',
2542  'pr_cascade' => 1
2543  ];
2544  }
2545 
2546  if ( $getPages ) {
2547  $cols = [ 'pr_page', 'page_namespace', 'page_title',
2548  'pr_expiry', 'pr_type', 'pr_level' ];
2549  $where_clauses[] = 'page_id=pr_page';
2550  $tables[] = 'page';
2551  } else {
2552  $cols = [ 'pr_expiry' ];
2553  }
2554 
2555  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2556 
2557  $sources = $getPages ? [] : false;
2558  $now = wfTimestampNow();
2559 
2560  foreach ( $res as $row ) {
2561  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2562  if ( $expiry > $now ) {
2563  if ( $getPages ) {
2564  $page_id = $row->pr_page;
2565  $page_ns = $row->page_namespace;
2566  $page_title = $row->page_title;
2567  $sources[$page_id] = self::makeTitle( $page_ns, $page_title );
2568  # Add groups needed for each restriction type if its not already there
2569  # Make sure this restriction type still exists
2570 
2571  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2572  $pagerestrictions[$row->pr_type] = [];
2573  }
2574 
2575  if (
2576  isset( $pagerestrictions[$row->pr_type] )
2577  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2578  ) {
2579  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2580  }
2581  } else {
2582  $sources = true;
2583  }
2584  }
2585  }
2586 
2587  if ( $getPages ) {
2588  $this->mCascadeSources = $sources;
2589  $this->mCascadingRestrictions = $pagerestrictions;
2590  } else {
2591  $this->mHasCascadingRestrictions = $sources;
2592  }
2593 
2594  return [ $sources, $pagerestrictions ];
2595  }
2596 
2604  public function areRestrictionsLoaded() {
2606  }
2607 
2617  public function getRestrictions( $action ) {
2618  if ( !$this->mRestrictionsLoaded ) {
2619  $this->loadRestrictions();
2620  }
2621  return $this->mRestrictions[$action] ?? [];
2622  }
2623 
2631  public function getAllRestrictions() {
2632  if ( !$this->mRestrictionsLoaded ) {
2633  $this->loadRestrictions();
2634  }
2635  return $this->mRestrictions;
2636  }
2637 
2645  public function getRestrictionExpiry( $action ) {
2646  if ( !$this->mRestrictionsLoaded ) {
2647  $this->loadRestrictions();
2648  }
2649  return $this->mRestrictionsExpiry[$action] ?? false;
2650  }
2651 
2658  if ( !$this->mRestrictionsLoaded ) {
2659  $this->loadRestrictions();
2660  }
2661 
2663  }
2664 
2676  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2677  // This function will only read rows from a table that we migrated away
2678  // from before adding READ_LATEST support to loadRestrictions, so we
2679  // don't need to support reading from DB_MASTER here.
2680  $dbr = wfGetDB( DB_REPLICA );
2681 
2682  $restrictionTypes = $this->getRestrictionTypes();
2683 
2684  foreach ( $restrictionTypes as $type ) {
2685  $this->mRestrictions[$type] = [];
2686  $this->mRestrictionsExpiry[$type] = 'infinity';
2687  }
2688 
2689  $this->mCascadeRestriction = false;
2690 
2691  # Backwards-compatibility: also load the restrictions from the page record (old format).
2692  if ( $oldFashionedRestrictions !== null ) {
2693  $this->mOldRestrictions = $oldFashionedRestrictions;
2694  }
2695 
2696  if ( $this->mOldRestrictions === false ) {
2697  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
2698  $linkCache->addLinkObj( $this ); # in case we already had an article ID
2699  $this->mOldRestrictions = $linkCache->getGoodLinkFieldObj( $this, 'restrictions' );
2700  }
2701 
2702  if ( $this->mOldRestrictions != '' ) {
2703  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2704  $temp = explode( '=', trim( $restrict ) );
2705  if ( count( $temp ) == 1 ) {
2706  // old old format should be treated as edit/move restriction
2707  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2708  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2709  } else {
2710  $restriction = trim( $temp[1] );
2711  if ( $restriction != '' ) { // some old entries are empty
2712  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2713  }
2714  }
2715  }
2716  }
2717 
2718  if ( count( $rows ) ) {
2719  # Current system - load second to make them override.
2720  $now = wfTimestampNow();
2721 
2722  # Cycle through all the restrictions.
2723  foreach ( $rows as $row ) {
2724  // Don't take care of restrictions types that aren't allowed
2725  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2726  continue;
2727  }
2728 
2729  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2730 
2731  // Only apply the restrictions if they haven't expired!
2732  if ( !$expiry || $expiry > $now ) {
2733  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2734  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2735 
2736  $this->mCascadeRestriction |= $row->pr_cascade;
2737  }
2738  }
2739  }
2740 
2741  $this->mRestrictionsLoaded = true;
2742  }
2743 
2754  public function loadRestrictions( $oldFashionedRestrictions = null, $flags = 0 ) {
2755  $readLatest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
2756  if ( $this->mRestrictionsLoaded && !$readLatest ) {
2757  return;
2758  }
2759 
2760  // TODO: should probably pass $flags into getArticleID, but it seems hacky
2761  // to mix READ_LATEST and GAID_FOR_UPDATE, even if they have the same value.
2762  // Maybe deprecate GAID_FOR_UPDATE now that we implement IDBAccessObject?
2763  $id = $this->getArticleID();
2764  if ( $id ) {
2765  $fname = __METHOD__;
2766  $loadRestrictionsFromDb = function ( IDatabase $dbr ) use ( $fname, $id ) {
2767  return iterator_to_array(
2768  $dbr->select(
2769  'page_restrictions',
2770  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
2771  [ 'pr_page' => $id ],
2772  $fname
2773  )
2774  );
2775  };
2776 
2777  if ( $readLatest ) {
2778  $dbr = wfGetDB( DB_MASTER );
2779  $rows = $loadRestrictionsFromDb( $dbr );
2780  } else {
2781  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2782  $rows = $cache->getWithSetCallback(
2783  // Page protections always leave a new null revision
2784  $cache->makeKey( 'page-restrictions', 'v1', $id, $this->getLatestRevID() ),
2785  $cache::TTL_DAY,
2786  function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
2787  $dbr = wfGetDB( DB_REPLICA );
2788 
2789  $setOpts += Database::getCacheSetOptions( $dbr );
2790  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2791  if ( $lb->hasOrMadeRecentMasterChanges() ) {
2792  // @TODO: cleanup Title cache and caller assumption mess in general
2794  }
2795 
2796  return $loadRestrictionsFromDb( $dbr );
2797  }
2798  );
2799  }
2800 
2801  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2802  } else {
2803  $title_protection = $this->getTitleProtectionInternal();
2804 
2805  if ( $title_protection ) {
2806  $now = wfTimestampNow();
2807  $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
2808 
2809  if ( !$expiry || $expiry > $now ) {
2810  // Apply the restrictions
2811  $this->mRestrictionsExpiry['create'] = $expiry;
2812  $this->mRestrictions['create'] =
2813  explode( ',', trim( $title_protection['permission'] ) );
2814  } else { // Get rid of the old restrictions
2815  $this->mTitleProtection = false;
2816  }
2817  } else {
2818  $this->mRestrictionsExpiry['create'] = 'infinity';
2819  }
2820  $this->mRestrictionsLoaded = true;
2821  }
2822  }
2823 
2828  public function flushRestrictions() {
2829  $this->mRestrictionsLoaded = false;
2830  $this->mTitleProtection = null;
2831  }
2832 
2838  static function purgeExpiredRestrictions() {
2839  if ( wfReadOnly() ) {
2840  return;
2841  }
2842 
2844  wfGetDB( DB_MASTER ),
2845  __METHOD__,
2846  function ( IDatabase $dbw, $fname ) {
2847  $config = MediaWikiServices::getInstance()->getMainConfig();
2848  $ids = $dbw->selectFieldValues(
2849  'page_restrictions',
2850  'pr_id',
2851  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
2852  $fname,
2853  [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
2854  );
2855  if ( $ids ) {
2856  $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
2857  }
2858  }
2859  ) );
2860 
2862  wfGetDB( DB_MASTER ),
2863  __METHOD__,
2864  function ( IDatabase $dbw, $fname ) {
2865  $dbw->delete(
2866  'protected_titles',
2867  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
2868  $fname
2869  );
2870  }
2871  ) );
2872  }
2873 
2879  public function hasSubpages() {
2880  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
2881  # Duh
2882  return false;
2883  }
2884 
2885  # We dynamically add a member variable for the purpose of this method
2886  # alone to cache the result. There's no point in having it hanging
2887  # around uninitialized in every Title object; therefore we only add it
2888  # if needed and don't declare it statically.
2889  if ( $this->mHasSubpages === null ) {
2890  $this->mHasSubpages = false;
2891  $subpages = $this->getSubpages( 1 );
2892  if ( $subpages instanceof TitleArray ) {
2893  $this->mHasSubpages = (bool)$subpages->count();
2894  }
2895  }
2896 
2897  return $this->mHasSubpages;
2898  }
2899 
2907  public function getSubpages( $limit = -1 ) {
2908  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
2909  return [];
2910  }
2911 
2912  $dbr = wfGetDB( DB_REPLICA );
2913  $conds['page_namespace'] = $this->mNamespace;
2914  $conds[] = 'page_title ' . $dbr->buildLike( $this->mDbkeyform . '/', $dbr->anyString() );
2915  $options = [];
2916  if ( $limit > -1 ) {
2917  $options['LIMIT'] = $limit;
2918  }
2920  $dbr->select( 'page',
2921  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
2922  $conds,
2923  __METHOD__,
2924  $options
2925  )
2926  );
2927  }
2928 
2934  public function isDeleted() {
2935  if ( $this->mNamespace < 0 ) {
2936  $n = 0;
2937  } else {
2938  $dbr = wfGetDB( DB_REPLICA );
2939 
2940  $n = $dbr->selectField( 'archive', 'COUNT(*)',
2941  [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
2942  __METHOD__
2943  );
2944  if ( $this->mNamespace == NS_FILE ) {
2945  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
2946  [ 'fa_name' => $this->mDbkeyform ],
2947  __METHOD__
2948  );
2949  }
2950  }
2951  return (int)$n;
2952  }
2953 
2959  public function isDeletedQuick() {
2960  if ( $this->mNamespace < 0 ) {
2961  return false;
2962  }
2963  $dbr = wfGetDB( DB_REPLICA );
2964  $deleted = (bool)$dbr->selectField( 'archive', '1',
2965  [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
2966  __METHOD__
2967  );
2968  if ( !$deleted && $this->mNamespace == NS_FILE ) {
2969  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
2970  [ 'fa_name' => $this->mDbkeyform ],
2971  __METHOD__
2972  );
2973  }
2974  return $deleted;
2975  }
2976 
2985  public function getArticleID( $flags = 0 ) {
2986  if ( $this->mNamespace < 0 ) {
2987  $this->mArticleID = 0;
2988  return $this->mArticleID;
2989  }
2990  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
2991  if ( $flags & self::GAID_FOR_UPDATE ) {
2992  $oldUpdate = $linkCache->forUpdate( true );
2993  $linkCache->clearLink( $this );
2994  $this->mArticleID = $linkCache->addLinkObj( $this );
2995  $linkCache->forUpdate( $oldUpdate );
2996  } elseif ( $this->mArticleID == -1 ) {
2997  $this->mArticleID = $linkCache->addLinkObj( $this );
2998  }
2999  return $this->mArticleID;
3000  }
3001 
3009  public function isRedirect( $flags = 0 ) {
3010  if ( !is_null( $this->mRedirect ) ) {
3011  return $this->mRedirect;
3012  }
3013  if ( !$this->getArticleID( $flags ) ) {
3014  $this->mRedirect = false;
3015  return $this->mRedirect;
3016  }
3017 
3018  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3019  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3020  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3021  if ( $cached === null ) {
3022  # Trust LinkCache's state over our own
3023  # LinkCache is telling us that the page doesn't exist, despite there being cached
3024  # data relating to an existing page in $this->mArticleID. Updaters should clear
3025  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3026  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3027  # LinkCache to refresh its data from the master.
3028  $this->mRedirect = false;
3029  return $this->mRedirect;
3030  }
3031 
3032  $this->mRedirect = (bool)$cached;
3033 
3034  return $this->mRedirect;
3035  }
3036 
3044  public function getLength( $flags = 0 ) {
3045  if ( $this->mLength != -1 ) {
3046  return $this->mLength;
3047  }
3048  if ( !$this->getArticleID( $flags ) ) {
3049  $this->mLength = 0;
3050  return $this->mLength;
3051  }
3052  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3053  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3054  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3055  if ( $cached === null ) {
3056  # Trust LinkCache's state over our own, as for isRedirect()
3057  $this->mLength = 0;
3058  return $this->mLength;
3059  }
3060 
3061  $this->mLength = intval( $cached );
3062 
3063  return $this->mLength;
3064  }
3065 
3072  public function getLatestRevID( $flags = 0 ) {
3073  if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3074  return intval( $this->mLatestID );
3075  }
3076  if ( !$this->getArticleID( $flags ) ) {
3077  $this->mLatestID = 0;
3078  return $this->mLatestID;
3079  }
3080  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3081  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3082  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3083  if ( $cached === null ) {
3084  # Trust LinkCache's state over our own, as for isRedirect()
3085  $this->mLatestID = 0;
3086  return $this->mLatestID;
3087  }
3088 
3089  $this->mLatestID = intval( $cached );
3090 
3091  return $this->mLatestID;
3092  }
3093 
3104  public function resetArticleID( $newid ) {
3105  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3106  $linkCache->clearLink( $this );
3107 
3108  if ( $newid === false ) {
3109  $this->mArticleID = -1;
3110  } else {
3111  $this->mArticleID = intval( $newid );
3112  }
3113  $this->mRestrictionsLoaded = false;
3114  $this->mRestrictions = [];
3115  $this->mOldRestrictions = false;
3116  $this->mRedirect = null;
3117  $this->mLength = -1;
3118  $this->mLatestID = false;
3119  $this->mContentModel = false;
3120  $this->mEstimateRevisions = null;
3121  $this->mPageLanguage = false;
3122  $this->mDbPageLanguage = false;
3123  $this->mIsBigDeletion = null;
3124  }
3125 
3126  public static function clearCaches() {
3127  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3128  $linkCache->clear();
3129 
3130  $titleCache = self::getTitleCache();
3131  $titleCache->clear();
3132  }
3133 
3141  public static function capitalize( $text, $ns = NS_MAIN ) {
3142  if ( MWNamespace::isCapitalized( $ns ) ) {
3143  return MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $text );
3144  } else {
3145  return $text;
3146  }
3147  }
3148 
3161  private function secureAndSplit() {
3162  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3163  // the parsing code with Title, while avoiding massive refactoring.
3164  // @todo: get rid of secureAndSplit, refactor parsing code.
3165  // @note: getTitleParser() returns a TitleParser implementation which does not have a
3166  // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3168  $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3169  // MalformedTitleException can be thrown here
3170  $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace );
3171 
3172  # Fill fields
3173  $this->setFragment( '#' . $parts['fragment'] );
3174  $this->mInterwiki = $parts['interwiki'];
3175  $this->mLocalInterwiki = $parts['local_interwiki'];
3176  $this->mNamespace = $parts['namespace'];
3177  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3178 
3179  $this->mDbkeyform = $parts['dbkey'];
3180  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3181  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3182 
3183  # We already know that some pages won't be in the database!
3184  if ( $this->isExternal() || $this->isSpecialPage() ) {
3185  $this->mArticleID = 0;
3186  }
3187 
3188  return true;
3189  }
3190 
3203  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3204  if ( count( $options ) > 0 ) {
3205  $db = wfGetDB( DB_MASTER );
3206  } else {
3207  $db = wfGetDB( DB_REPLICA );
3208  }
3209 
3210  $res = $db->select(
3211  [ 'page', $table ],
3212  self::getSelectFields(),
3213  [
3214  "{$prefix}_from=page_id",
3215  "{$prefix}_namespace" => $this->mNamespace,
3216  "{$prefix}_title" => $this->mDbkeyform ],
3217  __METHOD__,
3218  $options
3219  );
3220 
3221  $retVal = [];
3222  if ( $res->numRows() ) {
3223  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3224  foreach ( $res as $row ) {
3225  $titleObj = self::makeTitle( $row->page_namespace, $row->page_title );
3226  if ( $titleObj ) {
3227  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3228  $retVal[] = $titleObj;
3229  }
3230  }
3231  }
3232  return $retVal;
3233  }
3234 
3245  public function getTemplateLinksTo( $options = [] ) {
3246  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3247  }
3248 
3261  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3262  $id = $this->getArticleID();
3263 
3264  # If the page doesn't exist; there can't be any link from this page
3265  if ( !$id ) {
3266  return [];
3267  }
3268 
3269  $db = wfGetDB( DB_REPLICA );
3270 
3271  $blNamespace = "{$prefix}_namespace";
3272  $blTitle = "{$prefix}_title";
3273 
3274  $pageQuery = WikiPage::getQueryInfo();
3275  $res = $db->select(
3276  [ $table, 'nestpage' => $pageQuery['tables'] ],
3277  array_merge(
3278  [ $blNamespace, $blTitle ],
3279  $pageQuery['fields']
3280  ),
3281  [ "{$prefix}_from" => $id ],
3282  __METHOD__,
3283  $options,
3284  [ 'nestpage' => [
3285  'LEFT JOIN',
3286  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3287  ] ] + $pageQuery['joins']
3288  );
3289 
3290  $retVal = [];
3291  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3292  foreach ( $res as $row ) {
3293  if ( $row->page_id ) {
3294  $titleObj = self::newFromRow( $row );
3295  } else {
3296  $titleObj = self::makeTitle( $row->$blNamespace, $row->$blTitle );
3297  $linkCache->addBadLinkObj( $titleObj );
3298  }
3299  $retVal[] = $titleObj;
3300  }
3301 
3302  return $retVal;
3303  }
3304 
3315  public function getTemplateLinksFrom( $options = [] ) {
3316  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3317  }
3318 
3327  public function getBrokenLinksFrom() {
3328  if ( $this->getArticleID() == 0 ) {
3329  # All links from article ID 0 are false positives
3330  return [];
3331  }
3332 
3333  $dbr = wfGetDB( DB_REPLICA );
3334  $res = $dbr->select(
3335  [ 'page', 'pagelinks' ],
3336  [ 'pl_namespace', 'pl_title' ],
3337  [
3338  'pl_from' => $this->getArticleID(),
3339  'page_namespace IS NULL'
3340  ],
3341  __METHOD__, [],
3342  [
3343  'page' => [
3344  'LEFT JOIN',
3345  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3346  ]
3347  ]
3348  );
3349 
3350  $retVal = [];
3351  foreach ( $res as $row ) {
3352  $retVal[] = self::makeTitle( $row->pl_namespace, $row->pl_title );
3353  }
3354  return $retVal;
3355  }
3356 
3363  public function getCdnUrls() {
3364  $urls = [
3365  $this->getInternalURL(),
3366  $this->getInternalURL( 'action=history' )
3367  ];
3368 
3369  $pageLang = $this->getPageLanguage();
3370  if ( $pageLang->hasVariants() ) {
3371  $variants = $pageLang->getVariants();
3372  foreach ( $variants as $vCode ) {
3373  $urls[] = $this->getInternalURL( $vCode );
3374  }
3375  }
3376 
3377  // If we are looking at a css/js user subpage, purge the action=raw.
3378  if ( $this->isUserJsConfigPage() ) {
3379  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3380  } elseif ( $this->isUserJsonConfigPage() ) {
3381  $urls[] = $this->getInternalURL( 'action=raw&ctype=application/json' );
3382  } elseif ( $this->isUserCssConfigPage() ) {
3383  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3384  }
3385 
3386  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3387  return $urls;
3388  }
3389 
3393  public function purgeSquid() {
3395  new CdnCacheUpdate( $this->getCdnUrls() ),
3397  );
3398  }
3399 
3410  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3411  global $wgUser;
3412 
3413  if ( !( $nt instanceof Title ) ) {
3414  // Normally we'd add this to $errors, but we'll get
3415  // lots of syntax errors if $nt is not an object
3416  return [ [ 'badtitletext' ] ];
3417  }
3418 
3419  $mp = new MovePage( $this, $nt );
3420  $errors = $mp->isValidMove()->getErrorsArray();
3421  if ( $auth ) {
3422  $errors = wfMergeErrorArrays(
3423  $errors,
3424  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3425  );
3426  }
3427 
3428  return $errors ?: true;
3429  }
3430 
3444  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true,
3445  array $changeTags = []
3446  ) {
3447  global $wgUser;
3448  $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3449  if ( is_array( $err ) ) {
3450  // Auto-block user's IP if the account was "hard" blocked
3451  $wgUser->spreadAnyEditBlock();
3452  return $err;
3453  }
3454  // Check suppressredirect permission
3455  if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3456  $createRedirect = true;
3457  }
3458 
3459  $mp = new MovePage( $this, $nt );
3460  $status = $mp->move( $wgUser, $reason, $createRedirect, $changeTags );
3461  if ( $status->isOK() ) {
3462  return true;
3463  } else {
3464  return $status->getErrorsArray();
3465  }
3466  }
3467 
3482  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true,
3483  array $changeTags = []
3484  ) {
3485  global $wgMaximumMovedPages;
3486  // Check permissions
3487  if ( !$this->userCan( 'move-subpages' ) ) {
3488  return [
3489  [ 'cant-move-subpages' ],
3490  ];
3491  }
3492  // Do the source and target namespaces support subpages?
3493  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3494  return [
3495  [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $this->mNamespace ) ],
3496  ];
3497  }
3498  if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3499  return [
3500  [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $nt->getNamespace() ) ],
3501  ];
3502  }
3503 
3504  $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3505  $retval = [];
3506  $count = 0;
3507  foreach ( $subpages as $oldSubpage ) {
3508  $count++;
3509  if ( $count > $wgMaximumMovedPages ) {
3510  $retval[$oldSubpage->getPrefixedText()] = [
3511  [ 'movepage-max-pages', $wgMaximumMovedPages ],
3512  ];
3513  break;
3514  }
3515 
3516  // We don't know whether this function was called before
3517  // or after moving the root page, so check both
3518  // $this and $nt
3519  if ( $oldSubpage->getArticleID() == $this->getArticleID()
3520  || $oldSubpage->getArticleID() == $nt->getArticleID()
3521  ) {
3522  // When moving a page to a subpage of itself,
3523  // don't move it twice
3524  continue;
3525  }
3526  $newPageName = preg_replace(
3527  '#^' . preg_quote( $this->mDbkeyform, '#' ) . '#',
3528  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
3529  $oldSubpage->getDBkey() );
3530  if ( $oldSubpage->isTalkPage() ) {
3531  $newNs = $nt->getTalkPage()->getNamespace();
3532  } else {
3533  $newNs = $nt->getSubjectPage()->getNamespace();
3534  }
3535  # T16385: we need makeTitleSafe because the new page names may
3536  # be longer than 255 characters.
3537  $newSubpage = self::makeTitleSafe( $newNs, $newPageName );
3538 
3539  $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect, $changeTags );
3540  if ( $success === true ) {
3541  $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3542  } else {
3543  $retval[$oldSubpage->getPrefixedText()] = $success;
3544  }
3545  }
3546  return $retval;
3547  }
3548 
3555  public function isSingleRevRedirect() {
3556  global $wgContentHandlerUseDB;
3557 
3558  $dbw = wfGetDB( DB_MASTER );
3559 
3560  # Is it a redirect?
3561  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3562  if ( $wgContentHandlerUseDB ) {
3563  $fields[] = 'page_content_model';
3564  }
3565 
3566  $row = $dbw->selectRow( 'page',
3567  $fields,
3568  $this->pageCond(),
3569  __METHOD__,
3570  [ 'FOR UPDATE' ]
3571  );
3572  # Cache some fields we may want
3573  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3574  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3575  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3576  $this->mContentModel = $row && isset( $row->page_content_model )
3577  ? strval( $row->page_content_model )
3578  : false;
3579 
3580  if ( !$this->mRedirect ) {
3581  return false;
3582  }
3583  # Does the article have a history?
3584  $row = $dbw->selectField( [ 'page', 'revision' ],
3585  'rev_id',
3586  [ 'page_namespace' => $this->mNamespace,
3587  'page_title' => $this->mDbkeyform,
3588  'page_id=rev_page',
3589  'page_latest != rev_id'
3590  ],
3591  __METHOD__,
3592  [ 'FOR UPDATE' ]
3593  );
3594  # Return true if there was no history
3595  return ( $row === false );
3596  }
3597 
3606  public function isValidMoveTarget( $nt ) {
3607  # Is it an existing file?
3608  if ( $nt->getNamespace() == NS_FILE ) {
3609  $file = wfLocalFile( $nt );
3610  $file->load( File::READ_LATEST );
3611  if ( $file->exists() ) {
3612  wfDebug( __METHOD__ . ": file exists\n" );
3613  return false;
3614  }
3615  }
3616  # Is it a redirect with no history?
3617  if ( !$nt->isSingleRevRedirect() ) {
3618  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3619  return false;
3620  }
3621  # Get the article text
3622  $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
3623  if ( !is_object( $rev ) ) {
3624  return false;
3625  }
3626  $content = $rev->getContent();
3627  # Does the redirect point to the source?
3628  # Or is it a broken self-redirect, usually caused by namespace collisions?
3629  $redirTitle = $content ? $content->getRedirectTarget() : null;
3630 
3631  if ( $redirTitle ) {
3632  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3633  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3634  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3635  return false;
3636  } else {
3637  return true;
3638  }
3639  } else {
3640  # Fail safe (not a redirect after all. strange.)
3641  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3642  " is a redirect, but it doesn't contain a valid redirect.\n" );
3643  return false;
3644  }
3645  }
3646 
3654  public function getParentCategories() {
3655  $data = [];
3656 
3657  $titleKey = $this->getArticleID();
3658 
3659  if ( $titleKey === 0 ) {
3660  return $data;
3661  }
3662 
3663  $dbr = wfGetDB( DB_REPLICA );
3664 
3665  $res = $dbr->select(
3666  'categorylinks',
3667  'cl_to',
3668  [ 'cl_from' => $titleKey ],
3669  __METHOD__
3670  );
3671 
3672  if ( $res->numRows() > 0 ) {
3673  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3674  foreach ( $res as $row ) {
3675  // $data[] = Title::newFromText( $contLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3676  $data[$contLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] =
3677  $this->getFullText();
3678  }
3679  }
3680  return $data;
3681  }
3682 
3689  public function getParentCategoryTree( $children = [] ) {
3690  $stack = [];
3691  $parents = $this->getParentCategories();
3692 
3693  if ( $parents ) {
3694  foreach ( $parents as $parent => $current ) {
3695  if ( array_key_exists( $parent, $children ) ) {
3696  # Circular reference
3697  $stack[$parent] = [];
3698  } else {
3699  $nt = self::newFromText( $parent );
3700  if ( $nt ) {
3701  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3702  }
3703  }
3704  }
3705  }
3706 
3707  return $stack;
3708  }
3709 
3716  public function pageCond() {
3717  if ( $this->mArticleID > 0 ) {
3718  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3719  return [ 'page_id' => $this->mArticleID ];
3720  } else {
3721  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3722  }
3723  }
3724 
3732  private function getRelativeRevisionID( $revId, $flags, $dir ) {
3733  $revId = (int)$revId;
3734  if ( $dir === 'next' ) {
3735  $op = '>';
3736  $sort = 'ASC';
3737  } elseif ( $dir === 'prev' ) {
3738  $op = '<';
3739  $sort = 'DESC';
3740  } else {
3741  throw new InvalidArgumentException( '$dir must be "next" or "prev"' );
3742  }
3743 
3744  if ( $flags & self::GAID_FOR_UPDATE ) {
3745  $db = wfGetDB( DB_MASTER );
3746  } else {
3747  $db = wfGetDB( DB_REPLICA, 'contributions' );
3748  }
3749 
3750  // Intentionally not caring if the specified revision belongs to this
3751  // page. We only care about the timestamp.
3752  $ts = $db->selectField( 'revision', 'rev_timestamp', [ 'rev_id' => $revId ], __METHOD__ );
3753  if ( $ts === false ) {
3754  $ts = $db->selectField( 'archive', 'ar_timestamp', [ 'ar_rev_id' => $revId ], __METHOD__ );
3755  if ( $ts === false ) {
3756  // Or should this throw an InvalidArgumentException or something?
3757  return false;
3758  }
3759  }
3760  $ts = $db->addQuotes( $ts );
3761 
3762  $revId = $db->selectField( 'revision', 'rev_id',
3763  [
3764  'rev_page' => $this->getArticleID( $flags ),
3765  "rev_timestamp $op $ts OR (rev_timestamp = $ts AND rev_id $op $revId)"
3766  ],
3767  __METHOD__,
3768  [
3769  'ORDER BY' => "rev_timestamp $sort, rev_id $sort",
3770  'IGNORE INDEX' => 'rev_timestamp', // Probably needed for T159319
3771  ]
3772  );
3773 
3774  if ( $revId === false ) {
3775  return false;
3776  } else {
3777  return intval( $revId );
3778  }
3779  }
3780 
3788  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3789  return $this->getRelativeRevisionID( $revId, $flags, 'prev' );
3790  }
3791 
3799  public function getNextRevisionID( $revId, $flags = 0 ) {
3800  return $this->getRelativeRevisionID( $revId, $flags, 'next' );
3801  }
3802 
3809  public function getFirstRevision( $flags = 0 ) {
3810  $pageId = $this->getArticleID( $flags );
3811  if ( $pageId ) {
3812  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
3814  $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'],
3815  [ 'rev_page' => $pageId ],
3816  __METHOD__,
3817  [
3818  'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
3819  'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
3820  ],
3821  $revQuery['joins']
3822  );
3823  if ( $row ) {
3824  return new Revision( $row, 0, $this );
3825  }
3826  }
3827  return null;
3828  }
3829 
3836  public function getEarliestRevTime( $flags = 0 ) {
3837  $rev = $this->getFirstRevision( $flags );
3838  return $rev ? $rev->getTimestamp() : null;
3839  }
3840 
3846  public function isNewPage() {
3847  $dbr = wfGetDB( DB_REPLICA );
3848  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
3849  }
3850 
3856  public function isBigDeletion() {
3857  global $wgDeleteRevisionsLimit;
3858 
3859  if ( !$wgDeleteRevisionsLimit ) {
3860  return false;
3861  }
3862 
3863  if ( $this->mIsBigDeletion === null ) {
3864  $dbr = wfGetDB( DB_REPLICA );
3865 
3866  $revCount = $dbr->selectRowCount(
3867  'revision',
3868  '1',
3869  [ 'rev_page' => $this->getArticleID() ],
3870  __METHOD__,
3871  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
3872  );
3873 
3874  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
3875  }
3876 
3877  return $this->mIsBigDeletion;
3878  }
3879 
3885  public function estimateRevisionCount() {
3886  if ( !$this->exists() ) {
3887  return 0;
3888  }
3889 
3890  if ( $this->mEstimateRevisions === null ) {
3891  $dbr = wfGetDB( DB_REPLICA );
3892  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
3893  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
3894  }
3895 
3897  }
3898 
3908  public function countRevisionsBetween( $old, $new, $max = null ) {
3909  if ( !( $old instanceof Revision ) ) {
3910  $old = Revision::newFromTitle( $this, (int)$old );
3911  }
3912  if ( !( $new instanceof Revision ) ) {
3913  $new = Revision::newFromTitle( $this, (int)$new );
3914  }
3915  if ( !$old || !$new ) {
3916  return 0; // nothing to compare
3917  }
3918  $dbr = wfGetDB( DB_REPLICA );
3919  $conds = [
3920  'rev_page' => $this->getArticleID(),
3921  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
3922  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
3923  ];
3924  if ( $max !== null ) {
3925  return $dbr->selectRowCount( 'revision', '1',
3926  $conds,
3927  __METHOD__,
3928  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
3929  );
3930  } else {
3931  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
3932  }
3933  }
3934 
3951  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
3952  if ( !( $old instanceof Revision ) ) {
3953  $old = Revision::newFromTitle( $this, (int)$old );
3954  }
3955  if ( !( $new instanceof Revision ) ) {
3956  $new = Revision::newFromTitle( $this, (int)$new );
3957  }
3958  // XXX: what if Revision objects are passed in, but they don't refer to this title?
3959  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
3960  // in the sanity check below?
3961  if ( !$old || !$new ) {
3962  return null; // nothing to compare
3963  }
3964  $authors = [];
3965  $old_cmp = '>';
3966  $new_cmp = '<';
3967  $options = (array)$options;
3968  if ( in_array( 'include_old', $options ) ) {
3969  $old_cmp = '>=';
3970  }
3971  if ( in_array( 'include_new', $options ) ) {
3972  $new_cmp = '<=';
3973  }
3974  if ( in_array( 'include_both', $options ) ) {
3975  $old_cmp = '>=';
3976  $new_cmp = '<=';
3977  }
3978  // No DB query needed if $old and $new are the same or successive revisions:
3979  if ( $old->getId() === $new->getId() ) {
3980  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
3981  [] :
3982  [ $old->getUserText( Revision::RAW ) ];
3983  } elseif ( $old->getId() === $new->getParentId() ) {
3984  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
3985  $authors[] = $old->getUserText( Revision::RAW );
3986  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
3987  $authors[] = $new->getUserText( Revision::RAW );
3988  }
3989  } elseif ( $old_cmp === '>=' ) {
3990  $authors[] = $old->getUserText( Revision::RAW );
3991  } elseif ( $new_cmp === '<=' ) {
3992  $authors[] = $new->getUserText( Revision::RAW );
3993  }
3994  return $authors;
3995  }
3996  $dbr = wfGetDB( DB_REPLICA );
3998  $authors = $dbr->selectFieldValues(
3999  $revQuery['tables'],
4000  $revQuery['fields']['rev_user_text'],
4001  [
4002  'rev_page' => $this->getArticleID(),
4003  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4004  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4005  ], __METHOD__,
4006  [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated
4007  $revQuery['joins']
4008  );
4009  return $authors;
4010  }
4011 
4026  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4027  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4028  return $authors ? count( $authors ) : 0;
4029  }
4030 
4037  public function equals( Title $title ) {
4038  // Note: === is necessary for proper matching of number-like titles.
4039  return $this->mInterwiki === $title->mInterwiki
4040  && $this->mNamespace == $title->mNamespace
4041  && $this->mDbkeyform === $title->mDbkeyform;
4042  }
4043 
4050  public function isSubpageOf( Title $title ) {
4051  return $this->mInterwiki === $title->mInterwiki
4052  && $this->mNamespace == $title->mNamespace
4053  && strpos( $this->mDbkeyform, $title->mDbkeyform . '/' ) === 0;
4054  }
4055 
4067  public function exists( $flags = 0 ) {
4068  $exists = $this->getArticleID( $flags ) != 0;
4069  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4070  return $exists;
4071  }
4072 
4089  public function isAlwaysKnown() {
4090  $isKnown = null;
4091 
4102  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4103 
4104  if ( !is_null( $isKnown ) ) {
4105  return $isKnown;
4106  }
4107 
4108  if ( $this->isExternal() ) {
4109  return true; // any interwiki link might be viewable, for all we know
4110  }
4111 
4112  switch ( $this->mNamespace ) {
4113  case NS_MEDIA:
4114  case NS_FILE:
4115  // file exists, possibly in a foreign repo
4116  return (bool)wfFindFile( $this );
4117  case NS_SPECIAL:
4118  // valid special page
4119  return MediaWikiServices::getInstance()->getSpecialPageFactory()->
4120  exists( $this->mDbkeyform );
4121  case NS_MAIN:
4122  // selflink, possibly with fragment
4123  return $this->mDbkeyform == '';
4124  case NS_MEDIAWIKI:
4125  // known system message
4126  return $this->hasSourceText() !== false;
4127  default:
4128  return false;
4129  }
4130  }
4131 
4143  public function isKnown() {
4144  return $this->isAlwaysKnown() || $this->exists();
4145  }
4146 
4152  public function hasSourceText() {
4153  if ( $this->exists() ) {
4154  return true;
4155  }
4156 
4157  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4158  // If the page doesn't exist but is a known system message, default
4159  // message content will be displayed, same for language subpages-
4160  // Use always content language to avoid loading hundreds of languages
4161  // to get the link color.
4162  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
4163  list( $name, ) = MessageCache::singleton()->figureMessage(
4164  $contLang->lcfirst( $this->getText() )
4165  );
4166  $message = wfMessage( $name )->inLanguage( $contLang )->useDatabase( false );
4167  return $message->exists();
4168  }
4169 
4170  return false;
4171  }
4172 
4210  public function getDefaultMessageText() {
4211  if ( $this->mNamespace != NS_MEDIAWIKI ) { // Just in case
4212  return false;
4213  }
4214 
4215  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4216  MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $this->getText() )
4217  );
4218  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4219 
4220  if ( $message->exists() ) {
4221  return $message->plain();
4222  } else {
4223  return false;
4224  }
4225  }
4226 
4233  public function invalidateCache( $purgeTime = null ) {
4234  if ( wfReadOnly() ) {
4235  return false;
4236  } elseif ( $this->mArticleID === 0 ) {
4237  return true; // avoid gap locking if we know it's not there
4238  }
4239 
4240  $dbw = wfGetDB( DB_MASTER );
4241  $dbw->onTransactionPreCommitOrIdle(
4242  function () use ( $dbw ) {
4244  $this, null, null, $dbw->getDomainID() );
4245  },
4246  __METHOD__
4247  );
4248 
4249  $conds = $this->pageCond();
4251  new AutoCommitUpdate(
4252  $dbw,
4253  __METHOD__,
4254  function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4255  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4256  $dbw->update(
4257  'page',
4258  [ 'page_touched' => $dbTimestamp ],
4259  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4260  $fname
4261  );
4262  MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4263  }
4264  ),
4266  );
4267 
4268  return true;
4269  }
4270 
4276  public function touchLinks() {
4277  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
4278  if ( $this->mNamespace == NS_CATEGORY ) {
4280  new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
4281  );
4282  }
4283  }
4284 
4291  public function getTouched( $db = null ) {
4292  if ( $db === null ) {
4293  $db = wfGetDB( DB_REPLICA );
4294  }
4295  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4296  return $touched;
4297  }
4298 
4305  public function getNotificationTimestamp( $user = null ) {
4306  global $wgUser;
4307 
4308  // Assume current user if none given
4309  if ( !$user ) {
4310  $user = $wgUser;
4311  }
4312  // Check cache first
4313  $uid = $user->getId();
4314  if ( !$uid ) {
4315  return false;
4316  }
4317  // avoid isset here, as it'll return false for null entries
4318  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4319  return $this->mNotificationTimestamp[$uid];
4320  }
4321  // Don't cache too much!
4322  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4323  $this->mNotificationTimestamp = [];
4324  }
4325 
4326  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4327  $watchedItem = $store->getWatchedItem( $user, $this );
4328  if ( $watchedItem ) {
4329  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4330  } else {
4331  $this->mNotificationTimestamp[$uid] = false;
4332  }
4333 
4334  return $this->mNotificationTimestamp[$uid];
4335  }
4336 
4343  public function getNamespaceKey( $prepend = 'nstab-' ) {
4344  // Gets the subject namespace of this title
4345  $subjectNS = MWNamespace::getSubject( $this->mNamespace );
4346  // Prefer canonical namespace name for HTML IDs
4347  $namespaceKey = MWNamespace::getCanonicalName( $subjectNS );
4348  if ( $namespaceKey === false ) {
4349  // Fallback to localised text
4350  $namespaceKey = $this->getSubjectNsText();
4351  }
4352  // Makes namespace key lowercase
4353  $namespaceKey = MediaWikiServices::getInstance()->getContentLanguage()->lc( $namespaceKey );
4354  // Uses main
4355  if ( $namespaceKey == '' ) {
4356  $namespaceKey = 'main';
4357  }
4358  // Changes file to image for backwards compatibility
4359  if ( $namespaceKey == 'file' ) {
4360  $namespaceKey = 'image';
4361  }
4362  return $prepend . $namespaceKey;
4363  }
4364 
4371  public function getRedirectsHere( $ns = null ) {
4372  $redirs = [];
4373 
4374  $dbr = wfGetDB( DB_REPLICA );
4375  $where = [
4376  'rd_namespace' => $this->mNamespace,
4377  'rd_title' => $this->mDbkeyform,
4378  'rd_from = page_id'
4379  ];
4380  if ( $this->isExternal() ) {
4381  $where['rd_interwiki'] = $this->mInterwiki;
4382  } else {
4383  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4384  }
4385  if ( !is_null( $ns ) ) {
4386  $where['page_namespace'] = $ns;
4387  }
4388 
4389  $res = $dbr->select(
4390  [ 'redirect', 'page' ],
4391  [ 'page_namespace', 'page_title' ],
4392  $where,
4393  __METHOD__
4394  );
4395 
4396  foreach ( $res as $row ) {
4397  $redirs[] = self::newFromRow( $row );
4398  }
4399  return $redirs;
4400  }
4401 
4407  public function isValidRedirectTarget() {
4409 
4410  if ( $this->isSpecialPage() ) {
4411  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4412  if ( $this->isSpecial( 'Userlogout' ) ) {
4413  return false;
4414  }
4415 
4416  foreach ( $wgInvalidRedirectTargets as $target ) {
4417  if ( $this->isSpecial( $target ) ) {
4418  return false;
4419  }
4420  }
4421  }
4422 
4423  return true;
4424  }
4425 
4431  public function getBacklinkCache() {
4432  return BacklinkCache::get( $this );
4433  }
4434 
4440  public function canUseNoindex() {
4442 
4443  $bannedNamespaces = $wgExemptFromUserRobotsControl ?? MWNamespace::getContentNamespaces();
4444 
4445  return !in_array( $this->mNamespace, $bannedNamespaces );
4446  }
4447 
4458  public function getCategorySortkey( $prefix = '' ) {
4459  $unprefixed = $this->getText();
4460 
4461  // Anything that uses this hook should only depend
4462  // on the Title object passed in, and should probably
4463  // tell the users to run updateCollations.php --force
4464  // in order to re-sort existing category relations.
4465  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4466  if ( $prefix !== '' ) {
4467  # Separate with a line feed, so the unprefixed part is only used as
4468  # a tiebreaker when two pages have the exact same prefix.
4469  # In UCA, tab is the only character that can sort above LF
4470  # so we strip both of them from the original prefix.
4471  $prefix = strtr( $prefix, "\n\t", ' ' );
4472  return "$prefix\n$unprefixed";
4473  }
4474  return $unprefixed;
4475  }
4476 
4484  private function getDbPageLanguageCode() {
4485  global $wgPageLanguageUseDB;
4486 
4487  // check, if the page language could be saved in the database, and if so and
4488  // the value is not requested already, lookup the page language using LinkCache
4489  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4490  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
4491  $linkCache->addLinkObj( $this );
4492  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4493  }
4494 
4495  return $this->mDbPageLanguage;
4496  }
4497 
4506  public function getPageLanguage() {
4507  global $wgLang, $wgLanguageCode;
4508  if ( $this->isSpecialPage() ) {
4509  // special pages are in the user language
4510  return $wgLang;
4511  }
4512 
4513  // Checking if DB language is set
4514  $dbPageLanguage = $this->getDbPageLanguageCode();
4515  if ( $dbPageLanguage ) {
4516  return wfGetLangObj( $dbPageLanguage );
4517  }
4518 
4519  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4520  // Note that this may depend on user settings, so the cache should
4521  // be only per-request.
4522  // NOTE: ContentHandler::getPageLanguage() may need to load the
4523  // content to determine the page language!
4524  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4525  // tests.
4526  $contentHandler = ContentHandler::getForTitle( $this );
4527  $langObj = $contentHandler->getPageLanguage( $this );
4528  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4529  } else {
4530  $langObj = Language::factory( $this->mPageLanguage[0] );
4531  }
4532 
4533  return $langObj;
4534  }
4535 
4544  public function getPageViewLanguage() {
4545  global $wgLang;
4546 
4547  if ( $this->isSpecialPage() ) {
4548  // If the user chooses a variant, the content is actually
4549  // in a language whose code is the variant code.
4550  $variant = $wgLang->getPreferredVariant();
4551  if ( $wgLang->getCode() !== $variant ) {
4552  return Language::factory( $variant );
4553  }
4554 
4555  return $wgLang;
4556  }
4557 
4558  // Checking if DB language is set
4559  $dbPageLanguage = $this->getDbPageLanguageCode();
4560  if ( $dbPageLanguage ) {
4561  $pageLang = wfGetLangObj( $dbPageLanguage );
4562  $variant = $pageLang->getPreferredVariant();
4563  if ( $pageLang->getCode() !== $variant ) {
4564  $pageLang = Language::factory( $variant );
4565  }
4566 
4567  return $pageLang;
4568  }
4569 
4570  // @note Can't be cached persistently, depends on user settings.
4571  // @note ContentHandler::getPageViewLanguage() may need to load the
4572  // content to determine the page language!
4573  $contentHandler = ContentHandler::getForTitle( $this );
4574  $pageLang = $contentHandler->getPageViewLanguage( $this );
4575  return $pageLang;
4576  }
4577 
4588  public function getEditNotices( $oldid = 0 ) {
4589  $notices = [];
4590 
4591  // Optional notice for the entire namespace
4592  $editnotice_ns = 'editnotice-' . $this->mNamespace;
4593  $msg = wfMessage( $editnotice_ns );
4594  if ( $msg->exists() ) {
4595  $html = $msg->parseAsBlock();
4596  // Edit notices may have complex logic, but output nothing (T91715)
4597  if ( trim( $html ) !== '' ) {
4598  $notices[$editnotice_ns] = Html::rawElement(
4599  'div',
4600  [ 'class' => [
4601  'mw-editnotice',
4602  'mw-editnotice-namespace',
4603  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4604  ] ],
4605  $html
4606  );
4607  }
4608  }
4609 
4610  if ( MWNamespace::hasSubpages( $this->mNamespace ) ) {
4611  // Optional notice for page itself and any parent page
4612  $editnotice_base = $editnotice_ns;
4613  foreach ( explode( '/', $this->mDbkeyform ) as $part ) {
4614  $editnotice_base .= '-' . $part;
4615  $msg = wfMessage( $editnotice_base );
4616  if ( $msg->exists() ) {
4617  $html = $msg->parseAsBlock();
4618  if ( trim( $html ) !== '' ) {
4619  $notices[$editnotice_base] = Html::rawElement(
4620  'div',
4621  [ 'class' => [
4622  'mw-editnotice',
4623  'mw-editnotice-base',
4624  Sanitizer::escapeClass( "mw-$editnotice_base" )
4625  ] ],
4626  $html
4627  );
4628  }
4629  }
4630  }
4631  } else {
4632  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4633  $editnoticeText = $editnotice_ns . '-' . strtr( $this->mDbkeyform, '/', '-' );
4634  $msg = wfMessage( $editnoticeText );
4635  if ( $msg->exists() ) {
4636  $html = $msg->parseAsBlock();
4637  if ( trim( $html ) !== '' ) {
4638  $notices[$editnoticeText] = Html::rawElement(
4639  'div',
4640  [ 'class' => [
4641  'mw-editnotice',
4642  'mw-editnotice-page',
4643  Sanitizer::escapeClass( "mw-$editnoticeText" )
4644  ] ],
4645  $html
4646  );
4647  }
4648  }
4649  }
4650 
4651  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4652  return $notices;
4653  }
4654 
4658  public function __sleep() {
4659  return [
4660  'mNamespace',
4661  'mDbkeyform',
4662  'mFragment',
4663  'mInterwiki',
4664  'mLocalInterwiki',
4665  'mUserCaseDBKey',
4666  'mDefaultNamespace',
4667  ];
4668  }
4669 
4670  public function __wakeup() {
4671  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4672  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4673  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4674  }
4675 
4676 }
bool $mHasSubpages
Whether a page has any subpages.
Definition: Title.php:177
static getFilteredRestrictionTypes( $exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition: Title.php:2276
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition: Title.php:4089
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
areCascadeProtectionSourcesLoaded( $getPages=true)
Determines whether cascading protection sources have already been loaded from the database...
Definition: Title.php:2501
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition: Title.php:2838
clear( $keys=null)
Clear one or several cache entries, or all cache entries.
static castFromLinkTarget( $linkTarget)
Same as newFromLinkTarget, but if passed null, returns null.
Definition: Title.php:295
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title...
Definition: Title.php:4276
getFragment()
Get the Title fragment (i.e.
Definition: Title.php:1580
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2633
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1982
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
mixed $mTitleProtection
Cached value for getTitleProtection (create protection)
Definition: Title.php:158
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4233
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition: Title.php:2617
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren&#39;t movable...
Definition: Title.php:1271
getRootTitle()
Get the root page name title, i.e.
Definition: Title.php:1755
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:2985
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition: Title.php:3951
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1585
canHaveTalkPage()
Can this title have a corresponding talk page?
Definition: Title.php:1132
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition: Title.php:4440
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:235
wasLocalInterwiki()
Was this a local interwiki link?
Definition: Title.php:920
$wgScript
The URL path to index.php.
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page...
Definition: Title.php:1261
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
getUserCaseDBKey()
Get the DB key with the initial letter case as specified by the user.
Definition: Title.php:1011
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2409
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:470
Handles the backend logic of moving a page from one title to another.
Definition: MovePage.php:32
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition: Title.php:2469
static clearCaches()
Definition: Title.php:3126
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:3908
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:2879
const NS_MAIN
Definition: Defines.php:64
$success
static invalidateModuleCache(Title $title, Revision $old=null, Revision $new=null, $domain)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
getSkinFromConfigSubpage()
Trim down a .css, .json, or .js subpage title to get the corresponding skin name. ...
Definition: Title.php:1370
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:983
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition: Title.php:1812
getBaseText()
Get the base page name without a namespace, i.e.
Definition: Title.php:1770
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:653
getParentCategoryTree( $children=[])
Get a tree of parent categories.
Definition: Title.php:3689
getDefaultMessageText()
Get the default (plain) message contents for an page that overrides an interface message key...
Definition: Title.php:4210
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition: Title.php:2676
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1982
static fixUrlQueryArgs( $query, $query2=false)
Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args get{Canonical,Full,Link,Local,Internal}URL methods accepted an optional second argument named variant.
Definition: Title.php:1872
loadFromRow( $row)
Load Title object fields from a DB row.
Definition: Title.php:531
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
setFragment( $fragment)
Set the fragment for this title.
Definition: Title.php:1626
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition: Title.php:1109
bool null $mIsBigDeletion
Would deleting this page be a big deletion?
Definition: Title.php:190
isUserJsConfigPage()
Is this a JS "config" sub-page of a user page?
Definition: Title.php:1414
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
__wakeup()
Definition: Title.php:4670
getTalkPageIfDefined()
Get a Title object associated with the talk page of this article, if such a talk page can exist...
Definition: Title.php:1519
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition: Title.php:943
get( $key, $maxAge=0.0)
Get the value for a key.
bool $mForcedContentModel
If a content model was forced via setContentModel() this will be true to avoid having other code path...
Definition: Title.php:113
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
$wgActionPaths
Definition: img_auth.php:47
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition: Title.php:2249
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition: Title.php:1945
$wgInternalServer
Internal server name as known to CDN, if different.
isWatchable()
Can this title be added to a user&#39;s watchlist?
Definition: Title.php:1150
if(!isset( $args[0])) $lang
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter...
isUserCssConfigPage()
Is this a CSS "config" sub-page of a user page?
Definition: Title.php:1386
static getSubject( $index)
Get the subject namespace index for a given namespace Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject.
Definition: MWNamespace.php:76
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
$wgRestrictionLevels
Rights which can be required for each protection level (via action=protect)
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition: Title.php:3161
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition: Title.php:1222
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
const TTL_UNCACHEABLE
Idiom for getWithSetCallback() callbacks to avoid calling set()
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1979
isTalkPage()
Is this a talk page of some sort?
Definition: Title.php:1497
Handles purging appropriate CDN URLs given a title (or titles)
string $mUrlform
URL-encoded form of the main part.
Definition: Title.php:77
$sort
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title...
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition: Title.php:4484
null for the local wiki Added in
Definition: hooks.txt:1585
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition: Title.php:3856
const NS_SPECIAL
Definition: Defines.php:53
const PROTO_CURRENT
Definition: Defines.php:222
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition: Title.php:4305
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:1550
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
static getInterwikiLookup()
B/C kludge: provide an InterwikiLookup for use by Title.
Definition: Title.php:213
$wgSemiprotectedRestrictionLevels
Restriction levels that should be considered "semiprotected".
static isTalk( $index)
Is the given namespace a talk namespace?
Definition: MWNamespace.php:55
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of or an object and a method hook function The function part of a third party developers and local administrators to define code that will be run at certain points in the mainline and to modify the data run by that mainline code Hooks can keep mainline code and make it easier to write extensions Hooks are a principled alternative to local patches for two options in MediaWiki One reverses the order of a title before displaying the article
Definition: hooks.txt:23
inNamespace( $ns)
Returns true if the title is inside the specified namespace.
Definition: Title.php:1211
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1691
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move a title to a new location.
Definition: Title.php:3444
getBaseTitle()
Get the base page name title, i.e.
Definition: Title.php:1797
getParentCategories()
Get categories to which this Title belongs and return an array of categories&#39; names.
Definition: Title.php:3654
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:519
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:3203
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4431
$wgArticlePath
Definition: img_auth.php:46
TitleValue null $mTitleValue
A corresponding TitleValue object.
Definition: Title.php:187
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition: Title.php:4458
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that&#39;s attached to a given link target...
Definition: Revision.php:137
const CONTENT_MODEL_JSON
Definition: Defines.php:239
wfLocalFile( $title)
Get an object referring to a locally registered file.
The TitleArray class only exists to provide the newFromResult method at pre- sent.
Definition: TitleArray.php:33
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition: hooks.txt:1799
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:3261
const DB_MASTER
Definition: defines.php:26
$wgRestrictionTypes
Set of available actions that can be restricted via action=protect You probably shouldn&#39;t change this...
this hook is for auditing only RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition: hooks.txt:979
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition: Title.php:1506
userCan( $action, $user=null, $rigor=PermissionManager::RIGOR_SECURE)
Can $user perform $action on this page?
Definition: Title.php:2189
getNamespace()
Get the namespace index.
static isContent( $index)
Does this namespace contain content, for the purposes of calculating statistics, etc?
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
const NEW_CLONE
Flag for use with factory methods like newFromLinkTarget() that have a $forceClone parameter...
Definition: Title.php:64
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition: Title.php:142
isSiteConfigPage()
Could this MediaWiki namespace page contain custom CSS, JSON, or JavaScript for the global UI...
Definition: Title.php:1342
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias...
Definition: Title.php:1187
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1980
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition: Title.php:714
getFragment()
Get the link fragment (i.e.
static hasTalkNamespace( $index)
Does this namespace ever have a talk namespace?
static nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition: Title.php:678
Deferrable Update for closure/callback updates that should use auto-commit mode.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:48
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:780
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e.g.
static get(Title $title)
Create a new BacklinkCache or reuse any existing one.
getSubpage( $text)
Get the title for a subpage of the current page.
Definition: Title.php:1833
$wgLanguageCode
Site language code.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition: Title.php:130
getLinkURL( $query='', $query2=false, $proto=false)
Get a URL that&#39;s the simplest URL that will be valid to link, locally, to the current Title...
Definition: Title.php:2078
quickUserCan( $action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition: Title.php:2170
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1263
isSiteCssConfigPage()
Is this a sitewide CSS "config" page?
Definition: Title.php:1428
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition: Title.php:2828
string null $prefixedText
Text form including namespace/interwiki, initialised on demand.
Definition: Title.php:155
string $mDbkeyform
Main part with underscores.
Definition: Title.php:80
getNsText()
Get the namespace text.
Definition: Title.php:1085
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition: Title.php:2127
__sleep()
Definition: Title.php:4658
string bool $mOldRestrictions
Comma-separated set of permission keys indicating who can move or edit the page from the page table...
Definition: Title.php:127
__construct()
Definition: Title.php:220
wfMergeErrorArrays(... $args)
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
wfReadOnly()
Check whether the wiki is in read-only mode.
isExternal()
Is this Title interwiki?
Definition: Title.php:900
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
$wgLang
Definition: Setup.php:876
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
isUserConfigPage()
Is this a "config" (.css, .json, or .js) sub-page of a user page?
Definition: Title.php:1356
deleteTitleProtection()
Remove any title protection due to page existing.
Definition: Title.php:2391
int $mNamespace
Namespace index, i.e.
Definition: Title.php:86
set( $key, $value, $rank=self::RANK_TOP)
Set a key/value pair.
hasSourceText()
Does this page have source text?
Definition: Title.php:4152
isUserJsonConfigPage()
Is this a JSON "config" sub-page of a user page?
Definition: Title.php:1400
resetArticleID( $newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3104
static getTitleCache()
Definition: Title.php:430
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition: Title.php:201
Class to invalidate the HTML cache of all the pages linking to a given title.
getDBkey()
Get the main part with underscores.
Definition: Title.php:1001
__toString()
Return a string representation of this title.
Definition: Title.php:1705
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition: Title.php:1853
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1169
hasFragment()
Check if a Title fragment is set.
Definition: Title.php:1590
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4371
$wgExemptFromUserRobotsControl
An array of namespace keys in which the INDEX/__NOINDEX__ magic words will not function, so users can&#39;t decide whether pages in that namespace are indexed by search engines.
$wgInvalidRedirectTargets
Array of invalid page redirect targets.
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:3245
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition: Title.php:2657
wfFindFile( $title, $options=[])
Find a file.
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition: Title.php:1312
const NS_MEDIA
Definition: Defines.php:52
getRootText()
Get the root page name text without a namespace, i.e.
Definition: Title.php:1735
$res
Definition: database.txt:21
bool string $mContentModel
ID of the page&#39;s content model, i.e.
Definition: Title.php:107
canExist()
Is this in a namespace that allows actual pages?
Definition: Title.php:1141
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getSubpages( $limit=-1)
Get all subpages of this page.
Definition: Title.php:2907
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:55
null $mRedirect
Is the article at this title a redirect?
Definition: Title.php:171
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
hasContentModel( $id)
Convenience method for checking a title&#39;s content model name.
Definition: Title.php:1060
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:4407
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not...
Definition: Title.php:2487
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition: Title.php:3606
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition: Title.php:3410
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3363
$cache
Definition: mcc.php:33
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition: Title.php:1569
const NS_CATEGORY
Definition: Defines.php:78
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1982
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3847
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:4050
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition: Title.php:174
static isMovable( $index)
Can pages in the given namespace be moved?
Definition: MWNamespace.php:34
loadRestrictions( $oldFashionedRestrictions=null, $flags=0)
Load restrictions from the page_restrictions table.
Definition: Title.php:2754
static exists( $index)
Returns whether the specified namespace exists.
static getContentNamespaces()
Get a list of all namespace indices which are considered to contain content.
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with &#39;#&#39;)
Definition: Title.php:1715
static newFromIDs( $ids)
Make an array of titles from an array of IDs.
Definition: Title.php:493
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:184
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
static newFromResult( $res)
Definition: TitleArray.php:40
isMainPage()
Is this the mainpage?
Definition: Title.php:1292
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object. ...
Definition: Title.php:1653
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1410
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object...
Definition: Revision.php:511
getNamespaceKey( $prepend='nstab-')
Generate strings used for xml &#39;id&#39; names in monobook tabs.
Definition: Title.php:4343
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:215
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition: Title.php:1910
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1025
const PROTO_RELATIVE
Definition: Defines.php:221
string $mInterwiki
Interwiki prefix.
Definition: Title.php:89
equals(Title $title)
Compare with another title.
Definition: Title.php:4037
static hasFlags( $bitfield, $flags)
isKnown()
Does this title refer to a page that can (or might) be meaningfully viewed? In particular, this function may be used to determine if links to the title should be rendered as "bluelinks" (as opposed to "redlinks" to non-existent pages).
Definition: Title.php:4143
isProtected( $action='')
Does the title correspond to a protected article?
Definition: Title.php:2437
const NS_FILE
Definition: Defines.php:70
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2604
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:82
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1766
isSiteJsonConfigPage()
Is this a sitewide JSON "config" page?
Definition: Title.php:1446
isSubpage()
Is this a subpage?
Definition: Title.php:1301
getInterwiki()
Get the interwiki prefix.
Definition: Title.php:911
const RAW
Definition: Revision.php:56
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:925
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page...
Definition: Title.php:3327
const NS_MEDIAWIKI
Definition: Defines.php:72
const PROTO_HTTP
Definition: Defines.php:219
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:123
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition: Title.php:136
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:3315
static isWatchable( $index)
Can pages in a namespace be watched?
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid, rather than returning null.
Definition: Title.php:356
isValid()
Returns true if the title is valid, false if it is invalid.
Definition: Title.php:864
CONTENT_MODEL_JAVASCRIPT
Allow users to upload files.
static equals( $ns1, $ns2)
Returns whether the specified namespaces are the same namespace.
bool $wgPageLanguageUseDB
Enable page language feature Allows setting page language in database.
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition: Title.php:1533
int $mLength
The page length, 0 for special pages.
Definition: Title.php:168
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:617
hasSubjectNamespace( $ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition: Title.php:1250
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:271
isSpecialPage()
Returns true if this is a special page.
Definition: Title.php:1159
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:2141
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:589
$wgLegalTitleChars
Allowed title characters – regex character class Don&#39;t change this unless you know what you&#39;re doing...
static isCapitalized( $index)
Is the namespace first-letter capitalized?
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
$wgNamespaceProtection
Set the minimum permissions required to edit pages in each namespace.
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition: Title.php:3555
const PROTO_CANONICAL
Definition: Defines.php:223
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
getFirstRevision( $flags=0)
Get the first revision of the page.
Definition: Title.php:3809
static newFromDBkey( $key)
Create a new Title from a prefixed DB key.
Definition: Title.php:231
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition: Title.php:4544
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition: Title.php:1842
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition: Title.php:133
static getStore()
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
createFragmentTarget( $fragment)
Creates a new Title for a different fragment of the same page.
Definition: Title.php:1637
linkcache txt The LinkCache class maintains a list of article titles and the information about whether or not the article exists in the database This is used to mark up links when displaying a page If the same link appears more than once on any page then it only has to be looked up once In most cases link lookups are done in batches with the LinkBatch class or the equivalent in so the link cache is mostly useful for short snippets of parsed and for links in the navigation areas of the skin The link cache was formerly used to track links used in a document for the purposes of updating the link tables This application is now deprecated To create a you can use the following $titles
Definition: linkcache.txt:17
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition: Title.php:407
countAuthorsBetween( $old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition: Title.php:4026
getTitleProtectionInternal()
Fetch title protection settings.
Definition: Title.php:2344
getRestrictionTypes()
Returns restriction types for the current Title.
Definition: Title.php:2294
$parent
Definition: pageupdater.txt:71
bool $mPageLanguage
The (string) language code of the page&#39;s language and content code.
Definition: Title.php:180
static escapeIdForLink( $id)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid URL...
Definition: Sanitizer.php:1314
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition: Title.php:930
getContentModel( $flags=0)
Get the page&#39;s content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:1037
string $mFragment
Title fragment (i.e.
Definition: Title.php:95
static hasSubpages( $index)
Does the namespace allow subpages?
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition: Title.php:98
static getTalk( $index)
Get the talk namespace index for a given namespace.
Definition: MWNamespace.php:65
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition: Title.php:165
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition: Title.php:3141
string [] $wgRawHtmlMessages
List of messages which might contain raw HTML.
getPreviousRevisionID( $revId, $flags=0)
Get the revision ID of the previous revision.
Definition: Title.php:3788
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition: Title.php:4506
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles...
Definition: Title.php:49
$revQuery
static escapeIdForExternalInterwiki( $id)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid URL...
Definition: Sanitizer.php:1337
isRawHtmlMessage()
Is this a message which can contain raw HTML?
Definition: Title.php:1482
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition: Title.php:1599
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the &#39;bigdelete&#39; perm...
exists( $flags=0)
Check if page exists.
Definition: Title.php:4067
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition: Title.php:3072
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition: Title.php:3716
getInterwiki()
The interwiki component of this LinkTarget.
const CONTENT_MODEL_CSS
Definition: Defines.php:237
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:3836
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition: Title.php:2959
isNewPage()
Check if this is a new page.
Definition: Title.php:3846
getText()
Returns the link in text form, without namespace prefix or fragment.
getRestrictionExpiry( $action)
Get the expiry time for the restriction against a given action.
Definition: Title.php:2645
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition: Title.php:845
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition: Title.php:145
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:700
getCascadeProtectionSources( $getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition: Title.php:2518
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3009
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
isSiteJsConfigPage()
Is this a sitewide JS "config" page?
Definition: Title.php:1464
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:728
$wgServer
URL of the server.
isDeleted()
Is there a version of this page in the deletion archive?
Definition: Title.php:2934
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition: Title.php:885
array $mRestrictions
Array of groups allowed to edit this article.
Definition: Title.php:119
const DB_REPLICA
Definition: defines.php:25
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition: Title.php:2321
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition: Title.php:92
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition: Title.php:116
getEditNotices( $oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:4588
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
getTouched( $db=null)
Get the last touched timestamp.
Definition: Title.php:4291
static MapCacheLRU null $titleCache
Definition: Title.php:42
getTalkNsText()
Get the namespace text of the talk page.
Definition: Title.php:1119
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries...
Definition: Title.php:444
static subjectEquals( $ns1, $ns2)
Returns whether the specified namespaces share the same subject.
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
string $mTextform
Text form (spaces not underscores) of the main part.
Definition: Title.php:74
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition: Title.php:2631
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition: Title.php:2103
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition: Title.php:819
$content
Definition: pageupdater.txt:72
addQuotes( $s)
Adds quotes and backslashes.
bool int $mLatestID
ID of most recent revision.
Definition: Title.php:101
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:1075
getUserPermissionsErrors( $action, $user, $rigor=PermissionManager::RIGOR_SECURE, $ignoreErrors=[])
Can $user perform $action on this page?
Definition: Title.php:2227
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
static newFromTitleValue(TitleValue $titleValue, $forceClone='')
Returns a Title given a TitleValue.
Definition: Title.php:256
getRelativeRevisionID( $revId, $flags, $dir)
Get next/previous revision ID relative to another revision ID.
Definition: Title.php:3732
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new page object...
Definition: WikiPage.php:383
$wgMaximumMovedPages
Maximum number of pages to move at once when moving subpages with a page.
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
getLength( $flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition: Title.php:3044
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition: Title.php:1324
static singleton()
Get the signleton instance of this class.
purgeSquid()
Purge all applicable CDN URLs.
Definition: Title.php:3393
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1473
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
getTitleValue()
Get a TitleValue object representing this Title.
Definition: Title.php:960
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition: Title.php:139
getPartialURL()
Get the URL-encoded form of the main part.
Definition: Title.php:992
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1679
static decodeCharReferencesAndNormalize( $text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1678
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move this page&#39;s subpages to be subpages of $nt.
Definition: Title.php:3482
estimateRevisionCount()
Get the approximate revision count of this page.
Definition: Title.php:3885
$matches
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition: Title.php:3799
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition: Title.php:83
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319