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 ( !MediaWikiServices::getInstance()->getNamespaceInfo()->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 = MediaWikiServices::getInstance()->getNamespaceInfo()->
824  getCanonicalName( $ns );
825  } else {
826  $namespace = MediaWikiServices::getInstance()->getContentLanguage()->getNsText( $ns );
827  }
828  $name = $namespace == '' ? $title : "$namespace:$title";
829  if ( strval( $interwiki ) != '' ) {
830  $name = "$interwiki:$name";
831  }
832  if ( strval( $fragment ) != '' ) {
833  $name .= '#' . $fragment;
834  }
835  return $name;
836  }
837 
846  public static function compare( LinkTarget $a, LinkTarget $b ) {
847  return $a->getNamespace() <=> $b->getNamespace()
848  ?: strcmp( $a->getText(), $b->getText() );
849  }
850 
865  public function isValid() {
866  $services = MediaWikiServices::getInstance();
867  if ( !$services->getNamespaceInfo()->exists( $this->mNamespace ) ) {
868  return false;
869  }
870 
871  try {
872  $services->getTitleParser()->parseTitle( $this->mDbkeyform, $this->mNamespace );
873  return true;
874  } catch ( MalformedTitleException $ex ) {
875  return false;
876  }
877  }
878 
886  public function isLocal() {
887  if ( $this->isExternal() ) {
888  $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
889  if ( $iw ) {
890  return $iw->isLocal();
891  }
892  }
893  return true;
894  }
895 
901  public function isExternal() {
902  return $this->mInterwiki !== '';
903  }
904 
912  public function getInterwiki() {
913  return $this->mInterwiki;
914  }
915 
921  public function wasLocalInterwiki() {
922  return $this->mLocalInterwiki;
923  }
924 
931  public function isTrans() {
932  if ( !$this->isExternal() ) {
933  return false;
934  }
935 
936  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
937  }
938 
944  public function getTransWikiID() {
945  if ( !$this->isExternal() ) {
946  return false;
947  }
948 
949  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
950  }
951 
961  public function getTitleValue() {
962  if ( $this->mTitleValue === null ) {
963  try {
964  $this->mTitleValue = new TitleValue(
965  $this->mNamespace,
966  $this->mDbkeyform,
967  $this->mFragment,
968  $this->mInterwiki
969  );
970  } catch ( InvalidArgumentException $ex ) {
971  wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
972  $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
973  }
974  }
975 
976  return $this->mTitleValue;
977  }
978 
984  public function getText() {
985  return $this->mTextform;
986  }
987 
993  public function getPartialURL() {
994  return $this->mUrlform;
995  }
996 
1002  public function getDBkey() {
1003  return $this->mDbkeyform;
1004  }
1005 
1012  function getUserCaseDBKey() {
1013  if ( !is_null( $this->mUserCaseDBKey ) ) {
1014  return $this->mUserCaseDBKey;
1015  } else {
1016  // If created via makeTitle(), $this->mUserCaseDBKey is not set.
1017  return $this->mDbkeyform;
1018  }
1019  }
1020 
1026  public function getNamespace() {
1027  return $this->mNamespace;
1028  }
1029 
1038  public function getContentModel( $flags = 0 ) {
1039  if ( !$this->mForcedContentModel
1040  && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE )
1041  && $this->getArticleID( $flags )
1042  ) {
1043  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1044  $linkCache->addLinkObj( $this ); # in case we already had an article ID
1045  $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
1046  }
1047 
1048  if ( !$this->mContentModel ) {
1049  $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
1050  }
1051 
1052  return $this->mContentModel;
1053  }
1054 
1061  public function hasContentModel( $id ) {
1062  return $this->getContentModel() == $id;
1063  }
1064 
1076  public function setContentModel( $model ) {
1077  $this->mContentModel = $model;
1078  $this->mForcedContentModel = true;
1079  }
1080 
1086  public function getNsText() {
1087  if ( $this->isExternal() ) {
1088  // This probably shouldn't even happen, except for interwiki transclusion.
1089  // If possible, use the canonical name for the foreign namespace.
1090  $nsText = MediaWikiServices::getInstance()->getNamespaceInfo()->
1091  getCanonicalName( $this->mNamespace );
1092  if ( $nsText !== false ) {
1093  return $nsText;
1094  }
1095  }
1096 
1097  try {
1098  $formatter = self::getTitleFormatter();
1099  return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
1100  } catch ( InvalidArgumentException $ex ) {
1101  wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
1102  return false;
1103  }
1104  }
1105 
1111  public function getSubjectNsText() {
1112  $services = MediaWikiServices::getInstance();
1113  return $services->getContentLanguage()->
1114  getNsText( $services->getNamespaceInfo()->getSubject( $this->mNamespace ) );
1115  }
1116 
1122  public function getTalkNsText() {
1123  $services = MediaWikiServices::getInstance();
1124  return $services->getContentLanguage()->
1125  getNsText( $services->getNamespaceInfo()->getTalk( $this->mNamespace ) );
1126  }
1127 
1136  public function canHaveTalkPage() {
1137  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1138  hasTalkNamespace( $this->mNamespace );
1139  }
1140 
1146  public function canExist() {
1147  return $this->mNamespace >= NS_MAIN;
1148  }
1149 
1155  public function isWatchable() {
1156  return !$this->isExternal() && MediaWikiServices::getInstance()->getNamespaceInfo()->
1157  isWatchable( $this->mNamespace );
1158  }
1159 
1165  public function isSpecialPage() {
1166  return $this->mNamespace == NS_SPECIAL;
1167  }
1168 
1175  public function isSpecial( $name ) {
1176  if ( $this->isSpecialPage() ) {
1177  list( $thisName, /* $subpage */ ) =
1178  MediaWikiServices::getInstance()->getSpecialPageFactory()->
1179  resolveAlias( $this->mDbkeyform );
1180  if ( $name == $thisName ) {
1181  return true;
1182  }
1183  }
1184  return false;
1185  }
1186 
1193  public function fixSpecialName() {
1194  if ( $this->isSpecialPage() ) {
1195  $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
1196  list( $canonicalName, $par ) = $spFactory->resolveAlias( $this->mDbkeyform );
1197  if ( $canonicalName ) {
1198  $localName = $spFactory->getLocalNameFor( $canonicalName, $par );
1199  if ( $localName != $this->mDbkeyform ) {
1200  return self::makeTitle( NS_SPECIAL, $localName );
1201  }
1202  }
1203  }
1204  return $this;
1205  }
1206 
1217  public function inNamespace( $ns ) {
1218  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1219  equals( $this->mNamespace, $ns );
1220  }
1221 
1229  public function inNamespaces( /* ... */ ) {
1230  $namespaces = func_get_args();
1231  if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1232  $namespaces = $namespaces[0];
1233  }
1234 
1235  foreach ( $namespaces as $ns ) {
1236  if ( $this->inNamespace( $ns ) ) {
1237  return true;
1238  }
1239  }
1240 
1241  return false;
1242  }
1243 
1257  public function hasSubjectNamespace( $ns ) {
1258  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1259  subjectEquals( $this->mNamespace, $ns );
1260  }
1261 
1269  public function isContentPage() {
1270  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1271  isContent( $this->mNamespace );
1272  }
1273 
1280  public function isMovable() {
1281  if (
1282  !MediaWikiServices::getInstance()->getNamespaceInfo()->
1283  isMovable( $this->mNamespace ) || $this->isExternal()
1284  ) {
1285  // Interwiki title or immovable namespace. Hooks don't get to override here
1286  return false;
1287  }
1288 
1289  $result = true;
1290  Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1291  return $result;
1292  }
1293 
1304  public function isMainPage() {
1305  return $this->equals( self::newMainPage() );
1306  }
1307 
1313  public function isSubpage() {
1314  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1315  hasSubpages( $this->mNamespace )
1316  ? strpos( $this->getText(), '/' ) !== false
1317  : false;
1318  }
1319 
1325  public function isConversionTable() {
1326  // @todo ConversionTable should become a separate content model.
1327 
1328  return $this->mNamespace == NS_MEDIAWIKI &&
1329  strpos( $this->getText(), 'Conversiontable/' ) === 0;
1330  }
1331 
1337  public function isWikitextPage() {
1338  return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1339  }
1340 
1355  public function isSiteConfigPage() {
1356  return (
1357  $this->isSiteCssConfigPage()
1358  || $this->isSiteJsonConfigPage()
1359  || $this->isSiteJsConfigPage()
1360  );
1361  }
1362 
1369  public function isUserConfigPage() {
1370  return (
1371  $this->isUserCssConfigPage()
1372  || $this->isUserJsonConfigPage()
1373  || $this->isUserJsConfigPage()
1374  );
1375  }
1376 
1383  public function getSkinFromConfigSubpage() {
1384  $subpage = explode( '/', $this->mTextform );
1385  $subpage = $subpage[count( $subpage ) - 1];
1386  $lastdot = strrpos( $subpage, '.' );
1387  if ( $lastdot === false ) {
1388  return $subpage; # Never happens: only called for names ending in '.css'/'.json'/'.js'
1389  }
1390  return substr( $subpage, 0, $lastdot );
1391  }
1392 
1399  public function isUserCssConfigPage() {
1400  return (
1401  NS_USER == $this->mNamespace
1402  && $this->isSubpage()
1403  && $this->hasContentModel( CONTENT_MODEL_CSS )
1404  );
1405  }
1406 
1413  public function isUserJsonConfigPage() {
1414  return (
1415  NS_USER == $this->mNamespace
1416  && $this->isSubpage()
1417  && $this->hasContentModel( CONTENT_MODEL_JSON )
1418  );
1419  }
1420 
1427  public function isUserJsConfigPage() {
1428  return (
1429  NS_USER == $this->mNamespace
1430  && $this->isSubpage()
1432  );
1433  }
1434 
1441  public function isSiteCssConfigPage() {
1442  return (
1443  NS_MEDIAWIKI == $this->mNamespace
1444  && (
1446  // paranoia - a MediaWiki: namespace page with mismatching extension and content
1447  // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1448  || substr( $this->mDbkeyform, -4 ) === '.css'
1449  )
1450  );
1451  }
1452 
1459  public function isSiteJsonConfigPage() {
1460  return (
1461  NS_MEDIAWIKI == $this->mNamespace
1462  && (
1464  // paranoia - a MediaWiki: namespace page with mismatching extension and content
1465  // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1466  || substr( $this->mDbkeyform, -5 ) === '.json'
1467  )
1468  );
1469  }
1470 
1477  public function isSiteJsConfigPage() {
1478  return (
1479  NS_MEDIAWIKI == $this->mNamespace
1480  && (
1482  // paranoia - a MediaWiki: namespace page with mismatching extension and content
1483  // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1484  || substr( $this->mDbkeyform, -3 ) === '.js'
1485  )
1486  );
1487  }
1488 
1495  public function isRawHtmlMessage() {
1496  global $wgRawHtmlMessages;
1497 
1498  if ( !$this->inNamespace( NS_MEDIAWIKI ) ) {
1499  return false;
1500  }
1501  $message = lcfirst( $this->getRootTitle()->getDBkey() );
1502  return in_array( $message, $wgRawHtmlMessages, true );
1503  }
1504 
1510  public function isTalkPage() {
1511  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1512  isTalk( $this->mNamespace );
1513  }
1514 
1521  public function getTalkPage() {
1522  return self::castFromLinkTarget(
1523  MediaWikiServices::getInstance()->getNamespaceInfo()->getTalkPage( $this ) );
1524  }
1525 
1535  public function getTalkPageIfDefined() {
1536  if ( !$this->canHaveTalkPage() ) {
1537  return null;
1538  }
1539 
1540  return $this->getTalkPage();
1541  }
1542 
1550  public function getSubjectPage() {
1551  return self::castFromLinkTarget(
1552  MediaWikiServices::getInstance()->getNamespaceInfo()->getSubjectPage( $this ) );
1553  }
1554 
1564  public function getOtherPage() {
1565  return self::castFromLinkTarget(
1566  MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociatedPage( $this ) );
1567  }
1568 
1574  public function getDefaultNamespace() {
1575  return $this->mDefaultNamespace;
1576  }
1577 
1585  public function getFragment() {
1586  return $this->mFragment;
1587  }
1588 
1595  public function hasFragment() {
1596  return $this->mFragment !== '';
1597  }
1598 
1604  public function getFragmentForURL() {
1605  if ( !$this->hasFragment() ) {
1606  return '';
1607  } elseif ( $this->isExternal() ) {
1608  // Note: If the interwiki is unknown, it's treated as a namespace on the local wiki,
1609  // so we treat it like a local interwiki.
1610  $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1611  if ( $interwiki && !$interwiki->isLocal() ) {
1612  return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->mFragment );
1613  }
1614  }
1615 
1616  return '#' . Sanitizer::escapeIdForLink( $this->mFragment );
1617  }
1618 
1631  public function setFragment( $fragment ) {
1632  $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1633  }
1634 
1642  public function createFragmentTarget( $fragment ) {
1643  return self::makeTitle(
1644  $this->mNamespace,
1645  $this->getText(),
1646  $fragment,
1647  $this->mInterwiki
1648  );
1649  }
1650 
1658  private function prefix( $name ) {
1659  $p = '';
1660  if ( $this->isExternal() ) {
1661  $p = $this->mInterwiki . ':';
1662  }
1663 
1664  if ( $this->mNamespace != 0 ) {
1665  $nsText = $this->getNsText();
1666 
1667  if ( $nsText === false ) {
1668  // See T165149. Awkward, but better than erroneously linking to the main namespace.
1669  $nsText = MediaWikiServices::getInstance()->getContentLanguage()->
1670  getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}";
1671  }
1672 
1673  $p .= $nsText . ':';
1674  }
1675  return $p . $name;
1676  }
1677 
1684  public function getPrefixedDBkey() {
1685  $s = $this->prefix( $this->mDbkeyform );
1686  $s = strtr( $s, ' ', '_' );
1687  return $s;
1688  }
1689 
1696  public function getPrefixedText() {
1697  if ( $this->prefixedText === null ) {
1698  $s = $this->prefix( $this->mTextform );
1699  $s = strtr( $s, '_', ' ' );
1700  $this->prefixedText = $s;
1701  }
1702  return $this->prefixedText;
1703  }
1704 
1710  public function __toString() {
1711  return $this->getPrefixedText();
1712  }
1713 
1720  public function getFullText() {
1721  $text = $this->getPrefixedText();
1722  if ( $this->hasFragment() ) {
1723  $text .= '#' . $this->mFragment;
1724  }
1725  return $text;
1726  }
1727 
1740  public function getRootText() {
1741  if (
1742  !MediaWikiServices::getInstance()->getNamespaceInfo()->
1743  hasSubpages( $this->mNamespace )
1744  ) {
1745  return $this->getText();
1746  }
1747 
1748  return strtok( $this->getText(), '/' );
1749  }
1750 
1763  public function getRootTitle() {
1764  return self::makeTitle( $this->mNamespace, $this->getRootText() );
1765  }
1766 
1778  public function getBaseText() {
1779  $text = $this->getText();
1780  if (
1781  !MediaWikiServices::getInstance()->getNamespaceInfo()->
1782  hasSubpages( $this->mNamespace )
1783  ) {
1784  return $text;
1785  }
1786 
1787  $lastSlashPos = strrpos( $text, '/' );
1788  // Don't discard the real title if there's no subpage involved
1789  if ( $lastSlashPos === false ) {
1790  return $text;
1791  }
1792 
1793  return substr( $text, 0, $lastSlashPos );
1794  }
1795 
1808  public function getBaseTitle() {
1809  return self::makeTitle( $this->mNamespace, $this->getBaseText() );
1810  }
1811 
1823  public function getSubpageText() {
1824  if (
1825  !MediaWikiServices::getInstance()->getNamespaceInfo()->
1826  hasSubpages( $this->mNamespace )
1827  ) {
1828  return $this->mTextform;
1829  }
1830  $parts = explode( '/', $this->mTextform );
1831  return $parts[count( $parts ) - 1];
1832  }
1833 
1847  public function getSubpage( $text ) {
1848  return self::makeTitleSafe( $this->mNamespace, $this->getText() . '/' . $text );
1849  }
1850 
1856  public function getSubpageUrlForm() {
1857  $text = $this->getSubpageText();
1858  $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1859  return $text;
1860  }
1861 
1867  public function getPrefixedURL() {
1868  $s = $this->prefix( $this->mDbkeyform );
1869  $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1870  return $s;
1871  }
1872 
1886  private static function fixUrlQueryArgs( $query, $query2 = false ) {
1887  if ( $query2 !== false ) {
1888  wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1889  "method called with a second parameter is deprecated. Add your " .
1890  "parameter to an array passed as the first parameter.", "1.19" );
1891  }
1892  if ( is_array( $query ) ) {
1893  $query = wfArrayToCgi( $query );
1894  }
1895  if ( $query2 ) {
1896  if ( is_string( $query2 ) ) {
1897  // $query2 is a string, we will consider this to be
1898  // a deprecated $variant argument and add it to the query
1899  $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1900  } else {
1901  $query2 = wfArrayToCgi( $query2 );
1902  }
1903  // If we have $query content add a & to it first
1904  if ( $query ) {
1905  $query .= '&';
1906  }
1907  // Now append the queries together
1908  $query .= $query2;
1909  }
1910  return $query;
1911  }
1912 
1924  public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1925  $query = self::fixUrlQueryArgs( $query, $query2 );
1926 
1927  # Hand off all the decisions on urls to getLocalURL
1928  $url = $this->getLocalURL( $query );
1929 
1930  # Expand the url to make it a full url. Note that getLocalURL has the
1931  # potential to output full urls for a variety of reasons, so we use
1932  # wfExpandUrl instead of simply prepending $wgServer
1933  $url = wfExpandUrl( $url, $proto );
1934 
1935  # Finally, add the fragment.
1936  $url .= $this->getFragmentForURL();
1937  // Avoid PHP 7.1 warning from passing $this by reference
1938  $titleRef = $this;
1939  Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
1940  return $url;
1941  }
1942 
1959  public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1960  $target = $this;
1961  if ( $this->isExternal() ) {
1962  $target = SpecialPage::getTitleFor(
1963  'GoToInterwiki',
1964  $this->getPrefixedDBkey()
1965  );
1966  }
1967  return $target->getFullURL( $query, false, $proto );
1968  }
1969 
1993  public function getLocalURL( $query = '', $query2 = false ) {
1995 
1996  $query = self::fixUrlQueryArgs( $query, $query2 );
1997 
1998  $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1999  if ( $interwiki ) {
2000  $namespace = $this->getNsText();
2001  if ( $namespace != '' ) {
2002  # Can this actually happen? Interwikis shouldn't be parsed.
2003  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
2004  $namespace .= ':';
2005  }
2006  $url = $interwiki->getURL( $namespace . $this->mDbkeyform );
2007  $url = wfAppendQuery( $url, $query );
2008  } else {
2009  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
2010  if ( $query == '' ) {
2011  $url = str_replace( '$1', $dbkey, $wgArticlePath );
2012  // Avoid PHP 7.1 warning from passing $this by reference
2013  $titleRef = $this;
2014  Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
2015  } else {
2017  $url = false;
2018  $matches = [];
2019 
2020  if ( !empty( $wgActionPaths )
2021  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
2022  ) {
2023  $action = urldecode( $matches[2] );
2024  if ( isset( $wgActionPaths[$action] ) ) {
2025  $query = $matches[1];
2026  if ( isset( $matches[4] ) ) {
2027  $query .= $matches[4];
2028  }
2029  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
2030  if ( $query != '' ) {
2031  $url = wfAppendQuery( $url, $query );
2032  }
2033  }
2034  }
2035 
2036  if ( $url === false
2037  && $wgVariantArticlePath
2038  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
2039  && $this->getPageLanguage()->equals(
2040  MediaWikiServices::getInstance()->getContentLanguage() )
2041  && $this->getPageLanguage()->hasVariants()
2042  ) {
2043  $variant = urldecode( $matches[1] );
2044  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
2045  // Only do the variant replacement if the given variant is a valid
2046  // variant for the page's language.
2047  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
2048  $url = str_replace( '$1', $dbkey, $url );
2049  }
2050  }
2051 
2052  if ( $url === false ) {
2053  if ( $query == '-' ) {
2054  $query = '';
2055  }
2056  $url = "{$wgScript}?title={$dbkey}&{$query}";
2057  }
2058  }
2059  // Avoid PHP 7.1 warning from passing $this by reference
2060  $titleRef = $this;
2061  Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
2062 
2063  // @todo FIXME: This causes breakage in various places when we
2064  // actually expected a local URL and end up with dupe prefixes.
2065  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
2066  $url = $wgServer . $url;
2067  }
2068  }
2069  // Avoid PHP 7.1 warning from passing $this by reference
2070  $titleRef = $this;
2071  Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
2072  return $url;
2073  }
2074 
2092  public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
2093  if ( $this->isExternal() || $proto !== false ) {
2094  $ret = $this->getFullURL( $query, $query2, $proto );
2095  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
2096  $ret = $this->getFragmentForURL();
2097  } else {
2098  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
2099  }
2100  return $ret;
2101  }
2102 
2117  public function getInternalURL( $query = '', $query2 = false ) {
2118  global $wgInternalServer, $wgServer;
2119  $query = self::fixUrlQueryArgs( $query, $query2 );
2120  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
2121  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
2122  // Avoid PHP 7.1 warning from passing $this by reference
2123  $titleRef = $this;
2124  Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
2125  return $url;
2126  }
2127 
2141  public function getCanonicalURL( $query = '', $query2 = false ) {
2142  $query = self::fixUrlQueryArgs( $query, $query2 );
2143  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
2144  // Avoid PHP 7.1 warning from passing $this by reference
2145  $titleRef = $this;
2146  Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
2147  return $url;
2148  }
2149 
2155  public function getEditURL() {
2156  if ( $this->isExternal() ) {
2157  return '';
2158  }
2159  $s = $this->getLocalURL( 'action=edit' );
2160 
2161  return $s;
2162  }
2163 
2184  public function quickUserCan( $action, $user = null ) {
2185  return $this->userCan( $action, $user, false );
2186  }
2187 
2203  public function userCan( $action, $user = null, $rigor = PermissionManager::RIGOR_SECURE ) {
2204  if ( !$user instanceof User ) {
2205  global $wgUser;
2206  $user = $wgUser;
2207  }
2208 
2209  // TODO: this is for b/c, eventually will be removed
2210  if ( $rigor === true ) {
2211  $rigor = PermissionManager::RIGOR_SECURE; // b/c
2212  } elseif ( $rigor === false ) {
2213  $rigor = PermissionManager::RIGOR_QUICK; // b/c
2214  }
2215 
2216  return MediaWikiServices::getInstance()->getPermissionManager()
2217  ->userCan( $action, $user, $this, $rigor );
2218  }
2219 
2241  public function getUserPermissionsErrors(
2242  $action, $user, $rigor = PermissionManager::RIGOR_SECURE, $ignoreErrors = []
2243  ) {
2244  // TODO: this is for b/c, eventually will be removed
2245  if ( $rigor === true ) {
2246  $rigor = PermissionManager::RIGOR_SECURE; // b/c
2247  } elseif ( $rigor === false ) {
2248  $rigor = PermissionManager::RIGOR_QUICK; // b/c
2249  }
2250 
2251  return MediaWikiServices::getInstance()->getPermissionManager()
2252  ->getPermissionErrors( $action, $user, $this, $rigor, $ignoreErrors );
2253  }
2254 
2263  private function resultToError( $errors, $result ) {
2264  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2265  // A single array representing an error
2266  $errors[] = $result;
2267  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2268  // A nested array representing multiple errors
2269  $errors = array_merge( $errors, $result );
2270  } elseif ( $result !== '' && is_string( $result ) ) {
2271  // A string representing a message-id
2272  $errors[] = [ $result ];
2273  } elseif ( $result instanceof MessageSpecifier ) {
2274  // A message specifier representing an error
2275  $errors[] = [ $result ];
2276  } elseif ( $result === false ) {
2277  // a generic "We don't want them to do that"
2278  $errors[] = [ 'badaccess-group0' ];
2279  }
2280  return $errors;
2281  }
2282 
2290  public static function getFilteredRestrictionTypes( $exists = true ) {
2291  global $wgRestrictionTypes;
2292  $types = $wgRestrictionTypes;
2293  if ( $exists ) {
2294  # Remove the create restriction for existing titles
2295  $types = array_diff( $types, [ 'create' ] );
2296  } else {
2297  # Only the create and upload restrictions apply to non-existing titles
2298  $types = array_intersect( $types, [ 'create', 'upload' ] );
2299  }
2300  return $types;
2301  }
2302 
2308  public function getRestrictionTypes() {
2309  if ( $this->isSpecialPage() ) {
2310  return [];
2311  }
2312 
2313  $types = self::getFilteredRestrictionTypes( $this->exists() );
2314 
2315  if ( $this->mNamespace != NS_FILE ) {
2316  # Remove the upload restriction for non-file titles
2317  $types = array_diff( $types, [ 'upload' ] );
2318  }
2319 
2320  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2321 
2322  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2323  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2324 
2325  return $types;
2326  }
2327 
2335  public function getTitleProtection() {
2336  $protection = $this->getTitleProtectionInternal();
2337  if ( $protection ) {
2338  if ( $protection['permission'] == 'sysop' ) {
2339  $protection['permission'] = 'editprotected'; // B/C
2340  }
2341  if ( $protection['permission'] == 'autoconfirmed' ) {
2342  $protection['permission'] = 'editsemiprotected'; // B/C
2343  }
2344  }
2345  return $protection;
2346  }
2347 
2358  protected function getTitleProtectionInternal() {
2359  // Can't protect pages in special namespaces
2360  if ( $this->mNamespace < 0 ) {
2361  return false;
2362  }
2363 
2364  // Can't protect pages that exist.
2365  if ( $this->exists() ) {
2366  return false;
2367  }
2368 
2369  if ( $this->mTitleProtection === null ) {
2370  $dbr = wfGetDB( DB_REPLICA );
2371  $commentStore = CommentStore::getStore();
2372  $commentQuery = $commentStore->getJoin( 'pt_reason' );
2373  $res = $dbr->select(
2374  [ 'protected_titles' ] + $commentQuery['tables'],
2375  [
2376  'user' => 'pt_user',
2377  'expiry' => 'pt_expiry',
2378  'permission' => 'pt_create_perm'
2379  ] + $commentQuery['fields'],
2380  [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2381  __METHOD__,
2382  [],
2383  $commentQuery['joins']
2384  );
2385 
2386  // fetchRow returns false if there are no rows.
2387  $row = $dbr->fetchRow( $res );
2388  if ( $row ) {
2389  $this->mTitleProtection = [
2390  'user' => $row['user'],
2391  'expiry' => $dbr->decodeExpiry( $row['expiry'] ),
2392  'permission' => $row['permission'],
2393  'reason' => $commentStore->getComment( 'pt_reason', $row )->text,
2394  ];
2395  } else {
2396  $this->mTitleProtection = false;
2397  }
2398  }
2399  return $this->mTitleProtection;
2400  }
2401 
2405  public function deleteTitleProtection() {
2406  $dbw = wfGetDB( DB_MASTER );
2407 
2408  $dbw->delete(
2409  'protected_titles',
2410  [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2411  __METHOD__
2412  );
2413  $this->mTitleProtection = false;
2414  }
2415 
2423  public function isSemiProtected( $action = 'edit' ) {
2425 
2426  $restrictions = $this->getRestrictions( $action );
2428  if ( !$restrictions || !$semi ) {
2429  // Not protected, or all protection is full protection
2430  return false;
2431  }
2432 
2433  // Remap autoconfirmed to editsemiprotected for BC
2434  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2435  $semi[$key] = 'editsemiprotected';
2436  }
2437  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2438  $restrictions[$key] = 'editsemiprotected';
2439  }
2440 
2441  return !array_diff( $restrictions, $semi );
2442  }
2443 
2451  public function isProtected( $action = '' ) {
2452  global $wgRestrictionLevels;
2453 
2454  $restrictionTypes = $this->getRestrictionTypes();
2455 
2456  # Special pages have inherent protection
2457  if ( $this->isSpecialPage() ) {
2458  return true;
2459  }
2460 
2461  # Check regular protection levels
2462  foreach ( $restrictionTypes as $type ) {
2463  if ( $action == $type || $action == '' ) {
2464  $r = $this->getRestrictions( $type );
2465  foreach ( $wgRestrictionLevels as $level ) {
2466  if ( in_array( $level, $r ) && $level != '' ) {
2467  return true;
2468  }
2469  }
2470  }
2471  }
2472 
2473  return false;
2474  }
2475 
2483  public function isNamespaceProtected( User $user ) {
2484  global $wgNamespaceProtection;
2485 
2486  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2487  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2488  if ( $right != '' && !$user->isAllowed( $right ) ) {
2489  return true;
2490  }
2491  }
2492  }
2493  return false;
2494  }
2495 
2501  public function isCascadeProtected() {
2502  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2503  return ( $sources > 0 );
2504  }
2505 
2515  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2516  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2517  }
2518 
2532  public function getCascadeProtectionSources( $getPages = true ) {
2533  $pagerestrictions = [];
2534 
2535  if ( $this->mCascadeSources !== null && $getPages ) {
2537  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2538  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2539  }
2540 
2541  $dbr = wfGetDB( DB_REPLICA );
2542 
2543  if ( $this->mNamespace == NS_FILE ) {
2544  $tables = [ 'imagelinks', 'page_restrictions' ];
2545  $where_clauses = [
2546  'il_to' => $this->mDbkeyform,
2547  'il_from=pr_page',
2548  'pr_cascade' => 1
2549  ];
2550  } else {
2551  $tables = [ 'templatelinks', 'page_restrictions' ];
2552  $where_clauses = [
2553  'tl_namespace' => $this->mNamespace,
2554  'tl_title' => $this->mDbkeyform,
2555  'tl_from=pr_page',
2556  'pr_cascade' => 1
2557  ];
2558  }
2559 
2560  if ( $getPages ) {
2561  $cols = [ 'pr_page', 'page_namespace', 'page_title',
2562  'pr_expiry', 'pr_type', 'pr_level' ];
2563  $where_clauses[] = 'page_id=pr_page';
2564  $tables[] = 'page';
2565  } else {
2566  $cols = [ 'pr_expiry' ];
2567  }
2568 
2569  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2570 
2571  $sources = $getPages ? [] : false;
2572  $now = wfTimestampNow();
2573 
2574  foreach ( $res as $row ) {
2575  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2576  if ( $expiry > $now ) {
2577  if ( $getPages ) {
2578  $page_id = $row->pr_page;
2579  $page_ns = $row->page_namespace;
2580  $page_title = $row->page_title;
2581  $sources[$page_id] = self::makeTitle( $page_ns, $page_title );
2582  # Add groups needed for each restriction type if its not already there
2583  # Make sure this restriction type still exists
2584 
2585  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2586  $pagerestrictions[$row->pr_type] = [];
2587  }
2588 
2589  if (
2590  isset( $pagerestrictions[$row->pr_type] )
2591  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2592  ) {
2593  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2594  }
2595  } else {
2596  $sources = true;
2597  }
2598  }
2599  }
2600 
2601  if ( $getPages ) {
2602  $this->mCascadeSources = $sources;
2603  $this->mCascadingRestrictions = $pagerestrictions;
2604  } else {
2605  $this->mHasCascadingRestrictions = $sources;
2606  }
2607 
2608  return [ $sources, $pagerestrictions ];
2609  }
2610 
2618  public function areRestrictionsLoaded() {
2620  }
2621 
2631  public function getRestrictions( $action ) {
2632  if ( !$this->mRestrictionsLoaded ) {
2633  $this->loadRestrictions();
2634  }
2635  return $this->mRestrictions[$action] ?? [];
2636  }
2637 
2645  public function getAllRestrictions() {
2646  if ( !$this->mRestrictionsLoaded ) {
2647  $this->loadRestrictions();
2648  }
2649  return $this->mRestrictions;
2650  }
2651 
2659  public function getRestrictionExpiry( $action ) {
2660  if ( !$this->mRestrictionsLoaded ) {
2661  $this->loadRestrictions();
2662  }
2663  return $this->mRestrictionsExpiry[$action] ?? false;
2664  }
2665 
2672  if ( !$this->mRestrictionsLoaded ) {
2673  $this->loadRestrictions();
2674  }
2675 
2677  }
2678 
2690  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2691  // This function will only read rows from a table that we migrated away
2692  // from before adding READ_LATEST support to loadRestrictions, so we
2693  // don't need to support reading from DB_MASTER here.
2694  $dbr = wfGetDB( DB_REPLICA );
2695 
2696  $restrictionTypes = $this->getRestrictionTypes();
2697 
2698  foreach ( $restrictionTypes as $type ) {
2699  $this->mRestrictions[$type] = [];
2700  $this->mRestrictionsExpiry[$type] = 'infinity';
2701  }
2702 
2703  $this->mCascadeRestriction = false;
2704 
2705  # Backwards-compatibility: also load the restrictions from the page record (old format).
2706  if ( $oldFashionedRestrictions !== null ) {
2707  $this->mOldRestrictions = $oldFashionedRestrictions;
2708  }
2709 
2710  if ( $this->mOldRestrictions === false ) {
2711  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
2712  $linkCache->addLinkObj( $this ); # in case we already had an article ID
2713  $this->mOldRestrictions = $linkCache->getGoodLinkFieldObj( $this, 'restrictions' );
2714  }
2715 
2716  if ( $this->mOldRestrictions != '' ) {
2717  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2718  $temp = explode( '=', trim( $restrict ) );
2719  if ( count( $temp ) == 1 ) {
2720  // old old format should be treated as edit/move restriction
2721  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2722  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2723  } else {
2724  $restriction = trim( $temp[1] );
2725  if ( $restriction != '' ) { // some old entries are empty
2726  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2727  }
2728  }
2729  }
2730  }
2731 
2732  if ( count( $rows ) ) {
2733  # Current system - load second to make them override.
2734  $now = wfTimestampNow();
2735 
2736  # Cycle through all the restrictions.
2737  foreach ( $rows as $row ) {
2738  // Don't take care of restrictions types that aren't allowed
2739  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2740  continue;
2741  }
2742 
2743  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2744 
2745  // Only apply the restrictions if they haven't expired!
2746  if ( !$expiry || $expiry > $now ) {
2747  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2748  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2749 
2750  $this->mCascadeRestriction |= $row->pr_cascade;
2751  }
2752  }
2753  }
2754 
2755  $this->mRestrictionsLoaded = true;
2756  }
2757 
2768  public function loadRestrictions( $oldFashionedRestrictions = null, $flags = 0 ) {
2769  $readLatest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
2770  if ( $this->mRestrictionsLoaded && !$readLatest ) {
2771  return;
2772  }
2773 
2774  // TODO: should probably pass $flags into getArticleID, but it seems hacky
2775  // to mix READ_LATEST and GAID_FOR_UPDATE, even if they have the same value.
2776  // Maybe deprecate GAID_FOR_UPDATE now that we implement IDBAccessObject?
2777  $id = $this->getArticleID();
2778  if ( $id ) {
2779  $fname = __METHOD__;
2780  $loadRestrictionsFromDb = function ( IDatabase $dbr ) use ( $fname, $id ) {
2781  return iterator_to_array(
2782  $dbr->select(
2783  'page_restrictions',
2784  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
2785  [ 'pr_page' => $id ],
2786  $fname
2787  )
2788  );
2789  };
2790 
2791  if ( $readLatest ) {
2792  $dbr = wfGetDB( DB_MASTER );
2793  $rows = $loadRestrictionsFromDb( $dbr );
2794  } else {
2795  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2796  $rows = $cache->getWithSetCallback(
2797  // Page protections always leave a new null revision
2798  $cache->makeKey( 'page-restrictions', 'v1', $id, $this->getLatestRevID() ),
2799  $cache::TTL_DAY,
2800  function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
2801  $dbr = wfGetDB( DB_REPLICA );
2802 
2803  $setOpts += Database::getCacheSetOptions( $dbr );
2804  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2805  if ( $lb->hasOrMadeRecentMasterChanges() ) {
2806  // @TODO: cleanup Title cache and caller assumption mess in general
2808  }
2809 
2810  return $loadRestrictionsFromDb( $dbr );
2811  }
2812  );
2813  }
2814 
2815  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2816  } else {
2817  $title_protection = $this->getTitleProtectionInternal();
2818 
2819  if ( $title_protection ) {
2820  $now = wfTimestampNow();
2821  $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
2822 
2823  if ( !$expiry || $expiry > $now ) {
2824  // Apply the restrictions
2825  $this->mRestrictionsExpiry['create'] = $expiry;
2826  $this->mRestrictions['create'] =
2827  explode( ',', trim( $title_protection['permission'] ) );
2828  } else { // Get rid of the old restrictions
2829  $this->mTitleProtection = false;
2830  }
2831  } else {
2832  $this->mRestrictionsExpiry['create'] = 'infinity';
2833  }
2834  $this->mRestrictionsLoaded = true;
2835  }
2836  }
2837 
2842  public function flushRestrictions() {
2843  $this->mRestrictionsLoaded = false;
2844  $this->mTitleProtection = null;
2845  }
2846 
2852  static function purgeExpiredRestrictions() {
2853  if ( wfReadOnly() ) {
2854  return;
2855  }
2856 
2858  wfGetDB( DB_MASTER ),
2859  __METHOD__,
2860  function ( IDatabase $dbw, $fname ) {
2861  $config = MediaWikiServices::getInstance()->getMainConfig();
2862  $ids = $dbw->selectFieldValues(
2863  'page_restrictions',
2864  'pr_id',
2865  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
2866  $fname,
2867  [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
2868  );
2869  if ( $ids ) {
2870  $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
2871  }
2872  }
2873  ) );
2874 
2876  wfGetDB( DB_MASTER ),
2877  __METHOD__,
2878  function ( IDatabase $dbw, $fname ) {
2879  $dbw->delete(
2880  'protected_titles',
2881  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
2882  $fname
2883  );
2884  }
2885  ) );
2886  }
2887 
2893  public function hasSubpages() {
2894  if (
2895  !MediaWikiServices::getInstance()->getNamespaceInfo()->
2896  hasSubpages( $this->mNamespace )
2897  ) {
2898  # Duh
2899  return false;
2900  }
2901 
2902  # We dynamically add a member variable for the purpose of this method
2903  # alone to cache the result. There's no point in having it hanging
2904  # around uninitialized in every Title object; therefore we only add it
2905  # if needed and don't declare it statically.
2906  if ( $this->mHasSubpages === null ) {
2907  $this->mHasSubpages = false;
2908  $subpages = $this->getSubpages( 1 );
2909  if ( $subpages instanceof TitleArray ) {
2910  $this->mHasSubpages = (bool)$subpages->count();
2911  }
2912  }
2913 
2914  return $this->mHasSubpages;
2915  }
2916 
2924  public function getSubpages( $limit = -1 ) {
2925  if (
2926  !MediaWikiServices::getInstance()->getNamespaceInfo()->
2927  hasSubpages( $this->mNamespace )
2928  ) {
2929  return [];
2930  }
2931 
2932  $dbr = wfGetDB( DB_REPLICA );
2933  $conds['page_namespace'] = $this->mNamespace;
2934  $conds[] = 'page_title ' . $dbr->buildLike( $this->mDbkeyform . '/', $dbr->anyString() );
2935  $options = [];
2936  if ( $limit > -1 ) {
2937  $options['LIMIT'] = $limit;
2938  }
2940  $dbr->select( 'page',
2941  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
2942  $conds,
2943  __METHOD__,
2944  $options
2945  )
2946  );
2947  }
2948 
2954  public function isDeleted() {
2955  if ( $this->mNamespace < 0 ) {
2956  $n = 0;
2957  } else {
2958  $dbr = wfGetDB( DB_REPLICA );
2959 
2960  $n = $dbr->selectField( 'archive', 'COUNT(*)',
2961  [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
2962  __METHOD__
2963  );
2964  if ( $this->mNamespace == NS_FILE ) {
2965  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
2966  [ 'fa_name' => $this->mDbkeyform ],
2967  __METHOD__
2968  );
2969  }
2970  }
2971  return (int)$n;
2972  }
2973 
2979  public function isDeletedQuick() {
2980  if ( $this->mNamespace < 0 ) {
2981  return false;
2982  }
2983  $dbr = wfGetDB( DB_REPLICA );
2984  $deleted = (bool)$dbr->selectField( 'archive', '1',
2985  [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
2986  __METHOD__
2987  );
2988  if ( !$deleted && $this->mNamespace == NS_FILE ) {
2989  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
2990  [ 'fa_name' => $this->mDbkeyform ],
2991  __METHOD__
2992  );
2993  }
2994  return $deleted;
2995  }
2996 
3005  public function getArticleID( $flags = 0 ) {
3006  if ( $this->mNamespace < 0 ) {
3007  $this->mArticleID = 0;
3008  return $this->mArticleID;
3009  }
3010  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3011  if ( $flags & self::GAID_FOR_UPDATE ) {
3012  $oldUpdate = $linkCache->forUpdate( true );
3013  $linkCache->clearLink( $this );
3014  $this->mArticleID = $linkCache->addLinkObj( $this );
3015  $linkCache->forUpdate( $oldUpdate );
3016  } elseif ( $this->mArticleID == -1 ) {
3017  $this->mArticleID = $linkCache->addLinkObj( $this );
3018  }
3019  return $this->mArticleID;
3020  }
3021 
3029  public function isRedirect( $flags = 0 ) {
3030  if ( !is_null( $this->mRedirect ) ) {
3031  return $this->mRedirect;
3032  }
3033  if ( !$this->getArticleID( $flags ) ) {
3034  $this->mRedirect = false;
3035  return $this->mRedirect;
3036  }
3037 
3038  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3039  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3040  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3041  if ( $cached === null ) {
3042  # Trust LinkCache's state over our own
3043  # LinkCache is telling us that the page doesn't exist, despite there being cached
3044  # data relating to an existing page in $this->mArticleID. Updaters should clear
3045  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3046  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3047  # LinkCache to refresh its data from the master.
3048  $this->mRedirect = false;
3049  return $this->mRedirect;
3050  }
3051 
3052  $this->mRedirect = (bool)$cached;
3053 
3054  return $this->mRedirect;
3055  }
3056 
3064  public function getLength( $flags = 0 ) {
3065  if ( $this->mLength != -1 ) {
3066  return $this->mLength;
3067  }
3068  if ( !$this->getArticleID( $flags ) ) {
3069  $this->mLength = 0;
3070  return $this->mLength;
3071  }
3072  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3073  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3074  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3075  if ( $cached === null ) {
3076  # Trust LinkCache's state over our own, as for isRedirect()
3077  $this->mLength = 0;
3078  return $this->mLength;
3079  }
3080 
3081  $this->mLength = intval( $cached );
3082 
3083  return $this->mLength;
3084  }
3085 
3092  public function getLatestRevID( $flags = 0 ) {
3093  if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3094  return intval( $this->mLatestID );
3095  }
3096  if ( !$this->getArticleID( $flags ) ) {
3097  $this->mLatestID = 0;
3098  return $this->mLatestID;
3099  }
3100  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3101  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3102  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3103  if ( $cached === null ) {
3104  # Trust LinkCache's state over our own, as for isRedirect()
3105  $this->mLatestID = 0;
3106  return $this->mLatestID;
3107  }
3108 
3109  $this->mLatestID = intval( $cached );
3110 
3111  return $this->mLatestID;
3112  }
3113 
3124  public function resetArticleID( $newid ) {
3125  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3126  $linkCache->clearLink( $this );
3127 
3128  if ( $newid === false ) {
3129  $this->mArticleID = -1;
3130  } else {
3131  $this->mArticleID = intval( $newid );
3132  }
3133  $this->mRestrictionsLoaded = false;
3134  $this->mRestrictions = [];
3135  $this->mOldRestrictions = false;
3136  $this->mRedirect = null;
3137  $this->mLength = -1;
3138  $this->mLatestID = false;
3139  $this->mContentModel = false;
3140  $this->mEstimateRevisions = null;
3141  $this->mPageLanguage = false;
3142  $this->mDbPageLanguage = false;
3143  $this->mIsBigDeletion = null;
3144  }
3145 
3146  public static function clearCaches() {
3147  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3148  $linkCache->clear();
3149 
3150  $titleCache = self::getTitleCache();
3151  $titleCache->clear();
3152  }
3153 
3161  public static function capitalize( $text, $ns = NS_MAIN ) {
3162  $services = MediaWikiServices::getInstance();
3163  if ( $services->getNamespaceInfo()->isCapitalized( $ns ) ) {
3164  return MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $text );
3165  } else {
3166  return $text;
3167  }
3168  }
3169 
3182  private function secureAndSplit() {
3183  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3184  // the parsing code with Title, while avoiding massive refactoring.
3185  // @todo: get rid of secureAndSplit, refactor parsing code.
3186  // @note: getTitleParser() returns a TitleParser implementation which does not have a
3187  // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3189  $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3190  // MalformedTitleException can be thrown here
3191  $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace );
3192 
3193  # Fill fields
3194  $this->setFragment( '#' . $parts['fragment'] );
3195  $this->mInterwiki = $parts['interwiki'];
3196  $this->mLocalInterwiki = $parts['local_interwiki'];
3197  $this->mNamespace = $parts['namespace'];
3198  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3199 
3200  $this->mDbkeyform = $parts['dbkey'];
3201  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3202  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3203 
3204  # We already know that some pages won't be in the database!
3205  if ( $this->isExternal() || $this->isSpecialPage() ) {
3206  $this->mArticleID = 0;
3207  }
3208 
3209  return true;
3210  }
3211 
3224  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3225  if ( count( $options ) > 0 ) {
3226  $db = wfGetDB( DB_MASTER );
3227  } else {
3228  $db = wfGetDB( DB_REPLICA );
3229  }
3230 
3231  $res = $db->select(
3232  [ 'page', $table ],
3233  self::getSelectFields(),
3234  [
3235  "{$prefix}_from=page_id",
3236  "{$prefix}_namespace" => $this->mNamespace,
3237  "{$prefix}_title" => $this->mDbkeyform ],
3238  __METHOD__,
3239  $options
3240  );
3241 
3242  $retVal = [];
3243  if ( $res->numRows() ) {
3244  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3245  foreach ( $res as $row ) {
3246  $titleObj = self::makeTitle( $row->page_namespace, $row->page_title );
3247  if ( $titleObj ) {
3248  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3249  $retVal[] = $titleObj;
3250  }
3251  }
3252  }
3253  return $retVal;
3254  }
3255 
3266  public function getTemplateLinksTo( $options = [] ) {
3267  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3268  }
3269 
3282  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3283  $id = $this->getArticleID();
3284 
3285  # If the page doesn't exist; there can't be any link from this page
3286  if ( !$id ) {
3287  return [];
3288  }
3289 
3290  $db = wfGetDB( DB_REPLICA );
3291 
3292  $blNamespace = "{$prefix}_namespace";
3293  $blTitle = "{$prefix}_title";
3294 
3295  $pageQuery = WikiPage::getQueryInfo();
3296  $res = $db->select(
3297  [ $table, 'nestpage' => $pageQuery['tables'] ],
3298  array_merge(
3299  [ $blNamespace, $blTitle ],
3300  $pageQuery['fields']
3301  ),
3302  [ "{$prefix}_from" => $id ],
3303  __METHOD__,
3304  $options,
3305  [ 'nestpage' => [
3306  'LEFT JOIN',
3307  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3308  ] ] + $pageQuery['joins']
3309  );
3310 
3311  $retVal = [];
3312  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3313  foreach ( $res as $row ) {
3314  if ( $row->page_id ) {
3315  $titleObj = self::newFromRow( $row );
3316  } else {
3317  $titleObj = self::makeTitle( $row->$blNamespace, $row->$blTitle );
3318  $linkCache->addBadLinkObj( $titleObj );
3319  }
3320  $retVal[] = $titleObj;
3321  }
3322 
3323  return $retVal;
3324  }
3325 
3336  public function getTemplateLinksFrom( $options = [] ) {
3337  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3338  }
3339 
3348  public function getBrokenLinksFrom() {
3349  if ( $this->getArticleID() == 0 ) {
3350  # All links from article ID 0 are false positives
3351  return [];
3352  }
3353 
3354  $dbr = wfGetDB( DB_REPLICA );
3355  $res = $dbr->select(
3356  [ 'page', 'pagelinks' ],
3357  [ 'pl_namespace', 'pl_title' ],
3358  [
3359  'pl_from' => $this->getArticleID(),
3360  'page_namespace IS NULL'
3361  ],
3362  __METHOD__, [],
3363  [
3364  'page' => [
3365  'LEFT JOIN',
3366  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3367  ]
3368  ]
3369  );
3370 
3371  $retVal = [];
3372  foreach ( $res as $row ) {
3373  $retVal[] = self::makeTitle( $row->pl_namespace, $row->pl_title );
3374  }
3375  return $retVal;
3376  }
3377 
3384  public function getCdnUrls() {
3385  $urls = [
3386  $this->getInternalURL(),
3387  $this->getInternalURL( 'action=history' )
3388  ];
3389 
3390  $pageLang = $this->getPageLanguage();
3391  if ( $pageLang->hasVariants() ) {
3392  $variants = $pageLang->getVariants();
3393  foreach ( $variants as $vCode ) {
3394  $urls[] = $this->getInternalURL( $vCode );
3395  }
3396  }
3397 
3398  // If we are looking at a css/js user subpage, purge the action=raw.
3399  if ( $this->isUserJsConfigPage() ) {
3400  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3401  } elseif ( $this->isUserJsonConfigPage() ) {
3402  $urls[] = $this->getInternalURL( 'action=raw&ctype=application/json' );
3403  } elseif ( $this->isUserCssConfigPage() ) {
3404  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3405  }
3406 
3407  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3408  return $urls;
3409  }
3410 
3414  public function purgeSquid() {
3416  new CdnCacheUpdate( $this->getCdnUrls() ),
3418  );
3419  }
3420 
3431  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3432  wfDeprecated( __METHOD__, '1.25' );
3433 
3434  global $wgUser;
3435 
3436  if ( !( $nt instanceof Title ) ) {
3437  // Normally we'd add this to $errors, but we'll get
3438  // lots of syntax errors if $nt is not an object
3439  return [ [ 'badtitletext' ] ];
3440  }
3441 
3442  $mp = new MovePage( $this, $nt );
3443  $errors = $mp->isValidMove()->getErrorsArray();
3444  if ( $auth ) {
3445  $errors = wfMergeErrorArrays(
3446  $errors,
3447  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3448  );
3449  }
3450 
3451  return $errors ?: true;
3452  }
3453 
3467  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true,
3468  array $changeTags = []
3469  ) {
3470  wfDeprecated( __METHOD__, '1.25' );
3471 
3472  global $wgUser;
3473 
3474  $mp = new MovePage( $this, $nt );
3475  $method = $auth ? 'moveIfAllowed' : 'move';
3476  $status = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
3477  if ( $status->isOK() ) {
3478  return true;
3479  } else {
3480  return $status->getErrorsArray();
3481  }
3482  }
3483 
3499  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true,
3500  array $changeTags = []
3501  ) {
3502  wfDeprecated( __METHOD__, '1.34' );
3503 
3504  global $wgUser;
3505 
3506  $mp = new MovePage( $this, $nt );
3507  $method = $auth ? 'moveSubpagesIfAllowed' : 'moveSubpages';
3508  $result = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
3509 
3510  if ( !$result->isOk() ) {
3511  return $result->getErrorsArray();
3512  }
3513 
3514  $retval = [];
3515  foreach ( $result->getValue() as $key => $status ) {
3516  if ( $status->isOK() ) {
3517  $retval[$key] = $status->getValue();
3518  } else {
3519  $retval[$key] = $status->getErrorsArray();
3520  }
3521  }
3522  return $retval;
3523  }
3524 
3531  public function isSingleRevRedirect() {
3532  global $wgContentHandlerUseDB;
3533 
3534  $dbw = wfGetDB( DB_MASTER );
3535 
3536  # Is it a redirect?
3537  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3538  if ( $wgContentHandlerUseDB ) {
3539  $fields[] = 'page_content_model';
3540  }
3541 
3542  $row = $dbw->selectRow( 'page',
3543  $fields,
3544  $this->pageCond(),
3545  __METHOD__,
3546  [ 'FOR UPDATE' ]
3547  );
3548  # Cache some fields we may want
3549  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3550  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3551  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3552  $this->mContentModel = $row && isset( $row->page_content_model )
3553  ? strval( $row->page_content_model )
3554  : false;
3555 
3556  if ( !$this->mRedirect ) {
3557  return false;
3558  }
3559  # Does the article have a history?
3560  $row = $dbw->selectField( [ 'page', 'revision' ],
3561  'rev_id',
3562  [ 'page_namespace' => $this->mNamespace,
3563  'page_title' => $this->mDbkeyform,
3564  'page_id=rev_page',
3565  'page_latest != rev_id'
3566  ],
3567  __METHOD__,
3568  [ 'FOR UPDATE' ]
3569  );
3570  # Return true if there was no history
3571  return ( $row === false );
3572  }
3573 
3582  public function isValidMoveTarget( $nt ) {
3583  wfDeprecated( __METHOD__, '1.25' );
3584 
3585  # Is it an existing file?
3586  if ( $nt->getNamespace() == NS_FILE ) {
3587  $file = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
3588  ->newFile( $nt );
3589  $file->load( File::READ_LATEST );
3590  if ( $file->exists() ) {
3591  wfDebug( __METHOD__ . ": file exists\n" );
3592  return false;
3593  }
3594  }
3595  # Is it a redirect with no history?
3596  if ( !$nt->isSingleRevRedirect() ) {
3597  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3598  return false;
3599  }
3600  # Get the article text
3601  $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
3602  if ( !is_object( $rev ) ) {
3603  return false;
3604  }
3605  $content = $rev->getContent();
3606  # Does the redirect point to the source?
3607  # Or is it a broken self-redirect, usually caused by namespace collisions?
3608  $redirTitle = $content ? $content->getRedirectTarget() : null;
3609 
3610  if ( $redirTitle ) {
3611  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3612  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3613  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3614  return false;
3615  } else {
3616  return true;
3617  }
3618  } else {
3619  # Fail safe (not a redirect after all. strange.)
3620  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3621  " is a redirect, but it doesn't contain a valid redirect.\n" );
3622  return false;
3623  }
3624  }
3625 
3633  public function getParentCategories() {
3634  $data = [];
3635 
3636  $titleKey = $this->getArticleID();
3637 
3638  if ( $titleKey === 0 ) {
3639  return $data;
3640  }
3641 
3642  $dbr = wfGetDB( DB_REPLICA );
3643 
3644  $res = $dbr->select(
3645  'categorylinks',
3646  'cl_to',
3647  [ 'cl_from' => $titleKey ],
3648  __METHOD__
3649  );
3650 
3651  if ( $res->numRows() > 0 ) {
3652  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3653  foreach ( $res as $row ) {
3654  // $data[] = Title::newFromText( $contLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3655  $data[$contLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] =
3656  $this->getFullText();
3657  }
3658  }
3659  return $data;
3660  }
3661 
3668  public function getParentCategoryTree( $children = [] ) {
3669  $stack = [];
3670  $parents = $this->getParentCategories();
3671 
3672  if ( $parents ) {
3673  foreach ( $parents as $parent => $current ) {
3674  if ( array_key_exists( $parent, $children ) ) {
3675  # Circular reference
3676  $stack[$parent] = [];
3677  } else {
3678  $nt = self::newFromText( $parent );
3679  if ( $nt ) {
3680  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3681  }
3682  }
3683  }
3684  }
3685 
3686  return $stack;
3687  }
3688 
3695  public function pageCond() {
3696  if ( $this->mArticleID > 0 ) {
3697  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3698  return [ 'page_id' => $this->mArticleID ];
3699  } else {
3700  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3701  }
3702  }
3703 
3711  private function getRelativeRevisionID( $revId, $flags, $dir ) {
3712  $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3713  $rlFlags = $flags === self::GAID_FOR_UPDATE ? IDBAccessObject::READ_LATEST : 0;
3714  $rev = $rl->getRevisionById( $revId, $rlFlags );
3715  if ( !$rev ) {
3716  return false;
3717  }
3718  $oldRev = $dir === 'next'
3719  ? $rl->getNextRevision( $rev, $rlFlags )
3720  : $rl->getPreviousRevision( $rev, $rlFlags );
3721  if ( !$oldRev ) {
3722  return false;
3723  }
3724  return $oldRev->getId();
3725  }
3726 
3735  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3736  return $this->getRelativeRevisionID( $revId, $flags, 'prev' );
3737  }
3738 
3747  public function getNextRevisionID( $revId, $flags = 0 ) {
3748  return $this->getRelativeRevisionID( $revId, $flags, 'next' );
3749  }
3750 
3757  public function getFirstRevision( $flags = 0 ) {
3758  $pageId = $this->getArticleID( $flags );
3759  if ( $pageId ) {
3760  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
3762  $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'],
3763  [ 'rev_page' => $pageId ],
3764  __METHOD__,
3765  [
3766  'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
3767  'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
3768  ],
3769  $revQuery['joins']
3770  );
3771  if ( $row ) {
3772  return new Revision( $row, 0, $this );
3773  }
3774  }
3775  return null;
3776  }
3777 
3784  public function getEarliestRevTime( $flags = 0 ) {
3785  $rev = $this->getFirstRevision( $flags );
3786  return $rev ? $rev->getTimestamp() : null;
3787  }
3788 
3794  public function isNewPage() {
3795  $dbr = wfGetDB( DB_REPLICA );
3796  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
3797  }
3798 
3804  public function isBigDeletion() {
3805  global $wgDeleteRevisionsLimit;
3806 
3807  if ( !$wgDeleteRevisionsLimit ) {
3808  return false;
3809  }
3810 
3811  if ( $this->mIsBigDeletion === null ) {
3812  $dbr = wfGetDB( DB_REPLICA );
3813 
3814  $revCount = $dbr->selectRowCount(
3815  'revision',
3816  '1',
3817  [ 'rev_page' => $this->getArticleID() ],
3818  __METHOD__,
3819  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
3820  );
3821 
3822  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
3823  }
3824 
3825  return $this->mIsBigDeletion;
3826  }
3827 
3833  public function estimateRevisionCount() {
3834  if ( !$this->exists() ) {
3835  return 0;
3836  }
3837 
3838  if ( $this->mEstimateRevisions === null ) {
3839  $dbr = wfGetDB( DB_REPLICA );
3840  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
3841  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
3842  }
3843 
3845  }
3846 
3856  public function countRevisionsBetween( $old, $new, $max = null ) {
3857  if ( !( $old instanceof Revision ) ) {
3858  $old = Revision::newFromTitle( $this, (int)$old );
3859  }
3860  if ( !( $new instanceof Revision ) ) {
3861  $new = Revision::newFromTitle( $this, (int)$new );
3862  }
3863  if ( !$old || !$new ) {
3864  return 0; // nothing to compare
3865  }
3866  $dbr = wfGetDB( DB_REPLICA );
3867  $conds = [
3868  'rev_page' => $this->getArticleID(),
3869  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
3870  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
3871  ];
3872  if ( $max !== null ) {
3873  return $dbr->selectRowCount( 'revision', '1',
3874  $conds,
3875  __METHOD__,
3876  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
3877  );
3878  } else {
3879  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
3880  }
3881  }
3882 
3899  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
3900  if ( !( $old instanceof Revision ) ) {
3901  $old = Revision::newFromTitle( $this, (int)$old );
3902  }
3903  if ( !( $new instanceof Revision ) ) {
3904  $new = Revision::newFromTitle( $this, (int)$new );
3905  }
3906  // XXX: what if Revision objects are passed in, but they don't refer to this title?
3907  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
3908  // in the sanity check below?
3909  if ( !$old || !$new ) {
3910  return null; // nothing to compare
3911  }
3912  $authors = [];
3913  $old_cmp = '>';
3914  $new_cmp = '<';
3915  $options = (array)$options;
3916  if ( in_array( 'include_old', $options ) ) {
3917  $old_cmp = '>=';
3918  }
3919  if ( in_array( 'include_new', $options ) ) {
3920  $new_cmp = '<=';
3921  }
3922  if ( in_array( 'include_both', $options ) ) {
3923  $old_cmp = '>=';
3924  $new_cmp = '<=';
3925  }
3926  // No DB query needed if $old and $new are the same or successive revisions:
3927  if ( $old->getId() === $new->getId() ) {
3928  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
3929  [] :
3930  [ $old->getUserText( Revision::RAW ) ];
3931  } elseif ( $old->getId() === $new->getParentId() ) {
3932  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
3933  $authors[] = $old->getUserText( Revision::RAW );
3934  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
3935  $authors[] = $new->getUserText( Revision::RAW );
3936  }
3937  } elseif ( $old_cmp === '>=' ) {
3938  $authors[] = $old->getUserText( Revision::RAW );
3939  } elseif ( $new_cmp === '<=' ) {
3940  $authors[] = $new->getUserText( Revision::RAW );
3941  }
3942  return $authors;
3943  }
3944  $dbr = wfGetDB( DB_REPLICA );
3946  $authors = $dbr->selectFieldValues(
3947  $revQuery['tables'],
3948  $revQuery['fields']['rev_user_text'],
3949  [
3950  'rev_page' => $this->getArticleID(),
3951  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
3952  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
3953  ], __METHOD__,
3954  [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated
3955  $revQuery['joins']
3956  );
3957  return $authors;
3958  }
3959 
3974  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
3975  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
3976  return $authors ? count( $authors ) : 0;
3977  }
3978 
3985  public function equals( LinkTarget $title ) {
3986  // Note: === is necessary for proper matching of number-like titles.
3987  return $this->mInterwiki === $title->getInterwiki()
3988  && $this->mNamespace == $title->getNamespace()
3989  && $this->mDbkeyform === $title->getDBkey();
3990  }
3991 
3998  public function isSubpageOf( Title $title ) {
3999  return $this->mInterwiki === $title->mInterwiki
4000  && $this->mNamespace == $title->mNamespace
4001  && strpos( $this->mDbkeyform, $title->mDbkeyform . '/' ) === 0;
4002  }
4003 
4015  public function exists( $flags = 0 ) {
4016  $exists = $this->getArticleID( $flags ) != 0;
4017  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4018  return $exists;
4019  }
4020 
4037  public function isAlwaysKnown() {
4038  $isKnown = null;
4039 
4050  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4051 
4052  if ( !is_null( $isKnown ) ) {
4053  return $isKnown;
4054  }
4055 
4056  if ( $this->isExternal() ) {
4057  return true; // any interwiki link might be viewable, for all we know
4058  }
4059 
4060  $services = MediaWikiServices::getInstance();
4061  switch ( $this->mNamespace ) {
4062  case NS_MEDIA:
4063  case NS_FILE:
4064  // file exists, possibly in a foreign repo
4065  return (bool)$services->getRepoGroup()->findFile( $this );
4066  case NS_SPECIAL:
4067  // valid special page
4068  return $services->getSpecialPageFactory()->exists( $this->mDbkeyform );
4069  case NS_MAIN:
4070  // selflink, possibly with fragment
4071  return $this->mDbkeyform == '';
4072  case NS_MEDIAWIKI:
4073  // known system message
4074  return $this->hasSourceText() !== false;
4075  default:
4076  return false;
4077  }
4078  }
4079 
4091  public function isKnown() {
4092  return $this->isAlwaysKnown() || $this->exists();
4093  }
4094 
4100  public function hasSourceText() {
4101  if ( $this->exists() ) {
4102  return true;
4103  }
4104 
4105  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4106  // If the page doesn't exist but is a known system message, default
4107  // message content will be displayed, same for language subpages-
4108  // Use always content language to avoid loading hundreds of languages
4109  // to get the link color.
4110  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
4111  list( $name, ) = MessageCache::singleton()->figureMessage(
4112  $contLang->lcfirst( $this->getText() )
4113  );
4114  $message = wfMessage( $name )->inLanguage( $contLang )->useDatabase( false );
4115  return $message->exists();
4116  }
4117 
4118  return false;
4119  }
4120 
4158  public function getDefaultMessageText() {
4159  if ( $this->mNamespace != NS_MEDIAWIKI ) { // Just in case
4160  return false;
4161  }
4162 
4163  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4164  MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $this->getText() )
4165  );
4166  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4167 
4168  if ( $message->exists() ) {
4169  return $message->plain();
4170  } else {
4171  return false;
4172  }
4173  }
4174 
4181  public function invalidateCache( $purgeTime = null ) {
4182  if ( wfReadOnly() ) {
4183  return false;
4184  } elseif ( $this->mArticleID === 0 ) {
4185  return true; // avoid gap locking if we know it's not there
4186  }
4187 
4188  $dbw = wfGetDB( DB_MASTER );
4189  $dbw->onTransactionPreCommitOrIdle(
4190  function () use ( $dbw ) {
4192  $this, null, null, $dbw->getDomainID() );
4193  },
4194  __METHOD__
4195  );
4196 
4197  $conds = $this->pageCond();
4199  new AutoCommitUpdate(
4200  $dbw,
4201  __METHOD__,
4202  function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4203  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4204  $dbw->update(
4205  'page',
4206  [ 'page_touched' => $dbTimestamp ],
4207  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4208  $fname
4209  );
4210  MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4211  }
4212  ),
4214  );
4215 
4216  return true;
4217  }
4218 
4224  public function touchLinks() {
4225  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
4226  if ( $this->mNamespace == NS_CATEGORY ) {
4228  new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
4229  );
4230  }
4231  }
4232 
4239  public function getTouched( $db = null ) {
4240  if ( $db === null ) {
4241  $db = wfGetDB( DB_REPLICA );
4242  }
4243  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4244  return $touched;
4245  }
4246 
4253  public function getNotificationTimestamp( $user = null ) {
4254  global $wgUser;
4255 
4256  // Assume current user if none given
4257  if ( !$user ) {
4258  $user = $wgUser;
4259  }
4260  // Check cache first
4261  $uid = $user->getId();
4262  if ( !$uid ) {
4263  return false;
4264  }
4265  // avoid isset here, as it'll return false for null entries
4266  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4267  return $this->mNotificationTimestamp[$uid];
4268  }
4269  // Don't cache too much!
4270  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4271  $this->mNotificationTimestamp = [];
4272  }
4273 
4274  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4275  $watchedItem = $store->getWatchedItem( $user, $this );
4276  if ( $watchedItem ) {
4277  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4278  } else {
4279  $this->mNotificationTimestamp[$uid] = false;
4280  }
4281 
4282  return $this->mNotificationTimestamp[$uid];
4283  }
4284 
4291  public function getNamespaceKey( $prepend = 'nstab-' ) {
4292  // Gets the subject namespace of this title
4293  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
4294  $subjectNS = $nsInfo->getSubject( $this->mNamespace );
4295  // Prefer canonical namespace name for HTML IDs
4296  $namespaceKey = $nsInfo->getCanonicalName( $subjectNS );
4297  if ( $namespaceKey === false ) {
4298  // Fallback to localised text
4299  $namespaceKey = $this->getSubjectNsText();
4300  }
4301  // Makes namespace key lowercase
4302  $namespaceKey = MediaWikiServices::getInstance()->getContentLanguage()->lc( $namespaceKey );
4303  // Uses main
4304  if ( $namespaceKey == '' ) {
4305  $namespaceKey = 'main';
4306  }
4307  // Changes file to image for backwards compatibility
4308  if ( $namespaceKey == 'file' ) {
4309  $namespaceKey = 'image';
4310  }
4311  return $prepend . $namespaceKey;
4312  }
4313 
4320  public function getRedirectsHere( $ns = null ) {
4321  $redirs = [];
4322 
4323  $dbr = wfGetDB( DB_REPLICA );
4324  $where = [
4325  'rd_namespace' => $this->mNamespace,
4326  'rd_title' => $this->mDbkeyform,
4327  'rd_from = page_id'
4328  ];
4329  if ( $this->isExternal() ) {
4330  $where['rd_interwiki'] = $this->mInterwiki;
4331  } else {
4332  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4333  }
4334  if ( !is_null( $ns ) ) {
4335  $where['page_namespace'] = $ns;
4336  }
4337 
4338  $res = $dbr->select(
4339  [ 'redirect', 'page' ],
4340  [ 'page_namespace', 'page_title' ],
4341  $where,
4342  __METHOD__
4343  );
4344 
4345  foreach ( $res as $row ) {
4346  $redirs[] = self::newFromRow( $row );
4347  }
4348  return $redirs;
4349  }
4350 
4356  public function isValidRedirectTarget() {
4358 
4359  if ( $this->isSpecialPage() ) {
4360  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4361  if ( $this->isSpecial( 'Userlogout' ) ) {
4362  return false;
4363  }
4364 
4365  foreach ( $wgInvalidRedirectTargets as $target ) {
4366  if ( $this->isSpecial( $target ) ) {
4367  return false;
4368  }
4369  }
4370  }
4371 
4372  return true;
4373  }
4374 
4380  public function getBacklinkCache() {
4381  return BacklinkCache::get( $this );
4382  }
4383 
4389  public function canUseNoindex() {
4391 
4392  $bannedNamespaces = $wgExemptFromUserRobotsControl ??
4393  MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces();
4394 
4395  return !in_array( $this->mNamespace, $bannedNamespaces );
4396  }
4397 
4408  public function getCategorySortkey( $prefix = '' ) {
4409  $unprefixed = $this->getText();
4410 
4411  // Anything that uses this hook should only depend
4412  // on the Title object passed in, and should probably
4413  // tell the users to run updateCollations.php --force
4414  // in order to re-sort existing category relations.
4415  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4416  if ( $prefix !== '' ) {
4417  # Separate with a line feed, so the unprefixed part is only used as
4418  # a tiebreaker when two pages have the exact same prefix.
4419  # In UCA, tab is the only character that can sort above LF
4420  # so we strip both of them from the original prefix.
4421  $prefix = strtr( $prefix, "\n\t", ' ' );
4422  return "$prefix\n$unprefixed";
4423  }
4424  return $unprefixed;
4425  }
4426 
4434  private function getDbPageLanguageCode() {
4435  global $wgPageLanguageUseDB;
4436 
4437  // check, if the page language could be saved in the database, and if so and
4438  // the value is not requested already, lookup the page language using LinkCache
4439  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4440  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
4441  $linkCache->addLinkObj( $this );
4442  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4443  }
4444 
4445  return $this->mDbPageLanguage;
4446  }
4447 
4456  public function getPageLanguage() {
4457  global $wgLang, $wgLanguageCode;
4458  if ( $this->isSpecialPage() ) {
4459  // special pages are in the user language
4460  return $wgLang;
4461  }
4462 
4463  // Checking if DB language is set
4464  $dbPageLanguage = $this->getDbPageLanguageCode();
4465  if ( $dbPageLanguage ) {
4466  return wfGetLangObj( $dbPageLanguage );
4467  }
4468 
4469  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4470  // Note that this may depend on user settings, so the cache should
4471  // be only per-request.
4472  // NOTE: ContentHandler::getPageLanguage() may need to load the
4473  // content to determine the page language!
4474  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4475  // tests.
4476  $contentHandler = ContentHandler::getForTitle( $this );
4477  $langObj = $contentHandler->getPageLanguage( $this );
4478  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4479  } else {
4480  $langObj = Language::factory( $this->mPageLanguage[0] );
4481  }
4482 
4483  return $langObj;
4484  }
4485 
4494  public function getPageViewLanguage() {
4495  global $wgLang;
4496 
4497  if ( $this->isSpecialPage() ) {
4498  // If the user chooses a variant, the content is actually
4499  // in a language whose code is the variant code.
4500  $variant = $wgLang->getPreferredVariant();
4501  if ( $wgLang->getCode() !== $variant ) {
4502  return Language::factory( $variant );
4503  }
4504 
4505  return $wgLang;
4506  }
4507 
4508  // Checking if DB language is set
4509  $dbPageLanguage = $this->getDbPageLanguageCode();
4510  if ( $dbPageLanguage ) {
4511  $pageLang = wfGetLangObj( $dbPageLanguage );
4512  $variant = $pageLang->getPreferredVariant();
4513  if ( $pageLang->getCode() !== $variant ) {
4514  $pageLang = Language::factory( $variant );
4515  }
4516 
4517  return $pageLang;
4518  }
4519 
4520  // @note Can't be cached persistently, depends on user settings.
4521  // @note ContentHandler::getPageViewLanguage() may need to load the
4522  // content to determine the page language!
4523  $contentHandler = ContentHandler::getForTitle( $this );
4524  $pageLang = $contentHandler->getPageViewLanguage( $this );
4525  return $pageLang;
4526  }
4527 
4538  public function getEditNotices( $oldid = 0 ) {
4539  $notices = [];
4540 
4541  // Optional notice for the entire namespace
4542  $editnotice_ns = 'editnotice-' . $this->mNamespace;
4543  $msg = wfMessage( $editnotice_ns );
4544  if ( $msg->exists() ) {
4545  $html = $msg->parseAsBlock();
4546  // Edit notices may have complex logic, but output nothing (T91715)
4547  if ( trim( $html ) !== '' ) {
4548  $notices[$editnotice_ns] = Html::rawElement(
4549  'div',
4550  [ 'class' => [
4551  'mw-editnotice',
4552  'mw-editnotice-namespace',
4553  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4554  ] ],
4555  $html
4556  );
4557  }
4558  }
4559 
4560  if (
4561  MediaWikiServices::getInstance()->getNamespaceInfo()->
4562  hasSubpages( $this->mNamespace )
4563  ) {
4564  // Optional notice for page itself and any parent page
4565  $editnotice_base = $editnotice_ns;
4566  foreach ( explode( '/', $this->mDbkeyform ) as $part ) {
4567  $editnotice_base .= '-' . $part;
4568  $msg = wfMessage( $editnotice_base );
4569  if ( $msg->exists() ) {
4570  $html = $msg->parseAsBlock();
4571  if ( trim( $html ) !== '' ) {
4572  $notices[$editnotice_base] = Html::rawElement(
4573  'div',
4574  [ 'class' => [
4575  'mw-editnotice',
4576  'mw-editnotice-base',
4577  Sanitizer::escapeClass( "mw-$editnotice_base" )
4578  ] ],
4579  $html
4580  );
4581  }
4582  }
4583  }
4584  } else {
4585  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4586  $editnoticeText = $editnotice_ns . '-' . strtr( $this->mDbkeyform, '/', '-' );
4587  $msg = wfMessage( $editnoticeText );
4588  if ( $msg->exists() ) {
4589  $html = $msg->parseAsBlock();
4590  if ( trim( $html ) !== '' ) {
4591  $notices[$editnoticeText] = Html::rawElement(
4592  'div',
4593  [ 'class' => [
4594  'mw-editnotice',
4595  'mw-editnotice-page',
4596  Sanitizer::escapeClass( "mw-$editnoticeText" )
4597  ] ],
4598  $html
4599  );
4600  }
4601  }
4602  }
4603 
4604  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4605  return $notices;
4606  }
4607 
4611  public function __sleep() {
4612  return [
4613  'mNamespace',
4614  'mDbkeyform',
4615  'mFragment',
4616  'mInterwiki',
4617  'mLocalInterwiki',
4618  'mUserCaseDBKey',
4619  'mDefaultNamespace',
4620  ];
4621  }
4622 
4623  public function __wakeup() {
4624  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4625  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4626  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4627  }
4628 
4629 }
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:2290
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition: Title.php:4037
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:2515
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition: Title.php:2852
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:4224
getFragment()
Get the Title fragment (i.e.
Definition: Title.php:1585
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:4181
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition: Title.php:2631
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren&#39;t movable...
Definition: Title.php:1280
getRootTitle()
Get the root page name title, i.e.
Definition: Title.php:1763
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3005
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition: Title.php:3899
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:1136
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition: Title.php:4389
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:231
wasLocalInterwiki()
Was this a local interwiki link?
Definition: Title.php:921
$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:1269
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:1012
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2423
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:2483
static clearCaches()
Definition: Title.php:3146
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:3856
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:2893
const NS_MAIN
Definition: Defines.php:60
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:1383
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:984
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition: Title.php:1823
getBaseText()
Get the base page name without a namespace, i.e.
Definition: Title.php:1778
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:3668
getDefaultMessageText()
Get the default (plain) message contents for an page that overrides an interface message key...
Definition: Title.php:4158
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition: Title.php:2690
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:1886
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:1631
equals(LinkTarget $title)
Compare with another title.
Definition: Title.php:3985
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition: Title.php:1111
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:1427
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
__wakeup()
Definition: Title.php:4623
getTalkPageIfDefined()
Get a Title object associated with the talk page of this article, if such a talk page can exist...
Definition: Title.php:1535
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition: Title.php:944
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:2263
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition: Title.php:1959
$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:1155
if(!isset( $args[0])) $lang
isUserCssConfigPage()
Is this a CSS "config" sub-page of a user page?
Definition: Title.php:1399
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:3182
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition: Title.php:1229
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:1993
isTalkPage()
Is this a talk page of some sort?
Definition: Title.php:1510
Handles purging appropriate CDN URLs given a title (or titles)
string $mUrlform
URL-encoded form of the main part.
Definition: Title.php:77
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:4434
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:3804
const NS_SPECIAL
Definition: Defines.php:49
const PROTO_CURRENT
Definition: Defines.php:218
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition: Title.php:4253
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:1564
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".
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:1217
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1696
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move a title to a new location.
Definition: Title.php:3467
getBaseTitle()
Get the base page name title, i.e.
Definition: Title.php:1808
getParentCategories()
Get categories to which this Title belongs and return an array of categories&#39; names.
Definition: Title.php:3633
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:3224
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4380
$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:4408
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:235
The TitleArray class only exists to provide the newFromResult method at pre- sent.
Definition: TitleArray.php:33
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:3282
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:1521
userCan( $action, $user=null, $rigor=PermissionManager::RIGOR_SECURE)
Can $user perform $action on this page?
Definition: Title.php:2203
getNamespace()
Get the namespace index.
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:1355
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias...
Definition: Title.php:1193
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 nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition: Title.php:678
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
$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:1847
$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:2092
quickUserCan( $action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition: Title.php:2184
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:1441
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition: Title.php:2842
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:1086
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition: Title.php:2141
__sleep()
Definition: Title.php:4611
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:901
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:931
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:1369
deleteTitleProtection()
Remove any title protection due to page existing.
Definition: Title.php:2405
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:4100
isUserJsonConfigPage()
Is this a JSON "config" sub-page of a user page?
Definition: Title.php:1413
resetArticleID( $newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3124
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition: hooks.txt:2217
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:1002
__toString()
Return a string representation of this title.
Definition: Title.php:1710
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition: Title.php:1867
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1175
hasFragment()
Check if a Title fragment is set.
Definition: Title.php:1595
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4320
$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:3266
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition: Title.php:2671
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition: Title.php:1325
const NS_MEDIA
Definition: Defines.php:48
getRootText()
Get the root page name text without a namespace, i.e.
Definition: Title.php:1740
$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:1146
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:2924
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:1061
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:4356
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not...
Definition: Title.php:2501
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition: Title.php:3582
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition: Title.php:3431
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3384
getDBkey()
Get the main part with underscores.
$cache
Definition: mcc.php:33
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition: Title.php:1574
const NS_CATEGORY
Definition: Defines.php:74
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:3730
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:3998
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition: Title.php:174
loadRestrictions( $oldFashionedRestrictions=null, $flags=0)
Load restrictions from the page_restrictions table.
Definition: Title.php:2768
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with &#39;#&#39;)
Definition: Title.php:1720
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:1304
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object. ...
Definition: Title.php:1658
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:1411
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:4291
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:216
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition: Title.php:1924
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1026
const PROTO_RELATIVE
Definition: Defines.php:217
string $mInterwiki
Interwiki prefix.
Definition: Title.php:89
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:4091
isProtected( $action='')
Does the title correspond to a protected article?
Definition: Title.php:2451
const NS_FILE
Definition: Defines.php:66
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2618
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:83
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:1459
isSubpage()
Is this a subpage?
Definition: Title.php:1313
getInterwiki()
Get the interwiki prefix.
Definition: Title.php:912
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:3348
const NS_MEDIAWIKI
Definition: Defines.php:68
const PROTO_HTTP
Definition: Defines.php:215
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:3336
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:865
CONTENT_MODEL_JAVASCRIPT
Allow users to upload files.
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:1550
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:1257
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:1165
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:2155
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...
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:3531
const PROTO_CANONICAL
Definition: Defines.php:219
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:3757
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:4494
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition: Title.php:1856
$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:1642
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:3974
getTitleProtectionInternal()
Fetch title protection settings.
Definition: Title.php:2358
getRestrictionTypes()
Returns restriction types for the current Title.
Definition: Title.php:2308
$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:1315
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition: Title.php:931
getContentModel( $flags=0)
Get the page&#39;s content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:1038
string $mFragment
Title fragment (i.e.
Definition: Title.php:95
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition: Title.php:98
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:3161
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:3735
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:4456
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:1338
isRawHtmlMessage()
Is this a message which can contain raw HTML?
Definition: Title.php:1495
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition: Title.php:1604
$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:4015
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition: Title.php:3092
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition: Title.php:3695
getInterwiki()
The interwiki component of this LinkTarget.
const CONTENT_MODEL_CSS
Definition: Defines.php:233
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:3784
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition: Title.php:2979
isNewPage()
Check if this is a new page.
Definition: Title.php:3794
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:2659
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition: Title.php:846
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:2532
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3029
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:1477
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:783
$wgServer
URL of the server.
isDeleted()
Is there a version of this page in the deletion archive?
Definition: Title.php:2954
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition: Title.php:886
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:2335
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:4538
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
getTouched( $db=null)
Get the last touched timestamp.
Definition: Title.php:4239
static MapCacheLRU null $titleCache
Definition: Title.php:42
getTalkNsText()
Get the namespace text of the talk page.
Definition: Title.php:1122
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries...
Definition: Title.php:444
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:2645
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition: Title.php:2117
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:1076
getUserPermissionsErrors( $action, $user, $rigor=PermissionManager::RIGOR_SECURE, $ignoreErrors=[])
Can $user perform $action on this page?
Definition: Title.php:2241
$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:3711
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new page object...
Definition: WikiPage.php:383
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:3064
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition: Title.php:1337
static singleton()
Get the signleton instance of this class.
purgeSquid()
Purge all applicable CDN URLs.
Definition: Title.php:3414
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:961
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:993
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1684
static decodeCharReferencesAndNormalize( $text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1679
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move this page&#39;s subpages to be subpages of $nt.
Definition: Title.php:3499
estimateRevisionCount()
Get the approximate revision count of this page.
Definition: Title.php:3833
$matches
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition: Title.php:3747
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