MediaWiki  1.28.1
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  private $mContentModel = false;
95 
100  private $mForcedContentModel = false;
101 
104 
106  public $mRestrictions = [];
107 
109  protected $mOldRestrictions = false;
110 
113 
116 
118  protected $mRestrictionsExpiry = [];
119 
122 
125 
127  public $mRestrictionsLoaded = false;
128 
130  protected $mPrefixedText = null;
131 
134 
141 
143  protected $mLength = -1;
144 
146  public $mRedirect = null;
147 
150 
152  private $mHasSubpages;
153 
155  private $mPageLanguage = false;
156 
159  private $mDbPageLanguage = false;
160 
162  private $mTitleValue = null;
163 
165  private $mIsBigDeletion = null;
166  // @}
167 
176  private static function getTitleFormatter() {
177  return MediaWikiServices::getInstance()->getTitleFormatter();
178  }
179 
188  private static function getInterwikiLookup() {
189  return MediaWikiServices::getInstance()->getInterwikiLookup();
190  }
191 
195  function __construct() {
196  }
197 
206  public static function newFromDBkey( $key ) {
207  $t = new Title();
208  $t->mDbkeyform = $key;
209 
210  try {
211  $t->secureAndSplit();
212  return $t;
213  } catch ( MalformedTitleException $ex ) {
214  return null;
215  }
216  }
217 
225  public static function newFromTitleValue( TitleValue $titleValue ) {
226  return self::newFromLinkTarget( $titleValue );
227  }
228 
236  public static function newFromLinkTarget( LinkTarget $linkTarget ) {
237  if ( $linkTarget instanceof Title ) {
238  // Special case if it's already a Title object
239  return $linkTarget;
240  }
241  return self::makeTitle(
242  $linkTarget->getNamespace(),
243  $linkTarget->getText(),
244  $linkTarget->getFragment(),
245  $linkTarget->getInterwiki()
246  );
247  }
248 
262  public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
263  // DWIM: Integers can be passed in here when page titles are used as array keys.
264  if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
265  throw new InvalidArgumentException( '$text must be a string.' );
266  }
267  if ( $text === null ) {
268  return null;
269  }
270 
271  try {
272  return Title::newFromTextThrow( strval( $text ), $defaultNamespace );
273  } catch ( MalformedTitleException $ex ) {
274  return null;
275  }
276  }
277 
292  public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
293  if ( is_object( $text ) ) {
294  throw new MWException( '$text must be a string, given an object' );
295  }
296 
297  $titleCache = self::getTitleCache();
298 
299  // Wiki pages often contain multiple links to the same page.
300  // Title normalization and parsing can become expensive on pages with many
301  // links, so we can save a little time by caching them.
302  // In theory these are value objects and won't get changed...
303  if ( $defaultNamespace == NS_MAIN ) {
304  $t = $titleCache->get( $text );
305  if ( $t ) {
306  return $t;
307  }
308  }
309 
310  // Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
311  $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
312 
313  $t = new Title();
314  $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
315  $t->mDefaultNamespace = intval( $defaultNamespace );
316 
317  $t->secureAndSplit();
318  if ( $defaultNamespace == NS_MAIN ) {
319  $titleCache->set( $text, $t );
320  }
321  return $t;
322  }
323 
339  public static function newFromURL( $url ) {
340  $t = new Title();
341 
342  # For compatibility with old buggy URLs. "+" is usually not valid in titles,
343  # but some URLs used it as a space replacement and they still come
344  # from some external search tools.
345  if ( strpos( self::legalChars(), '+' ) === false ) {
346  $url = strtr( $url, '+', ' ' );
347  }
348 
349  $t->mDbkeyform = strtr( $url, ' ', '_' );
350 
351  try {
352  $t->secureAndSplit();
353  return $t;
354  } catch ( MalformedTitleException $ex ) {
355  return null;
356  }
357  }
358 
362  private static function getTitleCache() {
363  if ( self::$titleCache == null ) {
364  self::$titleCache = new HashBagOStuff( [ 'maxKeys' => self::CACHE_MAX ] );
365  }
366  return self::$titleCache;
367  }
368 
376  protected static function getSelectFields() {
377  global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
378 
379  $fields = [
380  'page_namespace', 'page_title', 'page_id',
381  'page_len', 'page_is_redirect', 'page_latest',
382  ];
383 
384  if ( $wgContentHandlerUseDB ) {
385  $fields[] = 'page_content_model';
386  }
387 
388  if ( $wgPageLanguageUseDB ) {
389  $fields[] = 'page_lang';
390  }
391 
392  return $fields;
393  }
394 
402  public static function newFromID( $id, $flags = 0 ) {
403  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
404  $row = $db->selectRow(
405  'page',
406  self::getSelectFields(),
407  [ 'page_id' => $id ],
408  __METHOD__
409  );
410  if ( $row !== false ) {
411  $title = Title::newFromRow( $row );
412  } else {
413  $title = null;
414  }
415  return $title;
416  }
417 
424  public static function newFromIDs( $ids ) {
425  if ( !count( $ids ) ) {
426  return [];
427  }
428  $dbr = wfGetDB( DB_REPLICA );
429 
430  $res = $dbr->select(
431  'page',
432  self::getSelectFields(),
433  [ 'page_id' => $ids ],
434  __METHOD__
435  );
436 
437  $titles = [];
438  foreach ( $res as $row ) {
439  $titles[] = Title::newFromRow( $row );
440  }
441  return $titles;
442  }
443 
450  public static function newFromRow( $row ) {
451  $t = self::makeTitle( $row->page_namespace, $row->page_title );
452  $t->loadFromRow( $row );
453  return $t;
454  }
455 
462  public function loadFromRow( $row ) {
463  if ( $row ) { // page found
464  if ( isset( $row->page_id ) ) {
465  $this->mArticleID = (int)$row->page_id;
466  }
467  if ( isset( $row->page_len ) ) {
468  $this->mLength = (int)$row->page_len;
469  }
470  if ( isset( $row->page_is_redirect ) ) {
471  $this->mRedirect = (bool)$row->page_is_redirect;
472  }
473  if ( isset( $row->page_latest ) ) {
474  $this->mLatestID = (int)$row->page_latest;
475  }
476  if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
477  $this->mContentModel = strval( $row->page_content_model );
478  } elseif ( !$this->mForcedContentModel ) {
479  $this->mContentModel = false; # initialized lazily in getContentModel()
480  }
481  if ( isset( $row->page_lang ) ) {
482  $this->mDbPageLanguage = (string)$row->page_lang;
483  }
484  if ( isset( $row->page_restrictions ) ) {
485  $this->mOldRestrictions = $row->page_restrictions;
486  }
487  } else { // page not found
488  $this->mArticleID = 0;
489  $this->mLength = 0;
490  $this->mRedirect = false;
491  $this->mLatestID = 0;
492  if ( !$this->mForcedContentModel ) {
493  $this->mContentModel = false; # initialized lazily in getContentModel()
494  }
495  }
496  }
497 
511  public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
512  $t = new Title();
513  $t->mInterwiki = $interwiki;
514  $t->mFragment = $fragment;
515  $t->mNamespace = $ns = intval( $ns );
516  $t->mDbkeyform = strtr( $title, ' ', '_' );
517  $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
518  $t->mUrlform = wfUrlencode( $t->mDbkeyform );
519  $t->mTextform = strtr( $title, '_', ' ' );
520  $t->mContentModel = false; # initialized lazily in getContentModel()
521  return $t;
522  }
523 
535  public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
536  if ( !MWNamespace::exists( $ns ) ) {
537  return null;
538  }
539 
540  $t = new Title();
541  $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
542 
543  try {
544  $t->secureAndSplit();
545  return $t;
546  } catch ( MalformedTitleException $ex ) {
547  return null;
548  }
549  }
550 
556  public static function newMainPage() {
557  $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
558  // Don't give fatal errors if the message is broken
559  if ( !$title ) {
560  $title = Title::newFromText( 'Main Page' );
561  }
562  return $title;
563  }
564 
571  public static function nameOf( $id ) {
572  $dbr = wfGetDB( DB_REPLICA );
573 
574  $s = $dbr->selectRow(
575  'page',
576  [ 'page_namespace', 'page_title' ],
577  [ 'page_id' => $id ],
578  __METHOD__
579  );
580  if ( $s === false ) {
581  return null;
582  }
583 
584  $n = self::makeName( $s->page_namespace, $s->page_title );
585  return $n;
586  }
587 
593  public static function legalChars() {
595  return $wgLegalTitleChars;
596  }
597 
607  static function getTitleInvalidRegex() {
608  wfDeprecated( __METHOD__, '1.25' );
610  }
611 
621  public static function convertByteClassToUnicodeClass( $byteClass ) {
622  $length = strlen( $byteClass );
623  // Input token queue
624  $x0 = $x1 = $x2 = '';
625  // Decoded queue
626  $d0 = $d1 = $d2 = '';
627  // Decoded integer codepoints
628  $ord0 = $ord1 = $ord2 = 0;
629  // Re-encoded queue
630  $r0 = $r1 = $r2 = '';
631  // Output
632  $out = '';
633  // Flags
634  $allowUnicode = false;
635  for ( $pos = 0; $pos < $length; $pos++ ) {
636  // Shift the queues down
637  $x2 = $x1;
638  $x1 = $x0;
639  $d2 = $d1;
640  $d1 = $d0;
641  $ord2 = $ord1;
642  $ord1 = $ord0;
643  $r2 = $r1;
644  $r1 = $r0;
645  // Load the current input token and decoded values
646  $inChar = $byteClass[$pos];
647  if ( $inChar == '\\' ) {
648  if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
649  $x0 = $inChar . $m[0];
650  $d0 = chr( hexdec( $m[1] ) );
651  $pos += strlen( $m[0] );
652  } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
653  $x0 = $inChar . $m[0];
654  $d0 = chr( octdec( $m[0] ) );
655  $pos += strlen( $m[0] );
656  } elseif ( $pos + 1 >= $length ) {
657  $x0 = $d0 = '\\';
658  } else {
659  $d0 = $byteClass[$pos + 1];
660  $x0 = $inChar . $d0;
661  $pos += 1;
662  }
663  } else {
664  $x0 = $d0 = $inChar;
665  }
666  $ord0 = ord( $d0 );
667  // Load the current re-encoded value
668  if ( $ord0 < 32 || $ord0 == 0x7f ) {
669  $r0 = sprintf( '\x%02x', $ord0 );
670  } elseif ( $ord0 >= 0x80 ) {
671  // Allow unicode if a single high-bit character appears
672  $r0 = sprintf( '\x%02x', $ord0 );
673  $allowUnicode = true;
674  } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
675  $r0 = '\\' . $d0;
676  } else {
677  $r0 = $d0;
678  }
679  // Do the output
680  if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
681  // Range
682  if ( $ord2 > $ord0 ) {
683  // Empty range
684  } elseif ( $ord0 >= 0x80 ) {
685  // Unicode range
686  $allowUnicode = true;
687  if ( $ord2 < 0x80 ) {
688  // Keep the non-unicode section of the range
689  $out .= "$r2-\\x7F";
690  }
691  } else {
692  // Normal range
693  $out .= "$r2-$r0";
694  }
695  // Reset state to the initial value
696  $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
697  } elseif ( $ord2 < 0x80 ) {
698  // ASCII character
699  $out .= $r2;
700  }
701  }
702  if ( $ord1 < 0x80 ) {
703  $out .= $r1;
704  }
705  if ( $ord0 < 0x80 ) {
706  $out .= $r0;
707  }
708  if ( $allowUnicode ) {
709  $out .= '\u0080-\uFFFF';
710  }
711  return $out;
712  }
713 
725  public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
726  $canonicalNamespace = false
727  ) {
729 
730  if ( $canonicalNamespace ) {
731  $namespace = MWNamespace::getCanonicalName( $ns );
732  } else {
733  $namespace = $wgContLang->getNsText( $ns );
734  }
735  $name = $namespace == '' ? $title : "$namespace:$title";
736  if ( strval( $interwiki ) != '' ) {
737  $name = "$interwiki:$name";
738  }
739  if ( strval( $fragment ) != '' ) {
740  $name .= '#' . $fragment;
741  }
742  return $name;
743  }
744 
751  static function escapeFragmentForURL( $fragment ) {
752  # Note that we don't urlencode the fragment. urlencoded Unicode
753  # fragments appear not to work in IE (at least up to 7) or in at least
754  # one version of Opera 9.x. The W3C validator, for one, doesn't seem
755  # to care if they aren't encoded.
756  return Sanitizer::escapeId( $fragment, 'noninitial' );
757  }
758 
767  public static function compare( LinkTarget $a, LinkTarget $b ) {
768  if ( $a->getNamespace() == $b->getNamespace() ) {
769  return strcmp( $a->getText(), $b->getText() );
770  } else {
771  return $a->getNamespace() - $b->getNamespace();
772  }
773  }
774 
782  public function isLocal() {
783  if ( $this->isExternal() ) {
784  $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
785  if ( $iw ) {
786  return $iw->isLocal();
787  }
788  }
789  return true;
790  }
791 
797  public function isExternal() {
798  return $this->mInterwiki !== '';
799  }
800 
808  public function getInterwiki() {
809  return $this->mInterwiki;
810  }
811 
817  public function wasLocalInterwiki() {
818  return $this->mLocalInterwiki;
819  }
820 
827  public function isTrans() {
828  if ( !$this->isExternal() ) {
829  return false;
830  }
831 
832  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
833  }
834 
840  public function getTransWikiID() {
841  if ( !$this->isExternal() ) {
842  return false;
843  }
844 
845  return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
846  }
847 
857  public function getTitleValue() {
858  if ( $this->mTitleValue === null ) {
859  try {
860  $this->mTitleValue = new TitleValue(
861  $this->getNamespace(),
862  $this->getDBkey(),
863  $this->getFragment(),
864  $this->getInterwiki()
865  );
866  } catch ( InvalidArgumentException $ex ) {
867  wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
868  $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
869  }
870  }
871 
872  return $this->mTitleValue;
873  }
874 
880  public function getText() {
881  return $this->mTextform;
882  }
883 
889  public function getPartialURL() {
890  return $this->mUrlform;
891  }
892 
898  public function getDBkey() {
899  return $this->mDbkeyform;
900  }
901 
907  function getUserCaseDBKey() {
908  if ( !is_null( $this->mUserCaseDBKey ) ) {
909  return $this->mUserCaseDBKey;
910  } else {
911  // If created via makeTitle(), $this->mUserCaseDBKey is not set.
912  return $this->mDbkeyform;
913  }
914  }
915 
921  public function getNamespace() {
922  return $this->mNamespace;
923  }
924 
931  public function getContentModel( $flags = 0 ) {
932  if ( !$this->mForcedContentModel
933  && ( !$this->mContentModel || $flags === Title::GAID_FOR_UPDATE )
934  && $this->getArticleID( $flags )
935  ) {
936  $linkCache = LinkCache::singleton();
937  $linkCache->addLinkObj( $this ); # in case we already had an article ID
938  $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
939  }
940 
941  if ( !$this->mContentModel ) {
942  $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
943  }
944 
945  return $this->mContentModel;
946  }
947 
954  public function hasContentModel( $id ) {
955  return $this->getContentModel() == $id;
956  }
957 
969  public function setContentModel( $model ) {
970  $this->mContentModel = $model;
971  $this->mForcedContentModel = true;
972  }
973 
979  public function getNsText() {
980  if ( $this->isExternal() ) {
981  // This probably shouldn't even happen,
982  // but for interwiki transclusion it sometimes does.
983  // Use the canonical namespaces if possible to try to
984  // resolve a foreign namespace.
985  if ( MWNamespace::exists( $this->mNamespace ) ) {
986  return MWNamespace::getCanonicalName( $this->mNamespace );
987  }
988  }
989 
990  try {
991  $formatter = self::getTitleFormatter();
992  return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
993  } catch ( InvalidArgumentException $ex ) {
994  wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
995  return false;
996  }
997  }
998 
1004  public function getSubjectNsText() {
1006  return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1007  }
1008 
1014  public function getTalkNsText() {
1016  return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1017  }
1018 
1024  public function canTalk() {
1025  return MWNamespace::canTalk( $this->mNamespace );
1026  }
1027 
1033  public function canExist() {
1034  return $this->mNamespace >= NS_MAIN;
1035  }
1036 
1042  public function isWatchable() {
1043  return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
1044  }
1045 
1051  public function isSpecialPage() {
1052  return $this->getNamespace() == NS_SPECIAL;
1053  }
1054 
1061  public function isSpecial( $name ) {
1062  if ( $this->isSpecialPage() ) {
1063  list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
1064  if ( $name == $thisName ) {
1065  return true;
1066  }
1067  }
1068  return false;
1069  }
1070 
1077  public function fixSpecialName() {
1078  if ( $this->isSpecialPage() ) {
1079  list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
1080  if ( $canonicalName ) {
1081  $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
1082  if ( $localName != $this->mDbkeyform ) {
1083  return Title::makeTitle( NS_SPECIAL, $localName );
1084  }
1085  }
1086  }
1087  return $this;
1088  }
1089 
1100  public function inNamespace( $ns ) {
1101  return MWNamespace::equals( $this->getNamespace(), $ns );
1102  }
1103 
1111  public function inNamespaces( /* ... */ ) {
1112  $namespaces = func_get_args();
1113  if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1114  $namespaces = $namespaces[0];
1115  }
1116 
1117  foreach ( $namespaces as $ns ) {
1118  if ( $this->inNamespace( $ns ) ) {
1119  return true;
1120  }
1121  }
1122 
1123  return false;
1124  }
1125 
1139  public function hasSubjectNamespace( $ns ) {
1140  return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
1141  }
1142 
1150  public function isContentPage() {
1151  return MWNamespace::isContent( $this->getNamespace() );
1152  }
1153 
1160  public function isMovable() {
1161  if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
1162  // Interwiki title or immovable namespace. Hooks don't get to override here
1163  return false;
1164  }
1165 
1166  $result = true;
1167  Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1168  return $result;
1169  }
1170 
1181  public function isMainPage() {
1182  return $this->equals( Title::newMainPage() );
1183  }
1184 
1190  public function isSubpage() {
1191  return MWNamespace::hasSubpages( $this->mNamespace )
1192  ? strpos( $this->getText(), '/' ) !== false
1193  : false;
1194  }
1195 
1201  public function isConversionTable() {
1202  // @todo ConversionTable should become a separate content model.
1203 
1204  return $this->getNamespace() == NS_MEDIAWIKI &&
1205  strpos( $this->getText(), 'Conversiontable/' ) === 0;
1206  }
1207 
1213  public function isWikitextPage() {
1214  return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1215  }
1216 
1231  public function isCssOrJsPage() {
1232  $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
1233  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1235 
1236  # @note This hook is also called in ContentHandler::getDefaultModel.
1237  # It's called here again to make sure hook functions can force this
1238  # method to return true even outside the MediaWiki namespace.
1239 
1240  Hooks::run( 'TitleIsCssOrJsPage', [ $this, &$isCssOrJsPage ], '1.25' );
1241 
1242  return $isCssOrJsPage;
1243  }
1244 
1250  public function isCssJsSubpage() {
1251  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1252  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1253  || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
1254  }
1255 
1261  public function getSkinFromCssJsSubpage() {
1262  $subpage = explode( '/', $this->mTextform );
1263  $subpage = $subpage[count( $subpage ) - 1];
1264  $lastdot = strrpos( $subpage, '.' );
1265  if ( $lastdot === false ) {
1266  return $subpage; # Never happens: only called for names ending in '.css' or '.js'
1267  }
1268  return substr( $subpage, 0, $lastdot );
1269  }
1270 
1276  public function isCssSubpage() {
1277  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1278  && $this->hasContentModel( CONTENT_MODEL_CSS ) );
1279  }
1280 
1286  public function isJsSubpage() {
1287  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1289  }
1290 
1296  public function isTalkPage() {
1297  return MWNamespace::isTalk( $this->getNamespace() );
1298  }
1299 
1305  public function getTalkPage() {
1306  return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
1307  }
1308 
1315  public function getSubjectPage() {
1316  // Is this the same title?
1317  $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
1318  if ( $this->getNamespace() == $subjectNS ) {
1319  return $this;
1320  }
1321  return Title::makeTitle( $subjectNS, $this->getDBkey() );
1322  }
1323 
1332  public function getOtherPage() {
1333  if ( $this->isSpecialPage() ) {
1334  throw new MWException( 'Special pages cannot have other pages' );
1335  }
1336  if ( $this->isTalkPage() ) {
1337  return $this->getSubjectPage();
1338  } else {
1339  return $this->getTalkPage();
1340  }
1341  }
1342 
1348  public function getDefaultNamespace() {
1349  return $this->mDefaultNamespace;
1350  }
1351 
1359  public function getFragment() {
1360  return $this->mFragment;
1361  }
1362 
1369  public function hasFragment() {
1370  return $this->mFragment !== '';
1371  }
1372 
1377  public function getFragmentForURL() {
1378  if ( !$this->hasFragment() ) {
1379  return '';
1380  } else {
1381  return '#' . Title::escapeFragmentForURL( $this->getFragment() );
1382  }
1383  }
1384 
1397  public function setFragment( $fragment ) {
1398  $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1399  }
1400 
1408  public function createFragmentTarget( $fragment ) {
1409  return self::makeTitle(
1410  $this->getNamespace(),
1411  $this->getText(),
1412  $fragment,
1413  $this->getInterwiki()
1414  );
1415 
1416  }
1417 
1425  private function prefix( $name ) {
1426  $p = '';
1427  if ( $this->isExternal() ) {
1428  $p = $this->mInterwiki . ':';
1429  }
1430 
1431  if ( 0 != $this->mNamespace ) {
1432  $p .= $this->getNsText() . ':';
1433  }
1434  return $p . $name;
1435  }
1436 
1443  public function getPrefixedDBkey() {
1444  $s = $this->prefix( $this->mDbkeyform );
1445  $s = strtr( $s, ' ', '_' );
1446  return $s;
1447  }
1448 
1455  public function getPrefixedText() {
1456  if ( $this->mPrefixedText === null ) {
1457  $s = $this->prefix( $this->mTextform );
1458  $s = strtr( $s, '_', ' ' );
1459  $this->mPrefixedText = $s;
1460  }
1461  return $this->mPrefixedText;
1462  }
1463 
1469  public function __toString() {
1470  return $this->getPrefixedText();
1471  }
1472 
1479  public function getFullText() {
1480  $text = $this->getPrefixedText();
1481  if ( $this->hasFragment() ) {
1482  $text .= '#' . $this->getFragment();
1483  }
1484  return $text;
1485  }
1486 
1499  public function getRootText() {
1500  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1501  return $this->getText();
1502  }
1503 
1504  return strtok( $this->getText(), '/' );
1505  }
1506 
1519  public function getRootTitle() {
1520  return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
1521  }
1522 
1534  public function getBaseText() {
1535  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1536  return $this->getText();
1537  }
1538 
1539  $parts = explode( '/', $this->getText() );
1540  # Don't discard the real title if there's no subpage involved
1541  if ( count( $parts ) > 1 ) {
1542  unset( $parts[count( $parts ) - 1] );
1543  }
1544  return implode( '/', $parts );
1545  }
1546 
1559  public function getBaseTitle() {
1560  return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
1561  }
1562 
1574  public function getSubpageText() {
1575  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1576  return $this->mTextform;
1577  }
1578  $parts = explode( '/', $this->mTextform );
1579  return $parts[count( $parts ) - 1];
1580  }
1581 
1595  public function getSubpage( $text ) {
1596  return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
1597  }
1598 
1604  public function getSubpageUrlForm() {
1605  $text = $this->getSubpageText();
1606  $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1607  return $text;
1608  }
1609 
1615  public function getPrefixedURL() {
1616  $s = $this->prefix( $this->mDbkeyform );
1617  $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1618  return $s;
1619  }
1620 
1634  private static function fixUrlQueryArgs( $query, $query2 = false ) {
1635  if ( $query2 !== false ) {
1636  wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1637  "method called with a second parameter is deprecated. Add your " .
1638  "parameter to an array passed as the first parameter.", "1.19" );
1639  }
1640  if ( is_array( $query ) ) {
1641  $query = wfArrayToCgi( $query );
1642  }
1643  if ( $query2 ) {
1644  if ( is_string( $query2 ) ) {
1645  // $query2 is a string, we will consider this to be
1646  // a deprecated $variant argument and add it to the query
1647  $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1648  } else {
1649  $query2 = wfArrayToCgi( $query2 );
1650  }
1651  // If we have $query content add a & to it first
1652  if ( $query ) {
1653  $query .= '&';
1654  }
1655  // Now append the queries together
1656  $query .= $query2;
1657  }
1658  return $query;
1659  }
1660 
1672  public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1673  $query = self::fixUrlQueryArgs( $query, $query2 );
1674 
1675  # Hand off all the decisions on urls to getLocalURL
1676  $url = $this->getLocalURL( $query );
1677 
1678  # Expand the url to make it a full url. Note that getLocalURL has the
1679  # potential to output full urls for a variety of reasons, so we use
1680  # wfExpandUrl instead of simply prepending $wgServer
1681  $url = wfExpandUrl( $url, $proto );
1682 
1683  # Finally, add the fragment.
1684  $url .= $this->getFragmentForURL();
1685 
1686  Hooks::run( 'GetFullURL', [ &$this, &$url, $query ] );
1687  return $url;
1688  }
1689 
1706  public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1707  $target = $this;
1708  if ( $this->isExternal() ) {
1709  $target = SpecialPage::getTitleFor(
1710  'GoToInterwiki',
1711  $this->getPrefixedDBKey()
1712  );
1713  }
1714  return $target->getFullUrl( $query, false, $proto );
1715  }
1716 
1740  public function getLocalURL( $query = '', $query2 = false ) {
1742 
1743  $query = self::fixUrlQueryArgs( $query, $query2 );
1744 
1745  $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1746  if ( $interwiki ) {
1747  $namespace = $this->getNsText();
1748  if ( $namespace != '' ) {
1749  # Can this actually happen? Interwikis shouldn't be parsed.
1750  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1751  $namespace .= ':';
1752  }
1753  $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1754  $url = wfAppendQuery( $url, $query );
1755  } else {
1756  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1757  if ( $query == '' ) {
1758  $url = str_replace( '$1', $dbkey, $wgArticlePath );
1759  Hooks::run( 'GetLocalURL::Article', [ &$this, &$url ] );
1760  } else {
1762  $url = false;
1763  $matches = [];
1764 
1765  if ( !empty( $wgActionPaths )
1766  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1767  ) {
1768  $action = urldecode( $matches[2] );
1769  if ( isset( $wgActionPaths[$action] ) ) {
1770  $query = $matches[1];
1771  if ( isset( $matches[4] ) ) {
1772  $query .= $matches[4];
1773  }
1774  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1775  if ( $query != '' ) {
1776  $url = wfAppendQuery( $url, $query );
1777  }
1778  }
1779  }
1780 
1781  if ( $url === false
1782  && $wgVariantArticlePath
1783  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1784  && $this->getPageLanguage()->equals( $wgContLang )
1785  && $this->getPageLanguage()->hasVariants()
1786  ) {
1787  $variant = urldecode( $matches[1] );
1788  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1789  // Only do the variant replacement if the given variant is a valid
1790  // variant for the page's language.
1791  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1792  $url = str_replace( '$1', $dbkey, $url );
1793  }
1794  }
1795 
1796  if ( $url === false ) {
1797  if ( $query == '-' ) {
1798  $query = '';
1799  }
1800  $url = "{$wgScript}?title={$dbkey}&{$query}";
1801  }
1802  }
1803 
1804  Hooks::run( 'GetLocalURL::Internal', [ &$this, &$url, $query ] );
1805 
1806  // @todo FIXME: This causes breakage in various places when we
1807  // actually expected a local URL and end up with dupe prefixes.
1808  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
1809  $url = $wgServer . $url;
1810  }
1811  }
1812  Hooks::run( 'GetLocalURL', [ &$this, &$url, $query ] );
1813  return $url;
1814  }
1815 
1833  public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
1834  if ( $this->isExternal() || $proto !== false ) {
1835  $ret = $this->getFullURL( $query, $query2, $proto );
1836  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
1837  $ret = $this->getFragmentForURL();
1838  } else {
1839  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
1840  }
1841  return $ret;
1842  }
1843 
1856  public function getInternalURL( $query = '', $query2 = false ) {
1858  $query = self::fixUrlQueryArgs( $query, $query2 );
1859  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
1860  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
1861  Hooks::run( 'GetInternalURL', [ &$this, &$url, $query ] );
1862  return $url;
1863  }
1864 
1876  public function getCanonicalURL( $query = '', $query2 = false ) {
1877  $query = self::fixUrlQueryArgs( $query, $query2 );
1878  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
1879  Hooks::run( 'GetCanonicalURL', [ &$this, &$url, $query ] );
1880  return $url;
1881  }
1882 
1888  public function getEditURL() {
1889  if ( $this->isExternal() ) {
1890  return '';
1891  }
1892  $s = $this->getLocalURL( 'action=edit' );
1893 
1894  return $s;
1895  }
1896 
1911  public function quickUserCan( $action, $user = null ) {
1912  return $this->userCan( $action, $user, false );
1913  }
1914 
1924  public function userCan( $action, $user = null, $rigor = 'secure' ) {
1925  if ( !$user instanceof User ) {
1926  global $wgUser;
1927  $user = $wgUser;
1928  }
1929 
1930  return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
1931  }
1932 
1948  public function getUserPermissionsErrors(
1949  $action, $user, $rigor = 'secure', $ignoreErrors = []
1950  ) {
1951  $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
1952 
1953  // Remove the errors being ignored.
1954  foreach ( $errors as $index => $error ) {
1955  $errKey = is_array( $error ) ? $error[0] : $error;
1956 
1957  if ( in_array( $errKey, $ignoreErrors ) ) {
1958  unset( $errors[$index] );
1959  }
1960  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
1961  unset( $errors[$index] );
1962  }
1963  }
1964 
1965  return $errors;
1966  }
1967 
1979  private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
1980  if ( !Hooks::run( 'TitleQuickPermissions',
1981  [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
1982  ) {
1983  return $errors;
1984  }
1985 
1986  if ( $action == 'create' ) {
1987  if (
1988  ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1989  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
1990  ) {
1991  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
1992  }
1993  } elseif ( $action == 'move' ) {
1994  if ( !$user->isAllowed( 'move-rootuserpages' )
1995  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1996  // Show user page-specific message only if the user can move other pages
1997  $errors[] = [ 'cant-move-user-page' ];
1998  }
1999 
2000  // Check if user is allowed to move files if it's a file
2001  if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
2002  $errors[] = [ 'movenotallowedfile' ];
2003  }
2004 
2005  // Check if user is allowed to move category pages if it's a category page
2006  if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
2007  $errors[] = [ 'cant-move-category-page' ];
2008  }
2009 
2010  if ( !$user->isAllowed( 'move' ) ) {
2011  // User can't move anything
2012  $userCanMove = User::groupHasPermission( 'user', 'move' );
2013  $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
2014  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
2015  // custom message if logged-in users without any special rights can move
2016  $errors[] = [ 'movenologintext' ];
2017  } else {
2018  $errors[] = [ 'movenotallowed' ];
2019  }
2020  }
2021  } elseif ( $action == 'move-target' ) {
2022  if ( !$user->isAllowed( 'move' ) ) {
2023  // User can't move anything
2024  $errors[] = [ 'movenotallowed' ];
2025  } elseif ( !$user->isAllowed( 'move-rootuserpages' )
2026  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2027  // Show user page-specific message only if the user can move other pages
2028  $errors[] = [ 'cant-move-to-user-page' ];
2029  } elseif ( !$user->isAllowed( 'move-categorypages' )
2030  && $this->mNamespace == NS_CATEGORY ) {
2031  // Show category page-specific message only if the user can move other pages
2032  $errors[] = [ 'cant-move-to-category-page' ];
2033  }
2034  } elseif ( !$user->isAllowed( $action ) ) {
2035  $errors[] = $this->missingPermissionError( $action, $short );
2036  }
2037 
2038  return $errors;
2039  }
2040 
2049  private function resultToError( $errors, $result ) {
2050  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2051  // A single array representing an error
2052  $errors[] = $result;
2053  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2054  // A nested array representing multiple errors
2055  $errors = array_merge( $errors, $result );
2056  } elseif ( $result !== '' && is_string( $result ) ) {
2057  // A string representing a message-id
2058  $errors[] = [ $result ];
2059  } elseif ( $result instanceof MessageSpecifier ) {
2060  // A message specifier representing an error
2061  $errors[] = [ $result ];
2062  } elseif ( $result === false ) {
2063  // a generic "We don't want them to do that"
2064  $errors[] = [ 'badaccess-group0' ];
2065  }
2066  return $errors;
2067  }
2068 
2080  private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2081  // Use getUserPermissionsErrors instead
2082  $result = '';
2083  if ( !Hooks::run( 'userCan', [ &$this, &$user, $action, &$result ] ) ) {
2084  return $result ? [] : [ [ 'badaccess-group0' ] ];
2085  }
2086  // Check getUserPermissionsErrors hook
2087  if ( !Hooks::run( 'getUserPermissionsErrors', [ &$this, &$user, $action, &$result ] ) ) {
2088  $errors = $this->resultToError( $errors, $result );
2089  }
2090  // Check getUserPermissionsErrorsExpensive hook
2091  if (
2092  $rigor !== 'quick'
2093  && !( $short && count( $errors ) > 0 )
2094  && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$this, &$user, $action, &$result ] )
2095  ) {
2096  $errors = $this->resultToError( $errors, $result );
2097  }
2098 
2099  return $errors;
2100  }
2101 
2113  private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2114  # Only 'createaccount' can be performed on special pages,
2115  # which don't actually exist in the DB.
2116  if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
2117  $errors[] = [ 'ns-specialprotected' ];
2118  }
2119 
2120  # Check $wgNamespaceProtection for restricted namespaces
2121  if ( $this->isNamespaceProtected( $user ) ) {
2122  $ns = $this->mNamespace == NS_MAIN ?
2123  wfMessage( 'nstab-main' )->text() : $this->getNsText();
2124  $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2125  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2126  }
2127 
2128  return $errors;
2129  }
2130 
2142  private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
2143  # Protect css/js subpages of user pages
2144  # XXX: this might be better using restrictions
2145  # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
2146  if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
2147  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2148  if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
2149  $errors[] = [ 'mycustomcssprotected', $action ];
2150  } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
2151  $errors[] = [ 'mycustomjsprotected', $action ];
2152  }
2153  } else {
2154  if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
2155  $errors[] = [ 'customcssprotected', $action ];
2156  } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
2157  $errors[] = [ 'customjsprotected', $action ];
2158  }
2159  }
2160  }
2161 
2162  return $errors;
2163  }
2164 
2178  private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2179  foreach ( $this->getRestrictions( $action ) as $right ) {
2180  // Backwards compatibility, rewrite sysop -> editprotected
2181  if ( $right == 'sysop' ) {
2182  $right = 'editprotected';
2183  }
2184  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2185  if ( $right == 'autoconfirmed' ) {
2186  $right = 'editsemiprotected';
2187  }
2188  if ( $right == '' ) {
2189  continue;
2190  }
2191  if ( !$user->isAllowed( $right ) ) {
2192  $errors[] = [ 'protectedpagetext', $right, $action ];
2193  } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2194  $errors[] = [ 'protectedpagetext', 'protect', $action ];
2195  }
2196  }
2197 
2198  return $errors;
2199  }
2200 
2212  private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2213  if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
2214  # We /could/ use the protection level on the source page, but it's
2215  # fairly ugly as we have to establish a precedence hierarchy for pages
2216  # included by multiple cascade-protected pages. So just restrict
2217  # it to people with 'protect' permission, as they could remove the
2218  # protection anyway.
2219  list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2220  # Cascading protection depends on more than this page...
2221  # Several cascading protected pages may include this page...
2222  # Check each cascading level
2223  # This is only for protection restrictions, not for all actions
2224  if ( isset( $restrictions[$action] ) ) {
2225  foreach ( $restrictions[$action] as $right ) {
2226  // Backwards compatibility, rewrite sysop -> editprotected
2227  if ( $right == 'sysop' ) {
2228  $right = 'editprotected';
2229  }
2230  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2231  if ( $right == 'autoconfirmed' ) {
2232  $right = 'editsemiprotected';
2233  }
2234  if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2235  $pages = '';
2236  foreach ( $cascadingSources as $page ) {
2237  $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2238  }
2239  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2240  }
2241  }
2242  }
2243  }
2244 
2245  return $errors;
2246  }
2247 
2259  private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2260  global $wgDeleteRevisionsLimit, $wgLang;
2261 
2262  if ( $action == 'protect' ) {
2263  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2264  // If they can't edit, they shouldn't protect.
2265  $errors[] = [ 'protect-cantedit' ];
2266  }
2267  } elseif ( $action == 'create' ) {
2268  $title_protection = $this->getTitleProtection();
2269  if ( $title_protection ) {
2270  if ( $title_protection['permission'] == ''
2271  || !$user->isAllowed( $title_protection['permission'] )
2272  ) {
2273  $errors[] = [
2274  'titleprotected',
2275  User::whoIs( $title_protection['user'] ),
2276  $title_protection['reason']
2277  ];
2278  }
2279  }
2280  } elseif ( $action == 'move' ) {
2281  // Check for immobile pages
2282  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2283  // Specific message for this case
2284  $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2285  } elseif ( !$this->isMovable() ) {
2286  // Less specific message for rarer cases
2287  $errors[] = [ 'immobile-source-page' ];
2288  }
2289  } elseif ( $action == 'move-target' ) {
2290  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2291  $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2292  } elseif ( !$this->isMovable() ) {
2293  $errors[] = [ 'immobile-target-page' ];
2294  }
2295  } elseif ( $action == 'delete' ) {
2296  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2297  if ( !$tempErrors ) {
2298  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2299  $user, $tempErrors, $rigor, true );
2300  }
2301  if ( $tempErrors ) {
2302  // If protection keeps them from editing, they shouldn't be able to delete.
2303  $errors[] = [ 'deleteprotected' ];
2304  }
2305  if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2306  && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2307  ) {
2308  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2309  }
2310  } elseif ( $action === 'undelete' ) {
2311  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2312  // Undeleting implies editing
2313  $errors[] = [ 'undelete-cantedit' ];
2314  }
2315  if ( !$this->exists()
2316  && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
2317  ) {
2318  // Undeleting where nothing currently exists implies creating
2319  $errors[] = [ 'undelete-cantcreate' ];
2320  }
2321  }
2322  return $errors;
2323  }
2324 
2336  private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2337  global $wgEmailConfirmToEdit, $wgBlockDisablesLogin;
2338  // Account creation blocks handled at userlogin.
2339  // Unblocking handled in SpecialUnblock
2340  if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2341  return $errors;
2342  }
2343 
2344  // Optimize for a very common case
2345  if ( $action === 'read' && !$wgBlockDisablesLogin ) {
2346  return $errors;
2347  }
2348 
2349  if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
2350  $errors[] = [ 'confirmedittext' ];
2351  }
2352 
2353  $useSlave = ( $rigor !== 'secure' );
2354  if ( ( $action == 'edit' || $action == 'create' )
2355  && !$user->isBlockedFrom( $this, $useSlave )
2356  ) {
2357  // Don't block the user from editing their own talk page unless they've been
2358  // explicitly blocked from that too.
2359  } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2360  // @todo FIXME: Pass the relevant context into this function.
2361  $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2362  }
2363 
2364  return $errors;
2365  }
2366 
2378  private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2379  global $wgWhitelistRead, $wgWhitelistReadRegexp;
2380 
2381  $whitelisted = false;
2382  if ( User::isEveryoneAllowed( 'read' ) ) {
2383  # Shortcut for public wikis, allows skipping quite a bit of code
2384  $whitelisted = true;
2385  } elseif ( $user->isAllowed( 'read' ) ) {
2386  # If the user is allowed to read pages, he is allowed to read all pages
2387  $whitelisted = true;
2388  } elseif ( $this->isSpecial( 'Userlogin' )
2389  || $this->isSpecial( 'PasswordReset' )
2390  || $this->isSpecial( 'Userlogout' )
2391  ) {
2392  # Always grant access to the login page.
2393  # Even anons need to be able to log in.
2394  $whitelisted = true;
2395  } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2396  # Time to check the whitelist
2397  # Only do these checks is there's something to check against
2398  $name = $this->getPrefixedText();
2399  $dbName = $this->getPrefixedDBkey();
2400 
2401  // Check for explicit whitelisting with and without underscores
2402  if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2403  $whitelisted = true;
2404  } elseif ( $this->getNamespace() == NS_MAIN ) {
2405  # Old settings might have the title prefixed with
2406  # a colon for main-namespace pages
2407  if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2408  $whitelisted = true;
2409  }
2410  } elseif ( $this->isSpecialPage() ) {
2411  # If it's a special page, ditch the subpage bit and check again
2412  $name = $this->getDBkey();
2413  list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2414  if ( $name ) {
2415  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2416  if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2417  $whitelisted = true;
2418  }
2419  }
2420  }
2421  }
2422 
2423  if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2424  $name = $this->getPrefixedText();
2425  // Check for regex whitelisting
2426  foreach ( $wgWhitelistReadRegexp as $listItem ) {
2427  if ( preg_match( $listItem, $name ) ) {
2428  $whitelisted = true;
2429  break;
2430  }
2431  }
2432  }
2433 
2434  if ( !$whitelisted ) {
2435  # If the title is not whitelisted, give extensions a chance to do so...
2436  Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2437  if ( !$whitelisted ) {
2438  $errors[] = $this->missingPermissionError( $action, $short );
2439  }
2440  }
2441 
2442  return $errors;
2443  }
2444 
2453  private function missingPermissionError( $action, $short ) {
2454  // We avoid expensive display logic for quickUserCan's and such
2455  if ( $short ) {
2456  return [ 'badaccess-group0' ];
2457  }
2458 
2459  $groups = array_map( [ 'User', 'makeGroupLinkWiki' ],
2460  User::getGroupsWithPermission( $action ) );
2461 
2462  if ( count( $groups ) ) {
2463  global $wgLang;
2464  return [
2465  'badaccess-groups',
2466  $wgLang->commaList( $groups ),
2467  count( $groups )
2468  ];
2469  } else {
2470  return [ 'badaccess-group0' ];
2471  }
2472  }
2473 
2489  $action, $user, $rigor = 'secure', $short = false
2490  ) {
2491  if ( $rigor === true ) {
2492  $rigor = 'secure'; // b/c
2493  } elseif ( $rigor === false ) {
2494  $rigor = 'quick'; // b/c
2495  } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2496  throw new Exception( "Invalid rigor parameter '$rigor'." );
2497  }
2498 
2499  # Read has special handling
2500  if ( $action == 'read' ) {
2501  $checks = [
2502  'checkPermissionHooks',
2503  'checkReadPermissions',
2504  'checkUserBlock', // for wgBlockDisablesLogin
2505  ];
2506  # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
2507  # here as it will lead to duplicate error messages. This is okay to do
2508  # since anywhere that checks for create will also check for edit, and
2509  # those checks are called for edit.
2510  } elseif ( $action == 'create' ) {
2511  $checks = [
2512  'checkQuickPermissions',
2513  'checkPermissionHooks',
2514  'checkPageRestrictions',
2515  'checkCascadingSourcesRestrictions',
2516  'checkActionPermissions',
2517  'checkUserBlock'
2518  ];
2519  } else {
2520  $checks = [
2521  'checkQuickPermissions',
2522  'checkPermissionHooks',
2523  'checkSpecialsAndNSPermissions',
2524  'checkCSSandJSPermissions',
2525  'checkPageRestrictions',
2526  'checkCascadingSourcesRestrictions',
2527  'checkActionPermissions',
2528  'checkUserBlock'
2529  ];
2530  }
2531 
2532  $errors = [];
2533  while ( count( $checks ) > 0 &&
2534  !( $short && count( $errors ) > 0 ) ) {
2535  $method = array_shift( $checks );
2536  $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2537  }
2538 
2539  return $errors;
2540  }
2541 
2549  public static function getFilteredRestrictionTypes( $exists = true ) {
2550  global $wgRestrictionTypes;
2551  $types = $wgRestrictionTypes;
2552  if ( $exists ) {
2553  # Remove the create restriction for existing titles
2554  $types = array_diff( $types, [ 'create' ] );
2555  } else {
2556  # Only the create and upload restrictions apply to non-existing titles
2557  $types = array_intersect( $types, [ 'create', 'upload' ] );
2558  }
2559  return $types;
2560  }
2561 
2567  public function getRestrictionTypes() {
2568  if ( $this->isSpecialPage() ) {
2569  return [];
2570  }
2571 
2572  $types = self::getFilteredRestrictionTypes( $this->exists() );
2573 
2574  if ( $this->getNamespace() != NS_FILE ) {
2575  # Remove the upload restriction for non-file titles
2576  $types = array_diff( $types, [ 'upload' ] );
2577  }
2578 
2579  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2580 
2581  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2582  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2583 
2584  return $types;
2585  }
2586 
2594  public function getTitleProtection() {
2595  // Can't protect pages in special namespaces
2596  if ( $this->getNamespace() < 0 ) {
2597  return false;
2598  }
2599 
2600  // Can't protect pages that exist.
2601  if ( $this->exists() ) {
2602  return false;
2603  }
2604 
2605  if ( $this->mTitleProtection === null ) {
2606  $dbr = wfGetDB( DB_REPLICA );
2607  $res = $dbr->select(
2608  'protected_titles',
2609  [
2610  'user' => 'pt_user',
2611  'reason' => 'pt_reason',
2612  'expiry' => 'pt_expiry',
2613  'permission' => 'pt_create_perm'
2614  ],
2615  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2616  __METHOD__
2617  );
2618 
2619  // fetchRow returns false if there are no rows.
2620  $row = $dbr->fetchRow( $res );
2621  if ( $row ) {
2622  if ( $row['permission'] == 'sysop' ) {
2623  $row['permission'] = 'editprotected'; // B/C
2624  }
2625  if ( $row['permission'] == 'autoconfirmed' ) {
2626  $row['permission'] = 'editsemiprotected'; // B/C
2627  }
2628  $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2629  }
2630  $this->mTitleProtection = $row;
2631  }
2632  return $this->mTitleProtection;
2633  }
2634 
2638  public function deleteTitleProtection() {
2639  $dbw = wfGetDB( DB_MASTER );
2640 
2641  $dbw->delete(
2642  'protected_titles',
2643  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2644  __METHOD__
2645  );
2646  $this->mTitleProtection = false;
2647  }
2648 
2656  public function isSemiProtected( $action = 'edit' ) {
2657  global $wgSemiprotectedRestrictionLevels;
2658 
2659  $restrictions = $this->getRestrictions( $action );
2660  $semi = $wgSemiprotectedRestrictionLevels;
2661  if ( !$restrictions || !$semi ) {
2662  // Not protected, or all protection is full protection
2663  return false;
2664  }
2665 
2666  // Remap autoconfirmed to editsemiprotected for BC
2667  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2668  $semi[$key] = 'editsemiprotected';
2669  }
2670  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2671  $restrictions[$key] = 'editsemiprotected';
2672  }
2673 
2674  return !array_diff( $restrictions, $semi );
2675  }
2676 
2684  public function isProtected( $action = '' ) {
2685  global $wgRestrictionLevels;
2686 
2687  $restrictionTypes = $this->getRestrictionTypes();
2688 
2689  # Special pages have inherent protection
2690  if ( $this->isSpecialPage() ) {
2691  return true;
2692  }
2693 
2694  # Check regular protection levels
2695  foreach ( $restrictionTypes as $type ) {
2696  if ( $action == $type || $action == '' ) {
2697  $r = $this->getRestrictions( $type );
2698  foreach ( $wgRestrictionLevels as $level ) {
2699  if ( in_array( $level, $r ) && $level != '' ) {
2700  return true;
2701  }
2702  }
2703  }
2704  }
2705 
2706  return false;
2707  }
2708 
2716  public function isNamespaceProtected( User $user ) {
2718 
2719  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2720  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2721  if ( $right != '' && !$user->isAllowed( $right ) ) {
2722  return true;
2723  }
2724  }
2725  }
2726  return false;
2727  }
2728 
2734  public function isCascadeProtected() {
2735  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2736  return ( $sources > 0 );
2737  }
2738 
2748  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2749  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2750  }
2751 
2765  public function getCascadeProtectionSources( $getPages = true ) {
2766  $pagerestrictions = [];
2767 
2768  if ( $this->mCascadeSources !== null && $getPages ) {
2770  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2771  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2772  }
2773 
2774  $dbr = wfGetDB( DB_REPLICA );
2775 
2776  if ( $this->getNamespace() == NS_FILE ) {
2777  $tables = [ 'imagelinks', 'page_restrictions' ];
2778  $where_clauses = [
2779  'il_to' => $this->getDBkey(),
2780  'il_from=pr_page',
2781  'pr_cascade' => 1
2782  ];
2783  } else {
2784  $tables = [ 'templatelinks', 'page_restrictions' ];
2785  $where_clauses = [
2786  'tl_namespace' => $this->getNamespace(),
2787  'tl_title' => $this->getDBkey(),
2788  'tl_from=pr_page',
2789  'pr_cascade' => 1
2790  ];
2791  }
2792 
2793  if ( $getPages ) {
2794  $cols = [ 'pr_page', 'page_namespace', 'page_title',
2795  'pr_expiry', 'pr_type', 'pr_level' ];
2796  $where_clauses[] = 'page_id=pr_page';
2797  $tables[] = 'page';
2798  } else {
2799  $cols = [ 'pr_expiry' ];
2800  }
2801 
2802  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2803 
2804  $sources = $getPages ? [] : false;
2805  $now = wfTimestampNow();
2806 
2807  foreach ( $res as $row ) {
2808  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2809  if ( $expiry > $now ) {
2810  if ( $getPages ) {
2811  $page_id = $row->pr_page;
2812  $page_ns = $row->page_namespace;
2813  $page_title = $row->page_title;
2814  $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2815  # Add groups needed for each restriction type if its not already there
2816  # Make sure this restriction type still exists
2817 
2818  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2819  $pagerestrictions[$row->pr_type] = [];
2820  }
2821 
2822  if (
2823  isset( $pagerestrictions[$row->pr_type] )
2824  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2825  ) {
2826  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2827  }
2828  } else {
2829  $sources = true;
2830  }
2831  }
2832  }
2833 
2834  if ( $getPages ) {
2835  $this->mCascadeSources = $sources;
2836  $this->mCascadingRestrictions = $pagerestrictions;
2837  } else {
2838  $this->mHasCascadingRestrictions = $sources;
2839  }
2840 
2841  return [ $sources, $pagerestrictions ];
2842  }
2843 
2851  public function areRestrictionsLoaded() {
2853  }
2854 
2864  public function getRestrictions( $action ) {
2865  if ( !$this->mRestrictionsLoaded ) {
2866  $this->loadRestrictions();
2867  }
2868  return isset( $this->mRestrictions[$action] )
2869  ? $this->mRestrictions[$action]
2870  : [];
2871  }
2872 
2880  public function getAllRestrictions() {
2881  if ( !$this->mRestrictionsLoaded ) {
2882  $this->loadRestrictions();
2883  }
2884  return $this->mRestrictions;
2885  }
2886 
2894  public function getRestrictionExpiry( $action ) {
2895  if ( !$this->mRestrictionsLoaded ) {
2896  $this->loadRestrictions();
2897  }
2898  return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2899  }
2900 
2907  if ( !$this->mRestrictionsLoaded ) {
2908  $this->loadRestrictions();
2909  }
2910 
2912  }
2913 
2923  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2924  $dbr = wfGetDB( DB_REPLICA );
2925 
2926  $restrictionTypes = $this->getRestrictionTypes();
2927 
2928  foreach ( $restrictionTypes as $type ) {
2929  $this->mRestrictions[$type] = [];
2930  $this->mRestrictionsExpiry[$type] = 'infinity';
2931  }
2932 
2933  $this->mCascadeRestriction = false;
2934 
2935  # Backwards-compatibility: also load the restrictions from the page record (old format).
2936  if ( $oldFashionedRestrictions !== null ) {
2937  $this->mOldRestrictions = $oldFashionedRestrictions;
2938  }
2939 
2940  if ( $this->mOldRestrictions === false ) {
2941  $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2942  [ 'page_id' => $this->getArticleID() ], __METHOD__ );
2943  }
2944 
2945  if ( $this->mOldRestrictions != '' ) {
2946  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2947  $temp = explode( '=', trim( $restrict ) );
2948  if ( count( $temp ) == 1 ) {
2949  // old old format should be treated as edit/move restriction
2950  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2951  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2952  } else {
2953  $restriction = trim( $temp[1] );
2954  if ( $restriction != '' ) { // some old entries are empty
2955  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2956  }
2957  }
2958  }
2959  }
2960 
2961  if ( count( $rows ) ) {
2962  # Current system - load second to make them override.
2963  $now = wfTimestampNow();
2964 
2965  # Cycle through all the restrictions.
2966  foreach ( $rows as $row ) {
2967 
2968  // Don't take care of restrictions types that aren't allowed
2969  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2970  continue;
2971  }
2972 
2973  // This code should be refactored, now that it's being used more generally,
2974  // But I don't really see any harm in leaving it in Block for now -werdna
2975  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2976 
2977  // Only apply the restrictions if they haven't expired!
2978  if ( !$expiry || $expiry > $now ) {
2979  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2980  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2981 
2982  $this->mCascadeRestriction |= $row->pr_cascade;
2983  }
2984  }
2985  }
2986 
2987  $this->mRestrictionsLoaded = true;
2988  }
2989 
2996  public function loadRestrictions( $oldFashionedRestrictions = null ) {
2997  if ( $this->mRestrictionsLoaded ) {
2998  return;
2999  }
3000 
3001  $id = $this->getArticleID();
3002  if ( $id ) {
3004  $rows = $cache->getWithSetCallback(
3005  // Page protections always leave a new null revision
3006  $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ),
3007  $cache::TTL_DAY,
3008  function ( $curValue, &$ttl, array &$setOpts ) {
3009  $dbr = wfGetDB( DB_REPLICA );
3010 
3011  $setOpts += Database::getCacheSetOptions( $dbr );
3012 
3013  return iterator_to_array(
3014  $dbr->select(
3015  'page_restrictions',
3016  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
3017  [ 'pr_page' => $this->getArticleID() ],
3018  __METHOD__
3019  )
3020  );
3021  }
3022  );
3023 
3024  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
3025  } else {
3026  $title_protection = $this->getTitleProtection();
3027 
3028  if ( $title_protection ) {
3029  $now = wfTimestampNow();
3030  $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
3031 
3032  if ( !$expiry || $expiry > $now ) {
3033  // Apply the restrictions
3034  $this->mRestrictionsExpiry['create'] = $expiry;
3035  $this->mRestrictions['create'] =
3036  explode( ',', trim( $title_protection['permission'] ) );
3037  } else { // Get rid of the old restrictions
3038  $this->mTitleProtection = false;
3039  }
3040  } else {
3041  $this->mRestrictionsExpiry['create'] = 'infinity';
3042  }
3043  $this->mRestrictionsLoaded = true;
3044  }
3045  }
3046 
3051  public function flushRestrictions() {
3052  $this->mRestrictionsLoaded = false;
3053  $this->mTitleProtection = null;
3054  }
3055 
3061  static function purgeExpiredRestrictions() {
3062  if ( wfReadOnly() ) {
3063  return;
3064  }
3065 
3067  wfGetDB( DB_MASTER ),
3068  __METHOD__,
3069  function ( IDatabase $dbw, $fname ) {
3070  $config = MediaWikiServices::getInstance()->getMainConfig();
3071  $ids = $dbw->selectFieldValues(
3072  'page_restrictions',
3073  'pr_id',
3074  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3075  $fname,
3076  [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
3077  );
3078  if ( $ids ) {
3079  $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
3080  }
3081  }
3082  ) );
3083 
3085  wfGetDB( DB_MASTER ),
3086  __METHOD__,
3087  function ( IDatabase $dbw, $fname ) {
3088  $dbw->delete(
3089  'protected_titles',
3090  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3091  $fname
3092  );
3093  }
3094  ) );
3095  }
3096 
3102  public function hasSubpages() {
3103  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3104  # Duh
3105  return false;
3106  }
3107 
3108  # We dynamically add a member variable for the purpose of this method
3109  # alone to cache the result. There's no point in having it hanging
3110  # around uninitialized in every Title object; therefore we only add it
3111  # if needed and don't declare it statically.
3112  if ( $this->mHasSubpages === null ) {
3113  $this->mHasSubpages = false;
3114  $subpages = $this->getSubpages( 1 );
3115  if ( $subpages instanceof TitleArray ) {
3116  $this->mHasSubpages = (bool)$subpages->count();
3117  }
3118  }
3119 
3120  return $this->mHasSubpages;
3121  }
3122 
3130  public function getSubpages( $limit = -1 ) {
3131  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3132  return [];
3133  }
3134 
3135  $dbr = wfGetDB( DB_REPLICA );
3136  $conds['page_namespace'] = $this->getNamespace();
3137  $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3138  $options = [];
3139  if ( $limit > -1 ) {
3140  $options['LIMIT'] = $limit;
3141  }
3142  $this->mSubpages = TitleArray::newFromResult(
3143  $dbr->select( 'page',
3144  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3145  $conds,
3146  __METHOD__,
3147  $options
3148  )
3149  );
3150  return $this->mSubpages;
3151  }
3152 
3158  public function isDeleted() {
3159  if ( $this->getNamespace() < 0 ) {
3160  $n = 0;
3161  } else {
3162  $dbr = wfGetDB( DB_REPLICA );
3163 
3164  $n = $dbr->selectField( 'archive', 'COUNT(*)',
3165  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3166  __METHOD__
3167  );
3168  if ( $this->getNamespace() == NS_FILE ) {
3169  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3170  [ 'fa_name' => $this->getDBkey() ],
3171  __METHOD__
3172  );
3173  }
3174  }
3175  return (int)$n;
3176  }
3177 
3183  public function isDeletedQuick() {
3184  if ( $this->getNamespace() < 0 ) {
3185  return false;
3186  }
3187  $dbr = wfGetDB( DB_REPLICA );
3188  $deleted = (bool)$dbr->selectField( 'archive', '1',
3189  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3190  __METHOD__
3191  );
3192  if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3193  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3194  [ 'fa_name' => $this->getDBkey() ],
3195  __METHOD__
3196  );
3197  }
3198  return $deleted;
3199  }
3200 
3209  public function getArticleID( $flags = 0 ) {
3210  if ( $this->getNamespace() < 0 ) {
3211  $this->mArticleID = 0;
3212  return $this->mArticleID;
3213  }
3214  $linkCache = LinkCache::singleton();
3215  if ( $flags & self::GAID_FOR_UPDATE ) {
3216  $oldUpdate = $linkCache->forUpdate( true );
3217  $linkCache->clearLink( $this );
3218  $this->mArticleID = $linkCache->addLinkObj( $this );
3219  $linkCache->forUpdate( $oldUpdate );
3220  } else {
3221  if ( -1 == $this->mArticleID ) {
3222  $this->mArticleID = $linkCache->addLinkObj( $this );
3223  }
3224  }
3225  return $this->mArticleID;
3226  }
3227 
3235  public function isRedirect( $flags = 0 ) {
3236  if ( !is_null( $this->mRedirect ) ) {
3237  return $this->mRedirect;
3238  }
3239  if ( !$this->getArticleID( $flags ) ) {
3240  $this->mRedirect = false;
3241  return $this->mRedirect;
3242  }
3243 
3244  $linkCache = LinkCache::singleton();
3245  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3246  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3247  if ( $cached === null ) {
3248  # Trust LinkCache's state over our own
3249  # LinkCache is telling us that the page doesn't exist, despite there being cached
3250  # data relating to an existing page in $this->mArticleID. Updaters should clear
3251  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3252  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3253  # LinkCache to refresh its data from the master.
3254  $this->mRedirect = false;
3255  return $this->mRedirect;
3256  }
3257 
3258  $this->mRedirect = (bool)$cached;
3259 
3260  return $this->mRedirect;
3261  }
3262 
3270  public function getLength( $flags = 0 ) {
3271  if ( $this->mLength != -1 ) {
3272  return $this->mLength;
3273  }
3274  if ( !$this->getArticleID( $flags ) ) {
3275  $this->mLength = 0;
3276  return $this->mLength;
3277  }
3278  $linkCache = LinkCache::singleton();
3279  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3280  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3281  if ( $cached === null ) {
3282  # Trust LinkCache's state over our own, as for isRedirect()
3283  $this->mLength = 0;
3284  return $this->mLength;
3285  }
3286 
3287  $this->mLength = intval( $cached );
3288 
3289  return $this->mLength;
3290  }
3291 
3298  public function getLatestRevID( $flags = 0 ) {
3299  if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3300  return intval( $this->mLatestID );
3301  }
3302  if ( !$this->getArticleID( $flags ) ) {
3303  $this->mLatestID = 0;
3304  return $this->mLatestID;
3305  }
3306  $linkCache = LinkCache::singleton();
3307  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3308  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3309  if ( $cached === null ) {
3310  # Trust LinkCache's state over our own, as for isRedirect()
3311  $this->mLatestID = 0;
3312  return $this->mLatestID;
3313  }
3314 
3315  $this->mLatestID = intval( $cached );
3316 
3317  return $this->mLatestID;
3318  }
3319 
3330  public function resetArticleID( $newid ) {
3331  $linkCache = LinkCache::singleton();
3332  $linkCache->clearLink( $this );
3333 
3334  if ( $newid === false ) {
3335  $this->mArticleID = -1;
3336  } else {
3337  $this->mArticleID = intval( $newid );
3338  }
3339  $this->mRestrictionsLoaded = false;
3340  $this->mRestrictions = [];
3341  $this->mOldRestrictions = false;
3342  $this->mRedirect = null;
3343  $this->mLength = -1;
3344  $this->mLatestID = false;
3345  $this->mContentModel = false;
3346  $this->mEstimateRevisions = null;
3347  $this->mPageLanguage = false;
3348  $this->mDbPageLanguage = false;
3349  $this->mIsBigDeletion = null;
3350  }
3351 
3352  public static function clearCaches() {
3353  $linkCache = LinkCache::singleton();
3354  $linkCache->clear();
3355 
3356  $titleCache = self::getTitleCache();
3357  $titleCache->clear();
3358  }
3359 
3367  public static function capitalize( $text, $ns = NS_MAIN ) {
3369 
3370  if ( MWNamespace::isCapitalized( $ns ) ) {
3371  return $wgContLang->ucfirst( $text );
3372  } else {
3373  return $text;
3374  }
3375  }
3376 
3389  private function secureAndSplit() {
3390  # Initialisation
3391  $this->mInterwiki = '';
3392  $this->mFragment = '';
3393  $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3394 
3395  $dbkey = $this->mDbkeyform;
3396 
3397  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3398  // the parsing code with Title, while avoiding massive refactoring.
3399  // @todo: get rid of secureAndSplit, refactor parsing code.
3400  // @note: getTitleParser() returns a TitleParser implementation which does not have a
3401  // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3402  $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3403  // MalformedTitleException can be thrown here
3404  $parts = $titleCodec->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3405 
3406  # Fill fields
3407  $this->setFragment( '#' . $parts['fragment'] );
3408  $this->mInterwiki = $parts['interwiki'];
3409  $this->mLocalInterwiki = $parts['local_interwiki'];
3410  $this->mNamespace = $parts['namespace'];
3411  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3412 
3413  $this->mDbkeyform = $parts['dbkey'];
3414  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3415  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3416 
3417  # We already know that some pages won't be in the database!
3418  if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3419  $this->mArticleID = 0;
3420  }
3421 
3422  return true;
3423  }
3424 
3437  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3438  if ( count( $options ) > 0 ) {
3439  $db = wfGetDB( DB_MASTER );
3440  } else {
3441  $db = wfGetDB( DB_REPLICA );
3442  }
3443 
3444  $res = $db->select(
3445  [ 'page', $table ],
3446  self::getSelectFields(),
3447  [
3448  "{$prefix}_from=page_id",
3449  "{$prefix}_namespace" => $this->getNamespace(),
3450  "{$prefix}_title" => $this->getDBkey() ],
3451  __METHOD__,
3452  $options
3453  );
3454 
3455  $retVal = [];
3456  if ( $res->numRows() ) {
3457  $linkCache = LinkCache::singleton();
3458  foreach ( $res as $row ) {
3459  $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3460  if ( $titleObj ) {
3461  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3462  $retVal[] = $titleObj;
3463  }
3464  }
3465  }
3466  return $retVal;
3467  }
3468 
3479  public function getTemplateLinksTo( $options = [] ) {
3480  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3481  }
3482 
3495  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3496  $id = $this->getArticleID();
3497 
3498  # If the page doesn't exist; there can't be any link from this page
3499  if ( !$id ) {
3500  return [];
3501  }
3502 
3503  $db = wfGetDB( DB_REPLICA );
3504 
3505  $blNamespace = "{$prefix}_namespace";
3506  $blTitle = "{$prefix}_title";
3507 
3508  $res = $db->select(
3509  [ $table, 'page' ],
3510  array_merge(
3511  [ $blNamespace, $blTitle ],
3513  ),
3514  [ "{$prefix}_from" => $id ],
3515  __METHOD__,
3516  $options,
3517  [ 'page' => [
3518  'LEFT JOIN',
3519  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3520  ] ]
3521  );
3522 
3523  $retVal = [];
3524  $linkCache = LinkCache::singleton();
3525  foreach ( $res as $row ) {
3526  if ( $row->page_id ) {
3527  $titleObj = Title::newFromRow( $row );
3528  } else {
3529  $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3530  $linkCache->addBadLinkObj( $titleObj );
3531  }
3532  $retVal[] = $titleObj;
3533  }
3534 
3535  return $retVal;
3536  }
3537 
3548  public function getTemplateLinksFrom( $options = [] ) {
3549  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3550  }
3551 
3560  public function getBrokenLinksFrom() {
3561  if ( $this->getArticleID() == 0 ) {
3562  # All links from article ID 0 are false positives
3563  return [];
3564  }
3565 
3566  $dbr = wfGetDB( DB_REPLICA );
3567  $res = $dbr->select(
3568  [ 'page', 'pagelinks' ],
3569  [ 'pl_namespace', 'pl_title' ],
3570  [
3571  'pl_from' => $this->getArticleID(),
3572  'page_namespace IS NULL'
3573  ],
3574  __METHOD__, [],
3575  [
3576  'page' => [
3577  'LEFT JOIN',
3578  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3579  ]
3580  ]
3581  );
3582 
3583  $retVal = [];
3584  foreach ( $res as $row ) {
3585  $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3586  }
3587  return $retVal;
3588  }
3589 
3596  public function getCdnUrls() {
3597  $urls = [
3598  $this->getInternalURL(),
3599  $this->getInternalURL( 'action=history' )
3600  ];
3601 
3602  $pageLang = $this->getPageLanguage();
3603  if ( $pageLang->hasVariants() ) {
3604  $variants = $pageLang->getVariants();
3605  foreach ( $variants as $vCode ) {
3606  $urls[] = $this->getInternalURL( $vCode );
3607  }
3608  }
3609 
3610  // If we are looking at a css/js user subpage, purge the action=raw.
3611  if ( $this->isJsSubpage() ) {
3612  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3613  } elseif ( $this->isCssSubpage() ) {
3614  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3615  }
3616 
3617  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3618  return $urls;
3619  }
3620 
3624  public function getSquidURLs() {
3625  return $this->getCdnUrls();
3626  }
3627 
3631  public function purgeSquid() {
3633  new CdnCacheUpdate( $this->getCdnUrls() ),
3635  );
3636  }
3637 
3645  public function moveNoAuth( &$nt ) {
3646  wfDeprecated( __METHOD__, '1.25' );
3647  return $this->moveTo( $nt, false );
3648  }
3649 
3660  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3661  global $wgUser;
3662 
3663  if ( !( $nt instanceof Title ) ) {
3664  // Normally we'd add this to $errors, but we'll get
3665  // lots of syntax errors if $nt is not an object
3666  return [ [ 'badtitletext' ] ];
3667  }
3668 
3669  $mp = new MovePage( $this, $nt );
3670  $errors = $mp->isValidMove()->getErrorsArray();
3671  if ( $auth ) {
3672  $errors = wfMergeErrorArrays(
3673  $errors,
3674  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3675  );
3676  }
3677 
3678  return $errors ?: true;
3679  }
3680 
3687  protected function validateFileMoveOperation( $nt ) {
3688  global $wgUser;
3689 
3690  $errors = [];
3691 
3692  $destFile = wfLocalFile( $nt );
3693  $destFile->load( File::READ_LATEST );
3694  if ( !$wgUser->isAllowed( 'reupload-shared' )
3695  && !$destFile->exists() && wfFindFile( $nt )
3696  ) {
3697  $errors[] = [ 'file-exists-sharedrepo' ];
3698  }
3699 
3700  return $errors;
3701  }
3702 
3715  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
3716  global $wgUser;
3717  $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3718  if ( is_array( $err ) ) {
3719  // Auto-block user's IP if the account was "hard" blocked
3720  $wgUser->spreadAnyEditBlock();
3721  return $err;
3722  }
3723  // Check suppressredirect permission
3724  if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3725  $createRedirect = true;
3726  }
3727 
3728  $mp = new MovePage( $this, $nt );
3729  $status = $mp->move( $wgUser, $reason, $createRedirect );
3730  if ( $status->isOK() ) {
3731  return true;
3732  } else {
3733  return $status->getErrorsArray();
3734  }
3735  }
3736 
3749  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
3750  global $wgMaximumMovedPages;
3751  // Check permissions
3752  if ( !$this->userCan( 'move-subpages' ) ) {
3753  return [ 'cant-move-subpages' ];
3754  }
3755  // Do the source and target namespaces support subpages?
3756  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3757  return [ 'namespace-nosubpages',
3759  }
3760  if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3761  return [ 'namespace-nosubpages',
3762  MWNamespace::getCanonicalName( $nt->getNamespace() ) ];
3763  }
3764 
3765  $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3766  $retval = [];
3767  $count = 0;
3768  foreach ( $subpages as $oldSubpage ) {
3769  $count++;
3770  if ( $count > $wgMaximumMovedPages ) {
3771  $retval[$oldSubpage->getPrefixedText()] =
3772  [ 'movepage-max-pages',
3773  $wgMaximumMovedPages ];
3774  break;
3775  }
3776 
3777  // We don't know whether this function was called before
3778  // or after moving the root page, so check both
3779  // $this and $nt
3780  if ( $oldSubpage->getArticleID() == $this->getArticleID()
3781  || $oldSubpage->getArticleID() == $nt->getArticleID()
3782  ) {
3783  // When moving a page to a subpage of itself,
3784  // don't move it twice
3785  continue;
3786  }
3787  $newPageName = preg_replace(
3788  '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3789  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
3790  $oldSubpage->getDBkey() );
3791  if ( $oldSubpage->isTalkPage() ) {
3792  $newNs = $nt->getTalkPage()->getNamespace();
3793  } else {
3794  $newNs = $nt->getSubjectPage()->getNamespace();
3795  }
3796  # Bug 14385: we need makeTitleSafe because the new page names may
3797  # be longer than 255 characters.
3798  $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3799 
3800  $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
3801  if ( $success === true ) {
3802  $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3803  } else {
3804  $retval[$oldSubpage->getPrefixedText()] = $success;
3805  }
3806  }
3807  return $retval;
3808  }
3809 
3816  public function isSingleRevRedirect() {
3817  global $wgContentHandlerUseDB;
3818 
3819  $dbw = wfGetDB( DB_MASTER );
3820 
3821  # Is it a redirect?
3822  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3823  if ( $wgContentHandlerUseDB ) {
3824  $fields[] = 'page_content_model';
3825  }
3826 
3827  $row = $dbw->selectRow( 'page',
3828  $fields,
3829  $this->pageCond(),
3830  __METHOD__,
3831  [ 'FOR UPDATE' ]
3832  );
3833  # Cache some fields we may want
3834  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3835  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3836  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3837  $this->mContentModel = $row && isset( $row->page_content_model )
3838  ? strval( $row->page_content_model )
3839  : false;
3840 
3841  if ( !$this->mRedirect ) {
3842  return false;
3843  }
3844  # Does the article have a history?
3845  $row = $dbw->selectField( [ 'page', 'revision' ],
3846  'rev_id',
3847  [ 'page_namespace' => $this->getNamespace(),
3848  'page_title' => $this->getDBkey(),
3849  'page_id=rev_page',
3850  'page_latest != rev_id'
3851  ],
3852  __METHOD__,
3853  [ 'FOR UPDATE' ]
3854  );
3855  # Return true if there was no history
3856  return ( $row === false );
3857  }
3858 
3867  public function isValidMoveTarget( $nt ) {
3868  # Is it an existing file?
3869  if ( $nt->getNamespace() == NS_FILE ) {
3870  $file = wfLocalFile( $nt );
3871  $file->load( File::READ_LATEST );
3872  if ( $file->exists() ) {
3873  wfDebug( __METHOD__ . ": file exists\n" );
3874  return false;
3875  }
3876  }
3877  # Is it a redirect with no history?
3878  if ( !$nt->isSingleRevRedirect() ) {
3879  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3880  return false;
3881  }
3882  # Get the article text
3883  $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
3884  if ( !is_object( $rev ) ) {
3885  return false;
3886  }
3887  $content = $rev->getContent();
3888  # Does the redirect point to the source?
3889  # Or is it a broken self-redirect, usually caused by namespace collisions?
3890  $redirTitle = $content ? $content->getRedirectTarget() : null;
3891 
3892  if ( $redirTitle ) {
3893  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3894  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3895  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3896  return false;
3897  } else {
3898  return true;
3899  }
3900  } else {
3901  # Fail safe (not a redirect after all. strange.)
3902  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3903  " is a redirect, but it doesn't contain a valid redirect.\n" );
3904  return false;
3905  }
3906  }
3907 
3915  public function getParentCategories() {
3917 
3918  $data = [];
3919 
3920  $titleKey = $this->getArticleID();
3921 
3922  if ( $titleKey === 0 ) {
3923  return $data;
3924  }
3925 
3926  $dbr = wfGetDB( DB_REPLICA );
3927 
3928  $res = $dbr->select(
3929  'categorylinks',
3930  'cl_to',
3931  [ 'cl_from' => $titleKey ],
3932  __METHOD__
3933  );
3934 
3935  if ( $res->numRows() > 0 ) {
3936  foreach ( $res as $row ) {
3937  // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3938  $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3939  }
3940  }
3941  return $data;
3942  }
3943 
3950  public function getParentCategoryTree( $children = [] ) {
3951  $stack = [];
3952  $parents = $this->getParentCategories();
3953 
3954  if ( $parents ) {
3955  foreach ( $parents as $parent => $current ) {
3956  if ( array_key_exists( $parent, $children ) ) {
3957  # Circular reference
3958  $stack[$parent] = [];
3959  } else {
3960  $nt = Title::newFromText( $parent );
3961  if ( $nt ) {
3962  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3963  }
3964  }
3965  }
3966  }
3967 
3968  return $stack;
3969  }
3970 
3977  public function pageCond() {
3978  if ( $this->mArticleID > 0 ) {
3979  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3980  return [ 'page_id' => $this->mArticleID ];
3981  } else {
3982  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3983  }
3984  }
3985 
3993  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3994  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
3995  $revId = $db->selectField( 'revision', 'rev_id',
3996  [
3997  'rev_page' => $this->getArticleID( $flags ),
3998  'rev_id < ' . intval( $revId )
3999  ],
4000  __METHOD__,
4001  [ 'ORDER BY' => 'rev_id DESC' ]
4002  );
4003 
4004  if ( $revId === false ) {
4005  return false;
4006  } else {
4007  return intval( $revId );
4008  }
4009  }
4010 
4018  public function getNextRevisionID( $revId, $flags = 0 ) {
4019  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
4020  $revId = $db->selectField( 'revision', 'rev_id',
4021  [
4022  'rev_page' => $this->getArticleID( $flags ),
4023  'rev_id > ' . intval( $revId )
4024  ],
4025  __METHOD__,
4026  [ 'ORDER BY' => 'rev_id' ]
4027  );
4028 
4029  if ( $revId === false ) {
4030  return false;
4031  } else {
4032  return intval( $revId );
4033  }
4034  }
4035 
4042  public function getFirstRevision( $flags = 0 ) {
4043  $pageId = $this->getArticleID( $flags );
4044  if ( $pageId ) {
4045  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
4046  $row = $db->selectRow( 'revision', Revision::selectFields(),
4047  [ 'rev_page' => $pageId ],
4048  __METHOD__,
4049  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ]
4050  );
4051  if ( $row ) {
4052  return new Revision( $row );
4053  }
4054  }
4055  return null;
4056  }
4057 
4064  public function getEarliestRevTime( $flags = 0 ) {
4065  $rev = $this->getFirstRevision( $flags );
4066  return $rev ? $rev->getTimestamp() : null;
4067  }
4068 
4074  public function isNewPage() {
4075  $dbr = wfGetDB( DB_REPLICA );
4076  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4077  }
4078 
4084  public function isBigDeletion() {
4085  global $wgDeleteRevisionsLimit;
4086 
4087  if ( !$wgDeleteRevisionsLimit ) {
4088  return false;
4089  }
4090 
4091  if ( $this->mIsBigDeletion === null ) {
4092  $dbr = wfGetDB( DB_REPLICA );
4093 
4094  $revCount = $dbr->selectRowCount(
4095  'revision',
4096  '1',
4097  [ 'rev_page' => $this->getArticleID() ],
4098  __METHOD__,
4099  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4100  );
4101 
4102  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4103  }
4104 
4105  return $this->mIsBigDeletion;
4106  }
4107 
4113  public function estimateRevisionCount() {
4114  if ( !$this->exists() ) {
4115  return 0;
4116  }
4117 
4118  if ( $this->mEstimateRevisions === null ) {
4119  $dbr = wfGetDB( DB_REPLICA );
4120  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4121  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4122  }
4123 
4125  }
4126 
4136  public function countRevisionsBetween( $old, $new, $max = null ) {
4137  if ( !( $old instanceof Revision ) ) {
4138  $old = Revision::newFromTitle( $this, (int)$old );
4139  }
4140  if ( !( $new instanceof Revision ) ) {
4141  $new = Revision::newFromTitle( $this, (int)$new );
4142  }
4143  if ( !$old || !$new ) {
4144  return 0; // nothing to compare
4145  }
4146  $dbr = wfGetDB( DB_REPLICA );
4147  $conds = [
4148  'rev_page' => $this->getArticleID(),
4149  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4150  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4151  ];
4152  if ( $max !== null ) {
4153  return $dbr->selectRowCount( 'revision', '1',
4154  $conds,
4155  __METHOD__,
4156  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4157  );
4158  } else {
4159  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4160  }
4161  }
4162 
4179  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4180  if ( !( $old instanceof Revision ) ) {
4181  $old = Revision::newFromTitle( $this, (int)$old );
4182  }
4183  if ( !( $new instanceof Revision ) ) {
4184  $new = Revision::newFromTitle( $this, (int)$new );
4185  }
4186  // XXX: what if Revision objects are passed in, but they don't refer to this title?
4187  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4188  // in the sanity check below?
4189  if ( !$old || !$new ) {
4190  return null; // nothing to compare
4191  }
4192  $authors = [];
4193  $old_cmp = '>';
4194  $new_cmp = '<';
4195  $options = (array)$options;
4196  if ( in_array( 'include_old', $options ) ) {
4197  $old_cmp = '>=';
4198  }
4199  if ( in_array( 'include_new', $options ) ) {
4200  $new_cmp = '<=';
4201  }
4202  if ( in_array( 'include_both', $options ) ) {
4203  $old_cmp = '>=';
4204  $new_cmp = '<=';
4205  }
4206  // No DB query needed if $old and $new are the same or successive revisions:
4207  if ( $old->getId() === $new->getId() ) {
4208  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4209  [] :
4210  [ $old->getUserText( Revision::RAW ) ];
4211  } elseif ( $old->getId() === $new->getParentId() ) {
4212  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4213  $authors[] = $old->getUserText( Revision::RAW );
4214  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4215  $authors[] = $new->getUserText( Revision::RAW );
4216  }
4217  } elseif ( $old_cmp === '>=' ) {
4218  $authors[] = $old->getUserText( Revision::RAW );
4219  } elseif ( $new_cmp === '<=' ) {
4220  $authors[] = $new->getUserText( Revision::RAW );
4221  }
4222  return $authors;
4223  }
4224  $dbr = wfGetDB( DB_REPLICA );
4225  $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4226  [
4227  'rev_page' => $this->getArticleID(),
4228  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4229  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4230  ], __METHOD__,
4231  [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
4232  );
4233  foreach ( $res as $row ) {
4234  $authors[] = $row->rev_user_text;
4235  }
4236  return $authors;
4237  }
4238 
4253  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4254  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4255  return $authors ? count( $authors ) : 0;
4256  }
4257 
4264  public function equals( Title $title ) {
4265  // Note: === is necessary for proper matching of number-like titles.
4266  return $this->getInterwiki() === $title->getInterwiki()
4267  && $this->getNamespace() == $title->getNamespace()
4268  && $this->getDBkey() === $title->getDBkey();
4269  }
4270 
4277  public function isSubpageOf( Title $title ) {
4278  return $this->getInterwiki() === $title->getInterwiki()
4279  && $this->getNamespace() == $title->getNamespace()
4280  && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4281  }
4282 
4294  public function exists( $flags = 0 ) {
4295  $exists = $this->getArticleID( $flags ) != 0;
4296  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4297  return $exists;
4298  }
4299 
4316  public function isAlwaysKnown() {
4317  $isKnown = null;
4318 
4329  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4330 
4331  if ( !is_null( $isKnown ) ) {
4332  return $isKnown;
4333  }
4334 
4335  if ( $this->isExternal() ) {
4336  return true; // any interwiki link might be viewable, for all we know
4337  }
4338 
4339  switch ( $this->mNamespace ) {
4340  case NS_MEDIA:
4341  case NS_FILE:
4342  // file exists, possibly in a foreign repo
4343  return (bool)wfFindFile( $this );
4344  case NS_SPECIAL:
4345  // valid special page
4346  return SpecialPageFactory::exists( $this->getDBkey() );
4347  case NS_MAIN:
4348  // selflink, possibly with fragment
4349  return $this->mDbkeyform == '';
4350  case NS_MEDIAWIKI:
4351  // known system message
4352  return $this->hasSourceText() !== false;
4353  default:
4354  return false;
4355  }
4356  }
4357 
4369  public function isKnown() {
4370  return $this->isAlwaysKnown() || $this->exists();
4371  }
4372 
4378  public function hasSourceText() {
4379  if ( $this->exists() ) {
4380  return true;
4381  }
4382 
4383  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4384  // If the page doesn't exist but is a known system message, default
4385  // message content will be displayed, same for language subpages-
4386  // Use always content language to avoid loading hundreds of languages
4387  // to get the link color.
4389  list( $name, ) = MessageCache::singleton()->figureMessage(
4390  $wgContLang->lcfirst( $this->getText() )
4391  );
4392  $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4393  return $message->exists();
4394  }
4395 
4396  return false;
4397  }
4398 
4404  public function getDefaultMessageText() {
4406 
4407  if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4408  return false;
4409  }
4410 
4411  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4412  $wgContLang->lcfirst( $this->getText() )
4413  );
4414  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4415 
4416  if ( $message->exists() ) {
4417  return $message->plain();
4418  } else {
4419  return false;
4420  }
4421  }
4422 
4429  public function invalidateCache( $purgeTime = null ) {
4430  if ( wfReadOnly() ) {
4431  return false;
4432  } elseif ( $this->mArticleID === 0 ) {
4433  return true; // avoid gap locking if we know it's not there
4434  }
4435 
4436  $dbw = wfGetDB( DB_MASTER );
4437  $dbw->onTransactionPreCommitOrIdle( function () {
4439  } );
4440 
4441  $conds = $this->pageCond();
4443  new AutoCommitUpdate(
4444  $dbw,
4445  __METHOD__,
4446  function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4447  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4448  $dbw->update(
4449  'page',
4450  [ 'page_touched' => $dbTimestamp ],
4451  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4452  $fname
4453  );
4454  MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4455  }
4456  ),
4458  );
4459 
4460  return true;
4461  }
4462 
4468  public function touchLinks() {
4469  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4470  if ( $this->getNamespace() == NS_CATEGORY ) {
4471  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4472  }
4473  }
4474 
4481  public function getTouched( $db = null ) {
4482  if ( $db === null ) {
4483  $db = wfGetDB( DB_REPLICA );
4484  }
4485  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4486  return $touched;
4487  }
4488 
4495  public function getNotificationTimestamp( $user = null ) {
4496  global $wgUser;
4497 
4498  // Assume current user if none given
4499  if ( !$user ) {
4500  $user = $wgUser;
4501  }
4502  // Check cache first
4503  $uid = $user->getId();
4504  if ( !$uid ) {
4505  return false;
4506  }
4507  // avoid isset here, as it'll return false for null entries
4508  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4509  return $this->mNotificationTimestamp[$uid];
4510  }
4511  // Don't cache too much!
4512  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4513  $this->mNotificationTimestamp = [];
4514  }
4515 
4516  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4517  $watchedItem = $store->getWatchedItem( $user, $this );
4518  if ( $watchedItem ) {
4519  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4520  } else {
4521  $this->mNotificationTimestamp[$uid] = false;
4522  }
4523 
4524  return $this->mNotificationTimestamp[$uid];
4525  }
4526 
4533  public function getNamespaceKey( $prepend = 'nstab-' ) {
4535  // Gets the subject namespace if this title
4536  $namespace = MWNamespace::getSubject( $this->getNamespace() );
4537  // Checks if canonical namespace name exists for namespace
4538  if ( MWNamespace::exists( $this->getNamespace() ) ) {
4539  // Uses canonical namespace name
4540  $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4541  } else {
4542  // Uses text of namespace
4543  $namespaceKey = $this->getSubjectNsText();
4544  }
4545  // Makes namespace key lowercase
4546  $namespaceKey = $wgContLang->lc( $namespaceKey );
4547  // Uses main
4548  if ( $namespaceKey == '' ) {
4549  $namespaceKey = 'main';
4550  }
4551  // Changes file to image for backwards compatibility
4552  if ( $namespaceKey == 'file' ) {
4553  $namespaceKey = 'image';
4554  }
4555  return $prepend . $namespaceKey;
4556  }
4557 
4564  public function getRedirectsHere( $ns = null ) {
4565  $redirs = [];
4566 
4567  $dbr = wfGetDB( DB_REPLICA );
4568  $where = [
4569  'rd_namespace' => $this->getNamespace(),
4570  'rd_title' => $this->getDBkey(),
4571  'rd_from = page_id'
4572  ];
4573  if ( $this->isExternal() ) {
4574  $where['rd_interwiki'] = $this->getInterwiki();
4575  } else {
4576  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4577  }
4578  if ( !is_null( $ns ) ) {
4579  $where['page_namespace'] = $ns;
4580  }
4581 
4582  $res = $dbr->select(
4583  [ 'redirect', 'page' ],
4584  [ 'page_namespace', 'page_title' ],
4585  $where,
4586  __METHOD__
4587  );
4588 
4589  foreach ( $res as $row ) {
4590  $redirs[] = self::newFromRow( $row );
4591  }
4592  return $redirs;
4593  }
4594 
4600  public function isValidRedirectTarget() {
4601  global $wgInvalidRedirectTargets;
4602 
4603  if ( $this->isSpecialPage() ) {
4604  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4605  if ( $this->isSpecial( 'Userlogout' ) ) {
4606  return false;
4607  }
4608 
4609  foreach ( $wgInvalidRedirectTargets as $target ) {
4610  if ( $this->isSpecial( $target ) ) {
4611  return false;
4612  }
4613  }
4614  }
4615 
4616  return true;
4617  }
4618 
4624  public function getBacklinkCache() {
4625  return BacklinkCache::get( $this );
4626  }
4627 
4633  public function canUseNoindex() {
4634  global $wgExemptFromUserRobotsControl;
4635 
4636  $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4638  : $wgExemptFromUserRobotsControl;
4639 
4640  return !in_array( $this->mNamespace, $bannedNamespaces );
4641 
4642  }
4643 
4654  public function getCategorySortkey( $prefix = '' ) {
4655  $unprefixed = $this->getText();
4656 
4657  // Anything that uses this hook should only depend
4658  // on the Title object passed in, and should probably
4659  // tell the users to run updateCollations.php --force
4660  // in order to re-sort existing category relations.
4661  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4662  if ( $prefix !== '' ) {
4663  # Separate with a line feed, so the unprefixed part is only used as
4664  # a tiebreaker when two pages have the exact same prefix.
4665  # In UCA, tab is the only character that can sort above LF
4666  # so we strip both of them from the original prefix.
4667  $prefix = strtr( $prefix, "\n\t", ' ' );
4668  return "$prefix\n$unprefixed";
4669  }
4670  return $unprefixed;
4671  }
4672 
4680  private function getDbPageLanguageCode() {
4681  global $wgPageLanguageUseDB;
4682 
4683  // check, if the page language could be saved in the database, and if so and
4684  // the value is not requested already, lookup the page language using LinkCache
4685  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4686  $linkCache = LinkCache::singleton();
4687  $linkCache->addLinkObj( $this );
4688  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4689  }
4690 
4691  return $this->mDbPageLanguage;
4692  }
4693 
4702  public function getPageLanguage() {
4704  if ( $this->isSpecialPage() ) {
4705  // special pages are in the user language
4706  return $wgLang;
4707  }
4708 
4709  // Checking if DB language is set
4710  $dbPageLanguage = $this->getDbPageLanguageCode();
4711  if ( $dbPageLanguage ) {
4712  return wfGetLangObj( $dbPageLanguage );
4713  }
4714 
4715  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4716  // Note that this may depend on user settings, so the cache should
4717  // be only per-request.
4718  // NOTE: ContentHandler::getPageLanguage() may need to load the
4719  // content to determine the page language!
4720  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4721  // tests.
4722  $contentHandler = ContentHandler::getForTitle( $this );
4723  $langObj = $contentHandler->getPageLanguage( $this );
4724  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4725  } else {
4726  $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4727  }
4728 
4729  return $langObj;
4730  }
4731 
4740  public function getPageViewLanguage() {
4741  global $wgLang;
4742 
4743  if ( $this->isSpecialPage() ) {
4744  // If the user chooses a variant, the content is actually
4745  // in a language whose code is the variant code.
4746  $variant = $wgLang->getPreferredVariant();
4747  if ( $wgLang->getCode() !== $variant ) {
4748  return Language::factory( $variant );
4749  }
4750 
4751  return $wgLang;
4752  }
4753 
4754  // Checking if DB language is set
4755  $dbPageLanguage = $this->getDbPageLanguageCode();
4756  if ( $dbPageLanguage ) {
4757  $pageLang = wfGetLangObj( $dbPageLanguage );
4758  $variant = $pageLang->getPreferredVariant();
4759  if ( $pageLang->getCode() !== $variant ) {
4760  $pageLang = Language::factory( $variant );
4761  }
4762 
4763  return $pageLang;
4764  }
4765 
4766  // @note Can't be cached persistently, depends on user settings.
4767  // @note ContentHandler::getPageViewLanguage() may need to load the
4768  // content to determine the page language!
4769  $contentHandler = ContentHandler::getForTitle( $this );
4770  $pageLang = $contentHandler->getPageViewLanguage( $this );
4771  return $pageLang;
4772  }
4773 
4784  public function getEditNotices( $oldid = 0 ) {
4785  $notices = [];
4786 
4787  // Optional notice for the entire namespace
4788  $editnotice_ns = 'editnotice-' . $this->getNamespace();
4789  $msg = wfMessage( $editnotice_ns );
4790  if ( $msg->exists() ) {
4791  $html = $msg->parseAsBlock();
4792  // Edit notices may have complex logic, but output nothing (T91715)
4793  if ( trim( $html ) !== '' ) {
4794  $notices[$editnotice_ns] = Html::rawElement(
4795  'div',
4796  [ 'class' => [
4797  'mw-editnotice',
4798  'mw-editnotice-namespace',
4799  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4800  ] ],
4801  $html
4802  );
4803  }
4804  }
4805 
4806  if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4807  // Optional notice for page itself and any parent page
4808  $parts = explode( '/', $this->getDBkey() );
4809  $editnotice_base = $editnotice_ns;
4810  while ( count( $parts ) > 0 ) {
4811  $editnotice_base .= '-' . array_shift( $parts );
4812  $msg = wfMessage( $editnotice_base );
4813  if ( $msg->exists() ) {
4814  $html = $msg->parseAsBlock();
4815  if ( trim( $html ) !== '' ) {
4816  $notices[$editnotice_base] = Html::rawElement(
4817  'div',
4818  [ 'class' => [
4819  'mw-editnotice',
4820  'mw-editnotice-base',
4821  Sanitizer::escapeClass( "mw-$editnotice_base" )
4822  ] ],
4823  $html
4824  );
4825  }
4826  }
4827  }
4828  } else {
4829  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4830  $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4831  $msg = wfMessage( $editnoticeText );
4832  if ( $msg->exists() ) {
4833  $html = $msg->parseAsBlock();
4834  if ( trim( $html ) !== '' ) {
4835  $notices[$editnoticeText] = Html::rawElement(
4836  'div',
4837  [ 'class' => [
4838  'mw-editnotice',
4839  'mw-editnotice-page',
4840  Sanitizer::escapeClass( "mw-$editnoticeText" )
4841  ] ],
4842  $html
4843  );
4844  }
4845  }
4846  }
4847 
4848  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4849  return $notices;
4850  }
4851 
4855  public function __sleep() {
4856  return [
4857  'mNamespace',
4858  'mDbkeyform',
4859  'mFragment',
4860  'mInterwiki',
4861  'mLocalInterwiki',
4862  'mUserCaseDBKey',
4863  'mDefaultNamespace',
4864  ];
4865  }
4866 
4867  public function __wakeup() {
4868  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4869  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4870  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4871  }
4872 
4873 }
getEarliestRevTime($flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:4064
bool $mHasSubpages
Whether a page has any subpages.
Definition: Title.php:152
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition: Title.php:4316
static newFromRow($row)
Make a Title object from a DB row.
Definition: Title.php:450
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition: Title.php:3061
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3298
setFragment($fragment)
Set the fragment for this title.
Definition: Title.php:1397
static newFromID($id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:402
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Definition: Title.php:607
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:708
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:4468
getFragment()
Get the Title fragment (i.e.
Definition: Title.php:1359
static getMainWANInstance()
Get the main WAN cache object.
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:1936
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:133
exists($flags=0)
Check if page exists.
Definition: Title.php:4294
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
getFullUrlForRedirect($query= '', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition: Title.php:1706
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable...
Definition: Title.php:1160
getInternalURL($query= '', $query2=false)
Get the URL form for an internal link.
Definition: Title.php:1856
getRootTitle()
Get the root page name title, i.e.
Definition: Title.php:1519
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:802
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:1555
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition: Title.php:4633
isJsSubpage()
Is this a .js subpage of a user page?
Definition: Title.php:1286
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:239
wasLocalInterwiki()
Was this a local interwiki link?
Definition: Title.php:817
getSquidURLs()
Definition: Title.php:3624
$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:1150
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:907
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:2716
isSpecial($name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1061
static clearCaches()
Definition: Title.php:3352
getArticleID($flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3209
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:3102
const NS_MAIN
Definition: Defines.php:56
$success
static nameOf($id)
Get the prefixed DB key associated with an ID.
Definition: Title.php:571
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:880
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition: Title.php:1574
getBaseText()
Get the base page name without a namespace, i.e.
Definition: Title.php:1534
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:556
moveSubpages($nt, $auth=true, $reason= '', $createRedirect=true)
Move this page's subpages to be subpages of $nt.
Definition: Title.php:3749
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition: Title.php:4404
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:1936
getEditNotices($oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:4784
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 If you don't need a full Title object...
Definition: SpecialPage.php:82
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition: Title.php:1004
if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:664
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:3039
__wakeup()
Definition: Title.php:4867
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:2488
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition: Title.php:840
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:209
bool $mForcedContentModel
If a content model was forced via setContentModel() this will be true to avoid having other code path...
Definition: Title.php:100
$wgActionPaths
Definition: img_auth.php:46
isCssJsSubpage()
Is this a .css or .js subpage of a user page?
Definition: Title.php:1250
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:1042
checkUserBlock($action, $user, $errors, $rigor, $short)
Check that the user isn't blocked from editing.
Definition: Title.php:2336
if(!isset($args[0])) $lang
static isTalk($index)
Is the given namespace a talk namespace?
Definition: MWNamespace.php:97
hasSubjectNamespace($ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition: Title.php:1139
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition: Title.php:3389
bool $mIsBigDeletion
Would deleting this page be a big deletion?
Definition: Title.php:165
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition: Title.php:1111
moveNoAuth(&$nt)
Move this page without authentication.
Definition: Title.php:3645
isTalkPage()
Is this a talk page of some sort?
Definition: Title.php:1296
Handles purging appropriate CDN URLs given a title (or titles)
string $mUrlform
URL-encoded form of the main part.
Definition: Title.php:64
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title...
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition: Title.php:4680
null for the local wiki Added in
Definition: hooks.txt:1555
isRedirect($flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3235
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:4084
set($key, $value, $exptime=0, $flags=0)
const NS_SPECIAL
Definition: Defines.php:45
const PROTO_CURRENT
Definition: Defines.php:226
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:1046
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:3548
prefix($name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object. ...
Definition: Title.php:1425
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:1332
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:751
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2703
static getInterwikiLookup()
B/C kludge: provide an InterwikiLookup for use by Title.
Definition: Title.php:188
isValidMoveTarget($nt)
Checks if $this can be moved to a given Title.
Definition: Title.php:3867
static escapeClass($class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1247
static escapeRegexReplacement($string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter...
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1455
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:262
getBaseTitle()
Get the base page name title, i.e.
Definition: Title.php:1559
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:3915
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4624
$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:236
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:128
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:1979
getSkinFromCssJsSubpage()
Trim down a .css or .js subpage title to get the corresponding skin name.
Definition: Title.php:1261
const DB_MASTER
Definition: defines.php:23
static getFilteredRestrictionTypes($exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition: Title.php:2549
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition: Title.php:1305
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition: hooks.txt:1007
getNamespace()
Get the namespace index.
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition: Title.php:124
getNotificationTimestamp($user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition: Title.php:4495
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:1077
static isEveryoneAllowed($right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4645
getSubpages($limit=-1)
Get all subpages of this page.
Definition: Title.php:3130
userCan($action, $user=null, $rigor= 'secure')
Can $user perform $action on this page?
Definition: Title.php:1924
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:1934
getFragment()
Get the link fragment (i.e.
Deferrable Update for closure/callback updates that should use auto-commit mode.
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:112
inNamespace($ns)
Returns true if the title is inside the specified namespace.
Definition: Title.php:1100
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition: Title.php:3051
getContentModel($flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:931
getLength($flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition: Title.php:3270
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:979
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:3660
if($wgScript===false) if($wgLoadScript===false) if($wgArticlePath===false) if(!empty($wgActionPaths)&&!isset($wgActionPaths['view'])) if($wgResourceBasePath===null) if($wgStylePath===false) if($wgLocalStylePath===false) if($wgExtensionAssetsPath===false) if($wgLogo===false) if($wgUploadPath===false) if($wgUploadDirectory===false) if($wgReadOnlyFile===false) if($wgFileCacheDirectory===false) if($wgDeletedDirectory===false) if($wgGitInfoCacheDirectory===false &&$wgCacheDirectory!==false) if($wgEnableParserCache===false) if($wgRightsIcon) if(isset($wgFooterIcons['copyright']['copyright'])&&$wgFooterIcons['copyright']['copyright']===[]) if(isset($wgFooterIcons['poweredby'])&&isset($wgFooterIcons['poweredby']['mediawiki'])&&$wgFooterIcons['poweredby']['mediawiki']['src']===null) $wgNamespaceProtection[NS_MEDIAWIKI]
Unconditional protection for NS_MEDIAWIKI since otherwise it's too easy for a sysadmin to set $wgName...
Definition: Setup.php:155
createFragmentTarget($fragment)
Creates a new Title for a different fragment of the same page.
Definition: Title.php:1408
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:4018
__sleep()
Definition: Title.php:4855
string bool $mOldRestrictions
Text form (spaces not underscores) of the main part.
Definition: Title.php:109
__construct()
Definition: Title.php:195
wfReadOnly()
Check whether the wiki is in read-only mode.
isExternal()
Is this Title interwiki?
Definition: Title.php:797
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:1634
deleteTitleProtection()
Remove any title protection due to page existing.
Definition: Title.php:2638
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:4625
static singleton()
Get an instance of this class.
Definition: LinkCache.php:64
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:4378
static newFromIDs($ids)
Make an array of titles from an array of IDs.
Definition: Title.php:424
wfAppendQuery($url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
static getTitleCache()
Definition: Title.php:362
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition: Title.php:176
isAllowed($action= '')
Internal mechanics of testing a permission.
Definition: User.php:3443
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:898
__toString()
Return a string representation of this title.
Definition: Title.php:1469
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition: Title.php:1615
hasFragment()
Check if a Title fragment is set.
Definition: Title.php:1369
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"&lt
hasContentModel($id)
Convenience method for checking a title's content model name.
Definition: Title.php:954
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:1046
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:2906
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition: Title.php:1201
const NS_MEDIA
Definition: Defines.php:44
static newFromDBkey($key)
Create a new Title from a prefixed DB key.
Definition: Title.php:206
getRootText()
Get the root page name text without a namespace, i.e.
Definition: Title.php:1499
getTouched($db=null)
Get the last touched timestamp.
Definition: Title.php:4481
getLocalURL($query= '', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1740
$res
Definition: database.txt:21
checkCSSandJSPermissions($action, $user, $errors, $rigor, $short)
Check CSS/JS sub-page permissions.
Definition: Title.php:2142
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:1033
getSubpage($text)
Get the title for a subpage of the current page.
Definition: Title.php:1595
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:146
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:4600
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not...
Definition: Title.php:2734
invalidateCache($purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4429
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3596
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:1948
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:3495
$cache
Definition: mcc.php:33
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition: Title.php:1348
const NS_CATEGORY
Definition: Defines.php:70
moveTo(&$nt, $auth=true, $reason= '', $createRedirect=true)
Move a title to a new location.
Definition: Title.php:3715
getRedirectsHere($ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4564
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition: Revision.php:442
checkSpecialsAndNSPermissions($action, $user, $errors, $rigor, $short)
Check permissions on special pages & namespaces.
Definition: Title.php:2113
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:1876
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:4277
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition: Title.php:149
static makeTitleSafe($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:535
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:1479
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:159
validateFileMoveOperation($nt)
Check if the requested move target is a valid file move target.
Definition: Title.php:3687
isCssSubpage()
Is this a .css subpage of a user page?
Definition: Title.php:1276
isMainPage()
Is this the mainpage?
Definition: Title.php:1181
static decodeCharReferencesAndNormalize($text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1517
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:953
getAuthorsBetween($old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition: Title.php:4179
static hasSubpages($index)
Does the namespace allow subpages?
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:921
const PROTO_RELATIVE
Definition: Defines.php:225
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:4264
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:4369
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:3479
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
const NS_FILE
Definition: Defines.php:62
static newFromResult($res)
Definition: TitleArray.php:38
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2851
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:1721
checkCascadingSourcesRestrictions($action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition: Title.php:2212
static equals($ns1, $ns2)
Returns whether the specified namespaces are the same namespace.
isSubpage()
Is this a subpage?
Definition: Title.php:1190
getInterwiki()
Get the interwiki prefix.
Definition: Title.php:808
const RAW
Definition: Revision.php:94
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
loadFromRow($row)
Load Title object fields from a DB row.
Definition: Title.php:462
areCascadeProtectionSourcesLoaded($getPages=true)
Determines whether cascading protection sources have already been loaded from the database...
Definition: Title.php:2748
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:953
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:3560
const NS_MEDIAWIKI
Definition: Defines.php:64
const PROTO_HTTP
Definition: Defines.php:223
getNamespaceKey($prepend= 'nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition: Title.php:4533
countRevisionsBetween($old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:4136
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition: Title.php:118
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:225
getRestrictions($action)
Accessor/initialisation for mRestrictions.
Definition: Title.php:2864
CONTENT_MODEL_JAVASCRIPT
Uploads have to be specially set up to be secure.
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition: Title.php:1315
int $mLength
The page length, 0 for special pages.
Definition: Title.php:143
getFirstRevision($flags=0)
Get the first revision of the page.
Definition: Title.php:4042
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:1170
static newFromTextThrow($text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid, rather than returning null.
Definition: Title.php:292
isSpecialPage()
Returns true if this is a special page.
Definition: Title.php:1051
quickUserCan($action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition: Title.php:1911
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:1888
$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:2996
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:621
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition: Title.php:3816
const PROTO_CANONICAL
Definition: Defines.php:227
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition: Title.php:4740
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition: Title.php:1604
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition: Title.php:115
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:2567
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:162
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition: Title.php:155
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition: Title.php:827
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:140
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:1046
getCategorySortkey($prefix= '')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition: Title.php:4654
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:725
static newFromURL($url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition: Title.php:339
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition: Title.php:4702
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:2378
isCssOrJsPage()
Could this page contain custom CSS or JavaScript for the global UI.
Definition: Title.php:1231
getCascadeProtectionSources($getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition: Title.php:2765
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition: Title.php:1377
get($key, $flags=0, $oldFlags=null)
Get an item with the given key.
Definition: BagOStuff.php:179
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition: Title.php:3977
static invalidateModuleCache(Title $title, Revision $old=null, Revision $new=null, $wikiId)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
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:1046
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:241
string $mPrefixedText
Text form including namespace/interwiki, initialised on demand.
Definition: Title.php:130
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition: Title.php:3183
isNewPage()
Check if this is a new page.
Definition: Title.php:4074
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:2453
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:1046
isProtected($action= '')
Does the title correspond to a protected article?
Definition: Title.php:2684
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition: Title.php:767
canTalk()
Could this title have a corresponding talk page?
Definition: Title.php:1024
$count
checkActionPermissions($action, $user, $errors, $rigor, $short)
Check action permissions not already checked in checkQuickPermissions.
Definition: Title.php:2259
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition: Title.php:127
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:593
$wgServer
URL of the server.
isDeleted()
Is there a version of this page in the deletion archive?
Definition: Title.php:3158
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition: Title.php:782
loadRestrictionsFromRows($rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition: Title.php:2923
array $mRestrictions
Array of groups allowed to edit this article.
Definition: Title.php:106
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:1833
const DB_REPLICA
Definition: defines.php:22
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition: Title.php:2594
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition: Title.php:79
addQuotes($s)
Adds quotes and backslashes.
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition: Title.php:103
countAuthorsBetween($old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition: Title.php:4253
getTalkNsText()
Get the namespace text of the talk page.
Definition: Title.php:1014
checkPageRestrictions($action, $user, $errors, $rigor, $short)
Check against page_restrictions table requirements on this page.
Definition: Title.php:2178
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries...
Definition: Title.php:376
checkPermissionHooks($action, $user, $errors, $rigor, $short)
Check various permission hooks.
Definition: Title.php:2080
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:181
getParentCategoryTree($children=[])
Get a tree of parent categories.
Definition: Title.php:3950
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:281
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition: Title.php:2880
isSemiProtected($action= 'edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2656
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:2049
getRestrictionExpiry($action)
Get the expiry time for the restriction against a given action.
Definition: Title.php:2894
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:3993
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
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:2491
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:511
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:3367
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition: Title.php:1213
static singleton()
Get the signleton instance of this class.
purgeSquid()
Purge all applicable CDN URLs.
Definition: Title.php:3631
static getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4602
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:3330
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:34
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:2491
getTitleValue()
Get a TitleValue object representing this Title.
Definition: Title.php:857
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:3437
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition: Title.php:121
getPartialURL()
Get the URL-encoded form of the main part.
Definition: Title.php:889
wfGetLangObj($langcode=false)
Return a Language object from $langcode.
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1443
getFullURL($query= '', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition: Title.php:1672
estimateRevisionCount()
Get the approximate revision count of this page.
Definition: Title.php:4113
setContentModel($model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:969
$wgUser
Definition: Setup.php:806
$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:300