MediaWiki  master
Title.php
Go to the documentation of this file.
1 <?php
33 class Title {
35  static private $titleCache = null;
36 
42  const CACHE_MAX = 1000;
43 
48  const GAID_FOR_UPDATE = 1;
49 
55  // @{
56 
58  public $mTextform = '';
59 
61  public $mUrlform = '';
62 
64  public $mDbkeyform = '';
65 
67  protected $mUserCaseDBKey;
68 
70  public $mNamespace = NS_MAIN;
71 
73  public $mInterwiki = '';
74 
76  private $mLocalInterwiki = false;
77 
79  public $mFragment = '';
80 
82  public $mArticleID = -1;
83 
85  protected $mLatestID = false;
86 
91  public $mContentModel = false;
92 
94  private $mEstimateRevisions;
95 
97  public $mRestrictions = array();
98 
100  protected $mOldRestrictions = false;
101 
103  public $mCascadeRestriction;
107 
109  protected $mRestrictionsExpiry = array();
112  protected $mHasCascadingRestrictions;
113 
116 
118  public $mRestrictionsLoaded = false;
121  protected $mPrefixedText = null;
125 
131  public $mDefaultNamespace = NS_MAIN;
137  protected $mWatched = null;
138 
140  protected $mLength = -1;
141 
143  public $mRedirect = null;
144 
146  private $mNotificationTimestamp = array();
147 
149  private $mHasSubpages;
150 
152  private $mPageLanguage = false;
153 
155  private $mDbPageLanguage = null;
156 
158  private $mTitleValue = null;
159 
161  private $mIsBigDeletion = null;
162  // @}
163 
172  private static function getTitleParser() {
174 
175  static $titleCodec = null;
176  static $titleCodecFingerprint = null;
177 
178  // $wgContLang and $wgLocalInterwikis may change (especially while testing),
179  // make sure we are using the right one. To detect changes over the course
180  // of a request, we remember a fingerprint of the config used to create the
181  // codec singleton, and re-create it if the fingerprint doesn't match.
182  $fingerprint = spl_object_hash( $wgContLang ) . '|' . join( '+', $wgLocalInterwikis );
183 
184  if ( $fingerprint !== $titleCodecFingerprint ) {
185  $titleCodec = null;
186  }
187 
188  if ( !$titleCodec ) {
189  $titleCodec = new MediaWikiTitleCodec(
190  $wgContLang,
192  $wgLocalInterwikis
193  );
194  $titleCodecFingerprint = $fingerprint;
195  }
197  return $titleCodec;
198  }
199 
208  private static function getTitleFormatter() {
209  // NOTE: we know that getTitleParser() returns a MediaWikiTitleCodec,
210  // which implements TitleFormatter.
211  return self::getTitleParser();
212  }
213 
214  function __construct() {
215  }
216 
225  public static function newFromDBkey( $key ) {
226  $t = new Title();
227  $t->mDbkeyform = $key;
228 
229  try {
230  $t->secureAndSplit();
231  return $t;
232  } catch ( MalformedTitleException $ex ) {
233  return null;
234  }
235  }
236 
244  public static function newFromTitleValue( TitleValue $titleValue ) {
245  return self::makeTitle(
246  $titleValue->getNamespace(),
247  $titleValue->getText(),
248  $titleValue->getFragment() );
249  }
250 
264  public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
265  if ( is_object( $text ) ) {
266  throw new InvalidArgumentException( '$text must be a string.' );
267  } elseif ( !is_string( $text ) ) {
268  wfDebugLog( 'T76305', wfGetAllCallers( 5 ) );
269  wfWarn(
270  __METHOD__ . ': $text must be a string. ' .
271  'This will throw an InvalidArgumentException in future.',
272  2
273  );
274  }
275 
276  try {
277  return Title::newFromTextThrow( $text, $defaultNamespace );
278  } catch ( MalformedTitleException $ex ) {
279  return null;
280  }
281  }
282 
297  public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
298  if ( is_object( $text ) ) {
299  throw new MWException( 'Title::newFromTextThrow given an object' );
300  }
301 
302  $cache = self::getTitleCache();
303 
312  if ( $defaultNamespace == NS_MAIN && $cache->has( $text ) ) {
313  return $cache->get( $text );
314  }
315 
316  # Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
317  $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
318 
319  $t = new Title();
320  $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
321  $t->mDefaultNamespace = intval( $defaultNamespace );
322 
323  $t->secureAndSplit();
324  if ( $defaultNamespace == NS_MAIN ) {
325  $cache->set( $text, $t );
326  }
327  return $t;
328  }
329 
345  public static function newFromURL( $url ) {
346  $t = new Title();
347 
348  # For compatibility with old buggy URLs. "+" is usually not valid in titles,
349  # but some URLs used it as a space replacement and they still come
350  # from some external search tools.
351  if ( strpos( self::legalChars(), '+' ) === false ) {
352  $url = strtr( $url, '+', ' ' );
353  }
354 
355  $t->mDbkeyform = strtr( $url, ' ', '_' );
356 
357  try {
358  $t->secureAndSplit();
359  return $t;
360  } catch ( MalformedTitleException $ex ) {
361  return null;
362  }
363  }
364 
368  private static function getTitleCache() {
369  if ( self::$titleCache == null ) {
370  self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
371  }
372  return self::$titleCache;
373  }
374 
382  protected static function getSelectFields() {
384 
385  $fields = array(
386  'page_namespace', 'page_title', 'page_id',
387  'page_len', 'page_is_redirect', 'page_latest',
388  );
389 
390  if ( $wgContentHandlerUseDB ) {
391  $fields[] = 'page_content_model';
392  }
393 
394  return $fields;
395  }
396 
404  public static function newFromID( $id, $flags = 0 ) {
405  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
406  $row = $db->selectRow(
407  'page',
408  self::getSelectFields(),
409  array( 'page_id' => $id ),
410  __METHOD__
411  );
412  if ( $row !== false ) {
413  $title = Title::newFromRow( $row );
414  } else {
415  $title = null;
416  }
417  return $title;
418  }
419 
426  public static function newFromIDs( $ids ) {
427  if ( !count( $ids ) ) {
428  return array();
429  }
430  $dbr = wfGetDB( DB_SLAVE );
431 
432  $res = $dbr->select(
433  'page',
434  self::getSelectFields(),
435  array( 'page_id' => $ids ),
436  __METHOD__
437  );
438 
439  $titles = array();
440  foreach ( $res as $row ) {
441  $titles[] = Title::newFromRow( $row );
442  }
443  return $titles;
444  }
445 
452  public static function newFromRow( $row ) {
453  $t = self::makeTitle( $row->page_namespace, $row->page_title );
454  $t->loadFromRow( $row );
455  return $t;
456  }
457 
464  public function loadFromRow( $row ) {
465  if ( $row ) { // page found
466  if ( isset( $row->page_id ) ) {
467  $this->mArticleID = (int)$row->page_id;
468  }
469  if ( isset( $row->page_len ) ) {
470  $this->mLength = (int)$row->page_len;
471  }
472  if ( isset( $row->page_is_redirect ) ) {
473  $this->mRedirect = (bool)$row->page_is_redirect;
474  }
475  if ( isset( $row->page_latest ) ) {
476  $this->mLatestID = (int)$row->page_latest;
477  }
478  if ( isset( $row->page_content_model ) ) {
479  $this->mContentModel = strval( $row->page_content_model );
480  } else {
481  $this->mContentModel = false; # initialized lazily in getContentModel()
482  }
483  if ( isset( $row->page_lang ) ) {
484  $this->mDbPageLanguage = (string)$row->page_lang;
485  }
486  if ( isset( $row->page_restrictions ) ) {
487  $this->mOldRestrictions = $row->page_restrictions;
488  }
489  } else { // page not found
490  $this->mArticleID = 0;
491  $this->mLength = 0;
492  $this->mRedirect = false;
493  $this->mLatestID = 0;
494  $this->mContentModel = false; # initialized lazily in getContentModel()
495  }
496  }
497 
511  public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
512  $t = new Title();
513  $t->mInterwiki = $interwiki;
514  $t->mFragment = $fragment;
515  $t->mNamespace = $ns = intval( $ns );
516  $t->mDbkeyform = strtr( $title, ' ', '_' );
517  $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
518  $t->mUrlform = wfUrlencode( $t->mDbkeyform );
519  $t->mTextform = strtr( $title, '_', ' ' );
520  $t->mContentModel = false; # initialized lazily in getContentModel()
521  return $t;
522  }
523 
535  public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
536  if ( !MWNamespace::exists( $ns ) ) {
537  return null;
538  }
539 
540  $t = new Title();
541  $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
542 
543  try {
544  $t->secureAndSplit();
545  return $t;
546  } catch ( MalformedTitleException $ex ) {
547  return null;
548  }
549  }
550 
556  public static function newMainPage() {
557  $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
558  // Don't give fatal errors if the message is broken
559  if ( !$title ) {
560  $title = Title::newFromText( 'Main Page' );
561  }
562  return $title;
563  }
564 
575  public static function newFromRedirect( $text ) {
576  ContentHandler::deprecated( __METHOD__, '1.21' );
577 
579  return $content->getRedirectTarget();
580  }
581 
592  public static function newFromRedirectRecurse( $text ) {
593  ContentHandler::deprecated( __METHOD__, '1.21' );
594 
596  return $content->getUltimateRedirectTarget();
597  }
598 
609  public static function newFromRedirectArray( $text ) {
610  ContentHandler::deprecated( __METHOD__, '1.21' );
611 
613  return $content->getRedirectChain();
614  }
622  public static function nameOf( $id ) {
623  $dbr = wfGetDB( DB_SLAVE );
624 
625  $s = $dbr->selectRow(
626  'page',
627  array( 'page_namespace', 'page_title' ),
628  array( 'page_id' => $id ),
629  __METHOD__
630  );
631  if ( $s === false ) {
632  return null;
633  }
634 
635  $n = self::makeName( $s->page_namespace, $s->page_title );
636  return $n;
637  }
638 
644  public static function legalChars() {
646  return $wgLegalTitleChars;
647  }
648 
658  static function getTitleInvalidRegex() {
659  wfDeprecated( __METHOD__, '1.25' );
661  }
662 
672  public static function convertByteClassToUnicodeClass( $byteClass ) {
673  $length = strlen( $byteClass );
674  // Input token queue
675  $x0 = $x1 = $x2 = '';
676  // Decoded queue
677  $d0 = $d1 = $d2 = '';
678  // Decoded integer codepoints
679  $ord0 = $ord1 = $ord2 = 0;
680  // Re-encoded queue
681  $r0 = $r1 = $r2 = '';
682  // Output
683  $out = '';
684  // Flags
685  $allowUnicode = false;
686  for ( $pos = 0; $pos < $length; $pos++ ) {
687  // Shift the queues down
688  $x2 = $x1;
689  $x1 = $x0;
690  $d2 = $d1;
691  $d1 = $d0;
692  $ord2 = $ord1;
693  $ord1 = $ord0;
694  $r2 = $r1;
695  $r1 = $r0;
696  // Load the current input token and decoded values
697  $inChar = $byteClass[$pos];
698  if ( $inChar == '\\' ) {
699  if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
700  $x0 = $inChar . $m[0];
701  $d0 = chr( hexdec( $m[1] ) );
702  $pos += strlen( $m[0] );
703  } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
704  $x0 = $inChar . $m[0];
705  $d0 = chr( octdec( $m[0] ) );
706  $pos += strlen( $m[0] );
707  } elseif ( $pos + 1 >= $length ) {
708  $x0 = $d0 = '\\';
709  } else {
710  $d0 = $byteClass[$pos + 1];
711  $x0 = $inChar . $d0;
712  $pos += 1;
713  }
714  } else {
715  $x0 = $d0 = $inChar;
716  }
717  $ord0 = ord( $d0 );
718  // Load the current re-encoded value
719  if ( $ord0 < 32 || $ord0 == 0x7f ) {
720  $r0 = sprintf( '\x%02x', $ord0 );
721  } elseif ( $ord0 >= 0x80 ) {
722  // Allow unicode if a single high-bit character appears
723  $r0 = sprintf( '\x%02x', $ord0 );
724  $allowUnicode = true;
725  } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
726  $r0 = '\\' . $d0;
727  } else {
728  $r0 = $d0;
729  }
730  // Do the output
731  if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
732  // Range
733  if ( $ord2 > $ord0 ) {
734  // Empty range
735  } elseif ( $ord0 >= 0x80 ) {
736  // Unicode range
737  $allowUnicode = true;
738  if ( $ord2 < 0x80 ) {
739  // Keep the non-unicode section of the range
740  $out .= "$r2-\\x7F";
741  }
742  } else {
743  // Normal range
744  $out .= "$r2-$r0";
745  }
746  // Reset state to the initial value
747  $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
748  } elseif ( $ord2 < 0x80 ) {
749  // ASCII character
750  $out .= $r2;
751  }
752  }
753  if ( $ord1 < 0x80 ) {
754  $out .= $r1;
755  }
756  if ( $ord0 < 0x80 ) {
757  $out .= $r0;
758  }
759  if ( $allowUnicode ) {
760  $out .= '\u0080-\uFFFF';
761  }
762  return $out;
763  }
764 
776  public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
777  $canoncialNamespace = false
778  ) {
780 
781  if ( $canoncialNamespace ) {
782  $namespace = MWNamespace::getCanonicalName( $ns );
783  } else {
784  $namespace = $wgContLang->getNsText( $ns );
785  }
786  $name = $namespace == '' ? $title : "$namespace:$title";
787  if ( strval( $interwiki ) != '' ) {
788  $name = "$interwiki:$name";
789  }
790  if ( strval( $fragment ) != '' ) {
791  $name .= '#' . $fragment;
792  }
793  return $name;
794  }
795 
802  static function escapeFragmentForURL( $fragment ) {
803  # Note that we don't urlencode the fragment. urlencoded Unicode
804  # fragments appear not to work in IE (at least up to 7) or in at least
805  # one version of Opera 9.x. The W3C validator, for one, doesn't seem
806  # to care if they aren't encoded.
807  return Sanitizer::escapeId( $fragment, 'noninitial' );
808  }
809 
818  public static function compare( $a, $b ) {
819  if ( $a->getNamespace() == $b->getNamespace() ) {
820  return strcmp( $a->getText(), $b->getText() );
821  } else {
822  return $a->getNamespace() - $b->getNamespace();
823  }
824  }
825 
833  public function isLocal() {
834  if ( $this->isExternal() ) {
835  $iw = Interwiki::fetch( $this->mInterwiki );
836  if ( $iw ) {
837  return $iw->isLocal();
838  }
839  }
840  return true;
841  }
842 
848  public function isExternal() {
849  return $this->mInterwiki !== '';
850  }
851 
859  public function getInterwiki() {
860  return $this->mInterwiki;
861  }
868  public function wasLocalInterwiki() {
869  return $this->mLocalInterwiki;
870  }
871 
878  public function isTrans() {
879  if ( !$this->isExternal() ) {
880  return false;
881  }
882 
883  return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
884  }
885 
891  public function getTransWikiID() {
892  if ( !$this->isExternal() ) {
893  return false;
894  }
895 
896  return Interwiki::fetch( $this->mInterwiki )->getWikiID();
897  }
898 
908  public function getTitleValue() {
909  if ( $this->mTitleValue === null ) {
910  try {
911  $this->mTitleValue = new TitleValue(
912  $this->getNamespace(),
913  $this->getDBkey(),
914  $this->getFragment() );
915  } catch ( InvalidArgumentException $ex ) {
916  wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
917  $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
918  }
919  }
920 
921  return $this->mTitleValue;
922  }
923 
929  public function getText() {
930  return $this->mTextform;
931  }
932 
938  public function getPartialURL() {
939  return $this->mUrlform;
940  }
947  public function getDBkey() {
948  return $this->mDbkeyform;
949  }
950 
956  function getUserCaseDBKey() {
957  if ( !is_null( $this->mUserCaseDBKey ) ) {
958  return $this->mUserCaseDBKey;
959  } else {
960  // If created via makeTitle(), $this->mUserCaseDBKey is not set.
961  return $this->mDbkeyform;
962  }
963  }
964 
970  public function getNamespace() {
972  }
973 
980  public function getContentModel( $flags = 0 ) {
981  if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
982  $linkCache = LinkCache::singleton();
983  $linkCache->addLinkObj( $this ); # in case we already had an article ID
984  $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
985  }
986 
987  if ( !$this->mContentModel ) {
988  $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
989  }
990 
991  return $this->mContentModel;
992  }
993 
1000  public function hasContentModel( $id ) {
1001  return $this->getContentModel() == $id;
1002  }
1003 
1009  public function getNsText() {
1010  if ( $this->isExternal() ) {
1011  // This probably shouldn't even happen,
1012  // but for interwiki transclusion it sometimes does.
1013  // Use the canonical namespaces if possible to try to
1014  // resolve a foreign namespace.
1015  if ( MWNamespace::exists( $this->mNamespace ) ) {
1016  return MWNamespace::getCanonicalName( $this->mNamespace );
1017  }
1018  }
1019 
1020  try {
1021  $formatter = self::getTitleFormatter();
1022  return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
1023  } catch ( InvalidArgumentException $ex ) {
1024  wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
1025  return false;
1026  }
1027  }
1028 
1034  public function getSubjectNsText() {
1036  return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1037  }
1038 
1044  public function getTalkNsText() {
1046  return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1047  }
1048 
1054  public function canTalk() {
1055  return MWNamespace::canTalk( $this->mNamespace );
1056  }
1057 
1063  public function canExist() {
1064  return $this->mNamespace >= NS_MAIN;
1065  }
1066 
1072  public function isWatchable() {
1073  return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
1074  }
1075 
1081  public function isSpecialPage() {
1082  return $this->getNamespace() == NS_SPECIAL;
1083  }
1084 
1091  public function isSpecial( $name ) {
1092  if ( $this->isSpecialPage() ) {
1093  list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
1094  if ( $name == $thisName ) {
1095  return true;
1096  }
1097  }
1098  return false;
1099  }
1100 
1107  public function fixSpecialName() {
1108  if ( $this->isSpecialPage() ) {
1109  list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
1110  if ( $canonicalName ) {
1111  $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
1112  if ( $localName != $this->mDbkeyform ) {
1113  return Title::makeTitle( NS_SPECIAL, $localName );
1114  }
1115  }
1116  }
1117  return $this;
1118  }
1119 
1130  public function inNamespace( $ns ) {
1131  return MWNamespace::equals( $this->getNamespace(), $ns );
1132  }
1133 
1141  public function inNamespaces( /* ... */ ) {
1142  $namespaces = func_get_args();
1143  if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1144  $namespaces = $namespaces[0];
1145  }
1146 
1147  foreach ( $namespaces as $ns ) {
1148  if ( $this->inNamespace( $ns ) ) {
1149  return true;
1150  }
1151  }
1152 
1153  return false;
1154  }
1155 
1169  public function hasSubjectNamespace( $ns ) {
1170  return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
1171  }
1172 
1180  public function isContentPage() {
1181  return MWNamespace::isContent( $this->getNamespace() );
1182  }
1183 
1190  public function isMovable() {
1191  if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
1192  // Interwiki title or immovable namespace. Hooks don't get to override here
1193  return false;
1194  }
1195 
1196  $result = true;
1197  Hooks::run( 'TitleIsMovable', array( $this, &$result ) );
1198  return $result;
1199  }
1200 
1211  public function isMainPage() {
1212  return $this->equals( Title::newMainPage() );
1213  }
1220  public function isSubpage() {
1221  return MWNamespace::hasSubpages( $this->mNamespace )
1222  ? strpos( $this->getText(), '/' ) !== false
1223  : false;
1224  }
1225 
1231  public function isConversionTable() {
1232  // @todo ConversionTable should become a separate content model.
1233 
1234  return $this->getNamespace() == NS_MEDIAWIKI &&
1235  strpos( $this->getText(), 'Conversiontable/' ) === 0;
1236  }
1237 
1243  public function isWikitextPage() {
1244  return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1245  }
1246 
1260  public function isCssOrJsPage() {
1261  $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
1262  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1264 
1265  # @note This hook is also called in ContentHandler::getDefaultModel.
1266  # It's called here again to make sure hook functions can force this
1267  # method to return true even outside the MediaWiki namespace.
1268 
1269  Hooks::run( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ), '1.25' );
1270 
1271  return $isCssOrJsPage;
1272  }
1273 
1278  public function isCssJsSubpage() {
1279  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1280  && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1281  || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
1282  }
1283 
1289  public function getSkinFromCssJsSubpage() {
1290  $subpage = explode( '/', $this->mTextform );
1291  $subpage = $subpage[count( $subpage ) - 1];
1292  $lastdot = strrpos( $subpage, '.' );
1293  if ( $lastdot === false ) {
1294  return $subpage; # Never happens: only called for names ending in '.css' or '.js'
1295  }
1296  return substr( $subpage, 0, $lastdot );
1297  }
1298 
1304  public function isCssSubpage() {
1305  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1306  && $this->hasContentModel( CONTENT_MODEL_CSS ) );
1307  }
1308 
1314  public function isJsSubpage() {
1315  return ( NS_USER == $this->mNamespace && $this->isSubpage()
1317  }
1318 
1324  public function isTalkPage() {
1325  return MWNamespace::isTalk( $this->getNamespace() );
1326  }
1327 
1333  public function getTalkPage() {
1334  return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
1335  }
1336 
1343  public function getSubjectPage() {
1344  // Is this the same title?
1345  $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
1346  if ( $this->getNamespace() == $subjectNS ) {
1347  return $this;
1348  }
1349  return Title::makeTitle( $subjectNS, $this->getDBkey() );
1350  }
1351 
1360  public function getOtherPage() {
1361  if ( $this->isSpecialPage() ) {
1362  throw new MWException( 'Special pages cannot have other pages' );
1363  }
1364  if ( $this->isTalkPage() ) {
1365  return $this->getSubjectPage();
1366  } else {
1367  return $this->getTalkPage();
1368  }
1369  }
1370 
1376  public function getDefaultNamespace() {
1377  return $this->mDefaultNamespace;
1378  }
1379 
1387  public function getFragment() {
1388  return $this->mFragment;
1389  }
1390 
1397  public function hasFragment() {
1398  return $this->mFragment !== '';
1399  }
1400 
1405  public function getFragmentForURL() {
1406  if ( !$this->hasFragment() ) {
1407  return '';
1408  } else {
1409  return '#' . Title::escapeFragmentForURL( $this->getFragment() );
1410  }
1411  }
1412 
1424  public function setFragment( $fragment ) {
1425  $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1426  }
1427 
1435  private function prefix( $name ) {
1436  $p = '';
1437  if ( $this->isExternal() ) {
1438  $p = $this->mInterwiki . ':';
1439  }
1440 
1441  if ( 0 != $this->mNamespace ) {
1442  $p .= $this->getNsText() . ':';
1443  }
1444  return $p . $name;
1445  }
1446 
1453  public function getPrefixedDBkey() {
1454  $s = $this->prefix( $this->mDbkeyform );
1455  $s = strtr( $s, ' ', '_' );
1456  return $s;
1457  }
1458 
1465  public function getPrefixedText() {
1466  if ( $this->mPrefixedText === null ) {
1467  $s = $this->prefix( $this->mTextform );
1468  $s = strtr( $s, '_', ' ' );
1469  $this->mPrefixedText = $s;
1470  }
1471  return $this->mPrefixedText;
1472  }
1473 
1479  public function __toString() {
1480  return $this->getPrefixedText();
1481  }
1482 
1489  public function getFullText() {
1490  $text = $this->getPrefixedText();
1491  if ( $this->hasFragment() ) {
1492  $text .= '#' . $this->getFragment();
1493  }
1494  return $text;
1495  }
1496 
1509  public function getRootText() {
1510  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1511  return $this->getText();
1512  }
1513 
1514  return strtok( $this->getText(), '/' );
1515  }
1516 
1529  public function getRootTitle() {
1530  return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
1531  }
1532 
1544  public function getBaseText() {
1545  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1546  return $this->getText();
1547  }
1548 
1549  $parts = explode( '/', $this->getText() );
1550  # Don't discard the real title if there's no subpage involved
1551  if ( count( $parts ) > 1 ) {
1552  unset( $parts[count( $parts ) - 1] );
1553  }
1554  return implode( '/', $parts );
1555  }
1556 
1569  public function getBaseTitle() {
1570  return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
1571  }
1572 
1584  public function getSubpageText() {
1585  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1586  return $this->mTextform;
1587  }
1588  $parts = explode( '/', $this->mTextform );
1589  return $parts[count( $parts ) - 1];
1590  }
1591 
1605  public function getSubpage( $text ) {
1606  return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
1607  }
1608 
1614  public function getSubpageUrlForm() {
1615  $text = $this->getSubpageText();
1616  $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1617  return $text;
1618  }
1619 
1625  public function getPrefixedURL() {
1626  $s = $this->prefix( $this->mDbkeyform );
1627  $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1628  return $s;
1629  }
1630 
1644  private static function fixUrlQueryArgs( $query, $query2 = false ) {
1645  if ( $query2 !== false ) {
1646  wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1647  "method called with a second parameter is deprecated. Add your " .
1648  "parameter to an array passed as the first parameter.", "1.19" );
1649  }
1650  if ( is_array( $query ) ) {
1651  $query = wfArrayToCgi( $query );
1652  }
1653  if ( $query2 ) {
1654  if ( is_string( $query2 ) ) {
1655  // $query2 is a string, we will consider this to be
1656  // a deprecated $variant argument and add it to the query
1657  $query2 = wfArrayToCgi( array( 'variant' => $query2 ) );
1658  } else {
1659  $query2 = wfArrayToCgi( $query2 );
1660  }
1661  // If we have $query content add a & to it first
1662  if ( $query ) {
1663  $query .= '&';
1664  }
1665  // Now append the queries together
1666  $query .= $query2;
1667  }
1668  return $query;
1669  }
1670 
1682  public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1683  $query = self::fixUrlQueryArgs( $query, $query2 );
1684 
1685  # Hand off all the decisions on urls to getLocalURL
1686  $url = $this->getLocalURL( $query );
1687 
1688  # Expand the url to make it a full url. Note that getLocalURL has the
1689  # potential to output full urls for a variety of reasons, so we use
1690  # wfExpandUrl instead of simply prepending $wgServer
1691  $url = wfExpandUrl( $url, $proto );
1692 
1693  # Finally, add the fragment.
1694  $url .= $this->getFragmentForURL();
1695 
1696  Hooks::run( 'GetFullURL', array( &$this, &$url, $query ) );
1697  return $url;
1698  }
1699 
1723  public function getLocalURL( $query = '', $query2 = false ) {
1724  global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
1725 
1726  $query = self::fixUrlQueryArgs( $query, $query2 );
1727 
1728  $interwiki = Interwiki::fetch( $this->mInterwiki );
1729  if ( $interwiki ) {
1730  $namespace = $this->getNsText();
1731  if ( $namespace != '' ) {
1732  # Can this actually happen? Interwikis shouldn't be parsed.
1733  # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1734  $namespace .= ':';
1735  }
1736  $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1737  $url = wfAppendQuery( $url, $query );
1738  } else {
1739  $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1740  if ( $query == '' ) {
1741  $url = str_replace( '$1', $dbkey, $wgArticlePath );
1742  Hooks::run( 'GetLocalURL::Article', array( &$this, &$url ) );
1743  } else {
1745  $url = false;
1746  $matches = array();
1747 
1748  if ( !empty( $wgActionPaths )
1749  && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1750  ) {
1751  $action = urldecode( $matches[2] );
1752  if ( isset( $wgActionPaths[$action] ) ) {
1753  $query = $matches[1];
1754  if ( isset( $matches[4] ) ) {
1755  $query .= $matches[4];
1756  }
1757  $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1758  if ( $query != '' ) {
1759  $url = wfAppendQuery( $url, $query );
1760  }
1761  }
1762  }
1763 
1764  if ( $url === false
1765  && $wgVariantArticlePath
1766  && $wgContLang->getCode() === $this->getPageLanguage()->getCode()
1767  && $this->getPageLanguage()->hasVariants()
1768  && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1769  ) {
1770  $variant = urldecode( $matches[1] );
1771  if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1772  // Only do the variant replacement if the given variant is a valid
1773  // variant for the page's language.
1774  $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1775  $url = str_replace( '$1', $dbkey, $url );
1776  }
1777  }
1778 
1779  if ( $url === false ) {
1780  if ( $query == '-' ) {
1781  $query = '';
1782  }
1783  $url = "{$wgScript}?title={$dbkey}&{$query}";
1784  }
1785  }
1787  Hooks::run( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
1788 
1789  // @todo FIXME: This causes breakage in various places when we
1790  // actually expected a local URL and end up with dupe prefixes.
1791  if ( $wgRequest->getVal( 'action' ) == 'render' ) {
1792  $url = $wgServer . $url;
1793  }
1794  }
1795  Hooks::run( 'GetLocalURL', array( &$this, &$url, $query ) );
1796  return $url;
1797  }
1798 
1815  public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1816  if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
1817  $ret = $this->getFullURL( $query, $query2, $proto );
1818  } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
1819  $ret = $this->getFragmentForURL();
1820  } else {
1821  $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
1822  }
1823  return $ret;
1824  }
1825 
1838  public function getInternalURL( $query = '', $query2 = false ) {
1840  $query = self::fixUrlQueryArgs( $query, $query2 );
1841  $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
1842  $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
1843  Hooks::run( 'GetInternalURL', array( &$this, &$url, $query ) );
1844  return $url;
1845  }
1846 
1858  public function getCanonicalURL( $query = '', $query2 = false ) {
1859  $query = self::fixUrlQueryArgs( $query, $query2 );
1860  $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
1861  Hooks::run( 'GetCanonicalURL', array( &$this, &$url, $query ) );
1862  return $url;
1863  }
1864 
1870  public function getEditURL() {
1871  if ( $this->isExternal() ) {
1872  return '';
1873  }
1874  $s = $this->getLocalURL( 'action=edit' );
1875 
1876  return $s;
1877  }
1878 
1885  public function userIsWatching() {
1886  global $wgUser;
1887 
1888  if ( is_null( $this->mWatched ) ) {
1889  if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn() ) {
1890  $this->mWatched = false;
1891  } else {
1892  $this->mWatched = $wgUser->isWatched( $this );
1893  }
1894  }
1895  return $this->mWatched;
1896  }
1897 
1912  public function quickUserCan( $action, $user = null ) {
1913  return $this->userCan( $action, $user, false );
1914  }
1915 
1925  public function userCan( $action, $user = null, $rigor = 'secure' ) {
1926  if ( !$user instanceof User ) {
1927  global $wgUser;
1928  $user = $wgUser;
1929  }
1930 
1931  return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
1932  }
1933 
1949  public function getUserPermissionsErrors(
1950  $action, $user, $rigor = 'secure', $ignoreErrors = array()
1951  ) {
1952  $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
1953 
1954  // Remove the errors being ignored.
1955  foreach ( $errors as $index => $error ) {
1956  $error_key = is_array( $error ) ? $error[0] : $error;
1957 
1958  if ( in_array( $error_key, $ignoreErrors ) ) {
1959  unset( $errors[$index] );
1960  }
1961  }
1962 
1963  return $errors;
1964  }
1965 
1977  private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
1978  if ( !Hooks::run( 'TitleQuickPermissions',
1979  array( $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ) )
1980  ) {
1981  return $errors;
1982  }
1983 
1984  if ( $action == 'create' ) {
1985  if (
1986  ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1987  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
1988  ) {
1989  $errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' );
1990  }
1991  } elseif ( $action == 'move' ) {
1992  if ( !$user->isAllowed( 'move-rootuserpages' )
1993  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1994  // Show user page-specific message only if the user can move other pages
1995  $errors[] = array( 'cant-move-user-page' );
1996  }
1997 
1998  // Check if user is allowed to move files if it's a file
1999  if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
2000  $errors[] = array( 'movenotallowedfile' );
2001  }
2002 
2003  // Check if user is allowed to move category pages if it's a category page
2004  if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
2005  $errors[] = array( 'cant-move-category-page' );
2006  }
2007 
2008  if ( !$user->isAllowed( 'move' ) ) {
2009  // User can't move anything
2010  $userCanMove = User::groupHasPermission( 'user', 'move' );
2011  $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
2012  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
2013  // custom message if logged-in users without any special rights can move
2014  $errors[] = array( 'movenologintext' );
2015  } else {
2016  $errors[] = array( 'movenotallowed' );
2017  }
2018  }
2019  } elseif ( $action == 'move-target' ) {
2020  if ( !$user->isAllowed( 'move' ) ) {
2021  // User can't move anything
2022  $errors[] = array( 'movenotallowed' );
2023  } elseif ( !$user->isAllowed( 'move-rootuserpages' )
2024  && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2025  // Show user page-specific message only if the user can move other pages
2026  $errors[] = array( 'cant-move-to-user-page' );
2027  } elseif ( !$user->isAllowed( 'move-categorypages' )
2028  && $this->mNamespace == NS_CATEGORY ) {
2029  // Show category page-specific message only if the user can move other pages
2030  $errors[] = array( 'cant-move-to-category-page' );
2031  }
2032  } elseif ( !$user->isAllowed( $action ) ) {
2033  $errors[] = $this->missingPermissionError( $action, $short );
2034  }
2035 
2036  return $errors;
2037  }
2038 
2047  private function resultToError( $errors, $result ) {
2048  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2049  // A single array representing an error
2050  $errors[] = $result;
2051  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2052  // A nested array representing multiple errors
2053  $errors = array_merge( $errors, $result );
2054  } elseif ( $result !== '' && is_string( $result ) ) {
2055  // A string representing a message-id
2056  $errors[] = array( $result );
2057  } elseif ( $result === false ) {
2058  // a generic "We don't want them to do that"
2059  $errors[] = array( 'badaccess-group0' );
2060  }
2061  return $errors;
2062  }
2063 
2075  private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2076  // Use getUserPermissionsErrors instead
2077  $result = '';
2078  if ( !Hooks::run( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
2079  return $result ? array() : array( array( 'badaccess-group0' ) );
2080  }
2081  // Check getUserPermissionsErrors hook
2082  if ( !Hooks::run( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
2083  $errors = $this->resultToError( $errors, $result );
2084  }
2085  // Check getUserPermissionsErrorsExpensive hook
2086  if (
2087  $rigor !== 'quick'
2088  && !( $short && count( $errors ) > 0 )
2089  && !Hooks::run( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
2090  ) {
2091  $errors = $this->resultToError( $errors, $result );
2092  }
2093 
2094  return $errors;
2095  }
2096 
2108  private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2109  # Only 'createaccount' can be performed on special pages,
2110  # which don't actually exist in the DB.
2111  if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
2112  $errors[] = array( 'ns-specialprotected' );
2113  }
2114 
2115  # Check $wgNamespaceProtection for restricted namespaces
2116  if ( $this->isNamespaceProtected( $user ) ) {
2117  $ns = $this->mNamespace == NS_MAIN ?
2118  wfMessage( 'nstab-main' )->text() : $this->getNsText();
2119  $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2120  array( 'protectedinterface', $action ) : array( 'namespaceprotected', $ns, $action );
2121  }
2122 
2123  return $errors;
2124  }
2125 
2137  private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
2138  # Protect css/js subpages of user pages
2139  # XXX: this might be better using restrictions
2140  # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
2141  if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
2142  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2143  if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
2144  $errors[] = array( 'mycustomcssprotected', $action );
2145  } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
2146  $errors[] = array( 'mycustomjsprotected', $action );
2147  }
2148  } else {
2149  if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
2150  $errors[] = array( 'customcssprotected', $action );
2151  } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
2152  $errors[] = array( 'customjsprotected', $action );
2153  }
2154  }
2155  }
2156 
2157  return $errors;
2158  }
2159 
2173  private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2174  foreach ( $this->getRestrictions( $action ) as $right ) {
2175  // Backwards compatibility, rewrite sysop -> editprotected
2176  if ( $right == 'sysop' ) {
2177  $right = 'editprotected';
2178  }
2179  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2180  if ( $right == 'autoconfirmed' ) {
2181  $right = 'editsemiprotected';
2182  }
2183  if ( $right == '' ) {
2184  continue;
2185  }
2186  if ( !$user->isAllowed( $right ) ) {
2187  $errors[] = array( 'protectedpagetext', $right, $action );
2188  } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2189  $errors[] = array( 'protectedpagetext', 'protect', $action );
2190  }
2191  }
2192 
2193  return $errors;
2194  }
2195 
2207  private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2208  if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
2209  # We /could/ use the protection level on the source page, but it's
2210  # fairly ugly as we have to establish a precedence hierarchy for pages
2211  # included by multiple cascade-protected pages. So just restrict
2212  # it to people with 'protect' permission, as they could remove the
2213  # protection anyway.
2214  list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2215  # Cascading protection depends on more than this page...
2216  # Several cascading protected pages may include this page...
2217  # Check each cascading level
2218  # This is only for protection restrictions, not for all actions
2219  if ( isset( $restrictions[$action] ) ) {
2220  foreach ( $restrictions[$action] as $right ) {
2221  // Backwards compatibility, rewrite sysop -> editprotected
2222  if ( $right == 'sysop' ) {
2223  $right = 'editprotected';
2224  }
2225  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2226  if ( $right == 'autoconfirmed' ) {
2227  $right = 'editsemiprotected';
2228  }
2229  if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2230  $pages = '';
2231  foreach ( $cascadingSources as $page ) {
2232  $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2233  }
2234  $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages, $action );
2235  }
2236  }
2237  }
2238  }
2239 
2240  return $errors;
2241  }
2242 
2254  private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2256 
2257  if ( $action == 'protect' ) {
2258  if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2259  // If they can't edit, they shouldn't protect.
2260  $errors[] = array( 'protect-cantedit' );
2261  }
2262  } elseif ( $action == 'create' ) {
2263  $title_protection = $this->getTitleProtection();
2264  if ( $title_protection ) {
2265  if ( $title_protection['permission'] == ''
2266  || !$user->isAllowed( $title_protection['permission'] )
2267  ) {
2268  $errors[] = array(
2269  'titleprotected',
2270  User::whoIs( $title_protection['user'] ),
2271  $title_protection['reason']
2272  );
2273  }
2274  }
2275  } elseif ( $action == 'move' ) {
2276  // Check for immobile pages
2277  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2278  // Specific message for this case
2279  $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
2280  } elseif ( !$this->isMovable() ) {
2281  // Less specific message for rarer cases
2282  $errors[] = array( 'immobile-source-page' );
2283  }
2284  } elseif ( $action == 'move-target' ) {
2285  if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2286  $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
2287  } elseif ( !$this->isMovable() ) {
2288  $errors[] = array( 'immobile-target-page' );
2289  }
2290  } elseif ( $action == 'delete' ) {
2291  $tempErrors = $this->checkPageRestrictions( 'edit', $user, array(), $rigor, true );
2292  if ( !$tempErrors ) {
2293  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2294  $user, $tempErrors, $rigor, true );
2295  }
2296  if ( $tempErrors ) {
2297  // If protection keeps them from editing, they shouldn't be able to delete.
2298  $errors[] = array( 'deleteprotected' );
2299  }
2300  if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2301  && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2302  ) {
2303  $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
2304  }
2305  }
2306  return $errors;
2307  }
2308 
2320  private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2321  // Account creation blocks handled at userlogin.
2322  // Unblocking handled in SpecialUnblock
2323  if ( $rigor === 'quick' || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
2324  return $errors;
2325  }
2326 
2328 
2329  if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
2330  $errors[] = array( 'confirmedittext' );
2331  }
2332 
2333  $useSlave = ( $rigor !== 'secure' );
2334  if ( ( $action == 'edit' || $action == 'create' )
2335  && !$user->isBlockedFrom( $this, $useSlave )
2336  ) {
2337  // Don't block the user from editing their own talk page unless they've been
2338  // explicitly blocked from that too.
2339  } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2340  // @todo FIXME: Pass the relevant context into this function.
2341  $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2342  }
2343 
2344  return $errors;
2345  }
2346 
2358  private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2360 
2361  $whitelisted = false;
2362  if ( User::isEveryoneAllowed( 'read' ) ) {
2363  # Shortcut for public wikis, allows skipping quite a bit of code
2364  $whitelisted = true;
2365  } elseif ( $user->isAllowed( 'read' ) ) {
2366  # If the user is allowed to read pages, he is allowed to read all pages
2367  $whitelisted = true;
2368  } elseif ( $this->isSpecial( 'Userlogin' )
2369  || $this->isSpecial( 'ChangePassword' )
2370  || $this->isSpecial( 'PasswordReset' )
2371  ) {
2372  # Always grant access to the login page.
2373  # Even anons need to be able to log in.
2374  $whitelisted = true;
2375  } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2376  # Time to check the whitelist
2377  # Only do these checks is there's something to check against
2378  $name = $this->getPrefixedText();
2379  $dbName = $this->getPrefixedDBkey();
2380 
2381  // Check for explicit whitelisting with and without underscores
2382  if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2383  $whitelisted = true;
2384  } elseif ( $this->getNamespace() == NS_MAIN ) {
2385  # Old settings might have the title prefixed with
2386  # a colon for main-namespace pages
2387  if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2388  $whitelisted = true;
2389  }
2390  } elseif ( $this->isSpecialPage() ) {
2391  # If it's a special page, ditch the subpage bit and check again
2392  $name = $this->getDBkey();
2393  list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2394  if ( $name ) {
2395  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2396  if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2397  $whitelisted = true;
2398  }
2399  }
2400  }
2401  }
2402 
2403  if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2405  // Check for regex whitelisting
2406  foreach ( $wgWhitelistReadRegexp as $listItem ) {
2407  if ( preg_match( $listItem, $name ) ) {
2408  $whitelisted = true;
2409  break;
2410  }
2411  }
2412  }
2413 
2414  if ( !$whitelisted ) {
2415  # If the title is not whitelisted, give extensions a chance to do so...
2416  Hooks::run( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
2417  if ( !$whitelisted ) {
2418  $errors[] = $this->missingPermissionError( $action, $short );
2419  }
2420  }
2421 
2422  return $errors;
2423  }
2424 
2433  private function missingPermissionError( $action, $short ) {
2434  // We avoid expensive display logic for quickUserCan's and such
2435  if ( $short ) {
2436  return array( 'badaccess-group0' );
2437  }
2438 
2439  $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
2440  User::getGroupsWithPermission( $action ) );
2441 
2442  if ( count( $groups ) ) {
2443  global $wgLang;
2444  return array(
2445  'badaccess-groups',
2446  $wgLang->commaList( $groups ),
2447  count( $groups )
2448  );
2449  } else {
2450  return array( 'badaccess-group0' );
2451  }
2452  }
2453 
2468  protected function getUserPermissionsErrorsInternal(
2469  $action, $user, $rigor = 'secure', $short = false
2470  ) {
2471  if ( $rigor === true ) {
2472  $rigor = 'secure'; // b/c
2473  } elseif ( $rigor === false ) {
2474  $rigor = 'quick'; // b/c
2475  } elseif ( !in_array( $rigor, array( 'quick', 'full', 'secure' ) ) ) {
2476  throw new Exception( "Invalid rigor parameter '$rigor'." );
2477  }
2478 
2479  # Read has special handling
2480  if ( $action == 'read' ) {
2481  $checks = array(
2482  'checkPermissionHooks',
2483  'checkReadPermissions',
2484  );
2485  # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
2486  # here as it will lead to duplicate error messages. This is okay to do
2487  # since anywhere that checks for create will also check for edit, and
2488  # those checks are called for edit.
2489  } elseif ( $action == 'create' ) {
2490  $checks = array(
2491  'checkQuickPermissions',
2492  'checkPermissionHooks',
2493  'checkPageRestrictions',
2494  'checkCascadingSourcesRestrictions',
2495  'checkActionPermissions',
2496  'checkUserBlock'
2497  );
2498  } else {
2499  $checks = array(
2500  'checkQuickPermissions',
2501  'checkPermissionHooks',
2502  'checkSpecialsAndNSPermissions',
2503  'checkCSSandJSPermissions',
2504  'checkPageRestrictions',
2505  'checkCascadingSourcesRestrictions',
2506  'checkActionPermissions',
2507  'checkUserBlock'
2508  );
2509  }
2510 
2511  $errors = array();
2512  while ( count( $checks ) > 0 &&
2513  !( $short && count( $errors ) > 0 ) ) {
2514  $method = array_shift( $checks );
2515  $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2516  }
2518  return $errors;
2519  }
2520 
2528  public static function getFilteredRestrictionTypes( $exists = true ) {
2530  $types = $wgRestrictionTypes;
2531  if ( $exists ) {
2532  # Remove the create restriction for existing titles
2533  $types = array_diff( $types, array( 'create' ) );
2534  } else {
2535  # Only the create and upload restrictions apply to non-existing titles
2536  $types = array_intersect( $types, array( 'create', 'upload' ) );
2537  }
2538  return $types;
2539  }
2540 
2546  public function getRestrictionTypes() {
2547  if ( $this->isSpecialPage() ) {
2548  return array();
2549  }
2550 
2551  $types = self::getFilteredRestrictionTypes( $this->exists() );
2552 
2553  if ( $this->getNamespace() != NS_FILE ) {
2554  # Remove the upload restriction for non-file titles
2555  $types = array_diff( $types, array( 'upload' ) );
2556  }
2557 
2558  Hooks::run( 'TitleGetRestrictionTypes', array( $this, &$types ) );
2559 
2560  wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2561  $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2562 
2563  return $types;
2564  }
2565 
2573  public function getTitleProtection() {
2574  // Can't protect pages in special namespaces
2575  if ( $this->getNamespace() < 0 ) {
2576  return false;
2577  }
2578 
2579  // Can't protect pages that exist.
2580  if ( $this->exists() ) {
2581  return false;
2582  }
2583 
2584  if ( $this->mTitleProtection === null ) {
2585  $dbr = wfGetDB( DB_SLAVE );
2586  $res = $dbr->select(
2587  'protected_titles',
2589  'user' => 'pt_user',
2590  'reason' => 'pt_reason',
2591  'expiry' => 'pt_expiry',
2592  'permission' => 'pt_create_perm'
2593  ),
2594  array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
2595  __METHOD__
2596  );
2597 
2598  // fetchRow returns false if there are no rows.
2599  $row = $dbr->fetchRow( $res );
2600  if ( $row ) {
2601  if ( $row['permission'] == 'sysop' ) {
2602  $row['permission'] = 'editprotected'; // B/C
2603  }
2604  if ( $row['permission'] == 'autoconfirmed' ) {
2605  $row['permission'] = 'editsemiprotected'; // B/C
2606  }
2607  $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2608  }
2609  $this->mTitleProtection = $row;
2610  }
2611  return $this->mTitleProtection;
2612  }
2613 
2617  public function deleteTitleProtection() {
2618  $dbw = wfGetDB( DB_MASTER );
2619 
2620  $dbw->delete(
2621  'protected_titles',
2622  array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
2623  __METHOD__
2624  );
2625  $this->mTitleProtection = false;
2626  }
2627 
2635  public function isSemiProtected( $action = 'edit' ) {
2637 
2638  $restrictions = $this->getRestrictions( $action );
2640  if ( !$restrictions || !$semi ) {
2641  // Not protected, or all protection is full protection
2642  return false;
2643  }
2644 
2645  // Remap autoconfirmed to editsemiprotected for BC
2646  foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2647  $semi[$key] = 'editsemiprotected';
2648  }
2649  foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2650  $restrictions[$key] = 'editsemiprotected';
2651  }
2652 
2653  return !array_diff( $restrictions, $semi );
2654  }
2655 
2663  public function isProtected( $action = '' ) {
2665 
2666  $restrictionTypes = $this->getRestrictionTypes();
2667 
2668  # Special pages have inherent protection
2669  if ( $this->isSpecialPage() ) {
2670  return true;
2671  }
2672 
2673  # Check regular protection levels
2674  foreach ( $restrictionTypes as $type ) {
2675  if ( $action == $type || $action == '' ) {
2676  $r = $this->getRestrictions( $type );
2677  foreach ( $wgRestrictionLevels as $level ) {
2678  if ( in_array( $level, $r ) && $level != '' ) {
2679  return true;
2680  }
2681  }
2682  }
2683  }
2685  return false;
2686  }
2687 
2695  public function isNamespaceProtected( User $user ) {
2697 
2698  if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2699  foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2700  if ( $right != '' && !$user->isAllowed( $right ) ) {
2701  return true;
2702  }
2703  }
2704  }
2705  return false;
2706  }
2707 
2713  public function isCascadeProtected() {
2714  list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2715  return ( $sources > 0 );
2716  }
2717 
2727  public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2728  return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2729  }
2730 
2744  public function getCascadeProtectionSources( $getPages = true ) {
2745  $pagerestrictions = array();
2746 
2747  if ( $this->mCascadeSources !== null && $getPages ) {
2748  return array( $this->mCascadeSources, $this->mCascadingRestrictions );
2749  } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2750  return array( $this->mHasCascadingRestrictions, $pagerestrictions );
2751  }
2752 
2753  $dbr = wfGetDB( DB_SLAVE );
2754 
2755  if ( $this->getNamespace() == NS_FILE ) {
2756  $tables = array( 'imagelinks', 'page_restrictions' );
2757  $where_clauses = array(
2758  'il_to' => $this->getDBkey(),
2759  'il_from=pr_page',
2760  'pr_cascade' => 1
2761  );
2762  } else {
2763  $tables = array( 'templatelinks', 'page_restrictions' );
2764  $where_clauses = array(
2765  'tl_namespace' => $this->getNamespace(),
2766  'tl_title' => $this->getDBkey(),
2767  'tl_from=pr_page',
2768  'pr_cascade' => 1
2769  );
2770  }
2771 
2772  if ( $getPages ) {
2773  $cols = array( 'pr_page', 'page_namespace', 'page_title',
2774  'pr_expiry', 'pr_type', 'pr_level' );
2775  $where_clauses[] = 'page_id=pr_page';
2776  $tables[] = 'page';
2777  } else {
2778  $cols = array( 'pr_expiry' );
2779  }
2780 
2781  $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2782 
2783  $sources = $getPages ? array() : false;
2784  $now = wfTimestampNow();
2785 
2786  foreach ( $res as $row ) {
2787  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2788  if ( $expiry > $now ) {
2789  if ( $getPages ) {
2790  $page_id = $row->pr_page;
2791  $page_ns = $row->page_namespace;
2792  $page_title = $row->page_title;
2793  $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2794  # Add groups needed for each restriction type if its not already there
2795  # Make sure this restriction type still exists
2796 
2797  if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2798  $pagerestrictions[$row->pr_type] = array();
2799  }
2800 
2801  if (
2802  isset( $pagerestrictions[$row->pr_type] )
2803  && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2804  ) {
2805  $pagerestrictions[$row->pr_type][] = $row->pr_level;
2806  }
2807  } else {
2808  $sources = true;
2809  }
2810  }
2811  }
2812 
2813  if ( $getPages ) {
2814  $this->mCascadeSources = $sources;
2815  $this->mCascadingRestrictions = $pagerestrictions;
2816  } else {
2817  $this->mHasCascadingRestrictions = $sources;
2818  }
2819 
2820  return array( $sources, $pagerestrictions );
2821  }
2822 
2830  public function areRestrictionsLoaded() {
2832  }
2833 
2843  public function getRestrictions( $action ) {
2844  if ( !$this->mRestrictionsLoaded ) {
2845  $this->loadRestrictions();
2846  }
2847  return isset( $this->mRestrictions[$action] )
2848  ? $this->mRestrictions[$action]
2849  : array();
2850  }
2851 
2859  public function getAllRestrictions() {
2860  if ( !$this->mRestrictionsLoaded ) {
2861  $this->loadRestrictions();
2862  }
2863  return $this->mRestrictions;
2864  }
2865 
2873  public function getRestrictionExpiry( $action ) {
2874  if ( !$this->mRestrictionsLoaded ) {
2875  $this->loadRestrictions();
2876  }
2877  return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2878  }
2879 
2885  function areRestrictionsCascading() {
2886  if ( !$this->mRestrictionsLoaded ) {
2887  $this->loadRestrictions();
2888  }
2889 
2891  }
2892 
2900  private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
2901  $rows = array();
2902 
2903  foreach ( $res as $row ) {
2904  $rows[] = $row;
2905  }
2906 
2907  $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2908  }
2909 
2919  public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2920  $dbr = wfGetDB( DB_SLAVE );
2921 
2922  $restrictionTypes = $this->getRestrictionTypes();
2923 
2924  foreach ( $restrictionTypes as $type ) {
2925  $this->mRestrictions[$type] = array();
2926  $this->mRestrictionsExpiry[$type] = 'infinity';
2927  }
2928 
2929  $this->mCascadeRestriction = false;
2930 
2931  # Backwards-compatibility: also load the restrictions from the page record (old format).
2932  if ( $oldFashionedRestrictions !== null ) {
2933  $this->mOldRestrictions = $oldFashionedRestrictions;
2934  }
2935 
2936  if ( $this->mOldRestrictions === false ) {
2937  $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2938  array( 'page_id' => $this->getArticleID() ), __METHOD__ );
2939  }
2940 
2941  if ( $this->mOldRestrictions != '' ) {
2942  foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2943  $temp = explode( '=', trim( $restrict ) );
2944  if ( count( $temp ) == 1 ) {
2945  // old old format should be treated as edit/move restriction
2946  $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2947  $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2948  } else {
2949  $restriction = trim( $temp[1] );
2950  if ( $restriction != '' ) { // some old entries are empty
2951  $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2952  }
2953  }
2954  }
2955  }
2956 
2957  if ( count( $rows ) ) {
2958  # Current system - load second to make them override.
2959  $now = wfTimestampNow();
2960 
2961  # Cycle through all the restrictions.
2962  foreach ( $rows as $row ) {
2964  // Don't take care of restrictions types that aren't allowed
2965  if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2966  continue;
2967  }
2968 
2969  // This code should be refactored, now that it's being used more generally,
2970  // But I don't really see any harm in leaving it in Block for now -werdna
2971  $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2972 
2973  // Only apply the restrictions if they haven't expired!
2974  if ( !$expiry || $expiry > $now ) {
2975  $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2976  $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2977 
2978  $this->mCascadeRestriction |= $row->pr_cascade;
2979  }
2980  }
2981  }
2982 
2983  $this->mRestrictionsLoaded = true;
2984  }
2985 
2992  public function loadRestrictions( $oldFashionedRestrictions = null ) {
2993  if ( !$this->mRestrictionsLoaded ) {
2994  $dbr = wfGetDB( DB_SLAVE );
2995  if ( $this->exists() ) {
2996  $res = $dbr->select(
2997  'page_restrictions',
2998  array( 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ),
2999  array( 'pr_page' => $this->getArticleID() ),
3000  __METHOD__
3001  );
3002 
3003  $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
3004  } else {
3005  $title_protection = $this->getTitleProtection();
3006 
3007  if ( $title_protection ) {
3008  $now = wfTimestampNow();
3009  $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
3010 
3011  if ( !$expiry || $expiry > $now ) {
3012  // Apply the restrictions
3013  $this->mRestrictionsExpiry['create'] = $expiry;
3014  $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
3015  } else { // Get rid of the old restrictions
3016  $this->mTitleProtection = false;
3017  }
3018  } else {
3019  $this->mRestrictionsExpiry['create'] = 'infinity';
3020  }
3021  $this->mRestrictionsLoaded = true;
3022  }
3023  }
3024  }
3025 
3030  public function flushRestrictions() {
3031  $this->mRestrictionsLoaded = false;
3032  $this->mTitleProtection = null;
3033  }
3034 
3038  static function purgeExpiredRestrictions() {
3039  if ( wfReadOnly() ) {
3040  return;
3041  }
3042 
3043  $method = __METHOD__;
3044  $dbw = wfGetDB( DB_MASTER );
3045  $dbw->onTransactionIdle( function () use ( $dbw, $method ) {
3046  $dbw->delete(
3047  'page_restrictions',
3048  array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
3049  $method
3050  );
3051  $dbw->delete(
3052  'protected_titles',
3053  array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
3054  $method
3055  );
3056  } );
3057  }
3058 
3064  public function hasSubpages() {
3065  if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3066  # Duh
3067  return false;
3068  }
3069 
3070  # We dynamically add a member variable for the purpose of this method
3071  # alone to cache the result. There's no point in having it hanging
3072  # around uninitialized in every Title object; therefore we only add it
3073  # if needed and don't declare it statically.
3074  if ( $this->mHasSubpages === null ) {
3075  $this->mHasSubpages = false;
3076  $subpages = $this->getSubpages( 1 );
3077  if ( $subpages instanceof TitleArray ) {
3078  $this->mHasSubpages = (bool)$subpages->count();
3079  }
3080  }
3081 
3082  return $this->mHasSubpages;
3083  }
3084 
3092  public function getSubpages( $limit = -1 ) {
3093  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3094  return array();
3095  }
3096 
3097  $dbr = wfGetDB( DB_SLAVE );
3098  $conds['page_namespace'] = $this->getNamespace();
3099  $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3100  $options = array();
3101  if ( $limit > -1 ) {
3102  $options['LIMIT'] = $limit;
3103  }
3104  $this->mSubpages = TitleArray::newFromResult(
3105  $dbr->select( 'page',
3106  array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
3107  $conds,
3108  __METHOD__,
3109  $options
3110  )
3111  );
3112  return $this->mSubpages;
3113  }
3114 
3120  public function isDeleted() {
3121  if ( $this->getNamespace() < 0 ) {
3122  $n = 0;
3123  } else {
3124  $dbr = wfGetDB( DB_SLAVE );
3125 
3126  $n = $dbr->selectField( 'archive', 'COUNT(*)',
3127  array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
3128  __METHOD__
3129  );
3130  if ( $this->getNamespace() == NS_FILE ) {
3131  $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3132  array( 'fa_name' => $this->getDBkey() ),
3133  __METHOD__
3134  );
3135  }
3136  }
3137  return (int)$n;
3138  }
3139 
3145  public function isDeletedQuick() {
3146  if ( $this->getNamespace() < 0 ) {
3147  return false;
3148  }
3149  $dbr = wfGetDB( DB_SLAVE );
3150  $deleted = (bool)$dbr->selectField( 'archive', '1',
3151  array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
3152  __METHOD__
3153  );
3154  if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3155  $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3156  array( 'fa_name' => $this->getDBkey() ),
3157  __METHOD__
3158  );
3159  }
3160  return $deleted;
3161  }
3162 
3171  public function getArticleID( $flags = 0 ) {
3172  if ( $this->getNamespace() < 0 ) {
3173  $this->mArticleID = 0;
3174  return $this->mArticleID;
3175  }
3176  $linkCache = LinkCache::singleton();
3177  if ( $flags & self::GAID_FOR_UPDATE ) {
3178  $oldUpdate = $linkCache->forUpdate( true );
3179  $linkCache->clearLink( $this );
3180  $this->mArticleID = $linkCache->addLinkObj( $this );
3181  $linkCache->forUpdate( $oldUpdate );
3182  } else {
3183  if ( -1 == $this->mArticleID ) {
3184  $this->mArticleID = $linkCache->addLinkObj( $this );
3185  }
3186  }
3187  return $this->mArticleID;
3188  }
3189 
3197  public function isRedirect( $flags = 0 ) {
3198  if ( !is_null( $this->mRedirect ) ) {
3199  return $this->mRedirect;
3200  }
3201  if ( !$this->getArticleID( $flags ) ) {
3202  $this->mRedirect = false;
3204  }
3205 
3206  $linkCache = LinkCache::singleton();
3207  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3208  $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3209  if ( $cached === null ) {
3210  # Trust LinkCache's state over our own
3211  # LinkCache is telling us that the page doesn't exist, despite there being cached
3212  # data relating to an existing page in $this->mArticleID. Updaters should clear
3213  # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3214  # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3215  # LinkCache to refresh its data from the master.
3216  $this->mRedirect = false;
3217  return $this->mRedirect;
3218  }
3219 
3220  $this->mRedirect = (bool)$cached;
3221 
3222  return $this->mRedirect;
3223  }
3224 
3232  public function getLength( $flags = 0 ) {
3233  if ( $this->mLength != -1 ) {
3234  return $this->mLength;
3235  }
3236  if ( !$this->getArticleID( $flags ) ) {
3237  $this->mLength = 0;
3238  return $this->mLength;
3239  }
3240  $linkCache = LinkCache::singleton();
3241  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3242  $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3243  if ( $cached === null ) {
3244  # Trust LinkCache's state over our own, as for isRedirect()
3245  $this->mLength = 0;
3246  return $this->mLength;
3247  }
3248 
3249  $this->mLength = intval( $cached );
3250 
3251  return $this->mLength;
3252  }
3253 
3260  public function getLatestRevID( $flags = 0 ) {
3261  if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3262  return intval( $this->mLatestID );
3263  }
3264  if ( !$this->getArticleID( $flags ) ) {
3265  $this->mLatestID = 0;
3266  return $this->mLatestID;
3267  }
3268  $linkCache = LinkCache::singleton();
3269  $linkCache->addLinkObj( $this ); # in case we already had an article ID
3270  $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3271  if ( $cached === null ) {
3272  # Trust LinkCache's state over our own, as for isRedirect()
3273  $this->mLatestID = 0;
3274  return $this->mLatestID;
3275  }
3276 
3277  $this->mLatestID = intval( $cached );
3278 
3279  return $this->mLatestID;
3280  }
3281 
3292  public function resetArticleID( $newid ) {
3293  $linkCache = LinkCache::singleton();
3294  $linkCache->clearLink( $this );
3295 
3296  if ( $newid === false ) {
3297  $this->mArticleID = -1;
3298  } else {
3299  $this->mArticleID = intval( $newid );
3300  }
3301  $this->mRestrictionsLoaded = false;
3302  $this->mRestrictions = array();
3303  $this->mOldRestrictions = false;
3304  $this->mRedirect = null;
3305  $this->mLength = -1;
3306  $this->mLatestID = false;
3307  $this->mContentModel = false;
3308  $this->mEstimateRevisions = null;
3309  $this->mPageLanguage = false;
3310  $this->mDbPageLanguage = null;
3311  $this->mIsBigDeletion = null;
3312  }
3313 
3314  public static function clearCaches() {
3315  $linkCache = LinkCache::singleton();
3316  $linkCache->clear();
3317 
3318  $titleCache = self::getTitleCache();
3319  $titleCache->clear();
3320  }
3321 
3329  public static function capitalize( $text, $ns = NS_MAIN ) {
3331 
3332  if ( MWNamespace::isCapitalized( $ns ) ) {
3333  return $wgContLang->ucfirst( $text );
3334  } else {
3335  return $text;
3336  }
3337  }
3338 
3351  private function secureAndSplit() {
3352  # Initialisation
3353  $this->mInterwiki = '';
3354  $this->mFragment = '';
3355  $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3356 
3357  $dbkey = $this->mDbkeyform;
3358 
3359  // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3360  // the parsing code with Title, while avoiding massive refactoring.
3361  // @todo: get rid of secureAndSplit, refactor parsing code.
3362  $titleParser = self::getTitleParser();
3363  // MalformedTitleException can be thrown here
3364  $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3365 
3366  # Fill fields
3367  $this->setFragment( '#' . $parts['fragment'] );
3368  $this->mInterwiki = $parts['interwiki'];
3369  $this->mLocalInterwiki = $parts['local_interwiki'];
3370  $this->mNamespace = $parts['namespace'];
3371  $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3372 
3373  $this->mDbkeyform = $parts['dbkey'];
3374  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3375  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3376 
3377  # We already know that some pages won't be in the database!
3378  if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3379  $this->mArticleID = 0;
3380  }
3381 
3382  return true;
3383  }
3384 
3397  public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
3398  if ( count( $options ) > 0 ) {
3399  $db = wfGetDB( DB_MASTER );
3400  } else {
3401  $db = wfGetDB( DB_SLAVE );
3402  }
3403 
3404  $res = $db->select(
3405  array( 'page', $table ),
3406  self::getSelectFields(),
3407  array(
3408  "{$prefix}_from=page_id",
3409  "{$prefix}_namespace" => $this->getNamespace(),
3410  "{$prefix}_title" => $this->getDBkey() ),
3411  __METHOD__,
3412  $options
3413  );
3414 
3415  $retVal = array();
3416  if ( $res->numRows() ) {
3417  $linkCache = LinkCache::singleton();
3418  foreach ( $res as $row ) {
3419  $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3420  if ( $titleObj ) {
3421  $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3422  $retVal[] = $titleObj;
3423  }
3424  }
3425  }
3426  return $retVal;
3427  }
3428 
3439  public function getTemplateLinksTo( $options = array() ) {
3440  return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3441  }
3442 
3455  public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
3456  $id = $this->getArticleID();
3457 
3458  # If the page doesn't exist; there can't be any link from this page
3459  if ( !$id ) {
3460  return array();
3461  }
3462 
3463  if ( count( $options ) > 0 ) {
3464  $db = wfGetDB( DB_MASTER );
3465  } else {
3466  $db = wfGetDB( DB_SLAVE );
3467  }
3468 
3469  $blNamespace = "{$prefix}_namespace";
3470  $blTitle = "{$prefix}_title";
3471 
3472  $res = $db->select(
3473  array( $table, 'page' ),
3474  array_merge(
3475  array( $blNamespace, $blTitle ),
3477  ),
3478  array( "{$prefix}_from" => $id ),
3479  __METHOD__,
3480  $options,
3481  array( 'page' => array(
3482  'LEFT JOIN',
3483  array( "page_namespace=$blNamespace", "page_title=$blTitle" )
3484  ) )
3485  );
3486 
3487  $retVal = array();
3488  $linkCache = LinkCache::singleton();
3489  foreach ( $res as $row ) {
3490  if ( $row->page_id ) {
3491  $titleObj = Title::newFromRow( $row );
3492  } else {
3493  $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3494  $linkCache->addBadLinkObj( $titleObj );
3495  }
3496  $retVal[] = $titleObj;
3497  }
3498 
3499  return $retVal;
3500  }
3501 
3512  public function getTemplateLinksFrom( $options = array() ) {
3513  return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3514  }
3515 
3524  public function getBrokenLinksFrom() {
3525  if ( $this->getArticleID() == 0 ) {
3526  # All links from article ID 0 are false positives
3527  return array();
3528  }
3529 
3530  $dbr = wfGetDB( DB_SLAVE );
3531  $res = $dbr->select(
3532  array( 'page', 'pagelinks' ),
3533  array( 'pl_namespace', 'pl_title' ),
3534  array(
3535  'pl_from' => $this->getArticleID(),
3536  'page_namespace IS NULL'
3537  ),
3538  __METHOD__, array(),
3539  array(
3540  'page' => array(
3541  'LEFT JOIN',
3542  array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
3543  )
3544  )
3545  );
3546 
3547  $retVal = array();
3548  foreach ( $res as $row ) {
3549  $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3550  }
3551  return $retVal;
3552  }
3553 
3560  public function getSquidURLs() {
3561  $urls = array(
3562  $this->getInternalURL(),
3563  $this->getInternalURL( 'action=history' )
3564  );
3565 
3566  $pageLang = $this->getPageLanguage();
3567  if ( $pageLang->hasVariants() ) {
3568  $variants = $pageLang->getVariants();
3569  foreach ( $variants as $vCode ) {
3570  $urls[] = $this->getInternalURL( '', $vCode );
3571  }
3572  }
3573 
3574  // If we are looking at a css/js user subpage, purge the action=raw.
3575  if ( $this->isJsSubpage() ) {
3576  $urls[] = $this->getInternalUrl( 'action=raw&ctype=text/javascript' );
3577  } elseif ( $this->isCssSubpage() ) {
3578  $urls[] = $this->getInternalUrl( 'action=raw&ctype=text/css' );
3579  }
3580 
3581  Hooks::run( 'TitleSquidURLs', array( $this, &$urls ) );
3582  return $urls;
3583  }
3584 
3588  public function purgeSquid() {
3590  if ( $wgUseSquid ) {
3591  $urls = $this->getSquidURLs();
3592  $u = new SquidUpdate( $urls );
3593  $u->doUpdate();
3594  }
3595  }
3596 
3604  public function moveNoAuth( &$nt ) {
3605  wfDeprecated( __METHOD__, '1.25' );
3606  return $this->moveTo( $nt, false );
3607  }
3608 
3619  public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3620  global $wgUser;
3621 
3622  if ( !( $nt instanceof Title ) ) {
3623  // Normally we'd add this to $errors, but we'll get
3624  // lots of syntax errors if $nt is not an object
3625  return array( array( 'badtitletext' ) );
3626  }
3627 
3628  $mp = new MovePage( $this, $nt );
3629  $errors = $mp->isValidMove()->getErrorsArray();
3630  if ( $auth ) {
3631  $errors = wfMergeErrorArrays(
3632  $errors,
3633  $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3634  );
3635  }
3636 
3637  return $errors ?: true;
3638  }
3639 
3646  protected function validateFileMoveOperation( $nt ) {
3647  global $wgUser;
3648 
3649  $errors = array();
3650 
3651  $destFile = wfLocalFile( $nt );
3652  $destFile->load( File::READ_LATEST );
3653  if ( !$wgUser->isAllowed( 'reupload-shared' )
3654  && !$destFile->exists() && wfFindFile( $nt )
3655  ) {
3656  $errors[] = array( 'file-exists-sharedrepo' );
3657  }
3658 
3659  return $errors;
3660  }
3661 
3674  public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
3675  global $wgUser;
3676  $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3677  if ( is_array( $err ) ) {
3678  // Auto-block user's IP if the account was "hard" blocked
3679  $wgUser->spreadAnyEditBlock();
3680  return $err;
3681  }
3682  // Check suppressredirect permission
3683  if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3684  $createRedirect = true;
3685  }
3686 
3687  $mp = new MovePage( $this, $nt );
3688  $status = $mp->move( $wgUser, $reason, $createRedirect );
3689  if ( $status->isOK() ) {
3690  return true;
3691  } else {
3692  return $status->getErrorsArray();
3693  }
3694  }
3695 
3708  public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
3710  // Check permissions
3711  if ( !$this->userCan( 'move-subpages' ) ) {
3712  return array( 'cant-move-subpages' );
3713  }
3714  // Do the source and target namespaces support subpages?
3715  if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3716  return array( 'namespace-nosubpages',
3718  }
3719  if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3720  return array( 'namespace-nosubpages',
3721  MWNamespace::getCanonicalName( $nt->getNamespace() ) );
3722  }
3723 
3724  $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3725  $retval = array();
3726  $count = 0;
3727  foreach ( $subpages as $oldSubpage ) {
3728  $count++;
3729  if ( $count > $wgMaximumMovedPages ) {
3730  $retval[$oldSubpage->getPrefixedText()] =
3731  array( 'movepage-max-pages',
3732  $wgMaximumMovedPages );
3733  break;
3734  }
3735 
3736  // We don't know whether this function was called before
3737  // or after moving the root page, so check both
3738  // $this and $nt
3739  if ( $oldSubpage->getArticleID() == $this->getArticleID()
3740  || $oldSubpage->getArticleID() == $nt->getArticleID()
3741  ) {
3742  // When moving a page to a subpage of itself,
3743  // don't move it twice
3744  continue;
3745  }
3746  $newPageName = preg_replace(
3747  '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3748  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
3749  $oldSubpage->getDBkey() );
3750  if ( $oldSubpage->isTalkPage() ) {
3751  $newNs = $nt->getTalkPage()->getNamespace();
3752  } else {
3753  $newNs = $nt->getSubjectPage()->getNamespace();
3754  }
3755  # Bug 14385: we need makeTitleSafe because the new page names may
3756  # be longer than 255 characters.
3757  $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3758 
3759  $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
3760  if ( $success === true ) {
3761  $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3762  } else {
3763  $retval[$oldSubpage->getPrefixedText()] = $success;
3764  }
3765  }
3766  return $retval;
3767  }
3768 
3775  public function isSingleRevRedirect() {
3777 
3778  $dbw = wfGetDB( DB_MASTER );
3779 
3780  # Is it a redirect?
3781  $fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
3782  if ( $wgContentHandlerUseDB ) {
3783  $fields[] = 'page_content_model';
3784  }
3785 
3786  $row = $dbw->selectRow( 'page',
3787  $fields,
3788  $this->pageCond(),
3789  __METHOD__,
3790  array( 'FOR UPDATE' )
3791  );
3792  # Cache some fields we may want
3793  $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3794  $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3795  $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3796  $this->mContentModel = $row && isset( $row->page_content_model )
3797  ? strval( $row->page_content_model )
3798  : false;
3799 
3800  if ( !$this->mRedirect ) {
3801  return false;
3802  }
3803  # Does the article have a history?
3804  $row = $dbw->selectField( array( 'page', 'revision' ),
3805  'rev_id',
3806  array( 'page_namespace' => $this->getNamespace(),
3807  'page_title' => $this->getDBkey(),
3808  'page_id=rev_page',
3809  'page_latest != rev_id'
3810  ),
3811  __METHOD__,
3812  array( 'FOR UPDATE' )
3813  );
3814  # Return true if there was no history
3815  return ( $row === false );
3816  }
3817 
3826  public function isValidMoveTarget( $nt ) {
3827  # Is it an existing file?
3828  if ( $nt->getNamespace() == NS_FILE ) {
3829  $file = wfLocalFile( $nt );
3830  $file->load( File::READ_LATEST );
3831  if ( $file->exists() ) {
3832  wfDebug( __METHOD__ . ": file exists\n" );
3833  return false;
3834  }
3835  }
3836  # Is it a redirect with no history?
3837  if ( !$nt->isSingleRevRedirect() ) {
3838  wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3839  return false;
3840  }
3841  # Get the article text
3843  if ( !is_object( $rev ) ) {
3844  return false;
3845  }
3846  $content = $rev->getContent();
3847  # Does the redirect point to the source?
3848  # Or is it a broken self-redirect, usually caused by namespace collisions?
3849  $redirTitle = $content ? $content->getRedirectTarget() : null;
3850 
3851  if ( $redirTitle ) {
3852  if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3853  $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3854  wfDebug( __METHOD__ . ": redirect points to other page\n" );
3855  return false;
3856  } else {
3857  return true;
3858  }
3859  } else {
3860  # Fail safe (not a redirect after all. strange.)
3861  wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3862  " is a redirect, but it doesn't contain a valid redirect.\n" );
3863  return false;
3864  }
3865  }
3866 
3874  public function getParentCategories() {
3876 
3877  $data = array();
3878 
3879  $titleKey = $this->getArticleID();
3881  if ( $titleKey === 0 ) {
3882  return $data;
3883  }
3884 
3885  $dbr = wfGetDB( DB_SLAVE );
3886 
3887  $res = $dbr->select(
3888  'categorylinks',
3889  'cl_to',
3890  array( 'cl_from' => $titleKey ),
3891  __METHOD__
3892  );
3893 
3894  if ( $res->numRows() > 0 ) {
3895  foreach ( $res as $row ) {
3896  // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3897  $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3898  }
3899  }
3900  return $data;
3901  }
3902 
3909  public function getParentCategoryTree( $children = array() ) {
3910  $stack = array();
3911  $parents = $this->getParentCategories();
3912 
3913  if ( $parents ) {
3914  foreach ( $parents as $parent => $current ) {
3915  if ( array_key_exists( $parent, $children ) ) {
3916  # Circular reference
3917  $stack[$parent] = array();
3918  } else {
3919  $nt = Title::newFromText( $parent );
3920  if ( $nt ) {
3921  $stack[$parent] = $nt->getParentCategoryTree( $children + array( $parent => 1 ) );
3922  }
3923  }
3924  }
3925  }
3926 
3927  return $stack;
3928  }
3929 
3936  public function pageCond() {
3937  if ( $this->mArticleID > 0 ) {
3938  // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3939  return array( 'page_id' => $this->mArticleID );
3940  } else {
3941  return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
3942  }
3943  }
3944 
3952  public function getPreviousRevisionID( $revId, $flags = 0 ) {
3953  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3954  $revId = $db->selectField( 'revision', 'rev_id',
3955  array(
3956  'rev_page' => $this->getArticleID( $flags ),
3957  'rev_id < ' . intval( $revId )
3958  ),
3959  __METHOD__,
3960  array( 'ORDER BY' => 'rev_id DESC' )
3961  );
3962 
3963  if ( $revId === false ) {
3964  return false;
3965  } else {
3966  return intval( $revId );
3967  }
3968  }
3969 
3977  public function getNextRevisionID( $revId, $flags = 0 ) {
3978  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
3979  $revId = $db->selectField( 'revision', 'rev_id',
3980  array(
3981  'rev_page' => $this->getArticleID( $flags ),
3982  'rev_id > ' . intval( $revId )
3983  ),
3984  __METHOD__,
3985  array( 'ORDER BY' => 'rev_id' )
3986  );
3987 
3988  if ( $revId === false ) {
3989  return false;
3990  } else {
3991  return intval( $revId );
3992  }
3993  }
4001  public function getFirstRevision( $flags = 0 ) {
4002  $pageId = $this->getArticleID( $flags );
4003  if ( $pageId ) {
4004  $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
4005  $row = $db->selectRow( 'revision', Revision::selectFields(),
4006  array( 'rev_page' => $pageId ),
4007  __METHOD__,
4008  array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
4009  );
4010  if ( $row ) {
4011  return new Revision( $row );
4012  }
4013  }
4014  return null;
4015  }
4016 
4023  public function getEarliestRevTime( $flags = 0 ) {
4024  $rev = $this->getFirstRevision( $flags );
4025  return $rev ? $rev->getTimestamp() : null;
4026  }
4027 
4033  public function isNewPage() {
4034  $dbr = wfGetDB( DB_SLAVE );
4035  return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4036  }
4037 
4043  public function isBigDeletion() {
4045 
4046  if ( !$wgDeleteRevisionsLimit ) {
4047  return false;
4048  }
4049 
4050  if ( $this->mIsBigDeletion === null ) {
4051  $dbr = wfGetDB( DB_SLAVE );
4052 
4053  $revCount = $dbr->selectRowCount(
4054  'revision',
4055  '1',
4056  array( 'rev_page' => $this->getArticleID() ),
4057  __METHOD__,
4058  array( 'LIMIT' => $wgDeleteRevisionsLimit + 1 )
4059  );
4060 
4061  $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4062  }
4063 
4064  return $this->mIsBigDeletion;
4065  }
4072  public function estimateRevisionCount() {
4073  if ( !$this->exists() ) {
4074  return 0;
4075  }
4076 
4077  if ( $this->mEstimateRevisions === null ) {
4078  $dbr = wfGetDB( DB_SLAVE );
4079  $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4080  array( 'rev_page' => $this->getArticleID() ), __METHOD__ );
4081  }
4082 
4084  }
4085 
4095  public function countRevisionsBetween( $old, $new, $max = null ) {
4096  if ( !( $old instanceof Revision ) ) {
4097  $old = Revision::newFromTitle( $this, (int)$old );
4098  }
4099  if ( !( $new instanceof Revision ) ) {
4100  $new = Revision::newFromTitle( $this, (int)$new );
4101  }
4102  if ( !$old || !$new ) {
4103  return 0; // nothing to compare
4104  }
4105  $dbr = wfGetDB( DB_SLAVE );
4106  $conds = array(
4107  'rev_page' => $this->getArticleID(),
4108  'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4109  'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4110  );
4111  if ( $max !== null ) {
4112  return $dbr->selectRowCount( 'revision', '1',
4113  $conds,
4114  __METHOD__,
4115  array( 'LIMIT' => $max + 1 ) // extra to detect truncation
4116  );
4117  } else {
4118  return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4119  }
4120  }
4121 
4138  public function getAuthorsBetween( $old, $new, $limit, $options = array() ) {
4139  if ( !( $old instanceof Revision ) ) {
4140  $old = Revision::newFromTitle( $this, (int)$old );
4141  }
4142  if ( !( $new instanceof Revision ) ) {
4143  $new = Revision::newFromTitle( $this, (int)$new );
4144  }
4145  // XXX: what if Revision objects are passed in, but they don't refer to this title?
4146  // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4147  // in the sanity check below?
4148  if ( !$old || !$new ) {
4149  return null; // nothing to compare
4150  }
4151  $authors = array();
4152  $old_cmp = '>';
4153  $new_cmp = '<';
4154  $options = (array)$options;
4155  if ( in_array( 'include_old', $options ) ) {
4156  $old_cmp = '>=';
4157  }
4158  if ( in_array( 'include_new', $options ) ) {
4159  $new_cmp = '<=';
4160  }
4161  if ( in_array( 'include_both', $options ) ) {
4162  $old_cmp = '>=';
4163  $new_cmp = '<=';
4164  }
4165  // No DB query needed if $old and $new are the same or successive revisions:
4166  if ( $old->getId() === $new->getId() ) {
4167  return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4168  array() :
4169  array( $old->getUserText( Revision::RAW ) );
4170  } elseif ( $old->getId() === $new->getParentId() ) {
4171  if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4172  $authors[] = $old->getUserText( Revision::RAW );
4173  if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4174  $authors[] = $new->getUserText( Revision::RAW );
4175  }
4176  } elseif ( $old_cmp === '>=' ) {
4177  $authors[] = $old->getUserText( Revision::RAW );
4178  } elseif ( $new_cmp === '<=' ) {
4179  $authors[] = $new->getUserText( Revision::RAW );
4180  }
4181  return $authors;
4182  }
4184  $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4185  array(
4186  'rev_page' => $this->getArticleID(),
4187  "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4188  "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4189  ), __METHOD__,
4190  array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated
4191  );
4192  foreach ( $res as $row ) {
4193  $authors[] = $row->rev_user_text;
4194  }
4195  return $authors;
4196  }
4197 
4212  public function countAuthorsBetween( $old, $new, $limit, $options = array() ) {
4213  $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4214  return $authors ? count( $authors ) : 0;
4215  }
4216 
4223  public function equals( Title $title ) {
4224  // Note: === is necessary for proper matching of number-like titles.
4225  return $this->getInterwiki() === $title->getInterwiki()
4226  && $this->getNamespace() == $title->getNamespace()
4227  && $this->getDBkey() === $title->getDBkey();
4228  }
4229 
4236  public function isSubpageOf( Title $title ) {
4237  return $this->getInterwiki() === $title->getInterwiki()
4238  && $this->getNamespace() == $title->getNamespace()
4239  && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4240  }
4241 
4253  public function exists( $flags = 0 ) {
4254  $exists = $this->getArticleID( $flags ) != 0;
4255  Hooks::run( 'TitleExists', array( $this, &$exists ) );
4256  return $exists;
4257  }
4258 
4275  public function isAlwaysKnown() {
4276  $isKnown = null;
4277 
4288  Hooks::run( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
4289 
4290  if ( !is_null( $isKnown ) ) {
4291  return $isKnown;
4292  }
4293 
4294  if ( $this->isExternal() ) {
4295  return true; // any interwiki link might be viewable, for all we know
4296  }
4297 
4298  switch ( $this->mNamespace ) {
4299  case NS_MEDIA:
4300  case NS_FILE:
4301  // file exists, possibly in a foreign repo
4302  return (bool)wfFindFile( $this );
4303  case NS_SPECIAL:
4304  // valid special page
4305  return SpecialPageFactory::exists( $this->getDBkey() );
4306  case NS_MAIN:
4307  // selflink, possibly with fragment
4308  return $this->mDbkeyform == '';
4309  case NS_MEDIAWIKI:
4310  // known system message
4311  return $this->hasSourceText() !== false;
4312  default:
4313  return false;
4314  }
4315  }
4316 
4328  public function isKnown() {
4329  return $this->isAlwaysKnown() || $this->exists();
4330  }
4331 
4337  public function hasSourceText() {
4338  if ( $this->exists() ) {
4339  return true;
4340  }
4341 
4342  if ( $this->mNamespace == NS_MEDIAWIKI ) {
4343  // If the page doesn't exist but is a known system message, default
4344  // message content will be displayed, same for language subpages-
4345  // Use always content language to avoid loading hundreds of languages
4346  // to get the link color.
4348  list( $name, ) = MessageCache::singleton()->figureMessage(
4349  $wgContLang->lcfirst( $this->getText() )
4350  );
4351  $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4352  return $message->exists();
4353  }
4354 
4355  return false;
4356  }
4357 
4363  public function getDefaultMessageText() {
4365 
4366  if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4367  return false;
4368  }
4369 
4370  list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4371  $wgContLang->lcfirst( $this->getText() )
4372  );
4373  $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4374 
4375  if ( $message->exists() ) {
4376  return $message->plain();
4377  } else {
4378  return false;
4379  }
4380  }
4381 
4388  public function invalidateCache( $purgeTime = null ) {
4389  if ( wfReadOnly() ) {
4390  return false;
4391  }
4392 
4393  if ( $this->mArticleID === 0 ) {
4394  return true; // avoid gap locking if we know it's not there
4395  }
4396 
4397  $method = __METHOD__;
4398  $dbw = wfGetDB( DB_MASTER );
4399  $conds = $this->pageCond();
4400  $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method, $purgeTime ) {
4401  $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4402 
4403  $dbw->update(
4404  'page',
4405  array( 'page_touched' => $dbTimestamp ),
4406  $conds + array( 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ),
4407  $method
4408  );
4409  } );
4410 
4411  return true;
4412  }
4413 
4419  public function touchLinks() {
4420  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4421  if ( $this->getNamespace() == NS_CATEGORY ) {
4422  DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4423  }
4424  }
4425 
4432  public function getTouched( $db = null ) {
4433  if ( $db === null ) {
4434  $db = wfGetDB( DB_SLAVE );
4435  }
4436  $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4437  return $touched;
4438  }
4439 
4446  public function getNotificationTimestamp( $user = null ) {
4447  global $wgUser;
4448 
4449  // Assume current user if none given
4450  if ( !$user ) {
4451  $user = $wgUser;
4452  }
4453  // Check cache first
4454  $uid = $user->getId();
4455  if ( !$uid ) {
4456  return false;
4457  }
4458  // avoid isset here, as it'll return false for null entries
4459  if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4460  return $this->mNotificationTimestamp[$uid];
4461  }
4462  // Don't cache too much!
4463  if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4464  $this->mNotificationTimestamp = array();
4465  }
4466 
4467  $watchedItem = WatchedItem::fromUserTitle( $user, $this );
4468  $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4469 
4470  return $this->mNotificationTimestamp[$uid];
4471  }
4472 
4479  public function getNamespaceKey( $prepend = 'nstab-' ) {
4481  // Gets the subject namespace if this title
4482  $namespace = MWNamespace::getSubject( $this->getNamespace() );
4483  // Checks if canonical namespace name exists for namespace
4484  if ( MWNamespace::exists( $this->getNamespace() ) ) {
4485  // Uses canonical namespace name
4486  $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4487  } else {
4488  // Uses text of namespace
4489  $namespaceKey = $this->getSubjectNsText();
4490  }
4491  // Makes namespace key lowercase
4492  $namespaceKey = $wgContLang->lc( $namespaceKey );
4493  // Uses main
4494  if ( $namespaceKey == '' ) {
4495  $namespaceKey = 'main';
4496  }
4497  // Changes file to image for backwards compatibility
4498  if ( $namespaceKey == 'file' ) {
4499  $namespaceKey = 'image';
4500  }
4501  return $prepend . $namespaceKey;
4502  }
4503 
4510  public function getRedirectsHere( $ns = null ) {
4511  $redirs = array();
4512 
4513  $dbr = wfGetDB( DB_SLAVE );
4514  $where = array(
4515  'rd_namespace' => $this->getNamespace(),
4516  'rd_title' => $this->getDBkey(),
4517  'rd_from = page_id'
4518  );
4519  if ( $this->isExternal() ) {
4520  $where['rd_interwiki'] = $this->getInterwiki();
4521  } else {
4522  $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4523  }
4524  if ( !is_null( $ns ) ) {
4525  $where['page_namespace'] = $ns;
4526  }
4527 
4528  $res = $dbr->select(
4529  array( 'redirect', 'page' ),
4530  array( 'page_namespace', 'page_title' ),
4531  $where,
4532  __METHOD__
4533  );
4534 
4535  foreach ( $res as $row ) {
4536  $redirs[] = self::newFromRow( $row );
4537  }
4538  return $redirs;
4539  }
4540 
4546  public function isValidRedirectTarget() {
4547  global $wgInvalidRedirectTargets;
4548 
4549  if ( $this->isSpecialPage() ) {
4550  // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4551  if ( $this->isSpecial( 'Userlogout' ) ) {
4552  return false;
4553  }
4554 
4555  foreach ( $wgInvalidRedirectTargets as $target ) {
4556  if ( $this->isSpecial( $target ) ) {
4557  return false;
4558  }
4559  }
4560  }
4561 
4562  return true;
4563  }
4564 
4570  public function getBacklinkCache() {
4571  return BacklinkCache::get( $this );
4572  }
4573 
4579  public function canUseNoindex() {
4581 
4582  $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4583  ? $wgContentNamespaces
4585 
4586  return !in_array( $this->mNamespace, $bannedNamespaces );
4587 
4588  }
4589 
4600  public function getCategorySortkey( $prefix = '' ) {
4601  $unprefixed = $this->getText();
4602 
4603  // Anything that uses this hook should only depend
4604  // on the Title object passed in, and should probably
4605  // tell the users to run updateCollations.php --force
4606  // in order to re-sort existing category relations.
4607  Hooks::run( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
4608  if ( $prefix !== '' ) {
4609  # Separate with a line feed, so the unprefixed part is only used as
4610  # a tiebreaker when two pages have the exact same prefix.
4611  # In UCA, tab is the only character that can sort above LF
4612  # so we strip both of them from the original prefix.
4613  $prefix = strtr( $prefix, "\n\t", ' ' );
4614  return "$prefix\n$unprefixed";
4615  }
4616  return $unprefixed;
4617  }
4618 
4627  public function getPageLanguage() {
4629  if ( $this->isSpecialPage() ) {
4630  // special pages are in the user language
4631  return $wgLang;
4632  }
4633 
4634  // Checking if DB language is set
4635  if ( $this->mDbPageLanguage ) {
4636  return wfGetLangObj( $this->mDbPageLanguage );
4637  }
4638 
4639  if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4640  // Note that this may depend on user settings, so the cache should
4641  // be only per-request.
4642  // NOTE: ContentHandler::getPageLanguage() may need to load the
4643  // content to determine the page language!
4644  // Checking $wgLanguageCode hasn't changed for the benefit of unit
4645  // tests.
4646  $contentHandler = ContentHandler::getForTitle( $this );
4647  $langObj = wfGetLangObj( $contentHandler->getPageLanguage( $this ) );
4648  $this->mPageLanguage = array( $langObj->getCode(), $wgLanguageCode );
4649  } else {
4650  $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4651  }
4652 
4653  return $langObj;
4654  }
4655 
4664  public function getPageViewLanguage() {
4665  global $wgLang;
4666 
4667  if ( $this->isSpecialPage() ) {
4668  // If the user chooses a variant, the content is actually
4669  // in a language whose code is the variant code.
4670  $variant = $wgLang->getPreferredVariant();
4671  if ( $wgLang->getCode() !== $variant ) {
4672  return Language::factory( $variant );
4673  }
4674 
4675  return $wgLang;
4676  }
4677 
4678  // @note Can't be cached persistently, depends on user settings.
4679  // @note ContentHandler::getPageViewLanguage() may need to load the
4680  // content to determine the page language!
4681  $contentHandler = ContentHandler::getForTitle( $this );
4682  $pageLang = $contentHandler->getPageViewLanguage( $this );
4683  return $pageLang;
4684  }
4685 
4696  public function getEditNotices( $oldid = 0 ) {
4697  $notices = array();
4698 
4699  // Optional notice for the entire namespace
4700  $editnotice_ns = 'editnotice-' . $this->getNamespace();
4701  $msg = wfMessage( $editnotice_ns );
4702  if ( $msg->exists() ) {
4703  $html = $msg->parseAsBlock();
4704  // Edit notices may have complex logic, but output nothing (T91715)
4705  if ( trim( $html ) !== '' ) {
4706  $notices[$editnotice_ns] = Html::rawElement(
4707  'div',
4708  array( 'class' => array(
4709  'mw-editnotice',
4710  'mw-editnotice-namespace',
4711  Sanitizer::escapeClass( "mw-$editnotice_ns" )
4712  ) ),
4713  $html
4714  );
4715  }
4716  }
4717 
4718  if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4719  // Optional notice for page itself and any parent page
4720  $parts = explode( '/', $this->getDBkey() );
4721  $editnotice_base = $editnotice_ns;
4722  while ( count( $parts ) > 0 ) {
4723  $editnotice_base .= '-' . array_shift( $parts );
4724  $msg = wfMessage( $editnotice_base );
4725  if ( $msg->exists() ) {
4726  $html = $msg->parseAsBlock();
4727  if ( trim( $html ) !== '' ) {
4728  $notices[$editnotice_base] = Html::rawElement(
4729  'div',
4730  array( 'class' => array(
4731  'mw-editnotice',
4732  'mw-editnotice-base',
4733  Sanitizer::escapeClass( "mw-$editnotice_base" )
4734  ) ),
4735  $html
4736  );
4737  }
4738  }
4739  }
4740  } else {
4741  // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4742  $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4743  $msg = wfMessage( $editnoticeText );
4744  if ( $msg->exists() ) {
4745  $html = $msg->parseAsBlock();
4746  if ( trim( $html ) !== '' ) {
4747  $notices[$editnoticeText] = Html::rawElement(
4748  'div',
4749  array( 'class' => array(
4750  'mw-editnotice',
4751  'mw-editnotice-page',
4752  Sanitizer::escapeClass( "mw-$editnoticeText" )
4753  ) ),
4754  $html
4755  );
4756  }
4757  }
4758  }
4759 
4760  Hooks::run( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
4761  return $notices;
4762  }
4763 
4767  public function __sleep() {
4768  return array(
4769  'mNamespace',
4770  'mDbkeyform',
4771  'mFragment',
4772  'mInterwiki',
4773  'mLocalInterwiki',
4774  'mUserCaseDBKey',
4775  'mDefaultNamespace',
4776  );
4777  }
4778 
4779  public function __wakeup() {
4780  $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4781  $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4782  $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4783  }
4784 
4785 }
getEarliestRevTime($flags=0)
Get the oldest revision timestamp of this page.
Definition: Title.php:3994
bool $mHasSubpages
Whether a page has any subpages.
Definition: Title.php:124
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition: Title.php:4246
getText()
Returns the title in text form, without namespace prefix or fragment.
Definition: TitleValue.php:113
static newFromRow($row)
Make a Title object from a DB row.
Definition: Title.php:423
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition: Title.php:3009
getLatestRevID($flags=0)
What is the page_latest field for this page?
Definition: Title.php:3231
setFragment($fragment)
Set the fragment for this title.
Definition: Title.php:1395
static newFromID($id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:375
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Definition: Title.php:629
static whoIs($id)
Get the username corresponding to a given user ID.
Definition: User.php:638
static isMovable($index)
Can pages in the given namespace be moved?
Definition: MWNamespace.php:67
touchLinks()
Update page_touched timestamps and send squid purge messages for pages linking to this title...
Definition: Title.php:4390
A codec for MediaWiki page titles.
getFragment()
Get the Title fragment (i.e. the bit after the #) in text form.
Definition: Title.php:1358
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:1750
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:104
exists($flags=0)
Check if page exists.
Definition: Title.php:4224
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable...
Definition: Title.php:1161
getInternalURL($query= '', $query2=false)
Get the URL form for an internal link.
Definition: Title.php:1809
getRootTitle()
Get the root page name title, i.e.
Definition: Title.php:1500
static getLocalNameFor($name, $subpage=false)
Get the local name for a specified canonical name.
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:1381
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition: Title.php:4550
isJsSubpage()
Is this a .js subpage of a user page?
Definition: Title.php:1285
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:277
wasLocalInterwiki()
Was this a local interwiki link?
Definition: Title.php:839
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 '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 wfIsTrustedProxy() $ip:IP being check $result:Change this value to override the result of wfIsTrustedProxy() '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:1748
getSquidURLs()
Get a list of URLs to purge from the Squid cache when this page changes.
Definition: Title.php:3531
$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:2284
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page...
Definition: Title.php:1151
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:927
Handles the backend logic of moving a page from one title to another.
Definition: MovePage.php:28
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition: Title.php:2666
isSpecial($name)
Returns true if this title resolves to the named special page.
Definition: Title.php:1062
static clearCaches()
Definition: Title.php:3285
or
false for read/write
getArticleID($flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3142
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition: Title.php:3035
const NS_MAIN
Definition: Defines.php:69
$success
static nameOf($id)
Get the prefixed DB key associated with an ID.
Definition: Title.php:593
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:900
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition: Title.php:1555
getBaseText()
Get the base page name without a namespace, i.e.
Definition: Title.php:1515
static newMainPage()
Create a new Title for the Main Page.
Definition: Title.php:527
getTemplateLinksFrom($options=array())
Get an array of Title objects used on this Title as a template Also stores the IDs in the link cache...
Definition: Title.php:3483
moveSubpages($nt, $auth=true, $reason= '', $createRedirect=true)
Move this page's subpages to be subpages of $nt.
Definition: Title.php:3679
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition: Title.php:4334
getAuthorsBetween($old, $new, $limit, $options=array())
Get the authors between the given revisions or revision IDs.
Definition: Title.php:4109
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:324
getEditNotices($oldid=0)
Get a list of rendered edit notices for this page.
Definition: Title.php:4667
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:66
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition: Title.php:1005
__wakeup()
Definition: Title.php:4750
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:1750
getUserPermissionsErrorsInternal($action, $user, $rigor= 'secure', $short=false)
Can $user perform $action on this page? This is an internal function, which checks ONLY that previous...
Definition: Title.php:2439
namespace and then decline to actually register it file or subcat img or subcat 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:968
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition: Title.php:862
getUserPermissionsErrors($action, $user, $rigor= 'secure', $ignoreErrors=array())
Can $user perform $action on this page?
Definition: Title.php:1920
$wgActionPaths
Definition: img_auth.php:46
isCssJsSubpage()
Is this a .css or .js subpage of a user page?
Definition: Title.php:1249
static singleton()
Definition: GenderCache.php:39
static canTalk($index)
Can this namespace ever have a talk namespace?
$wgInternalServer
Internal server name as known to Squid, if different.
isWatchable()
Can this title be added to a user's watchlist?
Definition: Title.php:1043
checkUserBlock($action, $user, $errors, $rigor, $short)
Check that the user isn't blocked from editing.
Definition: Title.php:2291
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:188
static isTalk($index)
Is the given namespace a talk namespace?
Definition: MWNamespace.php:97
$wgRestrictionLevels
Rights which can be required for each protection level (via action=protect)
hasSubjectNamespace($ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition: Title.php:1140
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition: Title.php:3322
bool $mIsBigDeletion
Would deleting this page be a big deletion?
Definition: Title.php:132
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition: Title.php:1112
moveNoAuth(&$nt)
Move this page without authentication.
Definition: Title.php:3575
isTalkPage()
Is this a talk page of some sort?
Definition: Title.php:1295
string $mUrlform
URL-encoded form of the main part.
Definition: Title.php:59
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title...
wfDebug($text, $dest= 'all', array $context=array())
Sends a line to the debug log if enabled or, optionally, to a comment in output.
null for the local wiki Added in
Definition: hooks.txt:1381
isRedirect($flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3168
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:38
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition: Title.php:4014
const NS_SPECIAL
Definition: Defines.php:58
prefix($name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object. ...
Definition: Title.php:1406
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:1331
static escapeFragmentForURL($fragment)
Escape a text fragment, say from a link, for a URL.
Definition: Title.php:773
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2504
isValidMoveTarget($nt)
Checks if $this can be moved to a given Title.
Definition: Title.php:3797
static escapeClass($class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1166
$wgSemiprotectedRestrictionLevels
Restriction levels that should be considered "semiprotected".
$wgEmailConfirmToEdit
Should editors be required to have a validated e-mail address before being allowed to edit...
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1436
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:235
getBaseTitle()
Get the base page name title, i.e.
Definition: Title.php:1540
Represents a title within MediaWiki.
Definition: Title.php:33
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:3845
getBacklinkCache()
Get a backlink cache object.
Definition: Title.php:4541
$wgArticlePath
Definition: img_auth.php:45
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:1948
getSkinFromCssJsSubpage()
Trim down a .css or .js subpage title to get the corresponding skin name.
Definition: Title.php:1260
static getFilteredRestrictionTypes($exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition: Title.php:2499
$wgRestrictionTypes
Set of available actions that can be restricted via action=protect You probably shouldn't change this...
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition: Title.php:1304
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition: Title.php:97
getNotificationTimestamp($user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition: Title.php:4417
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias...
Definition: Title.php:1078
static isEveryoneAllowed($right)
Check if all users have the given permission.
Definition: User.php:4554
getSubpages($limit=-1)
Get all subpages of this page.
Definition: Title.php:3063
userCan($action, $user=null, $rigor= 'secure')
Can $user perform $action on this page?
Definition: Title.php:1896
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
$wgUseSquid
Enable/disable Squid.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:39
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility...
static isCapitalized($index)
Is the namespace first-letter capitalized?
static get(Title $title)
Create a new BacklinkCache or reuse any existing one.
getLinksTo($options=array(), $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:3368
$wgLanguageCode
Site language code.
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition: Title.php:88
inNamespace($ns)
Returns true if the title is inside the specified namespace.
Definition: Title.php:1101
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition: Title.php:3001
getContentModel($flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:951
getLength($flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition: Title.php:3203
string $mDbkeyform
Main part with underscores.
Definition: Title.php:61
getNsText()
Get the namespace text.
Definition: Title.php:980
static fromUserTitle($user, $title, $checkRights=WatchedItem::CHECK_USER_RIGHTS)
Create a WatchedItem object with the given user and title.
Definition: WatchedItem.php:76
isValidMoveOperation(&$nt, $auth=true, $reason= '')
Check whether a given move operation would be valid.
Definition: Title.php:3590
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:3948
loadRestrictionsFromResultWrapper($res, $oldFashionedRestrictions=null)
Loads a string into mRestrictions array.
Definition: Title.php:2871
__sleep()
Definition: Title.php:4738
string bool $mOldRestrictions
Text form (spaces not underscores) of the main part.
Definition: Title.php:86
__construct()
Definition: Title.php:185
wfReadOnly()
Check whether the wiki is in read-only mode.
isExternal()
Is this Title interwiki?
Definition: Title.php:819
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:1615
deleteTitleProtection()
Remove any title protection due to page existing.
Definition: Title.php:2588
static getMain()
Static methods.
int $mNamespace
Namespace index, i.e.
Definition: Title.php:65
static groupHasPermission($group, $role)
Check, if the given group has the given permission.
Definition: User.php:4541
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:4308
static newFromIDs($ids)
Make an array of titles from an array of IDs.
Definition: Title.php:397
wfAppendQuery($url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
$wgWhitelistRead
Pages anonymous user may see, set as an array of pages titles.
static getTitleCache()
Definition: Title.php:339
static deprecated($func, $version, $component=false)
Logs a deprecation warning, visible if $wgDevelopmentWarnings, but only if self::$enableDeprecationWa...
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition: Title.php:179
isAllowed($action= '')
Internal mechanics of testing a permission.
Definition: User.php:3234
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:918
__toString()
Return a string representation of this title.
Definition: Title.php:1450
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition: Title.php:1596
wfWarn($msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
hasFragment()
Check if a Title fragment is set.
Definition: Title.php:1368
hasContentModel($id)
Convenience method for checking a title's content model name.
Definition: Title.php:971
$wgExemptFromUserRobotsControl
An array of namespace keys in which the INDEX/__NOINDEX__ magic words will not function, so users can't decide whether pages in that namespace are indexed by search engines.
$wgLocalInterwikis
Array for multiple $wgLocalInterwiki values, in case there are several interwiki prefixes that point ...
wfGetDB($db, $groups=array(), $wiki=false)
Get a Database object.
namespace and then decline to actually register it file or subcat img or subcat 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:968
static addUpdate(DeferrableUpdate $update)
Add an update to the deferred list.
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition: Title.php:2856
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition: Title.php:1202
const NS_MEDIA
Definition: Defines.php:57
static newFromDBkey($key)
Create a new Title from a prefixed DB key.
Definition: Title.php:196
getRootText()
Get the root page name text without a namespace, i.e.
Definition: Title.php:1480
getTouched($db=null)
Get the last touched timestamp.
Definition: Title.php:4403
getLocalURL($query= '', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1694
$res
Definition: database.txt:21
checkCSSandJSPermissions($action, $user, $errors, $rigor, $short)
Check CSS/JS sub-page permissions.
Definition: Title.php:2108
bool string $mContentModel
ID of the page's content model, i.e.
Definition: Title.php:80
canExist()
Is this in a namespace that allows actual pages?
Definition: Title.php:1034
getSubpage($text)
Get the title for a subpage of the current page.
Definition: Title.php:1576
MediaWiki exception.
Definition: MWException.php:26
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:48
namespace and then decline to actually register it file or subcat img or subcat RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition: hooks.txt:882
static run($event, array $args=array(), $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:137
null $mRedirect
Is the article at this title a redirect?
Definition: Title.php:119
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
$wgWhitelistReadRegexp
Pages anonymous user may see, set as an array of regular expressions.
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition: Title.php:4517
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"&lt
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not...
Definition: Title.php:2684
invalidateCache($purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition: Title.php:4359
$cache
Definition: mcc.php:32
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition: Title.php:1347
const NS_CATEGORY
Definition: Defines.php:83
moveTo(&$nt, $auth=true, $reason= '', $createRedirect=true)
Move a title to a new location.
Definition: Title.php:3645
getRedirectsHere($ns=null)
Get all extant redirects to this Title.
Definition: Title.php:4481
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition: Revision.php:416
checkSpecialsAndNSPermissions($action, $user, $errors, $rigor, $short)
Check permissions on special pages & namespaces.
Definition: Title.php:2079
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
getCanonicalURL($query= '', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition: Title.php:1829
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition: Title.php:4207
static makeTitleSafe($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:506
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition: Title.php:1460
static subjectEquals($ns1, $ns2)
Returns whether the specified namespaces share the same subject.
validateFileMoveOperation($nt)
Check if the requested move target is a valid file move target.
Definition: Title.php:3617
isCssSubpage()
Is this a .css subpage of a user page?
Definition: Title.php:1275
static $titleCache
Definition: Title.php:35
const DB_SLAVE
Definition: Defines.php:46
isMainPage()
Is this the mainpage?
Definition: Title.php:1182
static decodeCharReferencesAndNormalize($text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
Definition: Sanitizer.php:1436
Allows to change the fields on the form that will be generated just before adding its HTML to parser output an object of one of the gallery classes(inheriting from ImageGalleryBase) $html conditions will AND in the final query as a Content object as a Content object $title
Definition: hooks.txt:327
static hasSubpages($index)
Does the namespace allow subpages?
namespace and then decline to actually register it file or subcat img or subcat 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:968
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:941
const PROTO_RELATIVE
Definition: Defines.php:263
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
string $mInterwiki
Interwiki prefix.
Definition: Title.php:67
equals(Title $title)
Compare with another title.
Definition: Title.php:4194
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:4299
const NS_FILE
Definition: Defines.php:75
static newFromResult($res)
Definition: TitleArray.php:38
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:34
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition: Title.php:2801
static makeContent($text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
static newFromRedirect($text)
Extract a redirect destination from a string and return the Title, or null if the text doesn't contai...
Definition: Title.php:546
static escapeId($id, $options=array())
Given a value, escape it so that it can be used in an id attribute and return it. ...
Definition: Sanitizer.php:1122
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:1543
checkCascadingSourcesRestrictions($action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition: Title.php:2178
static equals($ns1, $ns2)
Returns whether the specified namespaces are the same namespace.
isSubpage()
Is this a subpage?
Definition: Title.php:1191
getInterwiki()
Get the interwiki prefix.
Definition: Title.php:830
const RAW
Definition: Revision.php:72
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:435
areCascadeProtectionSourcesLoaded($getPages=true)
Determines whether cascading protection sources have already been loaded from the database...
Definition: Title.php:2698
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:882
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:3495
const NS_MEDIAWIKI
Definition: Defines.php:77
const PROTO_HTTP
Definition: Defines.php:261
getNamespaceKey($prepend= 'nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition: Title.php:4450
countRevisionsBetween($old, $new, $max=null)
Get the number of revisions between the given revision.
Definition: Title.php:4066
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition: Title.php:93
static fetch($prefix)
Fetch an Interwiki object.
Definition: Interwiki.php:79
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:240
static newFromTitleValue(TitleValue $titleValue)
Create a new Title from a TitleValue.
Definition: Title.php:215
getRestrictions($action)
Accessor/initialisation for mRestrictions.
Definition: Title.php:2814
namespace and then decline to actually register it file or subcat img or subcat 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:968
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition: Title.php:1314
int $mLength
The page length, 0 for special pages.
Definition: Title.php:117
Handles purging appropriate Squid URLs given a title (or titles)
Definition: SquidUpdate.php:28
wfDebugLog($logGroup, $text, $dest= 'all', array $context=array())
Send a line to a supplementary debug log file, if configured, or main debug log if not...
getFirstRevision($flags=0)
Get the first revision of the page.
Definition: Title.php:3972
static newFromTextThrow($text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid, rather than returning null.
Definition: Title.php:268
isSpecialPage()
Returns true if this is a special page.
Definition: Title.php:1052
$wgContentNamespaces
Array of namespaces which can be deemed to contain valid "content", as far as the site statistics are...
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 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:2301
quickUserCan($action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition: Title.php:1883
static newFromTitle($title, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given title.
Definition: Revision.php:104
getEditURL()
Get the edit URL for this Title.
Definition: Title.php:1841
wfGetAllCallers($limit=3)
Return a string consisting of callers in the stack.
$wgLegalTitleChars
Allowed title characters – regex character class Don't change this unless you know what you're doing...
loadRestrictions($oldFashionedRestrictions=null)
Load restrictions from the page_restrictions table.
Definition: Title.php:2963
static & singleton()
Get an instance of this class.
Definition: LinkCache.php:57
static getTalk($index)
Get the talk namespace index for a given namespace.
static convertByteClassToUnicodeClass($byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition: Title.php:643
$wgNamespaceProtection
Set the minimum permissions required to edit pages in each namespace.
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition: Title.php:3746
static makeName($ns, $title, $fragment= '', $interwiki= '', $canoncialNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition: Title.php:747
const PROTO_CANONICAL
Definition: Defines.php:265
getLinkURL($query= '', $query2=false, $proto=PROTO_RELATIVE)
Get a URL that's the simplest URL that will be valid to link, locally, to the current Title...
Definition: Title.php:1786
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
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition: Title.php:4635
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition: Title.php:1585
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition: Title.php:91
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
const CONTENT_MODEL_JAVASCRIPT
Definition: Defines.php:278
string $mDbPageLanguage
The page language code from the database.
Definition: Title.php:128
static resolveAlias($alias)
Given a special page name with a possible subpage, return an array where the first element is the spe...
getId()
Get the user's ID.
Definition: User.php:1943
getRestrictionTypes()
Returns restriction types for the current Title.
Definition: Title.php:2517
static exists($name)
Check if a given name exist as a special page or as a special page alias.
static isContent($index)
Does this namespace contain content, for the purposes of calculating statistics, etc?
TitleValue $mTitleValue
A corresponding TitleValue object.
Definition: Title.php:130
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition: Title.php:126
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
const NS_USER
Definition: Defines.php:71
bool $mWatched
Is $wgUser watching this page? null if unfilled, accessed through userIsWatching() ...
Definition: Title.php:115
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition: Title.php:849
string $mFragment
Title fragment (i.e.
Definition: Title.php:71
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition: Title.php:73
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition: Title.php:110
getCategorySortkey($prefix= '')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition: Title.php:4571
wfArrayToCgi($array1, $array2=null, $prefix= '')
This function takes two arrays as input, and returns a CGI-style string, e.g.
static newFromRedirectArray($text)
Extract a redirect destination from a string and return an array of Titles, or null if the text doesn...
Definition: Title.php:580
static newFromURL($url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition: Title.php:316
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition: Title.php:4598
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles...
Definition: Title.php:42
checkReadPermissions($action, $user, $errors, $rigor, $short)
Check that the user is allowed to read this page.
Definition: Title.php:2329
when a variable name is used in a function
Definition: design.txt:93
static getTitleParser()
B/C kludge: provide a TitleParser for use by Title.
Definition: Title.php:143
isCssOrJsPage()
Could this page contain custom CSS or JavaScript for the global UI.
Definition: Title.php:1231
getCascadeProtectionSources($getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition: Title.php:2715
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition: Title.php:1376
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition: Title.php:3907
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
const CONTENT_MODEL_CSS
Definition: Defines.php:279
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition: Title.php:3116
isNewPage()
Check if this is a new page.
Definition: Title.php:4004
missingPermissionError($action, $short)
Get a description array when the user doesn't have the right to perform $action (i.e.
Definition: Title.php:2404
isProtected($action= '')
Does the title correspond to a protected article?
Definition: Title.php:2634
canTalk()
Could this title have a corresponding talk page?
Definition: Title.php:1025
$count
checkActionPermissions($action, $user, $errors, $rigor, $short)
Check action permissions not already checked in checkQuickPermissions.
Definition: Title.php:2225
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition: Title.php:99
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:615
static rawElement($element, $attribs=array(), $contents= '')
Returns an HTML element in a string.
Definition: Html.php:213
getTemplateLinksTo($options=array())
Get an array of Title objects using this Title as a template Also stores the IDs in the link cache...
Definition: Title.php:3410
$mPrefixedText
Text form (spaces not underscores) of the main part.
Definition: Title.php:102
$wgServer
URL of the server.
isDeleted()
Is there a version of this page in the deletion archive?
Definition: Title.php:3091
const DB_MASTER
Definition: Defines.php:47
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition: Title.php:804
loadRestrictionsFromRows($rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition: Title.php:2890
array $mRestrictions
Array of groups allowed to edit this article.
Definition: Title.php:84
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition: Title.php:2544
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition: Title.php:69
getLinksFrom($options=array(), $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:3426
static compare($a, $b)
Callback for usort() to do title sorts by (namespace, title)
Definition: Title.php:789
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition: Title.php:82
wfFindFile($title, $options=array())
Find a file.
getParentCategoryTree($children=array())
Get a tree of parent categories.
Definition: Title.php:3880
getTalkNsText()
Get the namespace text of the talk page.
Definition: Title.php:1015
checkPageRestrictions($action, $user, $errors, $rigor, $short)
Check against page_restrictions table requirements on this page.
Definition: Title.php:2144
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries...
Definition: Title.php:353
maintenance dev scripts can help quickly setup a local MediaWiki for development purposes Wikis setup in this way are NOT meant to be publicly available They use a development database not acceptible for use in production Place a sqlite database in an unsafe location a real wiki should never place it in And use predictable default logins for the initial administrator user Running maintenance dev install sh will download and install a local copy of php
Definition: README:5
checkPermissionHooks($action, $user, $errors, $rigor, $short)
Check various permission hooks.
Definition: Title.php:2046
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:181
string $mTextform
Text form (spaces not underscores) of the main part.
Definition: Title.php:57
static selectFields()
Return the list of revision fields that should be selected to create a new page.
Definition: WikiPage.php:258
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition: Title.php:2830
isSemiProtected($action= 'edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition: Title.php:2606
namespace and then decline to actually register it file or subcat img or subcat 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:968
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:75
resultToError($errors, $result)
Add the resulting error code to the errors array.
Definition: Title.php:2018
getRestrictionExpiry($action)
Get the expiry time for the restriction against a given action.
Definition: Title.php:2844
userIsWatching()
Is $wgUser watching this page?
Definition: Title.php:1856
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:240
getPreviousRevisionID($revId, $flags=0)
Get the revision ID of the previous revision.
Definition: Title.php:3923
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
$wgMaximumMovedPages
Maximum number of pages to move at once when moving subpages with a page.
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:3300
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition: Title.php:1214
static & makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:482
static singleton()
Get the signleton instance of this class.
purgeSquid()
Purge all applicable Squid URLs.
Definition: Title.php:3559
static getGroupsWithPermission($role)
Get all the groups who have a given permission.
Definition: User.php:4518
resetArticleID($newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition: Title.php:3263
countAuthorsBetween($old, $new, $limit, $options=array())
Get the number of authors between the given revisions or revision IDs.
Definition: Title.php:4183
getTitleValue()
Get a TitleValue object representing this Title.
Definition: Title.php:879
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition: Title.php:95
getPartialURL()
Get the URL-encoded form of the main part.
Definition: Title.php:909
static newFromRedirectRecurse($text)
Extract a redirect destination from a string and return the Title, or null if the text doesn't contai...
Definition: Title.php:563
wfGetLangObj($langcode=false)
Return a Language object from $langcode.
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1424
getFullURL($query= '', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition: Title.php:1653
estimateRevisionCount()
Get the approximate revision count of this page.
Definition: Title.php:4043
$wgUser
Definition: Setup.php:662
$matches
$mNotificationTimestamp
Text form (spaces not underscores) of the main part.
Definition: Title.php:122
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition: Title.php:63