MediaWiki  1.27.2
Title.php
Go to the documentation of this file.
1 <?php
25 
34 class Title implements LinkTarget {
36  static private $titleCache = null;
37 
43  const CACHE_MAX = 1000;
44 
49  const GAID_FOR_UPDATE = 1;
50 
56  // @{
57 
59  public $mTextform = '';
60 
62  public $mUrlform = '';
63 
65  public $mDbkeyform = '';
66 
68  protected $mUserCaseDBKey;
69 
71  public $mNamespace = NS_MAIN;
72 
74  public $mInterwiki = '';
75 
77  private $mLocalInterwiki = false;
78 
80  public $mFragment = '';
81 
83  public $mArticleID = -1;
84 
86  protected $mLatestID = false;
87 
92  public $mContentModel = false;
93 
96 
98  public $mRestrictions = [];
99 
101  protected $mOldRestrictions = false;
102 
105 
108 
110  protected $mRestrictionsExpiry = [];
111 
114 
117 
119  public $mRestrictionsLoaded = false;
120 
122  protected $mPrefixedText = null;
123 
126 
133 
135  protected $mLength = -1;
136 
138  public $mRedirect = null;
139 
142 
144  private $mHasSubpages;
145 
147  private $mPageLanguage = false;
148 
151  private $mDbPageLanguage = false;
152 
154  private $mTitleValue = null;
155 
157  private $mIsBigDeletion = null;
158  // @}
159 
168  private static function getMediaWikiTitleCodec() {
170 
171  static $titleCodec = null;
172  static $titleCodecFingerprint = null;
173 
174  // $wgContLang and $wgLocalInterwikis may change (especially while testing),
175  // make sure we are using the right one. To detect changes over the course
176  // of a request, we remember a fingerprint of the config used to create the
177  // codec singleton, and re-create it if the fingerprint doesn't match.
178  $fingerprint = spl_object_hash( $wgContLang ) . '|' . implode( '+', $wgLocalInterwikis );
179 
180  if ( $fingerprint !== $titleCodecFingerprint ) {
181  $titleCodec = null;
182  }
183 
184  if ( !$titleCodec ) {
185  $titleCodec = new MediaWikiTitleCodec(
186  $wgContLang,
188  $wgLocalInterwikis
189  );
190  $titleCodecFingerprint = $fingerprint;
191  }
192 
193  return $titleCodec;
194  }
195 
204  private static function getTitleFormatter() {
205  // NOTE: we know that getMediaWikiTitleCodec() returns a MediaWikiTitleCodec,
206  // which implements TitleFormatter.
207  return self::getMediaWikiTitleCodec();
208  }
209 
210  function __construct() {
211  }
212 
221  public static function newFromDBkey( $key ) {
222  $t = new Title();
223  $t->mDbkeyform = $key;
224 
225  try {
226  $t->secureAndSplit();
227  return $t;
228  } catch ( MalformedTitleException $ex ) {
229  return null;
230  }
231  }
232 
240  public static function newFromTitleValue( TitleValue $titleValue ) {
241  return self::newFromLinkTarget( $titleValue );
242  }
243 
251  public static function newFromLinkTarget( LinkTarget $linkTarget ) {
252  if ( $linkTarget instanceof Title ) {
253  // Special case if it's already a Title object
254  return $linkTarget;
255  }
256  return self::makeTitle(
257  $linkTarget->getNamespace(),
258  $linkTarget->getText(),
259  $linkTarget->getFragment(),
260  $linkTarget->getInterwiki()
261  );
262  }
263 
277  public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
278  // DWIM: Integers can be passed in here when page titles are used as array keys.
279  if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
280  throw new InvalidArgumentException( '$text must be a string.' );
281  }
282  if ( $text === null ) {
283  return null;
284  }
285 
286  try {
287  return Title::newFromTextThrow( strval( $text ), $defaultNamespace );
288  } catch ( MalformedTitleException $ex ) {
289  return null;
290  }
291  }
292 
307  public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
308  if ( is_object( $text ) ) {
309  throw new MWException( '$text must be a string, given an object' );
310  }
311 
312  $titleCache = self::getTitleCache();
313 
314  // Wiki pages often contain multiple links to the same page.
315  // Title normalization and parsing can become expensive on pages with many
316  // links, so we can save a little time by caching them.
317  // In theory these are value objects and won't get changed...
318  if ( $defaultNamespace == NS_MAIN ) {
319  $t = $titleCache->get( $text );
320  if ( $t ) {
321  return $t;
322  }
323  }
324 
325  // Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
326  $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
327 
328  $t = new Title();
329  $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
330  $t->mDefaultNamespace = intval( $defaultNamespace );
331 
332  $t->secureAndSplit();
333  if ( $defaultNamespace == NS_MAIN ) {
334  $titleCache->set( $text, $t );
335  }
336  return $t;
337  }
338 
354  public static function newFromURL( $url ) {
355  $t = new Title();
356 
357  # For compatibility with old buggy URLs. "+" is usually not valid in titles,
358  # but some URLs used it as a space replacement and they still come
359  # from some external search tools.
360  if ( strpos( self::legalChars(), '+' ) === false ) {
361  $url = strtr( $url, '+', ' ' );
362  }
363 
364  $t->mDbkeyform = strtr( $url, ' ', '_' );
365 
366  try {
367  $t->secureAndSplit();
368  return $t;
369  } catch ( MalformedTitleException $ex ) {
370  return null;
371  }
372  }
373 
377  private static function getTitleCache() {
378  if ( self::$titleCache == null ) {
379  self::$titleCache = new HashBagOStuff( [ 'maxKeys' => self::CACHE_MAX ] );
380  }
381  return self::$titleCache;
382  }
383 
391  protected static function getSelectFields() {
392  global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
393 
394  $fields = [
395  'page_namespace', 'page_title', 'page_id',
396  'page_len', 'page_is_redirect', 'page_latest',
397  ];
398 
399  if ( $wgContentHandlerUseDB ) {
400  $fields[] = 'page_content_model';
401  }
402 
403  if ( $wgPageLanguageUseDB ) {
404  $fields[] = 'page_lang';
405  }
406 
407  return $fields;
408  }
409 
417  public static function newFromID( $id, $flags = 0 ) {
418  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
419  $row = $db->selectRow(
420  'page',
421  self::getSelectFields(),
422  [ 'page_id' => $id ],
423  __METHOD__
424  );
425  if ( $row !== false ) {
426  $title = Title::newFromRow( $row );
427  } else {
428  $title = null;
429  }
430  return $title;
431  }
432 
439  public static function newFromIDs( $ids ) {
440  if ( !count( $ids ) ) {
441  return [];
442  }
443  $dbr = wfGetDB( DB_SLAVE );
444 
445  $res = $dbr->select(
446  'page',
447  self::getSelectFields(),
448  [ 'page_id' => $ids ],
449  __METHOD__
450  );
451 
452  $titles = [];
453  foreach ( $res as $row ) {
454  $titles[] = Title::newFromRow( $row );
455  }
456  return $titles;
457  }
458 
465  public static function newFromRow( $row ) {
466  $t = self::makeTitle( $row->page_namespace, $row->page_title );
467  $t->loadFromRow( $row );
468  return $t;
469  }
470 
477  public function loadFromRow( $row ) {
478  if ( $row ) { // page found
479  if ( isset( $row->page_id ) ) {
480  $this->mArticleID = (int)$row->page_id;
481  }
482  if ( isset( $row->page_len ) ) {
483  $this->mLength = (int)$row->page_len;
484  }
485  if ( isset( $row->page_is_redirect ) ) {
486  $this->mRedirect = (bool)$row->page_is_redirect;
487  }
488  if ( isset( $row->page_latest ) ) {
489  $this->mLatestID = (int)$row->page_latest;
490  }
491  if ( isset( $row->page_content_model ) ) {
492  $this->mContentModel = strval( $row->page_content_model );
493  } else {
494  $this->mContentModel = false; # initialized lazily in getContentModel()
495  }
496  if ( isset( $row->page_lang ) ) {
497  $this->mDbPageLanguage = (string)$row->page_lang;
498  }
499  if ( isset( $row->page_restrictions ) ) {
500  $this->mOldRestrictions = $row->page_restrictions;
501  }
502  } else { // page not found
503  $this->mArticleID = 0;
504  $this->mLength = 0;
505  $this->mRedirect = false;
506  $this->mLatestID = 0;
507  $this->mContentModel = false; # initialized lazily in getContentModel()
508  }
509  }
510 
524  public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
525  $t = new Title();
526  $t->mInterwiki = $interwiki;
527  $t->mFragment = $fragment;
528  $t->mNamespace = $ns = intval( $ns );
529  $t->mDbkeyform = strtr( $title, ' ', '_' );
530  $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
531  $t->mUrlform = wfUrlencode( $t->mDbkeyform );
532  $t->mTextform = strtr( $title, '_', ' ' );
533  $t->mContentModel = false; # initialized lazily in getContentModel()
534  return $t;
535  }
536 
548  public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
549  if ( !MWNamespace::exists( $ns ) ) {
550  return null;
551  }
552 
553  $t = new Title();
554  $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
555 
556  try {
557  $t->secureAndSplit();
558  return $t;
559  } catch ( MalformedTitleException $ex ) {
560  return null;
561  }
562  }
563 
569  public static function newMainPage() {
570  $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
571  // Don't give fatal errors if the message is broken
572  if ( !$title ) {
573  $title = Title::newFromText( 'Main Page' );
574  }
575  return $title;
576  }
577 
584  public static function nameOf( $id ) {
585  $dbr = wfGetDB( DB_SLAVE );
586 
587  $s = $dbr->selectRow(
588  'page',
589  [ 'page_namespace', 'page_title' ],
590  [ 'page_id' => $id ],
591  __METHOD__
592  );
593  if ( $s === false ) {
594  return null;
595  }
596 
597  $n = self::makeName( $s->page_namespace, $s->page_title );
598  return $n;
599  }
600 
606  public static function legalChars() {
608  return $wgLegalTitleChars;
609  }
610 
620  static function getTitleInvalidRegex() {
621  wfDeprecated( __METHOD__, '1.25' );
623  }
624 
634  public static function convertByteClassToUnicodeClass( $byteClass ) {
635  $length = strlen( $byteClass );
636  // Input token queue
637  $x0 = $x1 = $x2 = '';
638  // Decoded queue
639  $d0 = $d1 = $d2 = '';
640  // Decoded integer codepoints
641  $ord0 = $ord1 = $ord2 = 0;
642  // Re-encoded queue
643  $r0 = $r1 = $r2 = '';
644  // Output
645  $out = '';
646  // Flags
647  $allowUnicode = false;
648  for ( $pos = 0; $pos < $length; $pos++ ) {
649  // Shift the queues down
650  $x2 = $x1;
651  $x1 = $x0;
652  $d2 = $d1;
653  $d1 = $d0;
654  $ord2 = $ord1;
655  $ord1 = $ord0;
656  $r2 = $r1;
657  $r1 = $r0;
658  // Load the current input token and decoded values
659  $inChar = $byteClass[$pos];
660  if ( $inChar == '\\' ) {
661  if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
662  $x0 = $inChar . $m[0];
663  $d0 = chr( hexdec( $m[1] ) );
664  $pos += strlen( $m[0] );
665  } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
666  $x0 = $inChar . $m[0];
667  $d0 = chr( octdec( $m[0] ) );
668  $pos += strlen( $m[0] );
669  } elseif ( $pos + 1 >= $length ) {
670  $x0 = $d0 = '\\';
671  } else {
672  $d0 = $byteClass[$pos + 1];
673  $x0 = $inChar . $d0;
674  $pos += 1;
675  }
676  } else {
677  $x0 = $d0 = $inChar;
678  }
679  $ord0 = ord( $d0 );
680  // Load the current re-encoded value
681  if ( $ord0 < 32 || $ord0 == 0x7f ) {
682  $r0 = sprintf( '\x%02x', $ord0 );
683  } elseif ( $ord0 >= 0x80 ) {
684  // Allow unicode if a single high-bit character appears
685  $r0 = sprintf( '\x%02x', $ord0 );
686  $allowUnicode = true;
687  } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
688  $r0 = '\\' . $d0;
689  } else {
690  $r0 = $d0;
691  }
692  // Do the output
693  if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
694  // Range
695  if ( $ord2 > $ord0 ) {
696  // Empty range
697  } elseif ( $ord0 >= 0x80 ) {
698  // Unicode range
699  $allowUnicode = true;
700  if ( $ord2 < 0x80 ) {
701  // Keep the non-unicode section of the range
702  $out .= "$r2-\\x7F";
703  }
704  } else {
705  // Normal range
706  $out .= "$r2-$r0";
707  }
708  // Reset state to the initial value
709  $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
710  } elseif ( $ord2 < 0x80 ) {
711  // ASCII character
712  $out .= $r2;
713  }
714  }
715  if ( $ord1 < 0x80 ) {
716  $out .= $r1;
717  }
718  if ( $ord0 < 0x80 ) {
719  $out .= $r0;
720  }
721  if ( $allowUnicode ) {
722  $out .= '\u0080-\uFFFF';
723  }
724  return $out;
725  }
726 
738  public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
739  $canonicalNamespace = false
740  ) {
742 
743  if ( $canonicalNamespace ) {
744  $namespace = MWNamespace::getCanonicalName( $ns );
745  } else {
746  $namespace = $wgContLang->getNsText( $ns );
747  }
748  $name = $namespace == '' ? $title : "$namespace:$title";
749  if ( strval( $interwiki ) != '' ) {
750  $name = "$interwiki:$name";
751  }
752  if ( strval( $fragment ) != '' ) {
753  $name .= '#' . $fragment;
754  }
755  return $name;
756  }
757 
764  static function escapeFragmentForURL( $fragment ) {
765  # Note that we don't urlencode the fragment. urlencoded Unicode
766  # fragments appear not to work in IE (at least up to 7) or in at least
767  # one version of Opera 9.x. The W3C validator, for one, doesn't seem
768  # to care if they aren't encoded.
769  return Sanitizer::escapeId( $fragment, 'noninitial' );
770  }
771 
780  public static function compare( $a, $b ) {
781  if ( $a->getNamespace() == $b->getNamespace() ) {
782  return strcmp( $a->getText(), $b->getText() );
783  } else {
784  return $a->getNamespace() - $b->getNamespace();
785  }
786  }
787 
795  public function isLocal() {
796  if ( $this->isExternal() ) {
797  $iw = Interwiki::fetch( $this->mInterwiki );
798  if ( $iw ) {
799  return $iw->isLocal();
800  }
801  }
802  return true;
803  }
804 
810  public function isExternal() {
811  return $this->mInterwiki !== '';
812  }
813 
821  public function getInterwiki() {
822  return $this->mInterwiki;
823  }
824 
830  public function wasLocalInterwiki() {
831  return $this->mLocalInterwiki;
832  }
833 
840  public function isTrans() {
841  if ( !$this->isExternal() ) {
842  return false;
843  }
844 
845  return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
846  }
847 
853  public function getTransWikiID() {
854  if ( !$this->isExternal() ) {
855  return false;
856  }
857 
858  return Interwiki::fetch( $this->mInterwiki )->getWikiID();
859  }
860 
870  public function getTitleValue() {
871  if ( $this->mTitleValue === null ) {
872  try {
873  $this->mTitleValue = new TitleValue(
874  $this->getNamespace(),
875  $this->getDBkey(),
876  $this->getFragment(),
877  $this->getInterwiki()
878  );
879  } catch ( InvalidArgumentException $ex ) {
880  wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
881  $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
882  }
883  }
884 
885  return $this->mTitleValue;
886  }
887 
893  public function getText() {
894  return $this->mTextform;
895  }
896 
902  public function getPartialURL() {
903  return $this->mUrlform;
904  }
905 
911  public function getDBkey() {
912  return $this->mDbkeyform;
913  }
914 
920  function getUserCaseDBKey() {
921  if ( !is_null( $this->mUserCaseDBKey ) ) {
922  return $this->mUserCaseDBKey;
923  } else {
924  // If created via makeTitle(), $this->mUserCaseDBKey is not set.
925  return $this->mDbkeyform;
926  }
927  }
928 
934  public function getNamespace() {
935  return $this->mNamespace;
936  }
937 
944  public function getContentModel( $flags = 0 ) {
945  if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
946  $linkCache = LinkCache::singleton();
947  $linkCache->addLinkObj( $this ); # in case we already had an article ID
948  $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
949  }
950 
951  if ( !$this->mContentModel ) {
952  $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
953  }
954 
955  return $this->mContentModel;
956  }
957 
964  public function hasContentModel( $id ) {
965  return $this->getContentModel() == $id;
966  }
967 
973  public function getNsText() {
974  if ( $this->isExternal() ) {
975  // This probably shouldn't even happen,
976  // but for interwiki transclusion it sometimes does.
977  // Use the canonical namespaces if possible to try to
978  // resolve a foreign namespace.
979  if ( MWNamespace::exists( $this->mNamespace ) ) {
980  return MWNamespace::getCanonicalName( $this->mNamespace );
981  }
982  }
983 
984  try {
985  $formatter = self::getTitleFormatter();
986  return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
987  } catch ( InvalidArgumentException $ex ) {
988  wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
989  return false;
990  }
991  }
992 
998  public function getSubjectNsText() {
1000  return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1001  }
1002 
1008  public function getTalkNsText() {
1010  return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1011  }
1012 
1018  public function canTalk() {
1019  return MWNamespace::canTalk( $this->mNamespace );
1020  }
1021 
1027  public function canExist() {
1028  return $this->mNamespace >= NS_MAIN;
1029  }
1030 
1036  public function isWatchable() {
1037  return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
1038  }
1039 
1045  public function isSpecialPage() {
1046  return $this->getNamespace() == NS_SPECIAL;
1047  }
1048 
1055  public function isSpecial( $name ) {
1056  if ( $this->isSpecialPage() ) {
1057  list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
1058  if ( $name == $thisName ) {
1059  return true;
1060  }
1061  }
1062  return false;
1063  }
1064 
1071  public function fixSpecialName() {
1072  if ( $this->isSpecialPage() ) {
1073  list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
1074  if ( $canonicalName ) {
1075  $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
1076  if ( $localName != $this->mDbkeyform ) {
1077  return Title::makeTitle( NS_SPECIAL, $localName );
1078  }
1079  }
1080  }
1081  return $this;
1082  }
1083 
1094  public function inNamespace( $ns ) {
1095  return MWNamespace::equals( $this->getNamespace(), $ns );
1096  }
1097 
1105  public function inNamespaces( /* ... */ ) {
1106  $namespaces = func_get_args();
1107  if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1108  $namespaces = $namespaces[0];
1109  }
1110 
1111  foreach ( $namespaces as $ns ) {
1112  if ( $this->inNamespace( $ns ) ) {
1113  return true;
1114  }
1115  }
1116 
1117  return false;
1118  }
1119 
1133  public function hasSubjectNamespace( $ns ) {
1134  return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
1135  }
1136 
1144  public function isContentPage() {
1145  return MWNamespace::isContent( $this->getNamespace() );
1146  }
1147 
1154  public function isMovable() {
1155  if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
1156  // Interwiki title or immovable namespace. Hooks don't get to override here
1157  return false;
1158  }
1159 
1160  $result = true;
1161  Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1162  return $result;
1163  }
1164 
1175  public function isMainPage() {
1176  return $this->equals( Title::newMainPage() );
1177  }
1178 
1184  public function isSubpage() {
1185  return MWNamespace::hasSubpages( $this->mNamespace )
1186  ? strpos( $this->getText(), '/' ) !== false
1187  : false;
1188  }
1189 
1195  public function isConversionTable() {
1196  // @todo ConversionTable should become a separate content model.
1197 
1198  return $this->getNamespace() == NS_MEDIAWIKI &&
1199  strpos( $this->getText(), 'Conversiontable/' ) === 0;
1200  }
1201 
1207  public function isWikitextPage() {
1208  return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1209  }
1210 
1225  public function isCssOrJsPage() {
1226  $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
1227  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1229 
1230  # @note This hook is also called in ContentHandler::getDefaultModel.
1231  # It's called here again to make sure hook functions can force this
1232  # method to return true even outside the MediaWiki namespace.
1233 
1234  Hooks::run( 'TitleIsCssOrJsPage', [ $this, &$isCssOrJsPage ], '1.25' );
1235 
1236  return $isCssOrJsPage;
1237  }
1238 
1244  public function isCssJsSubpage() {
1245  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1246  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1247  || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
1248  }
1249 
1255  public function getSkinFromCssJsSubpage() {
1256  $subpage = explode( '/', $this->mTextform );
1257  $subpage = $subpage[count( $subpage ) - 1];
1258  $lastdot = strrpos( $subpage, '.' );
1259  if ( $lastdot === false ) {
1260  return $subpage; # Never happens: only called for names ending in '.css' or '.js'
1261  }
1262  return substr( $subpage, 0, $lastdot );
1263  }
1264 
1270  public function isCssSubpage() {
1271  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1272  && $this->hasContentModel( CONTENT_MODEL_CSS ) );
1273  }
1274 
1280  public function isJsSubpage() {
1281  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1283  }
1284 
1290  public function isTalkPage() {
1291  return MWNamespace::isTalk( $this->getNamespace() );
1292  }
1293 
1299  public function getTalkPage() {
1300  return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
1301  }
1302 
1309  public function getSubjectPage() {
1310  // Is this the same title?
1311  $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
1312  if ( $this->getNamespace() == $subjectNS ) {
1313  return $this;
1314  }
1315  return Title::makeTitle( $subjectNS, $this->getDBkey() );
1316  }
1317 
1326  public function getOtherPage() {
1327  if ( $this->isSpecialPage() ) {
1328  throw new MWException( 'Special pages cannot have other pages' );
1329  }
1330  if ( $this->isTalkPage() ) {
1331  return $this->getSubjectPage();
1332  } else {
1333  return $this->getTalkPage();
1334  }
1335  }
1336 
1342  public function getDefaultNamespace() {
1343  return $this->mDefaultNamespace;
1344  }
1345 
1353  public function getFragment() {
1354  return $this->mFragment;
1355  }
1356 
1363  public function hasFragment() {
1364  return $this->mFragment !== '';
1365  }
1366 
1371  public function getFragmentForURL() {
1372  if ( !$this->hasFragment() ) {
1373  return '';
1374  } else {
1375  return '#' . Title::escapeFragmentForURL( $this->getFragment() );
1376  }
1377  }
1378 
1391  public function setFragment( $fragment ) {
1392  $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1393  }
1394 
1402  public function createFragmentTarget( $fragment ) {
1403  return self::makeTitle(
1404  $this->getNamespace(),
1405  $this->getText(),
1406  $fragment,
1407  $this->getInterwiki()
1408  );
1409 
1410  }
1411 
1419  private function prefix( $name ) {
1420  $p = '';
1421  if ( $this->isExternal() ) {
1422  $p = $this->mInterwiki . ':';
1423  }
1424 
1425  if ( 0 != $this->mNamespace ) {
1426  $p .= $this->getNsText() . ':';
1427  }
1428  return $p . $name;
1429  }
1430 
1437  public function getPrefixedDBkey() {
1438  $s = $this->prefix( $this->mDbkeyform );
1439  $s = strtr( $s, ' ', '_' );
1440  return $s;
1441  }
1442 
1449  public function getPrefixedText() {
1450  if ( $this->mPrefixedText === null ) {
1451  $s = $this->prefix( $this->mTextform );
1452  $s = strtr( $s, '_', ' ' );
1453  $this->mPrefixedText = $s;
1454  }
1455  return $this->mPrefixedText;
1456  }
1457 
1463  public function __toString() {
1464  return $this->getPrefixedText();
1465  }
1466 
1473  public function getFullText() {
1474  $text = $this->getPrefixedText();
1475  if ( $this->hasFragment() ) {
1476  $text .= '#' . $this->getFragment();
1477  }
1478  return $text;
1479  }
1480 
1493  public function getRootText() {
1494  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1495  return $this->getText();
1496  }
1497 
1498  return strtok( $this->getText(), '/' );
1499  }
1500 
1513  public function getRootTitle() {
1514  return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
1515  }
1516 
1528  public function getBaseText() {
1529  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1530  return $this->getText();
1531  }
1532 
1533  $parts = explode( '/', $this->getText() );
1534  # Don't discard the real title if there's no subpage involved
1535  if ( count( $parts ) > 1 ) {
1536  unset( $parts[count( $parts ) - 1] );
1537  }
1538  return implode( '/', $parts );
1539  }
1540 
1553  public function getBaseTitle() {
1554  return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
1555  }
1556 
1568  public function getSubpageText() {
1569  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1570  return $this->mTextform;
1571  }
1572  $parts = explode( '/', $this->mTextform );
1573  return $parts[count( $parts ) - 1];
1574  }
1575 
1589  public function getSubpage( $text ) {
1590  return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
1591  }
1592 
1598  public function getSubpageUrlForm() {
1599  $text = $this->getSubpageText();
1600  $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1601  return $text;
1602  }
1603 
1609  public function getPrefixedURL() {
1610  $s = $this->prefix( $this->mDbkeyform );
1611  $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1612  return $s;
1613  }
1614 
1628  private static function fixUrlQueryArgs( $query, $query2 = false ) {
1629  if ( $query2 !== false ) {
1630  wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1631  "method called with a second parameter is deprecated. Add your " .
1632  "parameter to an array passed as the first parameter.", "1.19" );
1633  }
1634  if ( is_array( $query ) ) {
1635  $query = wfArrayToCgi( $query );
1636  }
1637  if ( $query2 ) {
1638  if ( is_string( $query2 ) ) {
1639  // $query2 is a string, we will consider this to be
1640  // a deprecated $variant argument and add it to the query
1641  $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1642  } else {
1643  $query2 = wfArrayToCgi( $query2 );
1644  }
1645  // If we have $query content add a & to it first
1646  if ( $query ) {
1647  $query .= '&';
1648  }
1649  // Now append the queries together
1650  $query .= $query2;
1651  }
1652  return $query;
1653  }
1654 
1666  public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1667  $query = self::fixUrlQueryArgs( $query, $query2 );
1668 
1669  # Hand off all the decisions on urls to getLocalURL
1670  $url = $this->getLocalURL( $query );
1671 
1672  # Expand the url to make it a full url. Note that getLocalURL has the
1673  # potential to output full urls for a variety of reasons, so we use
1674  # wfExpandUrl instead of simply prepending $wgServer
1675  $url = wfExpandUrl( $url, $proto );
1676 
1677  # Finally, add the fragment.
1678  $url .= $this->getFragmentForURL();
1679 
1680  Hooks::run( 'GetFullURL', [ &$this, &$url, $query ] );
1681  return $url;
1682  }
1683 
1700  public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1701  $target = $this;
1702  if ( $this->isExternal() ) {
1703  $target = SpecialPage::getTitleFor(
1704  'GoToInterwiki',
1705  $this->getPrefixedDBKey()
1706  );
1707  }
1708  return $target->getFullUrl( $query, false, $proto );
1709  }
1710 
1734  public function getLocalURL( $query = '', $query2 = false ) {
1736 
1737  $query = self::fixUrlQueryArgs( $query, $query2 );
1738 
1739  $interwiki = Interwiki::fetch( $this->mInterwiki );
1740  if ( $interwiki ) {
1741  $namespace = $this->getNsText();
1742  if ( $namespace != '' ) {
1743  # Can this actually happen? Interwikis shouldn't be parsed.
1744  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1745  $namespace .= ':';
1746  }
1747  $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1748  $url = wfAppendQuery( $url, $query );
1749  } else {
1750  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1751  if ( $query == '' ) {
1752  $url = str_replace( '$1', $dbkey, $wgArticlePath );
1753  Hooks::run( 'GetLocalURL::Article', [ &$this, &$url ] );
1754  } else {
1756  $url = false;
1757  $matches = [];
1758 
1759  if ( !empty( $wgActionPaths )
1760  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1761  ) {
1762  $action = urldecode( $matches[2] );
1763  if ( isset( $wgActionPaths[$action] ) ) {
1764  $query = $matches[1];
1765  if ( isset( $matches[4] ) ) {
1766  $query .= $matches[4];
1767  }
1768  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1769  if ( $query != '' ) {
1770  $url = wfAppendQuery( $url, $query );
1771  }
1772  }
1773  }
1774 
1775  if ( $url === false
1776  && $wgVariantArticlePath
1777  && $wgContLang->getCode() === $this->getPageLanguage()->getCode()
1778  && $this->getPageLanguage()->hasVariants()
1779  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1780  ) {
1781  $variant = urldecode( $matches[1] );
1782  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1783  // Only do the variant replacement if the given variant is a valid
1784  // variant for the page's language.
1785  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1786  $url = str_replace( '$1', $dbkey, $url );
1787  }
1788  }
1789 
1790  if ( $url === false ) {
1791  if ( $query == '-' ) {
1792  $query = '';
1793  }
1794  $url = "{$wgScript}?title={$dbkey}&{$query}";
1795  }
1796  }
1797 
1798  Hooks::run( 'GetLocalURL::Internal', [ &$this, &$url, $query ] );
1799 
1800  // @todo FIXME: This causes breakage in various places when we
1801  // actually expected a local URL and end up with dupe prefixes.
1802  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
1803  $url = $wgServer . $url;
1804  }
1805  }
1806  Hooks::run( 'GetLocalURL', [ &$this, &$url, $query ] );
1807  return $url;
1808  }
1809 
1826  public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1827  if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
1828  $ret = $this->getFullURL( $query, $query2, $proto );
1829  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
1830  $ret = $this->getFragmentForURL();
1831  } else {
1832  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
1833  }
1834  return $ret;
1835  }
1836 
1849  public function getInternalURL( $query = '', $query2 = false ) {
1851  $query = self::fixUrlQueryArgs( $query, $query2 );
1852  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
1853  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
1854  Hooks::run( 'GetInternalURL', [ &$this, &$url, $query ] );
1855  return $url;
1856  }
1857 
1869  public function getCanonicalURL( $query = '', $query2 = false ) {
1870  $query = self::fixUrlQueryArgs( $query, $query2 );
1871  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
1872  Hooks::run( 'GetCanonicalURL', [ &$this, &$url, $query ] );
1873  return $url;
1874  }
1875 
1881  public function getEditURL() {
1882  if ( $this->isExternal() ) {
1883  return '';
1884  }
1885  $s = $this->getLocalURL( 'action=edit' );
1886 
1887  return $s;
1888  }
1889 
1904  public function quickUserCan( $action, $user = null ) {
1905  return $this->userCan( $action, $user, false );
1906  }
1907 
1917  public function userCan( $action, $user = null, $rigor = 'secure' ) {
1918  if ( !$user instanceof User ) {
1919  global $wgUser;
1920  $user = $wgUser;
1921  }
1922 
1923  return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
1924  }
1925 
1941  public function getUserPermissionsErrors(
1942  $action, $user, $rigor = 'secure', $ignoreErrors = []
1943  ) {
1944  $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
1945 
1946  // Remove the errors being ignored.
1947  foreach ( $errors as $index => $error ) {
1948  $errKey = is_array( $error ) ? $error[0] : $error;
1949 
1950  if ( in_array( $errKey, $ignoreErrors ) ) {
1951  unset( $errors[$index] );
1952  }
1953  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
1954  unset( $errors[$index] );
1955  }
1956  }
1957 
1958  return $errors;
1959  }
1960 
1972  private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
1973  if ( !Hooks::run( 'TitleQuickPermissions',
1974  [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
1975  ) {
1976  return $errors;
1977  }
1978 
1979  if ( $action == 'create' ) {
1980  if (
1981  ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1982  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
1983  ) {
1984  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
1985  }
1986  } elseif ( $action == 'move' ) {
1987  if ( !$user->isAllowed( 'move-rootuserpages' )
1988  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1989  // Show user page-specific message only if the user can move other pages
1990  $errors[] = [ 'cant-move-user-page' ];
1991  }
1992 
1993  // Check if user is allowed to move files if it's a file
1994  if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
1995  $errors[] = [ 'movenotallowedfile' ];
1996  }
1997 
1998  // Check if user is allowed to move category pages if it's a category page
1999  if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
2000  $errors[] = [ 'cant-move-category-page' ];
2001  }
2002 
2003  if ( !$user->isAllowed( 'move' ) ) {
2004  // User can't move anything
2005  $userCanMove = User::groupHasPermission( 'user', 'move' );
2006  $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
2007  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
2008  // custom message if logged-in users without any special rights can move
2009  $errors[] = [ 'movenologintext' ];
2010  } else {
2011  $errors[] = [ 'movenotallowed' ];
2012  }
2013  }
2014  } elseif ( $action == 'move-target' ) {
2015  if ( !$user->isAllowed( 'move' ) ) {
2016  // User can't move anything
2017  $errors[] = [ 'movenotallowed' ];
2018  } elseif ( !$user->isAllowed( 'move-rootuserpages' )
2019  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2020  // Show user page-specific message only if the user can move other pages
2021  $errors[] = [ 'cant-move-to-user-page' ];
2022  } elseif ( !$user->isAllowed( 'move-categorypages' )
2023  && $this->mNamespace == NS_CATEGORY ) {
2024  // Show category page-specific message only if the user can move other pages
2025  $errors[] = [ 'cant-move-to-category-page' ];
2026  }
2027  } elseif ( !$user->isAllowed( $action ) ) {
2028  $errors[] = $this->missingPermissionError( $action, $short );
2029  }
2030 
2031  return $errors;
2032  }
2033 
2042  private function resultToError( $errors, $result ) {
2043  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2044  // A single array representing an error
2045  $errors[] = $result;
2046  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2047  // A nested array representing multiple errors
2048  $errors = array_merge( $errors, $result );
2049  } elseif ( $result !== '' && is_string( $result ) ) {
2050  // A string representing a message-id
2051  $errors[] = [ $result ];
2052  } elseif ( $result instanceof MessageSpecifier ) {
2053  // A message specifier representing an error
2054  $errors[] = [ $result ];
2055  } elseif ( $result === false ) {
2056  // a generic "We don't want them to do that"
2057  $errors[] = [ 'badaccess-group0' ];
2058  }
2059  return $errors;
2060  }
2061 
2073  private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2074  // Use getUserPermissionsErrors instead
2075  $result = '';
2076  if ( !Hooks::run( 'userCan', [ &$this, &$user, $action, &$result ] ) ) {
2077  return $result ? [] : [ [ 'badaccess-group0' ] ];
2078  }
2079  // Check getUserPermissionsErrors hook
2080  if ( !Hooks::run( 'getUserPermissionsErrors', [ &$this, &$user, $action, &$result ] ) ) {
2081  $errors = $this->resultToError( $errors, $result );
2082  }
2083  // Check getUserPermissionsErrorsExpensive hook
2084  if (
2085  $rigor !== 'quick'
2086  && !( $short && count( $errors ) > 0 )
2087  && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$this, &$user, $action, &$result ] )
2088  ) {
2089  $errors = $this->resultToError( $errors, $result );
2090  }
2091 
2092  return $errors;
2093  }
2094 
2106  private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2107  # Only 'createaccount' can be performed on special pages,
2108  # which don't actually exist in the DB.
2109  if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
2110  $errors[] = [ 'ns-specialprotected' ];
2111  }
2112 
2113  # Check $wgNamespaceProtection for restricted namespaces
2114  if ( $this->isNamespaceProtected( $user ) ) {
2115  $ns = $this->mNamespace == NS_MAIN ?
2116  wfMessage( 'nstab-main' )->text() : $this->getNsText();
2117  $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2118  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2119  }
2120 
2121  return $errors;
2122  }
2123 
2135  private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
2136  # Protect css/js subpages of user pages
2137  # XXX: this might be better using restrictions
2138  # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
2139  if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
2140  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2141  if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
2142  $errors[] = [ 'mycustomcssprotected', $action ];
2143  } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
2144  $errors[] = [ 'mycustomjsprotected', $action ];
2145  }
2146  } else {
2147  if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
2148  $errors[] = [ 'customcssprotected', $action ];
2149  } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
2150  $errors[] = [ 'customjsprotected', $action ];
2151  }
2152  }
2153  }
2154 
2155  return $errors;
2156  }
2157 
2171  private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2172  foreach ( $this->getRestrictions( $action ) as $right ) {
2173  // Backwards compatibility, rewrite sysop -> editprotected
2174  if ( $right == 'sysop' ) {
2175  $right = 'editprotected';
2176  }
2177  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2178  if ( $right == 'autoconfirmed' ) {
2179  $right = 'editsemiprotected';
2180  }
2181  if ( $right == '' ) {
2182  continue;
2183  }
2184  if ( !$user->isAllowed( $right ) ) {
2185  $errors[] = [ 'protectedpagetext', $right, $action ];
2186  } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2187  $errors[] = [ 'protectedpagetext', 'protect', $action ];
2188  }
2189  }
2190 
2191  return $errors;
2192  }
2193 
2205  private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2206  if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
2207  # We /could/ use the protection level on the source page, but it's
2208  # fairly ugly as we have to establish a precedence hierarchy for pages
2209  # included by multiple cascade-protected pages. So just restrict
2210  # it to people with 'protect' permission, as they could remove the
2211  # protection anyway.
2212  list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2213  # Cascading protection depends on more than this page...
2214  # Several cascading protected pages may include this page...
2215  # Check each cascading level
2216  # This is only for protection restrictions, not for all actions
2217  if ( isset( $restrictions[$action] ) ) {
2218  foreach ( $restrictions[$action] as $right ) {
2219  // Backwards compatibility, rewrite sysop -> editprotected
2220  if ( $right == 'sysop' ) {
2221  $right = 'editprotected';
2222  }
2223  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2224  if ( $right == 'autoconfirmed' ) {
2225  $right = 'editsemiprotected';
2226  }
2227  if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2228  $pages = '';
2229  foreach ( $cascadingSources as $page ) {
2230  $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2231  }
2232  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2233  }
2234  }
2235  }
2236  }
2237 
2238  return $errors;
2239  }
2240 
2252  private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2253  global $wgDeleteRevisionsLimit, $wgLang;
2254 
2255  if ( $action == 'protect' ) {
2256  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2257  // If they can't edit, they shouldn't protect.
2258  $errors[] = [ 'protect-cantedit' ];
2259  }
2260  } elseif ( $action == 'create' ) {
2261  $title_protection = $this->getTitleProtection();
2262  if ( $title_protection ) {
2263  if ( $title_protection['permission'] == ''
2264  || !$user->isAllowed( $title_protection['permission'] )
2265  ) {
2266  $errors[] = [
2267  'titleprotected',
2268  User::whoIs( $title_protection['user'] ),
2269  $title_protection['reason']
2270  ];
2271  }
2272  }
2273  } elseif ( $action == 'move' ) {
2274  // Check for immobile pages
2275  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2276  // Specific message for this case
2277  $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2278  } elseif ( !$this->isMovable() ) {
2279  // Less specific message for rarer cases
2280  $errors[] = [ 'immobile-source-page' ];
2281  }
2282  } elseif ( $action == 'move-target' ) {
2283  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2284  $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2285  } elseif ( !$this->isMovable() ) {
2286  $errors[] = [ 'immobile-target-page' ];
2287  }
2288  } elseif ( $action == 'delete' ) {
2289  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2290  if ( !$tempErrors ) {
2291  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2292  $user, $tempErrors, $rigor, true );
2293  }
2294  if ( $tempErrors ) {
2295  // If protection keeps them from editing, they shouldn't be able to delete.
2296  $errors[] = [ 'deleteprotected' ];
2297  }
2298  if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2299  && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2300  ) {
2301  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2302  }
2303  } elseif ( $action === 'undelete' ) {
2304  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2305  // Undeleting implies editing
2306  $errors[] = [ 'undelete-cantedit' ];
2307  }
2308  if ( !$this->exists()
2309  && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
2310  ) {
2311  // Undeleting where nothing currently exists implies creating
2312  $errors[] = [ 'undelete-cantcreate' ];
2313  }
2314  }
2315  return $errors;
2316  }
2317 
2329  private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2330  global $wgEmailConfirmToEdit, $wgBlockDisablesLogin;
2331  // Account creation blocks handled at userlogin.
2332  // Unblocking handled in SpecialUnblock
2333  if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2334  return $errors;
2335  }
2336 
2337  // Optimize for a very common case
2338  if ( $action === 'read' && !$wgBlockDisablesLogin ) {
2339  return $errors;
2340  }
2341 
2342  if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
2343  $errors[] = [ 'confirmedittext' ];
2344  }
2345 
2346  $useSlave = ( $rigor !== 'secure' );
2347  if ( ( $action == 'edit' || $action == 'create' )
2348  && !$user->isBlockedFrom( $this, $useSlave )
2349  ) {
2350  // Don't block the user from editing their own talk page unless they've been
2351  // explicitly blocked from that too.
2352  } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2353  // @todo FIXME: Pass the relevant context into this function.
2354  $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2355  }
2356 
2357  return $errors;
2358  }
2359 
2371  private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2372  global $wgWhitelistRead, $wgWhitelistReadRegexp;
2373 
2374  $whitelisted = false;
2375  if ( User::isEveryoneAllowed( 'read' ) ) {
2376  # Shortcut for public wikis, allows skipping quite a bit of code
2377  $whitelisted = true;
2378  } elseif ( $user->isAllowed( 'read' ) ) {
2379  # If the user is allowed to read pages, he is allowed to read all pages
2380  $whitelisted = true;
2381  } elseif ( $this->isSpecial( 'Userlogin' )
2382  || $this->isSpecial( 'ChangePassword' )
2383  || $this->isSpecial( 'PasswordReset' )
2384  ) {
2385  # Always grant access to the login page.
2386  # Even anons need to be able to log in.
2387  $whitelisted = true;
2388  } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2389  # Time to check the whitelist
2390  # Only do these checks is there's something to check against
2391  $name = $this->getPrefixedText();
2392  $dbName = $this->getPrefixedDBkey();
2393 
2394  // Check for explicit whitelisting with and without underscores
2395  if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2396  $whitelisted = true;
2397  } elseif ( $this->getNamespace() == NS_MAIN ) {
2398  # Old settings might have the title prefixed with
2399  # a colon for main-namespace pages
2400  if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2401  $whitelisted = true;
2402  }
2403  } elseif ( $this->isSpecialPage() ) {
2404  # If it's a special page, ditch the subpage bit and check again
2405  $name = $this->getDBkey();
2406  list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2407  if ( $name ) {
2408  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2409  if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2410  $whitelisted = true;
2411  }
2412  }
2413  }
2414  }
2415 
2416  if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2417  $name = $this->getPrefixedText();
2418  // Check for regex whitelisting
2419  foreach ( $wgWhitelistReadRegexp as $listItem ) {
2420  if ( preg_match( $listItem, $name ) ) {
2421  $whitelisted = true;
2422  break;
2423  }
2424  }
2425  }
2426 
2427  if ( !$whitelisted ) {
2428  # If the title is not whitelisted, give extensions a chance to do so...
2429  Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2430  if ( !$whitelisted ) {
2431  $errors[] = $this->missingPermissionError( $action, $short );
2432  }
2433  }
2434 
2435  return $errors;
2436  }
2437 
2446  private function missingPermissionError( $action, $short ) {
2447  // We avoid expensive display logic for quickUserCan's and such
2448  if ( $short ) {
2449  return [ 'badaccess-group0' ];
2450  }
2451 
2452  $groups = array_map( [ 'User', 'makeGroupLinkWiki' ],
2453  User::getGroupsWithPermission( $action ) );
2454 
2455  if ( count( $groups ) ) {
2456  global $wgLang;
2457  return [
2458  'badaccess-groups',
2459  $wgLang->commaList( $groups ),
2460  count( $groups )
2461  ];
2462  } else {
2463  return [ 'badaccess-group0' ];
2464  }
2465  }
2466 
2482  $action, $user, $rigor = 'secure', $short = false
2483  ) {
2484  if ( $rigor === true ) {
2485  $rigor = 'secure'; // b/c
2486  } elseif ( $rigor === false ) {
2487  $rigor = 'quick'; // b/c
2488  } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2489  throw new Exception( "Invalid rigor parameter '$rigor'." );
2490  }
2491 
2492  # Read has special handling
2493  if ( $action == 'read' ) {
2494  $checks = [
2495  'checkPermissionHooks',
2496  'checkReadPermissions',
2497  'checkUserBlock', // for wgBlockDisablesLogin
2498  ];
2499  # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
2500  # here as it will lead to duplicate error messages. This is okay to do
2501  # since anywhere that checks for create will also check for edit, and
2502  # those checks are called for edit.
2503  } elseif ( $action == 'create' ) {
2504  $checks = [
2505  'checkQuickPermissions',
2506  'checkPermissionHooks',
2507  'checkPageRestrictions',
2508  'checkCascadingSourcesRestrictions',
2509  'checkActionPermissions',
2510  'checkUserBlock'
2511  ];
2512  } else {
2513  $checks = [
2514  'checkQuickPermissions',
2515  'checkPermissionHooks',
2516  'checkSpecialsAndNSPermissions',
2517  'checkCSSandJSPermissions',
2518  'checkPageRestrictions',
2519  'checkCascadingSourcesRestrictions',
2520  'checkActionPermissions',
2521  'checkUserBlock'
2522  ];
2523  }
2524 
2525  $errors = [];
2526  while ( count( $checks ) > 0 &&
2527  !( $short && count( $errors ) > 0 ) ) {
2528  $method = array_shift( $checks );
2529  $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2530  }
2531 
2532  return $errors;
2533  }
2534 
2542  public static function getFilteredRestrictionTypes( $exists = true ) {
2543  global $wgRestrictionTypes;
2544  $types = $wgRestrictionTypes;
2545  if ( $exists ) {
2546  # Remove the create restriction for existing titles
2547  $types = array_diff( $types, [ 'create' ] );
2548  } else {
2549  # Only the create and upload restrictions apply to non-existing titles
2550  $types = array_intersect( $types, [ 'create', 'upload' ] );
2551  }
2552  return $types;
2553  }
2554 
2560  public function getRestrictionTypes() {
2561  if ( $this->isSpecialPage() ) {
2562  return [];
2563  }
2564 
2565  $types = self::getFilteredRestrictionTypes( $this->exists() );
2566 
2567  if ( $this->getNamespace() != NS_FILE ) {
2568  # Remove the upload restriction for non-file titles
2569  $types = array_diff( $types, [ 'upload' ] );
2570  }
2571 
2572  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2573 
2574  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2575  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2576 
2577  return $types;
2578  }
2579 
2587  public function getTitleProtection() {
2588  // Can't protect pages in special namespaces
2589  if ( $this->getNamespace() < 0 ) {
2590  return false;
2591  }
2592 
2593  // Can't protect pages that exist.
2594  if ( $this->exists() ) {
2595  return false;
2596  }
2597 
2598  if ( $this->mTitleProtection === null ) {
2599  $dbr = wfGetDB( DB_SLAVE );
2600  $res = $dbr->select(
2601  'protected_titles',
2602  [
2603  'user' => 'pt_user',
2604  'reason' => 'pt_reason',
2605  'expiry' => 'pt_expiry',
2606  'permission' => 'pt_create_perm'
2607  ],
2608  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2609  __METHOD__
2610  );
2611 
2612  // fetchRow returns false if there are no rows.
2613  $row = $dbr->fetchRow( $res );
2614  if ( $row ) {
2615  if ( $row['permission'] == 'sysop' ) {
2616  $row['permission'] = 'editprotected'; // B/C
2617  }
2618  if ( $row['permission'] == 'autoconfirmed' ) {
2619  $row['permission'] = 'editsemiprotected'; // B/C
2620  }
2621  $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2622  }
2623  $this->mTitleProtection = $row;
2624  }
2625  return $this->mTitleProtection;
2626  }
2627 
2631  public function deleteTitleProtection() {
2632  $dbw = wfGetDB( DB_MASTER );
2633 
2634  $dbw->delete(
2635  'protected_titles',
2636  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2637  __METHOD__
2638  );
2639  $this->mTitleProtection = false;
2640  }
2641 
2649  public function isSemiProtected( $action = 'edit' ) {
2650  global $wgSemiprotectedRestrictionLevels;
2651 
2652  $restrictions = $this->getRestrictions( $action );
2653  $semi = $wgSemiprotectedRestrictionLevels;
2654  if ( !$restrictions || !$semi ) {
2655  // Not protected, or all protection is full protection
2656  return false;
2657  }
2658 
2659  // Remap autoconfirmed to editsemiprotected for BC
2660  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2661  $semi[$key] = 'editsemiprotected';
2662  }
2663  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2664  $restrictions[$key] = 'editsemiprotected';
2665  }
2666 
2667  return !array_diff( $restrictions, $semi );
2668  }
2669 
2677  public function isProtected( $action = '' ) {
2678  global $wgRestrictionLevels;
2679 
2680  $restrictionTypes = $this->getRestrictionTypes();
2681 
2682  # Special pages have inherent protection
2683  if ( $this->isSpecialPage() ) {
2684  return true;
2685  }
2686 
2687  # Check regular protection levels
2688  foreach ( $restrictionTypes as $type ) {
2689  if ( $action == $type || $action == '' ) {
2690  $r = $this->getRestrictions( $type );
2691  foreach ( $wgRestrictionLevels as $level ) {
2692  if ( in_array( $level, $r ) && $level != '' ) {
2693  return true;
2694  }
2695  }
2696  }
2697  }
2698 
2699  return false;
2700  }
2701 
2709  public function isNamespaceProtected( User $user ) {
2711 
2712  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2713  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2714  if ( $right != '' && !$user->isAllowed( $right ) ) {
2715  return true;
2716  }
2717  }
2718  }
2719  return false;
2720  }
2721 
2727  public function isCascadeProtected() {
2728  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2729  return ( $sources > 0 );
2730  }
2731 
2741  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2742  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2743  }
2744 
2758  public function getCascadeProtectionSources( $getPages = true ) {
2759  $pagerestrictions = [];
2760 
2761  if ( $this->mCascadeSources !== null && $getPages ) {
2763  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2764  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2765  }
2766 
2767  $dbr = wfGetDB( DB_SLAVE );
2768 
2769  if ( $this->getNamespace() == NS_FILE ) {
2770  $tables = [ 'imagelinks', 'page_restrictions' ];
2771  $where_clauses = [
2772  'il_to' => $this->getDBkey(),
2773  'il_from=pr_page',
2774  'pr_cascade' => 1
2775  ];
2776  } else {
2777  $tables = [ 'templatelinks', 'page_restrictions' ];
2778  $where_clauses = [
2779  'tl_namespace' => $this->getNamespace(),
2780  'tl_title' => $this->getDBkey(),
2781  'tl_from=pr_page',
2782  'pr_cascade' => 1
2783  ];
2784  }
2785 
2786  if ( $getPages ) {
2787  $cols = [ 'pr_page', 'page_namespace', 'page_title',
2788  'pr_expiry', 'pr_type', 'pr_level' ];
2789  $where_clauses[] = 'page_id=pr_page';
2790  $tables[] = 'page';
2791  } else {
2792  $cols = [ 'pr_expiry' ];
2793  }
2794 
2795  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2796 
2797  $sources = $getPages ? [] : false;
2798  $now = wfTimestampNow();
2799 
2800  foreach ( $res as $row ) {
2801  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2802  if ( $expiry > $now ) {
2803  if ( $getPages ) {
2804  $page_id = $row->pr_page;
2805  $page_ns = $row->page_namespace;
2806  $page_title = $row->page_title;
2807  $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2808  # Add groups needed for each restriction type if its not already there
2809  # Make sure this restriction type still exists
2810 
2811  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2812  $pagerestrictions[$row->pr_type] = [];
2813  }
2814 
2815  if (
2816  isset( $pagerestrictions[$row->pr_type] )
2817  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2818  ) {
2819  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2820  }
2821  } else {
2822  $sources = true;
2823  }
2824  }
2825  }
2826 
2827  if ( $getPages ) {
2828  $this->mCascadeSources = $sources;
2829  $this->mCascadingRestrictions = $pagerestrictions;
2830  } else {
2831  $this->mHasCascadingRestrictions = $sources;
2832  }
2833 
2834  return [ $sources, $pagerestrictions ];
2835  }
2836 
2844  public function areRestrictionsLoaded() {
2846  }
2847 
2857  public function getRestrictions( $action ) {
2858  if ( !$this->mRestrictionsLoaded ) {
2859  $this->loadRestrictions();
2860  }
2861  return isset( $this->mRestrictions[$action] )
2862  ? $this->mRestrictions[$action]
2863  : [];
2864  }
2865 
2873  public function getAllRestrictions() {
2874  if ( !$this->mRestrictionsLoaded ) {
2875  $this->loadRestrictions();
2876  }
2877  return $this->mRestrictions;
2878  }
2879 
2887  public function getRestrictionExpiry( $action ) {
2888  if ( !$this->mRestrictionsLoaded ) {
2889  $this->loadRestrictions();
2890  }
2891  return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2892  }
2893 
2900  if ( !$this->mRestrictionsLoaded ) {
2901  $this->loadRestrictions();
2902  }
2903 
2905  }
2906 
2914  private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
2915  $rows = [];
2916 
2917  foreach ( $res as $row ) {
2918  $rows[] = $row;
2919  }
2920 
2921  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2922  }
2923 
2933  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2934  $dbr = wfGetDB( DB_SLAVE );
2935 
2936  $restrictionTypes = $this->getRestrictionTypes();
2937 
2938  foreach ( $restrictionTypes as $type ) {
2939  $this->mRestrictions[$type] = [];
2940  $this->mRestrictionsExpiry[$type] = 'infinity';
2941  }
2942 
2943  $this->mCascadeRestriction = false;
2944 
2945  # Backwards-compatibility: also load the restrictions from the page record (old format).
2946  if ( $oldFashionedRestrictions !== null ) {
2947  $this->mOldRestrictions = $oldFashionedRestrictions;
2948  }
2949 
2950  if ( $this->mOldRestrictions === false ) {
2951  $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2952  [ 'page_id' => $this->getArticleID() ], __METHOD__ );
2953  }
2954 
2955  if ( $this->mOldRestrictions != '' ) {
2956  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2957  $temp = explode( '=', trim( $restrict ) );
2958  if ( count( $temp ) == 1 ) {
2959  // old old format should be treated as edit/move restriction
2960  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2961  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2962  } else {
2963  $restriction = trim( $temp[1] );
2964  if ( $restriction != '' ) { // some old entries are empty
2965  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2966  }
2967  }
2968  }
2969  }
2970 
2971  if ( count( $rows ) ) {
2972  # Current system - load second to make them override.
2973  $now = wfTimestampNow();
2974 
2975  # Cycle through all the restrictions.
2976  foreach ( $rows as $row ) {
2977 
2978  // Don't take care of restrictions types that aren't allowed
2979  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2980  continue;
2981  }
2982 
2983  // This code should be refactored, now that it's being used more generally,
2984  // But I don't really see any harm in leaving it in Block for now -werdna
2985  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2986 
2987  // Only apply the restrictions if they haven't expired!
2988  if ( !$expiry || $expiry > $now ) {
2989  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2990  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2991 
2992  $this->mCascadeRestriction |= $row->pr_cascade;
2993  }
2994  }
2995  }
2996 
2997  $this->mRestrictionsLoaded = true;
2998  }
2999 
3006  public function loadRestrictions( $oldFashionedRestrictions = null ) {
3007  if ( !$this->mRestrictionsLoaded ) {
3008  $dbr = wfGetDB( DB_SLAVE );
3009  if ( $this->exists() ) {
3010  $res = $dbr->select(
3011  'page_restrictions',
3012  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
3013  [ 'pr_page' => $this->getArticleID() ],
3014  __METHOD__
3015  );
3016 
3017  $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
3018  } else {
3019  $title_protection = $this->getTitleProtection();
3020 
3021  if ( $title_protection ) {
3022  $now = wfTimestampNow();
3023  $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
3024 
3025  if ( !$expiry || $expiry > $now ) {
3026  // Apply the restrictions
3027  $this->mRestrictionsExpiry['create'] = $expiry;
3028  $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
3029  } else { // Get rid of the old restrictions
3030  $this->mTitleProtection = false;
3031  }
3032  } else {
3033  $this->mRestrictionsExpiry['create'] = 'infinity';
3034  }
3035  $this->mRestrictionsLoaded = true;
3036  }
3037  }
3038  }
3039 
3044  public function flushRestrictions() {
3045  $this->mRestrictionsLoaded = false;
3046  $this->mTitleProtection = null;
3047  }
3048 
3052  static function purgeExpiredRestrictions() {
3053  if ( wfReadOnly() ) {
3054  return;
3055  }
3056 
3058  wfGetDB( DB_MASTER ),
3059  __METHOD__,
3060  function ( IDatabase $dbw, $fname ) {
3061  $dbw->delete(
3062  'page_restrictions',
3063  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3064  $fname
3065  );
3066  $dbw->delete(
3067  'protected_titles',
3068  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3069  $fname
3070  );
3071  }
3072  ) );
3073  }
3074 
3080  public function hasSubpages() {
3081  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3082  # Duh
3083  return false;
3084  }
3085 
3086  # We dynamically add a member variable for the purpose of this method
3087  # alone to cache the result. There's no point in having it hanging
3088  # around uninitialized in every Title object; therefore we only add it
3089  # if needed and don't declare it statically.
3090  if ( $this->mHasSubpages === null ) {
3091  $this->mHasSubpages = false;
3092  $subpages = $this->getSubpages( 1 );
3093  if ( $subpages instanceof TitleArray ) {
3094  $this->mHasSubpages = (bool)$subpages->count();
3095  }
3096  }
3097 
3098  return $this->mHasSubpages;
3099  }
3100 
3108  public function getSubpages( $limit = -1 ) {
3109  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3110  return [];
3111  }
3112 
3113  $dbr = wfGetDB( DB_SLAVE );
3114  $conds['page_namespace'] = $this->getNamespace();
3115  $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3116  $options = [];
3117  if ( $limit > -1 ) {
3118  $options['LIMIT'] = $limit;
3119  }
3120  $this->mSubpages = TitleArray::newFromResult(
3121  $dbr->select( 'page',
3122  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3123  $conds,
3124  __METHOD__,
3125  $options
3126  )
3127  );
3128  return $this->mSubpages;
3129  }
3130 
3136  public function isDeleted() {
3137  if ( $this->getNamespace() < 0 ) {
3138  $n = 0;
3139  } else {
3140  $dbr = wfGetDB( DB_SLAVE );
3141 
3142  $n = $dbr->selectField( 'archive', 'COUNT(*)',
3143  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3144  __METHOD__
3145  );
3146  if ( $this->getNamespace() == NS_FILE ) {
3147  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3148  [ 'fa_name' => $this->getDBkey() ],
3149  __METHOD__
3150  );
3151  }
3152  }
3153  return (int)$n;
3154  }
3155 
3161  public function isDeletedQuick() {
3162  if ( $this->getNamespace() < 0 ) {
3163  return false;
3164  }
3165  $dbr = wfGetDB( DB_SLAVE );
3166  $deleted = (bool)$dbr->selectField( 'archive', '1',
3167  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3168  __METHOD__
3169  );
3170  if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3171  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3172  [ 'fa_name' => $this->getDBkey() ],
3173  __METHOD__
3174  );
3175  }
3176  return $deleted;
3177  }
3178 
3187  public function getArticleID( $flags = 0 ) {
3188  if ( $this->getNamespace() < 0 ) {
3189  $this->mArticleID = 0;
3190  return $this->mArticleID;
3191  }
3192  $linkCache = LinkCache::singleton();
3193  if ( $flags & self::GAID_FOR_UPDATE ) {
3194  $oldUpdate = $linkCache->forUpdate( true );
3195  $linkCache->clearLink( $this );
3196  $this->mArticleID = $linkCache->addLinkObj( $this );
3197  $linkCache->forUpdate( $oldUpdate );
3198  } else {
3199  if ( -1 == $this->mArticleID ) {
3200  $this->mArticleID = $linkCache->addLinkObj( $this );
3201  }
3202  }
3203  return $this->mArticleID;
3204  }
3205 
3213  public function isRedirect( $flags = 0 ) {
3214  if ( !is_null( $this->mRedirect ) ) {
3215  return $this->mRedirect;
3216  }
3217  if ( !$this->getArticleID( $flags ) ) {
3218  $this->mRedirect = false;
3219  return $this->mRedirect;
3220  }
3221 
3222  $linkCache = LinkCache::singleton();
3223  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3224  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3225  if ( $cached === null ) {
3226  # Trust LinkCache's state over our own
3227  # LinkCache is telling us that the page doesn't exist, despite there being cached
3228  # data relating to an existing page in $this->mArticleID. Updaters should clear
3229  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3230  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3231  # LinkCache to refresh its data from the master.
3232  $this->mRedirect = false;
3233  return $this->mRedirect;
3234  }
3235 
3236  $this->mRedirect = (bool)$cached;
3237 
3238  return $this->mRedirect;
3239  }
3240 
3248  public function getLength( $flags = 0 ) {
3249  if ( $this->mLength != -1 ) {
3250  return $this->mLength;
3251  }
3252  if ( !$this->getArticleID( $flags ) ) {
3253  $this->mLength = 0;
3254  return $this->mLength;
3255  }
3256  $linkCache = LinkCache::singleton();
3257  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3258  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3259  if ( $cached === null ) {
3260  # Trust LinkCache's state over our own, as for isRedirect()
3261  $this->mLength = 0;
3262  return $this->mLength;
3263  }
3264 
3265  $this->mLength = intval( $cached );
3266 
3267  return $this->mLength;
3268  }
3269 
3276  public function getLatestRevID( $flags = 0 ) {
3277  if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3278  return intval( $this->mLatestID );
3279  }
3280  if ( !$this->getArticleID( $flags ) ) {
3281  $this->mLatestID = 0;
3282  return $this->mLatestID;
3283  }
3284  $linkCache = LinkCache::singleton();
3285  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3286  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3287  if ( $cached === null ) {
3288  # Trust LinkCache's state over our own, as for isRedirect()
3289  $this->mLatestID = 0;
3290  return $this->mLatestID;
3291  }
3292 
3293  $this->mLatestID = intval( $cached );
3294 
3295  return $this->mLatestID;
3296  }
3297 
3308  public function resetArticleID( $newid ) {
3309  $linkCache = LinkCache::singleton();
3310  $linkCache->clearLink( $this );
3311 
3312  if ( $newid === false ) {
3313  $this->mArticleID = -1;
3314  } else {
3315  $this->mArticleID = intval( $newid );
3316  }
3317  $this->mRestrictionsLoaded = false;
3318  $this->mRestrictions = [];
3319  $this->mOldRestrictions = false;
3320  $this->mRedirect = null;
3321  $this->mLength = -1;
3322  $this->mLatestID = false;
3323  $this->mContentModel = false;
3324  $this->mEstimateRevisions = null;
3325  $this->mPageLanguage = false;
3326  $this->mDbPageLanguage = false;
3327  $this->mIsBigDeletion = null;
3328  }
3329 
3330  public static function clearCaches() {
3331  $linkCache = LinkCache::singleton();
3332  $linkCache->clear();
3333 
3334  $titleCache = self::getTitleCache();
3335  $titleCache->clear();
3336  }
3337 
3345  public static function capitalize( $text, $ns = NS_MAIN ) {
3347 
3348  if ( MWNamespace::isCapitalized( $ns ) ) {
3349  return $wgContLang->ucfirst( $text );
3350  } else {
3351  return $text;
3352  }
3353  }
3354 
3367  private function secureAndSplit() {
3368  # Initialisation
3369  $this->mInterwiki = '';
3370  $this->mFragment = '';
3371  $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3372 
3373  $dbkey = $this->mDbkeyform;
3374 
3375  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3376  // the parsing code with Title, while avoiding massive refactoring.
3377  // @todo: get rid of secureAndSplit, refactor parsing code.
3378  $titleParser = self::getMediaWikiTitleCodec();
3379  // MalformedTitleException can be thrown here
3380  $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3381 
3382  # Fill fields
3383  $this->setFragment( '#' . $parts['fragment'] );
3384  $this->mInterwiki = $parts['interwiki'];
3385  $this->mLocalInterwiki = $parts['local_interwiki'];
3386  $this->mNamespace = $parts['namespace'];
3387  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3388 
3389  $this->mDbkeyform = $parts['dbkey'];
3390  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3391  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3392 
3393  # We already know that some pages won't be in the database!
3394  if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3395  $this->mArticleID = 0;
3396  }
3397 
3398  return true;
3399  }
3400 
3413  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3414  if ( count( $options ) > 0 ) {
3415  $db = wfGetDB( DB_MASTER );
3416  } else {
3417  $db = wfGetDB( DB_SLAVE );
3418  }
3419 
3420  $res = $db->select(
3421  [ 'page', $table ],
3422  self::getSelectFields(),
3423  [
3424  "{$prefix}_from=page_id",
3425  "{$prefix}_namespace" => $this->getNamespace(),
3426  "{$prefix}_title" => $this->getDBkey() ],
3427  __METHOD__,
3428  $options
3429  );
3430 
3431  $retVal = [];
3432  if ( $res->numRows() ) {
3433  $linkCache = LinkCache::singleton();
3434  foreach ( $res as $row ) {
3435  $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3436  if ( $titleObj ) {
3437  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3438  $retVal[] = $titleObj;
3439  }
3440  }
3441  }
3442  return $retVal;
3443  }
3444 
3455  public function getTemplateLinksTo( $options = [] ) {
3456  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3457  }
3458 
3471  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3472  $id = $this->getArticleID();
3473 
3474  # If the page doesn't exist; there can't be any link from this page
3475  if ( !$id ) {
3476  return [];
3477  }
3478 
3479  $db = wfGetDB( DB_SLAVE );
3480 
3481  $blNamespace = "{$prefix}_namespace";
3482  $blTitle = "{$prefix}_title";
3483 
3484  $res = $db->select(
3485  [ $table, 'page' ],
3486  array_merge(
3487  [ $blNamespace, $blTitle ],
3489  ),
3490  [ "{$prefix}_from" => $id ],
3491  __METHOD__,
3492  $options,
3493  [ 'page' => [
3494  'LEFT JOIN',
3495  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3496  ] ]
3497  );
3498 
3499  $retVal = [];
3500  $linkCache = LinkCache::singleton();
3501  foreach ( $res as $row ) {
3502  if ( $row->page_id ) {
3503  $titleObj = Title::newFromRow( $row );
3504  } else {
3505  $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3506  $linkCache->addBadLinkObj( $titleObj );
3507  }
3508  $retVal[] = $titleObj;
3509  }
3510 
3511  return $retVal;
3512  }
3513 
3524  public function getTemplateLinksFrom( $options = [] ) {
3525  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3526  }
3527 
3536  public function getBrokenLinksFrom() {
3537  if ( $this->getArticleID() == 0 ) {
3538  # All links from article ID 0 are false positives
3539  return [];
3540  }
3541 
3542  $dbr = wfGetDB( DB_SLAVE );
3543  $res = $dbr->select(
3544  [ 'page', 'pagelinks' ],
3545  [ 'pl_namespace', 'pl_title' ],
3546  [
3547  'pl_from' => $this->getArticleID(),
3548  'page_namespace IS NULL'
3549  ],
3550  __METHOD__, [],
3551  [
3552  'page' => [
3553  'LEFT JOIN',
3554  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3555  ]
3556  ]
3557  );
3558 
3559  $retVal = [];
3560  foreach ( $res as $row ) {
3561  $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3562  }
3563  return $retVal;
3564  }
3565 
3572  public function getCdnUrls() {
3573  $urls = [
3574  $this->getInternalURL(),
3575  $this->getInternalURL( 'action=history' )
3576  ];
3577 
3578  $pageLang = $this->getPageLanguage();
3579  if ( $pageLang->hasVariants() ) {
3580  $variants = $pageLang->getVariants();
3581  foreach ( $variants as $vCode ) {
3582  $urls[] = $this->getInternalURL( $vCode );
3583  }
3584  }
3585 
3586  // If we are looking at a css/js user subpage, purge the action=raw.
3587  if ( $this->isJsSubpage() ) {
3588  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3589  } elseif ( $this->isCssSubpage() ) {
3590  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3591  }
3592 
3593  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3594  return $urls;
3595  }
3596 
3600  public function getSquidURLs() {
3601  return $this->getCdnUrls();
3602  }
3603 
3607  public function purgeSquid() {
3609  new CdnCacheUpdate( $this->getCdnUrls() ),
3611  );
3612  }
3613 
3621  public function moveNoAuth( &$nt ) {
3622  wfDeprecated( __METHOD__, '1.25' );
3623  return $this->moveTo( $nt, false );
3624  }
3625 
3636  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3637  global $wgUser;
3638 
3639  if ( !( $nt instanceof Title ) ) {
3640  // Normally we'd add this to $errors, but we'll get
3641  // lots of syntax errors if $nt is not an object
3642  return [ [ 'badtitletext' ] ];
3643  }
3644 
3645  $mp = new MovePage( $this, $nt );
3646  $errors = $mp->isValidMove()->getErrorsArray();
3647  if ( $auth ) {
3648  $errors = wfMergeErrorArrays(
3649  $errors,
3650  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3651  );
3652  }
3653 
3654  return $errors ?: true;
3655  }
3656 
3663  protected function validateFileMoveOperation( $nt ) {
3664  global $wgUser;
3665 
3666  $errors = [];
3667 
3668  $destFile = wfLocalFile( $nt );
3669  $destFile->load( File::READ_LATEST );
3670  if ( !$wgUser->isAllowed( 'reupload-shared' )
3671  && !$destFile->exists() && wfFindFile( $nt )
3672  ) {
3673  $errors[] = [ 'file-exists-sharedrepo' ];
3674  }
3675 
3676  return $errors;
3677  }
3678 
3691  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
3692  global $wgUser;
3693  $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3694  if ( is_array( $err ) ) {
3695  // Auto-block user's IP if the account was "hard" blocked
3696  $wgUser->spreadAnyEditBlock();
3697  return $err;
3698  }
3699  // Check suppressredirect permission
3700  if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3701  $createRedirect = true;
3702  }
3703 
3704  $mp = new MovePage( $this, $nt );
3705  $status = $mp->move( $wgUser, $reason, $createRedirect );
3706  if ( $status->isOK() ) {
3707  return true;
3708  } else {
3709  return $status->getErrorsArray();
3710  }
3711  }
3712 
3725  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
3726  global $wgMaximumMovedPages;
3727  // Check permissions
3728  if ( !$this->userCan( 'move-subpages' ) ) {
3729  return [ 'cant-move-subpages' ];
3730  }
3731  // Do the source and target namespaces support subpages?
3732  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3733  return [ 'namespace-nosubpages',
3735  }
3736  if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3737  return [ 'namespace-nosubpages',
3738  MWNamespace::getCanonicalName( $nt->getNamespace() ) ];
3739  }
3740 
3741  $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3742  $retval = [];
3743  $count = 0;
3744  foreach ( $subpages as $oldSubpage ) {
3745  $count++;
3746  if ( $count > $wgMaximumMovedPages ) {
3747  $retval[$oldSubpage->getPrefixedText()] =
3748  [ 'movepage-max-pages',
3749  $wgMaximumMovedPages ];
3750  break;
3751  }
3752 
3753  // We don't know whether this function was called before
3754  // or after moving the root page, so check both
3755  // $this and $nt
3756  if ( $oldSubpage->getArticleID() == $this->getArticleID()
3757  || $oldSubpage->getArticleID() == $nt->getArticleID()
3758  ) {
3759  // When moving a page to a subpage of itself,
3760  // don't move it twice
3761  continue;
3762  }
3763  $newPageName = preg_replace(
3764  '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3765  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
3766  $oldSubpage->getDBkey() );
3767  if ( $oldSubpage->isTalkPage() ) {
3768  $newNs = $nt->getTalkPage()->getNamespace();
3769  } else {
3770  $newNs = $nt->getSubjectPage()->getNamespace();
3771  }
3772  # Bug 14385: we need makeTitleSafe because the new page names may
3773  # be longer than 255 characters.
3774  $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3775 
3776  $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
3777  if ( $success === true ) {
3778  $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3779  } else {
3780  $retval[$oldSubpage->getPrefixedText()] = $success;
3781  }
3782  }
3783  return $retval;
3784  }
3785 
3792  public function isSingleRevRedirect() {
3793  global $wgContentHandlerUseDB;
3794 
3795  $dbw = wfGetDB( DB_MASTER );
3796 
3797  # Is it a redirect?
3798  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3799  if ( $wgContentHandlerUseDB ) {
3800  $fields[] = 'page_content_model';
3801  }
3802 
3803  $row = $dbw->selectRow( 'page',
3804  $fields,
3805  $this->pageCond(),
3806  __METHOD__,
3807  [ 'FOR UPDATE' ]
3808  );
3809  # Cache some fields we may want
3810  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3811  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3812  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3813  $this->mContentModel = $row && isset( $row->page_content_model )
3814  ? strval( $row->page_content_model )
3815  : false;
3816 
3817  if ( !$this->mRedirect ) {
3818  return false;
3819  }
3820  # Does the article have a history?
3821  $row = $dbw->selectField( [ 'page', 'revision' ],
3822  'rev_id',
3823  [ 'page_namespace' => $this->getNamespace(),
3824  'page_title' => $this->getDBkey(),
3825  'page_id=rev_page',
3826  'page_latest != rev_id'
3827  ],
3828  __METHOD__,
3829  [ 'FOR UPDATE' ]
3830  );
3831  # Return true if there was no history
3832  return ( $row === false );
3833  }
3834 
3843  public function isValidMoveTarget( $nt ) {
3844  # Is it an existing file?
3845  if ( $nt->getNamespace() == NS_FILE ) {
3846  $file = wfLocalFile( $nt );
3847  $file->load( File::READ_LATEST );
3848  if ( $file->exists() ) {
3849  wfDebug( __METHOD__ . ": file exists\n" );
3850  return false;
3851  }
3852  }
3853  # Is it a redirect with no history?
3854  if ( !$nt->isSingleRevRedirect() ) {
3855  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3856  return false;
3857  }
3858  # Get the article text
3860  if ( !is_object( $rev ) ) {
3861  return false;
3862  }
3863  $content = $rev->getContent();
3864  # Does the redirect point to the source?
3865  # Or is it a broken self-redirect, usually caused by namespace collisions?
3866  $redirTitle = $content ? $content->getRedirectTarget() : null;
3867 
3868  if ( $redirTitle ) {
3869  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3870  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3871  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3872  return false;
3873  } else {
3874  return true;
3875  }
3876  } else {
3877  # Fail safe (not a redirect after all. strange.)
3878  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3879  " is a redirect, but it doesn't contain a valid redirect.\n" );
3880  return false;
3881  }
3882  }
3883 
3891  public function getParentCategories() {
3893 
3894  $data = [];
3895 
3896  $titleKey = $this->getArticleID();
3897 
3898  if ( $titleKey === 0 ) {
3899  return $data;
3900  }
3901 
3902  $dbr = wfGetDB( DB_SLAVE );
3903 
3904  $res = $dbr->select(
3905  'categorylinks',
3906  'cl_to',
3907  [ 'cl_from' => $titleKey ],
3908  __METHOD__
3909  );
3910 
3911  if ( $res->numRows() > 0 ) {
3912  foreach ( $res as $row ) {
3913  // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3914  $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3915  }
3916  }
3917  return $data;
3918  }
3919 
3926  public function getParentCategoryTree( $children = [] ) {
3927  $stack = [];
3928  $parents = $this->getParentCategories();
3929 
3930  if ( $parents ) {
3931  foreach ( $parents as $parent => $current ) {
3932  if ( array_key_exists( $parent, $children ) ) {
3933  # Circular reference
3934  $stack[$parent] = [];
3935  } else {
3936  $nt = Title::newFromText( $parent );
3937  if ( $nt ) {
3938  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3939  }
3940  }
3941  }
3942  }
3943 
3944  return $stack;
3945  }
3946 
3953  public function pageCond() {
3954  if ( $this->mArticleID > 0 ) {
3955  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3956  return [ 'page_id' => $this->mArticleID ];
3957  } else {
3958  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3959  }
3960  }
3961 
3969  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3970  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3971  $revId = $db->selectField( 'revision', 'rev_id',
3972  [
3973  'rev_page' => $this->getArticleID( $flags ),
3974  'rev_id < ' . intval( $revId )
3975  ],
3976  __METHOD__,
3977  [ 'ORDER BY' => 'rev_id DESC' ]
3978  );
3979 
3980  if ( $revId === false ) {
3981  return false;
3982  } else {
3983  return intval( $revId );
3984  }
3985  }
3986 
3994  public function getNextRevisionID( $revId, $flags = 0 ) {
3995  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3996  $revId = $db->selectField( 'revision', 'rev_id',
3997  [
3998  'rev_page' => $this->getArticleID( $flags ),
3999  'rev_id > ' . intval( $revId )
4000  ],
4001  __METHOD__,
4002  [ 'ORDER BY' => 'rev_id' ]
4003  );
4004 
4005  if ( $revId === false ) {
4006  return false;
4007  } else {
4008  return intval( $revId );
4009  }
4010  }
4011 
4018  public function getFirstRevision( $flags = 0 ) {
4019  $pageId = $this->getArticleID( $flags );
4020  if ( $pageId ) {
4021  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
4022  $row = $db->selectRow( 'revision', Revision::selectFields(),
4023  [ 'rev_page' => $pageId ],
4024  __METHOD__,
4025  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ]
4026  );
4027  if ( $row ) {
4028  return new Revision( $row );
4029  }
4030  }
4031  return null;
4032  }
4033 
4040  public function getEarliestRevTime( $flags = 0 ) {
4041  $rev = $this->getFirstRevision( $flags );
4042  return $rev ? $rev->getTimestamp() : null;
4043  }
4044 
4050  public function isNewPage() {
4051  $dbr = wfGetDB( DB_SLAVE );
4052  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4053  }
4054 
4060  public function isBigDeletion() {
4061  global $wgDeleteRevisionsLimit;
4062 
4063  if ( !$wgDeleteRevisionsLimit ) {
4064  return false;
4065  }
4066 
4067  if ( $this->mIsBigDeletion === null ) {
4068  $dbr = wfGetDB( DB_SLAVE );
4069 
4070  $revCount = $dbr->selectRowCount(
4071  'revision',
4072  '1',
4073  [ 'rev_page' => $this->getArticleID() ],
4074  __METHOD__,
4075  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4076  );
4077 
4078  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4079  }
4080 
4081  return $this->mIsBigDeletion;
4082  }
4083 
4089  public function estimateRevisionCount() {
4090  if ( !$this->exists() ) {
4091  return 0;
4092  }
4093 
4094  if ( $this->mEstimateRevisions === null ) {
4095  $dbr = wfGetDB( DB_SLAVE );
4096  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4097  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4098  }
4099 
4101  }
4102 
4112  public function countRevisionsBetween( $old, $new, $max = null ) {
4113  if ( !( $old instanceof Revision ) ) {
4114  $old = Revision::newFromTitle( $this, (int)$old );
4115  }
4116  if ( !( $new instanceof Revision ) ) {
4117  $new = Revision::newFromTitle( $this, (int)$new );
4118  }
4119  if ( !$old || !$new ) {
4120  return 0; // nothing to compare
4121  }
4122  $dbr = wfGetDB( DB_SLAVE );
4123  $conds = [
4124  'rev_page' => $this->getArticleID(),
4125  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4126  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4127  ];
4128  if ( $max !== null ) {
4129  return $dbr->selectRowCount( 'revision', '1',
4130  $conds,
4131  __METHOD__,
4132  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4133  );
4134  } else {
4135  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4136  }
4137  }
4138 
4155  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4156  if ( !( $old instanceof Revision ) ) {
4157  $old = Revision::newFromTitle( $this, (int)$old );
4158  }
4159  if ( !( $new instanceof Revision ) ) {
4160  $new = Revision::newFromTitle( $this, (int)$new );
4161  }
4162  // XXX: what if Revision objects are passed in, but they don't refer to this title?
4163  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4164  // in the sanity check below?
4165  if ( !$old || !$new ) {
4166  return null; // nothing to compare
4167  }
4168  $authors = [];
4169  $old_cmp = '>';
4170  $new_cmp = '<';
4171  $options = (array)$options;
4172  if ( in_array( 'include_old', $options ) ) {
4173  $old_cmp = '>=';
4174  }
4175  if ( in_array( 'include_new', $options ) ) {
4176  $new_cmp = '<=';
4177  }
4178  if ( in_array( 'include_both', $options ) ) {
4179  $old_cmp = '>=';
4180  $new_cmp = '<=';
4181  }
4182  // No DB query needed if $old and $new are the same or successive revisions:
4183  if ( $old->getId() === $new->getId() ) {
4184  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4185  [] :
4186  [ $old->getUserText( Revision::RAW ) ];
4187  } elseif ( $old->getId() === $new->getParentId() ) {
4188  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4189  $authors[] = $old->getUserText( Revision::RAW );
4190  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4191  $authors[] = $new->getUserText( Revision::RAW );
4192  }
4193  } elseif ( $old_cmp === '>=' ) {
4194  $authors[] = $old->getUserText( Revision::RAW );
4195  } elseif ( $new_cmp === '<=' ) {
4196  $authors[] = $new->getUserText( Revision::RAW );
4197  }
4198  return $authors;
4199  }
4200  $dbr = wfGetDB( DB_SLAVE );
4201  $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4202  [
4203  'rev_page' => $this->getArticleID(),
4204  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4205  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4206  ], __METHOD__,
4207  [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
4208  );
4209  foreach ( $res as $row ) {
4210  $authors[] = $row->rev_user_text;
4211  }
4212  return $authors;
4213  }
4214 
4229  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4230  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4231  return $authors ? count( $authors ) : 0;
4232  }
4233 
4240  public function equals( Title $title ) {
4241  // Note: === is necessary for proper matching of number-like titles.
4242  return $this->getInterwiki() === $title->getInterwiki()
4243  && $this->getNamespace() == $title->getNamespace()
4244  && $this->getDBkey() === $title->getDBkey();
4245  }
4246 
4253  public function isSubpageOf( Title $title ) {
4254  return $this->getInterwiki() === $title->getInterwiki()
4255  && $this->getNamespace() == $title->getNamespace()
4256  && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4257  }
4258 
4270  public function exists( $flags = 0 ) {
4271  $exists = $this->getArticleID( $flags ) != 0;
4272  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4273  return $exists;
4274  }
4275 
4292  public function isAlwaysKnown() {
4293  $isKnown = null;
4294 
4305  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4306 
4307  if ( !is_null( $isKnown ) ) {
4308  return $isKnown;
4309  }
4310 
4311  if ( $this->isExternal() ) {
4312  return true; // any interwiki link might be viewable, for all we know
4313  }
4314 
4315  switch ( $this->mNamespace ) {
4316  case NS_MEDIA:
4317  case NS_FILE:
4318  // file exists, possibly in a foreign repo
4319  return (bool)wfFindFile( $this );
4320  case NS_SPECIAL:
4321  // valid special page
4322  return SpecialPageFactory::exists( $this->getDBkey() );
4323  case NS_MAIN:
4324  // selflink, possibly with fragment
4325  return $this->mDbkeyform == '';
4326  case NS_MEDIAWIKI:
4327  // known system message
4328  return $this->hasSourceText() !== false;
4329  default:
4330  return false;
4331  }
4332  }
4333 
4345  public function isKnown() {
4346  return $this->isAlwaysKnown() || $this->exists();
4347  }
4348 
4354  public function hasSourceText() {
4355  if ( $this->exists() ) {
4356  return true;
4357  }
4358 
4359  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4360  // If the page doesn't exist but is a known system message, default
4361  // message content will be displayed, same for language subpages-
4362  // Use always content language to avoid loading hundreds of languages
4363  // to get the link color.
4365  list( $name, ) = MessageCache::singleton()->figureMessage(
4366  $wgContLang->lcfirst( $this->getText() )
4367  );
4368  $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4369  return $message->exists();
4370  }
4371 
4372  return false;
4373  }
4374 
4380  public function getDefaultMessageText() {
4382 
4383  if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4384  return false;
4385  }
4386 
4387  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4388  $wgContLang->lcfirst( $this->getText() )
4389  );
4390  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4391 
4392  if ( $message->exists() ) {
4393  return $message->plain();
4394  } else {
4395  return false;
4396  }
4397  }
4398 
4405  public function invalidateCache( $purgeTime = null ) {
4406  if ( wfReadOnly() ) {
4407  return false;
4408  }
4409 
4410  if ( $this->mArticleID === 0 ) {
4411  return true; // avoid gap locking if we know it's not there
4412  }
4413 
4414  $method = __METHOD__;
4415  $dbw = wfGetDB( DB_MASTER );
4416  $conds = $this->pageCond();
4417  $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method, $purgeTime ) {
4418  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4419 
4420  $dbw->update(
4421  'page',
4422  [ 'page_touched' => $dbTimestamp ],
4423  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4424  $method
4425  );
4426  } );
4427 
4428  return true;
4429  }
4430 
4436  public function touchLinks() {
4437  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4438  if ( $this->getNamespace() == NS_CATEGORY ) {
4439  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4440  }
4441  }
4442 
4449  public function getTouched( $db = null ) {
4450  if ( $db === null ) {
4451  $db = wfGetDB( DB_SLAVE );
4452  }
4453  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4454  return $touched;
4455  }
4456 
4463  public function getNotificationTimestamp( $user = null ) {
4464  global $wgUser;
4465 
4466  // Assume current user if none given
4467  if ( !$user ) {
4468  $user = $wgUser;
4469  }
4470  // Check cache first
4471  $uid = $user->getId();
4472  if ( !$uid ) {
4473  return false;
4474  }
4475  // avoid isset here, as it'll return false for null entries
4476  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4477  return $this->mNotificationTimestamp[$uid];
4478  }
4479  // Don't cache too much!
4480  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4481  $this->mNotificationTimestamp = [];
4482  }
4483 
4484  $watchedItem = WatchedItemStore::getDefaultInstance()->getWatchedItem( $user, $this );
4485  if ( $watchedItem ) {
4486  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4487  } else {
4488  $this->mNotificationTimestamp[$uid] = false;
4489  }
4490 
4491  return $this->mNotificationTimestamp[$uid];
4492  }
4493 
4500  public function getNamespaceKey( $prepend = 'nstab-' ) {
4502  // Gets the subject namespace if this title
4503  $namespace = MWNamespace::getSubject( $this->getNamespace() );
4504  // Checks if canonical namespace name exists for namespace
4505  if ( MWNamespace::exists( $this->getNamespace() ) ) {
4506  // Uses canonical namespace name
4507  $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4508  } else {
4509  // Uses text of namespace
4510  $namespaceKey = $this->getSubjectNsText();
4511  }
4512  // Makes namespace key lowercase
4513  $namespaceKey = $wgContLang->lc( $namespaceKey );
4514  // Uses main
4515  if ( $namespaceKey == '' ) {
4516  $namespaceKey = 'main';
4517  }
4518  // Changes file to image for backwards compatibility
4519  if ( $namespaceKey == 'file' ) {
4520  $namespaceKey = 'image';
4521  }
4522  return $prepend . $namespaceKey;
4523  }
4524 
4531  public function getRedirectsHere( $ns = null ) {
4532  $redirs = [];
4533 
4534  $dbr = wfGetDB( DB_SLAVE );
4535  $where = [
4536  'rd_namespace' => $this->getNamespace(),
4537  'rd_title' => $this->getDBkey(),
4538  'rd_from = page_id'
4539  ];
4540  if ( $this->isExternal() ) {
4541  $where['rd_interwiki'] = $this->getInterwiki();
4542  } else {
4543  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4544  }
4545  if ( !is_null( $ns ) ) {
4546  $where['page_namespace'] = $ns;
4547  }
4548 
4549  $res = $dbr->select(
4550  [ 'redirect', 'page' ],
4551  [ 'page_namespace', 'page_title' ],
4552  $where,
4553  __METHOD__
4554  );
4555 
4556  foreach ( $res as $row ) {
4557  $redirs[] = self::newFromRow( $row );
4558  }
4559  return $redirs;
4560  }
4561 
4567  public function isValidRedirectTarget() {
4568  global $wgInvalidRedirectTargets;
4569 
4570  if ( $this->isSpecialPage() ) {
4571  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4572  if ( $this->isSpecial( 'Userlogout' ) ) {
4573  return false;
4574  }
4575 
4576  foreach ( $wgInvalidRedirectTargets as $target ) {
4577  if ( $this->isSpecial( $target ) ) {
4578  return false;
4579  }
4580  }
4581  }
4582 
4583  return true;
4584  }
4585 
4591  public function getBacklinkCache() {
4592  return BacklinkCache::get( $this );
4593  }
4594 
4600  public function canUseNoindex() {
4601  global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
4602 
4603  $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4604  ? $wgContentNamespaces
4605  : $wgExemptFromUserRobotsControl;
4606 
4607  return !in_array( $this->mNamespace, $bannedNamespaces );
4608 
4609  }
4610 
4621  public function getCategorySortkey( $prefix = '' ) {
4622  $unprefixed = $this->getText();
4623 
4624  // Anything that uses this hook should only depend
4625  // on the Title object passed in, and should probably
4626  // tell the users to run updateCollations.php --force
4627  // in order to re-sort existing category relations.
4628  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4629  if ( $prefix !== '' ) {
4630  # Separate with a line feed, so the unprefixed part is only used as
4631  # a tiebreaker when two pages have the exact same prefix.
4632  # In UCA, tab is the only character that can sort above LF
4633  # so we strip both of them from the original prefix.
4634  $prefix = strtr( $prefix, "\n\t", ' ' );
4635  return "$prefix\n$unprefixed";
4636  }
4637  return $unprefixed;
4638  }
4639 
4647  private function getDbPageLanguageCode() {
4648  global $wgPageLanguageUseDB;
4649 
4650  // check, if the page language could be saved in the database, and if so and
4651  // the value is not requested already, lookup the page language using LinkCache
4652  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4653  $linkCache = LinkCache::singleton();
4654  $linkCache->addLinkObj( $this );
4655  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4656  }
4657 
4658  return $this->mDbPageLanguage;
4659  }
4660 
4669  public function getPageLanguage() {
4671  if ( $this->isSpecialPage() ) {
4672  // special pages are in the user language
4673  return $wgLang;
4674  }
4675 
4676  // Checking if DB language is set
4677  $dbPageLanguage = $this->getDbPageLanguageCode();
4678  if ( $dbPageLanguage ) {
4679  return wfGetLangObj( $dbPageLanguage );
4680  }
4681 
4682  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4683  // Note that this may depend on user settings, so the cache should
4684  // be only per-request.
4685  // NOTE: ContentHandler::getPageLanguage() may need to load the
4686  // content to determine the page language!
4687  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4688  // tests.
4689  $contentHandler = ContentHandler::getForTitle( $this );
4690  $langObj = $contentHandler->getPageLanguage( $this );
4691  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4692  } else {
4693  $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4694  }
4695 
4696  return $langObj;
4697  }
4698 
4707  public function getPageViewLanguage() {
4708  global $wgLang;
4709 
4710  if ( $this->isSpecialPage() ) {
4711  // If the user chooses a variant, the content is actually
4712  // in a language whose code is the variant code.
4713  $variant = $wgLang->getPreferredVariant();
4714  if ( $wgLang->getCode() !== $variant ) {
4715  return Language::factory( $variant );
4716  }
4717 
4718  return $wgLang;
4719  }
4720 
4721  // Checking if DB language is set
4722  $dbPageLanguage = $this->getDbPageLanguageCode();
4723  if ( $dbPageLanguage ) {
4724  $pageLang = wfGetLangObj( $dbPageLanguage );
4725  $variant = $pageLang->getPreferredVariant();
4726  if ( $pageLang->getCode() !== $variant ) {
4727  $pageLang = Language::factory( $variant );
4728  }
4729 
4730  return $pageLang;
4731  }
4732 
4733  // @note Can't be cached persistently, depends on user settings.
4734  // @note ContentHandler::getPageViewLanguage() may need to load the
4735  // content to determine the page language!
4736  $contentHandler = ContentHandler::getForTitle( $this );
4737  $pageLang = $contentHandler->getPageViewLanguage( $this );
4738  return $pageLang;
4739  }
4740 
4751  public function getEditNotices( $oldid = 0 ) {
4752  $notices = [];
4753 
4754  // Optional notice for the entire namespace
4755  $editnotice_ns = 'editnotice-' . $this->getNamespace();
4756  $msg = wfMessage( $editnotice_ns );
4757  if ( $msg->exists() ) {
4758  $html = $msg->parseAsBlock();
4759  // Edit notices may have complex logic, but output nothing (T91715)
4760  if ( trim( $html ) !== '' ) {
4761  $notices[$editnotice_ns] = Html::rawElement(
4762  'div',
4763  [ 'class' => [
4764  'mw-editnotice',
4765  'mw-editnotice-namespace',
4766  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4767  ] ],
4768  $html
4769  );
4770  }
4771  }
4772 
4773  if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4774  // Optional notice for page itself and any parent page
4775  $parts = explode( '/', $this->getDBkey() );
4776  $editnotice_base = $editnotice_ns;
4777  while ( count( $parts ) > 0 ) {
4778  $editnotice_base .= '-' . array_shift( $parts );
4779  $msg = wfMessage( $editnotice_base );
4780  if ( $msg->exists() ) {
4781  $html = $msg->parseAsBlock();
4782  if ( trim( $html ) !== '' ) {
4783  $notices[$editnotice_base] = Html::rawElement(
4784  'div',
4785  [ 'class' => [
4786  'mw-editnotice',
4787  'mw-editnotice-base',
4788  Sanitizer::escapeClass( "mw-$editnotice_base" )
4789  ] ],
4790  $html
4791  );
4792  }
4793  }
4794  }
4795  } else {
4796  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4797  $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4798  $msg = wfMessage( $editnoticeText );
4799  if ( $msg->exists() ) {
4800  $html = $msg->parseAsBlock();
4801  if ( trim( $html ) !== '' ) {
4802  $notices[$editnoticeText] = Html::rawElement(
4803  'div',
4804  [ 'class' => [
4805  'mw-editnotice',
4806  'mw-editnotice-page',
4807  Sanitizer::escapeClass( "mw-$editnoticeText" )
4808  ] ],
4809  $html
4810  );
4811  }
4812  }
4813  }
4814 
4815  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4816  return $notices;
4817  }
4818 
4822  public function __sleep() {
4823  return [
4824  'mNamespace',
4825  'mDbkeyform',
4826  'mFragment',
4827  'mInterwiki',
4828  'mLocalInterwiki',
4829  'mUserCaseDBKey',
4830  'mDefaultNamespace',
4831  ];
4832  }
4833 
4834  public function __wakeup() {
4835  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4836  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4837  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4838  }
4839 
4840 }
getEarliestRevTime($flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:4040
bool $mHasSubpages
Whether a page has any subpages.
Definition: Title.php:144
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition: Title.php:4292
static newFromRow($row)
Make a Title object from a DB row.
Definition: Title.php:465
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition: Title.php:3052
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3276
setFragment($fragment)
Set the fragment for this title.
Definition: Title.php:1391
static newFromID($id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:417
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Definition: Title.php:620
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:750
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:4436
A codec for MediaWiki page titles.
getFragment()
Get the Title fragment (i.e.
Definition: Title.php:1353
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:1798
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:125
exists($flags=0)
Check if page exists.
Definition: Title.php:4270
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:1700
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable...
Definition: Title.php:1154
getInternalURL($query= '', $query2=false)
Get the URL form for an internal link.
Definition: Title.php:1849
getRootTitle()
Get the root page name title, i.e.
Definition: Title.php:1513
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:762
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:1418
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition: Title.php:4600
isJsSubpage()
Is this a .js subpage of a user page?
Definition: Title.php:1280
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:277
wasLocalInterwiki()
Was this a local interwiki link?
Definition: Title.php:830
getSquidURLs()
Definition: Title.php:3600
$wgScript
The URL path to index.php.
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page...
Definition: Title.php:1144
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:920
Handles the backend logic of moving a page from one title to another.
Definition: MovePage.php:28
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition: Title.php:2709
isSpecial($name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1055
static clearCaches()
Definition: Title.php:3330
getArticleID($flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3187
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:3080
const NS_MAIN
Definition: Defines.php:69
$success
static nameOf($id)
Get the prefixed DB key associated with an ID.
Definition: Title.php:584
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:893
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition: Title.php:1568
getBaseText()
Get the base page name without a namespace, i.e.
Definition: Title.php:1528
static newMainPage()
Create a new Title for the Main Page.
Definition: Title.php:569
moveSubpages($nt, $auth=true, $reason= '', $createRedirect=true)
Move this page's subpages to be subpages of $nt.
Definition: Title.php:3725
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition: Title.php:4380
getEditNotices($oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:4751
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static getTitleFor($name, $subpage=false, $fragment= '')
Get a localised Title object for a specified special page name.
Definition: SpecialPage.php:75
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition: Title.php:998
__wakeup()
Definition: Title.php:4834
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:1798
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:2481
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition: Title.php:853
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:210
$wgActionPaths
Definition: img_auth.php:46
isCssJsSubpage()
Is this a .css or .js subpage of a user page?
Definition: Title.php:1244
static singleton()
Definition: GenderCache.php:39
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:1036
checkUserBlock($action, $user, $errors, $rigor, $short)
Check that the user isn't blocked from editing.
Definition: Title.php:2329
if(!isset($args[0])) $lang
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
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:1133
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition: Title.php:3367
bool $mIsBigDeletion
Would deleting this page be a big deletion?
Definition: Title.php:157
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition: Title.php:1105
moveNoAuth(&$nt)
Move this page without authentication.
Definition: Title.php:3621
isTalkPage()
Is this a talk page of some sort?
Definition: Title.php:1290
Handles purging appropriate CDN URLs given a title (or titles)
string $mUrlform
URL-encoded form of the main part.
Definition: Title.php:62
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:4647
null for the local wiki Added in
Definition: hooks.txt:1418
isRedirect($flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3213
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:4060
set($key, $value, $exptime=0, $flags=0)
const NS_SPECIAL
Definition: Defines.php:58
const PROTO_CURRENT
Definition: Defines.php:264
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:1004
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:3524
prefix($name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object. ...
Definition: Title.php:1419
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:1326
static escapeFragmentForURL($fragment)
Escape a text fragment, say from a link, for a URL.
Definition: Title.php:764
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2548
isValidMoveTarget($nt)
Checks if $this can be moved to a given Title.
Definition: Title.php:3843
static escapeClass($class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1209
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:1449
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:277
getBaseTitle()
Get the base page name title, i.e.
Definition: Title.php:1553
Represents a title within MediaWiki.
Definition: Title.php:34
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:3891
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4591
$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:251
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target...
Definition: Revision.php:117
wfLocalFile($title)
Get an object referring to a locally registered file.
The TitleArray class only exists to provide the newFromResult method at pre- sent.
Definition: TitleArray.php:31
checkQuickPermissions($action, $user, $errors, $rigor, $short)
Permissions checks that fail most often, and which are easiest to test.
Definition: Title.php:1972
getSkinFromCssJsSubpage()
Trim down a .css or .js subpage title to get the corresponding skin name.
Definition: Title.php:1255
static getFilteredRestrictionTypes($exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition: Title.php:2542
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition: Title.php:1299
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition: hooks.txt:965
getNamespace()
Get the namespace index.
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition: Title.php:116
getNotificationTimestamp($user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition: Title.php:4463
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:1071
static isEveryoneAllowed($right)
Check if all users may be assumed to have the given permission.
Definition: User.php:4572
getSubpages($limit=-1)
Get all subpages of this page.
Definition: Title.php:3108
userCan($action, $user=null, $rigor= 'secure')
Can $user perform $action on this page?
Definition: Title.php:1917
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':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:1796
getFragment()
Get the link fragment (i.e.
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:104
inNamespace($ns)
Returns true if the title is inside the specified namespace.
Definition: Title.php:1094
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition: Title.php:3044
getContentModel($flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:944
getLength($flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition: Title.php:3248
static HashBagOStuff $titleCache
Definition: Title.php:36
string $mDbkeyform
Main part with underscores.
Definition: Title.php:65
getNsText()
Get the namespace text.
Definition: Title.php:973
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:3636
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:154
createFragmentTarget($fragment)
Creates a new Title for a different fragment of the same page.
Definition: Title.php:1402
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:3994
loadRestrictionsFromResultWrapper($res, $oldFashionedRestrictions=null)
Loads a string into mRestrictions array.
Definition: Title.php:2914
__sleep()
Definition: Title.php:4822
string bool $mOldRestrictions
Text form (spaces not underscores) of the main part.
Definition: Title.php:101
__construct()
Definition: Title.php:210
wfReadOnly()
Check whether the wiki is in read-only mode.
isExternal()
Is this Title interwiki?
Definition: Title.php:810
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:1628
deleteTitleProtection()
Remove any title protection due to page existing.
Definition: Title.php:2631
static getMain()
Static methods.
int $mNamespace
Namespace index, i.e.
Definition: Title.php:71
static groupHasPermission($group, $role)
Check, if the given group has the given permission.
Definition: User.php:4552
static singleton()
Get an instance of this class.
Definition: LinkCache.php:61
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:4354
static newFromIDs($ids)
Make an array of titles from an array of IDs.
Definition: Title.php:439
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:377
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition: Title.php:204
isAllowed($action= '')
Internal mechanics of testing a permission.
Definition: User.php:3408
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:911
__toString()
Return a string representation of this title.
Definition: Title.php:1463
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition: Title.php:1609
hasFragment()
Check if a Title fragment is set.
Definition: Title.php:1363
hasContentModel($id)
Convenience method for checking a title's content model name.
Definition: Title.php:964
$wgLocalInterwikis
Array for multiple $wgLocalInterwiki values, in case there are several interwiki prefixes that point ...
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:1004
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition: Title.php:2899
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition: Title.php:1195
const NS_MEDIA
Definition: Defines.php:57
static newFromDBkey($key)
Create a new Title from a prefixed DB key.
Definition: Title.php:221
getRootText()
Get the root page name text without a namespace, i.e.
Definition: Title.php:1493
getTouched($db=null)
Get the last touched timestamp.
Definition: Title.php:4449
getLocalURL($query= '', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1734
$res
Definition: database.txt:21
checkCSSandJSPermissions($action, $user, $errors, $rigor, $short)
Check CSS/JS sub-page permissions.
Definition: Title.php:2135
bool string $mContentModel
ID of the page's content model, i.e.
Definition: Title.php:92
canExist()
Is this in a namespace that allows actual pages?
Definition: Title.php:1027
getSubpage($text)
Get the title for a subpage of the current page.
Definition: Title.php:1589
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:49
null $mRedirect
Is the article at this title a redirect?
Definition: Title.php:138
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:4567
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 after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"&lt
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not...
Definition: Title.php:2727
invalidateCache($purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4405
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3572
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:1941
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:3471
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition: Title.php:1342
const NS_CATEGORY
Definition: Defines.php:83
moveTo(&$nt, $auth=true, $reason= '', $createRedirect=true)
Move a title to a new location.
Definition: Title.php:3691
getRedirectsHere($ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4531
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition: Revision.php:429
checkSpecialsAndNSPermissions($action, $user, $errors, $rigor, $short)
Check permissions on special pages & namespaces.
Definition: Title.php:2106
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:1869
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:4253
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition: Title.php:141
static makeTitleSafe($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:548
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition: Title.php:1473
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:151
validateFileMoveOperation($nt)
Check if the requested move target is a valid file move target.
Definition: Title.php:3663
isCssSubpage()
Is this a .css subpage of a user page?
Definition: Title.php:1270
const DB_SLAVE
Definition: Defines.php:46
isMainPage()
Is this the mainpage?
Definition: Title.php:1175
static decodeCharReferencesAndNormalize($text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1479
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
getAuthorsBetween($old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition: Title.php:4155
static hasSubpages($index)
Does the namespace allow subpages?
static addUpdate(DeferrableUpdate $update, $type=self::POSTSEND)
Add an update to the deferred list.
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:934
const PROTO_RELATIVE
Definition: Defines.php:263
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:74
equals(Title $title)
Compare with another title.
Definition: Title.php:4240
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:4345
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:3455
const NS_FILE
Definition: Defines.php:75
static newFromResult($res)
Definition: TitleArray.php:38
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2844
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:1584
checkCascadingSourcesRestrictions($action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition: Title.php:2205
static equals($ns1, $ns2)
Returns whether the specified namespaces are the same namespace.
isSubpage()
Is this a subpage?
Definition: Title.php:1184
getInterwiki()
Get the interwiki prefix.
Definition: Title.php:821
const RAW
Definition: Revision.php:85
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:477
areCascadeProtectionSourcesLoaded($getPages=true)
Determines whether cascading protection sources have already been loaded from the database...
Definition: Title.php:2741
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:912
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page...
Definition: Title.php:3536
const NS_MEDIAWIKI
Definition: Defines.php:77
const PROTO_HTTP
Definition: Defines.php:261
getNamespaceKey($prepend= 'nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition: Title.php:4500
countRevisionsBetween($old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:4112
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition: Title.php:110
static fetch($prefix)
Fetch an Interwiki object.
Definition: Interwiki.php:85
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:240
getRestrictions($action)
Accessor/initialisation for mRestrictions.
Definition: Title.php:2857
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:1309
int $mLength
The page length, 0 for special pages.
Definition: Title.php:135
getFirstRevision($flags=0)
Get the first revision of the page.
Definition: Title.php:4018
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:1132
static newFromTextThrow($text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid, rather than returning null.
Definition: Title.php:307
isSpecialPage()
Returns true if this is a special page.
Definition: Title.php:1045
$wgContentNamespaces
Array of namespaces which can be deemed to contain valid "content", as far as the site statistics are...
quickUserCan($action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition: Title.php:1904
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:1881
$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:3006
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:634
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition: Title.php:3792
const PROTO_CANONICAL
Definition: Defines.php:265
getLinkURL($query= '', $query2=false, $proto=PROTO_RELATIVE)
Get a URL that's the simplest URL that will be valid to link, locally, to the current Title...
Definition: Title.php:1826
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition: Title.php:4707
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition: Title.php:1598
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition: Title.php:107
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:35
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:2560
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:154
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition: Title.php:147
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition: Title.php:840
string $mFragment
Title fragment (i.e.
Definition: Title.php:80
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition: Title.php:83
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition: Title.php:132
static getMediaWikiTitleCodec()
B/C kludge: provide a TitleParser for use by Title.
Definition: Title.php:168
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:1004
getCategorySortkey($prefix= '')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition: Title.php:4621
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:738
static newFromURL($url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition: Title.php:354
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition: Title.php:4669
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles...
Definition: Title.php:43
checkReadPermissions($action, $user, $errors, $rigor, $short)
Check that the user is allowed to read this page.
Definition: Title.php:2371
isCssOrJsPage()
Could this page contain custom CSS or JavaScript for the global UI.
Definition: Title.php:1225
getCascadeProtectionSources($getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition: Title.php:2758
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition: Title.php:1371
get($key, $flags=0, $oldFlags=null)
Get an item with the given key.
Definition: BagOStuff.php:173
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition: Title.php:3953
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:1004
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:279
string $mPrefixedText
Text form including namespace/interwiki, initialised on demand.
Definition: Title.php:122
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition: Title.php:3161
isNewPage()
Check if this is a new page.
Definition: Title.php:4050
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:2446
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:1004
isProtected($action= '')
Does the title correspond to a protected article?
Definition: Title.php:2677
canTalk()
Could this title have a corresponding talk page?
Definition: Title.php:1018
$count
checkActionPermissions($action, $user, $errors, $rigor, $short)
Check action permissions not already checked in checkQuickPermissions.
Definition: Title.php:2252
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition: Title.php:119
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:606
$wgServer
URL of the server.
isDeleted()
Is there a version of this page in the deletion archive?
Definition: Title.php:3136
const DB_MASTER
Definition: Defines.php:47
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition: Title.php:795
loadRestrictionsFromRows($rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition: Title.php:2933
array $mRestrictions
Array of groups allowed to edit this article.
Definition: Title.php:98
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition: Title.php:2587
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition: Title.php:77
static compare($a, $b)
Callback for usort() to do title sorts by (namespace, title)
Definition: Title.php:780
addQuotes($s)
Adds quotes and backslashes.
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition: Title.php:95
countAuthorsBetween($old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition: Title.php:4229
getTalkNsText()
Get the namespace text of the talk page.
Definition: Title.php:1008
checkPageRestrictions($action, $user, $errors, $rigor, $short)
Check against page_restrictions table requirements on this page.
Definition: Title.php:2171
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries...
Definition: Title.php:391
checkPermissionHooks($action, $user, $errors, $rigor, $short)
Check various permission hooks.
Definition: Title.php:2073
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:179
getParentCategoryTree($children=[])
Get a tree of parent categories.
Definition: Title.php:3926
string $mTextform
Text form (spaces not underscores) of the main part.
Definition: Title.php:59
static selectFields()
Return the list of revision fields that should be selected to create a new page.
Definition: WikiPage.php:266
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition: Title.php:2873
isSemiProtected($action= 'edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2649
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:86
resultToError($errors, $result)
Add the resulting error code to the errors array.
Definition: Title.php:2042
getRestrictionExpiry($action)
Get the expiry time for the restriction against a given action.
Definition: Title.php:2887
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:3969
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
if(is_null($wgLocalTZoffset)) if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:657
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:2338
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:3345
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition: Title.php:1207
static & makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:524
static singleton()
Get the signleton instance of this class.
purgeSquid()
Purge all applicable CDN URLs.
Definition: Title.php:3607
static getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4529
resetArticleID($newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3308
Basic database interface for live and lazy-loaded DB handles.
Definition: IDatabase.php:35
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2338
getTitleValue()
Get a TitleValue object representing this Title.
Definition: Title.php:870
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:3413
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition: Title.php:113
getPartialURL()
Get the URL-encoded form of the main part.
Definition: Title.php:902
wfGetLangObj($langcode=false)
Return a Language object from $langcode.
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1437
getFullURL($query= '', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition: Title.php:1666
estimateRevisionCount()
Get the approximate revision count of this page.
Definition: Title.php:4089
$wgUser
Definition: Setup.php:794
$matches
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition: Title.php:68
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310