MediaWiki  1.27.1
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 
1707  public function getLocalURL( $query = '', $query2 = false ) {
1709 
1710  $query = self::fixUrlQueryArgs( $query, $query2 );
1711 
1712  $interwiki = Interwiki::fetch( $this->mInterwiki );
1713  if ( $interwiki ) {
1714  $namespace = $this->getNsText();
1715  if ( $namespace != '' ) {
1716  # Can this actually happen? Interwikis shouldn't be parsed.
1717  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1718  $namespace .= ':';
1719  }
1720  $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1721  $url = wfAppendQuery( $url, $query );
1722  } else {
1723  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1724  if ( $query == '' ) {
1725  $url = str_replace( '$1', $dbkey, $wgArticlePath );
1726  Hooks::run( 'GetLocalURL::Article', [ &$this, &$url ] );
1727  } else {
1729  $url = false;
1730  $matches = [];
1731 
1732  if ( !empty( $wgActionPaths )
1733  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1734  ) {
1735  $action = urldecode( $matches[2] );
1736  if ( isset( $wgActionPaths[$action] ) ) {
1737  $query = $matches[1];
1738  if ( isset( $matches[4] ) ) {
1739  $query .= $matches[4];
1740  }
1741  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1742  if ( $query != '' ) {
1743  $url = wfAppendQuery( $url, $query );
1744  }
1745  }
1746  }
1747 
1748  if ( $url === false
1749  && $wgVariantArticlePath
1750  && $wgContLang->getCode() === $this->getPageLanguage()->getCode()
1751  && $this->getPageLanguage()->hasVariants()
1752  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1753  ) {
1754  $variant = urldecode( $matches[1] );
1755  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1756  // Only do the variant replacement if the given variant is a valid
1757  // variant for the page's language.
1758  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1759  $url = str_replace( '$1', $dbkey, $url );
1760  }
1761  }
1762 
1763  if ( $url === false ) {
1764  if ( $query == '-' ) {
1765  $query = '';
1766  }
1767  $url = "{$wgScript}?title={$dbkey}&{$query}";
1768  }
1769  }
1770 
1771  Hooks::run( 'GetLocalURL::Internal', [ &$this, &$url, $query ] );
1772 
1773  // @todo FIXME: This causes breakage in various places when we
1774  // actually expected a local URL and end up with dupe prefixes.
1775  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
1776  $url = $wgServer . $url;
1777  }
1778  }
1779  Hooks::run( 'GetLocalURL', [ &$this, &$url, $query ] );
1780  return $url;
1781  }
1782 
1799  public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1800  if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
1801  $ret = $this->getFullURL( $query, $query2, $proto );
1802  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
1803  $ret = $this->getFragmentForURL();
1804  } else {
1805  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
1806  }
1807  return $ret;
1808  }
1809 
1822  public function getInternalURL( $query = '', $query2 = false ) {
1824  $query = self::fixUrlQueryArgs( $query, $query2 );
1825  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
1826  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
1827  Hooks::run( 'GetInternalURL', [ &$this, &$url, $query ] );
1828  return $url;
1829  }
1830 
1842  public function getCanonicalURL( $query = '', $query2 = false ) {
1843  $query = self::fixUrlQueryArgs( $query, $query2 );
1844  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
1845  Hooks::run( 'GetCanonicalURL', [ &$this, &$url, $query ] );
1846  return $url;
1847  }
1848 
1854  public function getEditURL() {
1855  if ( $this->isExternal() ) {
1856  return '';
1857  }
1858  $s = $this->getLocalURL( 'action=edit' );
1859 
1860  return $s;
1861  }
1862 
1877  public function quickUserCan( $action, $user = null ) {
1878  return $this->userCan( $action, $user, false );
1879  }
1880 
1890  public function userCan( $action, $user = null, $rigor = 'secure' ) {
1891  if ( !$user instanceof User ) {
1892  global $wgUser;
1893  $user = $wgUser;
1894  }
1895 
1896  return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
1897  }
1898 
1914  public function getUserPermissionsErrors(
1915  $action, $user, $rigor = 'secure', $ignoreErrors = []
1916  ) {
1917  $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
1918 
1919  // Remove the errors being ignored.
1920  foreach ( $errors as $index => $error ) {
1921  $errKey = is_array( $error ) ? $error[0] : $error;
1922 
1923  if ( in_array( $errKey, $ignoreErrors ) ) {
1924  unset( $errors[$index] );
1925  }
1926  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
1927  unset( $errors[$index] );
1928  }
1929  }
1930 
1931  return $errors;
1932  }
1933 
1945  private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
1946  if ( !Hooks::run( 'TitleQuickPermissions',
1947  [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
1948  ) {
1949  return $errors;
1950  }
1951 
1952  if ( $action == 'create' ) {
1953  if (
1954  ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1955  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
1956  ) {
1957  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
1958  }
1959  } elseif ( $action == 'move' ) {
1960  if ( !$user->isAllowed( 'move-rootuserpages' )
1961  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1962  // Show user page-specific message only if the user can move other pages
1963  $errors[] = [ 'cant-move-user-page' ];
1964  }
1965 
1966  // Check if user is allowed to move files if it's a file
1967  if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
1968  $errors[] = [ 'movenotallowedfile' ];
1969  }
1970 
1971  // Check if user is allowed to move category pages if it's a category page
1972  if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
1973  $errors[] = [ 'cant-move-category-page' ];
1974  }
1975 
1976  if ( !$user->isAllowed( 'move' ) ) {
1977  // User can't move anything
1978  $userCanMove = User::groupHasPermission( 'user', 'move' );
1979  $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
1980  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
1981  // custom message if logged-in users without any special rights can move
1982  $errors[] = [ 'movenologintext' ];
1983  } else {
1984  $errors[] = [ 'movenotallowed' ];
1985  }
1986  }
1987  } elseif ( $action == 'move-target' ) {
1988  if ( !$user->isAllowed( 'move' ) ) {
1989  // User can't move anything
1990  $errors[] = [ 'movenotallowed' ];
1991  } elseif ( !$user->isAllowed( 'move-rootuserpages' )
1992  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1993  // Show user page-specific message only if the user can move other pages
1994  $errors[] = [ 'cant-move-to-user-page' ];
1995  } elseif ( !$user->isAllowed( 'move-categorypages' )
1996  && $this->mNamespace == NS_CATEGORY ) {
1997  // Show category page-specific message only if the user can move other pages
1998  $errors[] = [ 'cant-move-to-category-page' ];
1999  }
2000  } elseif ( !$user->isAllowed( $action ) ) {
2001  $errors[] = $this->missingPermissionError( $action, $short );
2002  }
2003 
2004  return $errors;
2005  }
2006 
2015  private function resultToError( $errors, $result ) {
2016  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2017  // A single array representing an error
2018  $errors[] = $result;
2019  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2020  // A nested array representing multiple errors
2021  $errors = array_merge( $errors, $result );
2022  } elseif ( $result !== '' && is_string( $result ) ) {
2023  // A string representing a message-id
2024  $errors[] = [ $result ];
2025  } elseif ( $result instanceof MessageSpecifier ) {
2026  // A message specifier representing an error
2027  $errors[] = [ $result ];
2028  } elseif ( $result === false ) {
2029  // a generic "We don't want them to do that"
2030  $errors[] = [ 'badaccess-group0' ];
2031  }
2032  return $errors;
2033  }
2034 
2046  private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2047  // Use getUserPermissionsErrors instead
2048  $result = '';
2049  if ( !Hooks::run( 'userCan', [ &$this, &$user, $action, &$result ] ) ) {
2050  return $result ? [] : [ [ 'badaccess-group0' ] ];
2051  }
2052  // Check getUserPermissionsErrors hook
2053  if ( !Hooks::run( 'getUserPermissionsErrors', [ &$this, &$user, $action, &$result ] ) ) {
2054  $errors = $this->resultToError( $errors, $result );
2055  }
2056  // Check getUserPermissionsErrorsExpensive hook
2057  if (
2058  $rigor !== 'quick'
2059  && !( $short && count( $errors ) > 0 )
2060  && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$this, &$user, $action, &$result ] )
2061  ) {
2062  $errors = $this->resultToError( $errors, $result );
2063  }
2064 
2065  return $errors;
2066  }
2067 
2079  private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2080  # Only 'createaccount' can be performed on special pages,
2081  # which don't actually exist in the DB.
2082  if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
2083  $errors[] = [ 'ns-specialprotected' ];
2084  }
2085 
2086  # Check $wgNamespaceProtection for restricted namespaces
2087  if ( $this->isNamespaceProtected( $user ) ) {
2088  $ns = $this->mNamespace == NS_MAIN ?
2089  wfMessage( 'nstab-main' )->text() : $this->getNsText();
2090  $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2091  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2092  }
2093 
2094  return $errors;
2095  }
2096 
2108  private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
2109  # Protect css/js subpages of user pages
2110  # XXX: this might be better using restrictions
2111  # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
2112  if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
2113  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2114  if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
2115  $errors[] = [ 'mycustomcssprotected', $action ];
2116  } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
2117  $errors[] = [ 'mycustomjsprotected', $action ];
2118  }
2119  } else {
2120  if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
2121  $errors[] = [ 'customcssprotected', $action ];
2122  } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
2123  $errors[] = [ 'customjsprotected', $action ];
2124  }
2125  }
2126  }
2127 
2128  return $errors;
2129  }
2130 
2144  private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2145  foreach ( $this->getRestrictions( $action ) as $right ) {
2146  // Backwards compatibility, rewrite sysop -> editprotected
2147  if ( $right == 'sysop' ) {
2148  $right = 'editprotected';
2149  }
2150  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2151  if ( $right == 'autoconfirmed' ) {
2152  $right = 'editsemiprotected';
2153  }
2154  if ( $right == '' ) {
2155  continue;
2156  }
2157  if ( !$user->isAllowed( $right ) ) {
2158  $errors[] = [ 'protectedpagetext', $right, $action ];
2159  } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2160  $errors[] = [ 'protectedpagetext', 'protect', $action ];
2161  }
2162  }
2163 
2164  return $errors;
2165  }
2166 
2178  private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2179  if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
2180  # We /could/ use the protection level on the source page, but it's
2181  # fairly ugly as we have to establish a precedence hierarchy for pages
2182  # included by multiple cascade-protected pages. So just restrict
2183  # it to people with 'protect' permission, as they could remove the
2184  # protection anyway.
2185  list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2186  # Cascading protection depends on more than this page...
2187  # Several cascading protected pages may include this page...
2188  # Check each cascading level
2189  # This is only for protection restrictions, not for all actions
2190  if ( isset( $restrictions[$action] ) ) {
2191  foreach ( $restrictions[$action] as $right ) {
2192  // Backwards compatibility, rewrite sysop -> editprotected
2193  if ( $right == 'sysop' ) {
2194  $right = 'editprotected';
2195  }
2196  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2197  if ( $right == 'autoconfirmed' ) {
2198  $right = 'editsemiprotected';
2199  }
2200  if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2201  $pages = '';
2202  foreach ( $cascadingSources as $page ) {
2203  $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2204  }
2205  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2206  }
2207  }
2208  }
2209  }
2210 
2211  return $errors;
2212  }
2213 
2225  private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2226  global $wgDeleteRevisionsLimit, $wgLang;
2227 
2228  if ( $action == 'protect' ) {
2229  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2230  // If they can't edit, they shouldn't protect.
2231  $errors[] = [ 'protect-cantedit' ];
2232  }
2233  } elseif ( $action == 'create' ) {
2234  $title_protection = $this->getTitleProtection();
2235  if ( $title_protection ) {
2236  if ( $title_protection['permission'] == ''
2237  || !$user->isAllowed( $title_protection['permission'] )
2238  ) {
2239  $errors[] = [
2240  'titleprotected',
2241  User::whoIs( $title_protection['user'] ),
2242  $title_protection['reason']
2243  ];
2244  }
2245  }
2246  } elseif ( $action == 'move' ) {
2247  // Check for immobile pages
2248  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2249  // Specific message for this case
2250  $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2251  } elseif ( !$this->isMovable() ) {
2252  // Less specific message for rarer cases
2253  $errors[] = [ 'immobile-source-page' ];
2254  }
2255  } elseif ( $action == 'move-target' ) {
2256  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2257  $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2258  } elseif ( !$this->isMovable() ) {
2259  $errors[] = [ 'immobile-target-page' ];
2260  }
2261  } elseif ( $action == 'delete' ) {
2262  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2263  if ( !$tempErrors ) {
2264  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2265  $user, $tempErrors, $rigor, true );
2266  }
2267  if ( $tempErrors ) {
2268  // If protection keeps them from editing, they shouldn't be able to delete.
2269  $errors[] = [ 'deleteprotected' ];
2270  }
2271  if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2272  && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2273  ) {
2274  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2275  }
2276  }
2277  return $errors;
2278  }
2279 
2291  private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2292  global $wgEmailConfirmToEdit, $wgBlockDisablesLogin;
2293  // Account creation blocks handled at userlogin.
2294  // Unblocking handled in SpecialUnblock
2295  if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2296  return $errors;
2297  }
2298 
2299  // Optimize for a very common case
2300  if ( $action === 'read' && !$wgBlockDisablesLogin ) {
2301  return $errors;
2302  }
2303 
2304  if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
2305  $errors[] = [ 'confirmedittext' ];
2306  }
2307 
2308  $useSlave = ( $rigor !== 'secure' );
2309  if ( ( $action == 'edit' || $action == 'create' )
2310  && !$user->isBlockedFrom( $this, $useSlave )
2311  ) {
2312  // Don't block the user from editing their own talk page unless they've been
2313  // explicitly blocked from that too.
2314  } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2315  // @todo FIXME: Pass the relevant context into this function.
2316  $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2317  }
2318 
2319  return $errors;
2320  }
2321 
2333  private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2334  global $wgWhitelistRead, $wgWhitelistReadRegexp;
2335 
2336  $whitelisted = false;
2337  if ( User::isEveryoneAllowed( 'read' ) ) {
2338  # Shortcut for public wikis, allows skipping quite a bit of code
2339  $whitelisted = true;
2340  } elseif ( $user->isAllowed( 'read' ) ) {
2341  # If the user is allowed to read pages, he is allowed to read all pages
2342  $whitelisted = true;
2343  } elseif ( $this->isSpecial( 'Userlogin' )
2344  || $this->isSpecial( 'ChangePassword' )
2345  || $this->isSpecial( 'PasswordReset' )
2346  ) {
2347  # Always grant access to the login page.
2348  # Even anons need to be able to log in.
2349  $whitelisted = true;
2350  } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2351  # Time to check the whitelist
2352  # Only do these checks is there's something to check against
2353  $name = $this->getPrefixedText();
2354  $dbName = $this->getPrefixedDBkey();
2355 
2356  // Check for explicit whitelisting with and without underscores
2357  if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2358  $whitelisted = true;
2359  } elseif ( $this->getNamespace() == NS_MAIN ) {
2360  # Old settings might have the title prefixed with
2361  # a colon for main-namespace pages
2362  if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2363  $whitelisted = true;
2364  }
2365  } elseif ( $this->isSpecialPage() ) {
2366  # If it's a special page, ditch the subpage bit and check again
2367  $name = $this->getDBkey();
2368  list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2369  if ( $name ) {
2370  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2371  if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2372  $whitelisted = true;
2373  }
2374  }
2375  }
2376  }
2377 
2378  if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2379  $name = $this->getPrefixedText();
2380  // Check for regex whitelisting
2381  foreach ( $wgWhitelistReadRegexp as $listItem ) {
2382  if ( preg_match( $listItem, $name ) ) {
2383  $whitelisted = true;
2384  break;
2385  }
2386  }
2387  }
2388 
2389  if ( !$whitelisted ) {
2390  # If the title is not whitelisted, give extensions a chance to do so...
2391  Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2392  if ( !$whitelisted ) {
2393  $errors[] = $this->missingPermissionError( $action, $short );
2394  }
2395  }
2396 
2397  return $errors;
2398  }
2399 
2408  private function missingPermissionError( $action, $short ) {
2409  // We avoid expensive display logic for quickUserCan's and such
2410  if ( $short ) {
2411  return [ 'badaccess-group0' ];
2412  }
2413 
2414  $groups = array_map( [ 'User', 'makeGroupLinkWiki' ],
2415  User::getGroupsWithPermission( $action ) );
2416 
2417  if ( count( $groups ) ) {
2418  global $wgLang;
2419  return [
2420  'badaccess-groups',
2421  $wgLang->commaList( $groups ),
2422  count( $groups )
2423  ];
2424  } else {
2425  return [ 'badaccess-group0' ];
2426  }
2427  }
2428 
2444  $action, $user, $rigor = 'secure', $short = false
2445  ) {
2446  if ( $rigor === true ) {
2447  $rigor = 'secure'; // b/c
2448  } elseif ( $rigor === false ) {
2449  $rigor = 'quick'; // b/c
2450  } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2451  throw new Exception( "Invalid rigor parameter '$rigor'." );
2452  }
2453 
2454  # Read has special handling
2455  if ( $action == 'read' ) {
2456  $checks = [
2457  'checkPermissionHooks',
2458  'checkReadPermissions',
2459  'checkUserBlock', // for wgBlockDisablesLogin
2460  ];
2461  # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
2462  # here as it will lead to duplicate error messages. This is okay to do
2463  # since anywhere that checks for create will also check for edit, and
2464  # those checks are called for edit.
2465  } elseif ( $action == 'create' ) {
2466  $checks = [
2467  'checkQuickPermissions',
2468  'checkPermissionHooks',
2469  'checkPageRestrictions',
2470  'checkCascadingSourcesRestrictions',
2471  'checkActionPermissions',
2472  'checkUserBlock'
2473  ];
2474  } else {
2475  $checks = [
2476  'checkQuickPermissions',
2477  'checkPermissionHooks',
2478  'checkSpecialsAndNSPermissions',
2479  'checkCSSandJSPermissions',
2480  'checkPageRestrictions',
2481  'checkCascadingSourcesRestrictions',
2482  'checkActionPermissions',
2483  'checkUserBlock'
2484  ];
2485  }
2486 
2487  $errors = [];
2488  while ( count( $checks ) > 0 &&
2489  !( $short && count( $errors ) > 0 ) ) {
2490  $method = array_shift( $checks );
2491  $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2492  }
2493 
2494  return $errors;
2495  }
2496 
2504  public static function getFilteredRestrictionTypes( $exists = true ) {
2505  global $wgRestrictionTypes;
2506  $types = $wgRestrictionTypes;
2507  if ( $exists ) {
2508  # Remove the create restriction for existing titles
2509  $types = array_diff( $types, [ 'create' ] );
2510  } else {
2511  # Only the create and upload restrictions apply to non-existing titles
2512  $types = array_intersect( $types, [ 'create', 'upload' ] );
2513  }
2514  return $types;
2515  }
2516 
2522  public function getRestrictionTypes() {
2523  if ( $this->isSpecialPage() ) {
2524  return [];
2525  }
2526 
2527  $types = self::getFilteredRestrictionTypes( $this->exists() );
2528 
2529  if ( $this->getNamespace() != NS_FILE ) {
2530  # Remove the upload restriction for non-file titles
2531  $types = array_diff( $types, [ 'upload' ] );
2532  }
2533 
2534  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2535 
2536  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2537  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2538 
2539  return $types;
2540  }
2541 
2549  public function getTitleProtection() {
2550  // Can't protect pages in special namespaces
2551  if ( $this->getNamespace() < 0 ) {
2552  return false;
2553  }
2554 
2555  // Can't protect pages that exist.
2556  if ( $this->exists() ) {
2557  return false;
2558  }
2559 
2560  if ( $this->mTitleProtection === null ) {
2561  $dbr = wfGetDB( DB_SLAVE );
2562  $res = $dbr->select(
2563  'protected_titles',
2564  [
2565  'user' => 'pt_user',
2566  'reason' => 'pt_reason',
2567  'expiry' => 'pt_expiry',
2568  'permission' => 'pt_create_perm'
2569  ],
2570  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2571  __METHOD__
2572  );
2573 
2574  // fetchRow returns false if there are no rows.
2575  $row = $dbr->fetchRow( $res );
2576  if ( $row ) {
2577  if ( $row['permission'] == 'sysop' ) {
2578  $row['permission'] = 'editprotected'; // B/C
2579  }
2580  if ( $row['permission'] == 'autoconfirmed' ) {
2581  $row['permission'] = 'editsemiprotected'; // B/C
2582  }
2583  $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2584  }
2585  $this->mTitleProtection = $row;
2586  }
2587  return $this->mTitleProtection;
2588  }
2589 
2593  public function deleteTitleProtection() {
2594  $dbw = wfGetDB( DB_MASTER );
2595 
2596  $dbw->delete(
2597  'protected_titles',
2598  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2599  __METHOD__
2600  );
2601  $this->mTitleProtection = false;
2602  }
2603 
2611  public function isSemiProtected( $action = 'edit' ) {
2612  global $wgSemiprotectedRestrictionLevels;
2613 
2614  $restrictions = $this->getRestrictions( $action );
2615  $semi = $wgSemiprotectedRestrictionLevels;
2616  if ( !$restrictions || !$semi ) {
2617  // Not protected, or all protection is full protection
2618  return false;
2619  }
2620 
2621  // Remap autoconfirmed to editsemiprotected for BC
2622  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2623  $semi[$key] = 'editsemiprotected';
2624  }
2625  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2626  $restrictions[$key] = 'editsemiprotected';
2627  }
2628 
2629  return !array_diff( $restrictions, $semi );
2630  }
2631 
2639  public function isProtected( $action = '' ) {
2640  global $wgRestrictionLevels;
2641 
2642  $restrictionTypes = $this->getRestrictionTypes();
2643 
2644  # Special pages have inherent protection
2645  if ( $this->isSpecialPage() ) {
2646  return true;
2647  }
2648 
2649  # Check regular protection levels
2650  foreach ( $restrictionTypes as $type ) {
2651  if ( $action == $type || $action == '' ) {
2652  $r = $this->getRestrictions( $type );
2653  foreach ( $wgRestrictionLevels as $level ) {
2654  if ( in_array( $level, $r ) && $level != '' ) {
2655  return true;
2656  }
2657  }
2658  }
2659  }
2660 
2661  return false;
2662  }
2663 
2671  public function isNamespaceProtected( User $user ) {
2673 
2674  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2675  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2676  if ( $right != '' && !$user->isAllowed( $right ) ) {
2677  return true;
2678  }
2679  }
2680  }
2681  return false;
2682  }
2683 
2689  public function isCascadeProtected() {
2690  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2691  return ( $sources > 0 );
2692  }
2693 
2703  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2704  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2705  }
2706 
2720  public function getCascadeProtectionSources( $getPages = true ) {
2721  $pagerestrictions = [];
2722 
2723  if ( $this->mCascadeSources !== null && $getPages ) {
2725  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2726  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2727  }
2728 
2729  $dbr = wfGetDB( DB_SLAVE );
2730 
2731  if ( $this->getNamespace() == NS_FILE ) {
2732  $tables = [ 'imagelinks', 'page_restrictions' ];
2733  $where_clauses = [
2734  'il_to' => $this->getDBkey(),
2735  'il_from=pr_page',
2736  'pr_cascade' => 1
2737  ];
2738  } else {
2739  $tables = [ 'templatelinks', 'page_restrictions' ];
2740  $where_clauses = [
2741  'tl_namespace' => $this->getNamespace(),
2742  'tl_title' => $this->getDBkey(),
2743  'tl_from=pr_page',
2744  'pr_cascade' => 1
2745  ];
2746  }
2747 
2748  if ( $getPages ) {
2749  $cols = [ 'pr_page', 'page_namespace', 'page_title',
2750  'pr_expiry', 'pr_type', 'pr_level' ];
2751  $where_clauses[] = 'page_id=pr_page';
2752  $tables[] = 'page';
2753  } else {
2754  $cols = [ 'pr_expiry' ];
2755  }
2756 
2757  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2758 
2759  $sources = $getPages ? [] : false;
2760  $now = wfTimestampNow();
2761 
2762  foreach ( $res as $row ) {
2763  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2764  if ( $expiry > $now ) {
2765  if ( $getPages ) {
2766  $page_id = $row->pr_page;
2767  $page_ns = $row->page_namespace;
2768  $page_title = $row->page_title;
2769  $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2770  # Add groups needed for each restriction type if its not already there
2771  # Make sure this restriction type still exists
2772 
2773  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2774  $pagerestrictions[$row->pr_type] = [];
2775  }
2776 
2777  if (
2778  isset( $pagerestrictions[$row->pr_type] )
2779  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2780  ) {
2781  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2782  }
2783  } else {
2784  $sources = true;
2785  }
2786  }
2787  }
2788 
2789  if ( $getPages ) {
2790  $this->mCascadeSources = $sources;
2791  $this->mCascadingRestrictions = $pagerestrictions;
2792  } else {
2793  $this->mHasCascadingRestrictions = $sources;
2794  }
2795 
2796  return [ $sources, $pagerestrictions ];
2797  }
2798 
2806  public function areRestrictionsLoaded() {
2808  }
2809 
2819  public function getRestrictions( $action ) {
2820  if ( !$this->mRestrictionsLoaded ) {
2821  $this->loadRestrictions();
2822  }
2823  return isset( $this->mRestrictions[$action] )
2824  ? $this->mRestrictions[$action]
2825  : [];
2826  }
2827 
2835  public function getAllRestrictions() {
2836  if ( !$this->mRestrictionsLoaded ) {
2837  $this->loadRestrictions();
2838  }
2839  return $this->mRestrictions;
2840  }
2841 
2849  public function getRestrictionExpiry( $action ) {
2850  if ( !$this->mRestrictionsLoaded ) {
2851  $this->loadRestrictions();
2852  }
2853  return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2854  }
2855 
2862  if ( !$this->mRestrictionsLoaded ) {
2863  $this->loadRestrictions();
2864  }
2865 
2867  }
2868 
2876  private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
2877  $rows = [];
2878 
2879  foreach ( $res as $row ) {
2880  $rows[] = $row;
2881  }
2882 
2883  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2884  }
2885 
2895  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2896  $dbr = wfGetDB( DB_SLAVE );
2897 
2898  $restrictionTypes = $this->getRestrictionTypes();
2899 
2900  foreach ( $restrictionTypes as $type ) {
2901  $this->mRestrictions[$type] = [];
2902  $this->mRestrictionsExpiry[$type] = 'infinity';
2903  }
2904 
2905  $this->mCascadeRestriction = false;
2906 
2907  # Backwards-compatibility: also load the restrictions from the page record (old format).
2908  if ( $oldFashionedRestrictions !== null ) {
2909  $this->mOldRestrictions = $oldFashionedRestrictions;
2910  }
2911 
2912  if ( $this->mOldRestrictions === false ) {
2913  $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2914  [ 'page_id' => $this->getArticleID() ], __METHOD__ );
2915  }
2916 
2917  if ( $this->mOldRestrictions != '' ) {
2918  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2919  $temp = explode( '=', trim( $restrict ) );
2920  if ( count( $temp ) == 1 ) {
2921  // old old format should be treated as edit/move restriction
2922  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2923  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2924  } else {
2925  $restriction = trim( $temp[1] );
2926  if ( $restriction != '' ) { // some old entries are empty
2927  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2928  }
2929  }
2930  }
2931  }
2932 
2933  if ( count( $rows ) ) {
2934  # Current system - load second to make them override.
2935  $now = wfTimestampNow();
2936 
2937  # Cycle through all the restrictions.
2938  foreach ( $rows as $row ) {
2939 
2940  // Don't take care of restrictions types that aren't allowed
2941  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2942  continue;
2943  }
2944 
2945  // This code should be refactored, now that it's being used more generally,
2946  // But I don't really see any harm in leaving it in Block for now -werdna
2947  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2948 
2949  // Only apply the restrictions if they haven't expired!
2950  if ( !$expiry || $expiry > $now ) {
2951  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2952  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2953 
2954  $this->mCascadeRestriction |= $row->pr_cascade;
2955  }
2956  }
2957  }
2958 
2959  $this->mRestrictionsLoaded = true;
2960  }
2961 
2968  public function loadRestrictions( $oldFashionedRestrictions = null ) {
2969  if ( !$this->mRestrictionsLoaded ) {
2970  $dbr = wfGetDB( DB_SLAVE );
2971  if ( $this->exists() ) {
2972  $res = $dbr->select(
2973  'page_restrictions',
2974  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
2975  [ 'pr_page' => $this->getArticleID() ],
2976  __METHOD__
2977  );
2978 
2979  $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
2980  } else {
2981  $title_protection = $this->getTitleProtection();
2982 
2983  if ( $title_protection ) {
2984  $now = wfTimestampNow();
2985  $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
2986 
2987  if ( !$expiry || $expiry > $now ) {
2988  // Apply the restrictions
2989  $this->mRestrictionsExpiry['create'] = $expiry;
2990  $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
2991  } else { // Get rid of the old restrictions
2992  $this->mTitleProtection = false;
2993  }
2994  } else {
2995  $this->mRestrictionsExpiry['create'] = 'infinity';
2996  }
2997  $this->mRestrictionsLoaded = true;
2998  }
2999  }
3000  }
3001 
3006  public function flushRestrictions() {
3007  $this->mRestrictionsLoaded = false;
3008  $this->mTitleProtection = null;
3009  }
3010 
3014  static function purgeExpiredRestrictions() {
3015  if ( wfReadOnly() ) {
3016  return;
3017  }
3018 
3020  wfGetDB( DB_MASTER ),
3021  __METHOD__,
3022  function ( IDatabase $dbw, $fname ) {
3023  $dbw->delete(
3024  'page_restrictions',
3025  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3026  $fname
3027  );
3028  $dbw->delete(
3029  'protected_titles',
3030  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3031  $fname
3032  );
3033  }
3034  ) );
3035  }
3036 
3042  public function hasSubpages() {
3043  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3044  # Duh
3045  return false;
3046  }
3047 
3048  # We dynamically add a member variable for the purpose of this method
3049  # alone to cache the result. There's no point in having it hanging
3050  # around uninitialized in every Title object; therefore we only add it
3051  # if needed and don't declare it statically.
3052  if ( $this->mHasSubpages === null ) {
3053  $this->mHasSubpages = false;
3054  $subpages = $this->getSubpages( 1 );
3055  if ( $subpages instanceof TitleArray ) {
3056  $this->mHasSubpages = (bool)$subpages->count();
3057  }
3058  }
3059 
3060  return $this->mHasSubpages;
3061  }
3062 
3070  public function getSubpages( $limit = -1 ) {
3071  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3072  return [];
3073  }
3074 
3075  $dbr = wfGetDB( DB_SLAVE );
3076  $conds['page_namespace'] = $this->getNamespace();
3077  $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3078  $options = [];
3079  if ( $limit > -1 ) {
3080  $options['LIMIT'] = $limit;
3081  }
3082  $this->mSubpages = TitleArray::newFromResult(
3083  $dbr->select( 'page',
3084  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3085  $conds,
3086  __METHOD__,
3087  $options
3088  )
3089  );
3090  return $this->mSubpages;
3091  }
3092 
3098  public function isDeleted() {
3099  if ( $this->getNamespace() < 0 ) {
3100  $n = 0;
3101  } else {
3102  $dbr = wfGetDB( DB_SLAVE );
3103 
3104  $n = $dbr->selectField( 'archive', 'COUNT(*)',
3105  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3106  __METHOD__
3107  );
3108  if ( $this->getNamespace() == NS_FILE ) {
3109  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3110  [ 'fa_name' => $this->getDBkey() ],
3111  __METHOD__
3112  );
3113  }
3114  }
3115  return (int)$n;
3116  }
3117 
3123  public function isDeletedQuick() {
3124  if ( $this->getNamespace() < 0 ) {
3125  return false;
3126  }
3127  $dbr = wfGetDB( DB_SLAVE );
3128  $deleted = (bool)$dbr->selectField( 'archive', '1',
3129  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3130  __METHOD__
3131  );
3132  if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3133  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3134  [ 'fa_name' => $this->getDBkey() ],
3135  __METHOD__
3136  );
3137  }
3138  return $deleted;
3139  }
3140 
3149  public function getArticleID( $flags = 0 ) {
3150  if ( $this->getNamespace() < 0 ) {
3151  $this->mArticleID = 0;
3152  return $this->mArticleID;
3153  }
3154  $linkCache = LinkCache::singleton();
3155  if ( $flags & self::GAID_FOR_UPDATE ) {
3156  $oldUpdate = $linkCache->forUpdate( true );
3157  $linkCache->clearLink( $this );
3158  $this->mArticleID = $linkCache->addLinkObj( $this );
3159  $linkCache->forUpdate( $oldUpdate );
3160  } else {
3161  if ( -1 == $this->mArticleID ) {
3162  $this->mArticleID = $linkCache->addLinkObj( $this );
3163  }
3164  }
3165  return $this->mArticleID;
3166  }
3167 
3175  public function isRedirect( $flags = 0 ) {
3176  if ( !is_null( $this->mRedirect ) ) {
3177  return $this->mRedirect;
3178  }
3179  if ( !$this->getArticleID( $flags ) ) {
3180  $this->mRedirect = false;
3181  return $this->mRedirect;
3182  }
3183 
3184  $linkCache = LinkCache::singleton();
3185  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3186  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3187  if ( $cached === null ) {
3188  # Trust LinkCache's state over our own
3189  # LinkCache is telling us that the page doesn't exist, despite there being cached
3190  # data relating to an existing page in $this->mArticleID. Updaters should clear
3191  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3192  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3193  # LinkCache to refresh its data from the master.
3194  $this->mRedirect = false;
3195  return $this->mRedirect;
3196  }
3197 
3198  $this->mRedirect = (bool)$cached;
3199 
3200  return $this->mRedirect;
3201  }
3202 
3210  public function getLength( $flags = 0 ) {
3211  if ( $this->mLength != -1 ) {
3212  return $this->mLength;
3213  }
3214  if ( !$this->getArticleID( $flags ) ) {
3215  $this->mLength = 0;
3216  return $this->mLength;
3217  }
3218  $linkCache = LinkCache::singleton();
3219  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3220  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3221  if ( $cached === null ) {
3222  # Trust LinkCache's state over our own, as for isRedirect()
3223  $this->mLength = 0;
3224  return $this->mLength;
3225  }
3226 
3227  $this->mLength = intval( $cached );
3228 
3229  return $this->mLength;
3230  }
3231 
3238  public function getLatestRevID( $flags = 0 ) {
3239  if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3240  return intval( $this->mLatestID );
3241  }
3242  if ( !$this->getArticleID( $flags ) ) {
3243  $this->mLatestID = 0;
3244  return $this->mLatestID;
3245  }
3246  $linkCache = LinkCache::singleton();
3247  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3248  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3249  if ( $cached === null ) {
3250  # Trust LinkCache's state over our own, as for isRedirect()
3251  $this->mLatestID = 0;
3252  return $this->mLatestID;
3253  }
3254 
3255  $this->mLatestID = intval( $cached );
3256 
3257  return $this->mLatestID;
3258  }
3259 
3270  public function resetArticleID( $newid ) {
3271  $linkCache = LinkCache::singleton();
3272  $linkCache->clearLink( $this );
3273 
3274  if ( $newid === false ) {
3275  $this->mArticleID = -1;
3276  } else {
3277  $this->mArticleID = intval( $newid );
3278  }
3279  $this->mRestrictionsLoaded = false;
3280  $this->mRestrictions = [];
3281  $this->mOldRestrictions = false;
3282  $this->mRedirect = null;
3283  $this->mLength = -1;
3284  $this->mLatestID = false;
3285  $this->mContentModel = false;
3286  $this->mEstimateRevisions = null;
3287  $this->mPageLanguage = false;
3288  $this->mDbPageLanguage = false;
3289  $this->mIsBigDeletion = null;
3290  }
3291 
3292  public static function clearCaches() {
3293  $linkCache = LinkCache::singleton();
3294  $linkCache->clear();
3295 
3296  $titleCache = self::getTitleCache();
3297  $titleCache->clear();
3298  }
3299 
3307  public static function capitalize( $text, $ns = NS_MAIN ) {
3309 
3310  if ( MWNamespace::isCapitalized( $ns ) ) {
3311  return $wgContLang->ucfirst( $text );
3312  } else {
3313  return $text;
3314  }
3315  }
3316 
3329  private function secureAndSplit() {
3330  # Initialisation
3331  $this->mInterwiki = '';
3332  $this->mFragment = '';
3333  $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3334 
3335  $dbkey = $this->mDbkeyform;
3336 
3337  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3338  // the parsing code with Title, while avoiding massive refactoring.
3339  // @todo: get rid of secureAndSplit, refactor parsing code.
3340  $titleParser = self::getMediaWikiTitleCodec();
3341  // MalformedTitleException can be thrown here
3342  $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3343 
3344  # Fill fields
3345  $this->setFragment( '#' . $parts['fragment'] );
3346  $this->mInterwiki = $parts['interwiki'];
3347  $this->mLocalInterwiki = $parts['local_interwiki'];
3348  $this->mNamespace = $parts['namespace'];
3349  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3350 
3351  $this->mDbkeyform = $parts['dbkey'];
3352  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3353  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3354 
3355  # We already know that some pages won't be in the database!
3356  if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3357  $this->mArticleID = 0;
3358  }
3359 
3360  return true;
3361  }
3362 
3375  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3376  if ( count( $options ) > 0 ) {
3377  $db = wfGetDB( DB_MASTER );
3378  } else {
3379  $db = wfGetDB( DB_SLAVE );
3380  }
3381 
3382  $res = $db->select(
3383  [ 'page', $table ],
3384  self::getSelectFields(),
3385  [
3386  "{$prefix}_from=page_id",
3387  "{$prefix}_namespace" => $this->getNamespace(),
3388  "{$prefix}_title" => $this->getDBkey() ],
3389  __METHOD__,
3390  $options
3391  );
3392 
3393  $retVal = [];
3394  if ( $res->numRows() ) {
3395  $linkCache = LinkCache::singleton();
3396  foreach ( $res as $row ) {
3397  $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3398  if ( $titleObj ) {
3399  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3400  $retVal[] = $titleObj;
3401  }
3402  }
3403  }
3404  return $retVal;
3405  }
3406 
3417  public function getTemplateLinksTo( $options = [] ) {
3418  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3419  }
3420 
3433  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3434  $id = $this->getArticleID();
3435 
3436  # If the page doesn't exist; there can't be any link from this page
3437  if ( !$id ) {
3438  return [];
3439  }
3440 
3441  $db = wfGetDB( DB_SLAVE );
3442 
3443  $blNamespace = "{$prefix}_namespace";
3444  $blTitle = "{$prefix}_title";
3445 
3446  $res = $db->select(
3447  [ $table, 'page' ],
3448  array_merge(
3449  [ $blNamespace, $blTitle ],
3451  ),
3452  [ "{$prefix}_from" => $id ],
3453  __METHOD__,
3454  $options,
3455  [ 'page' => [
3456  'LEFT JOIN',
3457  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3458  ] ]
3459  );
3460 
3461  $retVal = [];
3462  $linkCache = LinkCache::singleton();
3463  foreach ( $res as $row ) {
3464  if ( $row->page_id ) {
3465  $titleObj = Title::newFromRow( $row );
3466  } else {
3467  $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3468  $linkCache->addBadLinkObj( $titleObj );
3469  }
3470  $retVal[] = $titleObj;
3471  }
3472 
3473  return $retVal;
3474  }
3475 
3486  public function getTemplateLinksFrom( $options = [] ) {
3487  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3488  }
3489 
3498  public function getBrokenLinksFrom() {
3499  if ( $this->getArticleID() == 0 ) {
3500  # All links from article ID 0 are false positives
3501  return [];
3502  }
3503 
3504  $dbr = wfGetDB( DB_SLAVE );
3505  $res = $dbr->select(
3506  [ 'page', 'pagelinks' ],
3507  [ 'pl_namespace', 'pl_title' ],
3508  [
3509  'pl_from' => $this->getArticleID(),
3510  'page_namespace IS NULL'
3511  ],
3512  __METHOD__, [],
3513  [
3514  'page' => [
3515  'LEFT JOIN',
3516  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3517  ]
3518  ]
3519  );
3520 
3521  $retVal = [];
3522  foreach ( $res as $row ) {
3523  $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3524  }
3525  return $retVal;
3526  }
3527 
3534  public function getCdnUrls() {
3535  $urls = [
3536  $this->getInternalURL(),
3537  $this->getInternalURL( 'action=history' )
3538  ];
3539 
3540  $pageLang = $this->getPageLanguage();
3541  if ( $pageLang->hasVariants() ) {
3542  $variants = $pageLang->getVariants();
3543  foreach ( $variants as $vCode ) {
3544  $urls[] = $this->getInternalURL( $vCode );
3545  }
3546  }
3547 
3548  // If we are looking at a css/js user subpage, purge the action=raw.
3549  if ( $this->isJsSubpage() ) {
3550  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3551  } elseif ( $this->isCssSubpage() ) {
3552  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3553  }
3554 
3555  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3556  return $urls;
3557  }
3558 
3562  public function getSquidURLs() {
3563  return $this->getCdnUrls();
3564  }
3565 
3569  public function purgeSquid() {
3571  new CdnCacheUpdate( $this->getCdnUrls() ),
3573  );
3574  }
3575 
3583  public function moveNoAuth( &$nt ) {
3584  wfDeprecated( __METHOD__, '1.25' );
3585  return $this->moveTo( $nt, false );
3586  }
3587 
3598  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3599  global $wgUser;
3600 
3601  if ( !( $nt instanceof Title ) ) {
3602  // Normally we'd add this to $errors, but we'll get
3603  // lots of syntax errors if $nt is not an object
3604  return [ [ 'badtitletext' ] ];
3605  }
3606 
3607  $mp = new MovePage( $this, $nt );
3608  $errors = $mp->isValidMove()->getErrorsArray();
3609  if ( $auth ) {
3610  $errors = wfMergeErrorArrays(
3611  $errors,
3612  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3613  );
3614  }
3615 
3616  return $errors ?: true;
3617  }
3618 
3625  protected function validateFileMoveOperation( $nt ) {
3626  global $wgUser;
3627 
3628  $errors = [];
3629 
3630  $destFile = wfLocalFile( $nt );
3631  $destFile->load( File::READ_LATEST );
3632  if ( !$wgUser->isAllowed( 'reupload-shared' )
3633  && !$destFile->exists() && wfFindFile( $nt )
3634  ) {
3635  $errors[] = [ 'file-exists-sharedrepo' ];
3636  }
3637 
3638  return $errors;
3639  }
3640 
3653  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
3654  global $wgUser;
3655  $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3656  if ( is_array( $err ) ) {
3657  // Auto-block user's IP if the account was "hard" blocked
3658  $wgUser->spreadAnyEditBlock();
3659  return $err;
3660  }
3661  // Check suppressredirect permission
3662  if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3663  $createRedirect = true;
3664  }
3665 
3666  $mp = new MovePage( $this, $nt );
3667  $status = $mp->move( $wgUser, $reason, $createRedirect );
3668  if ( $status->isOK() ) {
3669  return true;
3670  } else {
3671  return $status->getErrorsArray();
3672  }
3673  }
3674 
3687  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
3688  global $wgMaximumMovedPages;
3689  // Check permissions
3690  if ( !$this->userCan( 'move-subpages' ) ) {
3691  return [ 'cant-move-subpages' ];
3692  }
3693  // Do the source and target namespaces support subpages?
3694  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3695  return [ 'namespace-nosubpages',
3697  }
3698  if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3699  return [ 'namespace-nosubpages',
3700  MWNamespace::getCanonicalName( $nt->getNamespace() ) ];
3701  }
3702 
3703  $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3704  $retval = [];
3705  $count = 0;
3706  foreach ( $subpages as $oldSubpage ) {
3707  $count++;
3708  if ( $count > $wgMaximumMovedPages ) {
3709  $retval[$oldSubpage->getPrefixedText()] =
3710  [ 'movepage-max-pages',
3711  $wgMaximumMovedPages ];
3712  break;
3713  }
3714 
3715  // We don't know whether this function was called before
3716  // or after moving the root page, so check both
3717  // $this and $nt
3718  if ( $oldSubpage->getArticleID() == $this->getArticleID()
3719  || $oldSubpage->getArticleID() == $nt->getArticleID()
3720  ) {
3721  // When moving a page to a subpage of itself,
3722  // don't move it twice
3723  continue;
3724  }
3725  $newPageName = preg_replace(
3726  '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3727  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
3728  $oldSubpage->getDBkey() );
3729  if ( $oldSubpage->isTalkPage() ) {
3730  $newNs = $nt->getTalkPage()->getNamespace();
3731  } else {
3732  $newNs = $nt->getSubjectPage()->getNamespace();
3733  }
3734  # Bug 14385: we need makeTitleSafe because the new page names may
3735  # be longer than 255 characters.
3736  $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3737 
3738  $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
3739  if ( $success === true ) {
3740  $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3741  } else {
3742  $retval[$oldSubpage->getPrefixedText()] = $success;
3743  }
3744  }
3745  return $retval;
3746  }
3747 
3754  public function isSingleRevRedirect() {
3755  global $wgContentHandlerUseDB;
3756 
3757  $dbw = wfGetDB( DB_MASTER );
3758 
3759  # Is it a redirect?
3760  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3761  if ( $wgContentHandlerUseDB ) {
3762  $fields[] = 'page_content_model';
3763  }
3764 
3765  $row = $dbw->selectRow( 'page',
3766  $fields,
3767  $this->pageCond(),
3768  __METHOD__,
3769  [ 'FOR UPDATE' ]
3770  );
3771  # Cache some fields we may want
3772  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3773  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3774  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3775  $this->mContentModel = $row && isset( $row->page_content_model )
3776  ? strval( $row->page_content_model )
3777  : false;
3778 
3779  if ( !$this->mRedirect ) {
3780  return false;
3781  }
3782  # Does the article have a history?
3783  $row = $dbw->selectField( [ 'page', 'revision' ],
3784  'rev_id',
3785  [ 'page_namespace' => $this->getNamespace(),
3786  'page_title' => $this->getDBkey(),
3787  'page_id=rev_page',
3788  'page_latest != rev_id'
3789  ],
3790  __METHOD__,
3791  [ 'FOR UPDATE' ]
3792  );
3793  # Return true if there was no history
3794  return ( $row === false );
3795  }
3796 
3805  public function isValidMoveTarget( $nt ) {
3806  # Is it an existing file?
3807  if ( $nt->getNamespace() == NS_FILE ) {
3808  $file = wfLocalFile( $nt );
3809  $file->load( File::READ_LATEST );
3810  if ( $file->exists() ) {
3811  wfDebug( __METHOD__ . ": file exists\n" );
3812  return false;
3813  }
3814  }
3815  # Is it a redirect with no history?
3816  if ( !$nt->isSingleRevRedirect() ) {
3817  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3818  return false;
3819  }
3820  # Get the article text
3822  if ( !is_object( $rev ) ) {
3823  return false;
3824  }
3825  $content = $rev->getContent();
3826  # Does the redirect point to the source?
3827  # Or is it a broken self-redirect, usually caused by namespace collisions?
3828  $redirTitle = $content ? $content->getRedirectTarget() : null;
3829 
3830  if ( $redirTitle ) {
3831  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3832  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3833  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3834  return false;
3835  } else {
3836  return true;
3837  }
3838  } else {
3839  # Fail safe (not a redirect after all. strange.)
3840  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3841  " is a redirect, but it doesn't contain a valid redirect.\n" );
3842  return false;
3843  }
3844  }
3845 
3853  public function getParentCategories() {
3855 
3856  $data = [];
3857 
3858  $titleKey = $this->getArticleID();
3859 
3860  if ( $titleKey === 0 ) {
3861  return $data;
3862  }
3863 
3864  $dbr = wfGetDB( DB_SLAVE );
3865 
3866  $res = $dbr->select(
3867  'categorylinks',
3868  'cl_to',
3869  [ 'cl_from' => $titleKey ],
3870  __METHOD__
3871  );
3872 
3873  if ( $res->numRows() > 0 ) {
3874  foreach ( $res as $row ) {
3875  // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3876  $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3877  }
3878  }
3879  return $data;
3880  }
3881 
3888  public function getParentCategoryTree( $children = [] ) {
3889  $stack = [];
3890  $parents = $this->getParentCategories();
3891 
3892  if ( $parents ) {
3893  foreach ( $parents as $parent => $current ) {
3894  if ( array_key_exists( $parent, $children ) ) {
3895  # Circular reference
3896  $stack[$parent] = [];
3897  } else {
3898  $nt = Title::newFromText( $parent );
3899  if ( $nt ) {
3900  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3901  }
3902  }
3903  }
3904  }
3905 
3906  return $stack;
3907  }
3908 
3915  public function pageCond() {
3916  if ( $this->mArticleID > 0 ) {
3917  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3918  return [ 'page_id' => $this->mArticleID ];
3919  } else {
3920  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3921  }
3922  }
3923 
3931  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3932  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3933  $revId = $db->selectField( 'revision', 'rev_id',
3934  [
3935  'rev_page' => $this->getArticleID( $flags ),
3936  'rev_id < ' . intval( $revId )
3937  ],
3938  __METHOD__,
3939  [ 'ORDER BY' => 'rev_id DESC' ]
3940  );
3941 
3942  if ( $revId === false ) {
3943  return false;
3944  } else {
3945  return intval( $revId );
3946  }
3947  }
3948 
3956  public function getNextRevisionID( $revId, $flags = 0 ) {
3957  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3958  $revId = $db->selectField( 'revision', 'rev_id',
3959  [
3960  'rev_page' => $this->getArticleID( $flags ),
3961  'rev_id > ' . intval( $revId )
3962  ],
3963  __METHOD__,
3964  [ 'ORDER BY' => 'rev_id' ]
3965  );
3966 
3967  if ( $revId === false ) {
3968  return false;
3969  } else {
3970  return intval( $revId );
3971  }
3972  }
3973 
3980  public function getFirstRevision( $flags = 0 ) {
3981  $pageId = $this->getArticleID( $flags );
3982  if ( $pageId ) {
3983  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3984  $row = $db->selectRow( 'revision', Revision::selectFields(),
3985  [ 'rev_page' => $pageId ],
3986  __METHOD__,
3987  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ]
3988  );
3989  if ( $row ) {
3990  return new Revision( $row );
3991  }
3992  }
3993  return null;
3994  }
3995 
4002  public function getEarliestRevTime( $flags = 0 ) {
4003  $rev = $this->getFirstRevision( $flags );
4004  return $rev ? $rev->getTimestamp() : null;
4005  }
4006 
4012  public function isNewPage() {
4013  $dbr = wfGetDB( DB_SLAVE );
4014  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4015  }
4016 
4022  public function isBigDeletion() {
4023  global $wgDeleteRevisionsLimit;
4024 
4025  if ( !$wgDeleteRevisionsLimit ) {
4026  return false;
4027  }
4028 
4029  if ( $this->mIsBigDeletion === null ) {
4030  $dbr = wfGetDB( DB_SLAVE );
4031 
4032  $revCount = $dbr->selectRowCount(
4033  'revision',
4034  '1',
4035  [ 'rev_page' => $this->getArticleID() ],
4036  __METHOD__,
4037  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4038  );
4039 
4040  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4041  }
4042 
4043  return $this->mIsBigDeletion;
4044  }
4045 
4051  public function estimateRevisionCount() {
4052  if ( !$this->exists() ) {
4053  return 0;
4054  }
4055 
4056  if ( $this->mEstimateRevisions === null ) {
4057  $dbr = wfGetDB( DB_SLAVE );
4058  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4059  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4060  }
4061 
4063  }
4064 
4074  public function countRevisionsBetween( $old, $new, $max = null ) {
4075  if ( !( $old instanceof Revision ) ) {
4076  $old = Revision::newFromTitle( $this, (int)$old );
4077  }
4078  if ( !( $new instanceof Revision ) ) {
4079  $new = Revision::newFromTitle( $this, (int)$new );
4080  }
4081  if ( !$old || !$new ) {
4082  return 0; // nothing to compare
4083  }
4084  $dbr = wfGetDB( DB_SLAVE );
4085  $conds = [
4086  'rev_page' => $this->getArticleID(),
4087  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4088  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4089  ];
4090  if ( $max !== null ) {
4091  return $dbr->selectRowCount( 'revision', '1',
4092  $conds,
4093  __METHOD__,
4094  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4095  );
4096  } else {
4097  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4098  }
4099  }
4100 
4117  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4118  if ( !( $old instanceof Revision ) ) {
4119  $old = Revision::newFromTitle( $this, (int)$old );
4120  }
4121  if ( !( $new instanceof Revision ) ) {
4122  $new = Revision::newFromTitle( $this, (int)$new );
4123  }
4124  // XXX: what if Revision objects are passed in, but they don't refer to this title?
4125  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4126  // in the sanity check below?
4127  if ( !$old || !$new ) {
4128  return null; // nothing to compare
4129  }
4130  $authors = [];
4131  $old_cmp = '>';
4132  $new_cmp = '<';
4133  $options = (array)$options;
4134  if ( in_array( 'include_old', $options ) ) {
4135  $old_cmp = '>=';
4136  }
4137  if ( in_array( 'include_new', $options ) ) {
4138  $new_cmp = '<=';
4139  }
4140  if ( in_array( 'include_both', $options ) ) {
4141  $old_cmp = '>=';
4142  $new_cmp = '<=';
4143  }
4144  // No DB query needed if $old and $new are the same or successive revisions:
4145  if ( $old->getId() === $new->getId() ) {
4146  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4147  [] :
4148  [ $old->getUserText( Revision::RAW ) ];
4149  } elseif ( $old->getId() === $new->getParentId() ) {
4150  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4151  $authors[] = $old->getUserText( Revision::RAW );
4152  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4153  $authors[] = $new->getUserText( Revision::RAW );
4154  }
4155  } elseif ( $old_cmp === '>=' ) {
4156  $authors[] = $old->getUserText( Revision::RAW );
4157  } elseif ( $new_cmp === '<=' ) {
4158  $authors[] = $new->getUserText( Revision::RAW );
4159  }
4160  return $authors;
4161  }
4162  $dbr = wfGetDB( DB_SLAVE );
4163  $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4164  [
4165  'rev_page' => $this->getArticleID(),
4166  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4167  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4168  ], __METHOD__,
4169  [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
4170  );
4171  foreach ( $res as $row ) {
4172  $authors[] = $row->rev_user_text;
4173  }
4174  return $authors;
4175  }
4176 
4191  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4192  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4193  return $authors ? count( $authors ) : 0;
4194  }
4195 
4202  public function equals( Title $title ) {
4203  // Note: === is necessary for proper matching of number-like titles.
4204  return $this->getInterwiki() === $title->getInterwiki()
4205  && $this->getNamespace() == $title->getNamespace()
4206  && $this->getDBkey() === $title->getDBkey();
4207  }
4208 
4215  public function isSubpageOf( Title $title ) {
4216  return $this->getInterwiki() === $title->getInterwiki()
4217  && $this->getNamespace() == $title->getNamespace()
4218  && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4219  }
4220 
4232  public function exists( $flags = 0 ) {
4233  $exists = $this->getArticleID( $flags ) != 0;
4234  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4235  return $exists;
4236  }
4237 
4254  public function isAlwaysKnown() {
4255  $isKnown = null;
4256 
4267  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4268 
4269  if ( !is_null( $isKnown ) ) {
4270  return $isKnown;
4271  }
4272 
4273  if ( $this->isExternal() ) {
4274  return true; // any interwiki link might be viewable, for all we know
4275  }
4276 
4277  switch ( $this->mNamespace ) {
4278  case NS_MEDIA:
4279  case NS_FILE:
4280  // file exists, possibly in a foreign repo
4281  return (bool)wfFindFile( $this );
4282  case NS_SPECIAL:
4283  // valid special page
4284  return SpecialPageFactory::exists( $this->getDBkey() );
4285  case NS_MAIN:
4286  // selflink, possibly with fragment
4287  return $this->mDbkeyform == '';
4288  case NS_MEDIAWIKI:
4289  // known system message
4290  return $this->hasSourceText() !== false;
4291  default:
4292  return false;
4293  }
4294  }
4295 
4307  public function isKnown() {
4308  return $this->isAlwaysKnown() || $this->exists();
4309  }
4310 
4316  public function hasSourceText() {
4317  if ( $this->exists() ) {
4318  return true;
4319  }
4320 
4321  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4322  // If the page doesn't exist but is a known system message, default
4323  // message content will be displayed, same for language subpages-
4324  // Use always content language to avoid loading hundreds of languages
4325  // to get the link color.
4327  list( $name, ) = MessageCache::singleton()->figureMessage(
4328  $wgContLang->lcfirst( $this->getText() )
4329  );
4330  $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4331  return $message->exists();
4332  }
4333 
4334  return false;
4335  }
4336 
4342  public function getDefaultMessageText() {
4344 
4345  if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4346  return false;
4347  }
4348 
4349  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4350  $wgContLang->lcfirst( $this->getText() )
4351  );
4352  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4353 
4354  if ( $message->exists() ) {
4355  return $message->plain();
4356  } else {
4357  return false;
4358  }
4359  }
4360 
4367  public function invalidateCache( $purgeTime = null ) {
4368  if ( wfReadOnly() ) {
4369  return false;
4370  }
4371 
4372  if ( $this->mArticleID === 0 ) {
4373  return true; // avoid gap locking if we know it's not there
4374  }
4375 
4376  $method = __METHOD__;
4377  $dbw = wfGetDB( DB_MASTER );
4378  $conds = $this->pageCond();
4379  $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method, $purgeTime ) {
4380  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4381 
4382  $dbw->update(
4383  'page',
4384  [ 'page_touched' => $dbTimestamp ],
4385  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4386  $method
4387  );
4388  } );
4389 
4390  return true;
4391  }
4392 
4398  public function touchLinks() {
4399  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4400  if ( $this->getNamespace() == NS_CATEGORY ) {
4401  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4402  }
4403  }
4404 
4411  public function getTouched( $db = null ) {
4412  if ( $db === null ) {
4413  $db = wfGetDB( DB_SLAVE );
4414  }
4415  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4416  return $touched;
4417  }
4418 
4425  public function getNotificationTimestamp( $user = null ) {
4426  global $wgUser;
4427 
4428  // Assume current user if none given
4429  if ( !$user ) {
4430  $user = $wgUser;
4431  }
4432  // Check cache first
4433  $uid = $user->getId();
4434  if ( !$uid ) {
4435  return false;
4436  }
4437  // avoid isset here, as it'll return false for null entries
4438  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4439  return $this->mNotificationTimestamp[$uid];
4440  }
4441  // Don't cache too much!
4442  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4443  $this->mNotificationTimestamp = [];
4444  }
4445 
4446  $watchedItem = WatchedItemStore::getDefaultInstance()->getWatchedItem( $user, $this );
4447  if ( $watchedItem ) {
4448  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4449  } else {
4450  $this->mNotificationTimestamp[$uid] = false;
4451  }
4452 
4453  return $this->mNotificationTimestamp[$uid];
4454  }
4455 
4462  public function getNamespaceKey( $prepend = 'nstab-' ) {
4464  // Gets the subject namespace if this title
4465  $namespace = MWNamespace::getSubject( $this->getNamespace() );
4466  // Checks if canonical namespace name exists for namespace
4467  if ( MWNamespace::exists( $this->getNamespace() ) ) {
4468  // Uses canonical namespace name
4469  $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4470  } else {
4471  // Uses text of namespace
4472  $namespaceKey = $this->getSubjectNsText();
4473  }
4474  // Makes namespace key lowercase
4475  $namespaceKey = $wgContLang->lc( $namespaceKey );
4476  // Uses main
4477  if ( $namespaceKey == '' ) {
4478  $namespaceKey = 'main';
4479  }
4480  // Changes file to image for backwards compatibility
4481  if ( $namespaceKey == 'file' ) {
4482  $namespaceKey = 'image';
4483  }
4484  return $prepend . $namespaceKey;
4485  }
4486 
4493  public function getRedirectsHere( $ns = null ) {
4494  $redirs = [];
4495 
4496  $dbr = wfGetDB( DB_SLAVE );
4497  $where = [
4498  'rd_namespace' => $this->getNamespace(),
4499  'rd_title' => $this->getDBkey(),
4500  'rd_from = page_id'
4501  ];
4502  if ( $this->isExternal() ) {
4503  $where['rd_interwiki'] = $this->getInterwiki();
4504  } else {
4505  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4506  }
4507  if ( !is_null( $ns ) ) {
4508  $where['page_namespace'] = $ns;
4509  }
4510 
4511  $res = $dbr->select(
4512  [ 'redirect', 'page' ],
4513  [ 'page_namespace', 'page_title' ],
4514  $where,
4515  __METHOD__
4516  );
4517 
4518  foreach ( $res as $row ) {
4519  $redirs[] = self::newFromRow( $row );
4520  }
4521  return $redirs;
4522  }
4523 
4529  public function isValidRedirectTarget() {
4530  global $wgInvalidRedirectTargets;
4531 
4532  if ( $this->isSpecialPage() ) {
4533  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4534  if ( $this->isSpecial( 'Userlogout' ) ) {
4535  return false;
4536  }
4537 
4538  foreach ( $wgInvalidRedirectTargets as $target ) {
4539  if ( $this->isSpecial( $target ) ) {
4540  return false;
4541  }
4542  }
4543  }
4544 
4545  return true;
4546  }
4547 
4553  public function getBacklinkCache() {
4554  return BacklinkCache::get( $this );
4555  }
4556 
4562  public function canUseNoindex() {
4563  global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
4564 
4565  $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4566  ? $wgContentNamespaces
4567  : $wgExemptFromUserRobotsControl;
4568 
4569  return !in_array( $this->mNamespace, $bannedNamespaces );
4570 
4571  }
4572 
4583  public function getCategorySortkey( $prefix = '' ) {
4584  $unprefixed = $this->getText();
4585 
4586  // Anything that uses this hook should only depend
4587  // on the Title object passed in, and should probably
4588  // tell the users to run updateCollations.php --force
4589  // in order to re-sort existing category relations.
4590  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4591  if ( $prefix !== '' ) {
4592  # Separate with a line feed, so the unprefixed part is only used as
4593  # a tiebreaker when two pages have the exact same prefix.
4594  # In UCA, tab is the only character that can sort above LF
4595  # so we strip both of them from the original prefix.
4596  $prefix = strtr( $prefix, "\n\t", ' ' );
4597  return "$prefix\n$unprefixed";
4598  }
4599  return $unprefixed;
4600  }
4601 
4609  private function getDbPageLanguageCode() {
4610  global $wgPageLanguageUseDB;
4611 
4612  // check, if the page language could be saved in the database, and if so and
4613  // the value is not requested already, lookup the page language using LinkCache
4614  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4615  $linkCache = LinkCache::singleton();
4616  $linkCache->addLinkObj( $this );
4617  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4618  }
4619 
4620  return $this->mDbPageLanguage;
4621  }
4622 
4631  public function getPageLanguage() {
4633  if ( $this->isSpecialPage() ) {
4634  // special pages are in the user language
4635  return $wgLang;
4636  }
4637 
4638  // Checking if DB language is set
4639  $dbPageLanguage = $this->getDbPageLanguageCode();
4640  if ( $dbPageLanguage ) {
4641  return wfGetLangObj( $dbPageLanguage );
4642  }
4643 
4644  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4645  // Note that this may depend on user settings, so the cache should
4646  // be only per-request.
4647  // NOTE: ContentHandler::getPageLanguage() may need to load the
4648  // content to determine the page language!
4649  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4650  // tests.
4651  $contentHandler = ContentHandler::getForTitle( $this );
4652  $langObj = $contentHandler->getPageLanguage( $this );
4653  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4654  } else {
4655  $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4656  }
4657 
4658  return $langObj;
4659  }
4660 
4669  public function getPageViewLanguage() {
4670  global $wgLang;
4671 
4672  if ( $this->isSpecialPage() ) {
4673  // If the user chooses a variant, the content is actually
4674  // in a language whose code is the variant code.
4675  $variant = $wgLang->getPreferredVariant();
4676  if ( $wgLang->getCode() !== $variant ) {
4677  return Language::factory( $variant );
4678  }
4679 
4680  return $wgLang;
4681  }
4682 
4683  // Checking if DB language is set
4684  $dbPageLanguage = $this->getDbPageLanguageCode();
4685  if ( $dbPageLanguage ) {
4686  $pageLang = wfGetLangObj( $dbPageLanguage );
4687  $variant = $pageLang->getPreferredVariant();
4688  if ( $pageLang->getCode() !== $variant ) {
4689  $pageLang = Language::factory( $variant );
4690  }
4691 
4692  return $pageLang;
4693  }
4694 
4695  // @note Can't be cached persistently, depends on user settings.
4696  // @note ContentHandler::getPageViewLanguage() may need to load the
4697  // content to determine the page language!
4698  $contentHandler = ContentHandler::getForTitle( $this );
4699  $pageLang = $contentHandler->getPageViewLanguage( $this );
4700  return $pageLang;
4701  }
4702 
4713  public function getEditNotices( $oldid = 0 ) {
4714  $notices = [];
4715 
4716  // Optional notice for the entire namespace
4717  $editnotice_ns = 'editnotice-' . $this->getNamespace();
4718  $msg = wfMessage( $editnotice_ns );
4719  if ( $msg->exists() ) {
4720  $html = $msg->parseAsBlock();
4721  // Edit notices may have complex logic, but output nothing (T91715)
4722  if ( trim( $html ) !== '' ) {
4723  $notices[$editnotice_ns] = Html::rawElement(
4724  'div',
4725  [ 'class' => [
4726  'mw-editnotice',
4727  'mw-editnotice-namespace',
4728  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4729  ] ],
4730  $html
4731  );
4732  }
4733  }
4734 
4735  if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4736  // Optional notice for page itself and any parent page
4737  $parts = explode( '/', $this->getDBkey() );
4738  $editnotice_base = $editnotice_ns;
4739  while ( count( $parts ) > 0 ) {
4740  $editnotice_base .= '-' . array_shift( $parts );
4741  $msg = wfMessage( $editnotice_base );
4742  if ( $msg->exists() ) {
4743  $html = $msg->parseAsBlock();
4744  if ( trim( $html ) !== '' ) {
4745  $notices[$editnotice_base] = Html::rawElement(
4746  'div',
4747  [ 'class' => [
4748  'mw-editnotice',
4749  'mw-editnotice-base',
4750  Sanitizer::escapeClass( "mw-$editnotice_base" )
4751  ] ],
4752  $html
4753  );
4754  }
4755  }
4756  }
4757  } else {
4758  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4759  $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4760  $msg = wfMessage( $editnoticeText );
4761  if ( $msg->exists() ) {
4762  $html = $msg->parseAsBlock();
4763  if ( trim( $html ) !== '' ) {
4764  $notices[$editnoticeText] = Html::rawElement(
4765  'div',
4766  [ 'class' => [
4767  'mw-editnotice',
4768  'mw-editnotice-page',
4769  Sanitizer::escapeClass( "mw-$editnoticeText" )
4770  ] ],
4771  $html
4772  );
4773  }
4774  }
4775  }
4776 
4777  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4778  return $notices;
4779  }
4780 
4784  public function __sleep() {
4785  return [
4786  'mNamespace',
4787  'mDbkeyform',
4788  'mFragment',
4789  'mInterwiki',
4790  'mLocalInterwiki',
4791  'mUserCaseDBKey',
4792  'mDefaultNamespace',
4793  ];
4794  }
4795 
4796  public function __wakeup() {
4797  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4798  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4799  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4800  }
4801 
4802 }
getEarliestRevTime($flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:4002
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:4254
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:3014
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3238
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:4398
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:4232
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable...
Definition: Title.php:1154
getInternalURL($query= '', $query2=false)
Get the URL form for an internal link.
Definition: Title.php:1822
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:4562
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:3562
$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:2671
isSpecial($name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1055
static clearCaches()
Definition: Title.php:3292
getArticleID($flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3149
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:3042
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:3687
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition: Title.php:4342
getEditNotices($oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:4713
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:4796
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:2443
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:2291
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:3329
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:3583
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:4609
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:3175
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:4022
set($key, $value, $exptime=0, $flags=0)
const NS_SPECIAL
Definition: Defines.php:58
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context $revId
Definition: hooks.txt: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:3486
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:3805
static escapeClass($class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1208
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:3853
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4553
$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:1945
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:2504
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:4425
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:4574
getSubpages($limit=-1)
Get all subpages of this page.
Definition: Title.php:3070
userCan($action, $user=null, $rigor= 'secure')
Can $user perform $action on this page?
Definition: Title.php:1890
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:3006
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:3210
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:3598
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:3956
loadRestrictionsFromResultWrapper($res, $oldFashionedRestrictions=null)
Loads a string into mRestrictions array.
Definition: Title.php:2876
__sleep()
Definition: Title.php:4784
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:2593
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:4554
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:4316
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:3410
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:2861
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:4411
getLocalURL($query= '', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1707
$res
Definition: database.txt:21
checkCSSandJSPermissions($action, $user, $errors, $rigor, $short)
Check CSS/JS sub-page permissions.
Definition: Title.php:2108
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:4529
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:2689
invalidateCache($purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4367
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3534
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:1914
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:3433
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:3653
getRedirectsHere($ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4493
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:2079
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:1842
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:4215
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:3625
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:1478
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:4117
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:4202
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:4307
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:3417
const NS_FILE
Definition: Defines.php:75
static newFromResult($res)
Definition: TitleArray.php:38
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2806
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:2178
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:2703
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:3498
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:4462
countRevisionsBetween($old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:4074
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:2819
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:3980
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:1131
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:1877
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:1854
$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:2968
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:3754
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:1799
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition: Title.php:4669
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:2522
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:4583
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:4631
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:2333
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:2720
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:3915
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:3123
isNewPage()
Check if this is a new page.
Definition: Title.php:4012
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:2408
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:2639
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:2225
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:3098
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:2895
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:2549
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:4191
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:2144
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:2046
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:3888
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:2835
isSemiProtected($action= 'edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2611
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:2015
getRestrictionExpiry($action)
Get the expiry time for the restriction against a given action.
Definition: Title.php:2849
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:3931
$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:3307
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:3569
static getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4531
resetArticleID($newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3270
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:3375
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:4051
$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