MediaWiki  REL1_31
Title.php
Go to the documentation of this file.
1 <?php
30 
39 class Title implements LinkTarget {
41  static private $titleCache = null;
42 
48  const CACHE_MAX = 1000;
49 
54  const GAID_FOR_UPDATE = 1;
55 
61  // @{
62 
64  public $mTextform = '';
65 
67  public $mUrlform = '';
68 
70  public $mDbkeyform = '';
71 
73  protected $mUserCaseDBKey;
74 
76  public $mNamespace = NS_MAIN;
77 
79  public $mInterwiki = '';
80 
82  private $mLocalInterwiki = false;
83 
85  public $mFragment = '';
86 
88  public $mArticleID = -1;
89 
91  protected $mLatestID = false;
92 
97  private $mContentModel = false;
98 
103  private $mForcedContentModel = false;
104 
107 
109  public $mRestrictions = [];
110 
117  protected $mOldRestrictions = false;
118 
121 
124 
126  protected $mRestrictionsExpiry = [];
127 
130 
133 
135  public $mRestrictionsLoaded = false;
136 
138  protected $mPrefixedText = null;
139 
142 
149 
151  protected $mLength = -1;
152 
154  public $mRedirect = null;
155 
158 
160  private $mHasSubpages;
161 
163  private $mPageLanguage = false;
164 
167  private $mDbPageLanguage = false;
168 
170  private $mTitleValue = null;
171 
173  private $mIsBigDeletion = null;
174  // @}
175 
184  private static function getTitleFormatter() {
185  return MediaWikiServices::getInstance()->getTitleFormatter();
186  }
187 
196  private static function getInterwikiLookup() {
197  return MediaWikiServices::getInstance()->getInterwikiLookup();
198  }
199 
203  function __construct() {
204  }
205 
214  public static function newFromDBkey( $key ) {
215  $t = new Title();
216  $t->mDbkeyform = $key;
217 
218  try {
219  $t->secureAndSplit();
220  return $t;
221  } catch ( MalformedTitleException $ex ) {
222  return null;
223  }
224  }
225 
233  public static function newFromTitleValue( TitleValue $titleValue ) {
234  return self::newFromLinkTarget( $titleValue );
235  }
236 
244  public static function newFromLinkTarget( LinkTarget $linkTarget ) {
245  if ( $linkTarget instanceof Title ) {
246  // Special case if it's already a Title object
247  return $linkTarget;
248  }
249  return self::makeTitle(
250  $linkTarget->getNamespace(),
251  $linkTarget->getText(),
252  $linkTarget->getFragment(),
253  $linkTarget->getInterwiki()
254  );
255  }
256 
273  public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
274  // DWIM: Integers can be passed in here when page titles are used as array keys.
275  if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
276  throw new InvalidArgumentException( '$text must be a string.' );
277  }
278  if ( $text === null ) {
279  return null;
280  }
281 
282  try {
283  return self::newFromTextThrow( strval( $text ), $defaultNamespace );
284  } catch ( MalformedTitleException $ex ) {
285  return null;
286  }
287  }
288 
306  public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
307  if ( is_object( $text ) ) {
308  throw new MWException( '$text must be a string, given an object' );
309  }
310 
312 
313  // Wiki pages often contain multiple links to the same page.
314  // Title normalization and parsing can become expensive on pages with many
315  // links, so we can save a little time by caching them.
316  // In theory these are value objects and won't get changed...
317  if ( $defaultNamespace == NS_MAIN ) {
318  $t = $titleCache->get( $text );
319  if ( $t ) {
320  return $t;
321  }
322  }
323 
324  // Convert things like &eacute; &#257; or &#x3017; into normalized (T16952) text
325  $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
326 
327  $t = new Title();
328  $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
329  $t->mDefaultNamespace = intval( $defaultNamespace );
330 
331  $t->secureAndSplit();
332  if ( $defaultNamespace == NS_MAIN ) {
333  $titleCache->set( $text, $t );
334  }
335  return $t;
336  }
337 
353  public static function newFromURL( $url ) {
354  $t = new Title();
355 
356  # For compatibility with old buggy URLs. "+" is usually not valid in titles,
357  # but some URLs used it as a space replacement and they still come
358  # from some external search tools.
359  if ( strpos( self::legalChars(), '+' ) === false ) {
360  $url = strtr( $url, '+', ' ' );
361  }
362 
363  $t->mDbkeyform = strtr( $url, ' ', '_' );
364 
365  try {
366  $t->secureAndSplit();
367  return $t;
368  } catch ( MalformedTitleException $ex ) {
369  return null;
370  }
371  }
372 
376  private static function getTitleCache() {
377  if ( self::$titleCache == null ) {
378  self::$titleCache = new HashBagOStuff( [ 'maxKeys' => self::CACHE_MAX ] );
379  }
380  return self::$titleCache;
381  }
382 
390  protected static function getSelectFields() {
392 
393  $fields = [
394  'page_namespace', 'page_title', 'page_id',
395  'page_len', 'page_is_redirect', 'page_latest',
396  ];
397 
398  if ( $wgContentHandlerUseDB ) {
399  $fields[] = 'page_content_model';
400  }
401 
402  if ( $wgPageLanguageUseDB ) {
403  $fields[] = 'page_lang';
404  }
405 
406  return $fields;
407  }
408 
416  public static function newFromID( $id, $flags = 0 ) {
417  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
418  $row = $db->selectRow(
419  'page',
420  self::getSelectFields(),
421  [ 'page_id' => $id ],
422  __METHOD__
423  );
424  if ( $row !== false ) {
425  $title = self::newFromRow( $row );
426  } else {
427  $title = null;
428  }
429  return $title;
430  }
431 
438  public static function newFromIDs( $ids ) {
439  if ( !count( $ids ) ) {
440  return [];
441  }
442  $dbr = wfGetDB( DB_REPLICA );
443 
444  $res = $dbr->select(
445  'page',
446  self::getSelectFields(),
447  [ 'page_id' => $ids ],
448  __METHOD__
449  );
450 
451  $titles = [];
452  foreach ( $res as $row ) {
453  $titles[] = self::newFromRow( $row );
454  }
455  return $titles;
456  }
457 
464  public static function newFromRow( $row ) {
465  $t = self::makeTitle( $row->page_namespace, $row->page_title );
466  $t->loadFromRow( $row );
467  return $t;
468  }
469 
476  public function loadFromRow( $row ) {
477  if ( $row ) { // page found
478  if ( isset( $row->page_id ) ) {
479  $this->mArticleID = (int)$row->page_id;
480  }
481  if ( isset( $row->page_len ) ) {
482  $this->mLength = (int)$row->page_len;
483  }
484  if ( isset( $row->page_is_redirect ) ) {
485  $this->mRedirect = (bool)$row->page_is_redirect;
486  }
487  if ( isset( $row->page_latest ) ) {
488  $this->mLatestID = (int)$row->page_latest;
489  }
490  if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
491  $this->mContentModel = strval( $row->page_content_model );
492  } elseif ( !$this->mForcedContentModel ) {
493  $this->mContentModel = false; # initialized lazily in getContentModel()
494  }
495  if ( isset( $row->page_lang ) ) {
496  $this->mDbPageLanguage = (string)$row->page_lang;
497  }
498  if ( isset( $row->page_restrictions ) ) {
499  $this->mOldRestrictions = $row->page_restrictions;
500  }
501  } else { // page not found
502  $this->mArticleID = 0;
503  $this->mLength = 0;
504  $this->mRedirect = false;
505  $this->mLatestID = 0;
506  if ( !$this->mForcedContentModel ) {
507  $this->mContentModel = false; # initialized lazily in getContentModel()
508  }
509  }
510  }
511 
534  public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
535  $t = new Title();
536  $t->mInterwiki = $interwiki;
537  $t->mFragment = $fragment;
538  $t->mNamespace = $ns = intval( $ns );
539  $t->mDbkeyform = strtr( $title, ' ', '_' );
540  $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
541  $t->mUrlform = wfUrlencode( $t->mDbkeyform );
542  $t->mTextform = strtr( $title, '_', ' ' );
543  $t->mContentModel = false; # initialized lazily in getContentModel()
544  return $t;
545  }
546 
562  public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
563  // NOTE: ideally, this would just call makeTitle() and then isValid(),
564  // but presently, that means more overhead on a potential performance hotspot.
565 
566  if ( !MWNamespace::exists( $ns ) ) {
567  return null;
568  }
569 
570  $t = new Title();
571  $t->mDbkeyform = self::makeName( $ns, $title, $fragment, $interwiki, true );
572 
573  try {
574  $t->secureAndSplit();
575  return $t;
576  } catch ( MalformedTitleException $ex ) {
577  return null;
578  }
579  }
580 
586  public static function newMainPage() {
587  $title = self::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
588  // Don't give fatal errors if the message is broken
589  if ( !$title ) {
590  $title = self::newFromText( 'Main Page' );
591  }
592  return $title;
593  }
594 
601  public static function nameOf( $id ) {
602  $dbr = wfGetDB( DB_REPLICA );
603 
604  $s = $dbr->selectRow(
605  'page',
606  [ 'page_namespace', 'page_title' ],
607  [ 'page_id' => $id ],
608  __METHOD__
609  );
610  if ( $s === false ) {
611  return null;
612  }
613 
614  $n = self::makeName( $s->page_namespace, $s->page_title );
615  return $n;
616  }
617 
623  public static function legalChars() {
625  return $wgLegalTitleChars;
626  }
627 
637  public static function convertByteClassToUnicodeClass( $byteClass ) {
638  $length = strlen( $byteClass );
639  // Input token queue
640  $x0 = $x1 = $x2 = '';
641  // Decoded queue
642  $d0 = $d1 = $d2 = '';
643  // Decoded integer codepoints
644  $ord0 = $ord1 = $ord2 = 0;
645  // Re-encoded queue
646  $r0 = $r1 = $r2 = '';
647  // Output
648  $out = '';
649  // Flags
650  $allowUnicode = false;
651  for ( $pos = 0; $pos < $length; $pos++ ) {
652  // Shift the queues down
653  $x2 = $x1;
654  $x1 = $x0;
655  $d2 = $d1;
656  $d1 = $d0;
657  $ord2 = $ord1;
658  $ord1 = $ord0;
659  $r2 = $r1;
660  $r1 = $r0;
661  // Load the current input token and decoded values
662  $inChar = $byteClass[$pos];
663  if ( $inChar == '\\' ) {
664  if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
665  $x0 = $inChar . $m[0];
666  $d0 = chr( hexdec( $m[1] ) );
667  $pos += strlen( $m[0] );
668  } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
669  $x0 = $inChar . $m[0];
670  $d0 = chr( octdec( $m[0] ) );
671  $pos += strlen( $m[0] );
672  } elseif ( $pos + 1 >= $length ) {
673  $x0 = $d0 = '\\';
674  } else {
675  $d0 = $byteClass[$pos + 1];
676  $x0 = $inChar . $d0;
677  $pos += 1;
678  }
679  } else {
680  $x0 = $d0 = $inChar;
681  }
682  $ord0 = ord( $d0 );
683  // Load the current re-encoded value
684  if ( $ord0 < 32 || $ord0 == 0x7f ) {
685  $r0 = sprintf( '\x%02x', $ord0 );
686  } elseif ( $ord0 >= 0x80 ) {
687  // Allow unicode if a single high-bit character appears
688  $r0 = sprintf( '\x%02x', $ord0 );
689  $allowUnicode = true;
690  } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
691  $r0 = '\\' . $d0;
692  } else {
693  $r0 = $d0;
694  }
695  // Do the output
696  if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
697  // Range
698  if ( $ord2 > $ord0 ) {
699  // Empty range
700  } elseif ( $ord0 >= 0x80 ) {
701  // Unicode range
702  $allowUnicode = true;
703  if ( $ord2 < 0x80 ) {
704  // Keep the non-unicode section of the range
705  $out .= "$r2-\\x7F";
706  }
707  } else {
708  // Normal range
709  $out .= "$r2-$r0";
710  }
711  // Reset state to the initial value
712  $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
713  } elseif ( $ord2 < 0x80 ) {
714  // ASCII character
715  $out .= $r2;
716  }
717  }
718  if ( $ord1 < 0x80 ) {
719  $out .= $r1;
720  }
721  if ( $ord0 < 0x80 ) {
722  $out .= $r0;
723  }
724  if ( $allowUnicode ) {
725  $out .= '\u0080-\uFFFF';
726  }
727  return $out;
728  }
729 
741  public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
742  $canonicalNamespace = false
743  ) {
745 
746  if ( $canonicalNamespace ) {
747  $namespace = MWNamespace::getCanonicalName( $ns );
748  } else {
749  $namespace = $wgContLang->getNsText( $ns );
750  }
751  $name = $namespace == '' ? $title : "$namespace:$title";
752  if ( strval( $interwiki ) != '' ) {
753  $name = "$interwiki:$name";
754  }
755  if ( strval( $fragment ) != '' ) {
756  $name .= '#' . $fragment;
757  }
758  return $name;
759  }
760 
769  static function escapeFragmentForURL( $fragment ) {
770  wfDeprecated( __METHOD__, '1.30' );
771  # Note that we don't urlencode the fragment. urlencoded Unicode
772  # fragments appear not to work in IE (at least up to 7) or in at least
773  # one version of Opera 9.x. The W3C validator, for one, doesn't seem
774  # to care if they aren't encoded.
775  return Sanitizer::escapeId( $fragment, 'noninitial' );
776  }
777 
786  public static function compare( LinkTarget $a, LinkTarget $b ) {
787  if ( $a->getNamespace() == $b->getNamespace() ) {
788  return strcmp( $a->getText(), $b->getText() );
789  } else {
790  return $a->getNamespace() - $b->getNamespace();
791  }
792  }
793 
808  public function isValid() {
809  $ns = $this->getNamespace();
810 
811  if ( !MWNamespace::exists( $ns ) ) {
812  return false;
813  }
814 
815  try {
816  $parser = MediaWikiServices::getInstance()->getTitleParser();
817  $parser->parseTitle( $this->getDBkey(), $ns );
818  return true;
819  } catch ( MalformedTitleException $ex ) {
820  return false;
821  }
822  }
823 
831  public function isLocal() {
832  if ( $this->isExternal() ) {
833  $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
834  if ( $iw ) {
835  return $iw->isLocal();
836  }
837  }
838  return true;
839  }
840 
846  public function isExternal() {
847  return $this->mInterwiki !== '';
848  }
849 
857  public function getInterwiki() {
858  return $this->mInterwiki;
859  }
860 
866  public function wasLocalInterwiki() {
867  return $this->mLocalInterwiki;
868  }
869 
876  public function isTrans() {
877  if ( !$this->isExternal() ) {
878  return false;
879  }
880 
881  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
882  }
883 
889  public function getTransWikiID() {
890  if ( !$this->isExternal() ) {
891  return false;
892  }
893 
894  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
895  }
896 
906  public function getTitleValue() {
907  if ( $this->mTitleValue === null ) {
908  try {
909  $this->mTitleValue = new TitleValue(
910  $this->getNamespace(),
911  $this->getDBkey(),
912  $this->getFragment(),
913  $this->getInterwiki()
914  );
915  } catch ( InvalidArgumentException $ex ) {
916  wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
917  $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
918  }
919  }
920 
921  return $this->mTitleValue;
922  }
923 
929  public function getText() {
930  return $this->mTextform;
931  }
932 
938  public function getPartialURL() {
939  return $this->mUrlform;
940  }
941 
947  public function getDBkey() {
948  return $this->mDbkeyform;
949  }
950 
956  function getUserCaseDBKey() {
957  if ( !is_null( $this->mUserCaseDBKey ) ) {
958  return $this->mUserCaseDBKey;
959  } else {
960  // If created via makeTitle(), $this->mUserCaseDBKey is not set.
961  return $this->mDbkeyform;
962  }
963  }
964 
970  public function getNamespace() {
971  return $this->mNamespace;
972  }
973 
980  public function getContentModel( $flags = 0 ) {
981  if ( !$this->mForcedContentModel
982  && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE )
983  && $this->getArticleID( $flags )
984  ) {
985  $linkCache = LinkCache::singleton();
986  $linkCache->addLinkObj( $this ); # in case we already had an article ID
987  $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
988  }
989 
990  if ( !$this->mContentModel ) {
991  $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
992  }
993 
994  return $this->mContentModel;
995  }
996 
1003  public function hasContentModel( $id ) {
1004  return $this->getContentModel() == $id;
1005  }
1006 
1018  public function setContentModel( $model ) {
1019  $this->mContentModel = $model;
1020  $this->mForcedContentModel = true;
1021  }
1022 
1028  public function getNsText() {
1029  if ( $this->isExternal() ) {
1030  // This probably shouldn't even happen, except for interwiki transclusion.
1031  // If possible, use the canonical name for the foreign namespace.
1032  $nsText = MWNamespace::getCanonicalName( $this->mNamespace );
1033  if ( $nsText !== false ) {
1034  return $nsText;
1035  }
1036  }
1037 
1038  try {
1039  $formatter = self::getTitleFormatter();
1040  return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
1041  } catch ( InvalidArgumentException $ex ) {
1042  wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
1043  return false;
1044  }
1045  }
1046 
1052  public function getSubjectNsText() {
1054  return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1055  }
1056 
1062  public function getTalkNsText() {
1064  return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1065  }
1066 
1074  public function canTalk() {
1075  return $this->canHaveTalkPage();
1076  }
1077 
1086  public function canHaveTalkPage() {
1087  return MWNamespace::hasTalkNamespace( $this->mNamespace );
1088  }
1089 
1095  public function canExist() {
1096  return $this->mNamespace >= NS_MAIN;
1097  }
1098 
1104  public function isWatchable() {
1105  return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
1106  }
1107 
1113  public function isSpecialPage() {
1114  return $this->getNamespace() == NS_SPECIAL;
1115  }
1116 
1123  public function isSpecial( $name ) {
1124  if ( $this->isSpecialPage() ) {
1125  list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
1126  if ( $name == $thisName ) {
1127  return true;
1128  }
1129  }
1130  return false;
1131  }
1132 
1139  public function fixSpecialName() {
1140  if ( $this->isSpecialPage() ) {
1141  list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
1142  if ( $canonicalName ) {
1143  $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
1144  if ( $localName != $this->mDbkeyform ) {
1145  return self::makeTitle( NS_SPECIAL, $localName );
1146  }
1147  }
1148  }
1149  return $this;
1150  }
1151 
1162  public function inNamespace( $ns ) {
1163  return MWNamespace::equals( $this->getNamespace(), $ns );
1164  }
1165 
1173  public function inNamespaces( /* ... */ ) {
1174  $namespaces = func_get_args();
1175  if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1176  $namespaces = $namespaces[0];
1177  }
1178 
1179  foreach ( $namespaces as $ns ) {
1180  if ( $this->inNamespace( $ns ) ) {
1181  return true;
1182  }
1183  }
1184 
1185  return false;
1186  }
1187 
1201  public function hasSubjectNamespace( $ns ) {
1202  return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
1203  }
1204 
1212  public function isContentPage() {
1213  return MWNamespace::isContent( $this->getNamespace() );
1214  }
1215 
1222  public function isMovable() {
1223  if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
1224  // Interwiki title or immovable namespace. Hooks don't get to override here
1225  return false;
1226  }
1227 
1228  $result = true;
1229  Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1230  return $result;
1231  }
1232 
1243  public function isMainPage() {
1244  return $this->equals( self::newMainPage() );
1245  }
1246 
1252  public function isSubpage() {
1253  return MWNamespace::hasSubpages( $this->mNamespace )
1254  ? strpos( $this->getText(), '/' ) !== false
1255  : false;
1256  }
1257 
1263  public function isConversionTable() {
1264  // @todo ConversionTable should become a separate content model.
1265 
1266  return $this->getNamespace() == NS_MEDIAWIKI &&
1267  strpos( $this->getText(), 'Conversiontable/' ) === 0;
1268  }
1269 
1275  public function isWikitextPage() {
1276  return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1277  }
1278 
1293  public function isSiteConfigPage() {
1294  return (
1295  NS_MEDIAWIKI == $this->mNamespace
1296  && (
1298  || $this->hasContentModel( CONTENT_MODEL_JSON )
1300  )
1301  );
1302  }
1303 
1308  public function isCssOrJsPage() {
1309  wfDeprecated( __METHOD__, '1.31' );
1310  return ( NS_MEDIAWIKI == $this->mNamespace
1311  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1312  || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
1313  }
1314 
1321  public function isUserConfigPage() {
1322  return (
1323  NS_USER == $this->mNamespace
1324  && $this->isSubpage()
1325  && (
1327  || $this->hasContentModel( CONTENT_MODEL_JSON )
1329  )
1330  );
1331  }
1332 
1337  public function isCssJsSubpage() {
1338  wfDeprecated( __METHOD__, '1.31' );
1339  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1340  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1341  || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
1342  }
1343 
1350  public function getSkinFromConfigSubpage() {
1351  $subpage = explode( '/', $this->mTextform );
1352  $subpage = $subpage[count( $subpage ) - 1];
1353  $lastdot = strrpos( $subpage, '.' );
1354  if ( $lastdot === false ) {
1355  return $subpage; # Never happens: only called for names ending in '.css'/'.json'/'.js'
1356  }
1357  return substr( $subpage, 0, $lastdot );
1358  }
1359 
1364  public function getSkinFromCssJsSubpage() {
1365  wfDeprecated( __METHOD__, '1.31' );
1366  return $this->getSkinFromConfigSubpage();
1367  }
1368 
1375  public function isUserCssConfigPage() {
1376  return (
1377  NS_USER == $this->mNamespace
1378  && $this->isSubpage()
1379  && $this->hasContentModel( CONTENT_MODEL_CSS )
1380  );
1381  }
1382 
1387  public function isCssSubpage() {
1388  wfDeprecated( __METHOD__, '1.31' );
1389  return $this->isUserCssConfigPage();
1390  }
1391 
1398  public function isUserJsonConfigPage() {
1399  return (
1400  NS_USER == $this->mNamespace
1401  && $this->isSubpage()
1402  && $this->hasContentModel( CONTENT_MODEL_JSON )
1403  );
1404  }
1405 
1412  public function isUserJsConfigPage() {
1413  return (
1414  NS_USER == $this->mNamespace
1415  && $this->isSubpage()
1417  );
1418  }
1419 
1424  public function isJsSubpage() {
1425  wfDeprecated( __METHOD__, '1.31' );
1426  return $this->isUserJsConfigPage();
1427  }
1428 
1434  public function isTalkPage() {
1435  return MWNamespace::isTalk( $this->getNamespace() );
1436  }
1437 
1443  public function getTalkPage() {
1444  return self::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
1445  }
1446 
1456  public function getTalkPageIfDefined() {
1457  if ( !$this->canHaveTalkPage() ) {
1458  return null;
1459  }
1460 
1461  return $this->getTalkPage();
1462  }
1463 
1470  public function getSubjectPage() {
1471  // Is this the same title?
1472  $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
1473  if ( $this->getNamespace() == $subjectNS ) {
1474  return $this;
1475  }
1476  return self::makeTitle( $subjectNS, $this->getDBkey() );
1477  }
1478 
1487  public function getOtherPage() {
1488  if ( $this->isSpecialPage() ) {
1489  throw new MWException( 'Special pages cannot have other pages' );
1490  }
1491  if ( $this->isTalkPage() ) {
1492  return $this->getSubjectPage();
1493  } else {
1494  if ( !$this->canHaveTalkPage() ) {
1495  throw new MWException( "{$this->getPrefixedText()} does not have an other page" );
1496  }
1497  return $this->getTalkPage();
1498  }
1499  }
1500 
1506  public function getDefaultNamespace() {
1507  return $this->mDefaultNamespace;
1508  }
1509 
1517  public function getFragment() {
1518  return $this->mFragment;
1519  }
1520 
1527  public function hasFragment() {
1528  return $this->mFragment !== '';
1529  }
1530 
1536  public function getFragmentForURL() {
1537  if ( !$this->hasFragment() ) {
1538  return '';
1539  } elseif ( $this->isExternal()
1540  && !self::getInterwikiLookup()->fetch( $this->mInterwiki )->isLocal()
1541  ) {
1542  return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->getFragment() );
1543  }
1544  return '#' . Sanitizer::escapeIdForLink( $this->getFragment() );
1545  }
1546 
1559  public function setFragment( $fragment ) {
1560  $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1561  }
1562 
1570  public function createFragmentTarget( $fragment ) {
1571  return self::makeTitle(
1572  $this->getNamespace(),
1573  $this->getText(),
1574  $fragment,
1575  $this->getInterwiki()
1576  );
1577  }
1578 
1586  private function prefix( $name ) {
1588 
1589  $p = '';
1590  if ( $this->isExternal() ) {
1591  $p = $this->mInterwiki . ':';
1592  }
1593 
1594  if ( 0 != $this->mNamespace ) {
1595  $nsText = $this->getNsText();
1596 
1597  if ( $nsText === false ) {
1598  // See T165149. Awkward, but better than erroneously linking to the main namespace.
1599  $nsText = $wgContLang->getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}";
1600  }
1601 
1602  $p .= $nsText . ':';
1603  }
1604  return $p . $name;
1605  }
1606 
1613  public function getPrefixedDBkey() {
1614  $s = $this->prefix( $this->mDbkeyform );
1615  $s = strtr( $s, ' ', '_' );
1616  return $s;
1617  }
1618 
1625  public function getPrefixedText() {
1626  if ( $this->mPrefixedText === null ) {
1627  $s = $this->prefix( $this->mTextform );
1628  $s = strtr( $s, '_', ' ' );
1629  $this->mPrefixedText = $s;
1630  }
1631  return $this->mPrefixedText;
1632  }
1633 
1639  public function __toString() {
1640  return $this->getPrefixedText();
1641  }
1642 
1649  public function getFullText() {
1650  $text = $this->getPrefixedText();
1651  if ( $this->hasFragment() ) {
1652  $text .= '#' . $this->getFragment();
1653  }
1654  return $text;
1655  }
1656 
1669  public function getRootText() {
1670  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1671  return $this->getText();
1672  }
1673 
1674  return strtok( $this->getText(), '/' );
1675  }
1676 
1689  public function getRootTitle() {
1690  return self::makeTitle( $this->getNamespace(), $this->getRootText() );
1691  }
1692 
1704  public function getBaseText() {
1705  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1706  return $this->getText();
1707  }
1708 
1709  $parts = explode( '/', $this->getText() );
1710  # Don't discard the real title if there's no subpage involved
1711  if ( count( $parts ) > 1 ) {
1712  unset( $parts[count( $parts ) - 1] );
1713  }
1714  return implode( '/', $parts );
1715  }
1716 
1729  public function getBaseTitle() {
1730  return self::makeTitle( $this->getNamespace(), $this->getBaseText() );
1731  }
1732 
1744  public function getSubpageText() {
1745  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1746  return $this->mTextform;
1747  }
1748  $parts = explode( '/', $this->mTextform );
1749  return $parts[count( $parts ) - 1];
1750  }
1751 
1765  public function getSubpage( $text ) {
1766  return self::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
1767  }
1768 
1774  public function getSubpageUrlForm() {
1775  $text = $this->getSubpageText();
1776  $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1777  return $text;
1778  }
1779 
1785  public function getPrefixedURL() {
1786  $s = $this->prefix( $this->mDbkeyform );
1787  $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1788  return $s;
1789  }
1790 
1804  private static function fixUrlQueryArgs( $query, $query2 = false ) {
1805  if ( $query2 !== false ) {
1806  wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1807  "method called with a second parameter is deprecated. Add your " .
1808  "parameter to an array passed as the first parameter.", "1.19" );
1809  }
1810  if ( is_array( $query ) ) {
1811  $query = wfArrayToCgi( $query );
1812  }
1813  if ( $query2 ) {
1814  if ( is_string( $query2 ) ) {
1815  // $query2 is a string, we will consider this to be
1816  // a deprecated $variant argument and add it to the query
1817  $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1818  } else {
1819  $query2 = wfArrayToCgi( $query2 );
1820  }
1821  // If we have $query content add a & to it first
1822  if ( $query ) {
1823  $query .= '&';
1824  }
1825  // Now append the queries together
1826  $query .= $query2;
1827  }
1828  return $query;
1829  }
1830 
1842  public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1843  $query = self::fixUrlQueryArgs( $query, $query2 );
1844 
1845  # Hand off all the decisions on urls to getLocalURL
1846  $url = $this->getLocalURL( $query );
1847 
1848  # Expand the url to make it a full url. Note that getLocalURL has the
1849  # potential to output full urls for a variety of reasons, so we use
1850  # wfExpandUrl instead of simply prepending $wgServer
1851  $url = wfExpandUrl( $url, $proto );
1852 
1853  # Finally, add the fragment.
1854  $url .= $this->getFragmentForURL();
1855  // Avoid PHP 7.1 warning from passing $this by reference
1856  $titleRef = $this;
1857  Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
1858  return $url;
1859  }
1860 
1877  public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1878  $target = $this;
1879  if ( $this->isExternal() ) {
1880  $target = SpecialPage::getTitleFor(
1881  'GoToInterwiki',
1882  $this->getPrefixedDBkey()
1883  );
1884  }
1885  return $target->getFullURL( $query, false, $proto );
1886  }
1887 
1911  public function getLocalURL( $query = '', $query2 = false ) {
1913 
1914  $query = self::fixUrlQueryArgs( $query, $query2 );
1915 
1916  $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1917  if ( $interwiki ) {
1918  $namespace = $this->getNsText();
1919  if ( $namespace != '' ) {
1920  # Can this actually happen? Interwikis shouldn't be parsed.
1921  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1922  $namespace .= ':';
1923  }
1924  $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1925  $url = wfAppendQuery( $url, $query );
1926  } else {
1927  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1928  if ( $query == '' ) {
1929  $url = str_replace( '$1', $dbkey, $wgArticlePath );
1930  // Avoid PHP 7.1 warning from passing $this by reference
1931  $titleRef = $this;
1932  Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
1933  } else {
1935  $url = false;
1936  $matches = [];
1937 
1938  if ( !empty( $wgActionPaths )
1939  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1940  ) {
1941  $action = urldecode( $matches[2] );
1942  if ( isset( $wgActionPaths[$action] ) ) {
1943  $query = $matches[1];
1944  if ( isset( $matches[4] ) ) {
1945  $query .= $matches[4];
1946  }
1947  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1948  if ( $query != '' ) {
1949  $url = wfAppendQuery( $url, $query );
1950  }
1951  }
1952  }
1953 
1954  if ( $url === false
1956  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1957  && $this->getPageLanguage()->equals( $wgContLang )
1958  && $this->getPageLanguage()->hasVariants()
1959  ) {
1960  $variant = urldecode( $matches[1] );
1961  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1962  // Only do the variant replacement if the given variant is a valid
1963  // variant for the page's language.
1964  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1965  $url = str_replace( '$1', $dbkey, $url );
1966  }
1967  }
1968 
1969  if ( $url === false ) {
1970  if ( $query == '-' ) {
1971  $query = '';
1972  }
1973  $url = "{$wgScript}?title={$dbkey}&{$query}";
1974  }
1975  }
1976  // Avoid PHP 7.1 warning from passing $this by reference
1977  $titleRef = $this;
1978  Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
1979 
1980  // @todo FIXME: This causes breakage in various places when we
1981  // actually expected a local URL and end up with dupe prefixes.
1982  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
1983  $url = $wgServer . $url;
1984  }
1985  }
1986  // Avoid PHP 7.1 warning from passing $this by reference
1987  $titleRef = $this;
1988  Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
1989  return $url;
1990  }
1991 
2009  public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
2010  if ( $this->isExternal() || $proto !== false ) {
2011  $ret = $this->getFullURL( $query, $query2, $proto );
2012  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
2013  $ret = $this->getFragmentForURL();
2014  } else {
2015  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
2016  }
2017  return $ret;
2018  }
2019 
2034  public function getInternalURL( $query = '', $query2 = false ) {
2036  $query = self::fixUrlQueryArgs( $query, $query2 );
2037  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
2038  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
2039  // Avoid PHP 7.1 warning from passing $this by reference
2040  $titleRef = $this;
2041  Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
2042  return $url;
2043  }
2044 
2058  public function getCanonicalURL( $query = '', $query2 = false ) {
2059  $query = self::fixUrlQueryArgs( $query, $query2 );
2060  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
2061  // Avoid PHP 7.1 warning from passing $this by reference
2062  $titleRef = $this;
2063  Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
2064  return $url;
2065  }
2066 
2072  public function getEditURL() {
2073  if ( $this->isExternal() ) {
2074  return '';
2075  }
2076  $s = $this->getLocalURL( 'action=edit' );
2077 
2078  return $s;
2079  }
2080 
2095  public function quickUserCan( $action, $user = null ) {
2096  return $this->userCan( $action, $user, false );
2097  }
2098 
2108  public function userCan( $action, $user = null, $rigor = 'secure' ) {
2109  if ( !$user instanceof User ) {
2110  global $wgUser;
2111  $user = $wgUser;
2112  }
2113 
2114  return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
2115  }
2116 
2132  public function getUserPermissionsErrors(
2133  $action, $user, $rigor = 'secure', $ignoreErrors = []
2134  ) {
2135  $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
2136 
2137  // Remove the errors being ignored.
2138  foreach ( $errors as $index => $error ) {
2139  $errKey = is_array( $error ) ? $error[0] : $error;
2140 
2141  if ( in_array( $errKey, $ignoreErrors ) ) {
2142  unset( $errors[$index] );
2143  }
2144  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
2145  unset( $errors[$index] );
2146  }
2147  }
2148 
2149  return $errors;
2150  }
2151 
2163  private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
2164  if ( !Hooks::run( 'TitleQuickPermissions',
2165  [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
2166  ) {
2167  return $errors;
2168  }
2169 
2170  if ( $action == 'create' ) {
2171  if (
2172  ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
2173  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
2174  ) {
2175  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
2176  }
2177  } elseif ( $action == 'move' ) {
2178  if ( !$user->isAllowed( 'move-rootuserpages' )
2179  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2180  // Show user page-specific message only if the user can move other pages
2181  $errors[] = [ 'cant-move-user-page' ];
2182  }
2183 
2184  // Check if user is allowed to move files if it's a file
2185  if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
2186  $errors[] = [ 'movenotallowedfile' ];
2187  }
2188 
2189  // Check if user is allowed to move category pages if it's a category page
2190  if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
2191  $errors[] = [ 'cant-move-category-page' ];
2192  }
2193 
2194  if ( !$user->isAllowed( 'move' ) ) {
2195  // User can't move anything
2196  $userCanMove = User::groupHasPermission( 'user', 'move' );
2197  $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
2198  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
2199  // custom message if logged-in users without any special rights can move
2200  $errors[] = [ 'movenologintext' ];
2201  } else {
2202  $errors[] = [ 'movenotallowed' ];
2203  }
2204  }
2205  } elseif ( $action == 'move-target' ) {
2206  if ( !$user->isAllowed( 'move' ) ) {
2207  // User can't move anything
2208  $errors[] = [ 'movenotallowed' ];
2209  } elseif ( !$user->isAllowed( 'move-rootuserpages' )
2210  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2211  // Show user page-specific message only if the user can move other pages
2212  $errors[] = [ 'cant-move-to-user-page' ];
2213  } elseif ( !$user->isAllowed( 'move-categorypages' )
2214  && $this->mNamespace == NS_CATEGORY ) {
2215  // Show category page-specific message only if the user can move other pages
2216  $errors[] = [ 'cant-move-to-category-page' ];
2217  }
2218  } elseif ( !$user->isAllowed( $action ) ) {
2219  $errors[] = $this->missingPermissionError( $action, $short );
2220  }
2221 
2222  return $errors;
2223  }
2224 
2233  private function resultToError( $errors, $result ) {
2234  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2235  // A single array representing an error
2236  $errors[] = $result;
2237  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2238  // A nested array representing multiple errors
2239  $errors = array_merge( $errors, $result );
2240  } elseif ( $result !== '' && is_string( $result ) ) {
2241  // A string representing a message-id
2242  $errors[] = [ $result ];
2243  } elseif ( $result instanceof MessageSpecifier ) {
2244  // A message specifier representing an error
2245  $errors[] = [ $result ];
2246  } elseif ( $result === false ) {
2247  // a generic "We don't want them to do that"
2248  $errors[] = [ 'badaccess-group0' ];
2249  }
2250  return $errors;
2251  }
2252 
2264  private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2265  // Use getUserPermissionsErrors instead
2266  $result = '';
2267  // Avoid PHP 7.1 warning from passing $this by reference
2268  $titleRef = $this;
2269  if ( !Hooks::run( 'userCan', [ &$titleRef, &$user, $action, &$result ] ) ) {
2270  return $result ? [] : [ [ 'badaccess-group0' ] ];
2271  }
2272  // Check getUserPermissionsErrors hook
2273  // Avoid PHP 7.1 warning from passing $this by reference
2274  $titleRef = $this;
2275  if ( !Hooks::run( 'getUserPermissionsErrors', [ &$titleRef, &$user, $action, &$result ] ) ) {
2276  $errors = $this->resultToError( $errors, $result );
2277  }
2278  // Check getUserPermissionsErrorsExpensive hook
2279  if (
2280  $rigor !== 'quick'
2281  && !( $short && count( $errors ) > 0 )
2282  && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$titleRef, &$user, $action, &$result ] )
2283  ) {
2284  $errors = $this->resultToError( $errors, $result );
2285  }
2286 
2287  return $errors;
2288  }
2289 
2301  private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2302  # Only 'createaccount' can be performed on special pages,
2303  # which don't actually exist in the DB.
2304  if ( $this->isSpecialPage() && $action !== 'createaccount' ) {
2305  $errors[] = [ 'ns-specialprotected' ];
2306  }
2307 
2308  # Check $wgNamespaceProtection for restricted namespaces
2309  if ( $this->isNamespaceProtected( $user ) ) {
2310  $ns = $this->mNamespace == NS_MAIN ?
2311  wfMessage( 'nstab-main' )->text() : $this->getNsText();
2312  $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2313  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2314  }
2315 
2316  return $errors;
2317  }
2318 
2330  private function checkUserConfigPermissions( $action, $user, $errors, $rigor, $short ) {
2331  # Protect css/json/js subpages of user pages
2332  # XXX: this might be better using restrictions
2333 
2334  if ( $action != 'patrol' ) {
2335  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2336  if (
2337  $this->isUserCssConfigPage()
2338  && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
2339  ) {
2340  $errors[] = [ 'mycustomcssprotected', $action ];
2341  } elseif (
2342  $this->isUserJsonConfigPage()
2343  && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
2344  ) {
2345  $errors[] = [ 'mycustomjsonprotected', $action ];
2346  } elseif (
2347  $this->isUserJsConfigPage()
2348  && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
2349  ) {
2350  $errors[] = [ 'mycustomjsprotected', $action ];
2351  }
2352  } else {
2353  if (
2354  $this->isUserCssConfigPage()
2355  && !$user->isAllowed( 'editusercss' )
2356  ) {
2357  $errors[] = [ 'customcssprotected', $action ];
2358  } elseif (
2359  $this->isUserJsonConfigPage()
2360  && !$user->isAllowed( 'edituserjson' )
2361  ) {
2362  $errors[] = [ 'customjsonprotected', $action ];
2363  } elseif (
2364  $this->isUserJsConfigPage()
2365  && !$user->isAllowed( 'edituserjs' )
2366  ) {
2367  $errors[] = [ 'customjsprotected', $action ];
2368  }
2369  }
2370  }
2371 
2372  return $errors;
2373  }
2374 
2388  private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2389  foreach ( $this->getRestrictions( $action ) as $right ) {
2390  // Backwards compatibility, rewrite sysop -> editprotected
2391  if ( $right == 'sysop' ) {
2392  $right = 'editprotected';
2393  }
2394  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2395  if ( $right == 'autoconfirmed' ) {
2396  $right = 'editsemiprotected';
2397  }
2398  if ( $right == '' ) {
2399  continue;
2400  }
2401  if ( !$user->isAllowed( $right ) ) {
2402  $errors[] = [ 'protectedpagetext', $right, $action ];
2403  } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2404  $errors[] = [ 'protectedpagetext', 'protect', $action ];
2405  }
2406  }
2407 
2408  return $errors;
2409  }
2410 
2422  private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2423  if ( $rigor !== 'quick' && !$this->isUserConfigPage() ) {
2424  # We /could/ use the protection level on the source page, but it's
2425  # fairly ugly as we have to establish a precedence hierarchy for pages
2426  # included by multiple cascade-protected pages. So just restrict
2427  # it to people with 'protect' permission, as they could remove the
2428  # protection anyway.
2429  list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2430  # Cascading protection depends on more than this page...
2431  # Several cascading protected pages may include this page...
2432  # Check each cascading level
2433  # This is only for protection restrictions, not for all actions
2434  if ( isset( $restrictions[$action] ) ) {
2435  foreach ( $restrictions[$action] as $right ) {
2436  // Backwards compatibility, rewrite sysop -> editprotected
2437  if ( $right == 'sysop' ) {
2438  $right = 'editprotected';
2439  }
2440  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2441  if ( $right == 'autoconfirmed' ) {
2442  $right = 'editsemiprotected';
2443  }
2444  if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2445  $pages = '';
2446  foreach ( $cascadingSources as $page ) {
2447  $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2448  }
2449  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2450  }
2451  }
2452  }
2453  }
2454 
2455  return $errors;
2456  }
2457 
2469  private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2471 
2472  if ( $action == 'protect' ) {
2473  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2474  // If they can't edit, they shouldn't protect.
2475  $errors[] = [ 'protect-cantedit' ];
2476  }
2477  } elseif ( $action == 'create' ) {
2478  $title_protection = $this->getTitleProtection();
2479  if ( $title_protection ) {
2480  if ( $title_protection['permission'] == ''
2481  || !$user->isAllowed( $title_protection['permission'] )
2482  ) {
2483  $errors[] = [
2484  'titleprotected',
2485  User::whoIs( $title_protection['user'] ),
2486  $title_protection['reason']
2487  ];
2488  }
2489  }
2490  } elseif ( $action == 'move' ) {
2491  // Check for immobile pages
2492  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2493  // Specific message for this case
2494  $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2495  } elseif ( !$this->isMovable() ) {
2496  // Less specific message for rarer cases
2497  $errors[] = [ 'immobile-source-page' ];
2498  }
2499  } elseif ( $action == 'move-target' ) {
2500  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2501  $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2502  } elseif ( !$this->isMovable() ) {
2503  $errors[] = [ 'immobile-target-page' ];
2504  }
2505  } elseif ( $action == 'delete' ) {
2506  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2507  if ( !$tempErrors ) {
2508  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2509  $user, $tempErrors, $rigor, true );
2510  }
2511  if ( $tempErrors ) {
2512  // If protection keeps them from editing, they shouldn't be able to delete.
2513  $errors[] = [ 'deleteprotected' ];
2514  }
2515  if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2516  && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2517  ) {
2518  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2519  }
2520  } elseif ( $action === 'undelete' ) {
2521  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2522  // Undeleting implies editing
2523  $errors[] = [ 'undelete-cantedit' ];
2524  }
2525  if ( !$this->exists()
2526  && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
2527  ) {
2528  // Undeleting where nothing currently exists implies creating
2529  $errors[] = [ 'undelete-cantcreate' ];
2530  }
2531  }
2532  return $errors;
2533  }
2534 
2546  private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2548  // Account creation blocks handled at userlogin.
2549  // Unblocking handled in SpecialUnblock
2550  if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2551  return $errors;
2552  }
2553 
2554  // Optimize for a very common case
2555  if ( $action === 'read' && !$wgBlockDisablesLogin ) {
2556  return $errors;
2557  }
2558 
2560  && !$user->isEmailConfirmed()
2561  && $action === 'edit'
2562  ) {
2563  $errors[] = [ 'confirmedittext' ];
2564  }
2565 
2566  $useSlave = ( $rigor !== 'secure' );
2567  if ( ( $action == 'edit' || $action == 'create' )
2568  && !$user->isBlockedFrom( $this, $useSlave )
2569  ) {
2570  // Don't block the user from editing their own talk page unless they've been
2571  // explicitly blocked from that too.
2572  } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2573  // @todo FIXME: Pass the relevant context into this function.
2574  $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2575  }
2576 
2577  return $errors;
2578  }
2579 
2591  private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2593 
2594  $whitelisted = false;
2595  if ( User::isEveryoneAllowed( 'read' ) ) {
2596  # Shortcut for public wikis, allows skipping quite a bit of code
2597  $whitelisted = true;
2598  } elseif ( $user->isAllowed( 'read' ) ) {
2599  # If the user is allowed to read pages, he is allowed to read all pages
2600  $whitelisted = true;
2601  } elseif ( $this->isSpecial( 'Userlogin' )
2602  || $this->isSpecial( 'PasswordReset' )
2603  || $this->isSpecial( 'Userlogout' )
2604  ) {
2605  # Always grant access to the login page.
2606  # Even anons need to be able to log in.
2607  $whitelisted = true;
2608  } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2609  # Time to check the whitelist
2610  # Only do these checks is there's something to check against
2611  $name = $this->getPrefixedText();
2612  $dbName = $this->getPrefixedDBkey();
2613 
2614  // Check for explicit whitelisting with and without underscores
2615  if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2616  $whitelisted = true;
2617  } elseif ( $this->getNamespace() == NS_MAIN ) {
2618  # Old settings might have the title prefixed with
2619  # a colon for main-namespace pages
2620  if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2621  $whitelisted = true;
2622  }
2623  } elseif ( $this->isSpecialPage() ) {
2624  # If it's a special page, ditch the subpage bit and check again
2625  $name = $this->getDBkey();
2626  list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2627  if ( $name ) {
2628  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2629  if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2630  $whitelisted = true;
2631  }
2632  }
2633  }
2634  }
2635 
2636  if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2637  $name = $this->getPrefixedText();
2638  // Check for regex whitelisting
2639  foreach ( $wgWhitelistReadRegexp as $listItem ) {
2640  if ( preg_match( $listItem, $name ) ) {
2641  $whitelisted = true;
2642  break;
2643  }
2644  }
2645  }
2646 
2647  if ( !$whitelisted ) {
2648  # If the title is not whitelisted, give extensions a chance to do so...
2649  Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2650  if ( !$whitelisted ) {
2651  $errors[] = $this->missingPermissionError( $action, $short );
2652  }
2653  }
2654 
2655  return $errors;
2656  }
2657 
2666  private function missingPermissionError( $action, $short ) {
2667  // We avoid expensive display logic for quickUserCan's and such
2668  if ( $short ) {
2669  return [ 'badaccess-group0' ];
2670  }
2671 
2672  return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
2673  }
2674 
2690  $action, $user, $rigor = 'secure', $short = false
2691  ) {
2692  if ( $rigor === true ) {
2693  $rigor = 'secure'; // b/c
2694  } elseif ( $rigor === false ) {
2695  $rigor = 'quick'; // b/c
2696  } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2697  throw new Exception( "Invalid rigor parameter '$rigor'." );
2698  }
2699 
2700  # Read has special handling
2701  if ( $action == 'read' ) {
2702  $checks = [
2703  'checkPermissionHooks',
2704  'checkReadPermissions',
2705  'checkUserBlock', // for wgBlockDisablesLogin
2706  ];
2707  # Don't call checkSpecialsAndNSPermissions or checkUserConfigPermissions
2708  # here as it will lead to duplicate error messages. This is okay to do
2709  # since anywhere that checks for create will also check for edit, and
2710  # those checks are called for edit.
2711  } elseif ( $action == 'create' ) {
2712  $checks = [
2713  'checkQuickPermissions',
2714  'checkPermissionHooks',
2715  'checkPageRestrictions',
2716  'checkCascadingSourcesRestrictions',
2717  'checkActionPermissions',
2718  'checkUserBlock'
2719  ];
2720  } else {
2721  $checks = [
2722  'checkQuickPermissions',
2723  'checkPermissionHooks',
2724  'checkSpecialsAndNSPermissions',
2725  'checkUserConfigPermissions',
2726  'checkPageRestrictions',
2727  'checkCascadingSourcesRestrictions',
2728  'checkActionPermissions',
2729  'checkUserBlock'
2730  ];
2731  }
2732 
2733  $errors = [];
2734  while ( count( $checks ) > 0 &&
2735  !( $short && count( $errors ) > 0 ) ) {
2736  $method = array_shift( $checks );
2737  $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2738  }
2739 
2740  return $errors;
2741  }
2742 
2750  public static function getFilteredRestrictionTypes( $exists = true ) {
2752  $types = $wgRestrictionTypes;
2753  if ( $exists ) {
2754  # Remove the create restriction for existing titles
2755  $types = array_diff( $types, [ 'create' ] );
2756  } else {
2757  # Only the create and upload restrictions apply to non-existing titles
2758  $types = array_intersect( $types, [ 'create', 'upload' ] );
2759  }
2760  return $types;
2761  }
2762 
2768  public function getRestrictionTypes() {
2769  if ( $this->isSpecialPage() ) {
2770  return [];
2771  }
2772 
2773  $types = self::getFilteredRestrictionTypes( $this->exists() );
2774 
2775  if ( $this->getNamespace() != NS_FILE ) {
2776  # Remove the upload restriction for non-file titles
2777  $types = array_diff( $types, [ 'upload' ] );
2778  }
2779 
2780  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2781 
2782  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2783  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2784 
2785  return $types;
2786  }
2787 
2795  public function getTitleProtection() {
2796  $protection = $this->getTitleProtectionInternal();
2797  if ( $protection ) {
2798  if ( $protection['permission'] == 'sysop' ) {
2799  $protection['permission'] = 'editprotected'; // B/C
2800  }
2801  if ( $protection['permission'] == 'autoconfirmed' ) {
2802  $protection['permission'] = 'editsemiprotected'; // B/C
2803  }
2804  }
2805  return $protection;
2806  }
2807 
2818  protected function getTitleProtectionInternal() {
2819  // Can't protect pages in special namespaces
2820  if ( $this->getNamespace() < 0 ) {
2821  return false;
2822  }
2823 
2824  // Can't protect pages that exist.
2825  if ( $this->exists() ) {
2826  return false;
2827  }
2828 
2829  if ( $this->mTitleProtection === null ) {
2830  $dbr = wfGetDB( DB_REPLICA );
2831  $commentStore = CommentStore::getStore();
2832  $commentQuery = $commentStore->getJoin( 'pt_reason' );
2833  $res = $dbr->select(
2834  [ 'protected_titles' ] + $commentQuery['tables'],
2835  [
2836  'user' => 'pt_user',
2837  'expiry' => 'pt_expiry',
2838  'permission' => 'pt_create_perm'
2839  ] + $commentQuery['fields'],
2840  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2841  __METHOD__,
2842  [],
2843  $commentQuery['joins']
2844  );
2845 
2846  // fetchRow returns false if there are no rows.
2847  $row = $dbr->fetchRow( $res );
2848  if ( $row ) {
2849  $this->mTitleProtection = [
2850  'user' => $row['user'],
2851  'expiry' => $dbr->decodeExpiry( $row['expiry'] ),
2852  'permission' => $row['permission'],
2853  'reason' => $commentStore->getComment( 'pt_reason', $row )->text,
2854  ];
2855  } else {
2856  $this->mTitleProtection = false;
2857  }
2858  }
2859  return $this->mTitleProtection;
2860  }
2861 
2865  public function deleteTitleProtection() {
2866  $dbw = wfGetDB( DB_MASTER );
2867 
2868  $dbw->delete(
2869  'protected_titles',
2870  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2871  __METHOD__
2872  );
2873  $this->mTitleProtection = false;
2874  }
2875 
2883  public function isSemiProtected( $action = 'edit' ) {
2885 
2886  $restrictions = $this->getRestrictions( $action );
2888  if ( !$restrictions || !$semi ) {
2889  // Not protected, or all protection is full protection
2890  return false;
2891  }
2892 
2893  // Remap autoconfirmed to editsemiprotected for BC
2894  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2895  $semi[$key] = 'editsemiprotected';
2896  }
2897  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2898  $restrictions[$key] = 'editsemiprotected';
2899  }
2900 
2901  return !array_diff( $restrictions, $semi );
2902  }
2903 
2911  public function isProtected( $action = '' ) {
2913 
2914  $restrictionTypes = $this->getRestrictionTypes();
2915 
2916  # Special pages have inherent protection
2917  if ( $this->isSpecialPage() ) {
2918  return true;
2919  }
2920 
2921  # Check regular protection levels
2922  foreach ( $restrictionTypes as $type ) {
2923  if ( $action == $type || $action == '' ) {
2924  $r = $this->getRestrictions( $type );
2925  foreach ( $wgRestrictionLevels as $level ) {
2926  if ( in_array( $level, $r ) && $level != '' ) {
2927  return true;
2928  }
2929  }
2930  }
2931  }
2932 
2933  return false;
2934  }
2935 
2943  public function isNamespaceProtected( User $user ) {
2945 
2946  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2947  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2948  if ( $right != '' && !$user->isAllowed( $right ) ) {
2949  return true;
2950  }
2951  }
2952  }
2953  return false;
2954  }
2955 
2961  public function isCascadeProtected() {
2962  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2963  return ( $sources > 0 );
2964  }
2965 
2975  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2976  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2977  }
2978 
2992  public function getCascadeProtectionSources( $getPages = true ) {
2993  $pagerestrictions = [];
2994 
2995  if ( $this->mCascadeSources !== null && $getPages ) {
2997  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2998  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2999  }
3000 
3001  $dbr = wfGetDB( DB_REPLICA );
3002 
3003  if ( $this->getNamespace() == NS_FILE ) {
3004  $tables = [ 'imagelinks', 'page_restrictions' ];
3005  $where_clauses = [
3006  'il_to' => $this->getDBkey(),
3007  'il_from=pr_page',
3008  'pr_cascade' => 1
3009  ];
3010  } else {
3011  $tables = [ 'templatelinks', 'page_restrictions' ];
3012  $where_clauses = [
3013  'tl_namespace' => $this->getNamespace(),
3014  'tl_title' => $this->getDBkey(),
3015  'tl_from=pr_page',
3016  'pr_cascade' => 1
3017  ];
3018  }
3019 
3020  if ( $getPages ) {
3021  $cols = [ 'pr_page', 'page_namespace', 'page_title',
3022  'pr_expiry', 'pr_type', 'pr_level' ];
3023  $where_clauses[] = 'page_id=pr_page';
3024  $tables[] = 'page';
3025  } else {
3026  $cols = [ 'pr_expiry' ];
3027  }
3028 
3029  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
3030 
3031  $sources = $getPages ? [] : false;
3032  $now = wfTimestampNow();
3033 
3034  foreach ( $res as $row ) {
3035  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
3036  if ( $expiry > $now ) {
3037  if ( $getPages ) {
3038  $page_id = $row->pr_page;
3039  $page_ns = $row->page_namespace;
3040  $page_title = $row->page_title;
3041  $sources[$page_id] = self::makeTitle( $page_ns, $page_title );
3042  # Add groups needed for each restriction type if its not already there
3043  # Make sure this restriction type still exists
3044 
3045  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
3046  $pagerestrictions[$row->pr_type] = [];
3047  }
3048 
3049  if (
3050  isset( $pagerestrictions[$row->pr_type] )
3051  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
3052  ) {
3053  $pagerestrictions[$row->pr_type][] = $row->pr_level;
3054  }
3055  } else {
3056  $sources = true;
3057  }
3058  }
3059  }
3060 
3061  if ( $getPages ) {
3062  $this->mCascadeSources = $sources;
3063  $this->mCascadingRestrictions = $pagerestrictions;
3064  } else {
3065  $this->mHasCascadingRestrictions = $sources;
3066  }
3067 
3068  return [ $sources, $pagerestrictions ];
3069  }
3070 
3078  public function areRestrictionsLoaded() {
3080  }
3081 
3091  public function getRestrictions( $action ) {
3092  if ( !$this->mRestrictionsLoaded ) {
3093  $this->loadRestrictions();
3094  }
3095  return isset( $this->mRestrictions[$action] )
3096  ? $this->mRestrictions[$action]
3097  : [];
3098  }
3099 
3107  public function getAllRestrictions() {
3108  if ( !$this->mRestrictionsLoaded ) {
3109  $this->loadRestrictions();
3110  }
3111  return $this->mRestrictions;
3112  }
3113 
3121  public function getRestrictionExpiry( $action ) {
3122  if ( !$this->mRestrictionsLoaded ) {
3123  $this->loadRestrictions();
3124  }
3125  return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
3126  }
3127 
3134  if ( !$this->mRestrictionsLoaded ) {
3135  $this->loadRestrictions();
3136  }
3137 
3139  }
3140 
3152  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
3153  $dbr = wfGetDB( DB_REPLICA );
3154 
3155  $restrictionTypes = $this->getRestrictionTypes();
3156 
3157  foreach ( $restrictionTypes as $type ) {
3158  $this->mRestrictions[$type] = [];
3159  $this->mRestrictionsExpiry[$type] = 'infinity';
3160  }
3161 
3162  $this->mCascadeRestriction = false;
3163 
3164  # Backwards-compatibility: also load the restrictions from the page record (old format).
3165  if ( $oldFashionedRestrictions !== null ) {
3166  $this->mOldRestrictions = $oldFashionedRestrictions;
3167  }
3168 
3169  if ( $this->mOldRestrictions === false ) {
3170  $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
3171  [ 'page_id' => $this->getArticleID() ], __METHOD__ );
3172  }
3173 
3174  if ( $this->mOldRestrictions != '' ) {
3175  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
3176  $temp = explode( '=', trim( $restrict ) );
3177  if ( count( $temp ) == 1 ) {
3178  // old old format should be treated as edit/move restriction
3179  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
3180  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
3181  } else {
3182  $restriction = trim( $temp[1] );
3183  if ( $restriction != '' ) { // some old entries are empty
3184  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
3185  }
3186  }
3187  }
3188  }
3189 
3190  if ( count( $rows ) ) {
3191  # Current system - load second to make them override.
3192  $now = wfTimestampNow();
3193 
3194  # Cycle through all the restrictions.
3195  foreach ( $rows as $row ) {
3196  // Don't take care of restrictions types that aren't allowed
3197  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
3198  continue;
3199  }
3200 
3201  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
3202 
3203  // Only apply the restrictions if they haven't expired!
3204  if ( !$expiry || $expiry > $now ) {
3205  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
3206  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
3207 
3208  $this->mCascadeRestriction |= $row->pr_cascade;
3209  }
3210  }
3211  }
3212 
3213  $this->mRestrictionsLoaded = true;
3214  }
3215 
3224  public function loadRestrictions( $oldFashionedRestrictions = null ) {
3225  if ( $this->mRestrictionsLoaded ) {
3226  return;
3227  }
3228 
3229  $id = $this->getArticleID();
3230  if ( $id ) {
3232  $rows = $cache->getWithSetCallback(
3233  // Page protections always leave a new null revision
3234  $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ),
3235  $cache::TTL_DAY,
3236  function ( $curValue, &$ttl, array &$setOpts ) {
3237  $dbr = wfGetDB( DB_REPLICA );
3238 
3239  $setOpts += Database::getCacheSetOptions( $dbr );
3240 
3241  return iterator_to_array(
3242  $dbr->select(
3243  'page_restrictions',
3244  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
3245  [ 'pr_page' => $this->getArticleID() ],
3246  __METHOD__
3247  )
3248  );
3249  }
3250  );
3251 
3252  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
3253  } else {
3254  $title_protection = $this->getTitleProtectionInternal();
3255 
3256  if ( $title_protection ) {
3257  $now = wfTimestampNow();
3258  $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
3259 
3260  if ( !$expiry || $expiry > $now ) {
3261  // Apply the restrictions
3262  $this->mRestrictionsExpiry['create'] = $expiry;
3263  $this->mRestrictions['create'] =
3264  explode( ',', trim( $title_protection['permission'] ) );
3265  } else { // Get rid of the old restrictions
3266  $this->mTitleProtection = false;
3267  }
3268  } else {
3269  $this->mRestrictionsExpiry['create'] = 'infinity';
3270  }
3271  $this->mRestrictionsLoaded = true;
3272  }
3273  }
3274 
3279  public function flushRestrictions() {
3280  $this->mRestrictionsLoaded = false;
3281  $this->mTitleProtection = null;
3282  }
3283 
3289  static function purgeExpiredRestrictions() {
3290  if ( wfReadOnly() ) {
3291  return;
3292  }
3293 
3295  wfGetDB( DB_MASTER ),
3296  __METHOD__,
3297  function ( IDatabase $dbw, $fname ) {
3298  $config = MediaWikiServices::getInstance()->getMainConfig();
3299  $ids = $dbw->selectFieldValues(
3300  'page_restrictions',
3301  'pr_id',
3302  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3303  $fname,
3304  [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
3305  );
3306  if ( $ids ) {
3307  $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
3308  }
3309  }
3310  ) );
3311 
3313  wfGetDB( DB_MASTER ),
3314  __METHOD__,
3315  function ( IDatabase $dbw, $fname ) {
3316  $dbw->delete(
3317  'protected_titles',
3318  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3319  $fname
3320  );
3321  }
3322  ) );
3323  }
3324 
3330  public function hasSubpages() {
3331  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3332  # Duh
3333  return false;
3334  }
3335 
3336  # We dynamically add a member variable for the purpose of this method
3337  # alone to cache the result. There's no point in having it hanging
3338  # around uninitialized in every Title object; therefore we only add it
3339  # if needed and don't declare it statically.
3340  if ( $this->mHasSubpages === null ) {
3341  $this->mHasSubpages = false;
3342  $subpages = $this->getSubpages( 1 );
3343  if ( $subpages instanceof TitleArray ) {
3344  $this->mHasSubpages = (bool)$subpages->count();
3345  }
3346  }
3347 
3348  return $this->mHasSubpages;
3349  }
3350 
3358  public function getSubpages( $limit = -1 ) {
3359  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3360  return [];
3361  }
3362 
3363  $dbr = wfGetDB( DB_REPLICA );
3364  $conds['page_namespace'] = $this->getNamespace();
3365  $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3366  $options = [];
3367  if ( $limit > -1 ) {
3368  $options['LIMIT'] = $limit;
3369  }
3371  $dbr->select( 'page',
3372  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3373  $conds,
3374  __METHOD__,
3375  $options
3376  )
3377  );
3378  }
3379 
3385  public function isDeleted() {
3386  if ( $this->getNamespace() < 0 ) {
3387  $n = 0;
3388  } else {
3389  $dbr = wfGetDB( DB_REPLICA );
3390 
3391  $n = $dbr->selectField( 'archive', 'COUNT(*)',
3392  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3393  __METHOD__
3394  );
3395  if ( $this->getNamespace() == NS_FILE ) {
3396  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3397  [ 'fa_name' => $this->getDBkey() ],
3398  __METHOD__
3399  );
3400  }
3401  }
3402  return (int)$n;
3403  }
3404 
3410  public function isDeletedQuick() {
3411  if ( $this->getNamespace() < 0 ) {
3412  return false;
3413  }
3414  $dbr = wfGetDB( DB_REPLICA );
3415  $deleted = (bool)$dbr->selectField( 'archive', '1',
3416  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3417  __METHOD__
3418  );
3419  if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3420  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3421  [ 'fa_name' => $this->getDBkey() ],
3422  __METHOD__
3423  );
3424  }
3425  return $deleted;
3426  }
3427 
3436  public function getArticleID( $flags = 0 ) {
3437  if ( $this->getNamespace() < 0 ) {
3438  $this->mArticleID = 0;
3439  return $this->mArticleID;
3440  }
3441  $linkCache = LinkCache::singleton();
3442  if ( $flags & self::GAID_FOR_UPDATE ) {
3443  $oldUpdate = $linkCache->forUpdate( true );
3444  $linkCache->clearLink( $this );
3445  $this->mArticleID = $linkCache->addLinkObj( $this );
3446  $linkCache->forUpdate( $oldUpdate );
3447  } else {
3448  if ( -1 == $this->mArticleID ) {
3449  $this->mArticleID = $linkCache->addLinkObj( $this );
3450  }
3451  }
3452  return $this->mArticleID;
3453  }
3454 
3462  public function isRedirect( $flags = 0 ) {
3463  if ( !is_null( $this->mRedirect ) ) {
3464  return $this->mRedirect;
3465  }
3466  if ( !$this->getArticleID( $flags ) ) {
3467  $this->mRedirect = false;
3468  return $this->mRedirect;
3469  }
3470 
3471  $linkCache = LinkCache::singleton();
3472  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3473  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3474  if ( $cached === null ) {
3475  # Trust LinkCache's state over our own
3476  # LinkCache is telling us that the page doesn't exist, despite there being cached
3477  # data relating to an existing page in $this->mArticleID. Updaters should clear
3478  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3479  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3480  # LinkCache to refresh its data from the master.
3481  $this->mRedirect = false;
3482  return $this->mRedirect;
3483  }
3484 
3485  $this->mRedirect = (bool)$cached;
3486 
3487  return $this->mRedirect;
3488  }
3489 
3497  public function getLength( $flags = 0 ) {
3498  if ( $this->mLength != -1 ) {
3499  return $this->mLength;
3500  }
3501  if ( !$this->getArticleID( $flags ) ) {
3502  $this->mLength = 0;
3503  return $this->mLength;
3504  }
3505  $linkCache = LinkCache::singleton();
3506  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3507  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3508  if ( $cached === null ) {
3509  # Trust LinkCache's state over our own, as for isRedirect()
3510  $this->mLength = 0;
3511  return $this->mLength;
3512  }
3513 
3514  $this->mLength = intval( $cached );
3515 
3516  return $this->mLength;
3517  }
3518 
3525  public function getLatestRevID( $flags = 0 ) {
3526  if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3527  return intval( $this->mLatestID );
3528  }
3529  if ( !$this->getArticleID( $flags ) ) {
3530  $this->mLatestID = 0;
3531  return $this->mLatestID;
3532  }
3533  $linkCache = LinkCache::singleton();
3534  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3535  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3536  if ( $cached === null ) {
3537  # Trust LinkCache's state over our own, as for isRedirect()
3538  $this->mLatestID = 0;
3539  return $this->mLatestID;
3540  }
3541 
3542  $this->mLatestID = intval( $cached );
3543 
3544  return $this->mLatestID;
3545  }
3546 
3557  public function resetArticleID( $newid ) {
3558  $linkCache = LinkCache::singleton();
3559  $linkCache->clearLink( $this );
3560 
3561  if ( $newid === false ) {
3562  $this->mArticleID = -1;
3563  } else {
3564  $this->mArticleID = intval( $newid );
3565  }
3566  $this->mRestrictionsLoaded = false;
3567  $this->mRestrictions = [];
3568  $this->mOldRestrictions = false;
3569  $this->mRedirect = null;
3570  $this->mLength = -1;
3571  $this->mLatestID = false;
3572  $this->mContentModel = false;
3573  $this->mEstimateRevisions = null;
3574  $this->mPageLanguage = false;
3575  $this->mDbPageLanguage = false;
3576  $this->mIsBigDeletion = null;
3577  }
3578 
3579  public static function clearCaches() {
3580  $linkCache = LinkCache::singleton();
3581  $linkCache->clear();
3582 
3584  $titleCache->clear();
3585  }
3586 
3594  public static function capitalize( $text, $ns = NS_MAIN ) {
3596 
3597  if ( MWNamespace::isCapitalized( $ns ) ) {
3598  return $wgContLang->ucfirst( $text );
3599  } else {
3600  return $text;
3601  }
3602  }
3603 
3616  private function secureAndSplit() {
3617  # Initialisation
3618  $this->mInterwiki = '';
3619  $this->mFragment = '';
3620  $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3621 
3622  $dbkey = $this->mDbkeyform;
3623 
3624  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3625  // the parsing code with Title, while avoiding massive refactoring.
3626  // @todo: get rid of secureAndSplit, refactor parsing code.
3627  // @note: getTitleParser() returns a TitleParser implementation which does not have a
3628  // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3629  $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3630  // MalformedTitleException can be thrown here
3631  $parts = $titleCodec->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3632 
3633  # Fill fields
3634  $this->setFragment( '#' . $parts['fragment'] );
3635  $this->mInterwiki = $parts['interwiki'];
3636  $this->mLocalInterwiki = $parts['local_interwiki'];
3637  $this->mNamespace = $parts['namespace'];
3638  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3639 
3640  $this->mDbkeyform = $parts['dbkey'];
3641  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3642  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3643 
3644  # We already know that some pages won't be in the database!
3645  if ( $this->isExternal() || $this->isSpecialPage() ) {
3646  $this->mArticleID = 0;
3647  }
3648 
3649  return true;
3650  }
3651 
3664  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3665  if ( count( $options ) > 0 ) {
3666  $db = wfGetDB( DB_MASTER );
3667  } else {
3668  $db = wfGetDB( DB_REPLICA );
3669  }
3670 
3671  $res = $db->select(
3672  [ 'page', $table ],
3673  self::getSelectFields(),
3674  [
3675  "{$prefix}_from=page_id",
3676  "{$prefix}_namespace" => $this->getNamespace(),
3677  "{$prefix}_title" => $this->getDBkey() ],
3678  __METHOD__,
3679  $options
3680  );
3681 
3682  $retVal = [];
3683  if ( $res->numRows() ) {
3684  $linkCache = LinkCache::singleton();
3685  foreach ( $res as $row ) {
3686  $titleObj = self::makeTitle( $row->page_namespace, $row->page_title );
3687  if ( $titleObj ) {
3688  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3689  $retVal[] = $titleObj;
3690  }
3691  }
3692  }
3693  return $retVal;
3694  }
3695 
3706  public function getTemplateLinksTo( $options = [] ) {
3707  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3708  }
3709 
3722  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3723  $id = $this->getArticleID();
3724 
3725  # If the page doesn't exist; there can't be any link from this page
3726  if ( !$id ) {
3727  return [];
3728  }
3729 
3730  $db = wfGetDB( DB_REPLICA );
3731 
3732  $blNamespace = "{$prefix}_namespace";
3733  $blTitle = "{$prefix}_title";
3734 
3735  $pageQuery = WikiPage::getQueryInfo();
3736  $res = $db->select(
3737  [ $table, 'nestpage' => $pageQuery['tables'] ],
3738  array_merge(
3739  [ $blNamespace, $blTitle ],
3740  $pageQuery['fields']
3741  ),
3742  [ "{$prefix}_from" => $id ],
3743  __METHOD__,
3744  $options,
3745  [ 'nestpage' => [
3746  'LEFT JOIN',
3747  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3748  ] ] + $pageQuery['joins']
3749  );
3750 
3751  $retVal = [];
3752  $linkCache = LinkCache::singleton();
3753  foreach ( $res as $row ) {
3754  if ( $row->page_id ) {
3755  $titleObj = self::newFromRow( $row );
3756  } else {
3757  $titleObj = self::makeTitle( $row->$blNamespace, $row->$blTitle );
3758  $linkCache->addBadLinkObj( $titleObj );
3759  }
3760  $retVal[] = $titleObj;
3761  }
3762 
3763  return $retVal;
3764  }
3765 
3776  public function getTemplateLinksFrom( $options = [] ) {
3777  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3778  }
3779 
3788  public function getBrokenLinksFrom() {
3789  if ( $this->getArticleID() == 0 ) {
3790  # All links from article ID 0 are false positives
3791  return [];
3792  }
3793 
3794  $dbr = wfGetDB( DB_REPLICA );
3795  $res = $dbr->select(
3796  [ 'page', 'pagelinks' ],
3797  [ 'pl_namespace', 'pl_title' ],
3798  [
3799  'pl_from' => $this->getArticleID(),
3800  'page_namespace IS NULL'
3801  ],
3802  __METHOD__, [],
3803  [
3804  'page' => [
3805  'LEFT JOIN',
3806  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3807  ]
3808  ]
3809  );
3810 
3811  $retVal = [];
3812  foreach ( $res as $row ) {
3813  $retVal[] = self::makeTitle( $row->pl_namespace, $row->pl_title );
3814  }
3815  return $retVal;
3816  }
3817 
3824  public function getCdnUrls() {
3825  $urls = [
3826  $this->getInternalURL(),
3827  $this->getInternalURL( 'action=history' )
3828  ];
3829 
3830  $pageLang = $this->getPageLanguage();
3831  if ( $pageLang->hasVariants() ) {
3832  $variants = $pageLang->getVariants();
3833  foreach ( $variants as $vCode ) {
3834  $urls[] = $this->getInternalURL( $vCode );
3835  }
3836  }
3837 
3838  // If we are looking at a css/js user subpage, purge the action=raw.
3839  if ( $this->isUserJsConfigPage() ) {
3840  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3841  } elseif ( $this->isUserJsonConfigPage() ) {
3842  $urls[] = $this->getInternalURL( 'action=raw&ctype=application/json' );
3843  } elseif ( $this->isUserCssConfigPage() ) {
3844  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3845  }
3846 
3847  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3848  return $urls;
3849  }
3850 
3854  public function getSquidURLs() {
3855  return $this->getCdnUrls();
3856  }
3857 
3861  public function purgeSquid() {
3863  new CdnCacheUpdate( $this->getCdnUrls() ),
3865  );
3866  }
3867 
3878  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3879  global $wgUser;
3880 
3881  if ( !( $nt instanceof Title ) ) {
3882  // Normally we'd add this to $errors, but we'll get
3883  // lots of syntax errors if $nt is not an object
3884  return [ [ 'badtitletext' ] ];
3885  }
3886 
3887  $mp = new MovePage( $this, $nt );
3888  $errors = $mp->isValidMove()->getErrorsArray();
3889  if ( $auth ) {
3890  $errors = wfMergeErrorArrays(
3891  $errors,
3892  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3893  );
3894  }
3895 
3896  return $errors ?: true;
3897  }
3898 
3905  protected function validateFileMoveOperation( $nt ) {
3906  global $wgUser;
3907 
3908  $errors = [];
3909 
3910  $destFile = wfLocalFile( $nt );
3911  $destFile->load( File::READ_LATEST );
3912  if ( !$wgUser->isAllowed( 'reupload-shared' )
3913  && !$destFile->exists() && wfFindFile( $nt )
3914  ) {
3915  $errors[] = [ 'file-exists-sharedrepo' ];
3916  }
3917 
3918  return $errors;
3919  }
3920 
3934  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true,
3935  array $changeTags = []
3936  ) {
3937  global $wgUser;
3938  $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3939  if ( is_array( $err ) ) {
3940  // Auto-block user's IP if the account was "hard" blocked
3941  $wgUser->spreadAnyEditBlock();
3942  return $err;
3943  }
3944  // Check suppressredirect permission
3945  if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3946  $createRedirect = true;
3947  }
3948 
3949  $mp = new MovePage( $this, $nt );
3950  $status = $mp->move( $wgUser, $reason, $createRedirect, $changeTags );
3951  if ( $status->isOK() ) {
3952  return true;
3953  } else {
3954  return $status->getErrorsArray();
3955  }
3956  }
3957 
3972  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true,
3973  array $changeTags = []
3974  ) {
3976  // Check permissions
3977  if ( !$this->userCan( 'move-subpages' ) ) {
3978  return [
3979  [ 'cant-move-subpages' ],
3980  ];
3981  }
3982  // Do the source and target namespaces support subpages?
3983  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3984  return [
3985  [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $this->getNamespace() ) ],
3986  ];
3987  }
3988  if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3989  return [
3990  [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $nt->getNamespace() ) ],
3991  ];
3992  }
3993 
3994  $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3995  $retval = [];
3996  $count = 0;
3997  foreach ( $subpages as $oldSubpage ) {
3998  $count++;
3999  if ( $count > $wgMaximumMovedPages ) {
4000  $retval[$oldSubpage->getPrefixedText()] = [
4001  [ 'movepage-max-pages', $wgMaximumMovedPages ],
4002  ];
4003  break;
4004  }
4005 
4006  // We don't know whether this function was called before
4007  // or after moving the root page, so check both
4008  // $this and $nt
4009  if ( $oldSubpage->getArticleID() == $this->getArticleID()
4010  || $oldSubpage->getArticleID() == $nt->getArticleID()
4011  ) {
4012  // When moving a page to a subpage of itself,
4013  // don't move it twice
4014  continue;
4015  }
4016  $newPageName = preg_replace(
4017  '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
4018  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
4019  $oldSubpage->getDBkey() );
4020  if ( $oldSubpage->isTalkPage() ) {
4021  $newNs = $nt->getTalkPage()->getNamespace();
4022  } else {
4023  $newNs = $nt->getSubjectPage()->getNamespace();
4024  }
4025  # T16385: we need makeTitleSafe because the new page names may
4026  # be longer than 255 characters.
4027  $newSubpage = self::makeTitleSafe( $newNs, $newPageName );
4028 
4029  $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect, $changeTags );
4030  if ( $success === true ) {
4031  $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
4032  } else {
4033  $retval[$oldSubpage->getPrefixedText()] = $success;
4034  }
4035  }
4036  return $retval;
4037  }
4038 
4045  public function isSingleRevRedirect() {
4047 
4048  $dbw = wfGetDB( DB_MASTER );
4049 
4050  # Is it a redirect?
4051  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
4052  if ( $wgContentHandlerUseDB ) {
4053  $fields[] = 'page_content_model';
4054  }
4055 
4056  $row = $dbw->selectRow( 'page',
4057  $fields,
4058  $this->pageCond(),
4059  __METHOD__,
4060  [ 'FOR UPDATE' ]
4061  );
4062  # Cache some fields we may want
4063  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
4064  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
4065  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
4066  $this->mContentModel = $row && isset( $row->page_content_model )
4067  ? strval( $row->page_content_model )
4068  : false;
4069 
4070  if ( !$this->mRedirect ) {
4071  return false;
4072  }
4073  # Does the article have a history?
4074  $row = $dbw->selectField( [ 'page', 'revision' ],
4075  'rev_id',
4076  [ 'page_namespace' => $this->getNamespace(),
4077  'page_title' => $this->getDBkey(),
4078  'page_id=rev_page',
4079  'page_latest != rev_id'
4080  ],
4081  __METHOD__,
4082  [ 'FOR UPDATE' ]
4083  );
4084  # Return true if there was no history
4085  return ( $row === false );
4086  }
4087 
4096  public function isValidMoveTarget( $nt ) {
4097  # Is it an existing file?
4098  if ( $nt->getNamespace() == NS_FILE ) {
4099  $file = wfLocalFile( $nt );
4100  $file->load( File::READ_LATEST );
4101  if ( $file->exists() ) {
4102  wfDebug( __METHOD__ . ": file exists\n" );
4103  return false;
4104  }
4105  }
4106  # Is it a redirect with no history?
4107  if ( !$nt->isSingleRevRedirect() ) {
4108  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
4109  return false;
4110  }
4111  # Get the article text
4112  $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
4113  if ( !is_object( $rev ) ) {
4114  return false;
4115  }
4116  $content = $rev->getContent();
4117  # Does the redirect point to the source?
4118  # Or is it a broken self-redirect, usually caused by namespace collisions?
4119  $redirTitle = $content ? $content->getRedirectTarget() : null;
4120 
4121  if ( $redirTitle ) {
4122  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
4123  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
4124  wfDebug( __METHOD__ . ": redirect points to other page\n" );
4125  return false;
4126  } else {
4127  return true;
4128  }
4129  } else {
4130  # Fail safe (not a redirect after all. strange.)
4131  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
4132  " is a redirect, but it doesn't contain a valid redirect.\n" );
4133  return false;
4134  }
4135  }
4136 
4144  public function getParentCategories() {
4146 
4147  $data = [];
4148 
4149  $titleKey = $this->getArticleID();
4150 
4151  if ( $titleKey === 0 ) {
4152  return $data;
4153  }
4154 
4155  $dbr = wfGetDB( DB_REPLICA );
4156 
4157  $res = $dbr->select(
4158  'categorylinks',
4159  'cl_to',
4160  [ 'cl_from' => $titleKey ],
4161  __METHOD__
4162  );
4163 
4164  if ( $res->numRows() > 0 ) {
4165  foreach ( $res as $row ) {
4166  // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
4167  $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
4168  }
4169  }
4170  return $data;
4171  }
4172 
4179  public function getParentCategoryTree( $children = [] ) {
4180  $stack = [];
4181  $parents = $this->getParentCategories();
4182 
4183  if ( $parents ) {
4184  foreach ( $parents as $parent => $current ) {
4185  if ( array_key_exists( $parent, $children ) ) {
4186  # Circular reference
4187  $stack[$parent] = [];
4188  } else {
4189  $nt = self::newFromText( $parent );
4190  if ( $nt ) {
4191  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
4192  }
4193  }
4194  }
4195  }
4196 
4197  return $stack;
4198  }
4199 
4206  public function pageCond() {
4207  if ( $this->mArticleID > 0 ) {
4208  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
4209  return [ 'page_id' => $this->mArticleID ];
4210  } else {
4211  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
4212  }
4213  }
4214 
4222  private function getRelativeRevisionID( $revId, $flags, $dir ) {
4223  $revId = (int)$revId;
4224  if ( $dir === 'next' ) {
4225  $op = '>';
4226  $sort = 'ASC';
4227  } elseif ( $dir === 'prev' ) {
4228  $op = '<';
4229  $sort = 'DESC';
4230  } else {
4231  throw new InvalidArgumentException( '$dir must be "next" or "prev"' );
4232  }
4233 
4234  if ( $flags & self::GAID_FOR_UPDATE ) {
4235  $db = wfGetDB( DB_MASTER );
4236  } else {
4237  $db = wfGetDB( DB_REPLICA, 'contributions' );
4238  }
4239 
4240  // Intentionally not caring if the specified revision belongs to this
4241  // page. We only care about the timestamp.
4242  $ts = $db->selectField( 'revision', 'rev_timestamp', [ 'rev_id' => $revId ], __METHOD__ );
4243  if ( $ts === false ) {
4244  $ts = $db->selectField( 'archive', 'ar_timestamp', [ 'ar_rev_id' => $revId ], __METHOD__ );
4245  if ( $ts === false ) {
4246  // Or should this throw an InvalidArgumentException or something?
4247  return false;
4248  }
4249  }
4250  $ts = $db->addQuotes( $ts );
4251 
4252  $revId = $db->selectField( 'revision', 'rev_id',
4253  [
4254  'rev_page' => $this->getArticleID( $flags ),
4255  "rev_timestamp $op $ts OR (rev_timestamp = $ts AND rev_id $op $revId)"
4256  ],
4257  __METHOD__,
4258  [
4259  'ORDER BY' => "rev_timestamp $sort, rev_id $sort",
4260  'IGNORE INDEX' => 'rev_timestamp', // Probably needed for T159319
4261  ]
4262  );
4263 
4264  if ( $revId === false ) {
4265  return false;
4266  } else {
4267  return intval( $revId );
4268  }
4269  }
4270 
4278  public function getPreviousRevisionID( $revId, $flags = 0 ) {
4279  return $this->getRelativeRevisionID( $revId, $flags, 'prev' );
4280  }
4281 
4289  public function getNextRevisionID( $revId, $flags = 0 ) {
4290  return $this->getRelativeRevisionID( $revId, $flags, 'next' );
4291  }
4292 
4299  public function getFirstRevision( $flags = 0 ) {
4300  $pageId = $this->getArticleID( $flags );
4301  if ( $pageId ) {
4302  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
4304  $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'],
4305  [ 'rev_page' => $pageId ],
4306  __METHOD__,
4307  [
4308  'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
4309  'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
4310  ],
4311  $revQuery['joins']
4312  );
4313  if ( $row ) {
4314  return new Revision( $row );
4315  }
4316  }
4317  return null;
4318  }
4319 
4326  public function getEarliestRevTime( $flags = 0 ) {
4327  $rev = $this->getFirstRevision( $flags );
4328  return $rev ? $rev->getTimestamp() : null;
4329  }
4330 
4336  public function isNewPage() {
4337  $dbr = wfGetDB( DB_REPLICA );
4338  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4339  }
4340 
4346  public function isBigDeletion() {
4348 
4349  if ( !$wgDeleteRevisionsLimit ) {
4350  return false;
4351  }
4352 
4353  if ( $this->mIsBigDeletion === null ) {
4354  $dbr = wfGetDB( DB_REPLICA );
4355 
4356  $revCount = $dbr->selectRowCount(
4357  'revision',
4358  '1',
4359  [ 'rev_page' => $this->getArticleID() ],
4360  __METHOD__,
4361  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4362  );
4363 
4364  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4365  }
4366 
4367  return $this->mIsBigDeletion;
4368  }
4369 
4375  public function estimateRevisionCount() {
4376  if ( !$this->exists() ) {
4377  return 0;
4378  }
4379 
4380  if ( $this->mEstimateRevisions === null ) {
4381  $dbr = wfGetDB( DB_REPLICA );
4382  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4383  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4384  }
4385 
4387  }
4388 
4398  public function countRevisionsBetween( $old, $new, $max = null ) {
4399  if ( !( $old instanceof Revision ) ) {
4400  $old = Revision::newFromTitle( $this, (int)$old );
4401  }
4402  if ( !( $new instanceof Revision ) ) {
4403  $new = Revision::newFromTitle( $this, (int)$new );
4404  }
4405  if ( !$old || !$new ) {
4406  return 0; // nothing to compare
4407  }
4408  $dbr = wfGetDB( DB_REPLICA );
4409  $conds = [
4410  'rev_page' => $this->getArticleID(),
4411  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4412  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4413  ];
4414  if ( $max !== null ) {
4415  return $dbr->selectRowCount( 'revision', '1',
4416  $conds,
4417  __METHOD__,
4418  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4419  );
4420  } else {
4421  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4422  }
4423  }
4424 
4441  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4442  if ( !( $old instanceof Revision ) ) {
4443  $old = Revision::newFromTitle( $this, (int)$old );
4444  }
4445  if ( !( $new instanceof Revision ) ) {
4446  $new = Revision::newFromTitle( $this, (int)$new );
4447  }
4448  // XXX: what if Revision objects are passed in, but they don't refer to this title?
4449  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4450  // in the sanity check below?
4451  if ( !$old || !$new ) {
4452  return null; // nothing to compare
4453  }
4454  $authors = [];
4455  $old_cmp = '>';
4456  $new_cmp = '<';
4457  $options = (array)$options;
4458  if ( in_array( 'include_old', $options ) ) {
4459  $old_cmp = '>=';
4460  }
4461  if ( in_array( 'include_new', $options ) ) {
4462  $new_cmp = '<=';
4463  }
4464  if ( in_array( 'include_both', $options ) ) {
4465  $old_cmp = '>=';
4466  $new_cmp = '<=';
4467  }
4468  // No DB query needed if $old and $new are the same or successive revisions:
4469  if ( $old->getId() === $new->getId() ) {
4470  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4471  [] :
4472  [ $old->getUserText( Revision::RAW ) ];
4473  } elseif ( $old->getId() === $new->getParentId() ) {
4474  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4475  $authors[] = $old->getUserText( Revision::RAW );
4476  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4477  $authors[] = $new->getUserText( Revision::RAW );
4478  }
4479  } elseif ( $old_cmp === '>=' ) {
4480  $authors[] = $old->getUserText( Revision::RAW );
4481  } elseif ( $new_cmp === '<=' ) {
4482  $authors[] = $new->getUserText( Revision::RAW );
4483  }
4484  return $authors;
4485  }
4486  $dbr = wfGetDB( DB_REPLICA );
4488  $authors = $dbr->selectFieldValues(
4489  $revQuery['tables'],
4490  $revQuery['fields']['rev_user_text'],
4491  [
4492  'rev_page' => $this->getArticleID(),
4493  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4494  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4495  ], __METHOD__,
4496  [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated
4497  $revQuery['joins']
4498  );
4499  return $authors;
4500  }
4501 
4516  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4517  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4518  return $authors ? count( $authors ) : 0;
4519  }
4520 
4527  public function equals( Title $title ) {
4528  // Note: === is necessary for proper matching of number-like titles.
4529  return $this->getInterwiki() === $title->getInterwiki()
4530  && $this->getNamespace() == $title->getNamespace()
4531  && $this->getDBkey() === $title->getDBkey();
4532  }
4533 
4540  public function isSubpageOf( Title $title ) {
4541  return $this->getInterwiki() === $title->getInterwiki()
4542  && $this->getNamespace() == $title->getNamespace()
4543  && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4544  }
4545 
4557  public function exists( $flags = 0 ) {
4558  $exists = $this->getArticleID( $flags ) != 0;
4559  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4560  return $exists;
4561  }
4562 
4579  public function isAlwaysKnown() {
4580  $isKnown = null;
4581 
4592  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4593 
4594  if ( !is_null( $isKnown ) ) {
4595  return $isKnown;
4596  }
4597 
4598  if ( $this->isExternal() ) {
4599  return true; // any interwiki link might be viewable, for all we know
4600  }
4601 
4602  switch ( $this->mNamespace ) {
4603  case NS_MEDIA:
4604  case NS_FILE:
4605  // file exists, possibly in a foreign repo
4606  return (bool)wfFindFile( $this );
4607  case NS_SPECIAL:
4608  // valid special page
4609  return SpecialPageFactory::exists( $this->getDBkey() );
4610  case NS_MAIN:
4611  // selflink, possibly with fragment
4612  return $this->mDbkeyform == '';
4613  case NS_MEDIAWIKI:
4614  // known system message
4615  return $this->hasSourceText() !== false;
4616  default:
4617  return false;
4618  }
4619  }
4620 
4632  public function isKnown() {
4633  return $this->isAlwaysKnown() || $this->exists();
4634  }
4635 
4641  public function hasSourceText() {
4642  if ( $this->exists() ) {
4643  return true;
4644  }
4645 
4646  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4647  // If the page doesn't exist but is a known system message, default
4648  // message content will be displayed, same for language subpages-
4649  // Use always content language to avoid loading hundreds of languages
4650  // to get the link color.
4652  list( $name, ) = MessageCache::singleton()->figureMessage(
4653  $wgContLang->lcfirst( $this->getText() )
4654  );
4655  $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4656  return $message->exists();
4657  }
4658 
4659  return false;
4660  }
4661 
4667  public function getDefaultMessageText() {
4669 
4670  if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4671  return false;
4672  }
4673 
4674  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4675  $wgContLang->lcfirst( $this->getText() )
4676  );
4677  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4678 
4679  if ( $message->exists() ) {
4680  return $message->plain();
4681  } else {
4682  return false;
4683  }
4684  }
4685 
4692  public function invalidateCache( $purgeTime = null ) {
4693  if ( wfReadOnly() ) {
4694  return false;
4695  } elseif ( $this->mArticleID === 0 ) {
4696  return true; // avoid gap locking if we know it's not there
4697  }
4698 
4699  $dbw = wfGetDB( DB_MASTER );
4700  $dbw->onTransactionPreCommitOrIdle( function () {
4702  } );
4703 
4704  $conds = $this->pageCond();
4706  new AutoCommitUpdate(
4707  $dbw,
4708  __METHOD__,
4709  function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4710  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4711  $dbw->update(
4712  'page',
4713  [ 'page_touched' => $dbTimestamp ],
4714  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4715  $fname
4716  );
4717  MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4718  }
4719  ),
4721  );
4722 
4723  return true;
4724  }
4725 
4731  public function touchLinks() {
4732  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
4733  if ( $this->getNamespace() == NS_CATEGORY ) {
4735  new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
4736  );
4737  }
4738  }
4739 
4746  public function getTouched( $db = null ) {
4747  if ( $db === null ) {
4748  $db = wfGetDB( DB_REPLICA );
4749  }
4750  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4751  return $touched;
4752  }
4753 
4760  public function getNotificationTimestamp( $user = null ) {
4761  global $wgUser;
4762 
4763  // Assume current user if none given
4764  if ( !$user ) {
4765  $user = $wgUser;
4766  }
4767  // Check cache first
4768  $uid = $user->getId();
4769  if ( !$uid ) {
4770  return false;
4771  }
4772  // avoid isset here, as it'll return false for null entries
4773  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4774  return $this->mNotificationTimestamp[$uid];
4775  }
4776  // Don't cache too much!
4777  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4778  $this->mNotificationTimestamp = [];
4779  }
4780 
4781  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4782  $watchedItem = $store->getWatchedItem( $user, $this );
4783  if ( $watchedItem ) {
4784  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4785  } else {
4786  $this->mNotificationTimestamp[$uid] = false;
4787  }
4788 
4789  return $this->mNotificationTimestamp[$uid];
4790  }
4791 
4798  public function getNamespaceKey( $prepend = 'nstab-' ) {
4800  // Gets the subject namespace of this title
4801  $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
4802  // Prefer canonical namespace name for HTML IDs
4803  $namespaceKey = MWNamespace::getCanonicalName( $subjectNS );
4804  if ( $namespaceKey === false ) {
4805  // Fallback to localised text
4806  $namespaceKey = $this->getSubjectNsText();
4807  }
4808  // Makes namespace key lowercase
4809  $namespaceKey = $wgContLang->lc( $namespaceKey );
4810  // Uses main
4811  if ( $namespaceKey == '' ) {
4812  $namespaceKey = 'main';
4813  }
4814  // Changes file to image for backwards compatibility
4815  if ( $namespaceKey == 'file' ) {
4816  $namespaceKey = 'image';
4817  }
4818  return $prepend . $namespaceKey;
4819  }
4820 
4827  public function getRedirectsHere( $ns = null ) {
4828  $redirs = [];
4829 
4830  $dbr = wfGetDB( DB_REPLICA );
4831  $where = [
4832  'rd_namespace' => $this->getNamespace(),
4833  'rd_title' => $this->getDBkey(),
4834  'rd_from = page_id'
4835  ];
4836  if ( $this->isExternal() ) {
4837  $where['rd_interwiki'] = $this->getInterwiki();
4838  } else {
4839  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4840  }
4841  if ( !is_null( $ns ) ) {
4842  $where['page_namespace'] = $ns;
4843  }
4844 
4845  $res = $dbr->select(
4846  [ 'redirect', 'page' ],
4847  [ 'page_namespace', 'page_title' ],
4848  $where,
4849  __METHOD__
4850  );
4851 
4852  foreach ( $res as $row ) {
4853  $redirs[] = self::newFromRow( $row );
4854  }
4855  return $redirs;
4856  }
4857 
4863  public function isValidRedirectTarget() {
4865 
4866  if ( $this->isSpecialPage() ) {
4867  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4868  if ( $this->isSpecial( 'Userlogout' ) ) {
4869  return false;
4870  }
4871 
4872  foreach ( $wgInvalidRedirectTargets as $target ) {
4873  if ( $this->isSpecial( $target ) ) {
4874  return false;
4875  }
4876  }
4877  }
4878 
4879  return true;
4880  }
4881 
4887  public function getBacklinkCache() {
4888  return BacklinkCache::get( $this );
4889  }
4890 
4896  public function canUseNoindex() {
4898 
4899  $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4902 
4903  return !in_array( $this->mNamespace, $bannedNamespaces );
4904  }
4905 
4916  public function getCategorySortkey( $prefix = '' ) {
4917  $unprefixed = $this->getText();
4918 
4919  // Anything that uses this hook should only depend
4920  // on the Title object passed in, and should probably
4921  // tell the users to run updateCollations.php --force
4922  // in order to re-sort existing category relations.
4923  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4924  if ( $prefix !== '' ) {
4925  # Separate with a line feed, so the unprefixed part is only used as
4926  # a tiebreaker when two pages have the exact same prefix.
4927  # In UCA, tab is the only character that can sort above LF
4928  # so we strip both of them from the original prefix.
4929  $prefix = strtr( $prefix, "\n\t", ' ' );
4930  return "$prefix\n$unprefixed";
4931  }
4932  return $unprefixed;
4933  }
4934 
4942  private function getDbPageLanguageCode() {
4944 
4945  // check, if the page language could be saved in the database, and if so and
4946  // the value is not requested already, lookup the page language using LinkCache
4947  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4948  $linkCache = LinkCache::singleton();
4949  $linkCache->addLinkObj( $this );
4950  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4951  }
4952 
4953  return $this->mDbPageLanguage;
4954  }
4955 
4964  public function getPageLanguage() {
4966  if ( $this->isSpecialPage() ) {
4967  // special pages are in the user language
4968  return $wgLang;
4969  }
4970 
4971  // Checking if DB language is set
4972  $dbPageLanguage = $this->getDbPageLanguageCode();
4973  if ( $dbPageLanguage ) {
4974  return wfGetLangObj( $dbPageLanguage );
4975  }
4976 
4977  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4978  // Note that this may depend on user settings, so the cache should
4979  // be only per-request.
4980  // NOTE: ContentHandler::getPageLanguage() may need to load the
4981  // content to determine the page language!
4982  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4983  // tests.
4984  $contentHandler = ContentHandler::getForTitle( $this );
4985  $langObj = $contentHandler->getPageLanguage( $this );
4986  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4987  } else {
4988  $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4989  }
4990 
4991  return $langObj;
4992  }
4993 
5002  public function getPageViewLanguage() {
5003  global $wgLang;
5004 
5005  if ( $this->isSpecialPage() ) {
5006  // If the user chooses a variant, the content is actually
5007  // in a language whose code is the variant code.
5008  $variant = $wgLang->getPreferredVariant();
5009  if ( $wgLang->getCode() !== $variant ) {
5010  return Language::factory( $variant );
5011  }
5012 
5013  return $wgLang;
5014  }
5015 
5016  // Checking if DB language is set
5017  $dbPageLanguage = $this->getDbPageLanguageCode();
5018  if ( $dbPageLanguage ) {
5019  $pageLang = wfGetLangObj( $dbPageLanguage );
5020  $variant = $pageLang->getPreferredVariant();
5021  if ( $pageLang->getCode() !== $variant ) {
5022  $pageLang = Language::factory( $variant );
5023  }
5024 
5025  return $pageLang;
5026  }
5027 
5028  // @note Can't be cached persistently, depends on user settings.
5029  // @note ContentHandler::getPageViewLanguage() may need to load the
5030  // content to determine the page language!
5031  $contentHandler = ContentHandler::getForTitle( $this );
5032  $pageLang = $contentHandler->getPageViewLanguage( $this );
5033  return $pageLang;
5034  }
5035 
5046  public function getEditNotices( $oldid = 0 ) {
5047  $notices = [];
5048 
5049  // Optional notice for the entire namespace
5050  $editnotice_ns = 'editnotice-' . $this->getNamespace();
5051  $msg = wfMessage( $editnotice_ns );
5052  if ( $msg->exists() ) {
5053  $html = $msg->parseAsBlock();
5054  // Edit notices may have complex logic, but output nothing (T91715)
5055  if ( trim( $html ) !== '' ) {
5056  $notices[$editnotice_ns] = Html::rawElement(
5057  'div',
5058  [ 'class' => [
5059  'mw-editnotice',
5060  'mw-editnotice-namespace',
5061  Sanitizer::escapeClass( "mw-$editnotice_ns" )
5062  ] ],
5063  $html
5064  );
5065  }
5066  }
5067 
5068  if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
5069  // Optional notice for page itself and any parent page
5070  $parts = explode( '/', $this->getDBkey() );
5071  $editnotice_base = $editnotice_ns;
5072  while ( count( $parts ) > 0 ) {
5073  $editnotice_base .= '-' . array_shift( $parts );
5074  $msg = wfMessage( $editnotice_base );
5075  if ( $msg->exists() ) {
5076  $html = $msg->parseAsBlock();
5077  if ( trim( $html ) !== '' ) {
5078  $notices[$editnotice_base] = Html::rawElement(
5079  'div',
5080  [ 'class' => [
5081  'mw-editnotice',
5082  'mw-editnotice-base',
5083  Sanitizer::escapeClass( "mw-$editnotice_base" )
5084  ] ],
5085  $html
5086  );
5087  }
5088  }
5089  }
5090  } else {
5091  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
5092  $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
5093  $msg = wfMessage( $editnoticeText );
5094  if ( $msg->exists() ) {
5095  $html = $msg->parseAsBlock();
5096  if ( trim( $html ) !== '' ) {
5097  $notices[$editnoticeText] = Html::rawElement(
5098  'div',
5099  [ 'class' => [
5100  'mw-editnotice',
5101  'mw-editnotice-page',
5102  Sanitizer::escapeClass( "mw-$editnoticeText" )
5103  ] ],
5104  $html
5105  );
5106  }
5107  }
5108  }
5109 
5110  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
5111  return $notices;
5112  }
5113 
5117  public function __sleep() {
5118  return [
5119  'mNamespace',
5120  'mDbkeyform',
5121  'mFragment',
5122  'mInterwiki',
5123  'mLocalInterwiki',
5124  'mUserCaseDBKey',
5125  'mDefaultNamespace',
5126  ];
5127  }
5128 
5129  public function __wakeup() {
5130  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
5131  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
5132  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
5133  }
5134 
5135 }
Title\$mLocalInterwiki
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition: Title.php:82
Title\$mRestrictions
array $mRestrictions
Array of groups allowed to edit this article.
Definition: Title.php:109
Title\canHaveTalkPage
canHaveTalkPage()
Can this title have a corresponding talk page?
Definition: Title.php:1086
Title\getTitleProtection
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition: Title.php:2795
Title\canUseNoindex
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition: Title.php:4896
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:247
Title\inNamespaces
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition: Title.php:1173
Title\getLocalURL
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1911
CONTENT_MODEL_JSON
const CONTENT_MODEL_JSON
Definition: Defines.php:249
Title\isSemiProtected
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2883
Title\$mHasCascadingRestrictions
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition: Title.php:129
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:48
Title\isNamespaceProtected
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition: Title.php:2943
Title\getTalkNsText
getTalkNsText()
Get the namespace text of the talk page.
Definition: Title.php:1062
$wgUser
$wgUser
Definition: Setup.php:902
Title\getSubpageText
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition: Title.php:1744
MediaWiki\Linker\LinkTarget\getInterwiki
getInterwiki()
The interwiki component of this LinkTarget.
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:273
MWNamespace\subjectEquals
static subjectEquals( $ns1, $ns2)
Returns whether the specified namespaces share the same subject.
Definition: MWNamespace.php:216
Title\getNextRevisionID
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition: Title.php:4289
Title\isBigDeletion
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition: Title.php:4346
PROTO_CANONICAL
const PROTO_CANONICAL
Definition: Defines.php:233
Title\areCascadeProtectionSourcesLoaded
areCascadeProtectionSourcesLoaded( $getPages=true)
Determines whether cascading protection sources have already been loaded from the database.
Definition: Title.php:2975
MediaWiki\Linker\LinkTarget\getText
getText()
Returns the link in text form, without namespace prefix or fragment.
Title\userCan
userCan( $action, $user=null, $rigor='secure')
Can $user perform $action on this page?
Definition: Title.php:2108
Title\getFilteredRestrictionTypes
static getFilteredRestrictionTypes( $exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition: Title.php:2750
wfMergeErrorArrays
wfMergeErrorArrays()
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
Definition: GlobalFunctions.php:211
Title\makeName
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition: Title.php:741
Title\getInternalURL
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition: Title.php:2034
$wgActionPaths
$wgActionPaths
Definition: img_auth.php:46
Title\checkPageRestrictions
checkPageRestrictions( $action, $user, $errors, $rigor, $short)
Check against page_restrictions table requirements on this page.
Definition: Title.php:2388
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
Title\getFragment
getFragment()
Get the Title fragment (i.e.
Definition: Title.php:1517
MWNamespace\isTalk
static isTalk( $index)
Is the given namespace a talk namespace?
Definition: MWNamespace.php:118
$wgExemptFromUserRobotsControl
$wgExemptFromUserRobotsControl
An array of namespace keys in which the INDEX/__NOINDEX__ magic words will not function,...
Definition: DefaultSettings.php:8018
$rows
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:2783
HashBagOStuff
Simple store for keeping values in an associative array for the current process.
Definition: HashBagOStuff.php:31
$html
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:2013
Title\getTalkPageIfDefined
getTalkPageIfDefined()
Get a Title object associated with the talk page of this article, if such a talk page can exist.
Definition: Title.php:1456
names
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
Definition: APACHE-LICENSE-2.0.txt:140
Title\inNamespace
inNamespace( $ns)
Returns true if the title is inside the specified namespace.
Definition: Title.php:1162
Title\$mForcedContentModel
bool $mForcedContentModel
If a content model was forced via setContentModel() this will be true to avoid having other code path...
Definition: Title.php:103
TitleArray\newFromResult
static newFromResult( $res)
Definition: TitleArray.php:40
Title\isMovable
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable.
Definition: Title.php:1222
wfMessage
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 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
$wgMaximumMovedPages
$wgMaximumMovedPages
Maximum number of pages to move at once when moving subpages with a page.
Definition: DefaultSettings.php:8493
array
the array() calling protocol came about after MediaWiki 1.4rc1.
Title\isJsSubpage
isJsSubpage()
Definition: Title.php:1424
Title\wasLocalInterwiki
wasLocalInterwiki()
Was this a local interwiki link?
Definition: Title.php:866
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
Title\isWikitextPage
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition: Title.php:1275
Title\isUserCssConfigPage
isUserCssConfigPage()
Is this a CSS "config" sub-page of a user page?
Definition: Title.php:1375
Title\getBacklinkCache
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4887
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5696
Title\getPartialURL
getPartialURL()
Get the URL-encoded form of the main part.
Definition: Title.php:938
$wgLegalTitleChars
$wgLegalTitleChars
Allowed title characters – regex character class Don't change this unless you know what you're doing.
Definition: DefaultSettings.php:3942
Title\$mIsBigDeletion
bool $mIsBigDeletion
Would deleting this page be a big deletion?
Definition: Title.php:173
Title\clearCaches
static clearCaches()
Text form (spaces not underscores) of the main part.
Definition: Title.php:3579
AutoCommitUpdate
Deferrable Update for closure/callback updates that should use auto-commit mode.
Definition: AutoCommitUpdate.php:9
Title\moveSubpages
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move this page's subpages to be subpages of $nt.
Definition: Title.php:3972
Title\getTitleValue
getTitleValue()
Get a TitleValue object representing this Title.
Definition: Title.php:906
Title\estimateRevisionCount
estimateRevisionCount()
Get the approximate revision count of this page.
Definition: Title.php:4375
Title\countRevisionsBetween
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:4398
Title\getPrefixedDBkey
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1613
Title\getDbPageLanguageCode
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition: Title.php:4942
$wgWhitelistReadRegexp
$wgWhitelistReadRegexp
Pages anonymous user may see, set as an array of regular expressions.
Definition: DefaultSettings.php:5085
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:18
Title\checkActionPermissions
checkActionPermissions( $action, $user, $errors, $rigor, $short)
Check action permissions not already checked in checkQuickPermissions.
Definition: Title.php:2469
Title\getSkinFromConfigSubpage
getSkinFromConfigSubpage()
Trim down a .css, .json, or .js subpage title to get the corresponding skin name.
Definition: Title.php:1350
$wgScript
$wgScript
The URL path to index.php.
Definition: DefaultSettings.php:206
string
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:181
Title\$mUserCaseDBKey
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition: Title.php:73
Title\$mHasSubpages
bool $mHasSubpages
Whether a page has any subpages.
Definition: Title.php:160
Title\getTalkPage
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition: Title.php:1443
Title\newMainPage
static newMainPage()
Create a new Title for the Main Page.
Definition: Title.php:586
Title\getNotificationTimestamp
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition: Title.php:4760
Title\getParentCategoryTree
getParentCategoryTree( $children=[])
Get a tree of parent categories.
Definition: Title.php:4179
CONTENT_MODEL_CSS
const CONTENT_MODEL_CSS
Definition: Defines.php:247
Title\fixUrlQueryArgs
static fixUrlQueryArgs( $query, $query2=false)
Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args get{Canonical,...
Definition: Title.php:1804
$ret
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:2005
Title\checkSpecialsAndNSPermissions
checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short)
Check permissions on special pages & namespaces.
Definition: Title.php:2301
Title\fixSpecialName
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias,...
Definition: Title.php:1139
Title\getInterwikiLookup
static getInterwikiLookup()
B/C kludge: provide an InterwikiLookup for use by Title.
Definition: Title.php:196
Title\isUserJsConfigPage
isUserJsConfigPage()
Is this a JS "config" sub-page of a user page?
Definition: Title.php:1412
MessageSpecifier
Definition: MessageSpecifier.php:21
Title\touchLinks
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition: Title.php:4731
wfUrlencode
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
Definition: GlobalFunctions.php:340
Title\getPrefixedText
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1625
Title\getTransWikiID
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition: Title.php:889
Title\resultToError
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition: Title.php:2233
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:864
Title\isCssJsSubpage
isCssJsSubpage()
Definition: Title.php:1337
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:76
Title\getArticleID
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3436
StringUtils\escapeRegexReplacement
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
Definition: StringUtils.php:310
Title\getSquidURLs
getSquidURLs()
Definition: Title.php:3854
Title\getLinkURL
getLinkURL( $query='', $query2=false, $proto=false)
Get a URL that's the simplest URL that will be valid to link, locally, to the current Title.
Definition: Title.php:2009
Title\secureAndSplit
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition: Title.php:3616
Title\quickUserCan
quickUserCan( $action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition: Title.php:2095
NS_FILE
const NS_FILE
Definition: Defines.php:80
Title\isTalkPage
isTalkPage()
Is this a talk page of some sort?
Definition: Title.php:1434
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1262
Sanitizer\decodeCharReferencesAndNormalize
static decodeCharReferencesAndNormalize( $text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1664
$s
$s
Definition: mergeMessageFileList.php:187
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:82
Sanitizer\escapeClass
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1402
ContentHandler\getForTitle
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
Definition: ContentHandler.php:240
$res
$res
Definition: database.txt:21
Title\isExternal
isExternal()
Is this Title interwiki?
Definition: Title.php:846
Title\loadRestrictionsFromRows
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition: Title.php:3152
$success
$success
Definition: NoLocalSettings.php:42
Title\getDefaultMessageText
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition: Title.php:4667
CONTENT_MODEL_WIKITEXT
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:245
Title\hasSourceText
hasSourceText()
Does this page have source text?
Definition: Title.php:4641
Title\loadFromRow
loadFromRow( $row)
Load Title object fields from a DB row.
Definition: Title.php:476
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4991
Title\checkReadPermissions
checkReadPermissions( $action, $user, $errors, $rigor, $short)
Check that the user is allowed to read this page.
Definition: Title.php:2591
Title\resetArticleID
resetArticleID( $newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3557
$result
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=(\d*-\d*)") 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. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) '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 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) '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! 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:1993
Title\setFragment
setFragment( $fragment)
Set the fragment for this title.
Definition: Title.php:1559
MWNamespace\getContentNamespaces
static getContentNamespaces()
Get a list of all namespace indices which are considered to contain content.
Definition: MWNamespace.php:377
$wgContentHandlerUseDB
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility.
Definition: DefaultSettings.php:8581
Title\convertByteClassToUnicodeClass
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition: Title.php:637
Title\__wakeup
__wakeup()
Text form (spaces not underscores) of the main part.
Definition: Title.php:5129
$revQuery
$revQuery
Definition: testCompression.php:51
php
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:37
Title\getParentCategories
getParentCategories()
Get categories to which this Title belongs and return an array of categories' names.
Definition: Title.php:4144
Title\getLinksTo
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:3664
Title\$mContentModel
bool string $mContentModel
ID of the page's content model, i.e.
Definition: Title.php:97
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
HashBagOStuff\clear
clear()
Definition: HashBagOStuff.php:115
Title\getNsText
getNsText()
Get the namespace text.
Definition: Title.php:1028
Title\$mDbkeyform
string $mDbkeyform
Main part with underscores.
Definition: Title.php:70
Title\$mUrlform
string $mUrlform
URL-encoded form of the main part.
Definition: Title.php:67
$dbr
$dbr
Definition: testCompression.php:50
$wgWhitelistRead
$wgWhitelistRead
Pages anonymous user may see, set as an array of pages titles.
Definition: DefaultSettings.php:5057
Title\getLinksFrom
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:3722
Title\$mOldRestrictions
string bool $mOldRestrictions
Comma-separated set of permission keys indicating who can move or edit the page from the page table,...
Definition: Title.php:117
Title\getSkinFromCssJsSubpage
getSkinFromCssJsSubpage()
Definition: Title.php:1364
wfAppendQuery
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
Definition: GlobalFunctions.php:469
Revision
Definition: Revision.php:41
NS_MAIN
const NS_MAIN
Definition: Defines.php:74
HashBagOStuff\set
set( $key, $value, $exptime=0, $flags=0)
Set an item.
Definition: HashBagOStuff.php:92
Title\isCascadeProtected
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not.
Definition: Title.php:2961
Revision\newFromTitle
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition: Revision.php:133
Title\isValidMoveTarget
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition: Title.php:4096
Title\getUserPermissionsErrors
getUserPermissionsErrors( $action, $user, $rigor='secure', $ignoreErrors=[])
Can $user perform $action on this page?
Definition: Title.php:2132
Title\getCdnUrls
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3824
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:63
Title\getOtherPage
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:1487
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
Title\isSiteConfigPage
isSiteConfigPage()
Could this MediaWiki namespace page contain custom CSS, JSON, or JavaScript for the global UI.
Definition: Title.php:1293
article
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an article
Definition: design.txt:25
Title\isUserJsonConfigPage
isUserJsonConfigPage()
Is this a JSON "config" sub-page of a user page?
Definition: Title.php:1398
Wikimedia\Rdbms\IDatabase\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
ContentHandler\getDefaultModelFor
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
Definition: ContentHandler.php:178
Title\getDBkey
getDBkey()
Get the main part with underscores.
Definition: Title.php:947
Revision\getQueryInfo
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object.
Definition: Revision.php:492
Title\nameOf
static nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition: Title.php:601
Title\$mDbPageLanguage
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:167
Title\isSpecial
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1123
MWException
MediaWiki exception.
Definition: MWException.php:26
Title\getTitleFormatter
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition: Title.php:184
Title\checkCascadingSourcesRestrictions
checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition: Title.php:2422
Title\isMainPage
isMainPage()
Is this the mainpage?
Definition: Title.php:1243
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1123
$titles
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
Title\moveTo
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move a title to a new location.
Definition: Title.php:3934
Title\getSubpage
getSubpage( $text)
Get the title for a subpage of the current page.
Definition: Title.php:1765
Title\getBaseTitle
getBaseTitle()
Get the base page name title, i.e.
Definition: Title.php:1729
Title\getNamespace
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:970
BacklinkCache\get
static get(Title $title)
Create a new BacklinkCache or reuse any existing one.
Definition: BacklinkCache.php:113
Title\newFromRow
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:464
Title\isConversionTable
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition: Title.php:1263
MovePage
Handles the backend logic of moving a page from one title to another.
Definition: MovePage.php:30
Title\$titleCache
static HashBagOStuff $titleCache
Definition: Title.php:41
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget)
Create a new Title from a LinkTarget.
Definition: Title.php:244
Title\isProtected
isProtected( $action='')
Does the title correspond to a protected article?
Definition: Title.php:2911
Title\flushRestrictions
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition: Title.php:3279
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2812
Title\getCategorySortkey
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition: Title.php:4916
Title\getInterwiki
getInterwiki()
Get the interwiki prefix.
Definition: Title.php:857
$matches
$matches
Definition: NoLocalSettings.php:24
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:2603
Title\isValidRedirectTarget
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:4863
Title\deleteTitleProtection
deleteTitleProtection()
Remove any title protection due to page existing.
Definition: Title.php:2865
Title\__construct
__construct()
Definition: Title.php:203
MediaWiki\Interwiki\InterwikiLookup
Service interface for looking up Interwiki records.
Definition: InterwikiLookup.php:31
MWNamespace\isContent
static isContent( $index)
Does this namespace contain content, for the purposes of calculating statistics, etc?
Definition: MWNamespace.php:335
Title\getBrokenLinksFrom
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page.
Definition: Title.php:3788
Title\isUserConfigPage
isUserConfigPage()
Is this a "config" (.css, .json, or .js) sub-page of a user page?
Definition: Title.php:1321
PROTO_CURRENT
const PROTO_CURRENT
Definition: Defines.php:232
Title\missingPermissionError
missingPermissionError( $action, $short)
Get a description array when the user doesn't have the right to perform $action (i....
Definition: Title.php:2666
Title\$mCascadeSources
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition: Title.php:132
Title\getSubjectPage
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition: Title.php:1470
wfGetLangObj
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
Definition: GlobalFunctions.php:1306
$wgInvalidRedirectTargets
$wgInvalidRedirectTargets
Array of invalid page redirect targets.
Definition: DefaultSettings.php:4139
Title\getFullText
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition: Title.php:1649
MWNamespace\hasTalkNamespace
static hasTalkNamespace( $index)
Does this namespace ever have a talk namespace?
Definition: MWNamespace.php:324
MWNamespace\hasSubpages
static hasSubpages( $index)
Does the namespace allow subpages?
Definition: MWNamespace.php:368
Title\getTitleCache
static getTitleCache()
Definition: Title.php:376
Title\hasFragment
hasFragment()
Check if a Title fragment is set.
Definition: Title.php:1527
function
when a variable name is used in a function
Definition: design.txt:93
Title\isCssSubpage
isCssSubpage()
Definition: Title.php:1387
$wgLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition: design.txt:56
Title\getRedirectsHere
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4827
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
MWNamespace\isMovable
static isMovable( $index)
Can pages in the given namespace be moved?
Definition: MWNamespace.php:88
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:534
SpecialPageFactory\exists
static exists( $name)
Check if a given name exist as a special page or as a special page alias.
Definition: SpecialPageFactory.php:356
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
Title\$mInterwiki
string $mInterwiki
Interwiki prefix.
Definition: Title.php:79
Title\$mCascadeRestriction
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition: Title.php:120
Title\equals
equals(Title $title)
Compare with another title.
Definition: Title.php:4527
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2009
Title\getRootText
getRootText()
Get the root page name text without a namespace, i.e.
Definition: Title.php:1669
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:88
$wgNamespaceProtection
$wgNamespaceProtection
Set the minimum permissions required to edit pages in each namespace.
Definition: DefaultSettings.php:5353
Title\canExist
canExist()
Is this in a namespace that allows actual pages?
Definition: Title.php:1095
$wgDeleteRevisionsLimit
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
Definition: DefaultSettings.php:5530
DB_MASTER
const DB_MASTER
Definition: defines.php:26
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:994
Title\__sleep
__sleep()
Definition: Title.php:5117
Title\getCanonicalURL
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition: Title.php:2058
list
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
MessageCache\singleton
static singleton()
Get the signleton instance of this class.
Definition: MessageCache.php:113
$sort
$sort
Definition: profileinfo.php:317
Title\$mRedirect
null $mRedirect
Is the article at this title a redirect?
Definition: Title.php:154
Title\createFragmentTarget
createFragmentTarget( $fragment)
Creates a new Title for a different fragment of the same page.
Definition: Title.php:1570
Title\$mNamespace
int $mNamespace
Namespace index, i.e.
Definition: Title.php:76
Title\getDefaultNamespace
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition: Title.php:1506
$urls
$urls
Definition: opensearch_desc.php:76
$fname
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition: Setup.php:112
Title\newFromTextThrow
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,...
Definition: Title.php:306
$options
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:2001
$wgPageLanguageUseDB
bool $wgPageLanguageUseDB
Enable page language feature Allows setting page language in database.
Definition: DefaultSettings.php:8664
Title\newFromTitleValue
static newFromTitleValue(TitleValue $titleValue)
Create a new Title from a TitleValue.
Definition: Title.php:233
Title\isValid
isValid()
Returns true if the title is valid, false if it is invalid.
Definition: Title.php:808
Title\$mPageLanguage
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition: Title.php:163
AtomicSectionUpdate
Deferrable Update for closure/callback updates via IDatabase::doAtomicSection()
Definition: AtomicSectionUpdate.php:9
HTMLCacheUpdate
Class to invalidate the HTML cache of all the pages linking to a given title.
Definition: HTMLCacheUpdate.php:29
Title\getPrefixedURL
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition: Title.php:1785
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:562
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:2763
Title\__toString
__toString()
Return a string representation of this title.
Definition: Title.php:1639
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:863
Title\getTemplateLinksTo
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:3706
Title\areRestrictionsCascading
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition: Title.php:3133
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:62
CdnCacheUpdate
Handles purging appropriate CDN URLs given a title (or titles)
Definition: CdnCacheUpdate.php:30
Title\getLatestRevID
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition: Title.php:3525
Title\areRestrictionsLoaded
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:3078
Title\getSubpages
getSubpages( $limit=-1)
Get all subpages of this page.
Definition: Title.php:3358
Title\isSingleRevRedirect
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition: Title.php:4045
$wgServer
$wgServer
URL of the server.
Definition: DefaultSettings.php:106
Title\GAID_FOR_UPDATE
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:54
Title\getPageViewLanguage
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition: Title.php:5002
Title\newFromDBkey
static newFromDBkey( $key)
Create a new Title from a prefixed DB key.
Definition: Title.php:214
Sanitizer\escapeIdForExternalInterwiki
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:1311
$wgLanguageCode
$wgLanguageCode
Site language code.
Definition: DefaultSettings.php:2887
Title\isSubpage
isSubpage()
Is this a subpage?
Definition: Title.php:1252
Title\hasContentModel
hasContentModel( $id)
Convenience method for checking a title's content model name.
Definition: Title.php:1003
Title\getRestrictionExpiry
getRestrictionExpiry( $action)
Get the expiry time for the restriction against a given action.
Definition: Title.php:3121
Title\newFromURL
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition: Title.php:353
Title\isValidMoveOperation
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition: Title.php:3878
MediaWiki\Linker\LinkTarget\getFragment
getFragment()
Get the link fragment (i.e.
PROTO_HTTP
const PROTO_HTTP
Definition: Defines.php:229
$wgBlockDisablesLogin
$wgBlockDisablesLogin
If true, blocked users will not be allowed to login.
Definition: DefaultSettings.php:5035
Title\$mRestrictionsExpiry
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition: Title.php:126
SpecialPageFactory\resolveAlias
static resolveAlias( $alias)
Given a special page name with a possible subpage, return an array where the first element is the spe...
Definition: SpecialPageFactory.php:328
PROTO_RELATIVE
const PROTO_RELATIVE
Definition: Defines.php:231
BagOStuff\get
get( $key, $flags=0, $oldFlags=null)
Get an item with the given key.
Definition: BagOStuff.php:182
SpecialPageFactory\getLocalNameFor
static getLocalNameFor( $name, $subpage=false)
Get the local name for a specified canonical name.
Definition: SpecialPageFactory.php:647
Title\$mTitleValue
TitleValue $mTitleValue
A corresponding TitleValue object.
Definition: Title.php:170
Title\isSubpageOf
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:4540
Revision\RAW
const RAW
Definition: Revision.php:57
Title\isLocal
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition: Title.php:831
Title\$mFragment
string $mFragment
Title fragment (i.e.
Definition: Title.php:85
Title\getContentModel
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:980
Title\$mNotificationTimestamp
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition: Title.php:157
MWNamespace\exists
static exists( $index)
Returns whether the specified namespace exists.
Definition: MWNamespace.php:182
$status
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. '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 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object '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:1255
Title\newFromIDs
static newFromIDs( $ids)
Make an array of titles from an array of IDs.
Definition: Title.php:438
Wikimedia\Rdbms\IDatabase\update
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
Title\checkQuickPermissions
checkQuickPermissions( $action, $user, $errors, $rigor, $short)
Permissions checks that fail most often, and which are easiest to test.
Definition: Title.php:2163
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:434
WikiPage\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new page object.
Definition: WikiPage.php:325
Title\prefix
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object.
Definition: Title.php:1586
Title\getNamespaceKey
getNamespaceKey( $prepend='nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition: Title.php:4798
Title\getEditURL
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:2072
Title\getFullURL
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition: Title.php:1842
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:2853
$wgEmailConfirmToEdit
$wgEmailConfirmToEdit
Should editors be required to have a validated e-mail address before being allowed to edit?
Definition: DefaultSettings.php:5091
Title\isKnown
isKnown()
Does this title refer to a page that can (or might) be meaningfully viewed? In particular,...
Definition: Title.php:4632
$wgArticlePath
$wgArticlePath
Definition: img_auth.php:45
Title\validateFileMoveOperation
validateFileMoveOperation( $nt)
Check if the requested move target is a valid file move target.
Definition: Title.php:3905
$rev
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:1777
Title\getFirstRevision
getFirstRevision( $flags=0)
Get the first revision of the page.
Definition: Title.php:4299
Title\getAllRestrictions
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition: Title.php:3107
Title\getEarliestRevTime
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:4326
User\isEveryoneAllowed
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: User.php:5011
Title
Represents a title within MediaWiki.
Definition: Title.php:39
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
$namespaces
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:934
Title\$mCascadingRestrictions
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition: Title.php:123
Title\canTalk
canTalk()
Can this title have a corresponding talk page?
Definition: Title.php:1074
Title\getCascadeProtectionSources
getCascadeProtectionSources( $getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition: Title.php:2992
$cache
$cache
Definition: mcc.php:33
$wgSemiprotectedRestrictionLevels
$wgSemiprotectedRestrictionLevels
Restriction levels that should be considered "semiprotected".
Definition: DefaultSettings.php:5344
Title\getRestrictionTypes
getRestrictionTypes()
Returns restriction types for the current Title.
Definition: Title.php:2768
MalformedTitleException
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Definition: MalformedTitleException.php:25
Title\getTitleProtectionInternal
getTitleProtectionInternal()
Fetch title protection settings.
Definition: Title.php:2818
Wikimedia\Rdbms\IDatabase\addQuotes
addQuotes( $s)
Adds quotes and backslashes.
Title\getTemplateLinksFrom
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:3776
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:380
Title\isRedirect
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3462
$tables
this hook is for auditing only RecentChangesLinked and Watchlist 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:1015
Title\getUserPermissionsErrorsInternal
getUserPermissionsErrorsInternal( $action, $user, $rigor='secure', $short=false)
Can $user perform $action on this page? This is an internal function, with multiple levels of checks ...
Definition: Title.php:2689
DeferredUpdates\PRESEND
const PRESEND
Definition: DeferredUpdates.php:60
MWNamespace\equals
static equals( $ns1, $ns2)
Returns whether the specified namespaces are the same namespace.
Definition: MWNamespace.php:201
Title\$mArticleID
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition: Title.php:88
Title\isDeleted
isDeleted()
Is there a version of this page in the deletion archive?
Definition: Title.php:3385
Title\capitalize
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition: Title.php:3594
Title\$mDefaultNamespace
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition: Title.php:148
Title\$mLength
int $mLength
The page length, 0 for special pages.
Definition: Title.php:151
Title\$mEstimateRevisions
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition: Title.php:106
Title\hasSubjectNamespace
hasSubjectNamespace( $ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition: Title.php:1201
LinkCache\singleton
static singleton()
Get an instance of this class.
Definition: LinkCache.php:67
Title\isSpecialPage
isSpecialPage()
Returns true if this is a special page.
Definition: Title.php:1113
Title\purgeSquid
purgeSquid()
Purge all applicable CDN URLs.
Definition: Title.php:3861
Title\getPreviousRevisionID
getPreviousRevisionID( $revId, $flags=0)
Get the revision ID of the previous revision.
Definition: Title.php:4278
TitleArray
The TitleArray class only exists to provide the newFromResult method at pre- sent.
Definition: TitleArray.php:33
Title\getPageLanguage
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition: Title.php:4964
Title\CACHE_MAX
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles.
Definition: Title.php:48
Title\getEditNotices
getEditNotices( $oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:5046
as
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:22
Title\getTouched
getTouched( $db=null)
Get the last touched timestamp.
Definition: Title.php:4746
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
MWNamespace\isCapitalized
static isCapitalized( $index)
Is the namespace first-letter capitalized?
Definition: MWNamespace.php:421
Title\isCssOrJsPage
isCssOrJsPage()
Definition: Title.php:1308
Wikimedia\Rdbms\IDatabase\selectFieldValues
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
$wgRestrictionLevels
$wgRestrictionLevels
Rights which can be required for each protection level (via action=protect)
Definition: DefaultSettings.php:5319
Title\getFragmentForURL
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition: Title.php:1536
Title\exists
exists( $flags=0)
Check if page exists.
Definition: Title.php:4557
NS_USER
const NS_USER
Definition: Defines.php:76
in
null for the wiki Added in
Definition: hooks.txt:1591
Sanitizer\escapeIdForLink
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:1288
Title\isAlwaysKnown
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition: Title.php:4579
Title\isDeletedQuick
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition: Title.php:3410
Title\getSubpageUrlForm
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition: Title.php:1774
Language\factory
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:183
Title\isNewPage
isNewPage()
Check if this is a new page.
Definition: Title.php:4336
$wgRestrictionTypes
$wgRestrictionTypes
Set of available actions that can be restricted via action=protect You probably shouldn't change this...
Definition: DefaultSettings.php:5306
Title\setContentModel
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:1018
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:82
CONTENT_MODEL_JAVASCRIPT
const CONTENT_MODEL_JAVASCRIPT
Definition: Defines.php:246
Title\compare
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition: Title.php:786
Title\countAuthorsBetween
countAuthorsBetween( $old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition: Title.php:4516
$t
$t
Definition: testCompression.php:69
Title\legalChars
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:623
MWNamespace\isWatchable
static isWatchable( $index)
Can pages in a namespace be watched?
Definition: MWNamespace.php:358
Title\invalidateCache
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4692
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:737
MediaWikiServices
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:25
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
Title\isTrans
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition: Title.php:876
Title\checkUserConfigPermissions
checkUserConfigPermissions( $action, $user, $errors, $rigor, $short)
Check CSS/JSON/JS sub-page permissions.
Definition: Title.php:2330
Title\getRestrictions
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition: Title.php:3091
MWNamespace\getTalk
static getTalk( $index)
Get the talk namespace index for a given namespace.
Definition: MWNamespace.php:129
$query
null for the 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:1620
MWNamespace\getSubject
static getSubject( $index)
Get the subject namespace index for a given namespace Special namespaces (NS_MEDIA,...
Definition: MWNamespace.php:143
Title\checkPermissionHooks
checkPermissionHooks( $action, $user, $errors, $rigor, $short)
Check various permission hooks.
Definition: Title.php:2264
ResourceLoaderWikiModule\invalidateModuleCache
static invalidateModuleCache(Title $title, Revision $old=null, Revision $new=null, $wikiId)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
Definition: ResourceLoaderWikiModule.php:443
CommentStore\getStore
static getStore()
Definition: CommentStore.php:130
Title\isContentPage
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page,...
Definition: Title.php:1212
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
wfLocalFile
wfLocalFile( $title)
Get an object referring to a locally registered file.
Definition: GlobalFunctions.php:2864
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:416
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
$wgVariantArticlePath
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
Definition: DefaultSettings.php:3104
Title\getUserCaseDBKey
getUserCaseDBKey()
Get the DB key with the initial letter case as specified by the user.
Definition: Title.php:956
Title\getSelectFields
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
Definition: Title.php:390
Title\hasSubpages
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:3330
Title\escapeFragmentForURL
static escapeFragmentForURL( $fragment)
Escape a text fragment, say from a link, for a URL.
Definition: Title.php:769
$retval
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account incomplete not yet checked for validity & $retval
Definition: hooks.txt:266
Title\getBaseText
getBaseText()
Get the base page name without a namespace, i.e.
Definition: Title.php:1704
Title\pageCond
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition: Title.php:4206
Sanitizer\escapeId
static escapeId( $id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it.
Definition: Sanitizer.php:1215
Title\getText
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:929
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
MWNamespace\getCanonicalName
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
Definition: MWNamespace.php:255
Title\$mTextform
string $mTextform
Text form (spaces not underscores) of the main part.
Definition: Title.php:64
Title\$mPrefixedText
string $mPrefixedText
Text form including namespace/interwiki, initialised on demand.
Definition: Title.php:138
Wikimedia\Rdbms\IDatabase\delete
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
Title\purgeExpiredRestrictions
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition: Title.php:3289
$wgInternalServer
$wgInternalServer
Internal server name as known to CDN, if different.
Definition: DefaultSettings.php:2713
Title\$mLatestID
bool int $mLatestID
ID of most recent revision.
Definition: Title.php:91
Title\checkUserBlock
checkUserBlock( $action, $user, $errors, $rigor, $short)
Check that the user isn't blocked from editing.
Definition: Title.php:2546
Title\$mRestrictionsLoaded
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition: Title.php:135
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:521
Title\getSubjectNsText
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition: Title.php:1052
Title\$mTitleProtection
mixed $mTitleProtection
Cached value for getTitleProtection (create protection)
Definition: Title.php:141
Title\loadRestrictions
loadRestrictions( $oldFashionedRestrictions=null)
Load restrictions from the page_restrictions table.
Definition: Title.php:3224
TitleValue
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:35
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:57
Title\getRelativeRevisionID
getRelativeRevisionID( $revId, $flags, $dir)
Get next/previous revision ID relative to another revision ID.
Definition: Title.php:4222
wfArrayToCgi
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
Definition: GlobalFunctions.php:377
Title\getFullUrlForRedirect
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition: Title.php:1877
Title\getRootTitle
getRootTitle()
Get the root page name title, i.e.
Definition: Title.php:1689
$type
$type
Definition: testCompression.php:48
Title\getLength
getLength( $flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition: Title.php:3497
Title\getAuthorsBetween
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition: Title.php:4441
Title\isWatchable
isWatchable()
Can this title be added to a user's watchlist?
Definition: Title.php:1104