MediaWiki  1.27.0
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  // Account creation blocks handled at userlogin.
2293  // Unblocking handled in SpecialUnblock
2294  if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2295  return $errors;
2296  }
2297 
2298  global $wgEmailConfirmToEdit;
2299 
2300  if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
2301  $errors[] = [ 'confirmedittext' ];
2302  }
2303 
2304  $useSlave = ( $rigor !== 'secure' );
2305  if ( ( $action == 'edit' || $action == 'create' )
2306  && !$user->isBlockedFrom( $this, $useSlave )
2307  ) {
2308  // Don't block the user from editing their own talk page unless they've been
2309  // explicitly blocked from that too.
2310  } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2311  // @todo FIXME: Pass the relevant context into this function.
2312  $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2313  }
2314 
2315  return $errors;
2316  }
2317 
2329  private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2330  global $wgWhitelistRead, $wgWhitelistReadRegexp;
2331 
2332  $whitelisted = false;
2333  if ( User::isEveryoneAllowed( 'read' ) ) {
2334  # Shortcut for public wikis, allows skipping quite a bit of code
2335  $whitelisted = true;
2336  } elseif ( $user->isAllowed( 'read' ) ) {
2337  # If the user is allowed to read pages, he is allowed to read all pages
2338  $whitelisted = true;
2339  } elseif ( $this->isSpecial( 'Userlogin' )
2340  || $this->isSpecial( 'ChangePassword' )
2341  || $this->isSpecial( 'PasswordReset' )
2342  ) {
2343  # Always grant access to the login page.
2344  # Even anons need to be able to log in.
2345  $whitelisted = true;
2346  } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2347  # Time to check the whitelist
2348  # Only do these checks is there's something to check against
2349  $name = $this->getPrefixedText();
2350  $dbName = $this->getPrefixedDBkey();
2351 
2352  // Check for explicit whitelisting with and without underscores
2353  if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2354  $whitelisted = true;
2355  } elseif ( $this->getNamespace() == NS_MAIN ) {
2356  # Old settings might have the title prefixed with
2357  # a colon for main-namespace pages
2358  if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2359  $whitelisted = true;
2360  }
2361  } elseif ( $this->isSpecialPage() ) {
2362  # If it's a special page, ditch the subpage bit and check again
2363  $name = $this->getDBkey();
2364  list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2365  if ( $name ) {
2366  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2367  if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2368  $whitelisted = true;
2369  }
2370  }
2371  }
2372  }
2373 
2374  if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2375  $name = $this->getPrefixedText();
2376  // Check for regex whitelisting
2377  foreach ( $wgWhitelistReadRegexp as $listItem ) {
2378  if ( preg_match( $listItem, $name ) ) {
2379  $whitelisted = true;
2380  break;
2381  }
2382  }
2383  }
2384 
2385  if ( !$whitelisted ) {
2386  # If the title is not whitelisted, give extensions a chance to do so...
2387  Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2388  if ( !$whitelisted ) {
2389  $errors[] = $this->missingPermissionError( $action, $short );
2390  }
2391  }
2392 
2393  return $errors;
2394  }
2395 
2404  private function missingPermissionError( $action, $short ) {
2405  // We avoid expensive display logic for quickUserCan's and such
2406  if ( $short ) {
2407  return [ 'badaccess-group0' ];
2408  }
2409 
2410  $groups = array_map( [ 'User', 'makeGroupLinkWiki' ],
2411  User::getGroupsWithPermission( $action ) );
2412 
2413  if ( count( $groups ) ) {
2414  global $wgLang;
2415  return [
2416  'badaccess-groups',
2417  $wgLang->commaList( $groups ),
2418  count( $groups )
2419  ];
2420  } else {
2421  return [ 'badaccess-group0' ];
2422  }
2423  }
2424 
2440  $action, $user, $rigor = 'secure', $short = false
2441  ) {
2442  if ( $rigor === true ) {
2443  $rigor = 'secure'; // b/c
2444  } elseif ( $rigor === false ) {
2445  $rigor = 'quick'; // b/c
2446  } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2447  throw new Exception( "Invalid rigor parameter '$rigor'." );
2448  }
2449 
2450  # Read has special handling
2451  if ( $action == 'read' ) {
2452  $checks = [
2453  'checkPermissionHooks',
2454  'checkReadPermissions',
2455  ];
2456  # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
2457  # here as it will lead to duplicate error messages. This is okay to do
2458  # since anywhere that checks for create will also check for edit, and
2459  # those checks are called for edit.
2460  } elseif ( $action == 'create' ) {
2461  $checks = [
2462  'checkQuickPermissions',
2463  'checkPermissionHooks',
2464  'checkPageRestrictions',
2465  'checkCascadingSourcesRestrictions',
2466  'checkActionPermissions',
2467  'checkUserBlock'
2468  ];
2469  } else {
2470  $checks = [
2471  'checkQuickPermissions',
2472  'checkPermissionHooks',
2473  'checkSpecialsAndNSPermissions',
2474  'checkCSSandJSPermissions',
2475  'checkPageRestrictions',
2476  'checkCascadingSourcesRestrictions',
2477  'checkActionPermissions',
2478  'checkUserBlock'
2479  ];
2480  }
2481 
2482  $errors = [];
2483  while ( count( $checks ) > 0 &&
2484  !( $short && count( $errors ) > 0 ) ) {
2485  $method = array_shift( $checks );
2486  $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2487  }
2488 
2489  return $errors;
2490  }
2491 
2499  public static function getFilteredRestrictionTypes( $exists = true ) {
2500  global $wgRestrictionTypes;
2501  $types = $wgRestrictionTypes;
2502  if ( $exists ) {
2503  # Remove the create restriction for existing titles
2504  $types = array_diff( $types, [ 'create' ] );
2505  } else {
2506  # Only the create and upload restrictions apply to non-existing titles
2507  $types = array_intersect( $types, [ 'create', 'upload' ] );
2508  }
2509  return $types;
2510  }
2511 
2517  public function getRestrictionTypes() {
2518  if ( $this->isSpecialPage() ) {
2519  return [];
2520  }
2521 
2522  $types = self::getFilteredRestrictionTypes( $this->exists() );
2523 
2524  if ( $this->getNamespace() != NS_FILE ) {
2525  # Remove the upload restriction for non-file titles
2526  $types = array_diff( $types, [ 'upload' ] );
2527  }
2528 
2529  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2530 
2531  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2532  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2533 
2534  return $types;
2535  }
2536 
2544  public function getTitleProtection() {
2545  // Can't protect pages in special namespaces
2546  if ( $this->getNamespace() < 0 ) {
2547  return false;
2548  }
2549 
2550  // Can't protect pages that exist.
2551  if ( $this->exists() ) {
2552  return false;
2553  }
2554 
2555  if ( $this->mTitleProtection === null ) {
2556  $dbr = wfGetDB( DB_SLAVE );
2557  $res = $dbr->select(
2558  'protected_titles',
2559  [
2560  'user' => 'pt_user',
2561  'reason' => 'pt_reason',
2562  'expiry' => 'pt_expiry',
2563  'permission' => 'pt_create_perm'
2564  ],
2565  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2566  __METHOD__
2567  );
2568 
2569  // fetchRow returns false if there are no rows.
2570  $row = $dbr->fetchRow( $res );
2571  if ( $row ) {
2572  if ( $row['permission'] == 'sysop' ) {
2573  $row['permission'] = 'editprotected'; // B/C
2574  }
2575  if ( $row['permission'] == 'autoconfirmed' ) {
2576  $row['permission'] = 'editsemiprotected'; // B/C
2577  }
2578  $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2579  }
2580  $this->mTitleProtection = $row;
2581  }
2582  return $this->mTitleProtection;
2583  }
2584 
2588  public function deleteTitleProtection() {
2589  $dbw = wfGetDB( DB_MASTER );
2590 
2591  $dbw->delete(
2592  'protected_titles',
2593  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2594  __METHOD__
2595  );
2596  $this->mTitleProtection = false;
2597  }
2598 
2606  public function isSemiProtected( $action = 'edit' ) {
2607  global $wgSemiprotectedRestrictionLevels;
2608 
2609  $restrictions = $this->getRestrictions( $action );
2610  $semi = $wgSemiprotectedRestrictionLevels;
2611  if ( !$restrictions || !$semi ) {
2612  // Not protected, or all protection is full protection
2613  return false;
2614  }
2615 
2616  // Remap autoconfirmed to editsemiprotected for BC
2617  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2618  $semi[$key] = 'editsemiprotected';
2619  }
2620  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2621  $restrictions[$key] = 'editsemiprotected';
2622  }
2623 
2624  return !array_diff( $restrictions, $semi );
2625  }
2626 
2634  public function isProtected( $action = '' ) {
2635  global $wgRestrictionLevels;
2636 
2637  $restrictionTypes = $this->getRestrictionTypes();
2638 
2639  # Special pages have inherent protection
2640  if ( $this->isSpecialPage() ) {
2641  return true;
2642  }
2643 
2644  # Check regular protection levels
2645  foreach ( $restrictionTypes as $type ) {
2646  if ( $action == $type || $action == '' ) {
2647  $r = $this->getRestrictions( $type );
2648  foreach ( $wgRestrictionLevels as $level ) {
2649  if ( in_array( $level, $r ) && $level != '' ) {
2650  return true;
2651  }
2652  }
2653  }
2654  }
2655 
2656  return false;
2657  }
2658 
2666  public function isNamespaceProtected( User $user ) {
2668 
2669  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2670  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2671  if ( $right != '' && !$user->isAllowed( $right ) ) {
2672  return true;
2673  }
2674  }
2675  }
2676  return false;
2677  }
2678 
2684  public function isCascadeProtected() {
2685  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2686  return ( $sources > 0 );
2687  }
2688 
2698  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2699  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2700  }
2701 
2715  public function getCascadeProtectionSources( $getPages = true ) {
2716  $pagerestrictions = [];
2717 
2718  if ( $this->mCascadeSources !== null && $getPages ) {
2720  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2721  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2722  }
2723 
2724  $dbr = wfGetDB( DB_SLAVE );
2725 
2726  if ( $this->getNamespace() == NS_FILE ) {
2727  $tables = [ 'imagelinks', 'page_restrictions' ];
2728  $where_clauses = [
2729  'il_to' => $this->getDBkey(),
2730  'il_from=pr_page',
2731  'pr_cascade' => 1
2732  ];
2733  } else {
2734  $tables = [ 'templatelinks', 'page_restrictions' ];
2735  $where_clauses = [
2736  'tl_namespace' => $this->getNamespace(),
2737  'tl_title' => $this->getDBkey(),
2738  'tl_from=pr_page',
2739  'pr_cascade' => 1
2740  ];
2741  }
2742 
2743  if ( $getPages ) {
2744  $cols = [ 'pr_page', 'page_namespace', 'page_title',
2745  'pr_expiry', 'pr_type', 'pr_level' ];
2746  $where_clauses[] = 'page_id=pr_page';
2747  $tables[] = 'page';
2748  } else {
2749  $cols = [ 'pr_expiry' ];
2750  }
2751 
2752  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2753 
2754  $sources = $getPages ? [] : false;
2755  $now = wfTimestampNow();
2756 
2757  foreach ( $res as $row ) {
2758  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2759  if ( $expiry > $now ) {
2760  if ( $getPages ) {
2761  $page_id = $row->pr_page;
2762  $page_ns = $row->page_namespace;
2763  $page_title = $row->page_title;
2764  $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2765  # Add groups needed for each restriction type if its not already there
2766  # Make sure this restriction type still exists
2767 
2768  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2769  $pagerestrictions[$row->pr_type] = [];
2770  }
2771 
2772  if (
2773  isset( $pagerestrictions[$row->pr_type] )
2774  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2775  ) {
2776  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2777  }
2778  } else {
2779  $sources = true;
2780  }
2781  }
2782  }
2783 
2784  if ( $getPages ) {
2785  $this->mCascadeSources = $sources;
2786  $this->mCascadingRestrictions = $pagerestrictions;
2787  } else {
2788  $this->mHasCascadingRestrictions = $sources;
2789  }
2790 
2791  return [ $sources, $pagerestrictions ];
2792  }
2793 
2801  public function areRestrictionsLoaded() {
2803  }
2804 
2814  public function getRestrictions( $action ) {
2815  if ( !$this->mRestrictionsLoaded ) {
2816  $this->loadRestrictions();
2817  }
2818  return isset( $this->mRestrictions[$action] )
2819  ? $this->mRestrictions[$action]
2820  : [];
2821  }
2822 
2830  public function getAllRestrictions() {
2831  if ( !$this->mRestrictionsLoaded ) {
2832  $this->loadRestrictions();
2833  }
2834  return $this->mRestrictions;
2835  }
2836 
2844  public function getRestrictionExpiry( $action ) {
2845  if ( !$this->mRestrictionsLoaded ) {
2846  $this->loadRestrictions();
2847  }
2848  return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2849  }
2850 
2857  if ( !$this->mRestrictionsLoaded ) {
2858  $this->loadRestrictions();
2859  }
2860 
2862  }
2863 
2871  private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
2872  $rows = [];
2873 
2874  foreach ( $res as $row ) {
2875  $rows[] = $row;
2876  }
2877 
2878  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2879  }
2880 
2890  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2891  $dbr = wfGetDB( DB_SLAVE );
2892 
2893  $restrictionTypes = $this->getRestrictionTypes();
2894 
2895  foreach ( $restrictionTypes as $type ) {
2896  $this->mRestrictions[$type] = [];
2897  $this->mRestrictionsExpiry[$type] = 'infinity';
2898  }
2899 
2900  $this->mCascadeRestriction = false;
2901 
2902  # Backwards-compatibility: also load the restrictions from the page record (old format).
2903  if ( $oldFashionedRestrictions !== null ) {
2904  $this->mOldRestrictions = $oldFashionedRestrictions;
2905  }
2906 
2907  if ( $this->mOldRestrictions === false ) {
2908  $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2909  [ 'page_id' => $this->getArticleID() ], __METHOD__ );
2910  }
2911 
2912  if ( $this->mOldRestrictions != '' ) {
2913  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2914  $temp = explode( '=', trim( $restrict ) );
2915  if ( count( $temp ) == 1 ) {
2916  // old old format should be treated as edit/move restriction
2917  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2918  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2919  } else {
2920  $restriction = trim( $temp[1] );
2921  if ( $restriction != '' ) { // some old entries are empty
2922  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2923  }
2924  }
2925  }
2926  }
2927 
2928  if ( count( $rows ) ) {
2929  # Current system - load second to make them override.
2930  $now = wfTimestampNow();
2931 
2932  # Cycle through all the restrictions.
2933  foreach ( $rows as $row ) {
2934 
2935  // Don't take care of restrictions types that aren't allowed
2936  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2937  continue;
2938  }
2939 
2940  // This code should be refactored, now that it's being used more generally,
2941  // But I don't really see any harm in leaving it in Block for now -werdna
2942  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2943 
2944  // Only apply the restrictions if they haven't expired!
2945  if ( !$expiry || $expiry > $now ) {
2946  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2947  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2948 
2949  $this->mCascadeRestriction |= $row->pr_cascade;
2950  }
2951  }
2952  }
2953 
2954  $this->mRestrictionsLoaded = true;
2955  }
2956 
2963  public function loadRestrictions( $oldFashionedRestrictions = null ) {
2964  if ( !$this->mRestrictionsLoaded ) {
2965  $dbr = wfGetDB( DB_SLAVE );
2966  if ( $this->exists() ) {
2967  $res = $dbr->select(
2968  'page_restrictions',
2969  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
2970  [ 'pr_page' => $this->getArticleID() ],
2971  __METHOD__
2972  );
2973 
2974  $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
2975  } else {
2976  $title_protection = $this->getTitleProtection();
2977 
2978  if ( $title_protection ) {
2979  $now = wfTimestampNow();
2980  $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
2981 
2982  if ( !$expiry || $expiry > $now ) {
2983  // Apply the restrictions
2984  $this->mRestrictionsExpiry['create'] = $expiry;
2985  $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
2986  } else { // Get rid of the old restrictions
2987  $this->mTitleProtection = false;
2988  }
2989  } else {
2990  $this->mRestrictionsExpiry['create'] = 'infinity';
2991  }
2992  $this->mRestrictionsLoaded = true;
2993  }
2994  }
2995  }
2996 
3001  public function flushRestrictions() {
3002  $this->mRestrictionsLoaded = false;
3003  $this->mTitleProtection = null;
3004  }
3005 
3009  static function purgeExpiredRestrictions() {
3010  if ( wfReadOnly() ) {
3011  return;
3012  }
3013 
3015  wfGetDB( DB_MASTER ),
3016  __METHOD__,
3017  function ( IDatabase $dbw, $fname ) {
3018  $dbw->delete(
3019  'page_restrictions',
3020  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3021  $fname
3022  );
3023  $dbw->delete(
3024  'protected_titles',
3025  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3026  $fname
3027  );
3028  }
3029  ) );
3030  }
3031 
3037  public function hasSubpages() {
3038  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3039  # Duh
3040  return false;
3041  }
3042 
3043  # We dynamically add a member variable for the purpose of this method
3044  # alone to cache the result. There's no point in having it hanging
3045  # around uninitialized in every Title object; therefore we only add it
3046  # if needed and don't declare it statically.
3047  if ( $this->mHasSubpages === null ) {
3048  $this->mHasSubpages = false;
3049  $subpages = $this->getSubpages( 1 );
3050  if ( $subpages instanceof TitleArray ) {
3051  $this->mHasSubpages = (bool)$subpages->count();
3052  }
3053  }
3054 
3055  return $this->mHasSubpages;
3056  }
3057 
3065  public function getSubpages( $limit = -1 ) {
3066  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3067  return [];
3068  }
3069 
3070  $dbr = wfGetDB( DB_SLAVE );
3071  $conds['page_namespace'] = $this->getNamespace();
3072  $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3073  $options = [];
3074  if ( $limit > -1 ) {
3075  $options['LIMIT'] = $limit;
3076  }
3077  $this->mSubpages = TitleArray::newFromResult(
3078  $dbr->select( 'page',
3079  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3080  $conds,
3081  __METHOD__,
3082  $options
3083  )
3084  );
3085  return $this->mSubpages;
3086  }
3087 
3093  public function isDeleted() {
3094  if ( $this->getNamespace() < 0 ) {
3095  $n = 0;
3096  } else {
3097  $dbr = wfGetDB( DB_SLAVE );
3098 
3099  $n = $dbr->selectField( 'archive', 'COUNT(*)',
3100  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3101  __METHOD__
3102  );
3103  if ( $this->getNamespace() == NS_FILE ) {
3104  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3105  [ 'fa_name' => $this->getDBkey() ],
3106  __METHOD__
3107  );
3108  }
3109  }
3110  return (int)$n;
3111  }
3112 
3118  public function isDeletedQuick() {
3119  if ( $this->getNamespace() < 0 ) {
3120  return false;
3121  }
3122  $dbr = wfGetDB( DB_SLAVE );
3123  $deleted = (bool)$dbr->selectField( 'archive', '1',
3124  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3125  __METHOD__
3126  );
3127  if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3128  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3129  [ 'fa_name' => $this->getDBkey() ],
3130  __METHOD__
3131  );
3132  }
3133  return $deleted;
3134  }
3135 
3144  public function getArticleID( $flags = 0 ) {
3145  if ( $this->getNamespace() < 0 ) {
3146  $this->mArticleID = 0;
3147  return $this->mArticleID;
3148  }
3149  $linkCache = LinkCache::singleton();
3150  if ( $flags & self::GAID_FOR_UPDATE ) {
3151  $oldUpdate = $linkCache->forUpdate( true );
3152  $linkCache->clearLink( $this );
3153  $this->mArticleID = $linkCache->addLinkObj( $this );
3154  $linkCache->forUpdate( $oldUpdate );
3155  } else {
3156  if ( -1 == $this->mArticleID ) {
3157  $this->mArticleID = $linkCache->addLinkObj( $this );
3158  }
3159  }
3160  return $this->mArticleID;
3161  }
3162 
3170  public function isRedirect( $flags = 0 ) {
3171  if ( !is_null( $this->mRedirect ) ) {
3172  return $this->mRedirect;
3173  }
3174  if ( !$this->getArticleID( $flags ) ) {
3175  $this->mRedirect = false;
3176  return $this->mRedirect;
3177  }
3178 
3179  $linkCache = LinkCache::singleton();
3180  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3181  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3182  if ( $cached === null ) {
3183  # Trust LinkCache's state over our own
3184  # LinkCache is telling us that the page doesn't exist, despite there being cached
3185  # data relating to an existing page in $this->mArticleID. Updaters should clear
3186  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3187  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3188  # LinkCache to refresh its data from the master.
3189  $this->mRedirect = false;
3190  return $this->mRedirect;
3191  }
3192 
3193  $this->mRedirect = (bool)$cached;
3194 
3195  return $this->mRedirect;
3196  }
3197 
3205  public function getLength( $flags = 0 ) {
3206  if ( $this->mLength != -1 ) {
3207  return $this->mLength;
3208  }
3209  if ( !$this->getArticleID( $flags ) ) {
3210  $this->mLength = 0;
3211  return $this->mLength;
3212  }
3213  $linkCache = LinkCache::singleton();
3214  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3215  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3216  if ( $cached === null ) {
3217  # Trust LinkCache's state over our own, as for isRedirect()
3218  $this->mLength = 0;
3219  return $this->mLength;
3220  }
3221 
3222  $this->mLength = intval( $cached );
3223 
3224  return $this->mLength;
3225  }
3226 
3233  public function getLatestRevID( $flags = 0 ) {
3234  if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3235  return intval( $this->mLatestID );
3236  }
3237  if ( !$this->getArticleID( $flags ) ) {
3238  $this->mLatestID = 0;
3239  return $this->mLatestID;
3240  }
3241  $linkCache = LinkCache::singleton();
3242  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3243  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3244  if ( $cached === null ) {
3245  # Trust LinkCache's state over our own, as for isRedirect()
3246  $this->mLatestID = 0;
3247  return $this->mLatestID;
3248  }
3249 
3250  $this->mLatestID = intval( $cached );
3251 
3252  return $this->mLatestID;
3253  }
3254 
3265  public function resetArticleID( $newid ) {
3266  $linkCache = LinkCache::singleton();
3267  $linkCache->clearLink( $this );
3268 
3269  if ( $newid === false ) {
3270  $this->mArticleID = -1;
3271  } else {
3272  $this->mArticleID = intval( $newid );
3273  }
3274  $this->mRestrictionsLoaded = false;
3275  $this->mRestrictions = [];
3276  $this->mOldRestrictions = false;
3277  $this->mRedirect = null;
3278  $this->mLength = -1;
3279  $this->mLatestID = false;
3280  $this->mContentModel = false;
3281  $this->mEstimateRevisions = null;
3282  $this->mPageLanguage = false;
3283  $this->mDbPageLanguage = false;
3284  $this->mIsBigDeletion = null;
3285  }
3286 
3287  public static function clearCaches() {
3288  $linkCache = LinkCache::singleton();
3289  $linkCache->clear();
3290 
3291  $titleCache = self::getTitleCache();
3292  $titleCache->clear();
3293  }
3294 
3302  public static function capitalize( $text, $ns = NS_MAIN ) {
3304 
3305  if ( MWNamespace::isCapitalized( $ns ) ) {
3306  return $wgContLang->ucfirst( $text );
3307  } else {
3308  return $text;
3309  }
3310  }
3311 
3324  private function secureAndSplit() {
3325  # Initialisation
3326  $this->mInterwiki = '';
3327  $this->mFragment = '';
3328  $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3329 
3330  $dbkey = $this->mDbkeyform;
3331 
3332  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3333  // the parsing code with Title, while avoiding massive refactoring.
3334  // @todo: get rid of secureAndSplit, refactor parsing code.
3335  $titleParser = self::getMediaWikiTitleCodec();
3336  // MalformedTitleException can be thrown here
3337  $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3338 
3339  # Fill fields
3340  $this->setFragment( '#' . $parts['fragment'] );
3341  $this->mInterwiki = $parts['interwiki'];
3342  $this->mLocalInterwiki = $parts['local_interwiki'];
3343  $this->mNamespace = $parts['namespace'];
3344  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3345 
3346  $this->mDbkeyform = $parts['dbkey'];
3347  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3348  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3349 
3350  # We already know that some pages won't be in the database!
3351  if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3352  $this->mArticleID = 0;
3353  }
3354 
3355  return true;
3356  }
3357 
3370  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3371  if ( count( $options ) > 0 ) {
3372  $db = wfGetDB( DB_MASTER );
3373  } else {
3374  $db = wfGetDB( DB_SLAVE );
3375  }
3376 
3377  $res = $db->select(
3378  [ 'page', $table ],
3379  self::getSelectFields(),
3380  [
3381  "{$prefix}_from=page_id",
3382  "{$prefix}_namespace" => $this->getNamespace(),
3383  "{$prefix}_title" => $this->getDBkey() ],
3384  __METHOD__,
3385  $options
3386  );
3387 
3388  $retVal = [];
3389  if ( $res->numRows() ) {
3390  $linkCache = LinkCache::singleton();
3391  foreach ( $res as $row ) {
3392  $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3393  if ( $titleObj ) {
3394  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3395  $retVal[] = $titleObj;
3396  }
3397  }
3398  }
3399  return $retVal;
3400  }
3401 
3412  public function getTemplateLinksTo( $options = [] ) {
3413  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3414  }
3415 
3428  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3429  $id = $this->getArticleID();
3430 
3431  # If the page doesn't exist; there can't be any link from this page
3432  if ( !$id ) {
3433  return [];
3434  }
3435 
3436  $db = wfGetDB( DB_SLAVE );
3437 
3438  $blNamespace = "{$prefix}_namespace";
3439  $blTitle = "{$prefix}_title";
3440 
3441  $res = $db->select(
3442  [ $table, 'page' ],
3443  array_merge(
3444  [ $blNamespace, $blTitle ],
3446  ),
3447  [ "{$prefix}_from" => $id ],
3448  __METHOD__,
3449  $options,
3450  [ 'page' => [
3451  'LEFT JOIN',
3452  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3453  ] ]
3454  );
3455 
3456  $retVal = [];
3457  $linkCache = LinkCache::singleton();
3458  foreach ( $res as $row ) {
3459  if ( $row->page_id ) {
3460  $titleObj = Title::newFromRow( $row );
3461  } else {
3462  $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3463  $linkCache->addBadLinkObj( $titleObj );
3464  }
3465  $retVal[] = $titleObj;
3466  }
3467 
3468  return $retVal;
3469  }
3470 
3481  public function getTemplateLinksFrom( $options = [] ) {
3482  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3483  }
3484 
3493  public function getBrokenLinksFrom() {
3494  if ( $this->getArticleID() == 0 ) {
3495  # All links from article ID 0 are false positives
3496  return [];
3497  }
3498 
3499  $dbr = wfGetDB( DB_SLAVE );
3500  $res = $dbr->select(
3501  [ 'page', 'pagelinks' ],
3502  [ 'pl_namespace', 'pl_title' ],
3503  [
3504  'pl_from' => $this->getArticleID(),
3505  'page_namespace IS NULL'
3506  ],
3507  __METHOD__, [],
3508  [
3509  'page' => [
3510  'LEFT JOIN',
3511  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3512  ]
3513  ]
3514  );
3515 
3516  $retVal = [];
3517  foreach ( $res as $row ) {
3518  $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3519  }
3520  return $retVal;
3521  }
3522 
3529  public function getCdnUrls() {
3530  $urls = [
3531  $this->getInternalURL(),
3532  $this->getInternalURL( 'action=history' )
3533  ];
3534 
3535  $pageLang = $this->getPageLanguage();
3536  if ( $pageLang->hasVariants() ) {
3537  $variants = $pageLang->getVariants();
3538  foreach ( $variants as $vCode ) {
3539  $urls[] = $this->getInternalURL( $vCode );
3540  }
3541  }
3542 
3543  // If we are looking at a css/js user subpage, purge the action=raw.
3544  if ( $this->isJsSubpage() ) {
3545  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3546  } elseif ( $this->isCssSubpage() ) {
3547  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3548  }
3549 
3550  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3551  return $urls;
3552  }
3553 
3557  public function getSquidURLs() {
3558  return $this->getCdnUrls();
3559  }
3560 
3564  public function purgeSquid() {
3566  new CdnCacheUpdate( $this->getCdnUrls() ),
3568  );
3569  }
3570 
3578  public function moveNoAuth( &$nt ) {
3579  wfDeprecated( __METHOD__, '1.25' );
3580  return $this->moveTo( $nt, false );
3581  }
3582 
3593  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3594  global $wgUser;
3595 
3596  if ( !( $nt instanceof Title ) ) {
3597  // Normally we'd add this to $errors, but we'll get
3598  // lots of syntax errors if $nt is not an object
3599  return [ [ 'badtitletext' ] ];
3600  }
3601 
3602  $mp = new MovePage( $this, $nt );
3603  $errors = $mp->isValidMove()->getErrorsArray();
3604  if ( $auth ) {
3605  $errors = wfMergeErrorArrays(
3606  $errors,
3607  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3608  );
3609  }
3610 
3611  return $errors ?: true;
3612  }
3613 
3620  protected function validateFileMoveOperation( $nt ) {
3621  global $wgUser;
3622 
3623  $errors = [];
3624 
3625  $destFile = wfLocalFile( $nt );
3626  $destFile->load( File::READ_LATEST );
3627  if ( !$wgUser->isAllowed( 'reupload-shared' )
3628  && !$destFile->exists() && wfFindFile( $nt )
3629  ) {
3630  $errors[] = [ 'file-exists-sharedrepo' ];
3631  }
3632 
3633  return $errors;
3634  }
3635 
3648  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
3649  global $wgUser;
3650  $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3651  if ( is_array( $err ) ) {
3652  // Auto-block user's IP if the account was "hard" blocked
3653  $wgUser->spreadAnyEditBlock();
3654  return $err;
3655  }
3656  // Check suppressredirect permission
3657  if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3658  $createRedirect = true;
3659  }
3660 
3661  $mp = new MovePage( $this, $nt );
3662  $status = $mp->move( $wgUser, $reason, $createRedirect );
3663  if ( $status->isOK() ) {
3664  return true;
3665  } else {
3666  return $status->getErrorsArray();
3667  }
3668  }
3669 
3682  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
3683  global $wgMaximumMovedPages;
3684  // Check permissions
3685  if ( !$this->userCan( 'move-subpages' ) ) {
3686  return [ 'cant-move-subpages' ];
3687  }
3688  // Do the source and target namespaces support subpages?
3689  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3690  return [ 'namespace-nosubpages',
3692  }
3693  if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3694  return [ 'namespace-nosubpages',
3695  MWNamespace::getCanonicalName( $nt->getNamespace() ) ];
3696  }
3697 
3698  $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3699  $retval = [];
3700  $count = 0;
3701  foreach ( $subpages as $oldSubpage ) {
3702  $count++;
3703  if ( $count > $wgMaximumMovedPages ) {
3704  $retval[$oldSubpage->getPrefixedText()] =
3705  [ 'movepage-max-pages',
3706  $wgMaximumMovedPages ];
3707  break;
3708  }
3709 
3710  // We don't know whether this function was called before
3711  // or after moving the root page, so check both
3712  // $this and $nt
3713  if ( $oldSubpage->getArticleID() == $this->getArticleID()
3714  || $oldSubpage->getArticleID() == $nt->getArticleID()
3715  ) {
3716  // When moving a page to a subpage of itself,
3717  // don't move it twice
3718  continue;
3719  }
3720  $newPageName = preg_replace(
3721  '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3722  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
3723  $oldSubpage->getDBkey() );
3724  if ( $oldSubpage->isTalkPage() ) {
3725  $newNs = $nt->getTalkPage()->getNamespace();
3726  } else {
3727  $newNs = $nt->getSubjectPage()->getNamespace();
3728  }
3729  # Bug 14385: we need makeTitleSafe because the new page names may
3730  # be longer than 255 characters.
3731  $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3732 
3733  $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
3734  if ( $success === true ) {
3735  $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3736  } else {
3737  $retval[$oldSubpage->getPrefixedText()] = $success;
3738  }
3739  }
3740  return $retval;
3741  }
3742 
3749  public function isSingleRevRedirect() {
3750  global $wgContentHandlerUseDB;
3751 
3752  $dbw = wfGetDB( DB_MASTER );
3753 
3754  # Is it a redirect?
3755  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3756  if ( $wgContentHandlerUseDB ) {
3757  $fields[] = 'page_content_model';
3758  }
3759 
3760  $row = $dbw->selectRow( 'page',
3761  $fields,
3762  $this->pageCond(),
3763  __METHOD__,
3764  [ 'FOR UPDATE' ]
3765  );
3766  # Cache some fields we may want
3767  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3768  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3769  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3770  $this->mContentModel = $row && isset( $row->page_content_model )
3771  ? strval( $row->page_content_model )
3772  : false;
3773 
3774  if ( !$this->mRedirect ) {
3775  return false;
3776  }
3777  # Does the article have a history?
3778  $row = $dbw->selectField( [ 'page', 'revision' ],
3779  'rev_id',
3780  [ 'page_namespace' => $this->getNamespace(),
3781  'page_title' => $this->getDBkey(),
3782  'page_id=rev_page',
3783  'page_latest != rev_id'
3784  ],
3785  __METHOD__,
3786  [ 'FOR UPDATE' ]
3787  );
3788  # Return true if there was no history
3789  return ( $row === false );
3790  }
3791 
3800  public function isValidMoveTarget( $nt ) {
3801  # Is it an existing file?
3802  if ( $nt->getNamespace() == NS_FILE ) {
3803  $file = wfLocalFile( $nt );
3804  $file->load( File::READ_LATEST );
3805  if ( $file->exists() ) {
3806  wfDebug( __METHOD__ . ": file exists\n" );
3807  return false;
3808  }
3809  }
3810  # Is it a redirect with no history?
3811  if ( !$nt->isSingleRevRedirect() ) {
3812  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3813  return false;
3814  }
3815  # Get the article text
3817  if ( !is_object( $rev ) ) {
3818  return false;
3819  }
3820  $content = $rev->getContent();
3821  # Does the redirect point to the source?
3822  # Or is it a broken self-redirect, usually caused by namespace collisions?
3823  $redirTitle = $content ? $content->getRedirectTarget() : null;
3824 
3825  if ( $redirTitle ) {
3826  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3827  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3828  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3829  return false;
3830  } else {
3831  return true;
3832  }
3833  } else {
3834  # Fail safe (not a redirect after all. strange.)
3835  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3836  " is a redirect, but it doesn't contain a valid redirect.\n" );
3837  return false;
3838  }
3839  }
3840 
3848  public function getParentCategories() {
3850 
3851  $data = [];
3852 
3853  $titleKey = $this->getArticleID();
3854 
3855  if ( $titleKey === 0 ) {
3856  return $data;
3857  }
3858 
3859  $dbr = wfGetDB( DB_SLAVE );
3860 
3861  $res = $dbr->select(
3862  'categorylinks',
3863  'cl_to',
3864  [ 'cl_from' => $titleKey ],
3865  __METHOD__
3866  );
3867 
3868  if ( $res->numRows() > 0 ) {
3869  foreach ( $res as $row ) {
3870  // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3871  $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3872  }
3873  }
3874  return $data;
3875  }
3876 
3883  public function getParentCategoryTree( $children = [] ) {
3884  $stack = [];
3885  $parents = $this->getParentCategories();
3886 
3887  if ( $parents ) {
3888  foreach ( $parents as $parent => $current ) {
3889  if ( array_key_exists( $parent, $children ) ) {
3890  # Circular reference
3891  $stack[$parent] = [];
3892  } else {
3893  $nt = Title::newFromText( $parent );
3894  if ( $nt ) {
3895  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3896  }
3897  }
3898  }
3899  }
3900 
3901  return $stack;
3902  }
3903 
3910  public function pageCond() {
3911  if ( $this->mArticleID > 0 ) {
3912  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3913  return [ 'page_id' => $this->mArticleID ];
3914  } else {
3915  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3916  }
3917  }
3918 
3926  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3927  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3928  $revId = $db->selectField( 'revision', 'rev_id',
3929  [
3930  'rev_page' => $this->getArticleID( $flags ),
3931  'rev_id < ' . intval( $revId )
3932  ],
3933  __METHOD__,
3934  [ 'ORDER BY' => 'rev_id DESC' ]
3935  );
3936 
3937  if ( $revId === false ) {
3938  return false;
3939  } else {
3940  return intval( $revId );
3941  }
3942  }
3943 
3951  public function getNextRevisionID( $revId, $flags = 0 ) {
3952  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3953  $revId = $db->selectField( 'revision', 'rev_id',
3954  [
3955  'rev_page' => $this->getArticleID( $flags ),
3956  'rev_id > ' . intval( $revId )
3957  ],
3958  __METHOD__,
3959  [ 'ORDER BY' => 'rev_id' ]
3960  );
3961 
3962  if ( $revId === false ) {
3963  return false;
3964  } else {
3965  return intval( $revId );
3966  }
3967  }
3968 
3975  public function getFirstRevision( $flags = 0 ) {
3976  $pageId = $this->getArticleID( $flags );
3977  if ( $pageId ) {
3978  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3979  $row = $db->selectRow( 'revision', Revision::selectFields(),
3980  [ 'rev_page' => $pageId ],
3981  __METHOD__,
3982  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ]
3983  );
3984  if ( $row ) {
3985  return new Revision( $row );
3986  }
3987  }
3988  return null;
3989  }
3990 
3997  public function getEarliestRevTime( $flags = 0 ) {
3998  $rev = $this->getFirstRevision( $flags );
3999  return $rev ? $rev->getTimestamp() : null;
4000  }
4001 
4007  public function isNewPage() {
4008  $dbr = wfGetDB( DB_SLAVE );
4009  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4010  }
4011 
4017  public function isBigDeletion() {
4018  global $wgDeleteRevisionsLimit;
4019 
4020  if ( !$wgDeleteRevisionsLimit ) {
4021  return false;
4022  }
4023 
4024  if ( $this->mIsBigDeletion === null ) {
4025  $dbr = wfGetDB( DB_SLAVE );
4026 
4027  $revCount = $dbr->selectRowCount(
4028  'revision',
4029  '1',
4030  [ 'rev_page' => $this->getArticleID() ],
4031  __METHOD__,
4032  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4033  );
4034 
4035  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4036  }
4037 
4038  return $this->mIsBigDeletion;
4039  }
4040 
4046  public function estimateRevisionCount() {
4047  if ( !$this->exists() ) {
4048  return 0;
4049  }
4050 
4051  if ( $this->mEstimateRevisions === null ) {
4052  $dbr = wfGetDB( DB_SLAVE );
4053  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4054  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4055  }
4056 
4058  }
4059 
4069  public function countRevisionsBetween( $old, $new, $max = null ) {
4070  if ( !( $old instanceof Revision ) ) {
4071  $old = Revision::newFromTitle( $this, (int)$old );
4072  }
4073  if ( !( $new instanceof Revision ) ) {
4074  $new = Revision::newFromTitle( $this, (int)$new );
4075  }
4076  if ( !$old || !$new ) {
4077  return 0; // nothing to compare
4078  }
4079  $dbr = wfGetDB( DB_SLAVE );
4080  $conds = [
4081  'rev_page' => $this->getArticleID(),
4082  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4083  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4084  ];
4085  if ( $max !== null ) {
4086  return $dbr->selectRowCount( 'revision', '1',
4087  $conds,
4088  __METHOD__,
4089  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4090  );
4091  } else {
4092  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4093  }
4094  }
4095 
4112  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4113  if ( !( $old instanceof Revision ) ) {
4114  $old = Revision::newFromTitle( $this, (int)$old );
4115  }
4116  if ( !( $new instanceof Revision ) ) {
4117  $new = Revision::newFromTitle( $this, (int)$new );
4118  }
4119  // XXX: what if Revision objects are passed in, but they don't refer to this title?
4120  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4121  // in the sanity check below?
4122  if ( !$old || !$new ) {
4123  return null; // nothing to compare
4124  }
4125  $authors = [];
4126  $old_cmp = '>';
4127  $new_cmp = '<';
4128  $options = (array)$options;
4129  if ( in_array( 'include_old', $options ) ) {
4130  $old_cmp = '>=';
4131  }
4132  if ( in_array( 'include_new', $options ) ) {
4133  $new_cmp = '<=';
4134  }
4135  if ( in_array( 'include_both', $options ) ) {
4136  $old_cmp = '>=';
4137  $new_cmp = '<=';
4138  }
4139  // No DB query needed if $old and $new are the same or successive revisions:
4140  if ( $old->getId() === $new->getId() ) {
4141  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4142  [] :
4143  [ $old->getUserText( Revision::RAW ) ];
4144  } elseif ( $old->getId() === $new->getParentId() ) {
4145  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4146  $authors[] = $old->getUserText( Revision::RAW );
4147  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4148  $authors[] = $new->getUserText( Revision::RAW );
4149  }
4150  } elseif ( $old_cmp === '>=' ) {
4151  $authors[] = $old->getUserText( Revision::RAW );
4152  } elseif ( $new_cmp === '<=' ) {
4153  $authors[] = $new->getUserText( Revision::RAW );
4154  }
4155  return $authors;
4156  }
4157  $dbr = wfGetDB( DB_SLAVE );
4158  $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4159  [
4160  'rev_page' => $this->getArticleID(),
4161  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4162  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4163  ], __METHOD__,
4164  [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
4165  );
4166  foreach ( $res as $row ) {
4167  $authors[] = $row->rev_user_text;
4168  }
4169  return $authors;
4170  }
4171 
4186  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4187  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4188  return $authors ? count( $authors ) : 0;
4189  }
4190 
4197  public function equals( Title $title ) {
4198  // Note: === is necessary for proper matching of number-like titles.
4199  return $this->getInterwiki() === $title->getInterwiki()
4200  && $this->getNamespace() == $title->getNamespace()
4201  && $this->getDBkey() === $title->getDBkey();
4202  }
4203 
4210  public function isSubpageOf( Title $title ) {
4211  return $this->getInterwiki() === $title->getInterwiki()
4212  && $this->getNamespace() == $title->getNamespace()
4213  && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4214  }
4215 
4227  public function exists( $flags = 0 ) {
4228  $exists = $this->getArticleID( $flags ) != 0;
4229  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4230  return $exists;
4231  }
4232 
4249  public function isAlwaysKnown() {
4250  $isKnown = null;
4251 
4262  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4263 
4264  if ( !is_null( $isKnown ) ) {
4265  return $isKnown;
4266  }
4267 
4268  if ( $this->isExternal() ) {
4269  return true; // any interwiki link might be viewable, for all we know
4270  }
4271 
4272  switch ( $this->mNamespace ) {
4273  case NS_MEDIA:
4274  case NS_FILE:
4275  // file exists, possibly in a foreign repo
4276  return (bool)wfFindFile( $this );
4277  case NS_SPECIAL:
4278  // valid special page
4279  return SpecialPageFactory::exists( $this->getDBkey() );
4280  case NS_MAIN:
4281  // selflink, possibly with fragment
4282  return $this->mDbkeyform == '';
4283  case NS_MEDIAWIKI:
4284  // known system message
4285  return $this->hasSourceText() !== false;
4286  default:
4287  return false;
4288  }
4289  }
4290 
4302  public function isKnown() {
4303  return $this->isAlwaysKnown() || $this->exists();
4304  }
4305 
4311  public function hasSourceText() {
4312  if ( $this->exists() ) {
4313  return true;
4314  }
4315 
4316  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4317  // If the page doesn't exist but is a known system message, default
4318  // message content will be displayed, same for language subpages-
4319  // Use always content language to avoid loading hundreds of languages
4320  // to get the link color.
4322  list( $name, ) = MessageCache::singleton()->figureMessage(
4323  $wgContLang->lcfirst( $this->getText() )
4324  );
4325  $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4326  return $message->exists();
4327  }
4328 
4329  return false;
4330  }
4331 
4337  public function getDefaultMessageText() {
4339 
4340  if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4341  return false;
4342  }
4343 
4344  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4345  $wgContLang->lcfirst( $this->getText() )
4346  );
4347  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4348 
4349  if ( $message->exists() ) {
4350  return $message->plain();
4351  } else {
4352  return false;
4353  }
4354  }
4355 
4362  public function invalidateCache( $purgeTime = null ) {
4363  if ( wfReadOnly() ) {
4364  return false;
4365  }
4366 
4367  if ( $this->mArticleID === 0 ) {
4368  return true; // avoid gap locking if we know it's not there
4369  }
4370 
4371  $method = __METHOD__;
4372  $dbw = wfGetDB( DB_MASTER );
4373  $conds = $this->pageCond();
4374  $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method, $purgeTime ) {
4375  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4376 
4377  $dbw->update(
4378  'page',
4379  [ 'page_touched' => $dbTimestamp ],
4380  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4381  $method
4382  );
4383  } );
4384 
4385  return true;
4386  }
4387 
4393  public function touchLinks() {
4394  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4395  if ( $this->getNamespace() == NS_CATEGORY ) {
4396  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4397  }
4398  }
4399 
4406  public function getTouched( $db = null ) {
4407  if ( $db === null ) {
4408  $db = wfGetDB( DB_SLAVE );
4409  }
4410  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4411  return $touched;
4412  }
4413 
4420  public function getNotificationTimestamp( $user = null ) {
4421  global $wgUser;
4422 
4423  // Assume current user if none given
4424  if ( !$user ) {
4425  $user = $wgUser;
4426  }
4427  // Check cache first
4428  $uid = $user->getId();
4429  if ( !$uid ) {
4430  return false;
4431  }
4432  // avoid isset here, as it'll return false for null entries
4433  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4434  return $this->mNotificationTimestamp[$uid];
4435  }
4436  // Don't cache too much!
4437  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4438  $this->mNotificationTimestamp = [];
4439  }
4440 
4441  $watchedItem = WatchedItemStore::getDefaultInstance()->getWatchedItem( $user, $this );
4442  if ( $watchedItem ) {
4443  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4444  } else {
4445  $this->mNotificationTimestamp[$uid] = false;
4446  }
4447 
4448  return $this->mNotificationTimestamp[$uid];
4449  }
4450 
4457  public function getNamespaceKey( $prepend = 'nstab-' ) {
4459  // Gets the subject namespace if this title
4460  $namespace = MWNamespace::getSubject( $this->getNamespace() );
4461  // Checks if canonical namespace name exists for namespace
4462  if ( MWNamespace::exists( $this->getNamespace() ) ) {
4463  // Uses canonical namespace name
4464  $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4465  } else {
4466  // Uses text of namespace
4467  $namespaceKey = $this->getSubjectNsText();
4468  }
4469  // Makes namespace key lowercase
4470  $namespaceKey = $wgContLang->lc( $namespaceKey );
4471  // Uses main
4472  if ( $namespaceKey == '' ) {
4473  $namespaceKey = 'main';
4474  }
4475  // Changes file to image for backwards compatibility
4476  if ( $namespaceKey == 'file' ) {
4477  $namespaceKey = 'image';
4478  }
4479  return $prepend . $namespaceKey;
4480  }
4481 
4488  public function getRedirectsHere( $ns = null ) {
4489  $redirs = [];
4490 
4491  $dbr = wfGetDB( DB_SLAVE );
4492  $where = [
4493  'rd_namespace' => $this->getNamespace(),
4494  'rd_title' => $this->getDBkey(),
4495  'rd_from = page_id'
4496  ];
4497  if ( $this->isExternal() ) {
4498  $where['rd_interwiki'] = $this->getInterwiki();
4499  } else {
4500  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4501  }
4502  if ( !is_null( $ns ) ) {
4503  $where['page_namespace'] = $ns;
4504  }
4505 
4506  $res = $dbr->select(
4507  [ 'redirect', 'page' ],
4508  [ 'page_namespace', 'page_title' ],
4509  $where,
4510  __METHOD__
4511  );
4512 
4513  foreach ( $res as $row ) {
4514  $redirs[] = self::newFromRow( $row );
4515  }
4516  return $redirs;
4517  }
4518 
4524  public function isValidRedirectTarget() {
4525  global $wgInvalidRedirectTargets;
4526 
4527  if ( $this->isSpecialPage() ) {
4528  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4529  if ( $this->isSpecial( 'Userlogout' ) ) {
4530  return false;
4531  }
4532 
4533  foreach ( $wgInvalidRedirectTargets as $target ) {
4534  if ( $this->isSpecial( $target ) ) {
4535  return false;
4536  }
4537  }
4538  }
4539 
4540  return true;
4541  }
4542 
4548  public function getBacklinkCache() {
4549  return BacklinkCache::get( $this );
4550  }
4551 
4557  public function canUseNoindex() {
4558  global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
4559 
4560  $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4561  ? $wgContentNamespaces
4562  : $wgExemptFromUserRobotsControl;
4563 
4564  return !in_array( $this->mNamespace, $bannedNamespaces );
4565 
4566  }
4567 
4578  public function getCategorySortkey( $prefix = '' ) {
4579  $unprefixed = $this->getText();
4580 
4581  // Anything that uses this hook should only depend
4582  // on the Title object passed in, and should probably
4583  // tell the users to run updateCollations.php --force
4584  // in order to re-sort existing category relations.
4585  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4586  if ( $prefix !== '' ) {
4587  # Separate with a line feed, so the unprefixed part is only used as
4588  # a tiebreaker when two pages have the exact same prefix.
4589  # In UCA, tab is the only character that can sort above LF
4590  # so we strip both of them from the original prefix.
4591  $prefix = strtr( $prefix, "\n\t", ' ' );
4592  return "$prefix\n$unprefixed";
4593  }
4594  return $unprefixed;
4595  }
4596 
4604  private function getDbPageLanguageCode() {
4605  global $wgPageLanguageUseDB;
4606 
4607  // check, if the page language could be saved in the database, and if so and
4608  // the value is not requested already, lookup the page language using LinkCache
4609  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4610  $linkCache = LinkCache::singleton();
4611  $linkCache->addLinkObj( $this );
4612  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4613  }
4614 
4615  return $this->mDbPageLanguage;
4616  }
4617 
4626  public function getPageLanguage() {
4628  if ( $this->isSpecialPage() ) {
4629  // special pages are in the user language
4630  return $wgLang;
4631  }
4632 
4633  // Checking if DB language is set
4634  $dbPageLanguage = $this->getDbPageLanguageCode();
4635  if ( $dbPageLanguage ) {
4636  return wfGetLangObj( $dbPageLanguage );
4637  }
4638 
4639  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4640  // Note that this may depend on user settings, so the cache should
4641  // be only per-request.
4642  // NOTE: ContentHandler::getPageLanguage() may need to load the
4643  // content to determine the page language!
4644  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4645  // tests.
4646  $contentHandler = ContentHandler::getForTitle( $this );
4647  $langObj = $contentHandler->getPageLanguage( $this );
4648  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4649  } else {
4650  $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4651  }
4652 
4653  return $langObj;
4654  }
4655 
4664  public function getPageViewLanguage() {
4665  global $wgLang;
4666 
4667  if ( $this->isSpecialPage() ) {
4668  // If the user chooses a variant, the content is actually
4669  // in a language whose code is the variant code.
4670  $variant = $wgLang->getPreferredVariant();
4671  if ( $wgLang->getCode() !== $variant ) {
4672  return Language::factory( $variant );
4673  }
4674 
4675  return $wgLang;
4676  }
4677 
4678  // Checking if DB language is set
4679  $dbPageLanguage = $this->getDbPageLanguageCode();
4680  if ( $dbPageLanguage ) {
4681  $pageLang = wfGetLangObj( $dbPageLanguage );
4682  $variant = $pageLang->getPreferredVariant();
4683  if ( $pageLang->getCode() !== $variant ) {
4684  $pageLang = Language::factory( $variant );
4685  }
4686 
4687  return $pageLang;
4688  }
4689 
4690  // @note Can't be cached persistently, depends on user settings.
4691  // @note ContentHandler::getPageViewLanguage() may need to load the
4692  // content to determine the page language!
4693  $contentHandler = ContentHandler::getForTitle( $this );
4694  $pageLang = $contentHandler->getPageViewLanguage( $this );
4695  return $pageLang;
4696  }
4697 
4708  public function getEditNotices( $oldid = 0 ) {
4709  $notices = [];
4710 
4711  // Optional notice for the entire namespace
4712  $editnotice_ns = 'editnotice-' . $this->getNamespace();
4713  $msg = wfMessage( $editnotice_ns );
4714  if ( $msg->exists() ) {
4715  $html = $msg->parseAsBlock();
4716  // Edit notices may have complex logic, but output nothing (T91715)
4717  if ( trim( $html ) !== '' ) {
4718  $notices[$editnotice_ns] = Html::rawElement(
4719  'div',
4720  [ 'class' => [
4721  'mw-editnotice',
4722  'mw-editnotice-namespace',
4723  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4724  ] ],
4725  $html
4726  );
4727  }
4728  }
4729 
4730  if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4731  // Optional notice for page itself and any parent page
4732  $parts = explode( '/', $this->getDBkey() );
4733  $editnotice_base = $editnotice_ns;
4734  while ( count( $parts ) > 0 ) {
4735  $editnotice_base .= '-' . array_shift( $parts );
4736  $msg = wfMessage( $editnotice_base );
4737  if ( $msg->exists() ) {
4738  $html = $msg->parseAsBlock();
4739  if ( trim( $html ) !== '' ) {
4740  $notices[$editnotice_base] = Html::rawElement(
4741  'div',
4742  [ 'class' => [
4743  'mw-editnotice',
4744  'mw-editnotice-base',
4745  Sanitizer::escapeClass( "mw-$editnotice_base" )
4746  ] ],
4747  $html
4748  );
4749  }
4750  }
4751  }
4752  } else {
4753  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4754  $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4755  $msg = wfMessage( $editnoticeText );
4756  if ( $msg->exists() ) {
4757  $html = $msg->parseAsBlock();
4758  if ( trim( $html ) !== '' ) {
4759  $notices[$editnoticeText] = Html::rawElement(
4760  'div',
4761  [ 'class' => [
4762  'mw-editnotice',
4763  'mw-editnotice-page',
4764  Sanitizer::escapeClass( "mw-$editnoticeText" )
4765  ] ],
4766  $html
4767  );
4768  }
4769  }
4770  }
4771 
4772  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4773  return $notices;
4774  }
4775 
4779  public function __sleep() {
4780  return [
4781  'mNamespace',
4782  'mDbkeyform',
4783  'mFragment',
4784  'mInterwiki',
4785  'mLocalInterwiki',
4786  'mUserCaseDBKey',
4787  'mDefaultNamespace',
4788  ];
4789  }
4790 
4791  public function __wakeup() {
4792  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4793  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4794  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4795  }
4796 
4797 }
getEarliestRevTime($flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:3997
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:4249
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:3009
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3233
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:4393
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:4227
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:4557
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:3557
$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:2666
isSpecial($name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1055
static clearCaches()
Definition: Title.php:3287
getArticleID($flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3144
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:3037
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:3682
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition: Title.php:4337
getEditNotices($oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:4708
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:4791
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:2439
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:3324
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:3578
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:4604
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:3170
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:4017
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:3481
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:3800
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:3848
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4548
$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:2499
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:4420
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:4558
getSubpages($limit=-1)
Get all subpages of this page.
Definition: Title.php:3065
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:3001
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:3205
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:3593
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:3951
loadRestrictionsFromResultWrapper($res, $oldFashionedRestrictions=null)
Loads a string into mRestrictions array.
Definition: Title.php:2871
__sleep()
Definition: Title.php:4779
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:2588
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:4538
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:4311
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:3394
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:2856
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:4406
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:4524
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:2684
invalidateCache($purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4362
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3529
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:3428
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:3648
getRedirectsHere($ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4488
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:4210
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:3620
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:4112
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:4197
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:4302
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:3412
const NS_FILE
Definition: Defines.php:75
static newFromResult($res)
Definition: TitleArray.php:38
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2801
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:2698
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:3493
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:4457
countRevisionsBetween($old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:4069
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:2814
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:3975
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:2963
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:3749
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:4664
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:2517
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:4578
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:4626
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:2329
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:2715
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:3910
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:3118
isNewPage()
Check if this is a new page.
Definition: Title.php:4007
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:2404
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:2634
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:3093
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:2890
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:2544
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:4186
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:3883
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:2830
isSemiProtected($action= 'edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2606
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:2844
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:3926
$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:3302
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:3564
static getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4515
resetArticleID($newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3265
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:3370
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:4046
$wgUser
Definition: Setup.php:792
$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