MediaWiki  1.27.4
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  // Avoid PHP 7.1 warning from passing $this by reference
1680  $titleRef = $this;
1681  Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
1682  return $url;
1683  }
1684 
1701  public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1702  $target = $this;
1703  if ( $this->isExternal() ) {
1704  $target = SpecialPage::getTitleFor(
1705  'GoToInterwiki',
1706  $this->getPrefixedDBKey()
1707  );
1708  }
1709  return $target->getFullUrl( $query, false, $proto );
1710  }
1711 
1735  public function getLocalURL( $query = '', $query2 = false ) {
1737 
1738  $query = self::fixUrlQueryArgs( $query, $query2 );
1739 
1740  $interwiki = Interwiki::fetch( $this->mInterwiki );
1741  if ( $interwiki ) {
1742  $namespace = $this->getNsText();
1743  if ( $namespace != '' ) {
1744  # Can this actually happen? Interwikis shouldn't be parsed.
1745  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1746  $namespace .= ':';
1747  }
1748  $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1749  $url = wfAppendQuery( $url, $query );
1750  } else {
1751  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1752  if ( $query == '' ) {
1753  $url = str_replace( '$1', $dbkey, $wgArticlePath );
1754  // Avoid PHP 7.1 warning from passing $this by reference
1755  $titleRef = $this;
1756  Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
1757  } else {
1759  $url = false;
1760  $matches = [];
1761 
1762  if ( !empty( $wgActionPaths )
1763  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1764  ) {
1765  $action = urldecode( $matches[2] );
1766  if ( isset( $wgActionPaths[$action] ) ) {
1767  $query = $matches[1];
1768  if ( isset( $matches[4] ) ) {
1769  $query .= $matches[4];
1770  }
1771  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1772  if ( $query != '' ) {
1773  $url = wfAppendQuery( $url, $query );
1774  }
1775  }
1776  }
1777 
1778  if ( $url === false
1779  && $wgVariantArticlePath
1780  && $wgContLang->getCode() === $this->getPageLanguage()->getCode()
1781  && $this->getPageLanguage()->hasVariants()
1782  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1783  ) {
1784  $variant = urldecode( $matches[1] );
1785  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1786  // Only do the variant replacement if the given variant is a valid
1787  // variant for the page's language.
1788  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1789  $url = str_replace( '$1', $dbkey, $url );
1790  }
1791  }
1792 
1793  if ( $url === false ) {
1794  if ( $query == '-' ) {
1795  $query = '';
1796  }
1797  $url = "{$wgScript}?title={$dbkey}&{$query}";
1798  }
1799  }
1800  // Avoid PHP 7.1 warning from passing $this by reference
1801  $titleRef = $this;
1802  Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
1803 
1804  // @todo FIXME: This causes breakage in various places when we
1805  // actually expected a local URL and end up with dupe prefixes.
1806  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
1807  $url = $wgServer . $url;
1808  }
1809  }
1810  // Avoid PHP 7.1 warning from passing $this by reference
1811  $titleRef = $this;
1812  Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
1813  return $url;
1814  }
1815 
1832  public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1833  if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
1834  $ret = $this->getFullURL( $query, $query2, $proto );
1835  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
1836  $ret = $this->getFragmentForURL();
1837  } else {
1838  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
1839  }
1840  return $ret;
1841  }
1842 
1855  public function getInternalURL( $query = '', $query2 = false ) {
1857  $query = self::fixUrlQueryArgs( $query, $query2 );
1858  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
1859  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
1860  // Avoid PHP 7.1 warning from passing $this by reference
1861  $titleRef = $this;
1862  Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
1863  return $url;
1864  }
1865 
1877  public function getCanonicalURL( $query = '', $query2 = false ) {
1878  $query = self::fixUrlQueryArgs( $query, $query2 );
1879  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
1880  // Avoid PHP 7.1 warning from passing $this by reference
1881  $titleRef = $this;
1882  Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
1883  return $url;
1884  }
1885 
1891  public function getEditURL() {
1892  if ( $this->isExternal() ) {
1893  return '';
1894  }
1895  $s = $this->getLocalURL( 'action=edit' );
1896 
1897  return $s;
1898  }
1899 
1914  public function quickUserCan( $action, $user = null ) {
1915  return $this->userCan( $action, $user, false );
1916  }
1917 
1927  public function userCan( $action, $user = null, $rigor = 'secure' ) {
1928  if ( !$user instanceof User ) {
1929  global $wgUser;
1930  $user = $wgUser;
1931  }
1932 
1933  return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
1934  }
1935 
1951  public function getUserPermissionsErrors(
1952  $action, $user, $rigor = 'secure', $ignoreErrors = []
1953  ) {
1954  $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
1955 
1956  // Remove the errors being ignored.
1957  foreach ( $errors as $index => $error ) {
1958  $errKey = is_array( $error ) ? $error[0] : $error;
1959 
1960  if ( in_array( $errKey, $ignoreErrors ) ) {
1961  unset( $errors[$index] );
1962  }
1963  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
1964  unset( $errors[$index] );
1965  }
1966  }
1967 
1968  return $errors;
1969  }
1970 
1982  private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
1983  if ( !Hooks::run( 'TitleQuickPermissions',
1984  [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
1985  ) {
1986  return $errors;
1987  }
1988 
1989  if ( $action == 'create' ) {
1990  if (
1991  ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1992  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
1993  ) {
1994  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
1995  }
1996  } elseif ( $action == 'move' ) {
1997  if ( !$user->isAllowed( 'move-rootuserpages' )
1998  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1999  // Show user page-specific message only if the user can move other pages
2000  $errors[] = [ 'cant-move-user-page' ];
2001  }
2002 
2003  // Check if user is allowed to move files if it's a file
2004  if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
2005  $errors[] = [ 'movenotallowedfile' ];
2006  }
2007 
2008  // Check if user is allowed to move category pages if it's a category page
2009  if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
2010  $errors[] = [ 'cant-move-category-page' ];
2011  }
2012 
2013  if ( !$user->isAllowed( 'move' ) ) {
2014  // User can't move anything
2015  $userCanMove = User::groupHasPermission( 'user', 'move' );
2016  $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
2017  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
2018  // custom message if logged-in users without any special rights can move
2019  $errors[] = [ 'movenologintext' ];
2020  } else {
2021  $errors[] = [ 'movenotallowed' ];
2022  }
2023  }
2024  } elseif ( $action == 'move-target' ) {
2025  if ( !$user->isAllowed( 'move' ) ) {
2026  // User can't move anything
2027  $errors[] = [ 'movenotallowed' ];
2028  } elseif ( !$user->isAllowed( 'move-rootuserpages' )
2029  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2030  // Show user page-specific message only if the user can move other pages
2031  $errors[] = [ 'cant-move-to-user-page' ];
2032  } elseif ( !$user->isAllowed( 'move-categorypages' )
2033  && $this->mNamespace == NS_CATEGORY ) {
2034  // Show category page-specific message only if the user can move other pages
2035  $errors[] = [ 'cant-move-to-category-page' ];
2036  }
2037  } elseif ( !$user->isAllowed( $action ) ) {
2038  $errors[] = $this->missingPermissionError( $action, $short );
2039  }
2040 
2041  return $errors;
2042  }
2043 
2052  private function resultToError( $errors, $result ) {
2053  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2054  // A single array representing an error
2055  $errors[] = $result;
2056  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2057  // A nested array representing multiple errors
2058  $errors = array_merge( $errors, $result );
2059  } elseif ( $result !== '' && is_string( $result ) ) {
2060  // A string representing a message-id
2061  $errors[] = [ $result ];
2062  } elseif ( $result instanceof MessageSpecifier ) {
2063  // A message specifier representing an error
2064  $errors[] = [ $result ];
2065  } elseif ( $result === false ) {
2066  // a generic "We don't want them to do that"
2067  $errors[] = [ 'badaccess-group0' ];
2068  }
2069  return $errors;
2070  }
2071 
2083  private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2084  // Use getUserPermissionsErrors instead
2085  $result = '';
2086  // Avoid PHP 7.1 warning from passing $this by reference
2087  $titleRef = $this;
2088  if ( !Hooks::run( 'userCan', [ &$titleRef, &$user, $action, &$result ] ) ) {
2089  return $result ? [] : [ [ 'badaccess-group0' ] ];
2090  }
2091  // Check getUserPermissionsErrors hook
2092  // Avoid PHP 7.1 warning from passing $this by reference
2093  $titleRef = $this;
2094  if ( !Hooks::run( 'getUserPermissionsErrors', [ &$titleRef, &$user, $action, &$result ] ) ) {
2095  $errors = $this->resultToError( $errors, $result );
2096  }
2097  // Check getUserPermissionsErrorsExpensive hook
2098  if (
2099  $rigor !== 'quick'
2100  && !( $short && count( $errors ) > 0 )
2101  && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$titleRef, &$user, $action, &$result ] )
2102  ) {
2103  $errors = $this->resultToError( $errors, $result );
2104  }
2105 
2106  return $errors;
2107  }
2108 
2120  private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2121  # Only 'createaccount' can be performed on special pages,
2122  # which don't actually exist in the DB.
2123  if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
2124  $errors[] = [ 'ns-specialprotected' ];
2125  }
2126 
2127  # Check $wgNamespaceProtection for restricted namespaces
2128  if ( $this->isNamespaceProtected( $user ) ) {
2129  $ns = $this->mNamespace == NS_MAIN ?
2130  wfMessage( 'nstab-main' )->text() : $this->getNsText();
2131  $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2132  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2133  }
2134 
2135  return $errors;
2136  }
2137 
2149  private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
2150  # Protect css/js subpages of user pages
2151  # XXX: this might be better using restrictions
2152  # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
2153  if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
2154  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2155  if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
2156  $errors[] = [ 'mycustomcssprotected', $action ];
2157  } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
2158  $errors[] = [ 'mycustomjsprotected', $action ];
2159  }
2160  } else {
2161  if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
2162  $errors[] = [ 'customcssprotected', $action ];
2163  } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
2164  $errors[] = [ 'customjsprotected', $action ];
2165  }
2166  }
2167  }
2168 
2169  return $errors;
2170  }
2171 
2185  private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2186  foreach ( $this->getRestrictions( $action ) as $right ) {
2187  // Backwards compatibility, rewrite sysop -> editprotected
2188  if ( $right == 'sysop' ) {
2189  $right = 'editprotected';
2190  }
2191  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2192  if ( $right == 'autoconfirmed' ) {
2193  $right = 'editsemiprotected';
2194  }
2195  if ( $right == '' ) {
2196  continue;
2197  }
2198  if ( !$user->isAllowed( $right ) ) {
2199  $errors[] = [ 'protectedpagetext', $right, $action ];
2200  } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2201  $errors[] = [ 'protectedpagetext', 'protect', $action ];
2202  }
2203  }
2204 
2205  return $errors;
2206  }
2207 
2219  private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2220  if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
2221  # We /could/ use the protection level on the source page, but it's
2222  # fairly ugly as we have to establish a precedence hierarchy for pages
2223  # included by multiple cascade-protected pages. So just restrict
2224  # it to people with 'protect' permission, as they could remove the
2225  # protection anyway.
2226  list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2227  # Cascading protection depends on more than this page...
2228  # Several cascading protected pages may include this page...
2229  # Check each cascading level
2230  # This is only for protection restrictions, not for all actions
2231  if ( isset( $restrictions[$action] ) ) {
2232  foreach ( $restrictions[$action] as $right ) {
2233  // Backwards compatibility, rewrite sysop -> editprotected
2234  if ( $right == 'sysop' ) {
2235  $right = 'editprotected';
2236  }
2237  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2238  if ( $right == 'autoconfirmed' ) {
2239  $right = 'editsemiprotected';
2240  }
2241  if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2242  $pages = '';
2243  foreach ( $cascadingSources as $page ) {
2244  $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2245  }
2246  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2247  }
2248  }
2249  }
2250  }
2251 
2252  return $errors;
2253  }
2254 
2266  private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2267  global $wgDeleteRevisionsLimit, $wgLang;
2268 
2269  if ( $action == 'protect' ) {
2270  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2271  // If they can't edit, they shouldn't protect.
2272  $errors[] = [ 'protect-cantedit' ];
2273  }
2274  } elseif ( $action == 'create' ) {
2275  $title_protection = $this->getTitleProtection();
2276  if ( $title_protection ) {
2277  if ( $title_protection['permission'] == ''
2278  || !$user->isAllowed( $title_protection['permission'] )
2279  ) {
2280  $errors[] = [
2281  'titleprotected',
2282  User::whoIs( $title_protection['user'] ),
2283  $title_protection['reason']
2284  ];
2285  }
2286  }
2287  } elseif ( $action == 'move' ) {
2288  // Check for immobile pages
2289  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2290  // Specific message for this case
2291  $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2292  } elseif ( !$this->isMovable() ) {
2293  // Less specific message for rarer cases
2294  $errors[] = [ 'immobile-source-page' ];
2295  }
2296  } elseif ( $action == 'move-target' ) {
2297  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2298  $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2299  } elseif ( !$this->isMovable() ) {
2300  $errors[] = [ 'immobile-target-page' ];
2301  }
2302  } elseif ( $action == 'delete' ) {
2303  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2304  if ( !$tempErrors ) {
2305  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2306  $user, $tempErrors, $rigor, true );
2307  }
2308  if ( $tempErrors ) {
2309  // If protection keeps them from editing, they shouldn't be able to delete.
2310  $errors[] = [ 'deleteprotected' ];
2311  }
2312  if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2313  && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2314  ) {
2315  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2316  }
2317  } elseif ( $action === 'undelete' ) {
2318  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2319  // Undeleting implies editing
2320  $errors[] = [ 'undelete-cantedit' ];
2321  }
2322  if ( !$this->exists()
2323  && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
2324  ) {
2325  // Undeleting where nothing currently exists implies creating
2326  $errors[] = [ 'undelete-cantcreate' ];
2327  }
2328  }
2329  return $errors;
2330  }
2331 
2343  private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2344  global $wgEmailConfirmToEdit, $wgBlockDisablesLogin;
2345  // Account creation blocks handled at userlogin.
2346  // Unblocking handled in SpecialUnblock
2347  if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2348  return $errors;
2349  }
2350 
2351  // Optimize for a very common case
2352  if ( $action === 'read' && !$wgBlockDisablesLogin ) {
2353  return $errors;
2354  }
2355 
2356  if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
2357  $errors[] = [ 'confirmedittext' ];
2358  }
2359 
2360  $useSlave = ( $rigor !== 'secure' );
2361  if ( ( $action == 'edit' || $action == 'create' )
2362  && !$user->isBlockedFrom( $this, $useSlave )
2363  ) {
2364  // Don't block the user from editing their own talk page unless they've been
2365  // explicitly blocked from that too.
2366  } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2367  // @todo FIXME: Pass the relevant context into this function.
2368  $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2369  }
2370 
2371  return $errors;
2372  }
2373 
2385  private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2386  global $wgWhitelistRead, $wgWhitelistReadRegexp;
2387 
2388  $whitelisted = false;
2389  if ( User::isEveryoneAllowed( 'read' ) ) {
2390  # Shortcut for public wikis, allows skipping quite a bit of code
2391  $whitelisted = true;
2392  } elseif ( $user->isAllowed( 'read' ) ) {
2393  # If the user is allowed to read pages, he is allowed to read all pages
2394  $whitelisted = true;
2395  } elseif ( $this->isSpecial( 'Userlogin' )
2396  || $this->isSpecial( 'ChangePassword' )
2397  || $this->isSpecial( 'PasswordReset' )
2398  ) {
2399  # Always grant access to the login page.
2400  # Even anons need to be able to log in.
2401  $whitelisted = true;
2402  } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2403  # Time to check the whitelist
2404  # Only do these checks is there's something to check against
2405  $name = $this->getPrefixedText();
2406  $dbName = $this->getPrefixedDBkey();
2407 
2408  // Check for explicit whitelisting with and without underscores
2409  if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2410  $whitelisted = true;
2411  } elseif ( $this->getNamespace() == NS_MAIN ) {
2412  # Old settings might have the title prefixed with
2413  # a colon for main-namespace pages
2414  if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2415  $whitelisted = true;
2416  }
2417  } elseif ( $this->isSpecialPage() ) {
2418  # If it's a special page, ditch the subpage bit and check again
2419  $name = $this->getDBkey();
2420  list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2421  if ( $name ) {
2422  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2423  if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2424  $whitelisted = true;
2425  }
2426  }
2427  }
2428  }
2429 
2430  if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2431  $name = $this->getPrefixedText();
2432  // Check for regex whitelisting
2433  foreach ( $wgWhitelistReadRegexp as $listItem ) {
2434  if ( preg_match( $listItem, $name ) ) {
2435  $whitelisted = true;
2436  break;
2437  }
2438  }
2439  }
2440 
2441  if ( !$whitelisted ) {
2442  # If the title is not whitelisted, give extensions a chance to do so...
2443  Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2444  if ( !$whitelisted ) {
2445  $errors[] = $this->missingPermissionError( $action, $short );
2446  }
2447  }
2448 
2449  return $errors;
2450  }
2451 
2460  private function missingPermissionError( $action, $short ) {
2461  // We avoid expensive display logic for quickUserCan's and such
2462  if ( $short ) {
2463  return [ 'badaccess-group0' ];
2464  }
2465 
2466  $groups = array_map( [ 'User', 'makeGroupLinkWiki' ],
2467  User::getGroupsWithPermission( $action ) );
2468 
2469  if ( count( $groups ) ) {
2470  global $wgLang;
2471  return [
2472  'badaccess-groups',
2473  $wgLang->commaList( $groups ),
2474  count( $groups )
2475  ];
2476  } else {
2477  return [ 'badaccess-group0' ];
2478  }
2479  }
2480 
2496  $action, $user, $rigor = 'secure', $short = false
2497  ) {
2498  if ( $rigor === true ) {
2499  $rigor = 'secure'; // b/c
2500  } elseif ( $rigor === false ) {
2501  $rigor = 'quick'; // b/c
2502  } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2503  throw new Exception( "Invalid rigor parameter '$rigor'." );
2504  }
2505 
2506  # Read has special handling
2507  if ( $action == 'read' ) {
2508  $checks = [
2509  'checkPermissionHooks',
2510  'checkReadPermissions',
2511  'checkUserBlock', // for wgBlockDisablesLogin
2512  ];
2513  # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
2514  # here as it will lead to duplicate error messages. This is okay to do
2515  # since anywhere that checks for create will also check for edit, and
2516  # those checks are called for edit.
2517  } elseif ( $action == 'create' ) {
2518  $checks = [
2519  'checkQuickPermissions',
2520  'checkPermissionHooks',
2521  'checkPageRestrictions',
2522  'checkCascadingSourcesRestrictions',
2523  'checkActionPermissions',
2524  'checkUserBlock'
2525  ];
2526  } else {
2527  $checks = [
2528  'checkQuickPermissions',
2529  'checkPermissionHooks',
2530  'checkSpecialsAndNSPermissions',
2531  'checkCSSandJSPermissions',
2532  'checkPageRestrictions',
2533  'checkCascadingSourcesRestrictions',
2534  'checkActionPermissions',
2535  'checkUserBlock'
2536  ];
2537  }
2538 
2539  $errors = [];
2540  while ( count( $checks ) > 0 &&
2541  !( $short && count( $errors ) > 0 ) ) {
2542  $method = array_shift( $checks );
2543  $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2544  }
2545 
2546  return $errors;
2547  }
2548 
2556  public static function getFilteredRestrictionTypes( $exists = true ) {
2557  global $wgRestrictionTypes;
2558  $types = $wgRestrictionTypes;
2559  if ( $exists ) {
2560  # Remove the create restriction for existing titles
2561  $types = array_diff( $types, [ 'create' ] );
2562  } else {
2563  # Only the create and upload restrictions apply to non-existing titles
2564  $types = array_intersect( $types, [ 'create', 'upload' ] );
2565  }
2566  return $types;
2567  }
2568 
2574  public function getRestrictionTypes() {
2575  if ( $this->isSpecialPage() ) {
2576  return [];
2577  }
2578 
2579  $types = self::getFilteredRestrictionTypes( $this->exists() );
2580 
2581  if ( $this->getNamespace() != NS_FILE ) {
2582  # Remove the upload restriction for non-file titles
2583  $types = array_diff( $types, [ 'upload' ] );
2584  }
2585 
2586  Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2587 
2588  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2589  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2590 
2591  return $types;
2592  }
2593 
2601  public function getTitleProtection() {
2602  // Can't protect pages in special namespaces
2603  if ( $this->getNamespace() < 0 ) {
2604  return false;
2605  }
2606 
2607  // Can't protect pages that exist.
2608  if ( $this->exists() ) {
2609  return false;
2610  }
2611 
2612  if ( $this->mTitleProtection === null ) {
2613  $dbr = wfGetDB( DB_SLAVE );
2614  $res = $dbr->select(
2615  'protected_titles',
2616  [
2617  'user' => 'pt_user',
2618  'reason' => 'pt_reason',
2619  'expiry' => 'pt_expiry',
2620  'permission' => 'pt_create_perm'
2621  ],
2622  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2623  __METHOD__
2624  );
2625 
2626  // fetchRow returns false if there are no rows.
2627  $row = $dbr->fetchRow( $res );
2628  if ( $row ) {
2629  if ( $row['permission'] == 'sysop' ) {
2630  $row['permission'] = 'editprotected'; // B/C
2631  }
2632  if ( $row['permission'] == 'autoconfirmed' ) {
2633  $row['permission'] = 'editsemiprotected'; // B/C
2634  }
2635  $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2636  }
2637  $this->mTitleProtection = $row;
2638  }
2639  return $this->mTitleProtection;
2640  }
2641 
2645  public function deleteTitleProtection() {
2646  $dbw = wfGetDB( DB_MASTER );
2647 
2648  $dbw->delete(
2649  'protected_titles',
2650  [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2651  __METHOD__
2652  );
2653  $this->mTitleProtection = false;
2654  }
2655 
2663  public function isSemiProtected( $action = 'edit' ) {
2664  global $wgSemiprotectedRestrictionLevels;
2665 
2666  $restrictions = $this->getRestrictions( $action );
2667  $semi = $wgSemiprotectedRestrictionLevels;
2668  if ( !$restrictions || !$semi ) {
2669  // Not protected, or all protection is full protection
2670  return false;
2671  }
2672 
2673  // Remap autoconfirmed to editsemiprotected for BC
2674  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2675  $semi[$key] = 'editsemiprotected';
2676  }
2677  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2678  $restrictions[$key] = 'editsemiprotected';
2679  }
2680 
2681  return !array_diff( $restrictions, $semi );
2682  }
2683 
2691  public function isProtected( $action = '' ) {
2692  global $wgRestrictionLevels;
2693 
2694  $restrictionTypes = $this->getRestrictionTypes();
2695 
2696  # Special pages have inherent protection
2697  if ( $this->isSpecialPage() ) {
2698  return true;
2699  }
2700 
2701  # Check regular protection levels
2702  foreach ( $restrictionTypes as $type ) {
2703  if ( $action == $type || $action == '' ) {
2704  $r = $this->getRestrictions( $type );
2705  foreach ( $wgRestrictionLevels as $level ) {
2706  if ( in_array( $level, $r ) && $level != '' ) {
2707  return true;
2708  }
2709  }
2710  }
2711  }
2712 
2713  return false;
2714  }
2715 
2723  public function isNamespaceProtected( User $user ) {
2725 
2726  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2727  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2728  if ( $right != '' && !$user->isAllowed( $right ) ) {
2729  return true;
2730  }
2731  }
2732  }
2733  return false;
2734  }
2735 
2741  public function isCascadeProtected() {
2742  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2743  return ( $sources > 0 );
2744  }
2745 
2755  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2756  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2757  }
2758 
2772  public function getCascadeProtectionSources( $getPages = true ) {
2773  $pagerestrictions = [];
2774 
2775  if ( $this->mCascadeSources !== null && $getPages ) {
2777  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2778  return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2779  }
2780 
2781  $dbr = wfGetDB( DB_SLAVE );
2782 
2783  if ( $this->getNamespace() == NS_FILE ) {
2784  $tables = [ 'imagelinks', 'page_restrictions' ];
2785  $where_clauses = [
2786  'il_to' => $this->getDBkey(),
2787  'il_from=pr_page',
2788  'pr_cascade' => 1
2789  ];
2790  } else {
2791  $tables = [ 'templatelinks', 'page_restrictions' ];
2792  $where_clauses = [
2793  'tl_namespace' => $this->getNamespace(),
2794  'tl_title' => $this->getDBkey(),
2795  'tl_from=pr_page',
2796  'pr_cascade' => 1
2797  ];
2798  }
2799 
2800  if ( $getPages ) {
2801  $cols = [ 'pr_page', 'page_namespace', 'page_title',
2802  'pr_expiry', 'pr_type', 'pr_level' ];
2803  $where_clauses[] = 'page_id=pr_page';
2804  $tables[] = 'page';
2805  } else {
2806  $cols = [ 'pr_expiry' ];
2807  }
2808 
2809  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2810 
2811  $sources = $getPages ? [] : false;
2812  $now = wfTimestampNow();
2813 
2814  foreach ( $res as $row ) {
2815  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2816  if ( $expiry > $now ) {
2817  if ( $getPages ) {
2818  $page_id = $row->pr_page;
2819  $page_ns = $row->page_namespace;
2820  $page_title = $row->page_title;
2821  $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2822  # Add groups needed for each restriction type if its not already there
2823  # Make sure this restriction type still exists
2824 
2825  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2826  $pagerestrictions[$row->pr_type] = [];
2827  }
2828 
2829  if (
2830  isset( $pagerestrictions[$row->pr_type] )
2831  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2832  ) {
2833  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2834  }
2835  } else {
2836  $sources = true;
2837  }
2838  }
2839  }
2840 
2841  if ( $getPages ) {
2842  $this->mCascadeSources = $sources;
2843  $this->mCascadingRestrictions = $pagerestrictions;
2844  } else {
2845  $this->mHasCascadingRestrictions = $sources;
2846  }
2847 
2848  return [ $sources, $pagerestrictions ];
2849  }
2850 
2858  public function areRestrictionsLoaded() {
2860  }
2861 
2871  public function getRestrictions( $action ) {
2872  if ( !$this->mRestrictionsLoaded ) {
2873  $this->loadRestrictions();
2874  }
2875  return isset( $this->mRestrictions[$action] )
2876  ? $this->mRestrictions[$action]
2877  : [];
2878  }
2879 
2887  public function getAllRestrictions() {
2888  if ( !$this->mRestrictionsLoaded ) {
2889  $this->loadRestrictions();
2890  }
2891  return $this->mRestrictions;
2892  }
2893 
2901  public function getRestrictionExpiry( $action ) {
2902  if ( !$this->mRestrictionsLoaded ) {
2903  $this->loadRestrictions();
2904  }
2905  return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2906  }
2907 
2914  if ( !$this->mRestrictionsLoaded ) {
2915  $this->loadRestrictions();
2916  }
2917 
2919  }
2920 
2928  private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
2929  $rows = [];
2930 
2931  foreach ( $res as $row ) {
2932  $rows[] = $row;
2933  }
2934 
2935  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2936  }
2937 
2947  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2948  $dbr = wfGetDB( DB_SLAVE );
2949 
2950  $restrictionTypes = $this->getRestrictionTypes();
2951 
2952  foreach ( $restrictionTypes as $type ) {
2953  $this->mRestrictions[$type] = [];
2954  $this->mRestrictionsExpiry[$type] = 'infinity';
2955  }
2956 
2957  $this->mCascadeRestriction = false;
2958 
2959  # Backwards-compatibility: also load the restrictions from the page record (old format).
2960  if ( $oldFashionedRestrictions !== null ) {
2961  $this->mOldRestrictions = $oldFashionedRestrictions;
2962  }
2963 
2964  if ( $this->mOldRestrictions === false ) {
2965  $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2966  [ 'page_id' => $this->getArticleID() ], __METHOD__ );
2967  }
2968 
2969  if ( $this->mOldRestrictions != '' ) {
2970  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2971  $temp = explode( '=', trim( $restrict ) );
2972  if ( count( $temp ) == 1 ) {
2973  // old old format should be treated as edit/move restriction
2974  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2975  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2976  } else {
2977  $restriction = trim( $temp[1] );
2978  if ( $restriction != '' ) { // some old entries are empty
2979  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2980  }
2981  }
2982  }
2983  }
2984 
2985  if ( count( $rows ) ) {
2986  # Current system - load second to make them override.
2987  $now = wfTimestampNow();
2988 
2989  # Cycle through all the restrictions.
2990  foreach ( $rows as $row ) {
2991 
2992  // Don't take care of restrictions types that aren't allowed
2993  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2994  continue;
2995  }
2996 
2997  // This code should be refactored, now that it's being used more generally,
2998  // But I don't really see any harm in leaving it in Block for now -werdna
2999  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
3000 
3001  // Only apply the restrictions if they haven't expired!
3002  if ( !$expiry || $expiry > $now ) {
3003  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
3004  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
3005 
3006  $this->mCascadeRestriction |= $row->pr_cascade;
3007  }
3008  }
3009  }
3010 
3011  $this->mRestrictionsLoaded = true;
3012  }
3013 
3020  public function loadRestrictions( $oldFashionedRestrictions = null ) {
3021  if ( !$this->mRestrictionsLoaded ) {
3022  $dbr = wfGetDB( DB_SLAVE );
3023  if ( $this->exists() ) {
3024  $res = $dbr->select(
3025  'page_restrictions',
3026  [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
3027  [ 'pr_page' => $this->getArticleID() ],
3028  __METHOD__
3029  );
3030 
3031  $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
3032  } else {
3033  $title_protection = $this->getTitleProtection();
3034 
3035  if ( $title_protection ) {
3036  $now = wfTimestampNow();
3037  $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
3038 
3039  if ( !$expiry || $expiry > $now ) {
3040  // Apply the restrictions
3041  $this->mRestrictionsExpiry['create'] = $expiry;
3042  $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
3043  } else { // Get rid of the old restrictions
3044  $this->mTitleProtection = false;
3045  }
3046  } else {
3047  $this->mRestrictionsExpiry['create'] = 'infinity';
3048  }
3049  $this->mRestrictionsLoaded = true;
3050  }
3051  }
3052  }
3053 
3058  public function flushRestrictions() {
3059  $this->mRestrictionsLoaded = false;
3060  $this->mTitleProtection = null;
3061  }
3062 
3066  static function purgeExpiredRestrictions() {
3067  if ( wfReadOnly() ) {
3068  return;
3069  }
3070 
3072  wfGetDB( DB_MASTER ),
3073  __METHOD__,
3074  function ( IDatabase $dbw, $fname ) {
3075  $dbw->delete(
3076  'page_restrictions',
3077  [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3078  $fname
3079  );
3080  $dbw->delete(
3081  'protected_titles',
3082  [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3083  $fname
3084  );
3085  }
3086  ) );
3087  }
3088 
3094  public function hasSubpages() {
3095  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3096  # Duh
3097  return false;
3098  }
3099 
3100  # We dynamically add a member variable for the purpose of this method
3101  # alone to cache the result. There's no point in having it hanging
3102  # around uninitialized in every Title object; therefore we only add it
3103  # if needed and don't declare it statically.
3104  if ( $this->mHasSubpages === null ) {
3105  $this->mHasSubpages = false;
3106  $subpages = $this->getSubpages( 1 );
3107  if ( $subpages instanceof TitleArray ) {
3108  $this->mHasSubpages = (bool)$subpages->count();
3109  }
3110  }
3111 
3112  return $this->mHasSubpages;
3113  }
3114 
3122  public function getSubpages( $limit = -1 ) {
3123  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3124  return [];
3125  }
3126 
3127  $dbr = wfGetDB( DB_SLAVE );
3128  $conds['page_namespace'] = $this->getNamespace();
3129  $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3130  $options = [];
3131  if ( $limit > -1 ) {
3132  $options['LIMIT'] = $limit;
3133  }
3134  $this->mSubpages = TitleArray::newFromResult(
3135  $dbr->select( 'page',
3136  [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3137  $conds,
3138  __METHOD__,
3139  $options
3140  )
3141  );
3142  return $this->mSubpages;
3143  }
3144 
3150  public function isDeleted() {
3151  if ( $this->getNamespace() < 0 ) {
3152  $n = 0;
3153  } else {
3154  $dbr = wfGetDB( DB_SLAVE );
3155 
3156  $n = $dbr->selectField( 'archive', 'COUNT(*)',
3157  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3158  __METHOD__
3159  );
3160  if ( $this->getNamespace() == NS_FILE ) {
3161  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3162  [ 'fa_name' => $this->getDBkey() ],
3163  __METHOD__
3164  );
3165  }
3166  }
3167  return (int)$n;
3168  }
3169 
3175  public function isDeletedQuick() {
3176  if ( $this->getNamespace() < 0 ) {
3177  return false;
3178  }
3179  $dbr = wfGetDB( DB_SLAVE );
3180  $deleted = (bool)$dbr->selectField( 'archive', '1',
3181  [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3182  __METHOD__
3183  );
3184  if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3185  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3186  [ 'fa_name' => $this->getDBkey() ],
3187  __METHOD__
3188  );
3189  }
3190  return $deleted;
3191  }
3192 
3201  public function getArticleID( $flags = 0 ) {
3202  if ( $this->getNamespace() < 0 ) {
3203  $this->mArticleID = 0;
3204  return $this->mArticleID;
3205  }
3206  $linkCache = LinkCache::singleton();
3207  if ( $flags & self::GAID_FOR_UPDATE ) {
3208  $oldUpdate = $linkCache->forUpdate( true );
3209  $linkCache->clearLink( $this );
3210  $this->mArticleID = $linkCache->addLinkObj( $this );
3211  $linkCache->forUpdate( $oldUpdate );
3212  } else {
3213  if ( -1 == $this->mArticleID ) {
3214  $this->mArticleID = $linkCache->addLinkObj( $this );
3215  }
3216  }
3217  return $this->mArticleID;
3218  }
3219 
3227  public function isRedirect( $flags = 0 ) {
3228  if ( !is_null( $this->mRedirect ) ) {
3229  return $this->mRedirect;
3230  }
3231  if ( !$this->getArticleID( $flags ) ) {
3232  $this->mRedirect = false;
3233  return $this->mRedirect;
3234  }
3235 
3236  $linkCache = LinkCache::singleton();
3237  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3238  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3239  if ( $cached === null ) {
3240  # Trust LinkCache's state over our own
3241  # LinkCache is telling us that the page doesn't exist, despite there being cached
3242  # data relating to an existing page in $this->mArticleID. Updaters should clear
3243  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3244  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3245  # LinkCache to refresh its data from the master.
3246  $this->mRedirect = false;
3247  return $this->mRedirect;
3248  }
3249 
3250  $this->mRedirect = (bool)$cached;
3251 
3252  return $this->mRedirect;
3253  }
3254 
3262  public function getLength( $flags = 0 ) {
3263  if ( $this->mLength != -1 ) {
3264  return $this->mLength;
3265  }
3266  if ( !$this->getArticleID( $flags ) ) {
3267  $this->mLength = 0;
3268  return $this->mLength;
3269  }
3270  $linkCache = LinkCache::singleton();
3271  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3272  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3273  if ( $cached === null ) {
3274  # Trust LinkCache's state over our own, as for isRedirect()
3275  $this->mLength = 0;
3276  return $this->mLength;
3277  }
3278 
3279  $this->mLength = intval( $cached );
3280 
3281  return $this->mLength;
3282  }
3283 
3290  public function getLatestRevID( $flags = 0 ) {
3291  if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3292  return intval( $this->mLatestID );
3293  }
3294  if ( !$this->getArticleID( $flags ) ) {
3295  $this->mLatestID = 0;
3296  return $this->mLatestID;
3297  }
3298  $linkCache = LinkCache::singleton();
3299  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3300  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3301  if ( $cached === null ) {
3302  # Trust LinkCache's state over our own, as for isRedirect()
3303  $this->mLatestID = 0;
3304  return $this->mLatestID;
3305  }
3306 
3307  $this->mLatestID = intval( $cached );
3308 
3309  return $this->mLatestID;
3310  }
3311 
3322  public function resetArticleID( $newid ) {
3323  $linkCache = LinkCache::singleton();
3324  $linkCache->clearLink( $this );
3325 
3326  if ( $newid === false ) {
3327  $this->mArticleID = -1;
3328  } else {
3329  $this->mArticleID = intval( $newid );
3330  }
3331  $this->mRestrictionsLoaded = false;
3332  $this->mRestrictions = [];
3333  $this->mOldRestrictions = false;
3334  $this->mRedirect = null;
3335  $this->mLength = -1;
3336  $this->mLatestID = false;
3337  $this->mContentModel = false;
3338  $this->mEstimateRevisions = null;
3339  $this->mPageLanguage = false;
3340  $this->mDbPageLanguage = false;
3341  $this->mIsBigDeletion = null;
3342  }
3343 
3344  public static function clearCaches() {
3345  $linkCache = LinkCache::singleton();
3346  $linkCache->clear();
3347 
3348  $titleCache = self::getTitleCache();
3349  $titleCache->clear();
3350  }
3351 
3359  public static function capitalize( $text, $ns = NS_MAIN ) {
3361 
3362  if ( MWNamespace::isCapitalized( $ns ) ) {
3363  return $wgContLang->ucfirst( $text );
3364  } else {
3365  return $text;
3366  }
3367  }
3368 
3381  private function secureAndSplit() {
3382  # Initialisation
3383  $this->mInterwiki = '';
3384  $this->mFragment = '';
3385  $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3386 
3387  $dbkey = $this->mDbkeyform;
3388 
3389  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3390  // the parsing code with Title, while avoiding massive refactoring.
3391  // @todo: get rid of secureAndSplit, refactor parsing code.
3392  $titleParser = self::getMediaWikiTitleCodec();
3393  // MalformedTitleException can be thrown here
3394  $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3395 
3396  # Fill fields
3397  $this->setFragment( '#' . $parts['fragment'] );
3398  $this->mInterwiki = $parts['interwiki'];
3399  $this->mLocalInterwiki = $parts['local_interwiki'];
3400  $this->mNamespace = $parts['namespace'];
3401  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3402 
3403  $this->mDbkeyform = $parts['dbkey'];
3404  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3405  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3406 
3407  # We already know that some pages won't be in the database!
3408  if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3409  $this->mArticleID = 0;
3410  }
3411 
3412  return true;
3413  }
3414 
3427  public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3428  if ( count( $options ) > 0 ) {
3429  $db = wfGetDB( DB_MASTER );
3430  } else {
3431  $db = wfGetDB( DB_SLAVE );
3432  }
3433 
3434  $res = $db->select(
3435  [ 'page', $table ],
3436  self::getSelectFields(),
3437  [
3438  "{$prefix}_from=page_id",
3439  "{$prefix}_namespace" => $this->getNamespace(),
3440  "{$prefix}_title" => $this->getDBkey() ],
3441  __METHOD__,
3442  $options
3443  );
3444 
3445  $retVal = [];
3446  if ( $res->numRows() ) {
3447  $linkCache = LinkCache::singleton();
3448  foreach ( $res as $row ) {
3449  $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3450  if ( $titleObj ) {
3451  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3452  $retVal[] = $titleObj;
3453  }
3454  }
3455  }
3456  return $retVal;
3457  }
3458 
3469  public function getTemplateLinksTo( $options = [] ) {
3470  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3471  }
3472 
3485  public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3486  $id = $this->getArticleID();
3487 
3488  # If the page doesn't exist; there can't be any link from this page
3489  if ( !$id ) {
3490  return [];
3491  }
3492 
3493  $db = wfGetDB( DB_SLAVE );
3494 
3495  $blNamespace = "{$prefix}_namespace";
3496  $blTitle = "{$prefix}_title";
3497 
3498  $res = $db->select(
3499  [ $table, 'page' ],
3500  array_merge(
3501  [ $blNamespace, $blTitle ],
3503  ),
3504  [ "{$prefix}_from" => $id ],
3505  __METHOD__,
3506  $options,
3507  [ 'page' => [
3508  'LEFT JOIN',
3509  [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3510  ] ]
3511  );
3512 
3513  $retVal = [];
3514  $linkCache = LinkCache::singleton();
3515  foreach ( $res as $row ) {
3516  if ( $row->page_id ) {
3517  $titleObj = Title::newFromRow( $row );
3518  } else {
3519  $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3520  $linkCache->addBadLinkObj( $titleObj );
3521  }
3522  $retVal[] = $titleObj;
3523  }
3524 
3525  return $retVal;
3526  }
3527 
3538  public function getTemplateLinksFrom( $options = [] ) {
3539  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3540  }
3541 
3550  public function getBrokenLinksFrom() {
3551  if ( $this->getArticleID() == 0 ) {
3552  # All links from article ID 0 are false positives
3553  return [];
3554  }
3555 
3556  $dbr = wfGetDB( DB_SLAVE );
3557  $res = $dbr->select(
3558  [ 'page', 'pagelinks' ],
3559  [ 'pl_namespace', 'pl_title' ],
3560  [
3561  'pl_from' => $this->getArticleID(),
3562  'page_namespace IS NULL'
3563  ],
3564  __METHOD__, [],
3565  [
3566  'page' => [
3567  'LEFT JOIN',
3568  [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3569  ]
3570  ]
3571  );
3572 
3573  $retVal = [];
3574  foreach ( $res as $row ) {
3575  $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3576  }
3577  return $retVal;
3578  }
3579 
3586  public function getCdnUrls() {
3587  $urls = [
3588  $this->getInternalURL(),
3589  $this->getInternalURL( 'action=history' )
3590  ];
3591 
3592  $pageLang = $this->getPageLanguage();
3593  if ( $pageLang->hasVariants() ) {
3594  $variants = $pageLang->getVariants();
3595  foreach ( $variants as $vCode ) {
3596  $urls[] = $this->getInternalURL( $vCode );
3597  }
3598  }
3599 
3600  // If we are looking at a css/js user subpage, purge the action=raw.
3601  if ( $this->isJsSubpage() ) {
3602  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3603  } elseif ( $this->isCssSubpage() ) {
3604  $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3605  }
3606 
3607  Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3608  return $urls;
3609  }
3610 
3614  public function getSquidURLs() {
3615  return $this->getCdnUrls();
3616  }
3617 
3621  public function purgeSquid() {
3623  new CdnCacheUpdate( $this->getCdnUrls() ),
3625  );
3626  }
3627 
3635  public function moveNoAuth( &$nt ) {
3636  wfDeprecated( __METHOD__, '1.25' );
3637  return $this->moveTo( $nt, false );
3638  }
3639 
3650  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3651  global $wgUser;
3652 
3653  if ( !( $nt instanceof Title ) ) {
3654  // Normally we'd add this to $errors, but we'll get
3655  // lots of syntax errors if $nt is not an object
3656  return [ [ 'badtitletext' ] ];
3657  }
3658 
3659  $mp = new MovePage( $this, $nt );
3660  $errors = $mp->isValidMove()->getErrorsArray();
3661  if ( $auth ) {
3662  $errors = wfMergeErrorArrays(
3663  $errors,
3664  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3665  );
3666  }
3667 
3668  return $errors ?: true;
3669  }
3670 
3677  protected function validateFileMoveOperation( $nt ) {
3678  global $wgUser;
3679 
3680  $errors = [];
3681 
3682  $destFile = wfLocalFile( $nt );
3683  $destFile->load( File::READ_LATEST );
3684  if ( !$wgUser->isAllowed( 'reupload-shared' )
3685  && !$destFile->exists() && wfFindFile( $nt )
3686  ) {
3687  $errors[] = [ 'file-exists-sharedrepo' ];
3688  }
3689 
3690  return $errors;
3691  }
3692 
3705  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
3706  global $wgUser;
3707  $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3708  if ( is_array( $err ) ) {
3709  // Auto-block user's IP if the account was "hard" blocked
3710  $wgUser->spreadAnyEditBlock();
3711  return $err;
3712  }
3713  // Check suppressredirect permission
3714  if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3715  $createRedirect = true;
3716  }
3717 
3718  $mp = new MovePage( $this, $nt );
3719  $status = $mp->move( $wgUser, $reason, $createRedirect );
3720  if ( $status->isOK() ) {
3721  return true;
3722  } else {
3723  return $status->getErrorsArray();
3724  }
3725  }
3726 
3739  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
3740  global $wgMaximumMovedPages;
3741  // Check permissions
3742  if ( !$this->userCan( 'move-subpages' ) ) {
3743  return [ 'cant-move-subpages' ];
3744  }
3745  // Do the source and target namespaces support subpages?
3746  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3747  return [ 'namespace-nosubpages',
3749  }
3750  if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3751  return [ 'namespace-nosubpages',
3752  MWNamespace::getCanonicalName( $nt->getNamespace() ) ];
3753  }
3754 
3755  $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3756  $retval = [];
3757  $count = 0;
3758  foreach ( $subpages as $oldSubpage ) {
3759  $count++;
3760  if ( $count > $wgMaximumMovedPages ) {
3761  $retval[$oldSubpage->getPrefixedText()] =
3762  [ 'movepage-max-pages',
3763  $wgMaximumMovedPages ];
3764  break;
3765  }
3766 
3767  // We don't know whether this function was called before
3768  // or after moving the root page, so check both
3769  // $this and $nt
3770  if ( $oldSubpage->getArticleID() == $this->getArticleID()
3771  || $oldSubpage->getArticleID() == $nt->getArticleID()
3772  ) {
3773  // When moving a page to a subpage of itself,
3774  // don't move it twice
3775  continue;
3776  }
3777  $newPageName = preg_replace(
3778  '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3779  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
3780  $oldSubpage->getDBkey() );
3781  if ( $oldSubpage->isTalkPage() ) {
3782  $newNs = $nt->getTalkPage()->getNamespace();
3783  } else {
3784  $newNs = $nt->getSubjectPage()->getNamespace();
3785  }
3786  # Bug 14385: we need makeTitleSafe because the new page names may
3787  # be longer than 255 characters.
3788  $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3789 
3790  $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
3791  if ( $success === true ) {
3792  $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3793  } else {
3794  $retval[$oldSubpage->getPrefixedText()] = $success;
3795  }
3796  }
3797  return $retval;
3798  }
3799 
3806  public function isSingleRevRedirect() {
3807  global $wgContentHandlerUseDB;
3808 
3809  $dbw = wfGetDB( DB_MASTER );
3810 
3811  # Is it a redirect?
3812  $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3813  if ( $wgContentHandlerUseDB ) {
3814  $fields[] = 'page_content_model';
3815  }
3816 
3817  $row = $dbw->selectRow( 'page',
3818  $fields,
3819  $this->pageCond(),
3820  __METHOD__,
3821  [ 'FOR UPDATE' ]
3822  );
3823  # Cache some fields we may want
3824  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3825  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3826  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3827  $this->mContentModel = $row && isset( $row->page_content_model )
3828  ? strval( $row->page_content_model )
3829  : false;
3830 
3831  if ( !$this->mRedirect ) {
3832  return false;
3833  }
3834  # Does the article have a history?
3835  $row = $dbw->selectField( [ 'page', 'revision' ],
3836  'rev_id',
3837  [ 'page_namespace' => $this->getNamespace(),
3838  'page_title' => $this->getDBkey(),
3839  'page_id=rev_page',
3840  'page_latest != rev_id'
3841  ],
3842  __METHOD__,
3843  [ 'FOR UPDATE' ]
3844  );
3845  # Return true if there was no history
3846  return ( $row === false );
3847  }
3848 
3857  public function isValidMoveTarget( $nt ) {
3858  # Is it an existing file?
3859  if ( $nt->getNamespace() == NS_FILE ) {
3860  $file = wfLocalFile( $nt );
3861  $file->load( File::READ_LATEST );
3862  if ( $file->exists() ) {
3863  wfDebug( __METHOD__ . ": file exists\n" );
3864  return false;
3865  }
3866  }
3867  # Is it a redirect with no history?
3868  if ( !$nt->isSingleRevRedirect() ) {
3869  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3870  return false;
3871  }
3872  # Get the article text
3874  if ( !is_object( $rev ) ) {
3875  return false;
3876  }
3877  $content = $rev->getContent();
3878  # Does the redirect point to the source?
3879  # Or is it a broken self-redirect, usually caused by namespace collisions?
3880  $redirTitle = $content ? $content->getRedirectTarget() : null;
3881 
3882  if ( $redirTitle ) {
3883  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3884  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3885  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3886  return false;
3887  } else {
3888  return true;
3889  }
3890  } else {
3891  # Fail safe (not a redirect after all. strange.)
3892  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3893  " is a redirect, but it doesn't contain a valid redirect.\n" );
3894  return false;
3895  }
3896  }
3897 
3905  public function getParentCategories() {
3907 
3908  $data = [];
3909 
3910  $titleKey = $this->getArticleID();
3911 
3912  if ( $titleKey === 0 ) {
3913  return $data;
3914  }
3915 
3916  $dbr = wfGetDB( DB_SLAVE );
3917 
3918  $res = $dbr->select(
3919  'categorylinks',
3920  'cl_to',
3921  [ 'cl_from' => $titleKey ],
3922  __METHOD__
3923  );
3924 
3925  if ( $res->numRows() > 0 ) {
3926  foreach ( $res as $row ) {
3927  // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3928  $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3929  }
3930  }
3931  return $data;
3932  }
3933 
3940  public function getParentCategoryTree( $children = [] ) {
3941  $stack = [];
3942  $parents = $this->getParentCategories();
3943 
3944  if ( $parents ) {
3945  foreach ( $parents as $parent => $current ) {
3946  if ( array_key_exists( $parent, $children ) ) {
3947  # Circular reference
3948  $stack[$parent] = [];
3949  } else {
3950  $nt = Title::newFromText( $parent );
3951  if ( $nt ) {
3952  $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3953  }
3954  }
3955  }
3956  }
3957 
3958  return $stack;
3959  }
3960 
3967  public function pageCond() {
3968  if ( $this->mArticleID > 0 ) {
3969  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3970  return [ 'page_id' => $this->mArticleID ];
3971  } else {
3972  return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3973  }
3974  }
3975 
3983  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3984  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3985  $revId = $db->selectField( 'revision', 'rev_id',
3986  [
3987  'rev_page' => $this->getArticleID( $flags ),
3988  'rev_id < ' . intval( $revId )
3989  ],
3990  __METHOD__,
3991  [ 'ORDER BY' => 'rev_id DESC' ]
3992  );
3993 
3994  if ( $revId === false ) {
3995  return false;
3996  } else {
3997  return intval( $revId );
3998  }
3999  }
4000 
4008  public function getNextRevisionID( $revId, $flags = 0 ) {
4009  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
4010  $revId = $db->selectField( 'revision', 'rev_id',
4011  [
4012  'rev_page' => $this->getArticleID( $flags ),
4013  'rev_id > ' . intval( $revId )
4014  ],
4015  __METHOD__,
4016  [ 'ORDER BY' => 'rev_id' ]
4017  );
4018 
4019  if ( $revId === false ) {
4020  return false;
4021  } else {
4022  return intval( $revId );
4023  }
4024  }
4025 
4032  public function getFirstRevision( $flags = 0 ) {
4033  $pageId = $this->getArticleID( $flags );
4034  if ( $pageId ) {
4035  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
4036  $row = $db->selectRow( 'revision', Revision::selectFields(),
4037  [ 'rev_page' => $pageId ],
4038  __METHOD__,
4039  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ]
4040  );
4041  if ( $row ) {
4042  return new Revision( $row );
4043  }
4044  }
4045  return null;
4046  }
4047 
4054  public function getEarliestRevTime( $flags = 0 ) {
4055  $rev = $this->getFirstRevision( $flags );
4056  return $rev ? $rev->getTimestamp() : null;
4057  }
4058 
4064  public function isNewPage() {
4065  $dbr = wfGetDB( DB_SLAVE );
4066  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4067  }
4068 
4074  public function isBigDeletion() {
4075  global $wgDeleteRevisionsLimit;
4076 
4077  if ( !$wgDeleteRevisionsLimit ) {
4078  return false;
4079  }
4080 
4081  if ( $this->mIsBigDeletion === null ) {
4082  $dbr = wfGetDB( DB_SLAVE );
4083 
4084  $revCount = $dbr->selectRowCount(
4085  'revision',
4086  '1',
4087  [ 'rev_page' => $this->getArticleID() ],
4088  __METHOD__,
4089  [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4090  );
4091 
4092  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4093  }
4094 
4095  return $this->mIsBigDeletion;
4096  }
4097 
4103  public function estimateRevisionCount() {
4104  if ( !$this->exists() ) {
4105  return 0;
4106  }
4107 
4108  if ( $this->mEstimateRevisions === null ) {
4109  $dbr = wfGetDB( DB_SLAVE );
4110  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4111  [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4112  }
4113 
4115  }
4116 
4126  public function countRevisionsBetween( $old, $new, $max = null ) {
4127  if ( !( $old instanceof Revision ) ) {
4128  $old = Revision::newFromTitle( $this, (int)$old );
4129  }
4130  if ( !( $new instanceof Revision ) ) {
4131  $new = Revision::newFromTitle( $this, (int)$new );
4132  }
4133  if ( !$old || !$new ) {
4134  return 0; // nothing to compare
4135  }
4136  $dbr = wfGetDB( DB_SLAVE );
4137  $conds = [
4138  'rev_page' => $this->getArticleID(),
4139  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4140  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4141  ];
4142  if ( $max !== null ) {
4143  return $dbr->selectRowCount( 'revision', '1',
4144  $conds,
4145  __METHOD__,
4146  [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4147  );
4148  } else {
4149  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4150  }
4151  }
4152 
4169  public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4170  if ( !( $old instanceof Revision ) ) {
4171  $old = Revision::newFromTitle( $this, (int)$old );
4172  }
4173  if ( !( $new instanceof Revision ) ) {
4174  $new = Revision::newFromTitle( $this, (int)$new );
4175  }
4176  // XXX: what if Revision objects are passed in, but they don't refer to this title?
4177  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4178  // in the sanity check below?
4179  if ( !$old || !$new ) {
4180  return null; // nothing to compare
4181  }
4182  $authors = [];
4183  $old_cmp = '>';
4184  $new_cmp = '<';
4185  $options = (array)$options;
4186  if ( in_array( 'include_old', $options ) ) {
4187  $old_cmp = '>=';
4188  }
4189  if ( in_array( 'include_new', $options ) ) {
4190  $new_cmp = '<=';
4191  }
4192  if ( in_array( 'include_both', $options ) ) {
4193  $old_cmp = '>=';
4194  $new_cmp = '<=';
4195  }
4196  // No DB query needed if $old and $new are the same or successive revisions:
4197  if ( $old->getId() === $new->getId() ) {
4198  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4199  [] :
4200  [ $old->getUserText( Revision::RAW ) ];
4201  } elseif ( $old->getId() === $new->getParentId() ) {
4202  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4203  $authors[] = $old->getUserText( Revision::RAW );
4204  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4205  $authors[] = $new->getUserText( Revision::RAW );
4206  }
4207  } elseif ( $old_cmp === '>=' ) {
4208  $authors[] = $old->getUserText( Revision::RAW );
4209  } elseif ( $new_cmp === '<=' ) {
4210  $authors[] = $new->getUserText( Revision::RAW );
4211  }
4212  return $authors;
4213  }
4214  $dbr = wfGetDB( DB_SLAVE );
4215  $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4216  [
4217  'rev_page' => $this->getArticleID(),
4218  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4219  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4220  ], __METHOD__,
4221  [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
4222  );
4223  foreach ( $res as $row ) {
4224  $authors[] = $row->rev_user_text;
4225  }
4226  return $authors;
4227  }
4228 
4243  public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4244  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4245  return $authors ? count( $authors ) : 0;
4246  }
4247 
4254  public function equals( Title $title ) {
4255  // Note: === is necessary for proper matching of number-like titles.
4256  return $this->getInterwiki() === $title->getInterwiki()
4257  && $this->getNamespace() == $title->getNamespace()
4258  && $this->getDBkey() === $title->getDBkey();
4259  }
4260 
4267  public function isSubpageOf( Title $title ) {
4268  return $this->getInterwiki() === $title->getInterwiki()
4269  && $this->getNamespace() == $title->getNamespace()
4270  && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4271  }
4272 
4284  public function exists( $flags = 0 ) {
4285  $exists = $this->getArticleID( $flags ) != 0;
4286  Hooks::run( 'TitleExists', [ $this, &$exists ] );
4287  return $exists;
4288  }
4289 
4306  public function isAlwaysKnown() {
4307  $isKnown = null;
4308 
4319  Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4320 
4321  if ( !is_null( $isKnown ) ) {
4322  return $isKnown;
4323  }
4324 
4325  if ( $this->isExternal() ) {
4326  return true; // any interwiki link might be viewable, for all we know
4327  }
4328 
4329  switch ( $this->mNamespace ) {
4330  case NS_MEDIA:
4331  case NS_FILE:
4332  // file exists, possibly in a foreign repo
4333  return (bool)wfFindFile( $this );
4334  case NS_SPECIAL:
4335  // valid special page
4336  return SpecialPageFactory::exists( $this->getDBkey() );
4337  case NS_MAIN:
4338  // selflink, possibly with fragment
4339  return $this->mDbkeyform == '';
4340  case NS_MEDIAWIKI:
4341  // known system message
4342  return $this->hasSourceText() !== false;
4343  default:
4344  return false;
4345  }
4346  }
4347 
4359  public function isKnown() {
4360  return $this->isAlwaysKnown() || $this->exists();
4361  }
4362 
4368  public function hasSourceText() {
4369  if ( $this->exists() ) {
4370  return true;
4371  }
4372 
4373  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4374  // If the page doesn't exist but is a known system message, default
4375  // message content will be displayed, same for language subpages-
4376  // Use always content language to avoid loading hundreds of languages
4377  // to get the link color.
4379  list( $name, ) = MessageCache::singleton()->figureMessage(
4380  $wgContLang->lcfirst( $this->getText() )
4381  );
4382  $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4383  return $message->exists();
4384  }
4385 
4386  return false;
4387  }
4388 
4394  public function getDefaultMessageText() {
4396 
4397  if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4398  return false;
4399  }
4400 
4401  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4402  $wgContLang->lcfirst( $this->getText() )
4403  );
4404  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4405 
4406  if ( $message->exists() ) {
4407  return $message->plain();
4408  } else {
4409  return false;
4410  }
4411  }
4412 
4419  public function invalidateCache( $purgeTime = null ) {
4420  if ( wfReadOnly() ) {
4421  return false;
4422  }
4423 
4424  if ( $this->mArticleID === 0 ) {
4425  return true; // avoid gap locking if we know it's not there
4426  }
4427 
4428  $method = __METHOD__;
4429  $dbw = wfGetDB( DB_MASTER );
4430  $conds = $this->pageCond();
4431  $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method, $purgeTime ) {
4432  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4433 
4434  $dbw->update(
4435  'page',
4436  [ 'page_touched' => $dbTimestamp ],
4437  $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4438  $method
4439  );
4440  } );
4441 
4442  return true;
4443  }
4444 
4450  public function touchLinks() {
4451  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4452  if ( $this->getNamespace() == NS_CATEGORY ) {
4453  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4454  }
4455  }
4456 
4463  public function getTouched( $db = null ) {
4464  if ( $db === null ) {
4465  $db = wfGetDB( DB_SLAVE );
4466  }
4467  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4468  return $touched;
4469  }
4470 
4477  public function getNotificationTimestamp( $user = null ) {
4478  global $wgUser;
4479 
4480  // Assume current user if none given
4481  if ( !$user ) {
4482  $user = $wgUser;
4483  }
4484  // Check cache first
4485  $uid = $user->getId();
4486  if ( !$uid ) {
4487  return false;
4488  }
4489  // avoid isset here, as it'll return false for null entries
4490  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4491  return $this->mNotificationTimestamp[$uid];
4492  }
4493  // Don't cache too much!
4494  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4495  $this->mNotificationTimestamp = [];
4496  }
4497 
4498  $watchedItem = WatchedItemStore::getDefaultInstance()->getWatchedItem( $user, $this );
4499  if ( $watchedItem ) {
4500  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4501  } else {
4502  $this->mNotificationTimestamp[$uid] = false;
4503  }
4504 
4505  return $this->mNotificationTimestamp[$uid];
4506  }
4507 
4514  public function getNamespaceKey( $prepend = 'nstab-' ) {
4516  // Gets the subject namespace if this title
4517  $namespace = MWNamespace::getSubject( $this->getNamespace() );
4518  // Checks if canonical namespace name exists for namespace
4519  if ( MWNamespace::exists( $this->getNamespace() ) ) {
4520  // Uses canonical namespace name
4521  $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4522  } else {
4523  // Uses text of namespace
4524  $namespaceKey = $this->getSubjectNsText();
4525  }
4526  // Makes namespace key lowercase
4527  $namespaceKey = $wgContLang->lc( $namespaceKey );
4528  // Uses main
4529  if ( $namespaceKey == '' ) {
4530  $namespaceKey = 'main';
4531  }
4532  // Changes file to image for backwards compatibility
4533  if ( $namespaceKey == 'file' ) {
4534  $namespaceKey = 'image';
4535  }
4536  return $prepend . $namespaceKey;
4537  }
4538 
4545  public function getRedirectsHere( $ns = null ) {
4546  $redirs = [];
4547 
4548  $dbr = wfGetDB( DB_SLAVE );
4549  $where = [
4550  'rd_namespace' => $this->getNamespace(),
4551  'rd_title' => $this->getDBkey(),
4552  'rd_from = page_id'
4553  ];
4554  if ( $this->isExternal() ) {
4555  $where['rd_interwiki'] = $this->getInterwiki();
4556  } else {
4557  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4558  }
4559  if ( !is_null( $ns ) ) {
4560  $where['page_namespace'] = $ns;
4561  }
4562 
4563  $res = $dbr->select(
4564  [ 'redirect', 'page' ],
4565  [ 'page_namespace', 'page_title' ],
4566  $where,
4567  __METHOD__
4568  );
4569 
4570  foreach ( $res as $row ) {
4571  $redirs[] = self::newFromRow( $row );
4572  }
4573  return $redirs;
4574  }
4575 
4581  public function isValidRedirectTarget() {
4582  global $wgInvalidRedirectTargets;
4583 
4584  if ( $this->isSpecialPage() ) {
4585  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4586  if ( $this->isSpecial( 'Userlogout' ) ) {
4587  return false;
4588  }
4589 
4590  foreach ( $wgInvalidRedirectTargets as $target ) {
4591  if ( $this->isSpecial( $target ) ) {
4592  return false;
4593  }
4594  }
4595  }
4596 
4597  return true;
4598  }
4599 
4605  public function getBacklinkCache() {
4606  return BacklinkCache::get( $this );
4607  }
4608 
4614  public function canUseNoindex() {
4615  global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
4616 
4617  $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4618  ? $wgContentNamespaces
4619  : $wgExemptFromUserRobotsControl;
4620 
4621  return !in_array( $this->mNamespace, $bannedNamespaces );
4622 
4623  }
4624 
4635  public function getCategorySortkey( $prefix = '' ) {
4636  $unprefixed = $this->getText();
4637 
4638  // Anything that uses this hook should only depend
4639  // on the Title object passed in, and should probably
4640  // tell the users to run updateCollations.php --force
4641  // in order to re-sort existing category relations.
4642  Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4643  if ( $prefix !== '' ) {
4644  # Separate with a line feed, so the unprefixed part is only used as
4645  # a tiebreaker when two pages have the exact same prefix.
4646  # In UCA, tab is the only character that can sort above LF
4647  # so we strip both of them from the original prefix.
4648  $prefix = strtr( $prefix, "\n\t", ' ' );
4649  return "$prefix\n$unprefixed";
4650  }
4651  return $unprefixed;
4652  }
4653 
4661  private function getDbPageLanguageCode() {
4662  global $wgPageLanguageUseDB;
4663 
4664  // check, if the page language could be saved in the database, and if so and
4665  // the value is not requested already, lookup the page language using LinkCache
4666  if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4667  $linkCache = LinkCache::singleton();
4668  $linkCache->addLinkObj( $this );
4669  $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4670  }
4671 
4672  return $this->mDbPageLanguage;
4673  }
4674 
4683  public function getPageLanguage() {
4685  if ( $this->isSpecialPage() ) {
4686  // special pages are in the user language
4687  return $wgLang;
4688  }
4689 
4690  // Checking if DB language is set
4691  $dbPageLanguage = $this->getDbPageLanguageCode();
4692  if ( $dbPageLanguage ) {
4693  return wfGetLangObj( $dbPageLanguage );
4694  }
4695 
4696  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4697  // Note that this may depend on user settings, so the cache should
4698  // be only per-request.
4699  // NOTE: ContentHandler::getPageLanguage() may need to load the
4700  // content to determine the page language!
4701  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4702  // tests.
4703  $contentHandler = ContentHandler::getForTitle( $this );
4704  $langObj = $contentHandler->getPageLanguage( $this );
4705  $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4706  } else {
4707  $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4708  }
4709 
4710  return $langObj;
4711  }
4712 
4721  public function getPageViewLanguage() {
4722  global $wgLang;
4723 
4724  if ( $this->isSpecialPage() ) {
4725  // If the user chooses a variant, the content is actually
4726  // in a language whose code is the variant code.
4727  $variant = $wgLang->getPreferredVariant();
4728  if ( $wgLang->getCode() !== $variant ) {
4729  return Language::factory( $variant );
4730  }
4731 
4732  return $wgLang;
4733  }
4734 
4735  // Checking if DB language is set
4736  $dbPageLanguage = $this->getDbPageLanguageCode();
4737  if ( $dbPageLanguage ) {
4738  $pageLang = wfGetLangObj( $dbPageLanguage );
4739  $variant = $pageLang->getPreferredVariant();
4740  if ( $pageLang->getCode() !== $variant ) {
4741  $pageLang = Language::factory( $variant );
4742  }
4743 
4744  return $pageLang;
4745  }
4746 
4747  // @note Can't be cached persistently, depends on user settings.
4748  // @note ContentHandler::getPageViewLanguage() may need to load the
4749  // content to determine the page language!
4750  $contentHandler = ContentHandler::getForTitle( $this );
4751  $pageLang = $contentHandler->getPageViewLanguage( $this );
4752  return $pageLang;
4753  }
4754 
4765  public function getEditNotices( $oldid = 0 ) {
4766  $notices = [];
4767 
4768  // Optional notice for the entire namespace
4769  $editnotice_ns = 'editnotice-' . $this->getNamespace();
4770  $msg = wfMessage( $editnotice_ns );
4771  if ( $msg->exists() ) {
4772  $html = $msg->parseAsBlock();
4773  // Edit notices may have complex logic, but output nothing (T91715)
4774  if ( trim( $html ) !== '' ) {
4775  $notices[$editnotice_ns] = Html::rawElement(
4776  'div',
4777  [ 'class' => [
4778  'mw-editnotice',
4779  'mw-editnotice-namespace',
4780  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4781  ] ],
4782  $html
4783  );
4784  }
4785  }
4786 
4787  if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4788  // Optional notice for page itself and any parent page
4789  $parts = explode( '/', $this->getDBkey() );
4790  $editnotice_base = $editnotice_ns;
4791  while ( count( $parts ) > 0 ) {
4792  $editnotice_base .= '-' . array_shift( $parts );
4793  $msg = wfMessage( $editnotice_base );
4794  if ( $msg->exists() ) {
4795  $html = $msg->parseAsBlock();
4796  if ( trim( $html ) !== '' ) {
4797  $notices[$editnotice_base] = Html::rawElement(
4798  'div',
4799  [ 'class' => [
4800  'mw-editnotice',
4801  'mw-editnotice-base',
4802  Sanitizer::escapeClass( "mw-$editnotice_base" )
4803  ] ],
4804  $html
4805  );
4806  }
4807  }
4808  }
4809  } else {
4810  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4811  $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4812  $msg = wfMessage( $editnoticeText );
4813  if ( $msg->exists() ) {
4814  $html = $msg->parseAsBlock();
4815  if ( trim( $html ) !== '' ) {
4816  $notices[$editnoticeText] = Html::rawElement(
4817  'div',
4818  [ 'class' => [
4819  'mw-editnotice',
4820  'mw-editnotice-page',
4821  Sanitizer::escapeClass( "mw-$editnoticeText" )
4822  ] ],
4823  $html
4824  );
4825  }
4826  }
4827  }
4828 
4829  Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4830  return $notices;
4831  }
4832 
4836  public function __sleep() {
4837  return [
4838  'mNamespace',
4839  'mDbkeyform',
4840  'mFragment',
4841  'mInterwiki',
4842  'mLocalInterwiki',
4843  'mUserCaseDBKey',
4844  'mDefaultNamespace',
4845  ];
4846  }
4847 
4848  public function __wakeup() {
4849  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4850  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4851  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4852  }
4853 
4854 }
getEarliestRevTime($flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:4054
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:4306
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:3066
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3290
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:4450
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:1802
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:4284
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
getFullUrlForRedirect($query= '', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition: Title.php:1701
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:1855
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:766
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:1422
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition: Title.php:4614
isJsSubpage()
Is this a .js subpage of a user page?
Definition: Title.php:1280
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:278
wasLocalInterwiki()
Was this a local interwiki link?
Definition: Title.php:830
getSquidURLs()
Definition: Title.php:3614
$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:2325
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:2723
isSpecial($name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1055
static clearCaches()
Definition: Title.php:3344
getArticleID($flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3201
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:3094
const NS_MAIN
Definition: Defines.php:70
$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:3739
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition: Title.php:4394
getEditNotices($oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:4765
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:4848
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:1802
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:2495
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:2343
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:3381
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:3635
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:4661
null for the local wiki Added in
Definition: hooks.txt:1422
isRedirect($flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3227
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:4074
set($key, $value, $exptime=0, $flags=0)
const NS_SPECIAL
Definition: Defines.php:59
const PROTO_CURRENT
Definition: Defines.php:265
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:1008
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:3538
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:2552
isValidMoveTarget($nt)
Checks if $this can be moved to a given Title.
Definition: Title.php:3857
static escapeClass($class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1209
static escapeRegexReplacement($string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter...
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1449
static exists($index)
Returns whether the specified namespace exists.
wfUrlencode($s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:277
getBaseTitle()
Get the base page name title, i.e.
Definition: Title.php:1553
Represents a title within MediaWiki.
Definition: Title.php:34
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
wfExpandUrl($url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
getParentCategories()
Get categories to which this Title belongs and return an array of categories' names.
Definition: Title.php:3905
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4605
$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:1982
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:2556
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:969
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:4477
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:4593
getSubpages($limit=-1)
Get all subpages of this page.
Definition: Title.php:3122
userCan($action, $user=null, $rigor= 'secure')
Can $user perform $action on this page?
Definition: Title.php:1927
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:1800
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:3058
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:3262
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:3650
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:4008
loadRestrictionsFromResultWrapper($res, $oldFashionedRestrictions=null)
Loads a string into mRestrictions array.
Definition: Title.php:2928
__sleep()
Definition: Title.php:4836
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:2645
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:4573
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:4368
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:3421
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:1008
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition: Title.php:2913
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition: Title.php:1195
const NS_MEDIA
Definition: Defines.php:58
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:4463
getLocalURL($query= '', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1735
$res
Definition: database.txt:21
checkCSSandJSPermissions($action, $user, $errors, $rigor, $short)
Check CSS/JS sub-page permissions.
Definition: Title.php:2149
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:4581
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:2741
invalidateCache($purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4419
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition: Title.php:3586
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:1951
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:3485
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition: Title.php:1342
const NS_CATEGORY
Definition: Defines.php:84
moveTo(&$nt, $auth=true, $reason= '', $createRedirect=true)
Move a title to a new location.
Definition: Title.php:3705
getRedirectsHere($ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4545
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:2120
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:1877
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:4267
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:3677
isCssSubpage()
Is this a .css subpage of a user page?
Definition: Title.php:1270
const DB_SLAVE
Definition: Defines.php:47
isMainPage()
Is this the mainpage?
Definition: Title.php:1175
static decodeCharReferencesAndNormalize($text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1479
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:916
getAuthorsBetween($old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition: Title.php:4169
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:264
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
string $mInterwiki
Interwiki prefix.
Definition: Title.php:74
equals(Title $title)
Compare with another title.
Definition: Title.php:4254
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:4359
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:3469
const NS_FILE
Definition: Defines.php:76
static newFromResult($res)
Definition: TitleArray.php:38
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2858
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:1588
checkCascadingSourcesRestrictions($action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition: Title.php:2219
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:2755
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:916
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:3550
const NS_MEDIAWIKI
Definition: Defines.php:78
const PROTO_HTTP
Definition: Defines.php:262
getNamespaceKey($prepend= 'nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition: Title.php:4514
countRevisionsBetween($old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:4126
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:246
static newFromTitleValue(TitleValue $titleValue)
Create a new Title from a TitleValue.
Definition: Title.php:240
getRestrictions($action)
Accessor/initialisation for mRestrictions.
Definition: Title.php:2871
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:4032
static escapeId($id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it. ...
Definition: Sanitizer.php:1132
static newFromTextThrow($text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid, rather than returning null.
Definition: Title.php:307
isSpecialPage()
Returns true if this is a special page.
Definition: Title.php:1045
$wgContentNamespaces
Array of namespaces which can be deemed to contain valid "content", as far as the site statistics are...
quickUserCan($action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition: Title.php:1914
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:1891
$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:3020
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:3806
const PROTO_CANONICAL
Definition: Defines.php:266
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:1832
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition: Title.php:4721
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:2574
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:1008
getCategorySortkey($prefix= '')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition: Title.php:4635
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:4683
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:2385
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:2772
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:3967
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:1008
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition: design.txt:56
getInterwiki()
The interwiki component of this LinkTarget.
const CONTENT_MODEL_CSS
Definition: Defines.php:280
string $mPrefixedText
Text form including namespace/interwiki, initialised on demand.
Definition: Title.php:122
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition: Title.php:3175
isNewPage()
Check if this is a new page.
Definition: Title.php:4064
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:2460
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:1008
isProtected($action= '')
Does the title correspond to a protected article?
Definition: Title.php:2691
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:2266
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:3150
const DB_MASTER
Definition: Defines.php:48
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:2947
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:2601
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:4243
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:2185
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:2083
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:3940
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:2887
isSemiProtected($action= 'edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2663
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:2052
getRestrictionExpiry($action)
Get the expiry time for the restriction against a given action.
Definition: Title.php:2901
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:246
wfFindFile($title, $options=[])
Find a file.
getPreviousRevisionID($revId, $flags=0)
Get the revision ID of the previous revision.
Definition: Title.php:3983
$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:2342
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:3359
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:3621
static getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4550
resetArticleID($newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3322
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:2342
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:3427
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:4103
$wgUser
Definition: Setup.php:794
$matches
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition: Title.php:68
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:314