MediaWiki  master
Title.php
Go to the documentation of this file.
1 <?php
27 
36 class Title implements LinkTarget {
38  static private $titleCache = null;
39 
45  const CACHE_MAX = 1000;
46 
51  const GAID_FOR_UPDATE = 1;
52 
58  // @{
59 
61  public $mTextform = '';
62 
64  public $mUrlform = '';
65 
67  public $mDbkeyform = '';
68 
70  protected $mUserCaseDBKey;
71 
73  public $mNamespace = NS_MAIN;
74 
76  public $mInterwiki = '';
77 
79  private $mLocalInterwiki = false;
80 
82  public $mFragment = '';
83 
85  public $mArticleID = -1;
86 
88  protected $mLatestID = false;
89 
94  public $mContentModel = false;
95 
98 
100  public $mRestrictions = [];
101 
103  protected $mOldRestrictions = false;
104 
107 
110 
112  protected $mRestrictionsExpiry = [];
113 
116 
119 
121  public $mRestrictionsLoaded = false;
122 
124  protected $mPrefixedText = null;
125 
128 
135 
137  protected $mLength = -1;
138 
140  public $mRedirect = null;
141 
144 
146  private $mHasSubpages;
147 
149  private $mPageLanguage = false;
150 
153  private $mDbPageLanguage = false;
154 
156  private $mTitleValue = null;
157 
159  private $mIsBigDeletion = null;
160  // @}
161 
170  private static function getTitleFormatter() {
171  return MediaWikiServices::getInstance()->getTitleFormatter();
172  }
173 
182  private static function getInterwikiLookup() {
183  return MediaWikiServices::getInstance()->getInterwikiLookup();
184  }
185 
189  function __construct() {
190  }
191 
200  public static function newFromDBkey( $key ) {
201  $t = new Title();
202  $t->mDbkeyform = $key;
203 
204  try {
205  $t->secureAndSplit();
206  return $t;
207  } catch ( MalformedTitleException $ex ) {
208  return null;
209  }
210  }
211 
219  public static function newFromTitleValue( TitleValue $titleValue ) {
220  return self::newFromLinkTarget( $titleValue );
221  }
222 
230  public static function newFromLinkTarget( LinkTarget $linkTarget ) {
231  if ( $linkTarget instanceof Title ) {
232  // Special case if it's already a Title object
233  return $linkTarget;
234  }
235  return self::makeTitle(
236  $linkTarget->getNamespace(),
237  $linkTarget->getText(),
238  $linkTarget->getFragment(),
239  $linkTarget->getInterwiki()
240  );
241  }
242 
256  public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
257  // DWIM: Integers can be passed in here when page titles are used as array keys.
258  if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
259  throw new InvalidArgumentException( '$text must be a string.' );
260  }
261  if ( $text === null ) {
262  return null;
263  }
264 
265  try {
266  return Title::newFromTextThrow( strval( $text ), $defaultNamespace );
267  } catch ( MalformedTitleException $ex ) {
268  return null;
269  }
270  }
271 
286  public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
287  if ( is_object( $text ) ) {
288  throw new MWException( '$text must be a string, given an object' );
289  }
290 
291  $titleCache = self::getTitleCache();
292 
293  // Wiki pages often contain multiple links to the same page.
294  // Title normalization and parsing can become expensive on pages with many
295  // links, so we can save a little time by caching them.
296  // In theory these are value objects and won't get changed...
297  if ( $defaultNamespace == NS_MAIN ) {
298  $t = $titleCache->get( $text );
299  if ( $t ) {
300  return $t;
301  }
302  }
303 
304  // Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
305  $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
306 
307  $t = new Title();
308  $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
309  $t->mDefaultNamespace = intval( $defaultNamespace );
310 
311  $t->secureAndSplit();
312  if ( $defaultNamespace == NS_MAIN ) {
313  $titleCache->set( $text, $t );
314  }
315  return $t;
316  }
317 
333  public static function newFromURL( $url ) {
334  $t = new Title();
335 
336  # For compatibility with old buggy URLs. "+" is usually not valid in titles,
337  # but some URLs used it as a space replacement and they still come
338  # from some external search tools.
339  if ( strpos( self::legalChars(), '+' ) === false ) {
340  $url = strtr( $url, '+', ' ' );
341  }
342 
343  $t->mDbkeyform = strtr( $url, ' ', '_' );
344 
345  try {
346  $t->secureAndSplit();
347  return $t;
348  } catch ( MalformedTitleException $ex ) {
349  return null;
350  }
351  }
352 
356  private static function getTitleCache() {
357  if ( self::$titleCache == null ) {
358  self::$titleCache = new HashBagOStuff( [ 'maxKeys' => self::CACHE_MAX ] );
359  }
360  return self::$titleCache;
361  }
362 
370  protected static function getSelectFields() {
372 
373  $fields = [
374  'page_namespace', 'page_title', 'page_id',
375  'page_len', 'page_is_redirect', 'page_latest',
376  ];
377 
378  if ( $wgContentHandlerUseDB ) {
379  $fields[] = 'page_content_model';
380  }
381 
382  if ( $wgPageLanguageUseDB ) {
383  $fields[] = 'page_lang';
384  }
385 
386  return $fields;
387  }
388 
396  public static function newFromID( $id, $flags = 0 ) {
397  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
398  $row = $db->selectRow(
399  'page',
400  self::getSelectFields(),
401  [ 'page_id' => $id ],
402  __METHOD__
403  );
404  if ( $row !== false ) {
405  $title = Title::newFromRow( $row );
406  } else {
407  $title = null;
408  }
409  return $title;
410  }
411 
418  public static function newFromIDs( $ids ) {
419  if ( !count( $ids ) ) {
420  return [];
421  }
422  $dbr = wfGetDB( DB_SLAVE );
423 
424  $res = $dbr->select(
425  'page',
426  self::getSelectFields(),
427  [ 'page_id' => $ids ],
428  __METHOD__
429  );
430 
431  $titles = [];
432  foreach ( $res as $row ) {
433  $titles[] = Title::newFromRow( $row );
434  }
435  return $titles;
436  }
437 
444  public static function newFromRow( $row ) {
445  $t = self::makeTitle( $row->page_namespace, $row->page_title );
446  $t->loadFromRow( $row );
447  return $t;
448  }
449 
456  public function loadFromRow( $row ) {
457  if ( $row ) { // page found
458  if ( isset( $row->page_id ) ) {
459  $this->mArticleID = (int)$row->page_id;
460  }
461  if ( isset( $row->page_len ) ) {
462  $this->mLength = (int)$row->page_len;
463  }
464  if ( isset( $row->page_is_redirect ) ) {
465  $this->mRedirect = (bool)$row->page_is_redirect;
466  }
467  if ( isset( $row->page_latest ) ) {
468  $this->mLatestID = (int)$row->page_latest;
469  }
470  if ( isset( $row->page_content_model ) ) {
471  $this->mContentModel = strval( $row->page_content_model );
472  } else {
473  $this->mContentModel = false; # initialized lazily in getContentModel()
474  }
475  if ( isset( $row->page_lang ) ) {
476  $this->mDbPageLanguage = (string)$row->page_lang;
477  }
478  if ( isset( $row->page_restrictions ) ) {
479  $this->mOldRestrictions = $row->page_restrictions;
480  }
481  } else { // page not found
482  $this->mArticleID = 0;
483  $this->mLength = 0;
484  $this->mRedirect = false;
485  $this->mLatestID = 0;
486  $this->mContentModel = false; # initialized lazily in getContentModel()
487  }
488  }
489 
503  public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
504  $t = new Title();
505  $t->mInterwiki = $interwiki;
506  $t->mFragment = $fragment;
507  $t->mNamespace = $ns = intval( $ns );
508  $t->mDbkeyform = strtr( $title, ' ', '_' );
509  $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
510  $t->mUrlform = wfUrlencode( $t->mDbkeyform );
511  $t->mTextform = strtr( $title, '_', ' ' );
512  $t->mContentModel = false; # initialized lazily in getContentModel()
513  return $t;
514  }
515 
527  public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
528  if ( !MWNamespace::exists( $ns ) ) {
529  return null;
530  }
531 
532  $t = new Title();
533  $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
534 
535  try {
536  $t->secureAndSplit();
537  return $t;
538  } catch ( MalformedTitleException $ex ) {
539  return null;
540  }
541  }
542 
548  public static function newMainPage() {
549  $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
550  // Don't give fatal errors if the message is broken
551  if ( !$title ) {
552  $title = Title::newFromText( 'Main Page' );
553  }
554  return $title;
555  }
556 
563  public static function nameOf( $id ) {
564  $dbr = wfGetDB( DB_SLAVE );
565 
566  $s = $dbr->selectRow(
567  'page',
568  [ 'page_namespace', 'page_title' ],
569  [ 'page_id' => $id ],
570  __METHOD__
571  );
572  if ( $s === false ) {
573  return null;
574  }
575 
576  $n = self::makeName( $s->page_namespace, $s->page_title );
577  return $n;
578  }
579 
585  public static function legalChars() {
587  return $wgLegalTitleChars;
588  }
589 
599  static function getTitleInvalidRegex() {
600  wfDeprecated( __METHOD__, '1.25' );
602  }
603 
613  public static function convertByteClassToUnicodeClass( $byteClass ) {
614  $length = strlen( $byteClass );
615  // Input token queue
616  $x0 = $x1 = $x2 = '';
617  // Decoded queue
618  $d0 = $d1 = $d2 = '';
619  // Decoded integer codepoints
620  $ord0 = $ord1 = $ord2 = 0;
621  // Re-encoded queue
622  $r0 = $r1 = $r2 = '';
623  // Output
624  $out = '';
625  // Flags
626  $allowUnicode = false;
627  for ( $pos = 0; $pos < $length; $pos++ ) {
628  // Shift the queues down
629  $x2 = $x1;
630  $x1 = $x0;
631  $d2 = $d1;
632  $d1 = $d0;
633  $ord2 = $ord1;
634  $ord1 = $ord0;
635  $r2 = $r1;
636  $r1 = $r0;
637  // Load the current input token and decoded values
638  $inChar = $byteClass[$pos];
639  if ( $inChar == '\\' ) {
640  if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
641  $x0 = $inChar . $m[0];
642  $d0 = chr( hexdec( $m[1] ) );
643  $pos += strlen( $m[0] );
644  } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
645  $x0 = $inChar . $m[0];
646  $d0 = chr( octdec( $m[0] ) );
647  $pos += strlen( $m[0] );
648  } elseif ( $pos + 1 >= $length ) {
649  $x0 = $d0 = '\\';
650  } else {
651  $d0 = $byteClass[$pos + 1];
652  $x0 = $inChar . $d0;
653  $pos += 1;
654  }
655  } else {
656  $x0 = $d0 = $inChar;
657  }
658  $ord0 = ord( $d0 );
659  // Load the current re-encoded value
660  if ( $ord0 < 32 || $ord0 == 0x7f ) {
661  $r0 = sprintf( '\x%02x', $ord0 );
662  } elseif ( $ord0 >= 0x80 ) {
663  // Allow unicode if a single high-bit character appears
664  $r0 = sprintf( '\x%02x', $ord0 );
665  $allowUnicode = true;
666  } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
667  $r0 = '\\' . $d0;
668  } else {
669  $r0 = $d0;
670  }
671  // Do the output
672  if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
673  // Range
674  if ( $ord2 > $ord0 ) {
675  // Empty range
676  } elseif ( $ord0 >= 0x80 ) {
677  // Unicode range
678  $allowUnicode = true;
679  if ( $ord2 < 0x80 ) {
680  // Keep the non-unicode section of the range
681  $out .= "$r2-\\x7F";
682  }
683  } else {
684  // Normal range
685  $out .= "$r2-$r0";
686  }
687  // Reset state to the initial value
688  $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
689  } elseif ( $ord2 < 0x80 ) {
690  // ASCII character
691  $out .= $r2;
692  }
693  }
694  if ( $ord1 < 0x80 ) {
695  $out .= $r1;
696  }
697  if ( $ord0 < 0x80 ) {
698  $out .= $r0;
699  }
700  if ( $allowUnicode ) {
701  $out .= '\u0080-\uFFFF';
702  }
703  return $out;
704  }
705 
717  public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
718  $canonicalNamespace = false
719  ) {
721 
722  if ( $canonicalNamespace ) {
723  $namespace = MWNamespace::getCanonicalName( $ns );
724  } else {
725  $namespace = $wgContLang->getNsText( $ns );
726  }
727  $name = $namespace == '' ? $title : "$namespace:$title";
728  if ( strval( $interwiki ) != '' ) {
729  $name = "$interwiki:$name";
730  }
731  if ( strval( $fragment ) != '' ) {
732  $name .= '#' . $fragment;
733  }
734  return $name;
735  }
736 
743  static function escapeFragmentForURL( $fragment ) {
744  # Note that we don't urlencode the fragment. urlencoded Unicode
745  # fragments appear not to work in IE (at least up to 7) or in at least
746  # one version of Opera 9.x. The W3C validator, for one, doesn't seem
747  # to care if they aren't encoded.
748  return Sanitizer::escapeId( $fragment, 'noninitial' );
749  }
750 
759  public static function compare( $a, $b ) {
760  if ( $a->getNamespace() == $b->getNamespace() ) {
761  return strcmp( $a->getText(), $b->getText() );
762  } else {
763  return $a->getNamespace() - $b->getNamespace();
764  }
765  }
766 
774  public function isLocal() {
775  if ( $this->isExternal() ) {
776  $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
777  if ( $iw ) {
778  return $iw->isLocal();
779  }
780  }
781  return true;
782  }
783 
789  public function isExternal() {
790  return $this->mInterwiki !== '';
791  }
792 
800  public function getInterwiki() {
801  return $this->mInterwiki;
802  }
803 
809  public function wasLocalInterwiki() {
810  return $this->mLocalInterwiki;
811  }
812 
819  public function isTrans() {
820  if ( !$this->isExternal() ) {
821  return false;
822  }
823 
824  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
825  }
826 
832  public function getTransWikiID() {
833  if ( !$this->isExternal() ) {
834  return false;
835  }
836 
837  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
838  }
839 
849  public function getTitleValue() {
850  if ( $this->mTitleValue === null ) {
851  try {
852  $this->mTitleValue = new TitleValue(
853  $this->getNamespace(),
854  $this->getDBkey(),
855  $this->getFragment(),
856  $this->getInterwiki()
857  );
858  } catch ( InvalidArgumentException $ex ) {
859  wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
860  $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
861  }
862  }
863 
864  return $this->mTitleValue;
865  }
866 
872  public function getText() {
873  return $this->mTextform;
874  }
875 
881  public function getPartialURL() {
882  return $this->mUrlform;
883  }
884 
890  public function getDBkey() {
891  return $this->mDbkeyform;
892  }
893 
899  function getUserCaseDBKey() {
900  if ( !is_null( $this->mUserCaseDBKey ) ) {
901  return $this->mUserCaseDBKey;
902  } else {
903  // If created via makeTitle(), $this->mUserCaseDBKey is not set.
904  return $this->mDbkeyform;
905  }
906  }
907 
913  public function getNamespace() {
914  return $this->mNamespace;
915  }
916 
923  public function getContentModel( $flags = 0 ) {
924  if ( ( !$this->mContentModel || $flags === Title::GAID_FOR_UPDATE ) &&
925  $this->getArticleID( $flags )
926  ) {
927  $linkCache = LinkCache::singleton();
928  $linkCache->addLinkObj( $this ); # in case we already had an article ID
929  $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
930  }
931 
932  if ( !$this->mContentModel ) {
933  $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
934  }
935 
936  return $this->mContentModel;
937  }
938 
945  public function hasContentModel( $id ) {
946  return $this->getContentModel() == $id;
947  }
948 
954  public function getNsText() {
955  if ( $this->isExternal() ) {
956  // This probably shouldn't even happen,
957  // but for interwiki transclusion it sometimes does.
958  // Use the canonical namespaces if possible to try to
959  // resolve a foreign namespace.
960  if ( MWNamespace::exists( $this->mNamespace ) ) {
961  return MWNamespace::getCanonicalName( $this->mNamespace );
962  }
963  }
964 
965  try {
966  $formatter = self::getTitleFormatter();
967  return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
968  } catch ( InvalidArgumentException $ex ) {
969  wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
970  return false;
971  }
972  }
973 
979  public function getSubjectNsText() {
981  return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
982  }
983 
989  public function getTalkNsText() {
991  return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
992  }
993 
999  public function canTalk() {
1000  return MWNamespace::canTalk( $this->mNamespace );
1001  }
1002 
1008  public function canExist() {
1009  return $this->mNamespace >= NS_MAIN;
1010  }
1011 
1017  public function isWatchable() {
1018  return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
1019  }
1020 
1026  public function isSpecialPage() {
1027  return $this->getNamespace() == NS_SPECIAL;
1028  }
1029 
1036  public function isSpecial( $name ) {
1037  if ( $this->isSpecialPage() ) {
1038  list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
1039  if ( $name == $thisName ) {
1040  return true;
1041  }
1042  }
1043  return false;
1044  }
1045 
1052  public function fixSpecialName() {
1053  if ( $this->isSpecialPage() ) {
1054  list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
1055  if ( $canonicalName ) {
1056  $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
1057  if ( $localName != $this->mDbkeyform ) {
1058  return Title::makeTitle( NS_SPECIAL, $localName );
1059  }
1060  }
1061  }
1062  return $this;
1063  }
1064 
1075  public function inNamespace( $ns ) {
1076  return MWNamespace::equals( $this->getNamespace(), $ns );
1077  }
1078 
1086  public function inNamespaces( /* ... */ ) {
1087  $namespaces = func_get_args();
1088  if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1089  $namespaces = $namespaces[0];
1090  }
1091 
1092  foreach ( $namespaces as $ns ) {
1093  if ( $this->inNamespace( $ns ) ) {
1094  return true;
1095  }
1096  }
1097 
1098  return false;
1099  }
1100 
1114  public function hasSubjectNamespace( $ns ) {
1115  return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
1116  }
1117 
1125  public function isContentPage() {
1126  return MWNamespace::isContent( $this->getNamespace() );
1127  }
1128 
1135  public function isMovable() {
1136  if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
1137  // Interwiki title or immovable namespace. Hooks don't get to override here
1138  return false;
1139  }
1140 
1141  $result = true;
1142  Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1143  return $result;
1144  }
1145 
1156  public function isMainPage() {
1157  return $this->equals( Title::newMainPage() );
1158  }
1159 
1165  public function isSubpage() {
1166  return MWNamespace::hasSubpages( $this->mNamespace )
1167  ? strpos( $this->getText(), '/' ) !== false
1168  : false;
1169  }
1170 
1176  public function isConversionTable() {
1177  // @todo ConversionTable should become a separate content model.
1178 
1179  return $this->getNamespace() == NS_MEDIAWIKI &&
1180  strpos( $this->getText(), 'Conversiontable/' ) === 0;
1181  }
1182 
1188  public function isWikitextPage() {
1189  return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1190  }
1191 
1206  public function isCssOrJsPage() {
1207  $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
1208  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1210 
1211  # @note This hook is also called in ContentHandler::getDefaultModel.
1212  # It's called here again to make sure hook functions can force this
1213  # method to return true even outside the MediaWiki namespace.
1214 
1215  Hooks::run( 'TitleIsCssOrJsPage', [ $this, &$isCssOrJsPage ], '1.25' );
1216 
1217  return $isCssOrJsPage;
1218  }
1219 
1225  public function isCssJsSubpage() {
1226  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1227  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1228  || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
1229  }
1230 
1236  public function getSkinFromCssJsSubpage() {
1237  $subpage = explode( '/', $this->mTextform );
1238  $subpage = $subpage[count( $subpage ) - 1];
1239  $lastdot = strrpos( $subpage, '.' );
1240  if ( $lastdot === false ) {
1241  return $subpage; # Never happens: only called for names ending in '.css' or '.js'
1242  }
1243  return substr( $subpage, 0, $lastdot );
1244  }
1245 
1251  public function isCssSubpage() {
1252  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1253  && $this->hasContentModel( CONTENT_MODEL_CSS ) );
1254  }
1255 
1261  public function isJsSubpage() {
1262  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1264  }
1265 
1271  public function isTalkPage() {
1272  return MWNamespace::isTalk( $this->getNamespace() );
1273  }
1274 
1280  public function getTalkPage() {
1281  return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
1282  }
1283 
1290  public function getSubjectPage() {
1291  // Is this the same title?
1292  $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
1293  if ( $this->getNamespace() == $subjectNS ) {
1294  return $this;
1295  }
1296  return Title::makeTitle( $subjectNS, $this->getDBkey() );
1297  }
1298 
1307  public function getOtherPage() {
1308  if ( $this->isSpecialPage() ) {
1309  throw new MWException( 'Special pages cannot have other pages' );
1310  }
1311  if ( $this->isTalkPage() ) {
1312  return $this->getSubjectPage();
1313  } else {
1314  return $this->getTalkPage();
1315  }
1316  }
1317 
1323  public function getDefaultNamespace() {
1324  return $this->mDefaultNamespace;
1325  }
1326 
1334  public function getFragment() {
1335  return $this->mFragment;
1336  }
1337 
1344  public function hasFragment() {
1345  return $this->mFragment !== '';
1346  }
1347 
1352  public function getFragmentForURL() {
1353  if ( !$this->hasFragment() ) {
1354  return '';
1355  } else {
1356  return '#' . Title::escapeFragmentForURL( $this->getFragment() );
1357  }
1358  }
1359 
1372  public function setFragment( $fragment ) {
1373  $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1374  }
1375 
1383  public function createFragmentTarget( $fragment ) {
1384  return self::makeTitle(
1385  $this->getNamespace(),
1386  $this->getText(),
1387  $fragment,
1388  $this->getInterwiki()
1389  );
1390 
1391  }
1392 
1400  private function prefix( $name ) {
1401  $p = '';
1402  if ( $this->isExternal() ) {
1403  $p = $this->mInterwiki . ':';
1404  }
1405 
1406  if ( 0 != $this->mNamespace ) {
1407  $p .= $this->getNsText() . ':';
1408  }
1409  return $p . $name;
1410  }
1411 
1418  public function getPrefixedDBkey() {
1419  $s = $this->prefix( $this->mDbkeyform );
1420  $s = strtr( $s, ' ', '_' );
1421  return $s;
1422  }
1423 
1430  public function getPrefixedText() {
1431  if ( $this->mPrefixedText === null ) {
1432  $s = $this->prefix( $this->mTextform );
1433  $s = strtr( $s, '_', ' ' );
1434  $this->mPrefixedText = $s;
1435  }
1436  return $this->mPrefixedText;
1437  }
1438 
1444  public function __toString() {
1445  return $this->getPrefixedText();
1446  }
1447 
1454  public function getFullText() {
1455  $text = $this->getPrefixedText();
1456  if ( $this->hasFragment() ) {
1457  $text .= '#' . $this->getFragment();
1458  }
1459  return $text;
1460  }
1461 
1474  public function getRootText() {
1475  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1476  return $this->getText();
1477  }
1478 
1479  return strtok( $this->getText(), '/' );
1480  }
1481 
1494  public function getRootTitle() {
1495  return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
1496  }
1497 
1509  public function getBaseText() {
1510  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1511  return $this->getText();
1512  }
1513 
1514  $parts = explode( '/', $this->getText() );
1515  # Don't discard the real title if there's no subpage involved
1516  if ( count( $parts ) > 1 ) {
1517  unset( $parts[count( $parts ) - 1] );
1518  }
1519  return implode( '/', $parts );
1520  }
1521 
1534  public function getBaseTitle() {
1535  return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
1536  }
1537 
1549  public function getSubpageText() {
1550  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1551  return $this->mTextform;
1552  }
1553  $parts = explode( '/', $this->mTextform );
1554  return $parts[count( $parts ) - 1];
1555  }
1556 
1570  public function getSubpage( $text ) {
1571  return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
1572  }
1573 
1579  public function getSubpageUrlForm() {
1580  $text = $this->getSubpageText();
1581  $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1582  return $text;
1583  }
1584 
1590  public function getPrefixedURL() {
1591  $s = $this->prefix( $this->mDbkeyform );
1592  $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1593  return $s;
1594  }
1595 
1609  private static function fixUrlQueryArgs( $query, $query2 = false ) {
1610  if ( $query2 !== false ) {
1611  wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1612  "method called with a second parameter is deprecated. Add your " .
1613  "parameter to an array passed as the first parameter.", "1.19" );
1614  }
1615  if ( is_array( $query ) ) {
1616  $query = wfArrayToCgi( $query );
1617  }
1618  if ( $query2 ) {
1619  if ( is_string( $query2 ) ) {
1620  // $query2 is a string, we will consider this to be
1621  // a deprecated $variant argument and add it to the query
1622  $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1623  } else {
1624  $query2 = wfArrayToCgi( $query2 );
1625  }
1626  // If we have $query content add a & to it first
1627  if ( $query ) {
1628  $query .= '&';
1629  }
1630  // Now append the queries together
1631  $query .= $query2;
1632  }
1633  return $query;
1634  }
1635 
1647  public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1648  $query = self::fixUrlQueryArgs( $query, $query2 );
1649 
1650  # Hand off all the decisions on urls to getLocalURL
1651  $url = $this->getLocalURL( $query );
1652 
1653  # Expand the url to make it a full url. Note that getLocalURL has the
1654  # potential to output full urls for a variety of reasons, so we use
1655  # wfExpandUrl instead of simply prepending $wgServer
1656  $url = wfExpandUrl( $url, $proto );
1657 
1658  # Finally, add the fragment.
1659  $url .= $this->getFragmentForURL();
1660 
1661  Hooks::run( 'GetFullURL', [ &$this, &$url, $query ] );
1662  return $url;
1663  }
1664 
1688  public function getLocalURL( $query = '', $query2 = false ) {
1690 
1691  $query = self::fixUrlQueryArgs( $query, $query2 );
1692 
1693  $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1694  if ( $interwiki ) {
1695  $namespace = $this->getNsText();
1696  if ( $namespace != '' ) {
1697  # Can this actually happen? Interwikis shouldn't be parsed.
1698  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1699  $namespace .= ':';
1700  }
1701  $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1702  $url = wfAppendQuery( $url, $query );
1703  } else {
1704  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1705  if ( $query == '' ) {
1706  $url = str_replace( '$1', $dbkey, $wgArticlePath );
1707  Hooks::run( 'GetLocalURL::Article', [ &$this, &$url ] );
1708  } else {
1710  $url = false;
1711  $matches = [];
1712 
1713  if ( !empty( $wgActionPaths )
1714  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1715  ) {
1716  $action = urldecode( $matches[2] );
1717  if ( isset( $wgActionPaths[$action] ) ) {
1718  $query = $matches[1];
1719  if ( isset( $matches[4] ) ) {
1720  $query .= $matches[4];
1721  }
1722  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1723  if ( $query != '' ) {
1724  $url = wfAppendQuery( $url, $query );
1725  }
1726  }
1727  }
1728 
1729  if ( $url === false
1730  && $wgVariantArticlePath
1731  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1732  && $this->getPageLanguage()->equals( $wgContLang )
1733  && $this->getPageLanguage()->hasVariants()
1734  ) {
1735  $variant = urldecode( $matches[1] );
1736  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1737  // Only do the variant replacement if the given variant is a valid
1738  // variant for the page's language.
1739  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1740  $url = str_replace( '$1', $dbkey, $url );
1741  }
1742  }
1743 
1744  if ( $url === false ) {
1745  if ( $query == '-' ) {
1746  $query = '';
1747  }
1748  $url = "{$wgScript}?title={$dbkey}&{$query}";
1749  }
1750  }
1751 
1752  Hooks::run( 'GetLocalURL::Internal', [ &$this, &$url, $query ] );
1753 
1754  // @todo FIXME: This causes breakage in various places when we
1755  // actually expected a local URL and end up with dupe prefixes.
1756  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
1757  $url = $wgServer . $url;
1758  }
1759  }
1760  Hooks::run( 'GetLocalURL', [ &$this, &$url, $query ] );
1761  return $url;
1762  }
1763 
1781  public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
1782  if ( $this->isExternal() || $proto !== false ) {
1783  $ret = $this->getFullURL( $query, $query2, $proto );
1784  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
1785  $ret = $this->getFragmentForURL();
1786  } else {
1787  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
1788  }
1789  return $ret;
1790  }
1791 
1804  public function getInternalURL( $query = '', $query2 = false ) {
1806  $query = self::fixUrlQueryArgs( $query, $query2 );
1807  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
1808  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
1809  Hooks::run( 'GetInternalURL', [ &$this, &$url, $query ] );
1810  return $url;
1811  }
1812 
1824  public function getCanonicalURL( $query = '', $query2 = false ) {
1825  $query = self::fixUrlQueryArgs( $query, $query2 );
1826  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
1827  Hooks::run( 'GetCanonicalURL', [ &$this, &$url, $query ] );
1828  return $url;
1829  }
1830 
1836  public function getEditURL() {
1837  if ( $this->isExternal() ) {
1838  return '';
1839  }
1840  $s = $this->getLocalURL( 'action=edit' );
1841 
1842  return $s;
1843  }
1844 
1859  public function quickUserCan( $action, $user = null ) {
1860  return $this->userCan( $action, $user, false );
1861  }
1862 
1872  public function userCan( $action, $user = null, $rigor = 'secure' ) {
1873  if ( !$user instanceof User ) {
1874  global $wgUser;
1875  $user = $wgUser;
1876  }
1877 
1878  return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
1879  }
1880 
1896  public function getUserPermissionsErrors(
1897  $action, $user, $rigor = 'secure', $ignoreErrors = []
1898  ) {
1899  $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
1900 
1901  // Remove the errors being ignored.
1902  foreach ( $errors as $index => $error ) {
1903  $errKey = is_array( $error ) ? $error[0] : $error;
1904 
1905  if ( in_array( $errKey, $ignoreErrors ) ) {
1906  unset( $errors[$index] );
1907  }
1908  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
1909  unset( $errors[$index] );
1910  }
1911  }
1912 
1913  return $errors;
1914  }
1915 
1927  private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
1928  if ( !Hooks::run( 'TitleQuickPermissions',
1929  [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
1930  ) {
1931  return $errors;
1932  }
1933 
1934  if ( $action == 'create' ) {
1935  if (
1936  ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1937  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
1938  ) {
1939  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
1940  }
1941  } elseif ( $action == 'move' ) {
1942  if ( !$user->isAllowed( 'move-rootuserpages' )
1943  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1944  // Show user page-specific message only if the user can move other pages
1945  $errors[] = [ 'cant-move-user-page' ];
1946  }
1947 
1948  // Check if user is allowed to move files if it's a file
1949  if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
1950  $errors[] = [ 'movenotallowedfile' ];
1951  }
1952 
1953  // Check if user is allowed to move category pages if it's a category page
1954  if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
1955  $errors[] = [ 'cant-move-category-page' ];
1956  }
1957 
1958  if ( !$user->isAllowed( 'move' ) ) {
1959  // User can't move anything
1960  $userCanMove = User::groupHasPermission( 'user', 'move' );
1961  $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
1962  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
1963  // custom message if logged-in users without any special rights can move
1964  $errors[] = [ 'movenologintext' ];
1965  } else {
1966  $errors[] = [ 'movenotallowed' ];
1967  }
1968  }
1969  } elseif ( $action == 'move-target' ) {
1970  if ( !$user->isAllowed( 'move' ) ) {
1971  // User can't move anything
1972  $errors[] = [ 'movenotallowed' ];
1973  } elseif ( !$user->isAllowed( 'move-rootuserpages' )
1974  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1975  // Show user page-specific message only if the user can move other pages
1976  $errors[] = [ 'cant-move-to-user-page' ];
1977  } elseif ( !$user->isAllowed( 'move-categorypages' )
1978  && $this->mNamespace == NS_CATEGORY ) {
1979  // Show category page-specific message only if the user can move other pages
1980  $errors[] = [ 'cant-move-to-category-page' ];
1981  }
1982  } elseif ( !$user->isAllowed( $action ) ) {
1983  $errors[] = $this->missingPermissionError( $action, $short );
1984  }
1985 
1986  return $errors;
1987  }
1988 
1997  private function resultToError( $errors, $result ) {
1998  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
1999  // A single array representing an error
2000  $errors[] = $result;
2001  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2002  // A nested array representing multiple errors
2003  $errors = array_merge( $errors, $result );
2004  } elseif ( $result !== '' && is_string( $result ) ) {
2005  // A string representing a message-id
2006  $errors[] = [ $result ];
2007  } elseif ( $result instanceof MessageSpecifier ) {
2008  // A message specifier representing an error
2009  $errors[] = [ $result ];
2010  } elseif ( $result === false ) {
2011  // a generic "We don't want them to do that"
2012  $errors[] = [ 'badaccess-group0' ];
2013  }
2014  return $errors;
2015  }
2016 
2028  private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2029  // Use getUserPermissionsErrors instead
2030  $result = '';
2031  if ( !Hooks::run( 'userCan', [ &$this, &$user, $action, &$result ] ) ) {
2032  return $result ? [] : [ [ 'badaccess-group0' ] ];
2033  }
2034  // Check getUserPermissionsErrors hook
2035  if ( !Hooks::run( 'getUserPermissionsErrors', [ &$this, &$user, $action, &$result ] ) ) {
2036  $errors = $this->resultToError( $errors, $result );
2037  }
2038  // Check getUserPermissionsErrorsExpensive hook
2039  if (
2040  $rigor !== 'quick'
2041  && !( $short && count( $errors ) > 0 )
2042  && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$this, &$user, $action, &$result ] )
2043  ) {
2044  $errors = $this->resultToError( $errors, $result );
2045  }
2046 
2047  return $errors;
2048  }
2049 
2061  private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2062  # Only 'createaccount' can be performed on special pages,
2063  # which don't actually exist in the DB.
2064  if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
2065  $errors[] = [ 'ns-specialprotected' ];
2066  }
2067 
2068  # Check $wgNamespaceProtection for restricted namespaces
2069  if ( $this->isNamespaceProtected( $user ) ) {
2070  $ns = $this->mNamespace == NS_MAIN ?
2071  wfMessage( 'nstab-main' )->text() : $this->getNsText();
2072  $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2073  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2074  }
2075 
2076  return $errors;
2077  }
2078 
2090  private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
2091  # Protect css/js subpages of user pages
2092  # XXX: this might be better using restrictions
2093  # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
2094  if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
2095  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2096  if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
2097  $errors[] = [ 'mycustomcssprotected', $action ];
2098  } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
2099  $errors[] = [ 'mycustomjsprotected', $action ];
2100  }
2101  } else {
2102  if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
2103  $errors[] = [ 'customcssprotected', $action ];
2104  } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
2105  $errors[] = [ 'customjsprotected', $action ];
2106  }
2107  }
2108  }
2109 
2110  return $errors;
2111  }
2112 
2126  private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2127  foreach ( $this->getRestrictions( $action ) as $right ) {
2128  // Backwards compatibility, rewrite sysop -> editprotected
2129  if ( $right == 'sysop' ) {
2130  $right = 'editprotected';
2131  }
2132  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2133  if ( $right == 'autoconfirmed' ) {
2134  $right = 'editsemiprotected';
2135  }
2136  if ( $right == '' ) {
2137  continue;
2138  }
2139  if ( !$user->isAllowed( $right ) ) {
2140  $errors[] = [ 'protectedpagetext', $right, $action ];
2141  } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2142  $errors[] = [ 'protectedpagetext', 'protect', $action ];
2143  }
2144  }
2145 
2146  return $errors;
2147  }
2148 
2160  private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2161  if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
2162  # We /could/ use the protection level on the source page, but it's
2163  # fairly ugly as we have to establish a precedence hierarchy for pages
2164  # included by multiple cascade-protected pages. So just restrict
2165  # it to people with 'protect' permission, as they could remove the
2166  # protection anyway.
2167  list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2168  # Cascading protection depends on more than this page...
2169  # Several cascading protected pages may include this page...
2170  # Check each cascading level
2171  # This is only for protection restrictions, not for all actions
2172  if ( isset( $restrictions[$action] ) ) {
2173  foreach ( $restrictions[$action] as $right ) {
2174  // Backwards compatibility, rewrite sysop -> editprotected
2175  if ( $right == 'sysop' ) {
2176  $right = 'editprotected';
2177  }
2178  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2179  if ( $right == 'autoconfirmed' ) {
2180  $right = 'editsemiprotected';
2181  }
2182  if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2183  $pages = '';
2184  foreach ( $cascadingSources as $page ) {
2185  $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2186  }
2187  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2188  }
2189  }
2190  }
2191  }
2192 
2193  return $errors;
2194  }
2195 
2207  private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2209 
2210  if ( $action == 'protect' ) {
2211  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2212  // If they can't edit, they shouldn't protect.
2213  $errors[] = [ 'protect-cantedit' ];
2214  }
2215  } elseif ( $action == 'create' ) {
2216  $title_protection = $this->getTitleProtection();
2217  if ( $title_protection ) {
2218  if ( $title_protection['permission'] == ''
2219  || !$user->isAllowed( $title_protection['permission'] )
2220  ) {
2221  $errors[] = [
2222  'titleprotected',
2223  User::whoIs( $title_protection['user'] ),
2224  $title_protection['reason']
2225  ];
2226  }
2227  }
2228  } elseif ( $action == 'move' ) {
2229  // Check for immobile pages
2230  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2231  // Specific message for this case
2232  $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2233  } elseif ( !$this->isMovable() ) {
2234  // Less specific message for rarer cases
2235  $errors[] = [ 'immobile-source-page' ];
2236  }
2237  } elseif ( $action == 'move-target' ) {
2238  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2239  $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2240  } elseif ( !$this->isMovable() ) {
2241  $errors[] = [ 'immobile-target-page' ];
2242  }
2243  } elseif ( $action == 'delete' ) {
2244  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2245  if ( !$tempErrors ) {
2246  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2247  $user, $tempErrors, $rigor, true );
2248  }
2249  if ( $tempErrors ) {
2250  // If protection keeps them from editing, they shouldn't be able to delete.
2251  $errors[] = [ 'deleteprotected' ];
2252  }
2253  if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2254  && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2255  ) {
2256  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2257  }
2258  }
2259  return $errors;
2260  }
2261 
2273  private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2274  // Account creation blocks handled at userlogin.
2275  // Unblocking handled in SpecialUnblock
2276  if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2277  return $errors;
2278  }
2279 
2281 
2282  if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
2283  $errors[] = [ 'confirmedittext' ];
2284  }
2285 
2286  $useSlave = ( $rigor !== 'secure' );
2287  if ( ( $action == 'edit' || $action == 'create' )
2288  && !$user->isBlockedFrom( $this, $useSlave )
2289  ) {
2290  // Don't block the user from editing their own talk page unless they've been
2291  // explicitly blocked from that too.
2292  } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2293  // @todo FIXME: Pass the relevant context into this function.
2294  $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2295  }
2296 
2297  return $errors;
2298  }
2299 
2311  private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2313 
2314  $whitelisted = false;
2315  if ( User::isEveryoneAllowed( 'read' ) ) {
2316  # Shortcut for public wikis, allows skipping quite a bit of code
2317  $whitelisted = true;
2318  } elseif ( $user->isAllowed( 'read' ) ) {
2319  # If the user is allowed to read pages, he is allowed to read all pages
2320  $whitelisted = true;
2321  } elseif ( $this->isSpecial( 'Userlogin' )
2322  || $this->isSpecial( 'ChangePassword' )
2323  || $this->isSpecial( 'PasswordReset' )
2324  ) {
2325  # Always grant access to the login page.
2326  # Even anons need to be able to log in.
2327  $whitelisted = true;
2328  } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2329  # Time to check the whitelist
2330  # Only do these checks is there's something to check against
2331  $name = $this->getPrefixedText();
2332  $dbName = $this->getPrefixedDBkey();
2333 
2334  // Check for explicit whitelisting with and without underscores
2335  if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2336  $whitelisted = true;
2337  } elseif ( $this->getNamespace() == NS_MAIN ) {
2338  # Old settings might have the title prefixed with
2339  # a colon for main-namespace pages
2340  if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2341  $whitelisted = true;
2342  }
2343  } elseif ( $this->isSpecialPage() ) {
2344  # If it's a special page, ditch the subpage bit and check again
2345  $name = $this->getDBkey();
2346  list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2347  if ( $name ) {
2348  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2349  if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2350  $whitelisted = true;
2351  }
2352  }
2353  }
2354  }
2355 
2356  if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2357  $name = $this->getPrefixedText();
2358  // Check for regex whitelisting
2359  foreach ( $wgWhitelistReadRegexp as $listItem ) {
2360  if ( preg_match( $listItem, $name ) ) {
2361  $whitelisted = true;
2362  break;
2363  }
2364  }
2365  }
2366 
2367  if ( !$whitelisted ) {
2368  # If the title is not whitelisted, give extensions a chance to do so...
2369  Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2370  if ( !$whitelisted ) {
2371  $errors[] = $this->missingPermissionError( $action, $short );
2372  }
2373  }
2374 
2375  return $errors;
2376  }
2377 
2386  private function missingPermissionError( $action, $short ) {
2387  // We avoid expensive display logic for quickUserCan's and such
2388  if ( $short ) {
2389  return [ 'badaccess-group0' ];
2390  }
2391 
2392  $groups = array_map( [ 'User', 'makeGroupLinkWiki' ],
2393  User::getGroupsWithPermission( $action ) );
2394 
2395  if ( count( $groups ) ) {
2396  global $wgLang;
2397  return [
2398  'badaccess-groups',
2399  $wgLang->commaList( $groups ),
2400  count( $groups )
2401  ];
2402  } else {
2403  return [ 'badaccess-group0' ];
2404  }
2405  }
2406 
2422  $action, $user, $rigor = 'secure', $short = false
2423  ) {
2424  if ( $rigor === true ) {
2425  $rigor = 'secure'; // b/c
2426  } elseif ( $rigor === false ) {
2427  $rigor = 'quick'; // b/c
2428  } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2429  throw new Exception( "Invalid rigor parameter '$rigor'." );
2430  }
2431 
2432  # Read has special handling
2433  if ( $action == 'read' ) {
2434  $checks = [
2435  'checkPermissionHooks',
2436  'checkReadPermissions',
2437  ];
2438  # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
2439  # here as it will lead to duplicate error messages. This is okay to do
2440  # since anywhere that checks for create will also check for edit, and
2441  # those checks are called for edit.
2442  } elseif ( $action == 'create' ) {
2443  $checks = [
2444  'checkQuickPermissions',
2445  'checkPermissionHooks',
2446  'checkPageRestrictions',
2447  'checkCascadingSourcesRestrictions',
2448  'checkActionPermissions',
2449  'checkUserBlock'
2450  ];
2451  } else {
2452  $checks = [
2453  'checkQuickPermissions',
2454  'checkPermissionHooks',
2455  'checkSpecialsAndNSPermissions',
2456  'checkCSSandJSPermissions',
2457  'checkPageRestrictions',
2458  'checkCascadingSourcesRestrictions',
2459  'checkActionPermissions',
2460  'checkUserBlock'
2461  ];
2462  }
2463 
2464  $errors = [];
2465  while ( count( $checks ) > 0 &&
2466  !( $short && count( $errors ) > 0 ) ) {
2467  $method = array_shift( $checks );
2468  $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2469  }
2470 
2471  return $errors;
2472  }
2473 
2481  public static function getFilteredRestrictionTypes( $exists = true ) {
2483  $types = $wgRestrictionTypes;
2484  if ( $exists ) {
2485  # Remove the create restriction for existing titles
2486  $types = array_diff( $types, [ 'create' ] );
2487  } else {
2488  # Only the create and upload restrictions apply to non-existing titles
2489  $types = array_intersect( $types, [ 'create', 'upload' ] );
2490  }
2491  return $types;
2492  }
2493 
2499  public function getRestrictionTypes() {
2500  if ( $this->isSpecialPage() ) {
2501  return [];
2502  }
2503 
2504  $types = self::getFilteredRestrictionTypes( $this->exists() );
2505 
2506  if ( $this->getNamespace() != NS_FILE ) {
2507  # Remove the upload restriction for non-file titles
2508  $types = array_diff( $types, [ 'upload' ] );
2509  }
2510 
2511  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2512 
2513  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2514  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2515 
2516  return $types;
2517  }
2518 
2526  public function getTitleProtection() {
2527  // Can't protect pages in special namespaces
2528  if ( $this->getNamespace() < 0 ) {
2529  return false;
2530  }
2531 
2532  // Can't protect pages that exist.
2533  if ( $this->exists() ) {
2534  return false;
2535  }
2536 
2537  if ( $this->mTitleProtection === null ) {
2538  $dbr = wfGetDB( DB_SLAVE );
2539  $res = $dbr->select(
2540  'protected_titles',
2541  [
2542  'user' => 'pt_user',
2543  'reason' => 'pt_reason',
2544  'expiry' => 'pt_expiry',
2545  'permission' => 'pt_create_perm'
2546  ],
2547  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2548  __METHOD__
2549  );
2550 
2551  // fetchRow returns false if there are no rows.
2552  $row = $dbr->fetchRow( $res );
2553  if ( $row ) {
2554  if ( $row['permission'] == 'sysop' ) {
2555  $row['permission'] = 'editprotected'; // B/C
2556  }
2557  if ( $row['permission'] == 'autoconfirmed' ) {
2558  $row['permission'] = 'editsemiprotected'; // B/C
2559  }
2560  $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2561  }
2562  $this->mTitleProtection = $row;
2563  }
2564  return $this->mTitleProtection;
2565  }
2566 
2570  public function deleteTitleProtection() {
2571  $dbw = wfGetDB( DB_MASTER );
2572 
2573  $dbw->delete(
2574  'protected_titles',
2575  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2576  __METHOD__
2577  );
2578  $this->mTitleProtection = false;
2579  }
2580 
2588  public function isSemiProtected( $action = 'edit' ) {
2590 
2591  $restrictions = $this->getRestrictions( $action );
2593  if ( !$restrictions || !$semi ) {
2594  // Not protected, or all protection is full protection
2595  return false;
2596  }
2597 
2598  // Remap autoconfirmed to editsemiprotected for BC
2599  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2600  $semi[$key] = 'editsemiprotected';
2601  }
2602  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2603  $restrictions[$key] = 'editsemiprotected';
2604  }
2605 
2606  return !array_diff( $restrictions, $semi );
2607  }
2608 
2616  public function isProtected( $action = '' ) {
2618 
2619  $restrictionTypes = $this->getRestrictionTypes();
2620 
2621  # Special pages have inherent protection
2622  if ( $this->isSpecialPage() ) {
2623  return true;
2624  }
2625 
2626  # Check regular protection levels
2627  foreach ( $restrictionTypes as $type ) {
2628  if ( $action == $type || $action == '' ) {
2629  $r = $this->getRestrictions( $type );
2630  foreach ( $wgRestrictionLevels as $level ) {
2631  if ( in_array( $level, $r ) && $level != '' ) {
2632  return true;
2633  }
2634  }
2635  }
2636  }
2637 
2638  return false;
2639  }
2640 
2648  public function isNamespaceProtected( User $user ) {
2650 
2651  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2652  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2653  if ( $right != '' && !$user->isAllowed( $right ) ) {
2654  return true;
2655  }
2656  }
2657  }
2658  return false;
2659  }
2660 
2666  public function isCascadeProtected() {
2667  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2668  return ( $sources > 0 );
2669  }
2670 
2680  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2681  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2682  }
2683 
2697  public function getCascadeProtectionSources( $getPages = true ) {
2698  $pagerestrictions = [];
2699 
2700  if ( $this->mCascadeSources !== null && $getPages ) {
2702  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2703  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2704  }
2705 
2706  $dbr = wfGetDB( DB_SLAVE );
2707 
2708  if ( $this->getNamespace() == NS_FILE ) {
2709  $tables = [ 'imagelinks', 'page_restrictions' ];
2710  $where_clauses = [
2711  'il_to' => $this->getDBkey(),
2712  'il_from=pr_page',
2713  'pr_cascade' => 1
2714  ];
2715  } else {
2716  $tables = [ 'templatelinks', 'page_restrictions' ];
2717  $where_clauses = [
2718  'tl_namespace' => $this->getNamespace(),
2719  'tl_title' => $this->getDBkey(),
2720  'tl_from=pr_page',
2721  'pr_cascade' => 1
2722  ];
2723  }
2724 
2725  if ( $getPages ) {
2726  $cols = [ 'pr_page', 'page_namespace', 'page_title',
2727  'pr_expiry', 'pr_type', 'pr_level' ];
2728  $where_clauses[] = 'page_id=pr_page';
2729  $tables[] = 'page';
2730  } else {
2731  $cols = [ 'pr_expiry' ];
2732  }
2733 
2734  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2735 
2736  $sources = $getPages ? [] : false;
2737  $now = wfTimestampNow();
2738 
2739  foreach ( $res as $row ) {
2740  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2741  if ( $expiry > $now ) {
2742  if ( $getPages ) {
2743  $page_id = $row->pr_page;
2744  $page_ns = $row->page_namespace;
2745  $page_title = $row->page_title;
2746  $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2747  # Add groups needed for each restriction type if its not already there
2748  # Make sure this restriction type still exists
2749 
2750  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2751  $pagerestrictions[$row->pr_type] = [];
2752  }
2753 
2754  if (
2755  isset( $pagerestrictions[$row->pr_type] )
2756  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2757  ) {
2758  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2759  }
2760  } else {
2761  $sources = true;
2762  }
2763  }
2764  }
2765 
2766  if ( $getPages ) {
2767  $this->mCascadeSources = $sources;
2768  $this->mCascadingRestrictions = $pagerestrictions;
2769  } else {
2770  $this->mHasCascadingRestrictions = $sources;
2771  }
2772 
2773  return [ $sources, $pagerestrictions ];
2774  }
2775 
2783  public function areRestrictionsLoaded() {
2785  }
2786 
2796  public function getRestrictions( $action ) {
2797  if ( !$this->mRestrictionsLoaded ) {
2798  $this->loadRestrictions();
2799  }
2800  return isset( $this->mRestrictions[$action] )
2801  ? $this->mRestrictions[$action]
2802  : [];
2803  }
2804 
2812  public function getAllRestrictions() {
2813  if ( !$this->mRestrictionsLoaded ) {
2814  $this->loadRestrictions();
2815  }
2816  return $this->mRestrictions;
2817  }
2818 
2826  public function getRestrictionExpiry( $action ) {
2827  if ( !$this->mRestrictionsLoaded ) {
2828  $this->loadRestrictions();
2829  }
2830  return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2831  }
2832 
2839  if ( !$this->mRestrictionsLoaded ) {
2840  $this->loadRestrictions();
2841  }
2842 
2844  }
2845 
2853  private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
2854  $rows = [];
2855 
2856  foreach ( $res as $row ) {
2857  $rows[] = $row;
2858  }
2859 
2860  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2861  }
2862 
2872  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2873  $dbr = wfGetDB( DB_SLAVE );
2874 
2875  $restrictionTypes = $this->getRestrictionTypes();
2876 
2877  foreach ( $restrictionTypes as $type ) {
2878  $this->mRestrictions[$type] = [];
2879  $this->mRestrictionsExpiry[$type] = 'infinity';
2880  }
2881 
2882  $this->mCascadeRestriction = false;
2883 
2884  # Backwards-compatibility: also load the restrictions from the page record (old format).
2885  if ( $oldFashionedRestrictions !== null ) {
2886  $this->mOldRestrictions = $oldFashionedRestrictions;
2887  }
2888 
2889  if ( $this->mOldRestrictions === false ) {
2890  $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2891  [ 'page_id' => $this->getArticleID() ], __METHOD__ );
2892  }
2893 
2894  if ( $this->mOldRestrictions != '' ) {
2895  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2896  $temp = explode( '=', trim( $restrict ) );
2897  if ( count( $temp ) == 1 ) {
2898  // old old format should be treated as edit/move restriction
2899  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2900  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2901  } else {
2902  $restriction = trim( $temp[1] );
2903  if ( $restriction != '' ) { // some old entries are empty
2904  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2905  }
2906  }
2907  }
2908  }
2909 
2910  if ( count( $rows ) ) {
2911  # Current system - load second to make them override.
2912  $now = wfTimestampNow();
2913 
2914  # Cycle through all the restrictions.
2915  foreach ( $rows as $row ) {
2916 
2917  // Don't take care of restrictions types that aren't allowed
2918  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2919  continue;
2920  }
2921 
2922  // This code should be refactored, now that it's being used more generally,
2923  // But I don't really see any harm in leaving it in Block for now -werdna
2924  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2925 
2926  // Only apply the restrictions if they haven't expired!
2927  if ( !$expiry || $expiry > $now ) {
2928  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2929  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2930 
2931  $this->mCascadeRestriction |= $row->pr_cascade;
2932  }
2933  }
2934  }
2935 
2936  $this->mRestrictionsLoaded = true;
2937  }
2938 
2945  public function loadRestrictions( $oldFashionedRestrictions = null ) {
2946  if ( !$this->mRestrictionsLoaded ) {
2947  $dbr = wfGetDB( DB_SLAVE );
2948  if ( $this->exists() ) {
2949  $res = $dbr->select(
2950  'page_restrictions',
2951  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
2952  [ 'pr_page' => $this->getArticleID() ],
2953  __METHOD__
2954  );
2955 
2956  $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
2957  } else {
2958  $title_protection = $this->getTitleProtection();
2959 
2960  if ( $title_protection ) {
2961  $now = wfTimestampNow();
2962  $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
2963 
2964  if ( !$expiry || $expiry > $now ) {
2965  // Apply the restrictions
2966  $this->mRestrictionsExpiry['create'] = $expiry;
2967  $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
2968  } else { // Get rid of the old restrictions
2969  $this->mTitleProtection = false;
2970  }
2971  } else {
2972  $this->mRestrictionsExpiry['create'] = 'infinity';
2973  }
2974  $this->mRestrictionsLoaded = true;
2975  }
2976  }
2977  }
2978 
2983  public function flushRestrictions() {
2984  $this->mRestrictionsLoaded = false;
2985  $this->mTitleProtection = null;
2986  }
2987 
2993  static function purgeExpiredRestrictions() {
2994  if ( wfReadOnly() ) {
2995  return;
2996  }
2997 
2999  wfGetDB( DB_MASTER ),
3000  __METHOD__,
3001  function ( IDatabase $dbw, $fname ) {
3002  $config = MediaWikiServices::getInstance()->getMainConfig();
3003  $ids = $dbw->selectFieldValues(
3004  'page_restrictions',
3005  'pr_id',
3006  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3007  $fname,
3008  [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
3009  );
3010  if ( $ids ) {
3011  $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
3012  }
3013  }
3014  ) );
3015 
3017  wfGetDB( DB_MASTER ),
3018  __METHOD__,
3019  function ( IDatabase $dbw, $fname ) {
3020  $dbw->delete(
3021  'protected_titles',
3022  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3023  $fname
3024  );
3025  }
3026  ) );
3027  }
3028 
3034  public function hasSubpages() {
3035  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3036  # Duh
3037  return false;
3038  }
3039 
3040  # We dynamically add a member variable for the purpose of this method
3041  # alone to cache the result. There's no point in having it hanging
3042  # around uninitialized in every Title object; therefore we only add it
3043  # if needed and don't declare it statically.
3044  if ( $this->mHasSubpages === null ) {
3045  $this->mHasSubpages = false;
3046  $subpages = $this->getSubpages( 1 );
3047  if ( $subpages instanceof TitleArray ) {
3048  $this->mHasSubpages = (bool)$subpages->count();
3049  }
3050  }
3051 
3052  return $this->mHasSubpages;
3053  }
3054 
3062  public function getSubpages( $limit = -1 ) {
3063  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3064  return [];
3065  }
3066 
3067  $dbr = wfGetDB( DB_SLAVE );
3068  $conds['page_namespace'] = $this->getNamespace();
3069  $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3070  $options = [];
3071  if ( $limit > -1 ) {
3072  $options['LIMIT'] = $limit;
3073  }
3074  $this->mSubpages = TitleArray::newFromResult(
3075  $dbr->select( 'page',
3076  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3077  $conds,
3078  __METHOD__,
3079  $options
3080  )
3081  );
3082  return $this->mSubpages;
3083  }
3084 
3090  public function isDeleted() {
3091  if ( $this->getNamespace() < 0 ) {
3092  $n = 0;
3093  } else {
3094  $dbr = wfGetDB( DB_SLAVE );
3095 
3096  $n = $dbr->selectField( 'archive', 'COUNT(*)',
3097  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3098  __METHOD__
3099  );
3100  if ( $this->getNamespace() == NS_FILE ) {
3101  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3102  [ 'fa_name' => $this->getDBkey() ],
3103  __METHOD__
3104  );
3105  }
3106  }
3107  return (int)$n;
3108  }
3109 
3115  public function isDeletedQuick() {
3116  if ( $this->getNamespace() < 0 ) {
3117  return false;
3118  }
3119  $dbr = wfGetDB( DB_SLAVE );
3120  $deleted = (bool)$dbr->selectField( 'archive', '1',
3121  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3122  __METHOD__
3123  );
3124  if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3125  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3126  [ 'fa_name' => $this->getDBkey() ],
3127  __METHOD__
3128  );
3129  }
3130  return $deleted;
3131  }
3132 
3141  public function getArticleID( $flags = 0 ) {
3142  if ( $this->getNamespace() < 0 ) {
3143  $this->mArticleID = 0;
3144  return $this->mArticleID;
3145  }
3146  $linkCache = LinkCache::singleton();
3147  if ( $flags & self::GAID_FOR_UPDATE ) {
3148  $oldUpdate = $linkCache->forUpdate( true );
3149  $linkCache->clearLink( $this );
3150  $this->mArticleID = $linkCache->addLinkObj( $this );
3151  $linkCache->forUpdate( $oldUpdate );
3152  } else {
3153  if ( -1 == $this->mArticleID ) {
3154  $this->mArticleID = $linkCache->addLinkObj( $this );
3155  }
3156  }
3157  return $this->mArticleID;
3158  }
3159 
3167  public function isRedirect( $flags = 0 ) {
3168  if ( !is_null( $this->mRedirect ) ) {
3169  return $this->mRedirect;
3170  }
3171  if ( !$this->getArticleID( $flags ) ) {
3172  $this->mRedirect = false;
3173  return $this->mRedirect;
3174  }
3175 
3176  $linkCache = LinkCache::singleton();
3177  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3178  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3179  if ( $cached === null ) {
3180  # Trust LinkCache's state over our own
3181  # LinkCache is telling us that the page doesn't exist, despite there being cached
3182  # data relating to an existing page in $this->mArticleID. Updaters should clear
3183  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3184  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3185  # LinkCache to refresh its data from the master.
3186  $this->mRedirect = false;
3187  return $this->mRedirect;
3188  }
3189 
3190  $this->mRedirect = (bool)$cached;
3191 
3192  return $this->mRedirect;
3193  }
3194 
3202  public function getLength( $flags = 0 ) {
3203  if ( $this->mLength != -1 ) {
3204  return $this->mLength;
3205  }
3206  if ( !$this->getArticleID( $flags ) ) {
3207  $this->mLength = 0;
3208  return $this->mLength;
3209  }
3210  $linkCache = LinkCache::singleton();
3211  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3212  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3213  if ( $cached === null ) {
3214  # Trust LinkCache's state over our own, as for isRedirect()
3215  $this->mLength = 0;
3216  return $this->mLength;
3217  }
3218 
3219  $this->mLength = intval( $cached );
3220 
3221  return $this->mLength;
3222  }
3223 
3230  public function getLatestRevID( $flags = 0 ) {
3231  if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3232  return intval( $this->mLatestID );
3233  }
3234  if ( !$this->getArticleID( $flags ) ) {
3235  $this->mLatestID = 0;
3236  return $this->mLatestID;
3237  }
3238  $linkCache = LinkCache::singleton();
3239  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3240  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3241  if ( $cached === null ) {
3242  # Trust LinkCache's state over our own, as for isRedirect()
3243  $this->mLatestID = 0;
3244  return $this->mLatestID;
3245  }
3246 
3247  $this->mLatestID = intval( $cached );
3248 
3249  return $this->mLatestID;
3250  }
3251 
3262  public function resetArticleID( $newid ) {
3263  $linkCache = LinkCache::singleton();
3264  $linkCache->clearLink( $this );
3265 
3266  if ( $newid === false ) {
3267  $this->mArticleID = -1;
3268  } else {
3269  $this->mArticleID = intval( $newid );
3270  }
3271  $this->mRestrictionsLoaded = false;
3272  $this->mRestrictions = [];
3273  $this->mOldRestrictions = false;
3274  $this->mRedirect = null;
3275  $this->mLength = -1;
3276  $this->mLatestID = false;
3277  $this->mContentModel = false;
3278  $this->mEstimateRevisions = null;
3279  $this->mPageLanguage = false;
3280  $this->mDbPageLanguage = false;
3281  $this->mIsBigDeletion = null;
3282  }
3283 
3284  public static function clearCaches() {
3285  $linkCache = LinkCache::singleton();
3286  $linkCache->clear();
3287 
3288  $titleCache = self::getTitleCache();
3289  $titleCache->clear();
3290  }
3291 
3299  public static function capitalize( $text, $ns = NS_MAIN ) {
3301 
3302  if ( MWNamespace::isCapitalized( $ns ) ) {
3303  return $wgContLang->ucfirst( $text );
3304  } else {
3305  return $text;
3306  }
3307  }
3308 
3321  private function secureAndSplit() {
3322  # Initialisation
3323  $this->mInterwiki = '';
3324  $this->mFragment = '';
3325  $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3326 
3327  $dbkey = $this->mDbkeyform;
3328 
3329  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3330  // the parsing code with Title, while avoiding massive refactoring.
3331  // @todo: get rid of secureAndSplit, refactor parsing code.
3332  // @note: getTitleParser() returns a TitleParser implementation which does not have a
3333  // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3334  $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3335  // MalformedTitleException can be thrown here
3336  $parts = $titleCodec->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3337 
3338  # Fill fields
3339  $this->setFragment( '#' . $parts['fragment'] );
3340  $this->mInterwiki = $parts['interwiki'];
3341  $this->mLocalInterwiki = $parts['local_interwiki'];
3342  $this->mNamespace = $parts['namespace'];
3343  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3344 
3345  $this->mDbkeyform = $parts['dbkey'];
3346  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3347  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3348 
3349  # We already know that some pages won't be in the database!
3350  if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3351  $this->mArticleID = 0;
3352  }
3353 
3354  return true;
3355  }
3356 
3369  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3370  if ( count( $options ) > 0 ) {
3371  $db = wfGetDB( DB_MASTER );
3372  } else {
3373  $db = wfGetDB( DB_SLAVE );
3374  }
3375 
3376  $res = $db->select(
3377  [ 'page', $table ],
3378  self::getSelectFields(),
3379  [
3380  "{$prefix}_from=page_id",
3381  "{$prefix}_namespace" => $this->getNamespace(),
3382  "{$prefix}_title" => $this->getDBkey() ],
3383  __METHOD__,
3384  $options
3385  );
3386 
3387  $retVal = [];
3388  if ( $res->numRows() ) {
3389  $linkCache = LinkCache::singleton();
3390  foreach ( $res as $row ) {
3391  $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3392  if ( $titleObj ) {
3393  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3394  $retVal[] = $titleObj;
3395  }
3396  }
3397  }
3398  return $retVal;
3399  }
3400 
3411  public function getTemplateLinksTo( $options = [] ) {
3412  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3413  }
3414 
3427  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3428  $id = $this->getArticleID();
3429 
3430  # If the page doesn't exist; there can't be any link from this page
3431  if ( !$id ) {
3432  return [];
3433  }
3434 
3435  $db = wfGetDB( DB_SLAVE );
3436 
3437  $blNamespace = "{$prefix}_namespace";
3438  $blTitle = "{$prefix}_title";
3439 
3440  $res = $db->select(
3441  [ $table, 'page' ],
3442  array_merge(
3443  [ $blNamespace, $blTitle ],
3445  ),
3446  [ "{$prefix}_from" => $id ],
3447  __METHOD__,
3448  $options,
3449  [ 'page' => [
3450  'LEFT JOIN',
3451  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3452  ] ]
3453  );
3454 
3455  $retVal = [];
3456  $linkCache = LinkCache::singleton();
3457  foreach ( $res as $row ) {
3458  if ( $row->page_id ) {
3459  $titleObj = Title::newFromRow( $row );
3460  } else {
3461  $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3462  $linkCache->addBadLinkObj( $titleObj );
3463  }
3464  $retVal[] = $titleObj;
3465  }
3466 
3467  return $retVal;
3468  }
3469 
3480  public function getTemplateLinksFrom( $options = [] ) {
3481  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3482  }
3483 
3492  public function getBrokenLinksFrom() {
3493  if ( $this->getArticleID() == 0 ) {
3494  # All links from article ID 0 are false positives
3495  return [];
3496  }
3497 
3498  $dbr = wfGetDB( DB_SLAVE );
3499  $res = $dbr->select(
3500  [ 'page', 'pagelinks' ],
3501  [ 'pl_namespace', 'pl_title' ],
3502  [
3503  'pl_from' => $this->getArticleID(),
3504  'page_namespace IS NULL'
3505  ],
3506  __METHOD__, [],
3507  [
3508  'page' => [
3509  'LEFT JOIN',
3510  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3511  ]
3512  ]
3513  );
3514 
3515  $retVal = [];
3516  foreach ( $res as $row ) {
3517  $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3518  }
3519  return $retVal;
3520  }
3521 
3528  public function getCdnUrls() {
3529  $urls = [
3530  $this->getInternalURL(),
3531  $this->getInternalURL( 'action=history' )
3532  ];
3533 
3534  $pageLang = $this->getPageLanguage();
3535  if ( $pageLang->hasVariants() ) {
3536  $variants = $pageLang->getVariants();
3537  foreach ( $variants as $vCode ) {
3538  $urls[] = $this->getInternalURL( $vCode );
3539  }
3540  }
3541 
3542  // If we are looking at a css/js user subpage, purge the action=raw.
3543  if ( $this->isJsSubpage() ) {
3544  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3545  } elseif ( $this->isCssSubpage() ) {
3546  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3547  }
3548 
3549  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3550  return $urls;
3551  }
3552 
3556  public function getSquidURLs() {
3557  return $this->getCdnUrls();
3558  }
3559 
3563  public function purgeSquid() {
3565  new CdnCacheUpdate( $this->getCdnUrls() ),
3567  );
3568  }
3569 
3577  public function moveNoAuth( &$nt ) {
3578  wfDeprecated( __METHOD__, '1.25' );
3579  return $this->moveTo( $nt, false );
3580  }
3581 
3592  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3593  global $wgUser;
3594 
3595  if ( !( $nt instanceof Title ) ) {
3596  // Normally we'd add this to $errors, but we'll get
3597  // lots of syntax errors if $nt is not an object
3598  return [ [ 'badtitletext' ] ];
3599  }
3600 
3601  $mp = new MovePage( $this, $nt );
3602  $errors = $mp->isValidMove()->getErrorsArray();
3603  if ( $auth ) {
3604  $errors = wfMergeErrorArrays(
3605  $errors,
3606  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3607  );
3608  }
3609 
3610  return $errors ?: true;
3611  }
3612 
3619  protected function validateFileMoveOperation( $nt ) {
3620  global $wgUser;
3621 
3622  $errors = [];
3623 
3624  $destFile = wfLocalFile( $nt );
3625  $destFile->load( File::READ_LATEST );
3626  if ( !$wgUser->isAllowed( 'reupload-shared' )
3627  && !$destFile->exists() && wfFindFile( $nt )
3628  ) {
3629  $errors[] = [ 'file-exists-sharedrepo' ];
3630  }
3631 
3632  return $errors;
3633  }
3634 
3647  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
3648  global $wgUser;
3649  $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3650  if ( is_array( $err ) ) {
3651  // Auto-block user's IP if the account was "hard" blocked
3652  $wgUser->spreadAnyEditBlock();
3653  return $err;
3654  }
3655  // Check suppressredirect permission
3656  if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3657  $createRedirect = true;
3658  }
3659 
3660  $mp = new MovePage( $this, $nt );
3661  $status = $mp->move( $wgUser, $reason, $createRedirect );
3662  if ( $status->isOK() ) {
3663  return true;
3664  } else {
3665  return $status->getErrorsArray();
3666  }
3667  }
3668 
3681  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
3683  // Check permissions
3684  if ( !$this->userCan( 'move-subpages' ) ) {
3685  return [ 'cant-move-subpages' ];
3686  }
3687  // Do the source and target namespaces support subpages?
3688  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3689  return [ 'namespace-nosubpages',
3691  }
3692  if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3693  return [ 'namespace-nosubpages',
3694  MWNamespace::getCanonicalName( $nt->getNamespace() ) ];
3695  }
3696 
3697  $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3698  $retval = [];
3699  $count = 0;
3700  foreach ( $subpages as $oldSubpage ) {
3701  $count++;
3702  if ( $count > $wgMaximumMovedPages ) {
3703  $retval[$oldSubpage->getPrefixedText()] =
3704  [ 'movepage-max-pages',
3706  break;
3707  }
3708 
3709  // We don't know whether this function was called before
3710  // or after moving the root page, so check both
3711  // $this and $nt
3712  if ( $oldSubpage->getArticleID() == $this->getArticleID()
3713  || $oldSubpage->getArticleID() == $nt->getArticleID()
3714  ) {
3715  // When moving a page to a subpage of itself,
3716  // don't move it twice
3717  continue;
3718  }
3719  $newPageName = preg_replace(
3720  '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3721  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
3722  $oldSubpage->getDBkey() );
3723  if ( $oldSubpage->isTalkPage() ) {
3724  $newNs = $nt->getTalkPage()->getNamespace();
3725  } else {
3726  $newNs = $nt->getSubjectPage()->getNamespace();
3727  }
3728  # Bug 14385: we need makeTitleSafe because the new page names may
3729  # be longer than 255 characters.
3730  $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3731 
3732  $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
3733  if ( $success === true ) {
3734  $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3735  } else {
3736  $retval[$oldSubpage->getPrefixedText()] = $success;
3737  }
3738  }
3739  return $retval;
3740  }
3741 
3748  public function isSingleRevRedirect() {
3750 
3751  $dbw = wfGetDB( DB_MASTER );
3752 
3753  # Is it a redirect?
3754  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3755  if ( $wgContentHandlerUseDB ) {
3756  $fields[] = 'page_content_model';
3757  }
3758 
3759  $row = $dbw->selectRow( 'page',
3760  $fields,
3761  $this->pageCond(),
3762  __METHOD__,
3763  [ 'FOR UPDATE' ]
3764  );
3765  # Cache some fields we may want
3766  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3767  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3768  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3769  $this->mContentModel = $row && isset( $row->page_content_model )
3770  ? strval( $row->page_content_model )
3771  : false;
3772 
3773  if ( !$this->mRedirect ) {
3774  return false;
3775  }
3776  # Does the article have a history?
3777  $row = $dbw->selectField( [ 'page', 'revision' ],
3778  'rev_id',
3779  [ 'page_namespace' => $this->getNamespace(),
3780  'page_title' => $this->getDBkey(),
3781  'page_id=rev_page',
3782  'page_latest != rev_id'
3783  ],
3784  __METHOD__,
3785  [ 'FOR UPDATE' ]
3786  );
3787  # Return true if there was no history
3788  return ( $row === false );
3789  }
3790 
3799  public function isValidMoveTarget( $nt ) {
3800  # Is it an existing file?
3801  if ( $nt->getNamespace() == NS_FILE ) {
3802  $file = wfLocalFile( $nt );
3803  $file->load( File::READ_LATEST );
3804  if ( $file->exists() ) {
3805  wfDebug( __METHOD__ . ": file exists\n" );
3806  return false;
3807  }
3808  }
3809  # Is it a redirect with no history?
3810  if ( !$nt->isSingleRevRedirect() ) {
3811  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3812  return false;
3813  }
3814  # Get the article text
3816  if ( !is_object( $rev ) ) {
3817  return false;
3818  }
3819  $content = $rev->getContent();
3820  # Does the redirect point to the source?
3821  # Or is it a broken self-redirect, usually caused by namespace collisions?
3822  $redirTitle = $content ? $content->getRedirectTarget() : null;
3823 
3824  if ( $redirTitle ) {
3825  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3826  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3827  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3828  return false;
3829  } else {
3830  return true;
3831  }
3832  } else {
3833  # Fail safe (not a redirect after all. strange.)
3834  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3835  " is a redirect, but it doesn't contain a valid redirect.\n" );
3836  return false;
3837  }
3838  }
3839 
3847  public function getParentCategories() {
3849 
3850  $data = [];
3851 
3852  $titleKey = $this->getArticleID();
3853 
3854  if ( $titleKey === 0 ) {
3855  return $data;
3856  }
3857 
3858  $dbr = wfGetDB( DB_SLAVE );
3859 
3860  $res = $dbr->select(
3861  'categorylinks',
3862  'cl_to',
3863  [ 'cl_from' => $titleKey ],
3864  __METHOD__
3865  );
3866 
3867  if ( $res->numRows() > 0 ) {
3868  foreach ( $res as $row ) {
3869  // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3870  $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3871  }
3872  }
3873  return $data;
3874  }
3875 
3882  public function getParentCategoryTree( $children = [] ) {
3883  $stack = [];
3884  $parents = $this->getParentCategories();
3885 
3886  if ( $parents ) {
3887  foreach ( $parents as $parent => $current ) {
3888  if ( array_key_exists( $parent, $children ) ) {
3889  # Circular reference
3890  $stack[$parent] = [];
3891  } else {
3892  $nt = Title::newFromText( $parent );
3893  if ( $nt ) {
3894  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3895  }
3896  }
3897  }
3898  }
3899 
3900  return $stack;
3901  }
3902 
3909  public function pageCond() {
3910  if ( $this->mArticleID > 0 ) {
3911  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3912  return [ 'page_id' => $this->mArticleID ];
3913  } else {
3914  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3915  }
3916  }
3917 
3925  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3926  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3927  $revId = $db->selectField( 'revision', 'rev_id',
3928  [
3929  'rev_page' => $this->getArticleID( $flags ),
3930  'rev_id < ' . intval( $revId )
3931  ],
3932  __METHOD__,
3933  [ 'ORDER BY' => 'rev_id DESC' ]
3934  );
3935 
3936  if ( $revId === false ) {
3937  return false;
3938  } else {
3939  return intval( $revId );
3940  }
3941  }
3942 
3950  public function getNextRevisionID( $revId, $flags = 0 ) {
3951  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3952  $revId = $db->selectField( 'revision', 'rev_id',
3953  [
3954  'rev_page' => $this->getArticleID( $flags ),
3955  'rev_id > ' . intval( $revId )
3956  ],
3957  __METHOD__,
3958  [ 'ORDER BY' => 'rev_id' ]
3959  );
3960 
3961  if ( $revId === false ) {
3962  return false;
3963  } else {
3964  return intval( $revId );
3965  }
3966  }
3967 
3974  public function getFirstRevision( $flags = 0 ) {
3975  $pageId = $this->getArticleID( $flags );
3976  if ( $pageId ) {
3977  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3978  $row = $db->selectRow( 'revision', Revision::selectFields(),
3979  [ 'rev_page' => $pageId ],
3980  __METHOD__,
3981  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ]
3982  );
3983  if ( $row ) {
3984  return new Revision( $row );
3985  }
3986  }
3987  return null;
3988  }
3989 
3996  public function getEarliestRevTime( $flags = 0 ) {
3997  $rev = $this->getFirstRevision( $flags );
3998  return $rev ? $rev->getTimestamp() : null;
3999  }
4000 
4006  public function isNewPage() {
4007  $dbr = wfGetDB( DB_SLAVE );
4008  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4009  }
4010 
4016  public function isBigDeletion() {
4018 
4019  if ( !$wgDeleteRevisionsLimit ) {
4020  return false;
4021  }
4022 
4023  if ( $this->mIsBigDeletion === null ) {
4024  $dbr = wfGetDB( DB_SLAVE );
4025 
4026  $revCount = $dbr->selectRowCount(
4027  'revision',
4028  '1',
4029  [ 'rev_page' => $this->getArticleID() ],
4030  __METHOD__,
4031  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4032  );
4033 
4034  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4035  }
4036 
4037  return $this->mIsBigDeletion;
4038  }
4039 
4045  public function estimateRevisionCount() {
4046  if ( !$this->exists() ) {
4047  return 0;
4048  }
4049 
4050  if ( $this->mEstimateRevisions === null ) {
4051  $dbr = wfGetDB( DB_SLAVE );
4052  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4053  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4054  }
4055 
4057  }
4058 
4068  public function countRevisionsBetween( $old, $new, $max = null ) {
4069  if ( !( $old instanceof Revision ) ) {
4070  $old = Revision::newFromTitle( $this, (int)$old );
4071  }
4072  if ( !( $new instanceof Revision ) ) {
4073  $new = Revision::newFromTitle( $this, (int)$new );
4074  }
4075  if ( !$old || !$new ) {
4076  return 0; // nothing to compare
4077  }
4078  $dbr = wfGetDB( DB_SLAVE );
4079  $conds = [
4080  'rev_page' => $this->getArticleID(),
4081  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4082  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4083  ];
4084  if ( $max !== null ) {
4085  return $dbr->selectRowCount( 'revision', '1',
4086  $conds,
4087  __METHOD__,
4088  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4089  );
4090  } else {
4091  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4092  }
4093  }
4094 
4111  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4112  if ( !( $old instanceof Revision ) ) {
4113  $old = Revision::newFromTitle( $this, (int)$old );
4114  }
4115  if ( !( $new instanceof Revision ) ) {
4116  $new = Revision::newFromTitle( $this, (int)$new );
4117  }
4118  // XXX: what if Revision objects are passed in, but they don't refer to this title?
4119  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4120  // in the sanity check below?
4121  if ( !$old || !$new ) {
4122  return null; // nothing to compare
4123  }
4124  $authors = [];
4125  $old_cmp = '>';
4126  $new_cmp = '<';
4127  $options = (array)$options;
4128  if ( in_array( 'include_old', $options ) ) {
4129  $old_cmp = '>=';
4130  }
4131  if ( in_array( 'include_new', $options ) ) {
4132  $new_cmp = '<=';
4133  }
4134  if ( in_array( 'include_both', $options ) ) {
4135  $old_cmp = '>=';
4136  $new_cmp = '<=';
4137  }
4138  // No DB query needed if $old and $new are the same or successive revisions:
4139  if ( $old->getId() === $new->getId() ) {
4140  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4141  [] :
4142  [ $old->getUserText( Revision::RAW ) ];
4143  } elseif ( $old->getId() === $new->getParentId() ) {
4144  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4145  $authors[] = $old->getUserText( Revision::RAW );
4146  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4147  $authors[] = $new->getUserText( Revision::RAW );
4148  }
4149  } elseif ( $old_cmp === '>=' ) {
4150  $authors[] = $old->getUserText( Revision::RAW );
4151  } elseif ( $new_cmp === '<=' ) {
4152  $authors[] = $new->getUserText( Revision::RAW );
4153  }
4154  return $authors;
4155  }
4156  $dbr = wfGetDB( DB_SLAVE );
4157  $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4158  [
4159  'rev_page' => $this->getArticleID(),
4160  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4161  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4162  ], __METHOD__,
4163  [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
4164  );
4165  foreach ( $res as $row ) {
4166  $authors[] = $row->rev_user_text;
4167  }
4168  return $authors;
4169  }
4170 
4185  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4186  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4187  return $authors ? count( $authors ) : 0;
4188  }
4189 
4196  public function equals( Title $title ) {
4197  // Note: === is necessary for proper matching of number-like titles.
4198  return $this->getInterwiki() === $title->getInterwiki()
4199  && $this->getNamespace() == $title->getNamespace()
4200  && $this->getDBkey() === $title->getDBkey();
4201  }
4202 
4209  public function isSubpageOf( Title $title ) {
4210  return $this->getInterwiki() === $title->getInterwiki()
4211  && $this->getNamespace() == $title->getNamespace()
4212  && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4213  }
4214 
4226  public function exists( $flags = 0 ) {
4227  $exists = $this->getArticleID( $flags ) != 0;
4228  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4229  return $exists;
4230  }
4231 
4248  public function isAlwaysKnown() {
4249  $isKnown = null;
4250 
4261  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4262 
4263  if ( !is_null( $isKnown ) ) {
4264  return $isKnown;
4265  }
4266 
4267  if ( $this->isExternal() ) {
4268  return true; // any interwiki link might be viewable, for all we know
4269  }
4270 
4271  switch ( $this->mNamespace ) {
4272  case NS_MEDIA:
4273  case NS_FILE:
4274  // file exists, possibly in a foreign repo
4275  return (bool)wfFindFile( $this );
4276  case NS_SPECIAL:
4277  // valid special page
4278  return SpecialPageFactory::exists( $this->getDBkey() );
4279  case NS_MAIN:
4280  // selflink, possibly with fragment
4281  return $this->mDbkeyform == '';
4282  case NS_MEDIAWIKI:
4283  // known system message
4284  return $this->hasSourceText() !== false;
4285  default:
4286  return false;
4287  }
4288  }
4289 
4301  public function isKnown() {
4302  return $this->isAlwaysKnown() || $this->exists();
4303  }
4304 
4310  public function hasSourceText() {
4311  if ( $this->exists() ) {
4312  return true;
4313  }
4314 
4315  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4316  // If the page doesn't exist but is a known system message, default
4317  // message content will be displayed, same for language subpages-
4318  // Use always content language to avoid loading hundreds of languages
4319  // to get the link color.
4321  list( $name, ) = MessageCache::singleton()->figureMessage(
4322  $wgContLang->lcfirst( $this->getText() )
4323  );
4324  $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4325  return $message->exists();
4326  }
4327 
4328  return false;
4329  }
4330 
4336  public function getDefaultMessageText() {
4338 
4339  if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4340  return false;
4341  }
4342 
4343  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4344  $wgContLang->lcfirst( $this->getText() )
4345  );
4346  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4347 
4348  if ( $message->exists() ) {
4349  return $message->plain();
4350  } else {
4351  return false;
4352  }
4353  }
4354 
4361  public function invalidateCache( $purgeTime = null ) {
4362  if ( wfReadOnly() ) {
4363  return false;
4364  }
4365 
4366  if ( $this->mArticleID === 0 ) {
4367  return true; // avoid gap locking if we know it's not there
4368  }
4369 
4370  $conds = $this->pageCond();
4372  new AutoCommitUpdate(
4373  wfGetDB( DB_MASTER ),
4374  __METHOD__,
4375  function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4376  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4377  $dbw->update(
4378  'page',
4379  [ 'page_touched' => $dbTimestamp ],
4380  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4381  $fname
4382  );
4383  }
4384  ),
4386  );
4387 
4388  return true;
4389  }
4390 
4396  public function touchLinks() {
4397  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4398  if ( $this->getNamespace() == NS_CATEGORY ) {
4399  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4400  }
4401  }
4402 
4409  public function getTouched( $db = null ) {
4410  if ( $db === null ) {
4411  $db = wfGetDB( DB_SLAVE );
4412  }
4413  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4414  return $touched;
4415  }
4416 
4423  public function getNotificationTimestamp( $user = null ) {
4424  global $wgUser;
4425 
4426  // Assume current user if none given
4427  if ( !$user ) {
4428  $user = $wgUser;
4429  }
4430  // Check cache first
4431  $uid = $user->getId();
4432  if ( !$uid ) {
4433  return false;
4434  }
4435  // avoid isset here, as it'll return false for null entries
4436  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4437  return $this->mNotificationTimestamp[$uid];
4438  }
4439  // Don't cache too much!
4440  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4441  $this->mNotificationTimestamp = [];
4442  }
4443 
4444  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4445  $watchedItem = $store->getWatchedItem( $user, $this );
4446  if ( $watchedItem ) {
4447  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4448  } else {
4449  $this->mNotificationTimestamp[$uid] = false;
4450  }
4451 
4452  return $this->mNotificationTimestamp[$uid];
4453  }
4454 
4461  public function getNamespaceKey( $prepend = 'nstab-' ) {
4463  // Gets the subject namespace if this title
4464  $namespace = MWNamespace::getSubject( $this->getNamespace() );
4465  // Checks if canonical namespace name exists for namespace
4466  if ( MWNamespace::exists( $this->getNamespace() ) ) {
4467  // Uses canonical namespace name
4468  $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4469  } else {
4470  // Uses text of namespace
4471  $namespaceKey = $this->getSubjectNsText();
4472  }
4473  // Makes namespace key lowercase
4474  $namespaceKey = $wgContLang->lc( $namespaceKey );
4475  // Uses main
4476  if ( $namespaceKey == '' ) {
4477  $namespaceKey = 'main';
4478  }
4479  // Changes file to image for backwards compatibility
4480  if ( $namespaceKey == 'file' ) {
4481  $namespaceKey = 'image';
4482  }
4483  return $prepend . $namespaceKey;
4484  }
4485 
4492  public function getRedirectsHere( $ns = null ) {
4493  $redirs = [];
4494 
4495  $dbr = wfGetDB( DB_SLAVE );
4496  $where = [
4497  'rd_namespace' => $this->getNamespace(),
4498  'rd_title' => $this->getDBkey(),
4499  'rd_from = page_id'
4500  ];
4501  if ( $this->isExternal() ) {
4502  $where['rd_interwiki'] = $this->getInterwiki();
4503  } else {
4504  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4505  }
4506  if ( !is_null( $ns ) ) {
4507  $where['page_namespace'] = $ns;
4508  }
4509 
4510  $res = $dbr->select(
4511  [ 'redirect', 'page' ],
4512  [ 'page_namespace', 'page_title' ],
4513  $where,
4514  __METHOD__
4515  );
4516 
4517  foreach ( $res as $row ) {
4518  $redirs[] = self::newFromRow( $row );
4519  }
4520  return $redirs;
4521  }
4522 
4528  public function isValidRedirectTarget() {
4529  global $wgInvalidRedirectTargets;
4530 
4531  if ( $this->isSpecialPage() ) {
4532  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4533  if ( $this->isSpecial( 'Userlogout' ) ) {
4534  return false;
4535  }
4536 
4537  foreach ( $wgInvalidRedirectTargets as $target ) {
4538  if ( $this->isSpecial( $target ) ) {
4539  return false;
4540  }
4541  }
4542  }
4543 
4544  return true;
4545  }
4546 
4552  public function getBacklinkCache() {
4553  return BacklinkCache::get( $this );
4554  }
4555 
4561  public function canUseNoindex() {
4563 
4564  $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4567 
4568  return !in_array( $this->mNamespace, $bannedNamespaces );
4569 
4570  }
4571 
4582  public function getCategorySortkey( $prefix = '' ) {
4583  $unprefixed = $this->getText();
4584 
4585  // Anything that uses this hook should only depend
4586  // on the Title object passed in, and should probably
4587  // tell the users to run updateCollations.php --force
4588  // in order to re-sort existing category relations.
4589  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4590  if ( $prefix !== '' ) {
4591  # Separate with a line feed, so the unprefixed part is only used as
4592  # a tiebreaker when two pages have the exact same prefix.
4593  # In UCA, tab is the only character that can sort above LF
4594  # so we strip both of them from the original prefix.
4595  $prefix = strtr( $prefix, "\n\t", ' ' );
4596  return "$prefix\n$unprefixed";
4597  }
4598  return $unprefixed;
4599  }
4600 
4608  private function getDbPageLanguageCode() {
4610 
4611  // check, if the page language could be saved in the database, and if so and
4612  // the value is not requested already, lookup the page language using LinkCache
4613  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4614  $linkCache = LinkCache::singleton();
4615  $linkCache->addLinkObj( $this );
4616  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4617  }
4618 
4619  return $this->mDbPageLanguage;
4620  }
4621 
4630  public function getPageLanguage() {
4632  if ( $this->isSpecialPage() ) {
4633  // special pages are in the user language
4634  return $wgLang;
4635  }
4636 
4637  // Checking if DB language is set
4638  $dbPageLanguage = $this->getDbPageLanguageCode();
4639  if ( $dbPageLanguage ) {
4640  return wfGetLangObj( $dbPageLanguage );
4641  }
4642 
4643  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4644  // Note that this may depend on user settings, so the cache should
4645  // be only per-request.
4646  // NOTE: ContentHandler::getPageLanguage() may need to load the
4647  // content to determine the page language!
4648  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4649  // tests.
4650  $contentHandler = ContentHandler::getForTitle( $this );
4651  $langObj = $contentHandler->getPageLanguage( $this );
4652  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4653  } else {
4654  $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4655  }
4656 
4657  return $langObj;
4658  }
4659 
4668  public function getPageViewLanguage() {
4669  global $wgLang;
4670 
4671  if ( $this->isSpecialPage() ) {
4672  // If the user chooses a variant, the content is actually
4673  // in a language whose code is the variant code.
4674  $variant = $wgLang->getPreferredVariant();
4675  if ( $wgLang->getCode() !== $variant ) {
4676  return Language::factory( $variant );
4677  }
4678 
4679  return $wgLang;
4680  }
4681 
4682  // Checking if DB language is set
4683  $dbPageLanguage = $this->getDbPageLanguageCode();
4684  if ( $dbPageLanguage ) {
4685  $pageLang = wfGetLangObj( $dbPageLanguage );
4686  $variant = $pageLang->getPreferredVariant();
4687  if ( $pageLang->getCode() !== $variant ) {
4688  $pageLang = Language::factory( $variant );
4689  }
4690 
4691  return $pageLang;
4692  }
4693 
4694  // @note Can't be cached persistently, depends on user settings.
4695  // @note ContentHandler::getPageViewLanguage() may need to load the
4696  // content to determine the page language!
4697  $contentHandler = ContentHandler::getForTitle( $this );
4698  $pageLang = $contentHandler->getPageViewLanguage( $this );
4699  return $pageLang;
4700  }
4701 
4712  public function getEditNotices( $oldid = 0 ) {
4713  $notices = [];
4714 
4715  // Optional notice for the entire namespace
4716  $editnotice_ns = 'editnotice-' . $this->getNamespace();
4717  $msg = wfMessage( $editnotice_ns );
4718  if ( $msg->exists() ) {
4719  $html = $msg->parseAsBlock();
4720  // Edit notices may have complex logic, but output nothing (T91715)
4721  if ( trim( $html ) !== '' ) {
4722  $notices[$editnotice_ns] = Html::rawElement(
4723  'div',
4724  [ 'class' => [
4725  'mw-editnotice',
4726  'mw-editnotice-namespace',
4727  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4728  ] ],
4729  $html
4730  );
4731  }
4732  }
4733 
4734  if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4735  // Optional notice for page itself and any parent page
4736  $parts = explode( '/', $this->getDBkey() );
4737  $editnotice_base = $editnotice_ns;
4738  while ( count( $parts ) > 0 ) {
4739  $editnotice_base .= '-' . array_shift( $parts );
4740  $msg = wfMessage( $editnotice_base );
4741  if ( $msg->exists() ) {
4742  $html = $msg->parseAsBlock();
4743  if ( trim( $html ) !== '' ) {
4744  $notices[$editnotice_base] = Html::rawElement(
4745  'div',
4746  [ 'class' => [
4747  'mw-editnotice',
4748  'mw-editnotice-base',
4749  Sanitizer::escapeClass( "mw-$editnotice_base" )
4750  ] ],
4751  $html
4752  );
4753  }
4754  }
4755  }
4756  } else {
4757  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4758  $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4759  $msg = wfMessage( $editnoticeText );
4760  if ( $msg->exists() ) {
4761  $html = $msg->parseAsBlock();
4762  if ( trim( $html ) !== '' ) {
4763  $notices[$editnoticeText] = Html::rawElement(
4764  'div',
4765  [ 'class' => [
4766  'mw-editnotice',
4767  'mw-editnotice-page',
4768  Sanitizer::escapeClass( "mw-$editnoticeText" )
4769  ] ],
4770  $html
4771  );
4772  }
4773  }
4774  }
4775 
4776  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4777  return $notices;
4778  }
4779 
4783  public function __sleep() {
4784  return [
4785  'mNamespace',
4786  'mDbkeyform',
4787  'mFragment',
4788  'mInterwiki',
4789  'mLocalInterwiki',
4790  'mUserCaseDBKey',
4791  'mDefaultNamespace',
4792  ];
4793  }
4794 
4795  public function __wakeup() {
4796  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4797  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4798  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4799  }
4800 
4801 }
getEarliestRevTime($flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:3996
bool $mHasSubpages
Whether a page has any subpages.
Definition: Title.php:146
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition: Title.php:4248
static newFromRow($row)
Make a Title object from a DB row.
Definition: Title.php:444
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition: Title.php:2993
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3230
setFragment($fragment)
Set the fragment for this title.
Definition: Title.php:1372
static newFromID($id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:396
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Definition: Title.php:599
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:744
static isMovable($index)
Can pages in the given namespace be moved?
Definition: MWNamespace.php:67
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title...
Definition: Title.php:4396
getFragment()
Get the Title fragment (i.e.
Definition: Title.php:1334
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:1816
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
mixed $mTitleProtection
Cached value for getTitleProtection (create protection)
Definition: Title.php:127
exists($flags=0)
Check if page exists.
Definition: Title.php:4226
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable...
Definition: Title.php:1135
getInternalURL($query= '', $query2=false)
Get the URL form for an internal link.
Definition: Title.php:1804
getRootTitle()
Get the root page name title, i.e.
Definition: Title.php:1494
static getLocalNameFor($name, $subpage=false)
Get the local name for a specified canonical name.
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:776
the array() calling protocol came about after MediaWiki 1.4rc1.
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1435
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition: Title.php:4561
isJsSubpage()
Is this a .js subpage of a user page?
Definition: Title.php:1261
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:278
wasLocalInterwiki()
Was this a local interwiki link?
Definition: Title.php:809
getSquidURLs()
Definition: Title.php:3556
$wgScript
The URL path to index.php.
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page...
Definition: Title.php:1125
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
getUserCaseDBKey()
Get the DB key with the initial letter case as specified by the user.
Definition: Title.php:899
Handles the backend logic of moving a page from one title to another.
Definition: MovePage.php:30
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition: Title.php:2648
isSpecial($name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1036
static clearCaches()
Definition: Title.php:3284
getArticleID($flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3141
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:3034
const NS_MAIN
Definition: Defines.php:69
$success
static nameOf($id)
Get the prefixed DB key associated with an ID.
Definition: Title.php:563
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:872
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition: Title.php:1549
getBaseText()
Get the base page name without a namespace, i.e.
Definition: Title.php:1509
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
static newMainPage()
Create a new Title for the Main Page.
Definition: Title.php:548
moveSubpages($nt, $auth=true, $reason= '', $createRedirect=true)
Move this page's subpages to be subpages of $nt.
Definition: Title.php:3681
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition: Title.php:4336
getEditNotices($oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:4712
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static getTitleFor($name, $subpage=false, $fragment= '')
Get a localised Title object for a specified special page name.
Definition: SpecialPage.php:80
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition: Title.php:979
__wakeup()
Definition: Title.php:4795
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:2421
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition: Title.php:832
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:210
$wgActionPaths
Definition: img_auth.php:46
isCssJsSubpage()
Is this a .css or .js subpage of a user page?
Definition: Title.php:1225
static canTalk($index)
Can this namespace ever have a talk namespace?
$wgInternalServer
Internal server name as known to CDN, if different.
isWatchable()
Can this title be added to a user's watchlist?
Definition: Title.php:1017
checkUserBlock($action, $user, $errors, $rigor, $short)
Check that the user isn't blocked from editing.
Definition: Title.php:2273
if(!isset($args[0])) $lang
static isTalk($index)
Is the given namespace a talk namespace?
Definition: MWNamespace.php:97
$wgRestrictionLevels
Rights which can be required for each protection level (via action=protect)
hasSubjectNamespace($ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition: Title.php:1114
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition: Title.php:3321
bool $mIsBigDeletion
Would deleting this page be a big deletion?
Definition: Title.php:159
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition: Title.php:1086
moveNoAuth(&$nt)
Move this page without authentication.
Definition: Title.php:3577
isTalkPage()
Is this a talk page of some sort?
Definition: Title.php:1271
Handles purging appropriate CDN URLs given a title (or titles)
string $mUrlform
URL-encoded form of the main part.
Definition: Title.php:64
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition: Title.php:4608
null for the local wiki Added in
Definition: hooks.txt:1435
isRedirect($flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3167
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:36
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:177
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition: Title.php:4016
set($key, $value, $exptime=0, $flags=0)
const NS_SPECIAL
Definition: Defines.php:58
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context $revId
Definition: hooks.txt:1020
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:3480
prefix($name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object. ...
Definition: Title.php:1400
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:1307
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
static escapeFragmentForURL($fragment)
Escape a text fragment, say from a link, for a URL.
Definition: Title.php:743
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2588
static getInterwikiLookup()
B/C kludge: provide an InterwikiLookup for use by Title.
Definition: Title.php:182
isValidMoveTarget($nt)
Checks if $this can be moved to a given Title.
Definition: Title.php:3799
static escapeClass($class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1246
$wgSemiprotectedRestrictionLevels
Restriction levels that should be considered "semiprotected".
$wgEmailConfirmToEdit
Should editors be required to have a validated e-mail address before being allowed to edit...
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1430
static exists($index)
Returns whether the specified namespace exists.
wfUrlencode($s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:256
getBaseTitle()
Get the base page name title, i.e.
Definition: Title.php:1534
Represents a title within MediaWiki.
Definition: Title.php:36
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
wfExpandUrl($url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
getParentCategories()
Get categories to which this Title belongs and return an array of categories' names.
Definition: Title.php:3847
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4552
$wgArticlePath
Definition: img_auth.php:45
timestamp($ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
static newFromLinkTarget(LinkTarget $linkTarget)
Create a new Title from a LinkTarget.
Definition: Title.php:230
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:117
wfLocalFile($title)
Get an object referring to a locally registered file.
The TitleArray class only exists to provide the newFromResult method at pre- sent.
Definition: TitleArray.php:31
checkQuickPermissions($action, $user, $errors, $rigor, $short)
Permissions checks that fail most often, and which are easiest to test.
Definition: Title.php:1927
getSkinFromCssJsSubpage()
Trim down a .css or .js subpage title to get the corresponding skin name.
Definition: Title.php:1236
static getFilteredRestrictionTypes($exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition: Title.php:2481
$wgRestrictionTypes
Set of available actions that can be restricted via action=protect You probably shouldn't change this...
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition: Title.php:1280
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition: hooks.txt:981
getNamespace()
Get the namespace index.
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition: Title.php:118
getNotificationTimestamp($user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition: Title.php:4423
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias...
Definition: Title.php:1052
static isEveryoneAllowed($right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4842
getSubpages($limit=-1)
Get all subpages of this page.
Definition: Title.php:3062
userCan($action, $user=null, $rigor= 'secure')
Can $user perform $action on this page?
Definition: Title.php:1872
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
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message.Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item.Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page.Return false to stop further processing of the tag $reader:XMLReader object &$pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision.Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag.Return false to stop further processing of the tag $reader:XMLReader object '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:Associative array mapping language codes to prefixed links of the form"language:title".&$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:1814
getFragment()
Get the link fragment (i.e.
Deferrable Update for closure/callback updates that should use auto-commit mode.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility...
static isCapitalized($index)
Is the namespace first-letter capitalized?
static get(Title $title)
Create a new BacklinkCache or reuse any existing one.
$wgLanguageCode
Site language code.
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition: Title.php:106
inNamespace($ns)
Returns true if the title is inside the specified namespace.
Definition: Title.php:1075
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition: Title.php:2983
getContentModel($flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:923
getLength($flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition: Title.php:3202
static HashBagOStuff $titleCache
Definition: Title.php:38
string $mDbkeyform
Main part with underscores.
Definition: Title.php:67
getNsText()
Get the namespace text.
Definition: Title.php:954
delete($table, $conds, $fname=__METHOD__)
DELETE query wrapper.
isValidMoveOperation(&$nt, $auth=true, $reason= '')
Check whether a given move operation would be valid.
Definition: Title.php:3592
createFragmentTarget($fragment)
Creates a new Title for a different fragment of the same page.
Definition: Title.php:1383
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
getNextRevisionID($revId, $flags=0)
Get the revision ID of the next revision.
Definition: Title.php:3950
loadRestrictionsFromResultWrapper($res, $oldFashionedRestrictions=null)
Loads a string into mRestrictions array.
Definition: Title.php:2853
__sleep()
Definition: Title.php:4783
string bool $mOldRestrictions
Text form (spaces not underscores) of the main part.
Definition: Title.php:103
__construct()
Definition: Title.php:189
wfReadOnly()
Check whether the wiki is in read-only mode.
isExternal()
Is this Title interwiki?
Definition: Title.php:789
static fixUrlQueryArgs($query, $query2=false)
Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args get{Canonical,Full,Link,Local,Internal}URL methods accepted an optional second argument named variant.
Definition: Title.php:1609
deleteTitleProtection()
Remove any title protection due to page existing.
Definition: Title.php:2570
static getMain()
Static methods.
int $mNamespace
Namespace index, i.e.
Definition: Title.php:73
static groupHasPermission($group, $role)
Check, if the given group has the given permission.
Definition: User.php:4822
static singleton()
Get an instance of this class.
Definition: LinkCache.php:65
wfMergeErrorArrays()
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
static getCanonicalName($index)
Returns the canonical (English) name for a given index.
hasSourceText()
Does this page have source text?
Definition: Title.php:4310
static newFromIDs($ids)
Make an array of titles from an array of IDs.
Definition: Title.php:418
wfAppendQuery($url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
$wgWhitelistRead
Pages anonymous user may see, set as an array of pages titles.
static getTitleCache()
Definition: Title.php:356
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition: Title.php:170
isAllowed($action= '')
Internal mechanics of testing a permission.
Definition: User.php:3576
Class to invalidate the HTML cache of all the pages linking to a given title.
getDBkey()
Get the main part with underscores.
Definition: Title.php:890
__toString()
Return a string representation of this title.
Definition: Title.php:1444
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition: Title.php:1590
hasFragment()
Check if a Title fragment is set.
Definition: Title.php:1344
hasContentModel($id)
Convenience method for checking a title's content model name.
Definition: Title.php:945
$wgExemptFromUserRobotsControl
An array of namespace keys in which the INDEX/__NOINDEX__ magic words will not function, so users can't decide whether pages in that namespace are indexed by search engines.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1020
selectFieldValues($table, $var, $cond= '', $fname=__METHOD__, $options=[])
A SELECT wrapper which returns a list of single field values from result rows.
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition: Title.php:2838
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition: Title.php:1176
const NS_MEDIA
Definition: Defines.php:57
static newFromDBkey($key)
Create a new Title from a prefixed DB key.
Definition: Title.php:200
getRootText()
Get the root page name text without a namespace, i.e.
Definition: Title.php:1474
getTouched($db=null)
Get the last touched timestamp.
Definition: Title.php:4409
getLocalURL($query= '', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1688
$res
Definition: database.txt:21
checkCSSandJSPermissions($action, $user, $errors, $rigor, $short)
Check CSS/JS sub-page permissions.
Definition: Title.php:2090
bool string $mContentModel
ID of the page's content model, i.e.
Definition: Title.php:94
canExist()
Is this in a namespace that allows actual pages?
Definition: Title.php:1008
getSubpage($text)
Get the title for a subpage of the current page.
Definition: Title.php:1570
MediaWiki exception.
Definition: MWException.php:26
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:51
null $mRedirect
Is the article at this title a redirect?
Definition: Title.php:140
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
$wgWhitelistReadRegexp
Pages anonymous user may see, set as an array of regular expressions.
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:4528
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not...
Definition: Title.php:2666
invalidateCache($purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4361
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3528
Deferrable Update for closure/callback updates via IDatabase::doAtomicSection()
getUserPermissionsErrors($action, $user, $rigor= 'secure', $ignoreErrors=[])
Can $user perform $action on this page?
Definition: Title.php:1896
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:3427
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition: Title.php:1323
const NS_CATEGORY
Definition: Defines.php:83
moveTo(&$nt, $auth=true, $reason= '', $createRedirect=true)
Move a title to a new location.
Definition: Title.php:3647
getRedirectsHere($ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4492
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition: Revision.php:429
checkSpecialsAndNSPermissions($action, $user, $errors, $rigor, $short)
Check permissions on special pages & namespaces.
Definition: Title.php:2061
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
getCanonicalURL($query= '', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition: Title.php:1824
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:4209
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition: Title.php:143
static makeTitleSafe($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:527
static getContentNamespaces()
Get a list of all namespace indices which are considered to contain content.
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition: Title.php:1454
static subjectEquals($ns1, $ns2)
Returns whether the specified namespaces share the same subject.
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:153
validateFileMoveOperation($nt)
Check if the requested move target is a valid file move target.
Definition: Title.php:3619
isCssSubpage()
Is this a .css subpage of a user page?
Definition: Title.php:1251
const DB_SLAVE
Definition: Defines.php:46
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 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 after processing 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 unsetoffset-wrap String Wrap the message in html(usually something like"&lt
isMainPage()
Is this the mainpage?
Definition: Title.php:1156
static decodeCharReferencesAndNormalize($text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1516
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead.&$feedLinks conditions will AND in the final query as a Content object as a Content object $title
Definition: hooks.txt:312
getAuthorsBetween($old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition: Title.php:4111
static hasSubpages($index)
Does the namespace allow subpages?
static addUpdate(DeferrableUpdate $update, $type=self::POSTSEND)
Add an update to the deferred list.
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:913
const PROTO_RELATIVE
Definition: Defines.php:264
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:12
string $mInterwiki
Interwiki prefix.
Definition: Title.php:76
equals(Title $title)
Compare with another title.
Definition: Title.php:4196
isKnown()
Does this title refer to a page that can (or might) be meaningfully viewed? In particular, this function may be used to determine if links to the title should be rendered as "bluelinks" (as opposed to "redlinks" to non-existent pages).
Definition: Title.php:4301
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:3411
const NS_FILE
Definition: Defines.php:75
static newFromResult($res)
Definition: TitleArray.php:38
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2783
Service interface for looking up Interwiki records.
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:1601
checkCascadingSourcesRestrictions($action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition: Title.php:2160
static equals($ns1, $ns2)
Returns whether the specified namespaces are the same namespace.
isSubpage()
Is this a subpage?
Definition: Title.php:1165
getInterwiki()
Get the interwiki prefix.
Definition: Title.php:800
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:1816
const RAW
Definition: Revision.php:85
loadFromRow($row)
Load Title object fields from a DB row.
Definition: Title.php:456
areCascadeProtectionSourcesLoaded($getPages=true)
Determines whether cascading protection sources have already been loaded from the database...
Definition: Title.php:2680
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:927
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page...
Definition: Title.php:3492
const NS_MEDIAWIKI
Definition: Defines.php:77
const PROTO_HTTP
Definition: Defines.php:262
getNamespaceKey($prepend= 'nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition: Title.php:4461
countRevisionsBetween($old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:4068
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition: Title.php:112
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 local account $user
Definition: hooks.txt:242
static newFromTitleValue(TitleValue $titleValue)
Create a new Title from a TitleValue.
Definition: Title.php:219
getRestrictions($action)
Accessor/initialisation for mRestrictions.
Definition: Title.php:2796
CONTENT_MODEL_JAVASCRIPT
Uploads have to be specially set up to be secure.
bool $wgPageLanguageUseDB
Enable page language feature Allows setting page language in database.
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition: Title.php:1290
int $mLength
The page length, 0 for special pages.
Definition: Title.php:137
getFirstRevision($flags=0)
Get the first revision of the page.
Definition: Title.php:3974
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:1169
static newFromTextThrow($text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid, rather than returning null.
Definition: Title.php:286
isSpecialPage()
Returns true if this is a special page.
Definition: Title.php:1026
quickUserCan($action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition: Title.php:1859
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:1836
$wgLegalTitleChars
Allowed title characters – regex character class Don't change this unless you know what you're doing...
loadRestrictions($oldFashionedRestrictions=null)
Load restrictions from the page_restrictions table.
Definition: Title.php:2945
Simple store for keeping values in an associative array for the current process.
static getTalk($index)
Get the talk namespace index for a given namespace.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
static convertByteClassToUnicodeClass($byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition: Title.php:613
$wgNamespaceProtection
Set the minimum permissions required to edit pages in each namespace.
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition: Title.php:3748
const PROTO_CANONICAL
Definition: Defines.php:266
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition: Title.php:4668
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition: Title.php:1579
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition: Title.php:109
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:36
linkcache txt The LinkCache class maintains a list of article titles and the information about whether or not the article exists in the database This is used to mark up links when displaying a page If the same link appears more than once on any page then it only has to be looked up once In most cases link lookups are done in batches with the LinkBatch class or the equivalent in so the link cache is mostly useful for short snippets of parsed and for links in the navigation areas of the skin The link cache was formerly used to track links used in a document for the purposes of updating the link tables This application is now deprecated To create a you can use the following $titles
Definition: linkcache.txt:17
static resolveAlias($alias)
Given a special page name with a possible subpage, return an array where the first element is the spe...
getRestrictionTypes()
Returns restriction types for the current Title.
Definition: Title.php:2499
static exists($name)
Check if a given name exist as a special page or as a special page alias.
static isContent($index)
Does this namespace contain content, for the purposes of calculating statistics, etc?
TitleValue $mTitleValue
A corresponding TitleValue object.
Definition: Title.php:156
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition: Title.php:149
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition: Title.php:819
string $mFragment
Title fragment (i.e.
Definition: Title.php:82
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition: Title.php:85
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition: Title.php:134
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1020
getCategorySortkey($prefix= '')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition: Title.php:4582
wfArrayToCgi($array1, $array2=null, $prefix= '')
This function takes one or two arrays as input, and returns a CGI-style string, e.g.
static makeName($ns, $title, $fragment= '', $interwiki= '', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition: Title.php:717
static newFromURL($url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition: Title.php:333
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition: Title.php:4630
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles...
Definition: Title.php:45
checkReadPermissions($action, $user, $errors, $rigor, $short)
Check that the user is allowed to read this page.
Definition: Title.php:2311
isCssOrJsPage()
Could this page contain custom CSS or JavaScript for the global UI.
Definition: Title.php:1206
getCascadeProtectionSources($getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition: Title.php:2697
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition: Title.php:1352
get($key, $flags=0, $oldFlags=null)
Get an item with the given key.
Definition: BagOStuff.php:173
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition: Title.php:3909
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired 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 inclusive $limit
Definition: hooks.txt:1020
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 local content language as $wgContLang
Definition: design.txt:56
getInterwiki()
The interwiki component of this LinkTarget.
const CONTENT_MODEL_CSS
Definition: Defines.php:280
string $mPrefixedText
Text form including namespace/interwiki, initialised on demand.
Definition: Title.php:124
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition: Title.php:3115
isNewPage()
Check if this is a new page.
Definition: Title.php:4006
getText()
Returns the link in text form, without namespace prefix or fragment.
missingPermissionError($action, $short)
Get a description array when the user doesn't have the right to perform $action (i.e.
Definition: Title.php:2386
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1020
isProtected($action= '')
Does the title correspond to a protected article?
Definition: Title.php:2616
canTalk()
Could this title have a corresponding talk page?
Definition: Title.php:999
$count
checkActionPermissions($action, $user, $errors, $rigor, $short)
Check action permissions not already checked in checkQuickPermissions.
Definition: Title.php:2207
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition: Title.php:121
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:585
$wgServer
URL of the server.
isDeleted()
Is there a version of this page in the deletion archive?
Definition: Title.php:3090
const DB_MASTER
Definition: Defines.php:47
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition: Title.php:774
loadRestrictionsFromRows($rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition: Title.php:2872
array $mRestrictions
Array of groups allowed to edit this article.
Definition: Title.php:100
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:1781
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition: Title.php:2526
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition: Title.php:79
static compare($a, $b)
Callback for usort() to do title sorts by (namespace, title)
Definition: Title.php:759
addQuotes($s)
Adds quotes and backslashes.
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition: Title.php:97
countAuthorsBetween($old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition: Title.php:4185
getTalkNsText()
Get the namespace text of the talk page.
Definition: Title.php:989
checkPageRestrictions($action, $user, $errors, $rigor, $short)
Check against page_restrictions table requirements on this page.
Definition: Title.php:2126
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries...
Definition: Title.php:370
checkPermissionHooks($action, $user, $errors, $rigor, $short)
Check various permission hooks.
Definition: Title.php:2028
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:179
getParentCategoryTree($children=[])
Get a tree of parent categories.
Definition: Title.php:3882
string $mTextform
Text form (spaces not underscores) of the main part.
Definition: Title.php:61
static selectFields()
Return the list of revision fields that should be selected to create a new page.
Definition: WikiPage.php:262
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition: Title.php:2812
isSemiProtected($action= 'edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2588
static getSubject($index)
Get the subject namespace index for a given namespace Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject.
bool int $mLatestID
ID of most recent revision.
Definition: Title.php:88
resultToError($errors, $result)
Add the resulting error code to the errors array.
Definition: Title.php:1997
getRestrictionExpiry($action)
Get the expiry time for the restriction against a given action.
Definition: Title.php:2826
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 local account incomplete not yet checked for validity & $retval
Definition: hooks.txt:242
wfFindFile($title, $options=[])
Find a file.
getPreviousRevisionID($revId, $flags=0)
Get the revision ID of the previous revision.
Definition: Title.php:3925
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
if(is_null($wgLocalTZoffset)) if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:663
$wgMaximumMovedPages
Maximum number of pages to move at once when moving subpages with a page.
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2376
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:503
static isWatchable($index)
Can pages in a namespace be watched?
static capitalize($text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition: Title.php:3299
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition: Title.php:1188
static singleton()
Get the signleton instance of this class.
purgeSquid()
Purge all applicable CDN URLs.
Definition: Title.php:3563
static getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4799
update($table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
resetArticleID($newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3262
Basic database interface for live and lazy-loaded DB handles.
Definition: IDatabase.php:35
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2376
getTitleValue()
Get a TitleValue object representing this Title.
Definition: Title.php:849
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:3369
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition: Title.php:115
getPartialURL()
Get the URL-encoded form of the main part.
Definition: Title.php:881
wfGetLangObj($langcode=false)
Return a Language object from $langcode.
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1418
getFullURL($query= '', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition: Title.php:1647
estimateRevisionCount()
Get the approximate revision count of this page.
Definition: Title.php:4045
$wgUser
Definition: Setup.php:801
$matches
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition: Title.php:70
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310