MediaWiki  master
Title.php
Go to the documentation of this file.
1 <?php
33 
42 class Title implements LinkTarget, IDBAccessObject {
44  private static $titleCache = null;
45 
51  const CACHE_MAX = 1000;
52 
57  const GAID_FOR_UPDATE = 1;
58 
66  const NEW_CLONE = 'clone';
67 
73  // @{
74 
76  public $mTextform = '';
77 
79  public $mUrlform = '';
80 
82  public $mDbkeyform = '';
83 
85  protected $mUserCaseDBKey;
86 
88  public $mNamespace = NS_MAIN;
89 
91  public $mInterwiki = '';
92 
94  private $mLocalInterwiki = false;
95 
97  public $mFragment = '';
98 
100  public $mArticleID = -1;
101 
103  protected $mLatestID = false;
104 
109  private $mContentModel = false;
110 
115  private $mForcedContentModel = false;
116 
119 
121  public $mRestrictions = [];
122 
129  protected $mOldRestrictions = false;
130 
133 
136 
138  protected $mRestrictionsExpiry = [];
139 
142 
145 
147  public $mRestrictionsLoaded = false;
148 
157  public $prefixedText = null;
158 
161 
168 
170  protected $mLength = -1;
171 
173  public $mRedirect = null;
174 
177 
179  private $mHasSubpages;
180 
182  private $mPageLanguage = false;
183 
187  private $mDbPageLanguage = false;
188 
190  private $mTitleValue = null;
191 
194  // @}
195 
204  private static function getTitleFormatter() {
205  return MediaWikiServices::getInstance()->getTitleFormatter();
206  }
207 
216  private static function getInterwikiLookup() {
217  return MediaWikiServices::getInstance()->getInterwikiLookup();
218  }
219 
223  function __construct() {
224  }
225 
234  public static function newFromDBkey( $key ) {
235  $t = new self();
236  $t->mDbkeyform = $key;
237 
238  try {
239  $t->secureAndSplit();
240  return $t;
241  } catch ( MalformedTitleException $ex ) {
242  return null;
243  }
244  }
245 
259  public static function newFromTitleValue( TitleValue $titleValue, $forceClone = '' ) {
260  return self::newFromLinkTarget( $titleValue, $forceClone );
261  }
262 
274  public static function newFromLinkTarget( LinkTarget $linkTarget, $forceClone = '' ) {
275  if ( $linkTarget instanceof Title ) {
276  // Special case if it's already a Title object
277  if ( $forceClone === self::NEW_CLONE ) {
278  return clone $linkTarget;
279  } else {
280  return $linkTarget;
281  }
282  }
283  return self::makeTitle(
284  $linkTarget->getNamespace(),
285  $linkTarget->getText(),
286  $linkTarget->getFragment(),
287  $linkTarget->getInterwiki()
288  );
289  }
290 
298  public static function castFromLinkTarget( $linkTarget ) {
299  return $linkTarget ? self::newFromLinkTarget( $linkTarget ) : null;
300  }
301 
322  public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
323  // DWIM: Integers can be passed in here when page titles are used as array keys.
324  if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
325  throw new InvalidArgumentException( '$text must be a string.' );
326  }
327  if ( $text === null ) {
328  return null;
329  }
330 
331  try {
332  return self::newFromTextThrow( (string)$text, $defaultNamespace );
333  } catch ( MalformedTitleException $ex ) {
334  return null;
335  }
336  }
337 
359  public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
360  if ( is_object( $text ) ) {
361  throw new MWException( '$text must be a string, given an object' );
362  } elseif ( $text === null ) {
363  // Legacy code relies on MalformedTitleException being thrown in this case
364  // (happens when URL with no title in it is parsed). TODO fix
365  throw new MalformedTitleException( 'title-invalid-empty' );
366  }
367 
368  $titleCache = self::getTitleCache();
369 
370  // Wiki pages often contain multiple links to the same page.
371  // Title normalization and parsing can become expensive on pages with many
372  // links, so we can save a little time by caching them.
373  // In theory these are value objects and won't get changed...
374  if ( $defaultNamespace == NS_MAIN ) {
375  $t = $titleCache->get( $text );
376  if ( $t ) {
377  return $t;
378  }
379  }
380 
381  // Convert things like &eacute; &#257; or &#x3017; into normalized (T16952) text
382  $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
383 
384  $t = new Title();
385  $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
386  $t->mDefaultNamespace = (int)$defaultNamespace;
387 
388  $t->secureAndSplit();
389  if ( $defaultNamespace == NS_MAIN ) {
390  $titleCache->set( $text, $t );
391  }
392  return $t;
393  }
394 
410  public static function newFromURL( $url ) {
411  $t = new Title();
412 
413  # For compatibility with old buggy URLs. "+" is usually not valid in titles,
414  # but some URLs used it as a space replacement and they still come
415  # from some external search tools.
416  if ( strpos( self::legalChars(), '+' ) === false ) {
417  $url = strtr( $url, '+', ' ' );
418  }
419 
420  $t->mDbkeyform = strtr( $url, ' ', '_' );
421 
422  try {
423  $t->secureAndSplit();
424  return $t;
425  } catch ( MalformedTitleException $ex ) {
426  return null;
427  }
428  }
429 
433  private static function getTitleCache() {
434  if ( self::$titleCache === null ) {
435  self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
436  }
437  return self::$titleCache;
438  }
439 
447  protected static function getSelectFields() {
449 
450  $fields = [
451  'page_namespace', 'page_title', 'page_id',
452  'page_len', 'page_is_redirect', 'page_latest',
453  ];
454 
455  if ( $wgContentHandlerUseDB ) {
456  $fields[] = 'page_content_model';
457  }
458 
459  if ( $wgPageLanguageUseDB ) {
460  $fields[] = 'page_lang';
461  }
462 
463  return $fields;
464  }
465 
473  public static function newFromID( $id, $flags = 0 ) {
474  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
475  $row = $db->selectRow(
476  'page',
477  self::getSelectFields(),
478  [ 'page_id' => $id ],
479  __METHOD__
480  );
481  if ( $row !== false ) {
482  $title = self::newFromRow( $row );
483  } else {
484  $title = null;
485  }
486 
487  return $title;
488  }
489 
496  public static function newFromIDs( $ids ) {
497  if ( !count( $ids ) ) {
498  return [];
499  }
500  $dbr = wfGetDB( DB_REPLICA );
501 
502  $res = $dbr->select(
503  'page',
504  self::getSelectFields(),
505  [ 'page_id' => $ids ],
506  __METHOD__
507  );
508 
509  $titles = [];
510  foreach ( $res as $row ) {
511  $titles[] = self::newFromRow( $row );
512  }
513  return $titles;
514  }
515 
522  public static function newFromRow( $row ) {
523  $t = self::makeTitle( $row->page_namespace, $row->page_title );
524  $t->loadFromRow( $row );
525  return $t;
526  }
527 
534  public function loadFromRow( $row ) {
535  if ( $row ) { // page found
536  if ( isset( $row->page_id ) ) {
537  $this->mArticleID = (int)$row->page_id;
538  }
539  if ( isset( $row->page_len ) ) {
540  $this->mLength = (int)$row->page_len;
541  }
542  if ( isset( $row->page_is_redirect ) ) {
543  $this->mRedirect = (bool)$row->page_is_redirect;
544  }
545  if ( isset( $row->page_latest ) ) {
546  $this->mLatestID = (int)$row->page_latest;
547  }
548  if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
549  $this->mContentModel = (string)$row->page_content_model;
550  } elseif ( !$this->mForcedContentModel ) {
551  $this->mContentModel = false; # initialized lazily in getContentModel()
552  }
553  if ( isset( $row->page_lang ) ) {
554  $this->mDbPageLanguage = (string)$row->page_lang;
555  }
556  if ( isset( $row->page_restrictions ) ) {
557  $this->mOldRestrictions = $row->page_restrictions;
558  }
559  } else { // page not found
560  $this->mArticleID = 0;
561  $this->mLength = 0;
562  $this->mRedirect = false;
563  $this->mLatestID = 0;
564  if ( !$this->mForcedContentModel ) {
565  $this->mContentModel = false; # initialized lazily in getContentModel()
566  }
567  }
568  }
569 
592  public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
593  $t = new Title();
594  $t->mInterwiki = $interwiki;
595  $t->mFragment = $fragment;
596  $t->mNamespace = $ns = (int)$ns;
597  $t->mDbkeyform = strtr( $title, ' ', '_' );
598  $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
599  $t->mUrlform = wfUrlencode( $t->mDbkeyform );
600  $t->mTextform = strtr( $title, '_', ' ' );
601  $t->mContentModel = false; # initialized lazily in getContentModel()
602  return $t;
603  }
604 
620  public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
621  // NOTE: ideally, this would just call makeTitle() and then isValid(),
622  // but presently, that means more overhead on a potential performance hotspot.
623 
624  if ( !MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $ns ) ) {
625  return null;
626  }
627 
628  $t = new Title();
629  $t->mDbkeyform = self::makeName( $ns, $title, $fragment, $interwiki, true );
630 
631  try {
632  $t->secureAndSplit();
633  return $t;
634  } catch ( MalformedTitleException $ex ) {
635  return null;
636  }
637  }
638 
656  public static function newMainPage( MessageLocalizer $localizer = null ) {
657  if ( $localizer ) {
658  $msg = $localizer->msg( 'mainpage' );
659  } else {
660  $msg = wfMessage( 'mainpage' );
661  }
662 
663  $title = self::newFromText( $msg->inContentLanguage()->text() );
664 
665  // Every page renders at least one link to the Main Page (e.g. sidebar).
666  // If the localised value is invalid, don't produce fatal errors that
667  // would make the wiki inaccessible (and hard to fix the invalid message).
668  // Gracefully fallback...
669  if ( !$title ) {
670  $title = self::newFromText( 'Main Page' );
671  }
672  return $title;
673  }
674 
681  public static function nameOf( $id ) {
682  $dbr = wfGetDB( DB_REPLICA );
683 
684  $s = $dbr->selectRow(
685  'page',
686  [ 'page_namespace', 'page_title' ],
687  [ 'page_id' => $id ],
688  __METHOD__
689  );
690  if ( $s === false ) {
691  return null;
692  }
693 
694  $n = self::makeName( $s->page_namespace, $s->page_title );
695  return $n;
696  }
697 
703  public static function legalChars() {
704  global $wgLegalTitleChars;
705  return $wgLegalTitleChars;
706  }
707 
717  public static function convertByteClassToUnicodeClass( $byteClass ) {
718  $length = strlen( $byteClass );
719  // Input token queue
720  $x0 = $x1 = $x2 = '';
721  // Decoded queue
722  $d0 = $d1 = $d2 = '';
723  // Decoded integer codepoints
724  $ord0 = $ord1 = $ord2 = 0;
725  // Re-encoded queue
726  $r0 = $r1 = $r2 = '';
727  // Output
728  $out = '';
729  // Flags
730  $allowUnicode = false;
731  for ( $pos = 0; $pos < $length; $pos++ ) {
732  // Shift the queues down
733  $x2 = $x1;
734  $x1 = $x0;
735  $d2 = $d1;
736  $d1 = $d0;
737  $ord2 = $ord1;
738  $ord1 = $ord0;
739  $r2 = $r1;
740  $r1 = $r0;
741  // Load the current input token and decoded values
742  $inChar = $byteClass[$pos];
743  if ( $inChar == '\\' ) {
744  if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
745  $x0 = $inChar . $m[0];
746  $d0 = chr( hexdec( $m[1] ) );
747  $pos += strlen( $m[0] );
748  } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
749  $x0 = $inChar . $m[0];
750  $d0 = chr( octdec( $m[0] ) );
751  $pos += strlen( $m[0] );
752  } elseif ( $pos + 1 >= $length ) {
753  $x0 = $d0 = '\\';
754  } else {
755  $d0 = $byteClass[$pos + 1];
756  $x0 = $inChar . $d0;
757  $pos += 1;
758  }
759  } else {
760  $x0 = $d0 = $inChar;
761  }
762  $ord0 = ord( $d0 );
763  // Load the current re-encoded value
764  if ( $ord0 < 32 || $ord0 == 0x7f ) {
765  $r0 = sprintf( '\x%02x', $ord0 );
766  } elseif ( $ord0 >= 0x80 ) {
767  // Allow unicode if a single high-bit character appears
768  $r0 = sprintf( '\x%02x', $ord0 );
769  $allowUnicode = true;
770  // @phan-suppress-next-line PhanParamSuspiciousOrder false positive
771  } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
772  $r0 = '\\' . $d0;
773  } else {
774  $r0 = $d0;
775  }
776  // Do the output
777  if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
778  // Range
779  if ( $ord2 > $ord0 ) {
780  // Empty range
781  } elseif ( $ord0 >= 0x80 ) {
782  // Unicode range
783  $allowUnicode = true;
784  if ( $ord2 < 0x80 ) {
785  // Keep the non-unicode section of the range
786  $out .= "$r2-\\x7F";
787  }
788  } else {
789  // Normal range
790  $out .= "$r2-$r0";
791  }
792  // Reset state to the initial value
793  $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
794  } elseif ( $ord2 < 0x80 ) {
795  // ASCII character
796  $out .= $r2;
797  }
798  }
799  if ( $ord1 < 0x80 ) {
800  $out .= $r1;
801  }
802  if ( $ord0 < 0x80 ) {
803  $out .= $r0;
804  }
805  if ( $allowUnicode ) {
806  $out .= '\u0080-\uFFFF';
807  }
808  return $out;
809  }
810 
822  public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
823  $canonicalNamespace = false
824  ) {
825  if ( $canonicalNamespace ) {
826  $namespace = MediaWikiServices::getInstance()->getNamespaceInfo()->
827  getCanonicalName( $ns );
828  } else {
829  $namespace = MediaWikiServices::getInstance()->getContentLanguage()->getNsText( $ns );
830  }
831  $name = $namespace == '' ? $title : "$namespace:$title";
832  if ( strval( $interwiki ) != '' ) {
833  $name = "$interwiki:$name";
834  }
835  if ( strval( $fragment ) != '' ) {
836  $name .= '#' . $fragment;
837  }
838  return $name;
839  }
840 
849  public static function compare( LinkTarget $a, LinkTarget $b ) {
850  return $a->getNamespace() <=> $b->getNamespace()
851  ?: strcmp( $a->getText(), $b->getText() );
852  }
853 
871  public function isValid() {
872  $services = MediaWikiServices::getInstance();
873  if ( !$services->getNamespaceInfo()->exists( $this->mNamespace ) ) {
874  return false;
875  }
876 
877  try {
878  $services->getTitleParser()->parseTitle( $this->mDbkeyform, $this->mNamespace );
879  } catch ( MalformedTitleException $ex ) {
880  return false;
881  }
882 
883  try {
884  // Title value applies basic syntax checks. Should perhaps be moved elsewhere.
885  new TitleValue(
886  $this->mNamespace,
887  $this->mDbkeyform,
888  $this->mFragment,
889  $this->mInterwiki
890  );
891  } catch ( InvalidArgumentException $ex ) {
892  return false;
893  }
894 
895  return true;
896  }
897 
905  public function isLocal() {
906  if ( $this->isExternal() ) {
907  $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
908  if ( $iw ) {
909  return $iw->isLocal();
910  }
911  }
912  return true;
913  }
914 
920  public function isExternal() {
921  return $this->mInterwiki !== '';
922  }
923 
931  public function getInterwiki() {
932  return $this->mInterwiki;
933  }
934 
940  public function wasLocalInterwiki() {
941  return $this->mLocalInterwiki;
942  }
943 
950  public function isTrans() {
951  if ( !$this->isExternal() ) {
952  return false;
953  }
954 
955  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
956  }
957 
963  public function getTransWikiID() {
964  if ( !$this->isExternal() ) {
965  return false;
966  }
967 
968  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
969  }
970 
980  public function getTitleValue() {
981  if ( $this->mTitleValue === null ) {
982  try {
983  $this->mTitleValue = new TitleValue(
984  $this->mNamespace,
985  $this->mDbkeyform,
986  $this->mFragment,
987  $this->mInterwiki
988  );
989  } catch ( InvalidArgumentException $ex ) {
990  wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
991  $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
992  }
993  }
994 
995  return $this->mTitleValue;
996  }
997 
1003  public function getText() {
1004  return $this->mTextform;
1005  }
1006 
1012  public function getPartialURL() {
1013  return $this->mUrlform;
1014  }
1015 
1021  public function getDBkey() {
1022  return $this->mDbkeyform;
1023  }
1024 
1031  function getUserCaseDBKey() {
1032  if ( !is_null( $this->mUserCaseDBKey ) ) {
1033  return $this->mUserCaseDBKey;
1034  } else {
1035  // If created via makeTitle(), $this->mUserCaseDBKey is not set.
1036  return $this->mDbkeyform;
1037  }
1038  }
1039 
1045  public function getNamespace() {
1046  return $this->mNamespace;
1047  }
1048 
1057  public function getContentModel( $flags = 0 ) {
1058  if ( !$this->mForcedContentModel
1059  && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE )
1060  && $this->getArticleID( $flags )
1061  ) {
1062  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1063  $linkCache->addLinkObj( $this ); # in case we already had an article ID
1064  $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
1065  }
1066 
1067  if ( !$this->mContentModel ) {
1068  $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
1069  }
1070 
1071  return $this->mContentModel;
1072  }
1073 
1080  public function hasContentModel( $id ) {
1081  return $this->getContentModel() == $id;
1082  }
1083 
1095  public function setContentModel( $model ) {
1096  $this->mContentModel = $model;
1097  $this->mForcedContentModel = true;
1098  }
1099 
1105  public function getNsText() {
1106  if ( $this->isExternal() ) {
1107  // This probably shouldn't even happen, except for interwiki transclusion.
1108  // If possible, use the canonical name for the foreign namespace.
1109  $nsText = MediaWikiServices::getInstance()->getNamespaceInfo()->
1110  getCanonicalName( $this->mNamespace );
1111  if ( $nsText !== false ) {
1112  return $nsText;
1113  }
1114  }
1115 
1116  try {
1117  $formatter = self::getTitleFormatter();
1118  return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
1119  } catch ( InvalidArgumentException $ex ) {
1120  wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
1121  return false;
1122  }
1123  }
1124 
1130  public function getSubjectNsText() {
1131  $services = MediaWikiServices::getInstance();
1132  return $services->getContentLanguage()->
1133  getNsText( $services->getNamespaceInfo()->getSubject( $this->mNamespace ) );
1134  }
1135 
1141  public function getTalkNsText() {
1142  $services = MediaWikiServices::getInstance();
1143  return $services->getContentLanguage()->
1144  getNsText( $services->getNamespaceInfo()->getTalk( $this->mNamespace ) );
1145  }
1146 
1158  public function canHaveTalkPage() {
1159  return MediaWikiServices::getInstance()->getNamespaceInfo()->canHaveTalkPage( $this );
1160  }
1161 
1167  public function canExist() {
1168  return $this->mNamespace >= NS_MAIN;
1169  }
1170 
1179  public function isWatchable() {
1180  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
1181  return $this->getText() !== '' && !$this->isExternal() &&
1182  $nsInfo->isWatchable( $this->mNamespace );
1183  }
1184 
1190  public function isSpecialPage() {
1191  return $this->mNamespace == NS_SPECIAL;
1192  }
1193 
1200  public function isSpecial( $name ) {
1201  if ( $this->isSpecialPage() ) {
1202  list( $thisName, /* $subpage */ ) =
1203  MediaWikiServices::getInstance()->getSpecialPageFactory()->
1204  resolveAlias( $this->mDbkeyform );
1205  if ( $name == $thisName ) {
1206  return true;
1207  }
1208  }
1209  return false;
1210  }
1211 
1218  public function fixSpecialName() {
1219  if ( $this->isSpecialPage() ) {
1220  $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
1221  list( $canonicalName, $par ) = $spFactory->resolveAlias( $this->mDbkeyform );
1222  if ( $canonicalName ) {
1223  $localName = $spFactory->getLocalNameFor( $canonicalName, $par );
1224  if ( $localName != $this->mDbkeyform ) {
1225  return self::makeTitle( NS_SPECIAL, $localName );
1226  }
1227  }
1228  }
1229  return $this;
1230  }
1231 
1242  public function inNamespace( $ns ) {
1243  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1244  equals( $this->mNamespace, $ns );
1245  }
1246 
1254  public function inNamespaces( /* ... */ ) {
1255  $namespaces = func_get_args();
1256  if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1257  $namespaces = $namespaces[0];
1258  }
1259 
1260  foreach ( $namespaces as $ns ) {
1261  if ( $this->inNamespace( $ns ) ) {
1262  return true;
1263  }
1264  }
1265 
1266  return false;
1267  }
1268 
1282  public function hasSubjectNamespace( $ns ) {
1283  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1284  subjectEquals( $this->mNamespace, $ns );
1285  }
1286 
1294  public function isContentPage() {
1295  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1296  isContent( $this->mNamespace );
1297  }
1298 
1305  public function isMovable() {
1306  if (
1307  !MediaWikiServices::getInstance()->getNamespaceInfo()->
1308  isMovable( $this->mNamespace ) || $this->isExternal()
1309  ) {
1310  // Interwiki title or immovable namespace. Hooks don't get to override here
1311  return false;
1312  }
1313 
1314  $result = true;
1315  Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1316  return $result;
1317  }
1318 
1329  public function isMainPage() {
1330  return $this->equals( self::newMainPage() );
1331  }
1332 
1338  public function isSubpage() {
1339  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1340  hasSubpages( $this->mNamespace )
1341  ? strpos( $this->getText(), '/' ) !== false
1342  : false;
1343  }
1344 
1350  public function isConversionTable() {
1351  // @todo ConversionTable should become a separate content model.
1352 
1353  return $this->mNamespace == NS_MEDIAWIKI &&
1354  strpos( $this->getText(), 'Conversiontable/' ) === 0;
1355  }
1356 
1362  public function isWikitextPage() {
1363  return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1364  }
1365 
1380  public function isSiteConfigPage() {
1381  return (
1382  $this->isSiteCssConfigPage()
1383  || $this->isSiteJsonConfigPage()
1384  || $this->isSiteJsConfigPage()
1385  );
1386  }
1387 
1394  public function isUserConfigPage() {
1395  return (
1396  $this->isUserCssConfigPage()
1397  || $this->isUserJsonConfigPage()
1398  || $this->isUserJsConfigPage()
1399  );
1400  }
1401 
1408  public function getSkinFromConfigSubpage() {
1409  $subpage = explode( '/', $this->mTextform );
1410  $subpage = $subpage[count( $subpage ) - 1];
1411  $lastdot = strrpos( $subpage, '.' );
1412  if ( $lastdot === false ) {
1413  return $subpage; # Never happens: only called for names ending in '.css'/'.json'/'.js'
1414  }
1415  return substr( $subpage, 0, $lastdot );
1416  }
1417 
1424  public function isUserCssConfigPage() {
1425  return (
1426  NS_USER == $this->mNamespace
1427  && $this->isSubpage()
1428  && $this->hasContentModel( CONTENT_MODEL_CSS )
1429  );
1430  }
1431 
1438  public function isUserJsonConfigPage() {
1439  return (
1440  NS_USER == $this->mNamespace
1441  && $this->isSubpage()
1442  && $this->hasContentModel( CONTENT_MODEL_JSON )
1443  );
1444  }
1445 
1452  public function isUserJsConfigPage() {
1453  return (
1454  NS_USER == $this->mNamespace
1455  && $this->isSubpage()
1457  );
1458  }
1459 
1466  public function isSiteCssConfigPage() {
1467  return (
1468  NS_MEDIAWIKI == $this->mNamespace
1469  && (
1471  // paranoia - a MediaWiki: namespace page with mismatching extension and content
1472  // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1473  || substr( $this->mDbkeyform, -4 ) === '.css'
1474  )
1475  );
1476  }
1477 
1484  public function isSiteJsonConfigPage() {
1485  return (
1486  NS_MEDIAWIKI == $this->mNamespace
1487  && (
1489  // paranoia - a MediaWiki: namespace page with mismatching extension and content
1490  // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1491  || substr( $this->mDbkeyform, -5 ) === '.json'
1492  )
1493  );
1494  }
1495 
1502  public function isSiteJsConfigPage() {
1503  return (
1504  NS_MEDIAWIKI == $this->mNamespace
1505  && (
1507  // paranoia - a MediaWiki: namespace page with mismatching extension and content
1508  // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1509  || substr( $this->mDbkeyform, -3 ) === '.js'
1510  )
1511  );
1512  }
1513 
1520  public function isRawHtmlMessage() {
1521  global $wgRawHtmlMessages;
1522 
1523  if ( !$this->inNamespace( NS_MEDIAWIKI ) ) {
1524  return false;
1525  }
1526  $message = lcfirst( $this->getRootTitle()->getDBkey() );
1527  return in_array( $message, $wgRawHtmlMessages, true );
1528  }
1529 
1535  public function isTalkPage() {
1536  return MediaWikiServices::getInstance()->getNamespaceInfo()->
1537  isTalk( $this->mNamespace );
1538  }
1539 
1549  public function getTalkPage() {
1550  return self::castFromLinkTarget(
1551  MediaWikiServices::getInstance()->getNamespaceInfo()->getTalkPage( $this ) );
1552  }
1553 
1563  public function getTalkPageIfDefined() {
1564  if ( !$this->canHaveTalkPage() ) {
1565  return null;
1566  }
1567 
1568  return $this->getTalkPage();
1569  }
1570 
1578  public function getSubjectPage() {
1579  return self::castFromLinkTarget(
1580  MediaWikiServices::getInstance()->getNamespaceInfo()->getSubjectPage( $this ) );
1581  }
1582 
1592  public function getOtherPage() {
1593  return self::castFromLinkTarget(
1594  MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociatedPage( $this ) );
1595  }
1596 
1602  public function getDefaultNamespace() {
1603  return $this->mDefaultNamespace;
1604  }
1605 
1613  public function getFragment() {
1614  return $this->mFragment;
1615  }
1616 
1623  public function hasFragment() {
1624  return $this->mFragment !== '';
1625  }
1626 
1632  public function getFragmentForURL() {
1633  if ( !$this->hasFragment() ) {
1634  return '';
1635  } elseif ( $this->isExternal() ) {
1636  // Note: If the interwiki is unknown, it's treated as a namespace on the local wiki,
1637  // so we treat it like a local interwiki.
1638  $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1639  if ( $interwiki && !$interwiki->isLocal() ) {
1640  return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->mFragment );
1641  }
1642  }
1643 
1644  return '#' . Sanitizer::escapeIdForLink( $this->mFragment );
1645  }
1646 
1659  public function setFragment( $fragment ) {
1660  $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1661  }
1662 
1670  public function createFragmentTarget( $fragment ) {
1671  return self::makeTitle(
1672  $this->mNamespace,
1673  $this->getText(),
1674  $fragment,
1675  $this->mInterwiki
1676  );
1677  }
1678 
1686  private function prefix( $name ) {
1687  $p = '';
1688  if ( $this->isExternal() ) {
1689  $p = $this->mInterwiki . ':';
1690  }
1691 
1692  if ( $this->mNamespace != 0 ) {
1693  $nsText = $this->getNsText();
1694 
1695  if ( $nsText === false ) {
1696  // See T165149. Awkward, but better than erroneously linking to the main namespace.
1697  $nsText = MediaWikiServices::getInstance()->getContentLanguage()->
1698  getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}";
1699  }
1700 
1701  $p .= $nsText . ':';
1702  }
1703  return $p . $name;
1704  }
1705 
1712  public function getPrefixedDBkey() {
1713  $s = $this->prefix( $this->mDbkeyform );
1714  $s = strtr( $s, ' ', '_' );
1715  return $s;
1716  }
1717 
1724  public function getPrefixedText() {
1725  if ( $this->prefixedText === null ) {
1726  $s = $this->prefix( $this->mTextform );
1727  $s = strtr( $s, '_', ' ' );
1728  $this->prefixedText = $s;
1729  }
1730  return $this->prefixedText;
1731  }
1732 
1738  public function __toString() {
1739  return $this->getPrefixedText();
1740  }
1741 
1748  public function getFullText() {
1749  $text = $this->getPrefixedText();
1750  if ( $this->hasFragment() ) {
1751  $text .= '#' . $this->mFragment;
1752  }
1753  return $text;
1754  }
1755 
1771  public function getRootText() {
1772  if (
1773  !MediaWikiServices::getInstance()->getNamespaceInfo()->
1774  hasSubpages( $this->mNamespace )
1775  || strtok( $this->getText(), '/' ) === false
1776  ) {
1777  return $this->getText();
1778  }
1779 
1780  return strtok( $this->getText(), '/' );
1781  }
1782 
1795  public function getRootTitle() {
1796  $title = self::makeTitleSafe( $this->mNamespace, $this->getRootText() );
1797  Assert::postcondition(
1798  $title !== null,
1799  'makeTitleSafe() should always return a Title for the text returned by getRootText().'
1800  );
1801  return $title;
1802  }
1803 
1818  public function getBaseText() {
1819  $text = $this->getText();
1820  if (
1821  !MediaWikiServices::getInstance()->getNamespaceInfo()->
1822  hasSubpages( $this->mNamespace )
1823  ) {
1824  return $text;
1825  }
1826 
1827  $lastSlashPos = strrpos( $text, '/' );
1828  // Don't discard the real title if there's no subpage involved
1829  if ( $lastSlashPos === false ) {
1830  return $text;
1831  }
1832 
1833  return substr( $text, 0, $lastSlashPos );
1834  }
1835 
1848  public function getBaseTitle() {
1849  $title = self::makeTitleSafe( $this->mNamespace, $this->getBaseText() );
1850  Assert::postcondition(
1851  $title !== null,
1852  'makeTitleSafe() should always return a Title for the text returned by getBaseText().'
1853  );
1854  return $title;
1855  }
1856 
1868  public function getSubpageText() {
1869  if (
1870  !MediaWikiServices::getInstance()->getNamespaceInfo()->
1871  hasSubpages( $this->mNamespace )
1872  ) {
1873  return $this->mTextform;
1874  }
1875  $parts = explode( '/', $this->mTextform );
1876  return $parts[count( $parts ) - 1];
1877  }
1878 
1892  public function getSubpage( $text ) {
1893  return self::makeTitleSafe(
1894  $this->mNamespace,
1895  $this->getText() . '/' . $text,
1896  '',
1897  $this->mInterwiki
1898  );
1899  }
1900 
1906  public function getSubpageUrlForm() {
1907  $text = $this->getSubpageText();
1908  $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1909  return $text;
1910  }
1911 
1917  public function getPrefixedURL() {
1918  $s = $this->prefix( $this->mDbkeyform );
1919  $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1920  return $s;
1921  }
1922 
1936  private static function fixUrlQueryArgs( $query, $query2 = false ) {
1937  if ( $query2 !== false ) {
1938  wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1939  "method called with a second parameter is deprecated. Add your " .
1940  "parameter to an array passed as the first parameter.", "1.19" );
1941  }
1942  if ( is_array( $query ) ) {
1943  $query = wfArrayToCgi( $query );
1944  }
1945  if ( $query2 ) {
1946  if ( is_string( $query2 ) ) {
1947  // $query2 is a string, we will consider this to be
1948  // a deprecated $variant argument and add it to the query
1949  $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1950  } else {
1951  $query2 = wfArrayToCgi( $query2 );
1952  }
1953  // If we have $query content add a & to it first
1954  if ( $query ) {
1955  $query .= '&';
1956  }
1957  // Now append the queries together
1958  $query .= $query2;
1959  }
1960  return $query;
1961  }
1962 
1974  public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1975  $query = self::fixUrlQueryArgs( $query, $query2 );
1976 
1977  # Hand off all the decisions on urls to getLocalURL
1978  $url = $this->getLocalURL( $query );
1979 
1980  # Expand the url to make it a full url. Note that getLocalURL has the
1981  # potential to output full urls for a variety of reasons, so we use
1982  # wfExpandUrl instead of simply prepending $wgServer
1983  $url = wfExpandUrl( $url, $proto );
1984 
1985  # Finally, add the fragment.
1986  $url .= $this->getFragmentForURL();
1987  // Avoid PHP 7.1 warning from passing $this by reference
1988  $titleRef = $this;
1989  Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
1990  return $url;
1991  }
1992 
2009  public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
2010  $target = $this;
2011  if ( $this->isExternal() ) {
2012  $target = SpecialPage::getTitleFor(
2013  'GoToInterwiki',
2014  $this->getPrefixedDBkey()
2015  );
2016  }
2017  return $target->getFullURL( $query, false, $proto );
2018  }
2019 
2043  public function getLocalURL( $query = '', $query2 = false ) {
2045 
2046  $query = self::fixUrlQueryArgs( $query, $query2 );
2047 
2048  $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
2049  if ( $interwiki ) {
2050  $namespace = $this->getNsText();
2051  if ( $namespace != '' ) {
2052  # Can this actually happen? Interwikis shouldn't be parsed.
2053  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
2054  $namespace .= ':';
2055  }
2056  $url = $interwiki->getURL( $namespace . $this->mDbkeyform );
2057  $url = wfAppendQuery( $url, $query );
2058  } else {
2059  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
2060  if ( $query == '' ) {
2061  $url = str_replace( '$1', $dbkey, $wgArticlePath );
2062  // Avoid PHP 7.1 warning from passing $this by reference
2063  $titleRef = $this;
2064  Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
2065  } else {
2067  $url = false;
2068  $matches = [];
2069 
2070  if ( !empty( $wgActionPaths )
2071  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
2072  ) {
2073  $action = urldecode( $matches[2] );
2074  if ( isset( $wgActionPaths[$action] ) ) {
2075  $query = $matches[1];
2076  if ( isset( $matches[4] ) ) {
2077  $query .= $matches[4];
2078  }
2079  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
2080  if ( $query != '' ) {
2081  $url = wfAppendQuery( $url, $query );
2082  }
2083  }
2084  }
2085 
2086  if ( $url === false
2087  && $wgVariantArticlePath
2088  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
2089  && $this->getPageLanguage()->equals(
2090  MediaWikiServices::getInstance()->getContentLanguage() )
2091  && $this->getPageLanguage()->hasVariants()
2092  ) {
2093  $variant = urldecode( $matches[1] );
2094  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
2095  // Only do the variant replacement if the given variant is a valid
2096  // variant for the page's language.
2097  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
2098  $url = str_replace( '$1', $dbkey, $url );
2099  }
2100  }
2101 
2102  if ( $url === false ) {
2103  if ( $query == '-' ) {
2104  $query = '';
2105  }
2106  $url = "{$wgScript}?title={$dbkey}&{$query}";
2107  }
2108  }
2109  // Avoid PHP 7.1 warning from passing $this by reference
2110  $titleRef = $this;
2111  Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
2112 
2113  // @todo FIXME: This causes breakage in various places when we
2114  // actually expected a local URL and end up with dupe prefixes.
2115  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
2116  $url = $wgServer . $url;
2117  }
2118  }
2119  // Avoid PHP 7.1 warning from passing $this by reference
2120  $titleRef = $this;
2121  Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
2122  return $url;
2123  }
2124 
2142  public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
2143  if ( $this->isExternal() || $proto !== false ) {
2144  $ret = $this->getFullURL( $query, $query2, $proto );
2145  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
2146  $ret = $this->getFragmentForURL();
2147  } else {
2148  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
2149  }
2150  return $ret;
2151  }
2152 
2167  public function getInternalURL( $query = '', $query2 = false ) {
2168  global $wgInternalServer, $wgServer;
2169  $query = self::fixUrlQueryArgs( $query, $query2 );
2170  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
2171  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
2172  // Avoid PHP 7.1 warning from passing $this by reference
2173  $titleRef = $this;
2174  Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
2175  return $url;
2176  }
2177 
2191  public function getCanonicalURL( $query = '', $query2 = false ) {
2192  $query = self::fixUrlQueryArgs( $query, $query2 );
2193  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
2194  // Avoid PHP 7.1 warning from passing $this by reference
2195  $titleRef = $this;
2196  Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
2197  return $url;
2198  }
2199 
2205  public function getEditURL() {
2206  if ( $this->isExternal() ) {
2207  return '';
2208  }
2209  $s = $this->getLocalURL( 'action=edit' );
2210 
2211  return $s;
2212  }
2213 
2234  public function quickUserCan( $action, $user = null ) {
2235  return $this->userCan( $action, $user, false );
2236  }
2237 
2253  public function userCan( $action, $user = null, $rigor = PermissionManager::RIGOR_SECURE ) {
2254  if ( !$user instanceof User ) {
2255  global $wgUser;
2256  $user = $wgUser;
2257  }
2258 
2259  // TODO: this is for b/c, eventually will be removed
2260  if ( $rigor === true ) {
2261  $rigor = PermissionManager::RIGOR_SECURE; // b/c
2262  } elseif ( $rigor === false ) {
2263  $rigor = PermissionManager::RIGOR_QUICK; // b/c
2264  }
2265 
2266  return MediaWikiServices::getInstance()->getPermissionManager()
2267  ->userCan( $action, $user, $this, $rigor );
2268  }
2269 
2291  public function getUserPermissionsErrors(
2292  $action, $user, $rigor = PermissionManager::RIGOR_SECURE, $ignoreErrors = []
2293  ) {
2294  // TODO: this is for b/c, eventually will be removed
2295  if ( $rigor === true ) {
2296  $rigor = PermissionManager::RIGOR_SECURE; // b/c
2297  } elseif ( $rigor === false ) {
2298  $rigor = PermissionManager::RIGOR_QUICK; // b/c
2299  }
2300 
2301  return MediaWikiServices::getInstance()->getPermissionManager()
2302  ->getPermissionErrors( $action, $user, $this, $rigor, $ignoreErrors );
2303  }
2304 
2312  public static function getFilteredRestrictionTypes( $exists = true ) {
2313  global $wgRestrictionTypes;
2314  $types = $wgRestrictionTypes;
2315  if ( $exists ) {
2316  # Remove the create restriction for existing titles
2317  $types = array_diff( $types, [ 'create' ] );
2318  } else {
2319  # Only the create and upload restrictions apply to non-existing titles
2320  $types = array_intersect( $types, [ 'create', 'upload' ] );
2321  }
2322  return $types;
2323  }
2324 
2330  public function getRestrictionTypes() {
2331  if ( $this->isSpecialPage() ) {
2332  return [];
2333  }
2334 
2335  $types = self::getFilteredRestrictionTypes( $this->exists() );
2336 
2337  if ( $this->mNamespace != NS_FILE ) {
2338  # Remove the upload restriction for non-file titles
2339  $types = array_diff( $types, [ 'upload' ] );
2340  }
2341 
2342  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2343 
2344  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2345  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2346 
2347  return $types;
2348  }
2349 
2357  public function getTitleProtection() {
2358  $protection = $this->getTitleProtectionInternal();
2359  if ( $protection ) {
2360  if ( $protection['permission'] == 'sysop' ) {
2361  $protection['permission'] = 'editprotected'; // B/C
2362  }
2363  if ( $protection['permission'] == 'autoconfirmed' ) {
2364  $protection['permission'] = 'editsemiprotected'; // B/C
2365  }
2366  }
2367  return $protection;
2368  }
2369 
2380  protected function getTitleProtectionInternal() {
2381  // Can't protect pages in special namespaces
2382  if ( $this->mNamespace < 0 ) {
2383  return false;
2384  }
2385 
2386  // Can't protect pages that exist.
2387  if ( $this->exists() ) {
2388  return false;
2389  }
2390 
2391  if ( $this->mTitleProtection === null ) {
2392  $dbr = wfGetDB( DB_REPLICA );
2393  $commentStore = CommentStore::getStore();
2394  $commentQuery = $commentStore->getJoin( 'pt_reason' );
2395  $res = $dbr->select(
2396  [ 'protected_titles' ] + $commentQuery['tables'],
2397  [
2398  'user' => 'pt_user',
2399  'expiry' => 'pt_expiry',
2400  'permission' => 'pt_create_perm'
2401  ] + $commentQuery['fields'],
2402  [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2403  __METHOD__,
2404  [],
2405  $commentQuery['joins']
2406  );
2407 
2408  // fetchRow returns false if there are no rows.
2409  $row = $dbr->fetchRow( $res );
2410  if ( $row ) {
2411  $this->mTitleProtection = [
2412  'user' => $row['user'],
2413  'expiry' => $dbr->decodeExpiry( $row['expiry'] ),
2414  'permission' => $row['permission'],
2415  'reason' => $commentStore->getComment( 'pt_reason', $row )->text,
2416  ];
2417  } else {
2418  $this->mTitleProtection = false;
2419  }
2420  }
2421  return $this->mTitleProtection;
2422  }
2423 
2427  public function deleteTitleProtection() {
2428  $dbw = wfGetDB( DB_MASTER );
2429 
2430  $dbw->delete(
2431  'protected_titles',
2432  [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2433  __METHOD__
2434  );
2435  $this->mTitleProtection = false;
2436  }
2437 
2445  public function isSemiProtected( $action = 'edit' ) {
2447 
2448  $restrictions = $this->getRestrictions( $action );
2450  if ( !$restrictions || !$semi ) {
2451  // Not protected, or all protection is full protection
2452  return false;
2453  }
2454 
2455  // Remap autoconfirmed to editsemiprotected for BC
2456  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2457  $semi[$key] = 'editsemiprotected';
2458  }
2459  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2460  $restrictions[$key] = 'editsemiprotected';
2461  }
2462 
2463  return !array_diff( $restrictions, $semi );
2464  }
2465 
2473  public function isProtected( $action = '' ) {
2474  global $wgRestrictionLevels;
2475 
2476  $restrictionTypes = $this->getRestrictionTypes();
2477 
2478  # Special pages have inherent protection
2479  if ( $this->isSpecialPage() ) {
2480  return true;
2481  }
2482 
2483  # Check regular protection levels
2484  foreach ( $restrictionTypes as $type ) {
2485  if ( $action == $type || $action == '' ) {
2486  $r = $this->getRestrictions( $type );
2487  foreach ( $wgRestrictionLevels as $level ) {
2488  if ( in_array( $level, $r ) && $level != '' ) {
2489  return true;
2490  }
2491  }
2492  }
2493  }
2494 
2495  return false;
2496  }
2497 
2505  public function isNamespaceProtected( User $user ) {
2506  global $wgNamespaceProtection;
2507 
2508  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2509  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2510  if ( $right != '' && !$user->isAllowed( $right ) ) {
2511  return true;
2512  }
2513  }
2514  }
2515  return false;
2516  }
2517 
2523  public function isCascadeProtected() {
2524  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2525  return ( $sources > 0 );
2526  }
2527 
2537  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2538  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2539  }
2540 
2554  public function getCascadeProtectionSources( $getPages = true ) {
2555  $pagerestrictions = [];
2556 
2557  if ( $this->mCascadeSources !== null && $getPages ) {
2559  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2560  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2561  }
2562 
2563  $dbr = wfGetDB( DB_REPLICA );
2564 
2565  if ( $this->mNamespace == NS_FILE ) {
2566  $tables = [ 'imagelinks', 'page_restrictions' ];
2567  $where_clauses = [
2568  'il_to' => $this->mDbkeyform,
2569  'il_from=pr_page',
2570  'pr_cascade' => 1
2571  ];
2572  } else {
2573  $tables = [ 'templatelinks', 'page_restrictions' ];
2574  $where_clauses = [
2575  'tl_namespace' => $this->mNamespace,
2576  'tl_title' => $this->mDbkeyform,
2577  'tl_from=pr_page',
2578  'pr_cascade' => 1
2579  ];
2580  }
2581 
2582  if ( $getPages ) {
2583  $cols = [ 'pr_page', 'page_namespace', 'page_title',
2584  'pr_expiry', 'pr_type', 'pr_level' ];
2585  $where_clauses[] = 'page_id=pr_page';
2586  $tables[] = 'page';
2587  } else {
2588  $cols = [ 'pr_expiry' ];
2589  }
2590 
2591  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2592 
2593  $sources = $getPages ? [] : false;
2594  $now = wfTimestampNow();
2595 
2596  foreach ( $res as $row ) {
2597  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2598  if ( $expiry > $now ) {
2599  if ( $getPages ) {
2600  $page_id = $row->pr_page;
2601  $page_ns = $row->page_namespace;
2602  $page_title = $row->page_title;
2603  $sources[$page_id] = self::makeTitle( $page_ns, $page_title );
2604  # Add groups needed for each restriction type if its not already there
2605  # Make sure this restriction type still exists
2606 
2607  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2608  $pagerestrictions[$row->pr_type] = [];
2609  }
2610 
2611  if (
2612  isset( $pagerestrictions[$row->pr_type] )
2613  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2614  ) {
2615  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2616  }
2617  } else {
2618  $sources = true;
2619  }
2620  }
2621  }
2622 
2623  if ( $getPages ) {
2624  $this->mCascadeSources = $sources;
2625  $this->mCascadingRestrictions = $pagerestrictions;
2626  } else {
2627  $this->mHasCascadingRestrictions = $sources;
2628  }
2629 
2630  return [ $sources, $pagerestrictions ];
2631  }
2632 
2640  public function areRestrictionsLoaded() {
2642  }
2643 
2653  public function getRestrictions( $action ) {
2654  if ( !$this->mRestrictionsLoaded ) {
2655  $this->loadRestrictions();
2656  }
2657  return $this->mRestrictions[$action] ?? [];
2658  }
2659 
2667  public function getAllRestrictions() {
2668  if ( !$this->mRestrictionsLoaded ) {
2669  $this->loadRestrictions();
2670  }
2671  return $this->mRestrictions;
2672  }
2673 
2681  public function getRestrictionExpiry( $action ) {
2682  if ( !$this->mRestrictionsLoaded ) {
2683  $this->loadRestrictions();
2684  }
2685  return $this->mRestrictionsExpiry[$action] ?? false;
2686  }
2687 
2694  if ( !$this->mRestrictionsLoaded ) {
2695  $this->loadRestrictions();
2696  }
2697 
2699  }
2700 
2712  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2713  // This function will only read rows from a table that we migrated away
2714  // from before adding READ_LATEST support to loadRestrictions, so we
2715  // don't need to support reading from DB_MASTER here.
2716  $dbr = wfGetDB( DB_REPLICA );
2717 
2718  $restrictionTypes = $this->getRestrictionTypes();
2719 
2720  foreach ( $restrictionTypes as $type ) {
2721  $this->mRestrictions[$type] = [];
2722  $this->mRestrictionsExpiry[$type] = 'infinity';
2723  }
2724 
2725  $this->mCascadeRestriction = false;
2726 
2727  # Backwards-compatibility: also load the restrictions from the page record (old format).
2728  if ( $oldFashionedRestrictions !== null ) {
2729  $this->mOldRestrictions = $oldFashionedRestrictions;
2730  }
2731 
2732  if ( $this->mOldRestrictions === false ) {
2733  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
2734  $linkCache->addLinkObj( $this ); # in case we already had an article ID
2735  $this->mOldRestrictions = $linkCache->getGoodLinkFieldObj( $this, 'restrictions' );
2736  }
2737 
2738  if ( $this->mOldRestrictions != '' ) {
2739  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2740  $temp = explode( '=', trim( $restrict ) );
2741  if ( count( $temp ) == 1 ) {
2742  // old old format should be treated as edit/move restriction
2743  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2744  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2745  } else {
2746  $restriction = trim( $temp[1] );
2747  if ( $restriction != '' ) { // some old entries are empty
2748  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2749  }
2750  }
2751  }
2752  }
2753 
2754  if ( count( $rows ) ) {
2755  # Current system - load second to make them override.
2756  $now = wfTimestampNow();
2757 
2758  # Cycle through all the restrictions.
2759  foreach ( $rows as $row ) {
2760  // Don't take care of restrictions types that aren't allowed
2761  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2762  continue;
2763  }
2764 
2765  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2766 
2767  // Only apply the restrictions if they haven't expired!
2768  if ( !$expiry || $expiry > $now ) {
2769  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2770  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2771 
2772  $this->mCascadeRestriction |= $row->pr_cascade;
2773  }
2774  }
2775  }
2776 
2777  $this->mRestrictionsLoaded = true;
2778  }
2779 
2790  public function loadRestrictions( $oldFashionedRestrictions = null, $flags = 0 ) {
2791  $readLatest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
2792  if ( $this->mRestrictionsLoaded && !$readLatest ) {
2793  return;
2794  }
2795 
2796  // TODO: should probably pass $flags into getArticleID, but it seems hacky
2797  // to mix READ_LATEST and GAID_FOR_UPDATE, even if they have the same value.
2798  // Maybe deprecate GAID_FOR_UPDATE now that we implement IDBAccessObject?
2799  $id = $this->getArticleID();
2800  if ( $id ) {
2801  $fname = __METHOD__;
2802  $loadRestrictionsFromDb = function ( IDatabase $dbr ) use ( $fname, $id ) {
2803  return iterator_to_array(
2804  $dbr->select(
2805  'page_restrictions',
2806  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
2807  [ 'pr_page' => $id ],
2808  $fname
2809  )
2810  );
2811  };
2812 
2813  if ( $readLatest ) {
2814  $dbr = wfGetDB( DB_MASTER );
2815  $rows = $loadRestrictionsFromDb( $dbr );
2816  } else {
2817  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2818  $rows = $cache->getWithSetCallback(
2819  // Page protections always leave a new null revision
2820  $cache->makeKey( 'page-restrictions', 'v1', $id, $this->getLatestRevID() ),
2821  $cache::TTL_DAY,
2822  function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
2823  $dbr = wfGetDB( DB_REPLICA );
2824 
2825  $setOpts += Database::getCacheSetOptions( $dbr );
2826  $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2827  if ( $lb->hasOrMadeRecentMasterChanges() ) {
2828  // @TODO: cleanup Title cache and caller assumption mess in general
2829  $ttl = WANObjectCache::TTL_UNCACHEABLE;
2830  }
2831 
2832  return $loadRestrictionsFromDb( $dbr );
2833  }
2834  );
2835  }
2836 
2837  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2838  } else {
2839  $title_protection = $this->getTitleProtectionInternal();
2840 
2841  if ( $title_protection ) {
2842  $now = wfTimestampNow();
2843  $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
2844 
2845  if ( !$expiry || $expiry > $now ) {
2846  // Apply the restrictions
2847  $this->mRestrictionsExpiry['create'] = $expiry;
2848  $this->mRestrictions['create'] =
2849  explode( ',', trim( $title_protection['permission'] ) );
2850  } else { // Get rid of the old restrictions
2851  $this->mTitleProtection = false;
2852  }
2853  } else {
2854  $this->mRestrictionsExpiry['create'] = 'infinity';
2855  }
2856  $this->mRestrictionsLoaded = true;
2857  }
2858  }
2859 
2864  public function flushRestrictions() {
2865  $this->mRestrictionsLoaded = false;
2866  $this->mTitleProtection = null;
2867  }
2868 
2874  static function purgeExpiredRestrictions() {
2875  if ( wfReadOnly() ) {
2876  return;
2877  }
2878 
2880  wfGetDB( DB_MASTER ),
2881  __METHOD__,
2882  function ( IDatabase $dbw, $fname ) {
2883  $config = MediaWikiServices::getInstance()->getMainConfig();
2884  $ids = $dbw->selectFieldValues(
2885  'page_restrictions',
2886  'pr_id',
2887  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
2888  $fname,
2889  [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
2890  );
2891  if ( $ids ) {
2892  $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
2893  }
2894  }
2895  ) );
2896 
2898  wfGetDB( DB_MASTER ),
2899  __METHOD__,
2900  function ( IDatabase $dbw, $fname ) {
2901  $dbw->delete(
2902  'protected_titles',
2903  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
2904  $fname
2905  );
2906  }
2907  ) );
2908  }
2909 
2915  public function hasSubpages() {
2916  if (
2917  !MediaWikiServices::getInstance()->getNamespaceInfo()->
2918  hasSubpages( $this->mNamespace )
2919  ) {
2920  # Duh
2921  return false;
2922  }
2923 
2924  # We dynamically add a member variable for the purpose of this method
2925  # alone to cache the result. There's no point in having it hanging
2926  # around uninitialized in every Title object; therefore we only add it
2927  # if needed and don't declare it statically.
2928  if ( $this->mHasSubpages === null ) {
2929  $this->mHasSubpages = false;
2930  $subpages = $this->getSubpages( 1 );
2931  if ( $subpages instanceof TitleArray ) {
2932  $this->mHasSubpages = (bool)$subpages->current();
2933  }
2934  }
2935 
2936  return $this->mHasSubpages;
2937  }
2938 
2946  public function getSubpages( $limit = -1 ) {
2947  if (
2948  !MediaWikiServices::getInstance()->getNamespaceInfo()->
2949  hasSubpages( $this->mNamespace )
2950  ) {
2951  return [];
2952  }
2953 
2954  $dbr = wfGetDB( DB_REPLICA );
2955  $conds['page_namespace'] = $this->mNamespace;
2956  $conds[] = 'page_title ' . $dbr->buildLike( $this->mDbkeyform . '/', $dbr->anyString() );
2957  $options = [];
2958  if ( $limit > -1 ) {
2959  $options['LIMIT'] = $limit;
2960  }
2962  $dbr->select( 'page',
2963  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
2964  $conds,
2965  __METHOD__,
2966  $options
2967  )
2968  );
2969  }
2970 
2976  public function isDeleted() {
2977  if ( $this->mNamespace < 0 ) {
2978  $n = 0;
2979  } else {
2980  $dbr = wfGetDB( DB_REPLICA );
2981 
2982  $n = $dbr->selectField( 'archive', 'COUNT(*)',
2983  [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
2984  __METHOD__
2985  );
2986  if ( $this->mNamespace == NS_FILE ) {
2987  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
2988  [ 'fa_name' => $this->mDbkeyform ],
2989  __METHOD__
2990  );
2991  }
2992  }
2993  return (int)$n;
2994  }
2995 
3001  public function isDeletedQuick() {
3002  if ( $this->mNamespace < 0 ) {
3003  return false;
3004  }
3005  $dbr = wfGetDB( DB_REPLICA );
3006  $deleted = (bool)$dbr->selectField( 'archive', '1',
3007  [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
3008  __METHOD__
3009  );
3010  if ( !$deleted && $this->mNamespace == NS_FILE ) {
3011  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3012  [ 'fa_name' => $this->mDbkeyform ],
3013  __METHOD__
3014  );
3015  }
3016  return $deleted;
3017  }
3018 
3027  public function getArticleID( $flags = 0 ) {
3028  if ( $this->mNamespace < 0 ) {
3029  $this->mArticleID = 0;
3030  return $this->mArticleID;
3031  }
3032  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3033  if ( $flags & self::GAID_FOR_UPDATE ) {
3034  $oldUpdate = $linkCache->forUpdate( true );
3035  $linkCache->clearLink( $this );
3036  $this->mArticleID = $linkCache->addLinkObj( $this );
3037  $linkCache->forUpdate( $oldUpdate );
3038  } elseif ( $this->mArticleID == -1 ) {
3039  $this->mArticleID = $linkCache->addLinkObj( $this );
3040  }
3041  return $this->mArticleID;
3042  }
3043 
3051  public function isRedirect( $flags = 0 ) {
3052  if ( !is_null( $this->mRedirect ) ) {
3053  return $this->mRedirect;
3054  }
3055  if ( !$this->getArticleID( $flags ) ) {
3056  $this->mRedirect = false;
3057  return $this->mRedirect;
3058  }
3059 
3060  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3061  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3062  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3063  if ( $cached === null ) {
3064  # Trust LinkCache's state over our own
3065  # LinkCache is telling us that the page doesn't exist, despite there being cached
3066  # data relating to an existing page in $this->mArticleID. Updaters should clear
3067  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3068  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3069  # LinkCache to refresh its data from the master.
3070  $this->mRedirect = false;
3071  return $this->mRedirect;
3072  }
3073 
3074  $this->mRedirect = (bool)$cached;
3075 
3076  return $this->mRedirect;
3077  }
3078 
3086  public function getLength( $flags = 0 ) {
3087  if ( $this->mLength != -1 ) {
3088  return $this->mLength;
3089  }
3090  if ( !$this->getArticleID( $flags ) ) {
3091  $this->mLength = 0;
3092  return $this->mLength;
3093  }
3094  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3095  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3096  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3097  if ( $cached === null ) {
3098  # Trust LinkCache's state over our own, as for isRedirect()
3099  $this->mLength = 0;
3100  return $this->mLength;
3101  }
3102 
3103  $this->mLength = intval( $cached );
3104 
3105  return $this->mLength;
3106  }
3107 
3114  public function getLatestRevID( $flags = 0 ) {
3115  if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3116  return intval( $this->mLatestID );
3117  }
3118  if ( !$this->getArticleID( $flags ) ) {
3119  $this->mLatestID = 0;
3120  return $this->mLatestID;
3121  }
3122  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3123  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3124  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3125  if ( $cached === null ) {
3126  # Trust LinkCache's state over our own, as for isRedirect()
3127  $this->mLatestID = 0;
3128  return $this->mLatestID;
3129  }
3130 
3131  $this->mLatestID = intval( $cached );
3132 
3133  return $this->mLatestID;
3134  }
3135 
3146  public function resetArticleID( $newid ) {
3147  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3148  $linkCache->clearLink( $this );
3149 
3150  if ( $newid === false ) {
3151  $this->mArticleID = -1;
3152  } else {
3153  $this->mArticleID = intval( $newid );
3154  }
3155  $this->mRestrictionsLoaded = false;
3156  $this->mRestrictions = [];
3157  $this->mOldRestrictions = false;
3158  $this->mRedirect = null;
3159  $this->mLength = -1;
3160  $this->mLatestID = false;
3161  $this->mContentModel = false;
3162  $this->mEstimateRevisions = null;
3163  $this->mPageLanguage = false;
3164  $this->mDbPageLanguage = false;
3165  $this->mIsBigDeletion = null;
3166  }
3167 
3168  public static function clearCaches() {
3169  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3170  $linkCache->clear();
3171 
3172  $titleCache = self::getTitleCache();
3173  $titleCache->clear();
3174  }
3175 
3183  public static function capitalize( $text, $ns = NS_MAIN ) {
3184  $services = MediaWikiServices::getInstance();
3185  if ( $services->getNamespaceInfo()->isCapitalized( $ns ) ) {
3186  return MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $text );
3187  } else {
3188  return $text;
3189  }
3190  }
3191 
3204  private function secureAndSplit() {
3205  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3206  // the parsing code with Title, while avoiding massive refactoring.
3207  // @todo: get rid of secureAndSplit, refactor parsing code.
3208  // @note: getTitleParser() returns a TitleParser implementation which does not have a
3209  // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3211  $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3212  // MalformedTitleException can be thrown here
3213  $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace );
3214 
3215  # Fill fields
3216  $this->setFragment( '#' . $parts['fragment'] );
3217  $this->mInterwiki = $parts['interwiki'];
3218  $this->mLocalInterwiki = $parts['local_interwiki'];
3219  $this->mNamespace = $parts['namespace'];
3220  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3221 
3222  $this->mDbkeyform = $parts['dbkey'];
3223  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3224  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3225 
3226  # We already know that some pages won't be in the database!
3227  if ( $this->isExternal() || $this->isSpecialPage() ) {
3228  $this->mArticleID = 0;
3229  }
3230 
3231  return true;
3232  }
3233 
3246  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3247  if ( count( $options ) > 0 ) {
3248  $db = wfGetDB( DB_MASTER );
3249  } else {
3250  $db = wfGetDB( DB_REPLICA );
3251  }
3252 
3253  $res = $db->select(
3254  [ 'page', $table ],
3255  self::getSelectFields(),
3256  [
3257  "{$prefix}_from=page_id",
3258  "{$prefix}_namespace" => $this->mNamespace,
3259  "{$prefix}_title" => $this->mDbkeyform ],
3260  __METHOD__,
3261  $options
3262  );
3263 
3264  $retVal = [];
3265  if ( $res->numRows() ) {
3266  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3267  foreach ( $res as $row ) {
3268  $titleObj = self::makeTitle( $row->page_namespace, $row->page_title );
3269  if ( $titleObj ) {
3270  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3271  $retVal[] = $titleObj;
3272  }
3273  }
3274  }
3275  return $retVal;
3276  }
3277 
3288  public function getTemplateLinksTo( $options = [] ) {
3289  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3290  }
3291 
3304  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3305  $id = $this->getArticleID();
3306 
3307  # If the page doesn't exist; there can't be any link from this page
3308  if ( !$id ) {
3309  return [];
3310  }
3311 
3312  $db = wfGetDB( DB_REPLICA );
3313 
3314  $blNamespace = "{$prefix}_namespace";
3315  $blTitle = "{$prefix}_title";
3316 
3317  $pageQuery = WikiPage::getQueryInfo();
3318  $res = $db->select(
3319  [ $table, 'nestpage' => $pageQuery['tables'] ],
3320  array_merge(
3321  [ $blNamespace, $blTitle ],
3322  $pageQuery['fields']
3323  ),
3324  [ "{$prefix}_from" => $id ],
3325  __METHOD__,
3326  $options,
3327  [ 'nestpage' => [
3328  'LEFT JOIN',
3329  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3330  ] ] + $pageQuery['joins']
3331  );
3332 
3333  $retVal = [];
3334  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3335  foreach ( $res as $row ) {
3336  if ( $row->page_id ) {
3337  $titleObj = self::newFromRow( $row );
3338  } else {
3339  $titleObj = self::makeTitle( $row->$blNamespace, $row->$blTitle );
3340  $linkCache->addBadLinkObj( $titleObj );
3341  }
3342  $retVal[] = $titleObj;
3343  }
3344 
3345  return $retVal;
3346  }
3347 
3358  public function getTemplateLinksFrom( $options = [] ) {
3359  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3360  }
3361 
3370  public function getBrokenLinksFrom() {
3371  if ( $this->getArticleID() == 0 ) {
3372  # All links from article ID 0 are false positives
3373  return [];
3374  }
3375 
3376  $dbr = wfGetDB( DB_REPLICA );
3377  $res = $dbr->select(
3378  [ 'page', 'pagelinks' ],
3379  [ 'pl_namespace', 'pl_title' ],
3380  [
3381  'pl_from' => $this->getArticleID(),
3382  'page_namespace IS NULL'
3383  ],
3384  __METHOD__, [],
3385  [
3386  'page' => [
3387  'LEFT JOIN',
3388  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3389  ]
3390  ]
3391  );
3392 
3393  $retVal = [];
3394  foreach ( $res as $row ) {
3395  $retVal[] = self::makeTitle( $row->pl_namespace, $row->pl_title );
3396  }
3397  return $retVal;
3398  }
3399 
3406  public function getCdnUrls() {
3407  $urls = [
3408  $this->getInternalURL(),
3409  $this->getInternalURL( 'action=history' )
3410  ];
3411 
3412  $pageLang = $this->getPageLanguage();
3413  if ( $pageLang->hasVariants() ) {
3414  $variants = $pageLang->getVariants();
3415  foreach ( $variants as $vCode ) {
3416  $urls[] = $this->getInternalURL( $vCode );
3417  }
3418  }
3419 
3420  // If we are looking at a css/js user subpage, purge the action=raw.
3421  if ( $this->isUserJsConfigPage() ) {
3422  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3423  } elseif ( $this->isUserJsonConfigPage() ) {
3424  $urls[] = $this->getInternalURL( 'action=raw&ctype=application/json' );
3425  } elseif ( $this->isUserCssConfigPage() ) {
3426  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3427  }
3428 
3429  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3430  return $urls;
3431  }
3432 
3436  public function purgeSquid() {
3438  new CdnCacheUpdate( $this->getCdnUrls() ),
3440  );
3441  }
3442 
3453  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3454  wfDeprecated( __METHOD__, '1.25' );
3455 
3456  global $wgUser;
3457 
3458  if ( !( $nt instanceof Title ) ) {
3459  // Normally we'd add this to $errors, but we'll get
3460  // lots of syntax errors if $nt is not an object
3461  return [ [ 'badtitletext' ] ];
3462  }
3463 
3464  $mp = new MovePage( $this, $nt );
3465  $errors = $mp->isValidMove()->getErrorsArray();
3466  if ( $auth ) {
3467  $errors = wfMergeErrorArrays(
3468  $errors,
3469  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3470  );
3471  }
3472 
3473  return $errors ?: true;
3474  }
3475 
3489  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true,
3490  array $changeTags = []
3491  ) {
3492  wfDeprecated( __METHOD__, '1.25' );
3493 
3494  global $wgUser;
3495 
3496  $mp = new MovePage( $this, $nt );
3497  $method = $auth ? 'moveIfAllowed' : 'move';
3498  $status = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
3499  if ( $status->isOK() ) {
3500  return true;
3501  } else {
3502  return $status->getErrorsArray();
3503  }
3504  }
3505 
3521  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true,
3522  array $changeTags = []
3523  ) {
3524  wfDeprecated( __METHOD__, '1.34' );
3525 
3526  global $wgUser;
3527 
3528  $mp = new MovePage( $this, $nt );
3529  $method = $auth ? 'moveSubpagesIfAllowed' : 'moveSubpages';
3530  $result = $mp->$method( $wgUser, $reason, $createRedirect, $changeTags );
3531 
3532  if ( !$result->isOk() ) {
3533  return $result->getErrorsArray();
3534  }
3535 
3536  $retval = [];
3537  foreach ( $result->getValue() as $key => $status ) {
3538  if ( $status->isOK() ) {
3539  $retval[$key] = $status->getValue();
3540  } else {
3541  $retval[$key] = $status->getErrorsArray();
3542  }
3543  }
3544  return $retval;
3545  }
3546 
3553  public function isSingleRevRedirect() {
3554  global $wgContentHandlerUseDB;
3555 
3556  $dbw = wfGetDB( DB_MASTER );
3557 
3558  # Is it a redirect?
3559  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3560  if ( $wgContentHandlerUseDB ) {
3561  $fields[] = 'page_content_model';
3562  }
3563 
3564  $row = $dbw->selectRow( 'page',
3565  $fields,
3566  $this->pageCond(),
3567  __METHOD__,
3568  [ 'FOR UPDATE' ]
3569  );
3570  # Cache some fields we may want
3571  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3572  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3573  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3574  $this->mContentModel = $row && isset( $row->page_content_model )
3575  ? strval( $row->page_content_model )
3576  : false;
3577 
3578  if ( !$this->mRedirect ) {
3579  return false;
3580  }
3581  # Does the article have a history?
3582  $row = $dbw->selectField( [ 'page', 'revision' ],
3583  'rev_id',
3584  [ 'page_namespace' => $this->mNamespace,
3585  'page_title' => $this->mDbkeyform,
3586  'page_id=rev_page',
3587  'page_latest != rev_id'
3588  ],
3589  __METHOD__,
3590  [ 'FOR UPDATE' ]
3591  );
3592  # Return true if there was no history
3593  return ( $row === false );
3594  }
3595 
3604  public function isValidMoveTarget( $nt ) {
3605  wfDeprecated( __METHOD__, '1.25' );
3606 
3607  # Is it an existing file?
3608  if ( $nt->getNamespace() == NS_FILE ) {
3609  $file = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo()
3610  ->newFile( $nt );
3611  $file->load( File::READ_LATEST );
3612  if ( $file->exists() ) {
3613  wfDebug( __METHOD__ . ": file exists\n" );
3614  return false;
3615  }
3616  }
3617  # Is it a redirect with no history?
3618  if ( !$nt->isSingleRevRedirect() ) {
3619  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3620  return false;
3621  }
3622  # Get the article text
3623  $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
3624  if ( !is_object( $rev ) ) {
3625  return false;
3626  }
3627  $content = $rev->getContent();
3628  # Does the redirect point to the source?
3629  # Or is it a broken self-redirect, usually caused by namespace collisions?
3630  $redirTitle = $content ? $content->getRedirectTarget() : null;
3631 
3632  if ( $redirTitle ) {
3633  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3634  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3635  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3636  return false;
3637  } else {
3638  return true;
3639  }
3640  } else {
3641  # Fail safe (not a redirect after all. strange.)
3642  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3643  " is a redirect, but it doesn't contain a valid redirect.\n" );
3644  return false;
3645  }
3646  }
3647 
3655  public function getParentCategories() {
3656  $data = [];
3657 
3658  $titleKey = $this->getArticleID();
3659 
3660  if ( $titleKey === 0 ) {
3661  return $data;
3662  }
3663 
3664  $dbr = wfGetDB( DB_REPLICA );
3665 
3666  $res = $dbr->select(
3667  'categorylinks',
3668  'cl_to',
3669  [ 'cl_from' => $titleKey ],
3670  __METHOD__
3671  );
3672 
3673  if ( $res->numRows() > 0 ) {
3674  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3675  foreach ( $res as $row ) {
3676  // $data[] = Title::newFromText( $contLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3677  $data[$contLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] =
3678  $this->getFullText();
3679  }
3680  }
3681  return $data;
3682  }
3683 
3690  public function getParentCategoryTree( $children = [] ) {
3691  $stack = [];
3692  $parents = $this->getParentCategories();
3693 
3694  if ( $parents ) {
3695  foreach ( $parents as $parent => $current ) {
3696  if ( array_key_exists( $parent, $children ) ) {
3697  # Circular reference
3698  $stack[$parent] = [];
3699  } else {
3700  $nt = self::newFromText( $parent );
3701  if ( $nt ) {
3702  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3703  }
3704  }
3705  }
3706  }
3707 
3708  return $stack;
3709  }
3710 
3717  public function pageCond() {
3718  if ( $this->mArticleID > 0 ) {
3719  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3720  return [ 'page_id' => $this->mArticleID ];
3721  } else {
3722  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3723  }
3724  }
3725 
3733  private function getRelativeRevisionID( $revId, $flags, $dir ) {
3734  $rl = MediaWikiServices::getInstance()->getRevisionLookup();
3735  $rlFlags = $flags === self::GAID_FOR_UPDATE ? IDBAccessObject::READ_LATEST : 0;
3736  $rev = $rl->getRevisionById( $revId, $rlFlags );
3737  if ( !$rev ) {
3738  return false;
3739  }
3740  $oldRev = $dir === 'next'
3741  ? $rl->getNextRevision( $rev, $rlFlags )
3742  : $rl->getPreviousRevision( $rev, $rlFlags );
3743  if ( !$oldRev ) {
3744  return false;
3745  }
3746  return $oldRev->getId();
3747  }
3748 
3757  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3758  return $this->getRelativeRevisionID( $revId, $flags, 'prev' );
3759  }
3760 
3769  public function getNextRevisionID( $revId, $flags = 0 ) {
3770  return $this->getRelativeRevisionID( $revId, $flags, 'next' );
3771  }
3772 
3779  public function getFirstRevision( $flags = 0 ) {
3780  $pageId = $this->getArticleID( $flags );
3781  if ( $pageId ) {
3782  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
3784  $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'],
3785  [ 'rev_page' => $pageId ],
3786  __METHOD__,
3787  [
3788  'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
3789  'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
3790  ],
3791  $revQuery['joins']
3792  );
3793  if ( $row ) {
3794  return new Revision( $row, 0, $this );
3795  }
3796  }
3797  return null;
3798  }
3799 
3806  public function getEarliestRevTime( $flags = 0 ) {
3807  $rev = $this->getFirstRevision( $flags );
3808  return $rev ? $rev->getTimestamp() : null;
3809  }
3810 
3816  public function isNewPage() {
3817  $dbr = wfGetDB( DB_REPLICA );
3818  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
3819  }
3820 
3826  public function isBigDeletion() {
3827  global $wgDeleteRevisionsLimit;
3828 
3829  if ( !$wgDeleteRevisionsLimit ) {
3830  return false;
3831  }
3832 
3833  if ( $this->mIsBigDeletion === null ) {
3834  $dbr = wfGetDB( DB_REPLICA );
3835 
3836  $revCount = $dbr->selectRowCount(
3837  'revision',
3838  '1',
3839  [ 'rev_page' => $this->getArticleID() ],
3840  __METHOD__,
3841  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
3842  );
3843 
3844  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
3845  }
3846 
3847  return $this->mIsBigDeletion;
3848  }
3849 
3855  public function estimateRevisionCount() {
3856  if ( !$this->exists() ) {
3857  return 0;
3858  }
3859 
3860  if ( $this->mEstimateRevisions === null ) {
3861  $dbr = wfGetDB( DB_REPLICA );
3862  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
3863  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
3864  }
3865 
3867  }
3868 
3878  public function countRevisionsBetween( $old, $new, $max = null ) {
3879  if ( !( $old instanceof Revision ) ) {
3880  $old = Revision::newFromTitle( $this, (int)$old );
3881  }
3882  if ( !( $new instanceof Revision ) ) {
3883  $new = Revision::newFromTitle( $this, (int)$new );
3884  }
3885  if ( !$old || !$new ) {
3886  return 0; // nothing to compare
3887  }
3888  $dbr = wfGetDB( DB_REPLICA );
3889  $conds = [
3890  'rev_page' => $this->getArticleID(),
3891  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
3892  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
3893  ];
3894  if ( $max !== null ) {
3895  return $dbr->selectRowCount( 'revision', '1',
3896  $conds,
3897  __METHOD__,
3898  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
3899  );
3900  } else {
3901  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
3902  }
3903  }
3904 
3921  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
3922  if ( !( $old instanceof Revision ) ) {
3923  $old = Revision::newFromTitle( $this, (int)$old );
3924  }
3925  if ( !( $new instanceof Revision ) ) {
3926  $new = Revision::newFromTitle( $this, (int)$new );
3927  }
3928  // XXX: what if Revision objects are passed in, but they don't refer to this title?
3929  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
3930  // in the sanity check below?
3931  if ( !$old || !$new ) {
3932  return null; // nothing to compare
3933  }
3934  $authors = [];
3935  $old_cmp = '>';
3936  $new_cmp = '<';
3937  $options = (array)$options;
3938  if ( in_array( 'include_old', $options ) ) {
3939  $old_cmp = '>=';
3940  }
3941  if ( in_array( 'include_new', $options ) ) {
3942  $new_cmp = '<=';
3943  }
3944  if ( in_array( 'include_both', $options ) ) {
3945  $old_cmp = '>=';
3946  $new_cmp = '<=';
3947  }
3948  // No DB query needed if $old and $new are the same or successive revisions:
3949  if ( $old->getId() === $new->getId() ) {
3950  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
3951  [] :
3952  [ $old->getUserText( RevisionRecord::RAW ) ];
3953  } elseif ( $old->getId() === $new->getParentId() ) {
3954  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
3955  $authors[] = $oldUserText = $old->getUserText( RevisionRecord::RAW );
3956  $newUserText = $new->getUserText( RevisionRecord::RAW );
3957  if ( $oldUserText != $newUserText ) {
3958  $authors[] = $newUserText;
3959  }
3960  } elseif ( $old_cmp === '>=' ) {
3961  $authors[] = $old->getUserText( RevisionRecord::RAW );
3962  } elseif ( $new_cmp === '<=' ) {
3963  $authors[] = $new->getUserText( RevisionRecord::RAW );
3964  }
3965  return $authors;
3966  }
3967  $dbr = wfGetDB( DB_REPLICA );
3969  $authors = $dbr->selectFieldValues(
3970  $revQuery['tables'],
3971  $revQuery['fields']['rev_user_text'],
3972  [
3973  'rev_page' => $this->getArticleID(),
3974  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
3975  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
3976  ], __METHOD__,
3977  [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated
3978  $revQuery['joins']
3979  );
3980  return $authors;
3981  }
3982 
3997  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
3998  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
3999  return $authors ? count( $authors ) : 0;
4000  }
4001 
4008  public function equals( LinkTarget $title ) {
4009  // Note: === is necessary for proper matching of number-like titles.
4010  return $this->mInterwiki === $title->getInterwiki()
4011  && $this->mNamespace == $title->getNamespace()
4012  && $this->mDbkeyform === $title->getDBkey();
4013  }
4014 
4021  public function isSubpageOf( Title $title ) {
4022  return $this->mInterwiki === $title->mInterwiki
4023  && $this->mNamespace == $title->mNamespace
4024  && strpos( $this->mDbkeyform, $title->mDbkeyform . '/' ) === 0;
4025  }
4026 
4038  public function exists( $flags = 0 ) {
4039  $exists = $this->getArticleID( $flags ) != 0;
4040  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4041  return $exists;
4042  }
4043 
4060  public function isAlwaysKnown() {
4061  $isKnown = null;
4062 
4073  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4074 
4075  if ( !is_null( $isKnown ) ) {
4076  return $isKnown;
4077  }
4078 
4079  if ( $this->isExternal() ) {
4080  return true; // any interwiki link might be viewable, for all we know
4081  }
4082 
4083  $services = MediaWikiServices::getInstance();
4084  switch ( $this->mNamespace ) {
4085  case NS_MEDIA:
4086  case NS_FILE:
4087  // file exists, possibly in a foreign repo
4088  return (bool)$services->getRepoGroup()->findFile( $this );
4089  case NS_SPECIAL:
4090  // valid special page
4091  return $services->getSpecialPageFactory()->exists( $this->mDbkeyform );
4092  case NS_MAIN:
4093  // selflink, possibly with fragment
4094  return $this->mDbkeyform == '';
4095  case NS_MEDIAWIKI:
4096  // known system message
4097  return $this->hasSourceText() !== false;
4098  default:
4099  return false;
4100  }
4101  }
4102 
4114  public function isKnown() {
4115  return $this->isAlwaysKnown() || $this->exists();
4116  }
4117 
4123  public function hasSourceText() {
4124  if ( $this->exists() ) {
4125  return true;
4126  }
4127 
4128  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4129  // If the page doesn't exist but is a known system message, default
4130  // message content will be displayed, same for language subpages-
4131  // Use always content language to avoid loading hundreds of languages
4132  // to get the link color.
4133  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
4134  list( $name, ) = MessageCache::singleton()->figureMessage(
4135  $contLang->lcfirst( $this->getText() )
4136  );
4137  $message = wfMessage( $name )->inLanguage( $contLang )->useDatabase( false );
4138  return $message->exists();
4139  }
4140 
4141  return false;
4142  }
4143 
4181  public function getDefaultMessageText() {
4182  if ( $this->mNamespace != NS_MEDIAWIKI ) { // Just in case
4183  return false;
4184  }
4185 
4186  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4187  MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $this->getText() )
4188  );
4189  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4190 
4191  if ( $message->exists() ) {
4192  return $message->plain();
4193  } else {
4194  return false;
4195  }
4196  }
4197 
4204  public function invalidateCache( $purgeTime = null ) {
4205  if ( wfReadOnly() ) {
4206  return false;
4207  } elseif ( $this->mArticleID === 0 ) {
4208  return true; // avoid gap locking if we know it's not there
4209  }
4210 
4211  $dbw = wfGetDB( DB_MASTER );
4212  $dbw->onTransactionPreCommitOrIdle(
4213  function () use ( $dbw ) {
4215  $this, null, null, $dbw->getDomainID() );
4216  },
4217  __METHOD__
4218  );
4219 
4220  $conds = $this->pageCond();
4222  new AutoCommitUpdate(
4223  $dbw,
4224  __METHOD__,
4225  function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4226  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4227  $dbw->update(
4228  'page',
4229  [ 'page_touched' => $dbTimestamp ],
4230  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4231  $fname
4232  );
4233  MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4234  }
4235  ),
4237  );
4238 
4239  return true;
4240  }
4241 
4247  public function touchLinks() {
4248  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
4249  if ( $this->mNamespace == NS_CATEGORY ) {
4251  new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
4252  );
4253  }
4254  }
4255 
4262  public function getTouched( $db = null ) {
4263  if ( $db === null ) {
4264  $db = wfGetDB( DB_REPLICA );
4265  }
4266  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4267  return $touched;
4268  }
4269 
4276  public function getNotificationTimestamp( $user = null ) {
4277  global $wgUser;
4278 
4279  // Assume current user if none given
4280  if ( !$user ) {
4281  $user = $wgUser;
4282  }
4283  // Check cache first
4284  $uid = $user->getId();
4285  if ( !$uid ) {
4286  return false;
4287  }
4288  // avoid isset here, as it'll return false for null entries
4289  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4290  return $this->mNotificationTimestamp[$uid];
4291  }
4292  // Don't cache too much!
4293  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4294  $this->mNotificationTimestamp = [];
4295  }
4296 
4297  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4298  $watchedItem = $store->getWatchedItem( $user, $this );
4299  if ( $watchedItem ) {
4300  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4301  } else {
4302  $this->mNotificationTimestamp[$uid] = false;
4303  }
4304 
4305  return $this->mNotificationTimestamp[$uid];
4306  }
4307 
4314  public function getNamespaceKey( $prepend = 'nstab-' ) {
4315  // Gets the subject namespace of this title
4316  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
4317  $subjectNS = $nsInfo->getSubject( $this->mNamespace );
4318  // Prefer canonical namespace name for HTML IDs
4319  $namespaceKey = $nsInfo->getCanonicalName( $subjectNS );
4320  if ( $namespaceKey === false ) {
4321  // Fallback to localised text
4322  $namespaceKey = $this->getSubjectNsText();
4323  }
4324  // Makes namespace key lowercase
4325  $namespaceKey = MediaWikiServices::getInstance()->getContentLanguage()->lc( $namespaceKey );
4326  // Uses main
4327  if ( $namespaceKey == '' ) {
4328  $namespaceKey = 'main';
4329  }
4330  // Changes file to image for backwards compatibility
4331  if ( $namespaceKey == 'file' ) {
4332  $namespaceKey = 'image';
4333  }
4334  return $prepend . $namespaceKey;
4335  }
4336 
4343  public function getRedirectsHere( $ns = null ) {
4344  $redirs = [];
4345 
4346  $dbr = wfGetDB( DB_REPLICA );
4347  $where = [
4348  'rd_namespace' => $this->mNamespace,
4349  'rd_title' => $this->mDbkeyform,
4350  'rd_from = page_id'
4351  ];
4352  if ( $this->isExternal() ) {
4353  $where['rd_interwiki'] = $this->mInterwiki;
4354  } else {
4355  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4356  }
4357  if ( !is_null( $ns ) ) {
4358  $where['page_namespace'] = $ns;
4359  }
4360 
4361  $res = $dbr->select(
4362  [ 'redirect', 'page' ],
4363  [ 'page_namespace', 'page_title' ],
4364  $where,
4365  __METHOD__
4366  );
4367 
4368  foreach ( $res as $row ) {
4369  $redirs[] = self::newFromRow( $row );
4370  }
4371  return $redirs;
4372  }
4373 
4379  public function isValidRedirectTarget() {
4381 
4382  if ( $this->isSpecialPage() ) {
4383  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4384  if ( $this->isSpecial( 'Userlogout' ) ) {
4385  return false;
4386  }
4387 
4388  foreach ( $wgInvalidRedirectTargets as $target ) {
4389  if ( $this->isSpecial( $target ) ) {
4390  return false;
4391  }
4392  }
4393  }
4394 
4395  return true;
4396  }
4397 
4403  public function getBacklinkCache() {
4404  return BacklinkCache::get( $this );
4405  }
4406 
4412  public function canUseNoindex() {
4414 
4415  $bannedNamespaces = $wgExemptFromUserRobotsControl ??
4416  MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces();
4417 
4418  return !in_array( $this->mNamespace, $bannedNamespaces );
4419  }
4420 
4431  public function getCategorySortkey( $prefix = '' ) {
4432  $unprefixed = $this->getText();
4433 
4434  // Anything that uses this hook should only depend
4435  // on the Title object passed in, and should probably
4436  // tell the users to run updateCollations.php --force
4437  // in order to re-sort existing category relations.
4438  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4439  if ( $prefix !== '' ) {
4440  # Separate with a line feed, so the unprefixed part is only used as
4441  # a tiebreaker when two pages have the exact same prefix.
4442  # In UCA, tab is the only character that can sort above LF
4443  # so we strip both of them from the original prefix.
4444  $prefix = strtr( $prefix, "\n\t", ' ' );
4445  return "$prefix\n$unprefixed";
4446  }
4447  return $unprefixed;
4448  }
4449 
4457  private function getDbPageLanguageCode() {
4458  global $wgPageLanguageUseDB;
4459 
4460  // check, if the page language could be saved in the database, and if so and
4461  // the value is not requested already, lookup the page language using LinkCache
4462  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4463  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
4464  $linkCache->addLinkObj( $this );
4465  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4466  }
4467 
4468  return $this->mDbPageLanguage;
4469  }
4470 
4479  public function getPageLanguage() {
4480  global $wgLang, $wgLanguageCode;
4481  if ( $this->isSpecialPage() ) {
4482  // special pages are in the user language
4483  return $wgLang;
4484  }
4485 
4486  // Checking if DB language is set
4487  $dbPageLanguage = $this->getDbPageLanguageCode();
4488  if ( $dbPageLanguage ) {
4489  return wfGetLangObj( $dbPageLanguage );
4490  }
4491 
4492  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4493  // Note that this may depend on user settings, so the cache should
4494  // be only per-request.
4495  // NOTE: ContentHandler::getPageLanguage() may need to load the
4496  // content to determine the page language!
4497  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4498  // tests.
4499  $contentHandler = ContentHandler::getForTitle( $this );
4500  $langObj = $contentHandler->getPageLanguage( $this );
4501  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4502  } else {
4503  $langObj = Language::factory( $this->mPageLanguage[0] );
4504  }
4505 
4506  return $langObj;
4507  }
4508 
4517  public function getPageViewLanguage() {
4518  global $wgLang;
4519 
4520  if ( $this->isSpecialPage() ) {
4521  // If the user chooses a variant, the content is actually
4522  // in a language whose code is the variant code.
4523  $variant = $wgLang->getPreferredVariant();
4524  if ( $wgLang->getCode() !== $variant ) {
4525  return Language::factory( $variant );
4526  }
4527 
4528  return $wgLang;
4529  }
4530 
4531  // Checking if DB language is set
4532  $dbPageLanguage = $this->getDbPageLanguageCode();
4533  if ( $dbPageLanguage ) {
4534  $pageLang = wfGetLangObj( $dbPageLanguage );
4535  $variant = $pageLang->getPreferredVariant();
4536  if ( $pageLang->getCode() !== $variant ) {
4537  $pageLang = Language::factory( $variant );
4538  }
4539 
4540  return $pageLang;
4541  }
4542 
4543  // @note Can't be cached persistently, depends on user settings.
4544  // @note ContentHandler::getPageViewLanguage() may need to load the
4545  // content to determine the page language!
4546  $contentHandler = ContentHandler::getForTitle( $this );
4547  $pageLang = $contentHandler->getPageViewLanguage( $this );
4548  return $pageLang;
4549  }
4550 
4561  public function getEditNotices( $oldid = 0 ) {
4562  $notices = [];
4563 
4564  // Optional notice for the entire namespace
4565  $editnotice_ns = 'editnotice-' . $this->mNamespace;
4566  $msg = wfMessage( $editnotice_ns );
4567  if ( $msg->exists() ) {
4568  $html = $msg->parseAsBlock();
4569  // Edit notices may have complex logic, but output nothing (T91715)
4570  if ( trim( $html ) !== '' ) {
4571  $notices[$editnotice_ns] = Html::rawElement(
4572  'div',
4573  [ 'class' => [
4574  'mw-editnotice',
4575  'mw-editnotice-namespace',
4576  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4577  ] ],
4578  $html
4579  );
4580  }
4581  }
4582 
4583  if (
4584  MediaWikiServices::getInstance()->getNamespaceInfo()->
4585  hasSubpages( $this->mNamespace )
4586  ) {
4587  // Optional notice for page itself and any parent page
4588  $editnotice_base = $editnotice_ns;
4589  foreach ( explode( '/', $this->mDbkeyform ) as $part ) {
4590  $editnotice_base .= '-' . $part;
4591  $msg = wfMessage( $editnotice_base );
4592  if ( $msg->exists() ) {
4593  $html = $msg->parseAsBlock();
4594  if ( trim( $html ) !== '' ) {
4595  $notices[$editnotice_base] = Html::rawElement(
4596  'div',
4597  [ 'class' => [
4598  'mw-editnotice',
4599  'mw-editnotice-base',
4600  Sanitizer::escapeClass( "mw-$editnotice_base" )
4601  ] ],
4602  $html
4603  );
4604  }
4605  }
4606  }
4607  } else {
4608  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4609  $editnoticeText = $editnotice_ns . '-' . strtr( $this->mDbkeyform, '/', '-' );
4610  $msg = wfMessage( $editnoticeText );
4611  if ( $msg->exists() ) {
4612  $html = $msg->parseAsBlock();
4613  if ( trim( $html ) !== '' ) {
4614  $notices[$editnoticeText] = Html::rawElement(
4615  'div',
4616  [ 'class' => [
4617  'mw-editnotice',
4618  'mw-editnotice-page',
4619  Sanitizer::escapeClass( "mw-$editnoticeText" )
4620  ] ],
4621  $html
4622  );
4623  }
4624  }
4625  }
4626 
4627  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4628  return $notices;
4629  }
4630 
4634  public function __sleep() {
4635  return [
4636  'mNamespace',
4637  'mDbkeyform',
4638  'mFragment',
4639  'mInterwiki',
4640  'mLocalInterwiki',
4641  'mUserCaseDBKey',
4642  'mDefaultNamespace',
4643  ];
4644  }
4645 
4646  public function __wakeup() {
4647  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4648  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4649  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4650  }
4651 
4652 }
bool $mHasSubpages
Whether a page has any subpages.
Definition: Title.php:179
static getFilteredRestrictionTypes( $exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition: Title.php:2312
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition: Title.php:4060
areCascadeProtectionSourcesLoaded( $getPages=true)
Determines whether cascading protection sources have already been loaded from the database...
Definition: Title.php:2537
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition: Title.php:2874
clear( $keys=null)
Clear one or several cache entries, or all cache entries.
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used $groups Array of ChangesListFilterGroup objects(added in 1.34) 'FileDeleteComplete' null for the local wiki Added in
Definition: hooks.txt:1529
static castFromLinkTarget( $linkTarget)
Same as newFromLinkTarget, but if passed null, returns null.
Definition: Title.php:298
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title...
Definition: Title.php:4247
getFragment()
Get the Title fragment (i.e.
Definition: Title.php:1613
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:2621
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:1972
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:160
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4204
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition: Title.php:2653
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren&#39;t movable...
Definition: Title.php:1305
getRootTitle()
Get the root page name title, i.e.
Definition: Title.php:1795
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3027
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition: Title.php:3921
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used $groups Array of ChangesListFilterGroup objects(added in 1.34) 'FileDeleteComplete' 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:1529
canHaveTalkPage()
Can this title have a corresponding talk page?
Definition: Title.php:1158
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition: Title.php:4412
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:215
wasLocalInterwiki()
Was this a local interwiki link?
Definition: Title.php:940
get( $key, $maxAge=INF, $default=null)
Get the value for a key.
$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:1294
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:1031
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2445
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:473
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:2505
static clearCaches()
Definition: Title.php:3168
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:3878
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:2915
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:1408
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:1003
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition: Title.php:1868
getBaseText()
Get the base page name without a namespace, i.e.
Definition: Title.php:1818
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:656
getParentCategoryTree( $children=[])
Get a tree of parent categories.
Definition: Title.php:3690
getDefaultMessageText()
Get the default (plain) message contents for an page that overrides an interface message key...
Definition: Title.php:4181
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition: Title.php:2712
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:1972
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:1936
loadFromRow( $row)
Load Title object fields from a DB row.
Definition: Title.php:534
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:1659
equals(LinkTarget $title)
Compare with another title.
Definition: Title.php:4008
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition: Title.php:1130
bool null $mIsBigDeletion
Would deleting this page be a big deletion?
Definition: Title.php:193
isUserJsConfigPage()
Is this a JS "config" sub-page of a user page?
Definition: Title.php:1452
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
__wakeup()
Definition: Title.php:4646
getTalkPageIfDefined()
Get a Title object associated with the talk page of this article, if such a talk page can exist...
Definition: Title.php:1563
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition: Title.php:963
bool $mForcedContentModel
If a content model was forced via setContentModel() this will be true to avoid having other code path...
Definition: Title.php:115
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
$wgActionPaths
Definition: img_auth.php:47
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition: Title.php:2009
$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:1179
if(!isset( $args[0])) $lang
isUserCssConfigPage()
Is this a CSS "config" sub-page of a user page?
Definition: Title.php:1424
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
$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:3204
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition: Title.php:1254
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:2043
isTalkPage()
Is this a talk page of some sort?
Definition: Title.php:1535
Handles purging appropriate CDN URLs given a title (or titles)
string $mUrlform
URL-encoded form of the main part.
Definition: Title.php:79
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:4457
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:3826
const NS_SPECIAL
Definition: Defines.php:49
const PROTO_CURRENT
Definition: Defines.php:202
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition: Title.php:4276
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:1592
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:216
$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:1242
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1724
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move a title to a new location.
Definition: Title.php:3489
getBaseTitle()
Get the base page name title, i.e.
Definition: Title.php:1848
getParentCategories()
Get categories to which this Title belongs and return an array of categories&#39; names.
Definition: Title.php:3655
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:522
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:3246
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4403
$wgArticlePath
Definition: img_auth.php:46
TitleValue null $mTitleValue
A corresponding TitleValue object.
Definition: Title.php:190
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition: Title.php:4431
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:219
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:3304
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:960
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition: Title.php:1549
userCan( $action, $user=null, $rigor=PermissionManager::RIGOR_SECURE)
Can $user perform $action on this page?
Definition: Title.php:2253
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:66
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:144
isSiteConfigPage()
Could this MediaWiki namespace page contain custom CSS, JSON, or JavaScript for the global UI...
Definition: Title.php:1380
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias...
Definition: Title.php:1218
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. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\*-\*)") will be honored when streaming the file. '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:1970
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition: Title.php:717
getFragment()
Get the link fragment (i.e.
static nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition: Title.php:681
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:767
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:1892
$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:132
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:2142
quickUserCan( $action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition: Title.php:2234
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:1244
isSiteCssConfigPage()
Is this a sitewide CSS "config" page?
Definition: Title.php:1466
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition: Title.php:2864
string null $prefixedText
Text form including namespace/interwiki, initialised on demand.
Definition: Title.php:157
string $mDbkeyform
Main part with underscores.
Definition: Title.php:82
getNsText()
Get the namespace text.
Definition: Title.php:1105
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition: Title.php:2191
__sleep()
Definition: Title.php:4634
string bool $mOldRestrictions
Comma-separated set of permission keys indicating who can move or edit the page from the page table...
Definition: Title.php:129
__construct()
Definition: Title.php:223
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:920
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:922
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:1394
deleteTitleProtection()
Remove any title protection due to page existing.
Definition: Title.php:2427
int $mNamespace
Namespace index, i.e.
Definition: Title.php:88
set( $key, $value, $rank=self::RANK_TOP)
Set a key/value pair.
hasSourceText()
Does this page have source text?
Definition: Title.php:4123
isUserJsonConfigPage()
Is this a JSON "config" sub-page of a user page?
Definition: Title.php:1438
resetArticleID( $newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3146
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:2205
static getTitleCache()
Definition: Title.php:433
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition: Title.php:204
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:1021
__toString()
Return a string representation of this title.
Definition: Title.php:1738
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition: Title.php:1917
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1200
hasFragment()
Check if a Title fragment is set.
Definition: Title.php:1623
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4343
$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:3288
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition: Title.php:2693
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition: Title.php:1350
const NS_MEDIA
Definition: Defines.php:48
getRootText()
Get the root page name text without a namespace, i.e.
Definition: Title.php:1771
$res
Definition: database.txt:21
bool string $mContentModel
ID of the page&#39;s content model, i.e.
Definition: Title.php:109
canExist()
Is this in a namespace that allows actual pages?
Definition: Title.php:1167
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:2946
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:57
null $mRedirect
Is the article at this title a redirect?
Definition: Title.php:173
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:1080
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:4379
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not...
Definition: Title.php:2523
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition: Title.php:3604
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition: Title.php:3453
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3406
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:1602
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:1972
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3642
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:4021
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition: Title.php:176
loadRestrictions( $oldFashionedRestrictions=null, $flags=0)
Load restrictions from the page_restrictions table.
Definition: Title.php:2790
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with &#39;#&#39;)
Definition: Title.php:1748
static newFromIDs( $ids)
Make an array of titles from an array of IDs.
Definition: Title.php:496
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:187
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:767
static newFromResult( $res)
Definition: TitleArray.php:40
isMainPage()
Is this the mainpage?
Definition: Title.php:1329
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object. ...
Definition: Title.php:1686
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1418
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:4314
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:1974
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1045
const PROTO_RELATIVE
Definition: Defines.php:201
string $mInterwiki
Interwiki prefix.
Definition: Title.php:91
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:4114
isProtected( $action='')
Does the title correspond to a protected article?
Definition: Title.php:2473
const NS_FILE
Definition: Defines.php:66
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2640
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:1748
isSiteJsonConfigPage()
Is this a sitewide JSON "config" page?
Definition: Title.php:1484
isSubpage()
Is this a subpage?
Definition: Title.php:1338
getInterwiki()
Get the interwiki prefix.
Definition: Title.php:931
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:912
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:3370
const NS_MEDIAWIKI
Definition: Defines.php:68
const PROTO_HTTP
Definition: Defines.php:199
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:131
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition: Title.php:138
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:3358
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid, rather than returning null.
Definition: Title.php:359
isValid()
Returns true if the title is valid, false if it is invalid.
Definition: Title.php:871
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:1578
int $mLength
The page length, 0 for special pages.
Definition: Title.php:170
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:620
hasSubjectNamespace( $ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition: Title.php:1282
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:274
isSpecialPage()
Returns true if this is a special page.
Definition: Title.php:1190
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:2205
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:592
$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:3553
const PROTO_CANONICAL
Definition: Defines.php:203
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:3779
static newFromDBkey( $key)
Create a new Title from a prefixed DB key.
Definition: Title.php:234
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition: Title.php:4517
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition: Title.php:1906
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition: Title.php:135
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:1670
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:410
countAuthorsBetween( $old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition: Title.php:3997
getTitleProtectionInternal()
Fetch title protection settings.
Definition: Title.php:2380
getRestrictionTypes()
Returns restriction types for the current Title.
Definition: Title.php:2330
$parent
Definition: pageupdater.txt:71
bool $mPageLanguage
The (string) language code of the page&#39;s language and content code.
Definition: Title.php:182
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:1322
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition: Title.php:950
getContentModel( $flags=0)
Get the page&#39;s content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:1057
string $mFragment
Title fragment (i.e.
Definition: Title.php:97
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition: Title.php:100
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition: Title.php:167
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition: Title.php:3183
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:3757
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:4479
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles...
Definition: Title.php:51
$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:1345
isRawHtmlMessage()
Is this a message which can contain raw HTML?
Definition: Title.php:1520
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition: Title.php:1632
$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:4038
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition: Title.php:3114
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition: Title.php:3717
getInterwiki()
The interwiki component of this LinkTarget.
const CONTENT_MODEL_CSS
Definition: Defines.php:217
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:3806
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition: Title.php:3001
isNewPage()
Check if this is a new page.
Definition: Title.php:3816
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:2681
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition: Title.php:849
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition: Title.php:147
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:703
getCascadeProtectionSources( $getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition: Title.php:2554
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3051
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:1502
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:776
$wgServer
URL of the server.
isDeleted()
Is there a version of this page in the deletion archive?
Definition: Title.php:2976
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition: Title.php:905
array $mRestrictions
Array of groups allowed to edit this article.
Definition: Title.php:121
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:2357
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition: Title.php:94
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition: Title.php:118
getEditNotices( $oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:4561
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
getTouched( $db=null)
Get the last touched timestamp.
Definition: Title.php:4262
static MapCacheLRU null $titleCache
Definition: Title.php:44
getTalkNsText()
Get the namespace text of the talk page.
Definition: Title.php:1141
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries...
Definition: Title.php:447
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
string $mTextform
Text form (spaces not underscores) of the main part.
Definition: Title.php:76
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition: Title.php:2667
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition: Title.php:2167
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition: Title.php:822
$content
Definition: pageupdater.txt:72
addQuotes( $s)
Adds quotes and backslashes.
bool int $mLatestID
ID of most recent revision.
Definition: Title.php:103
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:1095
getUserPermissionsErrors( $action, $user, $rigor=PermissionManager::RIGOR_SECURE, $ignoreErrors=[])
Can $user perform $action on this page?
Definition: Title.php:2291
$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:259
getRelativeRevisionID( $revId, $flags, $dir)
Get next/previous revision ID relative to another revision ID.
Definition: Title.php:3733
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:3086
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition: Title.php:1362
static singleton()
Get the signleton instance of this class.
purgeSquid()
Purge all applicable CDN URLs.
Definition: Title.php:3436
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1454
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:980
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition: Title.php:141
getPartialURL()
Get the URL-encoded form of the main part.
Definition: Title.php:1012
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1712
static decodeCharReferencesAndNormalize( $text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1686
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move this page&#39;s subpages to be subpages of $nt.
Definition: Title.php:3521
estimateRevisionCount()
Get the approximate revision count of this page.
Definition: Title.php:3855
$matches
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition: Title.php:3769
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition: Title.php:85
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:322