MediaWiki REL1_29
Title.php
Go to the documentation of this file.
1<?php
30
39class Title implements LinkTarget {
41 static private $titleCache = null;
42
48 const CACHE_MAX = 1000;
49
54 const GAID_FOR_UPDATE = 1;
55
61 // @{
62
64 public $mTextform = '';
65
67 public $mUrlform = '';
68
70 public $mDbkeyform = '';
71
73 protected $mUserCaseDBKey;
74
77
79 public $mInterwiki = '';
80
82 private $mLocalInterwiki = false;
83
85 public $mFragment = '';
86
88 public $mArticleID = -1;
89
91 protected $mLatestID = false;
92
97 private $mContentModel = false;
98
103 private $mForcedContentModel = false;
104
107
109 public $mRestrictions = [];
110
112 protected $mOldRestrictions = false;
113
116
119
121 protected $mRestrictionsExpiry = [];
122
125
128
130 public $mRestrictionsLoaded = false;
131
133 protected $mPrefixedText = null;
134
137
144
146 protected $mLength = -1;
147
149 public $mRedirect = null;
150
153
156
158 private $mPageLanguage = false;
159
162 private $mDbPageLanguage = false;
163
165 private $mTitleValue = null;
166
168 private $mIsBigDeletion = null;
169 // @}
170
179 private static function getTitleFormatter() {
180 return MediaWikiServices::getInstance()->getTitleFormatter();
181 }
182
191 private static function getInterwikiLookup() {
192 return MediaWikiServices::getInstance()->getInterwikiLookup();
193 }
194
198 function __construct() {
199 }
200
209 public static function newFromDBkey( $key ) {
210 $t = new Title();
211 $t->mDbkeyform = $key;
212
213 try {
214 $t->secureAndSplit();
215 return $t;
216 } catch ( MalformedTitleException $ex ) {
217 return null;
218 }
219 }
220
228 public static function newFromTitleValue( TitleValue $titleValue ) {
229 return self::newFromLinkTarget( $titleValue );
230 }
231
239 public static function newFromLinkTarget( LinkTarget $linkTarget ) {
240 if ( $linkTarget instanceof Title ) {
241 // Special case if it's already a Title object
242 return $linkTarget;
243 }
244 return self::makeTitle(
245 $linkTarget->getNamespace(),
246 $linkTarget->getText(),
247 $linkTarget->getFragment(),
248 $linkTarget->getInterwiki()
249 );
250 }
251
265 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
266 // DWIM: Integers can be passed in here when page titles are used as array keys.
267 if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
268 throw new InvalidArgumentException( '$text must be a string.' );
269 }
270 if ( $text === null ) {
271 return null;
272 }
273
274 try {
275 return Title::newFromTextThrow( strval( $text ), $defaultNamespace );
276 } catch ( MalformedTitleException $ex ) {
277 return null;
278 }
279 }
280
295 public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
296 if ( is_object( $text ) ) {
297 throw new MWException( '$text must be a string, given an object' );
298 }
299
300 $titleCache = self::getTitleCache();
301
302 // Wiki pages often contain multiple links to the same page.
303 // Title normalization and parsing can become expensive on pages with many
304 // links, so we can save a little time by caching them.
305 // In theory these are value objects and won't get changed...
306 if ( $defaultNamespace == NS_MAIN ) {
307 $t = $titleCache->get( $text );
308 if ( $t ) {
309 return $t;
310 }
311 }
312
313 // Convert things like &eacute; &#257; or &#x3017; into normalized (T16952) text
314 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
315
316 $t = new Title();
317 $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
318 $t->mDefaultNamespace = intval( $defaultNamespace );
319
320 $t->secureAndSplit();
321 if ( $defaultNamespace == NS_MAIN ) {
322 $titleCache->set( $text, $t );
323 }
324 return $t;
325 }
326
342 public static function newFromURL( $url ) {
343 $t = new Title();
344
345 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
346 # but some URLs used it as a space replacement and they still come
347 # from some external search tools.
348 if ( strpos( self::legalChars(), '+' ) === false ) {
349 $url = strtr( $url, '+', ' ' );
350 }
351
352 $t->mDbkeyform = strtr( $url, ' ', '_' );
353
354 try {
355 $t->secureAndSplit();
356 return $t;
357 } catch ( MalformedTitleException $ex ) {
358 return null;
359 }
360 }
361
365 private static function getTitleCache() {
366 if ( self::$titleCache == null ) {
367 self::$titleCache = new HashBagOStuff( [ 'maxKeys' => self::CACHE_MAX ] );
368 }
369 return self::$titleCache;
370 }
371
379 protected static function getSelectFields() {
381
382 $fields = [
383 'page_namespace', 'page_title', 'page_id',
384 'page_len', 'page_is_redirect', 'page_latest',
385 ];
386
388 $fields[] = 'page_content_model';
389 }
390
391 if ( $wgPageLanguageUseDB ) {
392 $fields[] = 'page_lang';
393 }
394
395 return $fields;
396 }
397
405 public static function newFromID( $id, $flags = 0 ) {
406 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
407 $row = $db->selectRow(
408 'page',
409 self::getSelectFields(),
410 [ 'page_id' => $id ],
411 __METHOD__
412 );
413 if ( $row !== false ) {
414 $title = Title::newFromRow( $row );
415 } else {
416 $title = null;
417 }
418 return $title;
419 }
420
427 public static function newFromIDs( $ids ) {
428 if ( !count( $ids ) ) {
429 return [];
430 }
432
433 $res = $dbr->select(
434 'page',
435 self::getSelectFields(),
436 [ 'page_id' => $ids ],
437 __METHOD__
438 );
439
440 $titles = [];
441 foreach ( $res as $row ) {
442 $titles[] = Title::newFromRow( $row );
443 }
444 return $titles;
445 }
446
453 public static function newFromRow( $row ) {
454 $t = self::makeTitle( $row->page_namespace, $row->page_title );
455 $t->loadFromRow( $row );
456 return $t;
457 }
458
465 public function loadFromRow( $row ) {
466 if ( $row ) { // page found
467 if ( isset( $row->page_id ) ) {
468 $this->mArticleID = (int)$row->page_id;
469 }
470 if ( isset( $row->page_len ) ) {
471 $this->mLength = (int)$row->page_len;
472 }
473 if ( isset( $row->page_is_redirect ) ) {
474 $this->mRedirect = (bool)$row->page_is_redirect;
475 }
476 if ( isset( $row->page_latest ) ) {
477 $this->mLatestID = (int)$row->page_latest;
478 }
479 if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
480 $this->mContentModel = strval( $row->page_content_model );
481 } elseif ( !$this->mForcedContentModel ) {
482 $this->mContentModel = false; # initialized lazily in getContentModel()
483 }
484 if ( isset( $row->page_lang ) ) {
485 $this->mDbPageLanguage = (string)$row->page_lang;
486 }
487 if ( isset( $row->page_restrictions ) ) {
488 $this->mOldRestrictions = $row->page_restrictions;
489 }
490 } else { // page not found
491 $this->mArticleID = 0;
492 $this->mLength = 0;
493 $this->mRedirect = false;
494 $this->mLatestID = 0;
495 if ( !$this->mForcedContentModel ) {
496 $this->mContentModel = false; # initialized lazily in getContentModel()
497 }
498 }
499 }
500
514 public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
515 $t = new Title();
516 $t->mInterwiki = $interwiki;
517 $t->mFragment = $fragment;
518 $t->mNamespace = $ns = intval( $ns );
519 $t->mDbkeyform = strtr( $title, ' ', '_' );
520 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
521 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
522 $t->mTextform = strtr( $title, '_', ' ' );
523 $t->mContentModel = false; # initialized lazily in getContentModel()
524 return $t;
525 }
526
538 public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
539 if ( !MWNamespace::exists( $ns ) ) {
540 return null;
541 }
542
543 $t = new Title();
544 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
545
546 try {
547 $t->secureAndSplit();
548 return $t;
549 } catch ( MalformedTitleException $ex ) {
550 return null;
551 }
552 }
553
559 public static function newMainPage() {
560 $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
561 // Don't give fatal errors if the message is broken
562 if ( !$title ) {
563 $title = Title::newFromText( 'Main Page' );
564 }
565 return $title;
566 }
567
574 public static function nameOf( $id ) {
576
577 $s = $dbr->selectRow(
578 'page',
579 [ 'page_namespace', 'page_title' ],
580 [ 'page_id' => $id ],
581 __METHOD__
582 );
583 if ( $s === false ) {
584 return null;
585 }
586
587 $n = self::makeName( $s->page_namespace, $s->page_title );
588 return $n;
589 }
590
596 public static function legalChars() {
598 return $wgLegalTitleChars;
599 }
600
610 static function getTitleInvalidRegex() {
611 wfDeprecated( __METHOD__, '1.25' );
613 }
614
624 public static function convertByteClassToUnicodeClass( $byteClass ) {
625 $length = strlen( $byteClass );
626 // Input token queue
627 $x0 = $x1 = $x2 = '';
628 // Decoded queue
629 $d0 = $d1 = $d2 = '';
630 // Decoded integer codepoints
631 $ord0 = $ord1 = $ord2 = 0;
632 // Re-encoded queue
633 $r0 = $r1 = $r2 = '';
634 // Output
635 $out = '';
636 // Flags
637 $allowUnicode = false;
638 for ( $pos = 0; $pos < $length; $pos++ ) {
639 // Shift the queues down
640 $x2 = $x1;
641 $x1 = $x0;
642 $d2 = $d1;
643 $d1 = $d0;
644 $ord2 = $ord1;
645 $ord1 = $ord0;
646 $r2 = $r1;
647 $r1 = $r0;
648 // Load the current input token and decoded values
649 $inChar = $byteClass[$pos];
650 if ( $inChar == '\\' ) {
651 if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
652 $x0 = $inChar . $m[0];
653 $d0 = chr( hexdec( $m[1] ) );
654 $pos += strlen( $m[0] );
655 } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
656 $x0 = $inChar . $m[0];
657 $d0 = chr( octdec( $m[0] ) );
658 $pos += strlen( $m[0] );
659 } elseif ( $pos + 1 >= $length ) {
660 $x0 = $d0 = '\\';
661 } else {
662 $d0 = $byteClass[$pos + 1];
663 $x0 = $inChar . $d0;
664 $pos += 1;
665 }
666 } else {
667 $x0 = $d0 = $inChar;
668 }
669 $ord0 = ord( $d0 );
670 // Load the current re-encoded value
671 if ( $ord0 < 32 || $ord0 == 0x7f ) {
672 $r0 = sprintf( '\x%02x', $ord0 );
673 } elseif ( $ord0 >= 0x80 ) {
674 // Allow unicode if a single high-bit character appears
675 $r0 = sprintf( '\x%02x', $ord0 );
676 $allowUnicode = true;
677 } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
678 $r0 = '\\' . $d0;
679 } else {
680 $r0 = $d0;
681 }
682 // Do the output
683 if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
684 // Range
685 if ( $ord2 > $ord0 ) {
686 // Empty range
687 } elseif ( $ord0 >= 0x80 ) {
688 // Unicode range
689 $allowUnicode = true;
690 if ( $ord2 < 0x80 ) {
691 // Keep the non-unicode section of the range
692 $out .= "$r2-\\x7F";
693 }
694 } else {
695 // Normal range
696 $out .= "$r2-$r0";
697 }
698 // Reset state to the initial value
699 $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
700 } elseif ( $ord2 < 0x80 ) {
701 // ASCII character
702 $out .= $r2;
703 }
704 }
705 if ( $ord1 < 0x80 ) {
706 $out .= $r1;
707 }
708 if ( $ord0 < 0x80 ) {
709 $out .= $r0;
710 }
711 if ( $allowUnicode ) {
712 $out .= '\u0080-\uFFFF';
713 }
714 return $out;
715 }
716
728 public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
729 $canonicalNamespace = false
730 ) {
732
733 if ( $canonicalNamespace ) {
734 $namespace = MWNamespace::getCanonicalName( $ns );
735 } else {
736 $namespace = $wgContLang->getNsText( $ns );
737 }
738 $name = $namespace == '' ? $title : "$namespace:$title";
739 if ( strval( $interwiki ) != '' ) {
740 $name = "$interwiki:$name";
741 }
742 if ( strval( $fragment ) != '' ) {
743 $name .= '#' . $fragment;
744 }
745 return $name;
746 }
747
754 static function escapeFragmentForURL( $fragment ) {
755 # Note that we don't urlencode the fragment. urlencoded Unicode
756 # fragments appear not to work in IE (at least up to 7) or in at least
757 # one version of Opera 9.x. The W3C validator, for one, doesn't seem
758 # to care if they aren't encoded.
759 return Sanitizer::escapeId( $fragment, 'noninitial' );
760 }
761
770 public static function compare( LinkTarget $a, LinkTarget $b ) {
771 if ( $a->getNamespace() == $b->getNamespace() ) {
772 return strcmp( $a->getText(), $b->getText() );
773 } else {
774 return $a->getNamespace() - $b->getNamespace();
775 }
776 }
777
785 public function isLocal() {
786 if ( $this->isExternal() ) {
787 $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
788 if ( $iw ) {
789 return $iw->isLocal();
790 }
791 }
792 return true;
793 }
794
800 public function isExternal() {
801 return $this->mInterwiki !== '';
802 }
803
811 public function getInterwiki() {
812 return $this->mInterwiki;
813 }
814
820 public function wasLocalInterwiki() {
821 return $this->mLocalInterwiki;
822 }
823
830 public function isTrans() {
831 if ( !$this->isExternal() ) {
832 return false;
833 }
834
835 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
836 }
837
843 public function getTransWikiID() {
844 if ( !$this->isExternal() ) {
845 return false;
846 }
847
848 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
849 }
850
860 public function getTitleValue() {
861 if ( $this->mTitleValue === null ) {
862 try {
863 $this->mTitleValue = new TitleValue(
864 $this->getNamespace(),
865 $this->getDBkey(),
866 $this->getFragment(),
867 $this->getInterwiki()
868 );
869 } catch ( InvalidArgumentException $ex ) {
870 wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
871 $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
872 }
873 }
874
875 return $this->mTitleValue;
876 }
877
883 public function getText() {
884 return $this->mTextform;
885 }
886
892 public function getPartialURL() {
893 return $this->mUrlform;
894 }
895
901 public function getDBkey() {
902 return $this->mDbkeyform;
903 }
904
910 function getUserCaseDBKey() {
911 if ( !is_null( $this->mUserCaseDBKey ) ) {
912 return $this->mUserCaseDBKey;
913 } else {
914 // If created via makeTitle(), $this->mUserCaseDBKey is not set.
915 return $this->mDbkeyform;
916 }
917 }
918
924 public function getNamespace() {
925 return $this->mNamespace;
926 }
927
934 public function getContentModel( $flags = 0 ) {
935 if ( !$this->mForcedContentModel
936 && ( !$this->mContentModel || $flags === Title::GAID_FOR_UPDATE )
937 && $this->getArticleID( $flags )
938 ) {
939 $linkCache = LinkCache::singleton();
940 $linkCache->addLinkObj( $this ); # in case we already had an article ID
941 $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
942 }
943
944 if ( !$this->mContentModel ) {
945 $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
946 }
947
948 return $this->mContentModel;
949 }
950
957 public function hasContentModel( $id ) {
958 return $this->getContentModel() == $id;
959 }
960
972 public function setContentModel( $model ) {
973 $this->mContentModel = $model;
974 $this->mForcedContentModel = true;
975 }
976
982 public function getNsText() {
983 if ( $this->isExternal() ) {
984 // This probably shouldn't even happen,
985 // but for interwiki transclusion it sometimes does.
986 // Use the canonical namespaces if possible to try to
987 // resolve a foreign namespace.
988 if ( MWNamespace::exists( $this->mNamespace ) ) {
989 return MWNamespace::getCanonicalName( $this->mNamespace );
990 }
991 }
992
993 try {
994 $formatter = self::getTitleFormatter();
995 return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
996 } catch ( InvalidArgumentException $ex ) {
997 wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
998 return false;
999 }
1000 }
1001
1007 public function getSubjectNsText() {
1009 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1010 }
1011
1017 public function getTalkNsText() {
1019 return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1020 }
1021
1027 public function canTalk() {
1028 return MWNamespace::canTalk( $this->mNamespace );
1029 }
1030
1036 public function canExist() {
1037 return $this->mNamespace >= NS_MAIN;
1038 }
1039
1045 public function isWatchable() {
1046 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
1047 }
1048
1054 public function isSpecialPage() {
1055 return $this->getNamespace() == NS_SPECIAL;
1056 }
1057
1064 public function isSpecial( $name ) {
1065 if ( $this->isSpecialPage() ) {
1066 list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
1067 if ( $name == $thisName ) {
1068 return true;
1069 }
1070 }
1071 return false;
1072 }
1073
1080 public function fixSpecialName() {
1081 if ( $this->isSpecialPage() ) {
1082 list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
1083 if ( $canonicalName ) {
1084 $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
1085 if ( $localName != $this->mDbkeyform ) {
1086 return Title::makeTitle( NS_SPECIAL, $localName );
1087 }
1088 }
1089 }
1090 return $this;
1091 }
1092
1103 public function inNamespace( $ns ) {
1104 return MWNamespace::equals( $this->getNamespace(), $ns );
1105 }
1106
1114 public function inNamespaces( /* ... */ ) {
1115 $namespaces = func_get_args();
1116 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1118 }
1119
1120 foreach ( $namespaces as $ns ) {
1121 if ( $this->inNamespace( $ns ) ) {
1122 return true;
1123 }
1124 }
1125
1126 return false;
1127 }
1128
1142 public function hasSubjectNamespace( $ns ) {
1143 return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
1144 }
1145
1153 public function isContentPage() {
1154 return MWNamespace::isContent( $this->getNamespace() );
1155 }
1156
1163 public function isMovable() {
1164 if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
1165 // Interwiki title or immovable namespace. Hooks don't get to override here
1166 return false;
1167 }
1168
1169 $result = true;
1170 Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1171 return $result;
1172 }
1173
1184 public function isMainPage() {
1185 return $this->equals( Title::newMainPage() );
1186 }
1187
1193 public function isSubpage() {
1194 return MWNamespace::hasSubpages( $this->mNamespace )
1195 ? strpos( $this->getText(), '/' ) !== false
1196 : false;
1197 }
1198
1204 public function isConversionTable() {
1205 // @todo ConversionTable should become a separate content model.
1206
1207 return $this->getNamespace() == NS_MEDIAWIKI &&
1208 strpos( $this->getText(), 'Conversiontable/' ) === 0;
1209 }
1210
1216 public function isWikitextPage() {
1217 return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1218 }
1219
1234 public function isCssOrJsPage() {
1235 $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
1236 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1238
1239 return $isCssOrJsPage;
1240 }
1241
1247 public function isCssJsSubpage() {
1248 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1249 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1251 }
1252
1258 public function getSkinFromCssJsSubpage() {
1259 $subpage = explode( '/', $this->mTextform );
1260 $subpage = $subpage[count( $subpage ) - 1];
1261 $lastdot = strrpos( $subpage, '.' );
1262 if ( $lastdot === false ) {
1263 return $subpage; # Never happens: only called for names ending in '.css' or '.js'
1264 }
1265 return substr( $subpage, 0, $lastdot );
1266 }
1267
1273 public function isCssSubpage() {
1274 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1275 && $this->hasContentModel( CONTENT_MODEL_CSS ) );
1276 }
1277
1283 public function isJsSubpage() {
1284 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1286 }
1287
1293 public function isTalkPage() {
1294 return MWNamespace::isTalk( $this->getNamespace() );
1295 }
1296
1302 public function getTalkPage() {
1303 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
1304 }
1305
1312 public function getSubjectPage() {
1313 // Is this the same title?
1314 $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
1315 if ( $this->getNamespace() == $subjectNS ) {
1316 return $this;
1317 }
1318 return Title::makeTitle( $subjectNS, $this->getDBkey() );
1319 }
1320
1329 public function getOtherPage() {
1330 if ( $this->isSpecialPage() ) {
1331 throw new MWException( 'Special pages cannot have other pages' );
1332 }
1333 if ( $this->isTalkPage() ) {
1334 return $this->getSubjectPage();
1335 } else {
1336 return $this->getTalkPage();
1337 }
1338 }
1339
1345 public function getDefaultNamespace() {
1346 return $this->mDefaultNamespace;
1347 }
1348
1356 public function getFragment() {
1357 return $this->mFragment;
1358 }
1359
1366 public function hasFragment() {
1367 return $this->mFragment !== '';
1368 }
1369
1374 public function getFragmentForURL() {
1375 if ( !$this->hasFragment() ) {
1376 return '';
1377 } else {
1378 return '#' . Title::escapeFragmentForURL( $this->getFragment() );
1379 }
1380 }
1381
1394 public function setFragment( $fragment ) {
1395 $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1396 }
1397
1405 public function createFragmentTarget( $fragment ) {
1406 return self::makeTitle(
1407 $this->getNamespace(),
1408 $this->getText(),
1409 $fragment,
1410 $this->getInterwiki()
1411 );
1412 }
1413
1421 private function prefix( $name ) {
1422 $p = '';
1423 if ( $this->isExternal() ) {
1424 $p = $this->mInterwiki . ':';
1425 }
1426
1427 if ( 0 != $this->mNamespace ) {
1428 $p .= $this->getNsText() . ':';
1429 }
1430 return $p . $name;
1431 }
1432
1439 public function getPrefixedDBkey() {
1440 $s = $this->prefix( $this->mDbkeyform );
1441 $s = strtr( $s, ' ', '_' );
1442 return $s;
1443 }
1444
1451 public function getPrefixedText() {
1452 if ( $this->mPrefixedText === null ) {
1453 $s = $this->prefix( $this->mTextform );
1454 $s = strtr( $s, '_', ' ' );
1455 $this->mPrefixedText = $s;
1456 }
1457 return $this->mPrefixedText;
1458 }
1459
1465 public function __toString() {
1466 return $this->getPrefixedText();
1467 }
1468
1475 public function getFullText() {
1476 $text = $this->getPrefixedText();
1477 if ( $this->hasFragment() ) {
1478 $text .= '#' . $this->getFragment();
1479 }
1480 return $text;
1481 }
1482
1495 public function getRootText() {
1496 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1497 return $this->getText();
1498 }
1499
1500 return strtok( $this->getText(), '/' );
1501 }
1502
1515 public function getRootTitle() {
1516 return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
1517 }
1518
1530 public function getBaseText() {
1531 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1532 return $this->getText();
1533 }
1534
1535 $parts = explode( '/', $this->getText() );
1536 # Don't discard the real title if there's no subpage involved
1537 if ( count( $parts ) > 1 ) {
1538 unset( $parts[count( $parts ) - 1] );
1539 }
1540 return implode( '/', $parts );
1541 }
1542
1555 public function getBaseTitle() {
1556 return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
1557 }
1558
1570 public function getSubpageText() {
1571 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1572 return $this->mTextform;
1573 }
1574 $parts = explode( '/', $this->mTextform );
1575 return $parts[count( $parts ) - 1];
1576 }
1577
1591 public function getSubpage( $text ) {
1592 return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
1593 }
1594
1600 public function getSubpageUrlForm() {
1601 $text = $this->getSubpageText();
1602 $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1603 return $text;
1604 }
1605
1611 public function getPrefixedURL() {
1612 $s = $this->prefix( $this->mDbkeyform );
1613 $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1614 return $s;
1615 }
1616
1630 private static function fixUrlQueryArgs( $query, $query2 = false ) {
1631 if ( $query2 !== false ) {
1632 wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1633 "method called with a second parameter is deprecated. Add your " .
1634 "parameter to an array passed as the first parameter.", "1.19" );
1635 }
1636 if ( is_array( $query ) ) {
1638 }
1639 if ( $query2 ) {
1640 if ( is_string( $query2 ) ) {
1641 // $query2 is a string, we will consider this to be
1642 // a deprecated $variant argument and add it to the query
1643 $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1644 } else {
1645 $query2 = wfArrayToCgi( $query2 );
1646 }
1647 // If we have $query content add a & to it first
1648 if ( $query ) {
1649 $query .= '&';
1650 }
1651 // Now append the queries together
1652 $query .= $query2;
1653 }
1654 return $query;
1655 }
1656
1668 public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1669 $query = self::fixUrlQueryArgs( $query, $query2 );
1670
1671 # Hand off all the decisions on urls to getLocalURL
1672 $url = $this->getLocalURL( $query );
1673
1674 # Expand the url to make it a full url. Note that getLocalURL has the
1675 # potential to output full urls for a variety of reasons, so we use
1676 # wfExpandUrl instead of simply prepending $wgServer
1677 $url = wfExpandUrl( $url, $proto );
1678
1679 # Finally, add the fragment.
1680 $url .= $this->getFragmentForURL();
1681 // Avoid PHP 7.1 warning from passing $this by reference
1682 $titleRef = $this;
1683 Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
1684 return $url;
1685 }
1686
1703 public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1704 $target = $this;
1705 if ( $this->isExternal() ) {
1706 $target = SpecialPage::getTitleFor(
1707 'GoToInterwiki',
1708 $this->getPrefixedDBKey()
1709 );
1710 }
1711 return $target->getFullUrl( $query, false, $proto );
1712 }
1713
1737 public function getLocalURL( $query = '', $query2 = false ) {
1739
1740 $query = self::fixUrlQueryArgs( $query, $query2 );
1741
1742 $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1743 if ( $interwiki ) {
1744 $namespace = $this->getNsText();
1745 if ( $namespace != '' ) {
1746 # Can this actually happen? Interwikis shouldn't be parsed.
1747 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1748 $namespace .= ':';
1749 }
1750 $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1751 $url = wfAppendQuery( $url, $query );
1752 } else {
1753 $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1754 if ( $query == '' ) {
1755 $url = str_replace( '$1', $dbkey, $wgArticlePath );
1756 // Avoid PHP 7.1 warning from passing $this by reference
1757 $titleRef = $this;
1758 Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
1759 } else {
1761 $url = false;
1762 $matches = [];
1763
1764 if ( !empty( $wgActionPaths )
1765 && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1766 ) {
1767 $action = urldecode( $matches[2] );
1768 if ( isset( $wgActionPaths[$action] ) ) {
1769 $query = $matches[1];
1770 if ( isset( $matches[4] ) ) {
1771 $query .= $matches[4];
1772 }
1773 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1774 if ( $query != '' ) {
1775 $url = wfAppendQuery( $url, $query );
1776 }
1777 }
1778 }
1779
1780 if ( $url === false
1782 && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1783 && $this->getPageLanguage()->equals( $wgContLang )
1784 && $this->getPageLanguage()->hasVariants()
1785 ) {
1786 $variant = urldecode( $matches[1] );
1787 if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1788 // Only do the variant replacement if the given variant is a valid
1789 // variant for the page's language.
1790 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1791 $url = str_replace( '$1', $dbkey, $url );
1792 }
1793 }
1794
1795 if ( $url === false ) {
1796 if ( $query == '-' ) {
1797 $query = '';
1798 }
1799 $url = "{$wgScript}?title={$dbkey}&{$query}";
1800 }
1801 }
1802 // Avoid PHP 7.1 warning from passing $this by reference
1803 $titleRef = $this;
1804 Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
1805
1806 // @todo FIXME: This causes breakage in various places when we
1807 // actually expected a local URL and end up with dupe prefixes.
1808 if ( $wgRequest->getVal( 'action' ) == 'render' ) {
1809 $url = $wgServer . $url;
1810 }
1811 }
1812 // Avoid PHP 7.1 warning from passing $this by reference
1813 $titleRef = $this;
1814 Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
1815 return $url;
1816 }
1817
1835 public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
1836 if ( $this->isExternal() || $proto !== false ) {
1837 $ret = $this->getFullURL( $query, $query2, $proto );
1838 } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
1839 $ret = $this->getFragmentForURL();
1840 } else {
1841 $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
1842 }
1843 return $ret;
1844 }
1845
1858 public function getInternalURL( $query = '', $query2 = false ) {
1860 $query = self::fixUrlQueryArgs( $query, $query2 );
1861 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
1862 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
1863 // Avoid PHP 7.1 warning from passing $this by reference
1864 $titleRef = $this;
1865 Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
1866 return $url;
1867 }
1868
1880 public function getCanonicalURL( $query = '', $query2 = false ) {
1881 $query = self::fixUrlQueryArgs( $query, $query2 );
1882 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
1883 // Avoid PHP 7.1 warning from passing $this by reference
1884 $titleRef = $this;
1885 Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
1886 return $url;
1887 }
1888
1894 public function getEditURL() {
1895 if ( $this->isExternal() ) {
1896 return '';
1897 }
1898 $s = $this->getLocalURL( 'action=edit' );
1899
1900 return $s;
1901 }
1902
1917 public function quickUserCan( $action, $user = null ) {
1918 return $this->userCan( $action, $user, false );
1919 }
1920
1930 public function userCan( $action, $user = null, $rigor = 'secure' ) {
1931 if ( !$user instanceof User ) {
1933 $user = $wgUser;
1934 }
1935
1936 return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
1937 }
1938
1955 $action, $user, $rigor = 'secure', $ignoreErrors = []
1956 ) {
1957 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
1958
1959 // Remove the errors being ignored.
1960 foreach ( $errors as $index => $error ) {
1961 $errKey = is_array( $error ) ? $error[0] : $error;
1962
1963 if ( in_array( $errKey, $ignoreErrors ) ) {
1964 unset( $errors[$index] );
1965 }
1966 if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
1967 unset( $errors[$index] );
1968 }
1969 }
1970
1971 return $errors;
1972 }
1973
1985 private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
1986 if ( !Hooks::run( 'TitleQuickPermissions',
1987 [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
1988 ) {
1989 return $errors;
1990 }
1991
1992 if ( $action == 'create' ) {
1993 if (
1994 ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1995 ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
1996 ) {
1997 $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
1998 }
1999 } elseif ( $action == 'move' ) {
2000 if ( !$user->isAllowed( 'move-rootuserpages' )
2001 && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2002 // Show user page-specific message only if the user can move other pages
2003 $errors[] = [ 'cant-move-user-page' ];
2004 }
2005
2006 // Check if user is allowed to move files if it's a file
2007 if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
2008 $errors[] = [ 'movenotallowedfile' ];
2009 }
2010
2011 // Check if user is allowed to move category pages if it's a category page
2012 if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
2013 $errors[] = [ 'cant-move-category-page' ];
2014 }
2015
2016 if ( !$user->isAllowed( 'move' ) ) {
2017 // User can't move anything
2018 $userCanMove = User::groupHasPermission( 'user', 'move' );
2019 $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
2020 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
2021 // custom message if logged-in users without any special rights can move
2022 $errors[] = [ 'movenologintext' ];
2023 } else {
2024 $errors[] = [ 'movenotallowed' ];
2025 }
2026 }
2027 } elseif ( $action == 'move-target' ) {
2028 if ( !$user->isAllowed( 'move' ) ) {
2029 // User can't move anything
2030 $errors[] = [ 'movenotallowed' ];
2031 } elseif ( !$user->isAllowed( 'move-rootuserpages' )
2032 && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2033 // Show user page-specific message only if the user can move other pages
2034 $errors[] = [ 'cant-move-to-user-page' ];
2035 } elseif ( !$user->isAllowed( 'move-categorypages' )
2036 && $this->mNamespace == NS_CATEGORY ) {
2037 // Show category page-specific message only if the user can move other pages
2038 $errors[] = [ 'cant-move-to-category-page' ];
2039 }
2040 } elseif ( !$user->isAllowed( $action ) ) {
2041 $errors[] = $this->missingPermissionError( $action, $short );
2042 }
2043
2044 return $errors;
2045 }
2046
2055 private function resultToError( $errors, $result ) {
2056 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2057 // A single array representing an error
2058 $errors[] = $result;
2059 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2060 // A nested array representing multiple errors
2061 $errors = array_merge( $errors, $result );
2062 } elseif ( $result !== '' && is_string( $result ) ) {
2063 // A string representing a message-id
2064 $errors[] = [ $result ];
2065 } elseif ( $result instanceof MessageSpecifier ) {
2066 // A message specifier representing an error
2067 $errors[] = [ $result ];
2068 } elseif ( $result === false ) {
2069 // a generic "We don't want them to do that"
2070 $errors[] = [ 'badaccess-group0' ];
2071 }
2072 return $errors;
2073 }
2074
2086 private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2087 // Use getUserPermissionsErrors instead
2088 $result = '';
2089 // Avoid PHP 7.1 warning from passing $this by reference
2090 $titleRef = $this;
2091 if ( !Hooks::run( 'userCan', [ &$titleRef, &$user, $action, &$result ] ) ) {
2092 return $result ? [] : [ [ 'badaccess-group0' ] ];
2093 }
2094 // Check getUserPermissionsErrors hook
2095 // Avoid PHP 7.1 warning from passing $this by reference
2096 $titleRef = $this;
2097 if ( !Hooks::run( 'getUserPermissionsErrors', [ &$titleRef, &$user, $action, &$result ] ) ) {
2098 $errors = $this->resultToError( $errors, $result );
2099 }
2100 // Check getUserPermissionsErrorsExpensive hook
2101 if (
2102 $rigor !== 'quick'
2103 && !( $short && count( $errors ) > 0 )
2104 && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$titleRef, &$user, $action, &$result ] )
2105 ) {
2106 $errors = $this->resultToError( $errors, $result );
2107 }
2108
2109 return $errors;
2110 }
2111
2123 private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2124 # Only 'createaccount' can be performed on special pages,
2125 # which don't actually exist in the DB.
2126 if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
2127 $errors[] = [ 'ns-specialprotected' ];
2128 }
2129
2130 # Check $wgNamespaceProtection for restricted namespaces
2131 if ( $this->isNamespaceProtected( $user ) ) {
2132 $ns = $this->mNamespace == NS_MAIN ?
2133 wfMessage( 'nstab-main' )->text() : $this->getNsText();
2134 $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2135 [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2136 }
2137
2138 return $errors;
2139 }
2140
2152 private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
2153 # Protect css/js subpages of user pages
2154 # XXX: this might be better using restrictions
2155 if ( $action != 'patrol' ) {
2156 if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2157 if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
2158 $errors[] = [ 'mycustomcssprotected', $action ];
2159 } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
2160 $errors[] = [ 'mycustomjsprotected', $action ];
2161 }
2162 } else {
2163 if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
2164 $errors[] = [ 'customcssprotected', $action ];
2165 } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
2166 $errors[] = [ 'customjsprotected', $action ];
2167 }
2168 }
2169 }
2170
2171 return $errors;
2172 }
2173
2187 private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2188 foreach ( $this->getRestrictions( $action ) as $right ) {
2189 // Backwards compatibility, rewrite sysop -> editprotected
2190 if ( $right == 'sysop' ) {
2191 $right = 'editprotected';
2192 }
2193 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2194 if ( $right == 'autoconfirmed' ) {
2195 $right = 'editsemiprotected';
2196 }
2197 if ( $right == '' ) {
2198 continue;
2199 }
2200 if ( !$user->isAllowed( $right ) ) {
2201 $errors[] = [ 'protectedpagetext', $right, $action ];
2202 } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2203 $errors[] = [ 'protectedpagetext', 'protect', $action ];
2204 }
2205 }
2206
2207 return $errors;
2208 }
2209
2221 private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2222 if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
2223 # We /could/ use the protection level on the source page, but it's
2224 # fairly ugly as we have to establish a precedence hierarchy for pages
2225 # included by multiple cascade-protected pages. So just restrict
2226 # it to people with 'protect' permission, as they could remove the
2227 # protection anyway.
2228 list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2229 # Cascading protection depends on more than this page...
2230 # Several cascading protected pages may include this page...
2231 # Check each cascading level
2232 # This is only for protection restrictions, not for all actions
2233 if ( isset( $restrictions[$action] ) ) {
2234 foreach ( $restrictions[$action] as $right ) {
2235 // Backwards compatibility, rewrite sysop -> editprotected
2236 if ( $right == 'sysop' ) {
2237 $right = 'editprotected';
2238 }
2239 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2240 if ( $right == 'autoconfirmed' ) {
2241 $right = 'editsemiprotected';
2242 }
2243 if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2244 $pages = '';
2245 foreach ( $cascadingSources as $page ) {
2246 $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2247 }
2248 $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2249 }
2250 }
2251 }
2252 }
2253
2254 return $errors;
2255 }
2256
2268 private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2270
2271 if ( $action == 'protect' ) {
2272 if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2273 // If they can't edit, they shouldn't protect.
2274 $errors[] = [ 'protect-cantedit' ];
2275 }
2276 } elseif ( $action == 'create' ) {
2277 $title_protection = $this->getTitleProtection();
2278 if ( $title_protection ) {
2279 if ( $title_protection['permission'] == ''
2280 || !$user->isAllowed( $title_protection['permission'] )
2281 ) {
2282 $errors[] = [
2283 'titleprotected',
2284 User::whoIs( $title_protection['user'] ),
2285 $title_protection['reason']
2286 ];
2287 }
2288 }
2289 } elseif ( $action == 'move' ) {
2290 // Check for immobile pages
2291 if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2292 // Specific message for this case
2293 $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2294 } elseif ( !$this->isMovable() ) {
2295 // Less specific message for rarer cases
2296 $errors[] = [ 'immobile-source-page' ];
2297 }
2298 } elseif ( $action == 'move-target' ) {
2299 if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2300 $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2301 } elseif ( !$this->isMovable() ) {
2302 $errors[] = [ 'immobile-target-page' ];
2303 }
2304 } elseif ( $action == 'delete' ) {
2305 $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2306 if ( !$tempErrors ) {
2307 $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2308 $user, $tempErrors, $rigor, true );
2309 }
2310 if ( $tempErrors ) {
2311 // If protection keeps them from editing, they shouldn't be able to delete.
2312 $errors[] = [ 'deleteprotected' ];
2313 }
2314 if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2315 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2316 ) {
2317 $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2318 }
2319 } elseif ( $action === 'undelete' ) {
2320 if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2321 // Undeleting implies editing
2322 $errors[] = [ 'undelete-cantedit' ];
2323 }
2324 if ( !$this->exists()
2325 && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
2326 ) {
2327 // Undeleting where nothing currently exists implies creating
2328 $errors[] = [ 'undelete-cantcreate' ];
2329 }
2330 }
2331 return $errors;
2332 }
2333
2345 private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2347 // Account creation blocks handled at userlogin.
2348 // Unblocking handled in SpecialUnblock
2349 if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2350 return $errors;
2351 }
2352
2353 // Optimize for a very common case
2354 if ( $action === 'read' && !$wgBlockDisablesLogin ) {
2355 return $errors;
2356 }
2357
2359 && !$user->isEmailConfirmed()
2360 && $action === 'edit'
2361 ) {
2362 $errors[] = [ 'confirmedittext' ];
2363 }
2364
2365 $useSlave = ( $rigor !== 'secure' );
2366 if ( ( $action == 'edit' || $action == 'create' )
2367 && !$user->isBlockedFrom( $this, $useSlave )
2368 ) {
2369 // Don't block the user from editing their own talk page unless they've been
2370 // explicitly blocked from that too.
2371 } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2372 // @todo FIXME: Pass the relevant context into this function.
2373 $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2374 }
2375
2376 return $errors;
2377 }
2378
2390 private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2392
2393 $whitelisted = false;
2394 if ( User::isEveryoneAllowed( 'read' ) ) {
2395 # Shortcut for public wikis, allows skipping quite a bit of code
2396 $whitelisted = true;
2397 } elseif ( $user->isAllowed( 'read' ) ) {
2398 # If the user is allowed to read pages, he is allowed to read all pages
2399 $whitelisted = true;
2400 } elseif ( $this->isSpecial( 'Userlogin' )
2401 || $this->isSpecial( 'PasswordReset' )
2402 || $this->isSpecial( 'Userlogout' )
2403 ) {
2404 # Always grant access to the login page.
2405 # Even anons need to be able to log in.
2406 $whitelisted = true;
2407 } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2408 # Time to check the whitelist
2409 # Only do these checks is there's something to check against
2410 $name = $this->getPrefixedText();
2411 $dbName = $this->getPrefixedDBkey();
2412
2413 // Check for explicit whitelisting with and without underscores
2414 if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2415 $whitelisted = true;
2416 } elseif ( $this->getNamespace() == NS_MAIN ) {
2417 # Old settings might have the title prefixed with
2418 # a colon for main-namespace pages
2419 if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2420 $whitelisted = true;
2421 }
2422 } elseif ( $this->isSpecialPage() ) {
2423 # If it's a special page, ditch the subpage bit and check again
2424 $name = $this->getDBkey();
2425 list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2426 if ( $name ) {
2427 $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2428 if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2429 $whitelisted = true;
2430 }
2431 }
2432 }
2433 }
2434
2435 if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2436 $name = $this->getPrefixedText();
2437 // Check for regex whitelisting
2438 foreach ( $wgWhitelistReadRegexp as $listItem ) {
2439 if ( preg_match( $listItem, $name ) ) {
2440 $whitelisted = true;
2441 break;
2442 }
2443 }
2444 }
2445
2446 if ( !$whitelisted ) {
2447 # If the title is not whitelisted, give extensions a chance to do so...
2448 Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2449 if ( !$whitelisted ) {
2450 $errors[] = $this->missingPermissionError( $action, $short );
2451 }
2452 }
2453
2454 return $errors;
2455 }
2456
2465 private function missingPermissionError( $action, $short ) {
2466 // We avoid expensive display logic for quickUserCan's and such
2467 if ( $short ) {
2468 return [ 'badaccess-group0' ];
2469 }
2470
2471 return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
2472 }
2473
2489 $action, $user, $rigor = 'secure', $short = false
2490 ) {
2491 if ( $rigor === true ) {
2492 $rigor = 'secure'; // b/c
2493 } elseif ( $rigor === false ) {
2494 $rigor = 'quick'; // b/c
2495 } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2496 throw new Exception( "Invalid rigor parameter '$rigor'." );
2497 }
2498
2499 # Read has special handling
2500 if ( $action == 'read' ) {
2501 $checks = [
2502 'checkPermissionHooks',
2503 'checkReadPermissions',
2504 'checkUserBlock', // for wgBlockDisablesLogin
2505 ];
2506 # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
2507 # here as it will lead to duplicate error messages. This is okay to do
2508 # since anywhere that checks for create will also check for edit, and
2509 # those checks are called for edit.
2510 } elseif ( $action == 'create' ) {
2511 $checks = [
2512 'checkQuickPermissions',
2513 'checkPermissionHooks',
2514 'checkPageRestrictions',
2515 'checkCascadingSourcesRestrictions',
2516 'checkActionPermissions',
2517 'checkUserBlock'
2518 ];
2519 } else {
2520 $checks = [
2521 'checkQuickPermissions',
2522 'checkPermissionHooks',
2523 'checkSpecialsAndNSPermissions',
2524 'checkCSSandJSPermissions',
2525 'checkPageRestrictions',
2526 'checkCascadingSourcesRestrictions',
2527 'checkActionPermissions',
2528 'checkUserBlock'
2529 ];
2530 }
2531
2532 $errors = [];
2533 while ( count( $checks ) > 0 &&
2534 !( $short && count( $errors ) > 0 ) ) {
2535 $method = array_shift( $checks );
2536 $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2537 }
2538
2539 return $errors;
2540 }
2541
2549 public static function getFilteredRestrictionTypes( $exists = true ) {
2551 $types = $wgRestrictionTypes;
2552 if ( $exists ) {
2553 # Remove the create restriction for existing titles
2554 $types = array_diff( $types, [ 'create' ] );
2555 } else {
2556 # Only the create and upload restrictions apply to non-existing titles
2557 $types = array_intersect( $types, [ 'create', 'upload' ] );
2558 }
2559 return $types;
2560 }
2561
2567 public function getRestrictionTypes() {
2568 if ( $this->isSpecialPage() ) {
2569 return [];
2570 }
2571
2572 $types = self::getFilteredRestrictionTypes( $this->exists() );
2573
2574 if ( $this->getNamespace() != NS_FILE ) {
2575 # Remove the upload restriction for non-file titles
2576 $types = array_diff( $types, [ 'upload' ] );
2577 }
2578
2579 Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2580
2581 wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2582 $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2583
2584 return $types;
2585 }
2586
2594 public function getTitleProtection() {
2595 $protection = $this->getTitleProtectionInternal();
2596 if ( $protection ) {
2597 if ( $protection['permission'] == 'sysop' ) {
2598 $protection['permission'] = 'editprotected'; // B/C
2599 }
2600 if ( $protection['permission'] == 'autoconfirmed' ) {
2601 $protection['permission'] = 'editsemiprotected'; // B/C
2602 }
2603 }
2604 return $protection;
2605 }
2606
2617 protected function getTitleProtectionInternal() {
2618 // Can't protect pages in special namespaces
2619 if ( $this->getNamespace() < 0 ) {
2620 return false;
2621 }
2622
2623 // Can't protect pages that exist.
2624 if ( $this->exists() ) {
2625 return false;
2626 }
2627
2628 if ( $this->mTitleProtection === null ) {
2629 $dbr = wfGetDB( DB_REPLICA );
2630 $res = $dbr->select(
2631 'protected_titles',
2632 [
2633 'user' => 'pt_user',
2634 'reason' => 'pt_reason',
2635 'expiry' => 'pt_expiry',
2636 'permission' => 'pt_create_perm'
2637 ],
2638 [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2639 __METHOD__
2640 );
2641
2642 // fetchRow returns false if there are no rows.
2643 $row = $dbr->fetchRow( $res );
2644 if ( $row ) {
2645 $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2646 }
2647 $this->mTitleProtection = $row;
2648 }
2649 return $this->mTitleProtection;
2650 }
2651
2655 public function deleteTitleProtection() {
2656 $dbw = wfGetDB( DB_MASTER );
2657
2658 $dbw->delete(
2659 'protected_titles',
2660 [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2661 __METHOD__
2662 );
2663 $this->mTitleProtection = false;
2664 }
2665
2673 public function isSemiProtected( $action = 'edit' ) {
2675
2676 $restrictions = $this->getRestrictions( $action );
2678 if ( !$restrictions || !$semi ) {
2679 // Not protected, or all protection is full protection
2680 return false;
2681 }
2682
2683 // Remap autoconfirmed to editsemiprotected for BC
2684 foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2685 $semi[$key] = 'editsemiprotected';
2686 }
2687 foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2688 $restrictions[$key] = 'editsemiprotected';
2689 }
2690
2691 return !array_diff( $restrictions, $semi );
2692 }
2693
2701 public function isProtected( $action = '' ) {
2703
2704 $restrictionTypes = $this->getRestrictionTypes();
2705
2706 # Special pages have inherent protection
2707 if ( $this->isSpecialPage() ) {
2708 return true;
2709 }
2710
2711 # Check regular protection levels
2712 foreach ( $restrictionTypes as $type ) {
2713 if ( $action == $type || $action == '' ) {
2714 $r = $this->getRestrictions( $type );
2715 foreach ( $wgRestrictionLevels as $level ) {
2716 if ( in_array( $level, $r ) && $level != '' ) {
2717 return true;
2718 }
2719 }
2720 }
2721 }
2722
2723 return false;
2724 }
2725
2733 public function isNamespaceProtected( User $user ) {
2735
2736 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2737 foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2738 if ( $right != '' && !$user->isAllowed( $right ) ) {
2739 return true;
2740 }
2741 }
2742 }
2743 return false;
2744 }
2745
2751 public function isCascadeProtected() {
2752 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2753 return ( $sources > 0 );
2754 }
2755
2765 public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2766 return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2767 }
2768
2782 public function getCascadeProtectionSources( $getPages = true ) {
2783 $pagerestrictions = [];
2784
2785 if ( $this->mCascadeSources !== null && $getPages ) {
2786 return [ $this->mCascadeSources, $this->mCascadingRestrictions ];
2787 } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2788 return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2789 }
2790
2791 $dbr = wfGetDB( DB_REPLICA );
2792
2793 if ( $this->getNamespace() == NS_FILE ) {
2794 $tables = [ 'imagelinks', 'page_restrictions' ];
2795 $where_clauses = [
2796 'il_to' => $this->getDBkey(),
2797 'il_from=pr_page',
2798 'pr_cascade' => 1
2799 ];
2800 } else {
2801 $tables = [ 'templatelinks', 'page_restrictions' ];
2802 $where_clauses = [
2803 'tl_namespace' => $this->getNamespace(),
2804 'tl_title' => $this->getDBkey(),
2805 'tl_from=pr_page',
2806 'pr_cascade' => 1
2807 ];
2808 }
2809
2810 if ( $getPages ) {
2811 $cols = [ 'pr_page', 'page_namespace', 'page_title',
2812 'pr_expiry', 'pr_type', 'pr_level' ];
2813 $where_clauses[] = 'page_id=pr_page';
2814 $tables[] = 'page';
2815 } else {
2816 $cols = [ 'pr_expiry' ];
2817 }
2818
2819 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2820
2821 $sources = $getPages ? [] : false;
2822 $now = wfTimestampNow();
2823
2824 foreach ( $res as $row ) {
2825 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2826 if ( $expiry > $now ) {
2827 if ( $getPages ) {
2828 $page_id = $row->pr_page;
2829 $page_ns = $row->page_namespace;
2830 $page_title = $row->page_title;
2831 $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2832 # Add groups needed for each restriction type if its not already there
2833 # Make sure this restriction type still exists
2834
2835 if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2836 $pagerestrictions[$row->pr_type] = [];
2837 }
2838
2839 if (
2840 isset( $pagerestrictions[$row->pr_type] )
2841 && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2842 ) {
2843 $pagerestrictions[$row->pr_type][] = $row->pr_level;
2844 }
2845 } else {
2846 $sources = true;
2847 }
2848 }
2849 }
2850
2851 if ( $getPages ) {
2852 $this->mCascadeSources = $sources;
2853 $this->mCascadingRestrictions = $pagerestrictions;
2854 } else {
2855 $this->mHasCascadingRestrictions = $sources;
2856 }
2857
2858 return [ $sources, $pagerestrictions ];
2859 }
2860
2868 public function areRestrictionsLoaded() {
2869 return $this->mRestrictionsLoaded;
2870 }
2871
2881 public function getRestrictions( $action ) {
2882 if ( !$this->mRestrictionsLoaded ) {
2883 $this->loadRestrictions();
2884 }
2885 return isset( $this->mRestrictions[$action] )
2886 ? $this->mRestrictions[$action]
2887 : [];
2888 }
2889
2897 public function getAllRestrictions() {
2898 if ( !$this->mRestrictionsLoaded ) {
2899 $this->loadRestrictions();
2900 }
2901 return $this->mRestrictions;
2902 }
2903
2911 public function getRestrictionExpiry( $action ) {
2912 if ( !$this->mRestrictionsLoaded ) {
2913 $this->loadRestrictions();
2914 }
2915 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2916 }
2917
2924 if ( !$this->mRestrictionsLoaded ) {
2925 $this->loadRestrictions();
2926 }
2927
2928 return $this->mCascadeRestriction;
2929 }
2930
2940 public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2941 $dbr = wfGetDB( DB_REPLICA );
2942
2943 $restrictionTypes = $this->getRestrictionTypes();
2944
2945 foreach ( $restrictionTypes as $type ) {
2946 $this->mRestrictions[$type] = [];
2947 $this->mRestrictionsExpiry[$type] = 'infinity';
2948 }
2949
2950 $this->mCascadeRestriction = false;
2951
2952 # Backwards-compatibility: also load the restrictions from the page record (old format).
2953 if ( $oldFashionedRestrictions !== null ) {
2954 $this->mOldRestrictions = $oldFashionedRestrictions;
2955 }
2956
2957 if ( $this->mOldRestrictions === false ) {
2958 $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2959 [ 'page_id' => $this->getArticleID() ], __METHOD__ );
2960 }
2961
2962 if ( $this->mOldRestrictions != '' ) {
2963 foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2964 $temp = explode( '=', trim( $restrict ) );
2965 if ( count( $temp ) == 1 ) {
2966 // old old format should be treated as edit/move restriction
2967 $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2968 $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2969 } else {
2970 $restriction = trim( $temp[1] );
2971 if ( $restriction != '' ) { // some old entries are empty
2972 $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2973 }
2974 }
2975 }
2976 }
2977
2978 if ( count( $rows ) ) {
2979 # Current system - load second to make them override.
2980 $now = wfTimestampNow();
2981
2982 # Cycle through all the restrictions.
2983 foreach ( $rows as $row ) {
2984 // Don't take care of restrictions types that aren't allowed
2985 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2986 continue;
2987 }
2988
2989 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2990
2991 // Only apply the restrictions if they haven't expired!
2992 if ( !$expiry || $expiry > $now ) {
2993 $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2994 $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2995
2996 $this->mCascadeRestriction |= $row->pr_cascade;
2997 }
2998 }
2999 }
3000
3001 $this->mRestrictionsLoaded = true;
3002 }
3003
3010 public function loadRestrictions( $oldFashionedRestrictions = null ) {
3011 if ( $this->mRestrictionsLoaded ) {
3012 return;
3013 }
3014
3015 $id = $this->getArticleID();
3016 if ( $id ) {
3017 $cache = ObjectCache::getMainWANInstance();
3018 $rows = $cache->getWithSetCallback(
3019 // Page protections always leave a new null revision
3020 $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ),
3021 $cache::TTL_DAY,
3022 function ( $curValue, &$ttl, array &$setOpts ) {
3023 $dbr = wfGetDB( DB_REPLICA );
3024
3025 $setOpts += Database::getCacheSetOptions( $dbr );
3026
3027 return iterator_to_array(
3028 $dbr->select(
3029 'page_restrictions',
3030 [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
3031 [ 'pr_page' => $this->getArticleID() ],
3032 __METHOD__
3033 )
3034 );
3035 }
3036 );
3037
3038 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
3039 } else {
3040 $title_protection = $this->getTitleProtectionInternal();
3041
3042 if ( $title_protection ) {
3043 $now = wfTimestampNow();
3044 $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
3045
3046 if ( !$expiry || $expiry > $now ) {
3047 // Apply the restrictions
3048 $this->mRestrictionsExpiry['create'] = $expiry;
3049 $this->mRestrictions['create'] =
3050 explode( ',', trim( $title_protection['permission'] ) );
3051 } else { // Get rid of the old restrictions
3052 $this->mTitleProtection = false;
3053 }
3054 } else {
3055 $this->mRestrictionsExpiry['create'] = 'infinity';
3056 }
3057 $this->mRestrictionsLoaded = true;
3058 }
3059 }
3060
3065 public function flushRestrictions() {
3066 $this->mRestrictionsLoaded = false;
3067 $this->mTitleProtection = null;
3068 }
3069
3075 static function purgeExpiredRestrictions() {
3076 if ( wfReadOnly() ) {
3077 return;
3078 }
3079
3080 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
3081 wfGetDB( DB_MASTER ),
3082 __METHOD__,
3083 function ( IDatabase $dbw, $fname ) {
3084 $config = MediaWikiServices::getInstance()->getMainConfig();
3085 $ids = $dbw->selectFieldValues(
3086 'page_restrictions',
3087 'pr_id',
3088 [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3089 $fname,
3090 [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
3091 );
3092 if ( $ids ) {
3093 $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
3094 }
3095 }
3096 ) );
3097
3098 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
3099 wfGetDB( DB_MASTER ),
3100 __METHOD__,
3101 function ( IDatabase $dbw, $fname ) {
3102 $dbw->delete(
3103 'protected_titles',
3104 [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3105 $fname
3106 );
3107 }
3108 ) );
3109 }
3110
3116 public function hasSubpages() {
3117 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3118 # Duh
3119 return false;
3120 }
3121
3122 # We dynamically add a member variable for the purpose of this method
3123 # alone to cache the result. There's no point in having it hanging
3124 # around uninitialized in every Title object; therefore we only add it
3125 # if needed and don't declare it statically.
3126 if ( $this->mHasSubpages === null ) {
3127 $this->mHasSubpages = false;
3128 $subpages = $this->getSubpages( 1 );
3129 if ( $subpages instanceof TitleArray ) {
3130 $this->mHasSubpages = (bool)$subpages->count();
3131 }
3132 }
3133
3134 return $this->mHasSubpages;
3135 }
3136
3144 public function getSubpages( $limit = -1 ) {
3145 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3146 return [];
3147 }
3148
3149 $dbr = wfGetDB( DB_REPLICA );
3150 $conds['page_namespace'] = $this->getNamespace();
3151 $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3152 $options = [];
3153 if ( $limit > -1 ) {
3154 $options['LIMIT'] = $limit;
3155 }
3156 $this->mSubpages = TitleArray::newFromResult(
3157 $dbr->select( 'page',
3158 [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3159 $conds,
3160 __METHOD__,
3161 $options
3162 )
3163 );
3164 return $this->mSubpages;
3165 }
3166
3172 public function isDeleted() {
3173 if ( $this->getNamespace() < 0 ) {
3174 $n = 0;
3175 } else {
3176 $dbr = wfGetDB( DB_REPLICA );
3177
3178 $n = $dbr->selectField( 'archive', 'COUNT(*)',
3179 [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3180 __METHOD__
3181 );
3182 if ( $this->getNamespace() == NS_FILE ) {
3183 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3184 [ 'fa_name' => $this->getDBkey() ],
3185 __METHOD__
3186 );
3187 }
3188 }
3189 return (int)$n;
3190 }
3191
3197 public function isDeletedQuick() {
3198 if ( $this->getNamespace() < 0 ) {
3199 return false;
3200 }
3201 $dbr = wfGetDB( DB_REPLICA );
3202 $deleted = (bool)$dbr->selectField( 'archive', '1',
3203 [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3204 __METHOD__
3205 );
3206 if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3207 $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3208 [ 'fa_name' => $this->getDBkey() ],
3209 __METHOD__
3210 );
3211 }
3212 return $deleted;
3213 }
3214
3223 public function getArticleID( $flags = 0 ) {
3224 if ( $this->getNamespace() < 0 ) {
3225 $this->mArticleID = 0;
3226 return $this->mArticleID;
3227 }
3228 $linkCache = LinkCache::singleton();
3229 if ( $flags & self::GAID_FOR_UPDATE ) {
3230 $oldUpdate = $linkCache->forUpdate( true );
3231 $linkCache->clearLink( $this );
3232 $this->mArticleID = $linkCache->addLinkObj( $this );
3233 $linkCache->forUpdate( $oldUpdate );
3234 } else {
3235 if ( -1 == $this->mArticleID ) {
3236 $this->mArticleID = $linkCache->addLinkObj( $this );
3237 }
3238 }
3239 return $this->mArticleID;
3240 }
3241
3249 public function isRedirect( $flags = 0 ) {
3250 if ( !is_null( $this->mRedirect ) ) {
3251 return $this->mRedirect;
3252 }
3253 if ( !$this->getArticleID( $flags ) ) {
3254 $this->mRedirect = false;
3255 return $this->mRedirect;
3256 }
3257
3258 $linkCache = LinkCache::singleton();
3259 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3260 $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3261 if ( $cached === null ) {
3262 # Trust LinkCache's state over our own
3263 # LinkCache is telling us that the page doesn't exist, despite there being cached
3264 # data relating to an existing page in $this->mArticleID. Updaters should clear
3265 # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3266 # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3267 # LinkCache to refresh its data from the master.
3268 $this->mRedirect = false;
3269 return $this->mRedirect;
3270 }
3271
3272 $this->mRedirect = (bool)$cached;
3273
3274 return $this->mRedirect;
3275 }
3276
3284 public function getLength( $flags = 0 ) {
3285 if ( $this->mLength != -1 ) {
3286 return $this->mLength;
3287 }
3288 if ( !$this->getArticleID( $flags ) ) {
3289 $this->mLength = 0;
3290 return $this->mLength;
3291 }
3292 $linkCache = LinkCache::singleton();
3293 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3294 $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3295 if ( $cached === null ) {
3296 # Trust LinkCache's state over our own, as for isRedirect()
3297 $this->mLength = 0;
3298 return $this->mLength;
3299 }
3300
3301 $this->mLength = intval( $cached );
3302
3303 return $this->mLength;
3304 }
3305
3312 public function getLatestRevID( $flags = 0 ) {
3313 if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3314 return intval( $this->mLatestID );
3315 }
3316 if ( !$this->getArticleID( $flags ) ) {
3317 $this->mLatestID = 0;
3318 return $this->mLatestID;
3319 }
3320 $linkCache = LinkCache::singleton();
3321 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3322 $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3323 if ( $cached === null ) {
3324 # Trust LinkCache's state over our own, as for isRedirect()
3325 $this->mLatestID = 0;
3326 return $this->mLatestID;
3327 }
3328
3329 $this->mLatestID = intval( $cached );
3330
3331 return $this->mLatestID;
3332 }
3333
3344 public function resetArticleID( $newid ) {
3345 $linkCache = LinkCache::singleton();
3346 $linkCache->clearLink( $this );
3347
3348 if ( $newid === false ) {
3349 $this->mArticleID = -1;
3350 } else {
3351 $this->mArticleID = intval( $newid );
3352 }
3353 $this->mRestrictionsLoaded = false;
3354 $this->mRestrictions = [];
3355 $this->mOldRestrictions = false;
3356 $this->mRedirect = null;
3357 $this->mLength = -1;
3358 $this->mLatestID = false;
3359 $this->mContentModel = false;
3360 $this->mEstimateRevisions = null;
3361 $this->mPageLanguage = false;
3362 $this->mDbPageLanguage = false;
3363 $this->mIsBigDeletion = null;
3364 }
3365
3366 public static function clearCaches() {
3367 $linkCache = LinkCache::singleton();
3368 $linkCache->clear();
3369
3370 $titleCache = self::getTitleCache();
3371 $titleCache->clear();
3372 }
3373
3381 public static function capitalize( $text, $ns = NS_MAIN ) {
3383
3384 if ( MWNamespace::isCapitalized( $ns ) ) {
3385 return $wgContLang->ucfirst( $text );
3386 } else {
3387 return $text;
3388 }
3389 }
3390
3403 private function secureAndSplit() {
3404 # Initialisation
3405 $this->mInterwiki = '';
3406 $this->mFragment = '';
3407 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3408
3409 $dbkey = $this->mDbkeyform;
3410
3411 // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3412 // the parsing code with Title, while avoiding massive refactoring.
3413 // @todo: get rid of secureAndSplit, refactor parsing code.
3414 // @note: getTitleParser() returns a TitleParser implementation which does not have a
3415 // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3416 $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3417 // MalformedTitleException can be thrown here
3418 $parts = $titleCodec->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3419
3420 # Fill fields
3421 $this->setFragment( '#' . $parts['fragment'] );
3422 $this->mInterwiki = $parts['interwiki'];
3423 $this->mLocalInterwiki = $parts['local_interwiki'];
3424 $this->mNamespace = $parts['namespace'];
3425 $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3426
3427 $this->mDbkeyform = $parts['dbkey'];
3428 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3429 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3430
3431 # We already know that some pages won't be in the database!
3432 if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3433 $this->mArticleID = 0;
3434 }
3435
3436 return true;
3437 }
3438
3451 public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3452 if ( count( $options ) > 0 ) {
3453 $db = wfGetDB( DB_MASTER );
3454 } else {
3455 $db = wfGetDB( DB_REPLICA );
3456 }
3457
3458 $res = $db->select(
3459 [ 'page', $table ],
3460 self::getSelectFields(),
3461 [
3462 "{$prefix}_from=page_id",
3463 "{$prefix}_namespace" => $this->getNamespace(),
3464 "{$prefix}_title" => $this->getDBkey() ],
3465 __METHOD__,
3466 $options
3467 );
3468
3469 $retVal = [];
3470 if ( $res->numRows() ) {
3471 $linkCache = LinkCache::singleton();
3472 foreach ( $res as $row ) {
3473 $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3474 if ( $titleObj ) {
3475 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3476 $retVal[] = $titleObj;
3477 }
3478 }
3479 }
3480 return $retVal;
3481 }
3482
3493 public function getTemplateLinksTo( $options = [] ) {
3494 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3495 }
3496
3509 public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3510 $id = $this->getArticleID();
3511
3512 # If the page doesn't exist; there can't be any link from this page
3513 if ( !$id ) {
3514 return [];
3515 }
3516
3517 $db = wfGetDB( DB_REPLICA );
3518
3519 $blNamespace = "{$prefix}_namespace";
3520 $blTitle = "{$prefix}_title";
3521
3522 $res = $db->select(
3523 [ $table, 'page' ],
3524 array_merge(
3525 [ $blNamespace, $blTitle ],
3527 ),
3528 [ "{$prefix}_from" => $id ],
3529 __METHOD__,
3530 $options,
3531 [ 'page' => [
3532 'LEFT JOIN',
3533 [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3534 ] ]
3535 );
3536
3537 $retVal = [];
3538 $linkCache = LinkCache::singleton();
3539 foreach ( $res as $row ) {
3540 if ( $row->page_id ) {
3541 $titleObj = Title::newFromRow( $row );
3542 } else {
3543 $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3544 $linkCache->addBadLinkObj( $titleObj );
3545 }
3546 $retVal[] = $titleObj;
3547 }
3548
3549 return $retVal;
3550 }
3551
3562 public function getTemplateLinksFrom( $options = [] ) {
3563 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3564 }
3565
3574 public function getBrokenLinksFrom() {
3575 if ( $this->getArticleID() == 0 ) {
3576 # All links from article ID 0 are false positives
3577 return [];
3578 }
3579
3580 $dbr = wfGetDB( DB_REPLICA );
3581 $res = $dbr->select(
3582 [ 'page', 'pagelinks' ],
3583 [ 'pl_namespace', 'pl_title' ],
3584 [
3585 'pl_from' => $this->getArticleID(),
3586 'page_namespace IS NULL'
3587 ],
3588 __METHOD__, [],
3589 [
3590 'page' => [
3591 'LEFT JOIN',
3592 [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3593 ]
3594 ]
3595 );
3596
3597 $retVal = [];
3598 foreach ( $res as $row ) {
3599 $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3600 }
3601 return $retVal;
3602 }
3603
3610 public function getCdnUrls() {
3611 $urls = [
3612 $this->getInternalURL(),
3613 $this->getInternalURL( 'action=history' )
3614 ];
3615
3616 $pageLang = $this->getPageLanguage();
3617 if ( $pageLang->hasVariants() ) {
3618 $variants = $pageLang->getVariants();
3619 foreach ( $variants as $vCode ) {
3620 $urls[] = $this->getInternalURL( $vCode );
3621 }
3622 }
3623
3624 // If we are looking at a css/js user subpage, purge the action=raw.
3625 if ( $this->isJsSubpage() ) {
3626 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3627 } elseif ( $this->isCssSubpage() ) {
3628 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3629 }
3630
3631 Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3632 return $urls;
3633 }
3634
3638 public function getSquidURLs() {
3639 return $this->getCdnUrls();
3640 }
3641
3645 public function purgeSquid() {
3646 DeferredUpdates::addUpdate(
3647 new CdnCacheUpdate( $this->getCdnUrls() ),
3648 DeferredUpdates::PRESEND
3649 );
3650 }
3651
3662 public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3664
3665 if ( !( $nt instanceof Title ) ) {
3666 // Normally we'd add this to $errors, but we'll get
3667 // lots of syntax errors if $nt is not an object
3668 return [ [ 'badtitletext' ] ];
3669 }
3670
3671 $mp = new MovePage( $this, $nt );
3672 $errors = $mp->isValidMove()->getErrorsArray();
3673 if ( $auth ) {
3674 $errors = wfMergeErrorArrays(
3675 $errors,
3676 $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3677 );
3678 }
3679
3680 return $errors ?: true;
3681 }
3682
3689 protected function validateFileMoveOperation( $nt ) {
3691
3692 $errors = [];
3693
3694 $destFile = wfLocalFile( $nt );
3695 $destFile->load( File::READ_LATEST );
3696 if ( !$wgUser->isAllowed( 'reupload-shared' )
3697 && !$destFile->exists() && wfFindFile( $nt )
3698 ) {
3699 $errors[] = [ 'file-exists-sharedrepo' ];
3700 }
3701
3702 return $errors;
3703 }
3704
3718 public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true,
3719 array $changeTags = [] ) {
3720
3722 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3723 if ( is_array( $err ) ) {
3724 // Auto-block user's IP if the account was "hard" blocked
3725 $wgUser->spreadAnyEditBlock();
3726 return $err;
3727 }
3728 // Check suppressredirect permission
3729 if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3730 $createRedirect = true;
3731 }
3732
3733 $mp = new MovePage( $this, $nt );
3734 $status = $mp->move( $wgUser, $reason, $createRedirect, $changeTags );
3735 if ( $status->isOK() ) {
3736 return true;
3737 } else {
3738 return $status->getErrorsArray();
3739 }
3740 }
3741
3756 public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true,
3757 array $changeTags = [] ) {
3758
3760 // Check permissions
3761 if ( !$this->userCan( 'move-subpages' ) ) {
3762 return [
3763 [ 'cant-move-subpages' ],
3764 ];
3765 }
3766 // Do the source and target namespaces support subpages?
3767 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3768 return [
3769 [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $this->getNamespace() ) ],
3770 ];
3771 }
3772 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3773 return [
3774 [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $nt->getNamespace() ) ],
3775 ];
3776 }
3777
3778 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3779 $retval = [];
3780 $count = 0;
3781 foreach ( $subpages as $oldSubpage ) {
3782 $count++;
3783 if ( $count > $wgMaximumMovedPages ) {
3784 $retval[$oldSubpage->getPrefixedText()] = [
3785 [ 'movepage-max-pages', $wgMaximumMovedPages ],
3786 ];
3787 break;
3788 }
3789
3790 // We don't know whether this function was called before
3791 // or after moving the root page, so check both
3792 // $this and $nt
3793 if ( $oldSubpage->getArticleID() == $this->getArticleID()
3794 || $oldSubpage->getArticleID() == $nt->getArticleID()
3795 ) {
3796 // When moving a page to a subpage of itself,
3797 // don't move it twice
3798 continue;
3799 }
3800 $newPageName = preg_replace(
3801 '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3802 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
3803 $oldSubpage->getDBkey() );
3804 if ( $oldSubpage->isTalkPage() ) {
3805 $newNs = $nt->getTalkPage()->getNamespace();
3806 } else {
3807 $newNs = $nt->getSubjectPage()->getNamespace();
3808 }
3809 # T16385: we need makeTitleSafe because the new page names may
3810 # be longer than 255 characters.
3811 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3812
3813 $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect, $changeTags );
3814 if ( $success === true ) {
3815 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3816 } else {
3817 $retval[$oldSubpage->getPrefixedText()] = $success;
3818 }
3819 }
3820 return $retval;
3821 }
3822
3829 public function isSingleRevRedirect() {
3831
3832 $dbw = wfGetDB( DB_MASTER );
3833
3834 # Is it a redirect?
3835 $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3836 if ( $wgContentHandlerUseDB ) {
3837 $fields[] = 'page_content_model';
3838 }
3839
3840 $row = $dbw->selectRow( 'page',
3841 $fields,
3842 $this->pageCond(),
3843 __METHOD__,
3844 [ 'FOR UPDATE' ]
3845 );
3846 # Cache some fields we may want
3847 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3848 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3849 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3850 $this->mContentModel = $row && isset( $row->page_content_model )
3851 ? strval( $row->page_content_model )
3852 : false;
3853
3854 if ( !$this->mRedirect ) {
3855 return false;
3856 }
3857 # Does the article have a history?
3858 $row = $dbw->selectField( [ 'page', 'revision' ],
3859 'rev_id',
3860 [ 'page_namespace' => $this->getNamespace(),
3861 'page_title' => $this->getDBkey(),
3862 'page_id=rev_page',
3863 'page_latest != rev_id'
3864 ],
3865 __METHOD__,
3866 [ 'FOR UPDATE' ]
3867 );
3868 # Return true if there was no history
3869 return ( $row === false );
3870 }
3871
3880 public function isValidMoveTarget( $nt ) {
3881 # Is it an existing file?
3882 if ( $nt->getNamespace() == NS_FILE ) {
3883 $file = wfLocalFile( $nt );
3884 $file->load( File::READ_LATEST );
3885 if ( $file->exists() ) {
3886 wfDebug( __METHOD__ . ": file exists\n" );
3887 return false;
3888 }
3889 }
3890 # Is it a redirect with no history?
3891 if ( !$nt->isSingleRevRedirect() ) {
3892 wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3893 return false;
3894 }
3895 # Get the article text
3896 $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
3897 if ( !is_object( $rev ) ) {
3898 return false;
3899 }
3900 $content = $rev->getContent();
3901 # Does the redirect point to the source?
3902 # Or is it a broken self-redirect, usually caused by namespace collisions?
3903 $redirTitle = $content ? $content->getRedirectTarget() : null;
3904
3905 if ( $redirTitle ) {
3906 if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3907 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3908 wfDebug( __METHOD__ . ": redirect points to other page\n" );
3909 return false;
3910 } else {
3911 return true;
3912 }
3913 } else {
3914 # Fail safe (not a redirect after all. strange.)
3915 wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3916 " is a redirect, but it doesn't contain a valid redirect.\n" );
3917 return false;
3918 }
3919 }
3920
3928 public function getParentCategories() {
3930
3931 $data = [];
3932
3933 $titleKey = $this->getArticleID();
3934
3935 if ( $titleKey === 0 ) {
3936 return $data;
3937 }
3938
3939 $dbr = wfGetDB( DB_REPLICA );
3940
3941 $res = $dbr->select(
3942 'categorylinks',
3943 'cl_to',
3944 [ 'cl_from' => $titleKey ],
3945 __METHOD__
3946 );
3947
3948 if ( $res->numRows() > 0 ) {
3949 foreach ( $res as $row ) {
3950 // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3951 $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3952 }
3953 }
3954 return $data;
3955 }
3956
3963 public function getParentCategoryTree( $children = [] ) {
3964 $stack = [];
3965 $parents = $this->getParentCategories();
3966
3967 if ( $parents ) {
3968 foreach ( $parents as $parent => $current ) {
3969 if ( array_key_exists( $parent, $children ) ) {
3970 # Circular reference
3971 $stack[$parent] = [];
3972 } else {
3973 $nt = Title::newFromText( $parent );
3974 if ( $nt ) {
3975 $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3976 }
3977 }
3978 }
3979 }
3980
3981 return $stack;
3982 }
3983
3990 public function pageCond() {
3991 if ( $this->mArticleID > 0 ) {
3992 // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3993 return [ 'page_id' => $this->mArticleID ];
3994 } else {
3995 return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3996 }
3997 }
3998
4006 public function getPreviousRevisionID( $revId, $flags = 0 ) {
4007 /* This function and getNextRevisionID have bad performance when
4008 used on a page with many revisions on mysql. An explicit extended
4009 primary key may help in some cases, if the PRIMARY KEY is banned:
4010 T159319 */
4011 if ( $flags & self::GAID_FOR_UPDATE ) {
4012 $db = wfGetDB( DB_MASTER );
4013 } else {
4014 $db = wfGetDB( DB_REPLICA, 'contributions' );
4015 }
4016 $revId = $db->selectField( 'revision', 'rev_id',
4017 [
4018 'rev_page' => $this->getArticleID( $flags ),
4019 'rev_id < ' . intval( $revId )
4020 ],
4021 __METHOD__,
4022 [ 'ORDER BY' => 'rev_id DESC', 'IGNORE INDEX' => 'PRIMARY' ]
4023 );
4024
4025 if ( $revId === false ) {
4026 return false;
4027 } else {
4028 return intval( $revId );
4029 }
4030 }
4031
4039 public function getNextRevisionID( $revId, $flags = 0 ) {
4040 if ( $flags & self::GAID_FOR_UPDATE ) {
4041 $db = wfGetDB( DB_MASTER );
4042 } else {
4043 $db = wfGetDB( DB_REPLICA, 'contributions' );
4044 }
4045 $revId = $db->selectField( 'revision', 'rev_id',
4046 [
4047 'rev_page' => $this->getArticleID( $flags ),
4048 'rev_id > ' . intval( $revId )
4049 ],
4050 __METHOD__,
4051 [ 'ORDER BY' => 'rev_id', 'IGNORE INDEX' => 'PRIMARY' ]
4052 );
4053
4054 if ( $revId === false ) {
4055 return false;
4056 } else {
4057 return intval( $revId );
4058 }
4059 }
4060
4067 public function getFirstRevision( $flags = 0 ) {
4068 $pageId = $this->getArticleID( $flags );
4069 if ( $pageId ) {
4070 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
4071 $row = $db->selectRow( 'revision', Revision::selectFields(),
4072 [ 'rev_page' => $pageId ],
4073 __METHOD__,
4074 [
4075 'ORDER BY' => 'rev_timestamp ASC',
4076 'IGNORE INDEX' => 'rev_timestamp'
4077 ]
4078 );
4079 if ( $row ) {
4080 return new Revision( $row );
4081 }
4082 }
4083 return null;
4084 }
4085
4092 public function getEarliestRevTime( $flags = 0 ) {
4093 $rev = $this->getFirstRevision( $flags );
4094 return $rev ? $rev->getTimestamp() : null;
4095 }
4096
4102 public function isNewPage() {
4103 $dbr = wfGetDB( DB_REPLICA );
4104 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4105 }
4106
4112 public function isBigDeletion() {
4114
4115 if ( !$wgDeleteRevisionsLimit ) {
4116 return false;
4117 }
4118
4119 if ( $this->mIsBigDeletion === null ) {
4120 $dbr = wfGetDB( DB_REPLICA );
4121
4122 $revCount = $dbr->selectRowCount(
4123 'revision',
4124 '1',
4125 [ 'rev_page' => $this->getArticleID() ],
4126 __METHOD__,
4127 [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4128 );
4129
4130 $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4131 }
4132
4133 return $this->mIsBigDeletion;
4134 }
4135
4141 public function estimateRevisionCount() {
4142 if ( !$this->exists() ) {
4143 return 0;
4144 }
4145
4146 if ( $this->mEstimateRevisions === null ) {
4147 $dbr = wfGetDB( DB_REPLICA );
4148 $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4149 [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4150 }
4151
4152 return $this->mEstimateRevisions;
4153 }
4154
4164 public function countRevisionsBetween( $old, $new, $max = null ) {
4165 if ( !( $old instanceof Revision ) ) {
4166 $old = Revision::newFromTitle( $this, (int)$old );
4167 }
4168 if ( !( $new instanceof Revision ) ) {
4169 $new = Revision::newFromTitle( $this, (int)$new );
4170 }
4171 if ( !$old || !$new ) {
4172 return 0; // nothing to compare
4173 }
4174 $dbr = wfGetDB( DB_REPLICA );
4175 $conds = [
4176 'rev_page' => $this->getArticleID(),
4177 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4178 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4179 ];
4180 if ( $max !== null ) {
4181 return $dbr->selectRowCount( 'revision', '1',
4182 $conds,
4183 __METHOD__,
4184 [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4185 );
4186 } else {
4187 return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4188 }
4189 }
4190
4207 public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4208 if ( !( $old instanceof Revision ) ) {
4209 $old = Revision::newFromTitle( $this, (int)$old );
4210 }
4211 if ( !( $new instanceof Revision ) ) {
4212 $new = Revision::newFromTitle( $this, (int)$new );
4213 }
4214 // XXX: what if Revision objects are passed in, but they don't refer to this title?
4215 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4216 // in the sanity check below?
4217 if ( !$old || !$new ) {
4218 return null; // nothing to compare
4219 }
4220 $authors = [];
4221 $old_cmp = '>';
4222 $new_cmp = '<';
4224 if ( in_array( 'include_old', $options ) ) {
4225 $old_cmp = '>=';
4226 }
4227 if ( in_array( 'include_new', $options ) ) {
4228 $new_cmp = '<=';
4229 }
4230 if ( in_array( 'include_both', $options ) ) {
4231 $old_cmp = '>=';
4232 $new_cmp = '<=';
4233 }
4234 // No DB query needed if $old and $new are the same or successive revisions:
4235 if ( $old->getId() === $new->getId() ) {
4236 return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4237 [] :
4238 [ $old->getUserText( Revision::RAW ) ];
4239 } elseif ( $old->getId() === $new->getParentId() ) {
4240 if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4241 $authors[] = $old->getUserText( Revision::RAW );
4242 if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4243 $authors[] = $new->getUserText( Revision::RAW );
4244 }
4245 } elseif ( $old_cmp === '>=' ) {
4246 $authors[] = $old->getUserText( Revision::RAW );
4247 } elseif ( $new_cmp === '<=' ) {
4248 $authors[] = $new->getUserText( Revision::RAW );
4249 }
4250 return $authors;
4251 }
4252 $dbr = wfGetDB( DB_REPLICA );
4253 $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4254 [
4255 'rev_page' => $this->getArticleID(),
4256 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4257 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4258 ], __METHOD__,
4259 [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
4260 );
4261 foreach ( $res as $row ) {
4262 $authors[] = $row->rev_user_text;
4263 }
4264 return $authors;
4265 }
4266
4281 public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4282 $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4283 return $authors ? count( $authors ) : 0;
4284 }
4285
4292 public function equals( Title $title ) {
4293 // Note: === is necessary for proper matching of number-like titles.
4294 return $this->getInterwiki() === $title->getInterwiki()
4295 && $this->getNamespace() == $title->getNamespace()
4296 && $this->getDBkey() === $title->getDBkey();
4297 }
4298
4305 public function isSubpageOf( Title $title ) {
4306 return $this->getInterwiki() === $title->getInterwiki()
4307 && $this->getNamespace() == $title->getNamespace()
4308 && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4309 }
4310
4322 public function exists( $flags = 0 ) {
4323 $exists = $this->getArticleID( $flags ) != 0;
4324 Hooks::run( 'TitleExists', [ $this, &$exists ] );
4325 return $exists;
4326 }
4327
4344 public function isAlwaysKnown() {
4345 $isKnown = null;
4346
4357 Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4358
4359 if ( !is_null( $isKnown ) ) {
4360 return $isKnown;
4361 }
4362
4363 if ( $this->isExternal() ) {
4364 return true; // any interwiki link might be viewable, for all we know
4365 }
4366
4367 switch ( $this->mNamespace ) {
4368 case NS_MEDIA:
4369 case NS_FILE:
4370 // file exists, possibly in a foreign repo
4371 return (bool)wfFindFile( $this );
4372 case NS_SPECIAL:
4373 // valid special page
4374 return SpecialPageFactory::exists( $this->getDBkey() );
4375 case NS_MAIN:
4376 // selflink, possibly with fragment
4377 return $this->mDbkeyform == '';
4378 case NS_MEDIAWIKI:
4379 // known system message
4380 return $this->hasSourceText() !== false;
4381 default:
4382 return false;
4383 }
4384 }
4385
4397 public function isKnown() {
4398 return $this->isAlwaysKnown() || $this->exists();
4399 }
4400
4406 public function hasSourceText() {
4407 if ( $this->exists() ) {
4408 return true;
4409 }
4410
4411 if ( $this->mNamespace == NS_MEDIAWIKI ) {
4412 // If the page doesn't exist but is a known system message, default
4413 // message content will be displayed, same for language subpages-
4414 // Use always content language to avoid loading hundreds of languages
4415 // to get the link color.
4417 list( $name, ) = MessageCache::singleton()->figureMessage(
4418 $wgContLang->lcfirst( $this->getText() )
4419 );
4420 $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4421 return $message->exists();
4422 }
4423
4424 return false;
4425 }
4426
4432 public function getDefaultMessageText() {
4434
4435 if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4436 return false;
4437 }
4438
4439 list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4440 $wgContLang->lcfirst( $this->getText() )
4441 );
4442 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4443
4444 if ( $message->exists() ) {
4445 return $message->plain();
4446 } else {
4447 return false;
4448 }
4449 }
4450
4457 public function invalidateCache( $purgeTime = null ) {
4458 if ( wfReadOnly() ) {
4459 return false;
4460 } elseif ( $this->mArticleID === 0 ) {
4461 return true; // avoid gap locking if we know it's not there
4462 }
4463
4464 $dbw = wfGetDB( DB_MASTER );
4465 $dbw->onTransactionPreCommitOrIdle( function () {
4467 } );
4468
4469 $conds = $this->pageCond();
4470 DeferredUpdates::addUpdate(
4471 new AutoCommitUpdate(
4472 $dbw,
4473 __METHOD__,
4474 function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4475 $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4476 $dbw->update(
4477 'page',
4478 [ 'page_touched' => $dbTimestamp ],
4479 $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4480 $fname
4481 );
4482 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4483 }
4484 ),
4485 DeferredUpdates::PRESEND
4486 );
4487
4488 return true;
4489 }
4490
4496 public function touchLinks() {
4497 DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4498 if ( $this->getNamespace() == NS_CATEGORY ) {
4499 DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4500 }
4501 }
4502
4509 public function getTouched( $db = null ) {
4510 if ( $db === null ) {
4511 $db = wfGetDB( DB_REPLICA );
4512 }
4513 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4514 return $touched;
4515 }
4516
4523 public function getNotificationTimestamp( $user = null ) {
4525
4526 // Assume current user if none given
4527 if ( !$user ) {
4528 $user = $wgUser;
4529 }
4530 // Check cache first
4531 $uid = $user->getId();
4532 if ( !$uid ) {
4533 return false;
4534 }
4535 // avoid isset here, as it'll return false for null entries
4536 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4537 return $this->mNotificationTimestamp[$uid];
4538 }
4539 // Don't cache too much!
4540 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4541 $this->mNotificationTimestamp = [];
4542 }
4543
4544 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4545 $watchedItem = $store->getWatchedItem( $user, $this );
4546 if ( $watchedItem ) {
4547 $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4548 } else {
4549 $this->mNotificationTimestamp[$uid] = false;
4550 }
4551
4552 return $this->mNotificationTimestamp[$uid];
4553 }
4554
4561 public function getNamespaceKey( $prepend = 'nstab-' ) {
4563 // Gets the subject namespace if this title
4564 $namespace = MWNamespace::getSubject( $this->getNamespace() );
4565 // Checks if canonical namespace name exists for namespace
4566 if ( MWNamespace::exists( $this->getNamespace() ) ) {
4567 // Uses canonical namespace name
4568 $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4569 } else {
4570 // Uses text of namespace
4571 $namespaceKey = $this->getSubjectNsText();
4572 }
4573 // Makes namespace key lowercase
4574 $namespaceKey = $wgContLang->lc( $namespaceKey );
4575 // Uses main
4576 if ( $namespaceKey == '' ) {
4577 $namespaceKey = 'main';
4578 }
4579 // Changes file to image for backwards compatibility
4580 if ( $namespaceKey == 'file' ) {
4581 $namespaceKey = 'image';
4582 }
4583 return $prepend . $namespaceKey;
4584 }
4585
4592 public function getRedirectsHere( $ns = null ) {
4593 $redirs = [];
4594
4595 $dbr = wfGetDB( DB_REPLICA );
4596 $where = [
4597 'rd_namespace' => $this->getNamespace(),
4598 'rd_title' => $this->getDBkey(),
4599 'rd_from = page_id'
4600 ];
4601 if ( $this->isExternal() ) {
4602 $where['rd_interwiki'] = $this->getInterwiki();
4603 } else {
4604 $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4605 }
4606 if ( !is_null( $ns ) ) {
4607 $where['page_namespace'] = $ns;
4608 }
4609
4610 $res = $dbr->select(
4611 [ 'redirect', 'page' ],
4612 [ 'page_namespace', 'page_title' ],
4613 $where,
4614 __METHOD__
4615 );
4616
4617 foreach ( $res as $row ) {
4618 $redirs[] = self::newFromRow( $row );
4619 }
4620 return $redirs;
4621 }
4622
4628 public function isValidRedirectTarget() {
4630
4631 if ( $this->isSpecialPage() ) {
4632 // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4633 if ( $this->isSpecial( 'Userlogout' ) ) {
4634 return false;
4635 }
4636
4637 foreach ( $wgInvalidRedirectTargets as $target ) {
4638 if ( $this->isSpecial( $target ) ) {
4639 return false;
4640 }
4641 }
4642 }
4643
4644 return true;
4645 }
4646
4652 public function getBacklinkCache() {
4653 return BacklinkCache::get( $this );
4654 }
4655
4661 public function canUseNoindex() {
4663
4664 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4665 ? MWNamespace::getContentNamespaces()
4667
4668 return !in_array( $this->mNamespace, $bannedNamespaces );
4669 }
4670
4681 public function getCategorySortkey( $prefix = '' ) {
4682 $unprefixed = $this->getText();
4683
4684 // Anything that uses this hook should only depend
4685 // on the Title object passed in, and should probably
4686 // tell the users to run updateCollations.php --force
4687 // in order to re-sort existing category relations.
4688 Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4689 if ( $prefix !== '' ) {
4690 # Separate with a line feed, so the unprefixed part is only used as
4691 # a tiebreaker when two pages have the exact same prefix.
4692 # In UCA, tab is the only character that can sort above LF
4693 # so we strip both of them from the original prefix.
4694 $prefix = strtr( $prefix, "\n\t", ' ' );
4695 return "$prefix\n$unprefixed";
4696 }
4697 return $unprefixed;
4698 }
4699
4707 private function getDbPageLanguageCode() {
4709
4710 // check, if the page language could be saved in the database, and if so and
4711 // the value is not requested already, lookup the page language using LinkCache
4712 if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4713 $linkCache = LinkCache::singleton();
4714 $linkCache->addLinkObj( $this );
4715 $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4716 }
4717
4718 return $this->mDbPageLanguage;
4719 }
4720
4729 public function getPageLanguage() {
4731 if ( $this->isSpecialPage() ) {
4732 // special pages are in the user language
4733 return $wgLang;
4734 }
4735
4736 // Checking if DB language is set
4737 $dbPageLanguage = $this->getDbPageLanguageCode();
4738 if ( $dbPageLanguage ) {
4739 return wfGetLangObj( $dbPageLanguage );
4740 }
4741
4742 if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4743 // Note that this may depend on user settings, so the cache should
4744 // be only per-request.
4745 // NOTE: ContentHandler::getPageLanguage() may need to load the
4746 // content to determine the page language!
4747 // Checking $wgLanguageCode hasn't changed for the benefit of unit
4748 // tests.
4749 $contentHandler = ContentHandler::getForTitle( $this );
4750 $langObj = $contentHandler->getPageLanguage( $this );
4751 $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4752 } else {
4753 $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4754 }
4755
4756 return $langObj;
4757 }
4758
4767 public function getPageViewLanguage() {
4769
4770 if ( $this->isSpecialPage() ) {
4771 // If the user chooses a variant, the content is actually
4772 // in a language whose code is the variant code.
4773 $variant = $wgLang->getPreferredVariant();
4774 if ( $wgLang->getCode() !== $variant ) {
4775 return Language::factory( $variant );
4776 }
4777
4778 return $wgLang;
4779 }
4780
4781 // Checking if DB language is set
4782 $dbPageLanguage = $this->getDbPageLanguageCode();
4783 if ( $dbPageLanguage ) {
4784 $pageLang = wfGetLangObj( $dbPageLanguage );
4785 $variant = $pageLang->getPreferredVariant();
4786 if ( $pageLang->getCode() !== $variant ) {
4787 $pageLang = Language::factory( $variant );
4788 }
4789
4790 return $pageLang;
4791 }
4792
4793 // @note Can't be cached persistently, depends on user settings.
4794 // @note ContentHandler::getPageViewLanguage() may need to load the
4795 // content to determine the page language!
4796 $contentHandler = ContentHandler::getForTitle( $this );
4797 $pageLang = $contentHandler->getPageViewLanguage( $this );
4798 return $pageLang;
4799 }
4800
4811 public function getEditNotices( $oldid = 0 ) {
4812 $notices = [];
4813
4814 // Optional notice for the entire namespace
4815 $editnotice_ns = 'editnotice-' . $this->getNamespace();
4816 $msg = wfMessage( $editnotice_ns );
4817 if ( $msg->exists() ) {
4818 $html = $msg->parseAsBlock();
4819 // Edit notices may have complex logic, but output nothing (T91715)
4820 if ( trim( $html ) !== '' ) {
4821 $notices[$editnotice_ns] = Html::rawElement(
4822 'div',
4823 [ 'class' => [
4824 'mw-editnotice',
4825 'mw-editnotice-namespace',
4826 Sanitizer::escapeClass( "mw-$editnotice_ns" )
4827 ] ],
4828 $html
4829 );
4830 }
4831 }
4832
4833 if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4834 // Optional notice for page itself and any parent page
4835 $parts = explode( '/', $this->getDBkey() );
4836 $editnotice_base = $editnotice_ns;
4837 while ( count( $parts ) > 0 ) {
4838 $editnotice_base .= '-' . array_shift( $parts );
4839 $msg = wfMessage( $editnotice_base );
4840 if ( $msg->exists() ) {
4841 $html = $msg->parseAsBlock();
4842 if ( trim( $html ) !== '' ) {
4843 $notices[$editnotice_base] = Html::rawElement(
4844 'div',
4845 [ 'class' => [
4846 'mw-editnotice',
4847 'mw-editnotice-base',
4848 Sanitizer::escapeClass( "mw-$editnotice_base" )
4849 ] ],
4850 $html
4851 );
4852 }
4853 }
4854 }
4855 } else {
4856 // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4857 $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4858 $msg = wfMessage( $editnoticeText );
4859 if ( $msg->exists() ) {
4860 $html = $msg->parseAsBlock();
4861 if ( trim( $html ) !== '' ) {
4862 $notices[$editnoticeText] = Html::rawElement(
4863 'div',
4864 [ 'class' => [
4865 'mw-editnotice',
4866 'mw-editnotice-page',
4867 Sanitizer::escapeClass( "mw-$editnoticeText" )
4868 ] ],
4869 $html
4870 );
4871 }
4872 }
4873 }
4874
4875 Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4876 return $notices;
4877 }
4878
4882 public function __sleep() {
4883 return [
4884 'mNamespace',
4885 'mDbkeyform',
4886 'mFragment',
4887 'mInterwiki',
4888 'mLocalInterwiki',
4889 'mUserCaseDBKey',
4890 'mDefaultNamespace',
4891 ];
4892 }
4893
4894 public function __wakeup() {
4895 $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4896 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4897 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4898 }
4899
4900}
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
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$wgRestrictionLevels
Rights which can be required for each protection level (via action=protect)
$wgExemptFromUserRobotsControl
An array of namespace keys in which the INDEX/__NOINDEX__ magic words will not function,...
$wgLegalTitleChars
Allowed title characters – regex character class Don't change this unless you know what you're doing.
bool $wgPageLanguageUseDB
Enable page language feature Allows setting page language in database.
$wgSemiprotectedRestrictionLevels
Restriction levels that should be considered "semiprotected".
$wgLanguageCode
Site language code.
$wgMaximumMovedPages
Maximum number of pages to move at once when moving subpages with a page.
$wgScript
The URL path to index.php.
$wgInternalServer
Internal server name as known to CDN, if different.
$wgInvalidRedirectTargets
Array of invalid page redirect targets.
$wgNamespaceProtection
Set the minimum permissions required to edit pages in each namespace.
$wgRestrictionTypes
Set of available actions that can be restricted via action=protect You probably shouldn't change this...
$wgWhitelistRead
Pages anonymous user may see, set as an array of pages titles.
$wgWhitelistReadRegexp
Pages anonymous user may see, set as an array of regular expressions.
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
$wgBlockDisablesLogin
If true, blocked users will not be allowed to login.
$wgEmailConfirmToEdit
Should editors be required to have a validated e-mail address before being allowed to edit?
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
$wgServer
URL of the server.
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility.
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfMergeErrorArrays()
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
wfLocalFile( $title)
Get an object referring to a locally registered file.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfFindFile( $title, $options=[])
Find a file.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
$wgUser
Definition Setup.php:781
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition Setup.php:36
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:639
Deferrable Update for closure/callback updates via IDatabase::doAtomicSection()
Deferrable Update for closure/callback updates that should use auto-commit mode.
static get(Title $title)
Create a new BacklinkCache or reuse any existing one.
get( $key, $flags=0, $oldFlags=null)
Get an item with the given key.
Handles purging appropriate CDN URLs given a title (or titles)
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
Class to invalidate the HTML cache of all the pages linking to a given title.
Simple store for keeping values in an associative array for the current process.
set( $key, $value, $exptime=0, $flags=0)
Set an item.
MediaWiki exception.
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.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static singleton()
Get the signleton instance of this class.
Handles the backend logic of moving a page from one title to another.
Definition MovePage.php:30
static getMain()
Static methods.
static invalidateModuleCache(Title $title, Revision $old=null, Revision $new=null, $wikiId)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition Revision.php:448
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition Revision.php:134
const RAW
Definition Revision.php:100
static getLocalNameFor( $name, $subpage=false)
Get the local name for a specified canonical name.
static exists( $name)
Check if a given name exist as a special page or as a special page alias.
static resolveAlias( $alias)
Given a special page name with a possible subpage, return an array where the first element is the spe...
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
The TitleArray class only exists to provide the newFromResult method at pre- sent.
static newFromResult( $res)
Represents a page (or page fragment) title within MediaWiki.
Represents a title within MediaWiki.
Definition Title.php:39
string $mInterwiki
Interwiki prefix.
Definition Title.php:79
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition Title.php:405
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition Title.php:1114
getSubpages( $limit=-1)
Get all subpages of this page.
Definition Title.php:3144
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Definition Title.php:610
isWatchable()
Can this title be added to a user's watchlist?
Definition Title.php:1045
getNamespace()
Get the namespace index, i.e.
Definition Title.php:924
estimateRevisionCount()
Get the approximate revision count of this page.
Definition Title.php:4141
__wakeup()
Text form (spaces not underscores) of the main part.
Definition Title.php:4894
static newFromDBkey( $key)
Create a new Title from a prefixed DB key.
Definition Title.php:209
isProtected( $action='')
Does the title correspond to a protected article?
Definition Title.php:2701
getTitleProtectionInternal()
Fetch title protection settings.
Definition Title.php:2617
getLinkURL( $query='', $query2=false, $proto=false)
Get a URL that's the simplest URL that will be valid to link, locally, to the current Title.
Definition Title.php:1835
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition Title.php:158
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition Title.php:127
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition Title.php:3829
wasLocalInterwiki()
Was this a local interwiki link?
Definition Title.php:820
checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short)
Check CSS/JS sub-page permissions.
Definition Title.php:2152
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition Title.php:1858
purgeSquid()
Purge all applicable CDN URLs.
Definition Title.php:3645
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition Title.php:1668
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition Title.php:2881
isKnown()
Does this title refer to a page that can (or might) be meaningfully viewed? In particular,...
Definition Title.php:4397
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition Title.php:106
getBacklinkCache()
Get a backlink cache object.
Definition Title.php:4652
static getInterwikiLookup()
B/C kludge: provide an InterwikiLookup for use by Title.
Definition Title.php:191
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition Title.php:179
inNamespace( $ns)
Returns true if the title is inside the specified namespace.
Definition Title.php:1103
equals(Title $title)
Compare with another title.
Definition Title.php:4292
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition Title.php:3197
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition Title.php:3381
checkQuickPermissions( $action, $user, $errors, $rigor, $short)
Permissions checks that fail most often, and which are easiest to test.
Definition Title.php:1985
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition Title.php:1302
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition Title.php:3403
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition Title.php:2897
hasContentModel( $id)
Convenience method for checking a title's content model name.
Definition Title.php:957
getSkinFromCssJsSubpage()
Trim down a .css or .js subpage title to get the corresponding skin name.
Definition Title.php:1258
static clearCaches()
Text form (spaces not underscores) of the main part.
Definition Title.php:3366
createFragmentTarget( $fragment)
Creates a new Title for a different fragment of the same page.
Definition Title.php:1405
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition Title.php:1345
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move a title to a new location.
Definition Title.php:3718
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition Title.php:1204
getFragment()
Get the Title fragment (i.e.
Definition Title.php:1356
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not.
Definition Title.php:2751
static getFilteredRestrictionTypes( $exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition Title.php:2549
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition Title.php:1611
TitleValue $mTitleValue
A corresponding TitleValue object.
Definition Title.php:165
checkUserBlock( $action, $user, $errors, $rigor, $short)
Check that the user isn't blocked from editing.
Definition Title.php:2345
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition Title.php:1216
validateFileMoveOperation( $nt)
Check if the requested move target is a valid file move target.
Definition Title.php:3689
getTalkNsText()
Get the namespace text of the talk page.
Definition Title.php:1017
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition Title.php:2923
hasFragment()
Check if a Title fragment is set.
Definition Title.php:1366
static nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition Title.php:574
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition Title.php:1064
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition Title.php:4592
getLength( $flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition Title.php:3284
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition Title.php:152
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition Title.php:3662
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition Title.php:1475
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition Title.php:2868
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition Title.php:4661
exists( $flags=0)
Check if page exists.
Definition Title.php:4322
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition Title.php:342
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,...
Definition Title.php:295
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition Title.php:785
int $mLength
The page length, 0 for special pages.
Definition Title.php:146
loadFromRow( $row)
Load Title object fields from a DB row.
Definition Title.php:465
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition Title.php:4729
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition Title.php:82
getUserCaseDBKey()
Get the DB key with the initial letter case as specified by the user.
Definition Title.php:910
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable.
Definition Title.php:1163
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles.
Definition Title.php:48
getRestrictionExpiry( $action)
Get the expiry time for the restriction against a given action.
Definition Title.php:2911
getUserPermissionsErrors( $action, $user, $rigor='secure', $ignoreErrors=[])
Can $user perform $action on this page?
Definition Title.php:1954
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition Title.php:1312
getTemplateLinksTo( $options=[])
Get an array of Title objects using this Title as a template Also stores the IDs in the link cache.
Definition Title.php:3493
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias,...
Definition Title.php:1080
getRestrictionTypes()
Returns restriction types for the current Title.
Definition Title.php:2567
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition Title.php:596
__toString()
Return a string representation of this title.
Definition Title.php:1465
hasSubjectNamespace( $ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition Title.php:1142
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition Title.php:2673
isCssJsSubpage()
Is this a .css or .js subpage of a user page?
Definition Title.php:1247
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1439
areCascadeProtectionSourcesLoaded( $getPages=true)
Determines whether cascading protection sources have already been loaded from the database.
Definition Title.php:2765
getPreviousRevisionID( $revId, $flags=0)
Get the revision ID of the previous revision.
Definition Title.php:4006
getNsText()
Get the namespace text.
Definition Title.php:982
canExist()
Is this in a namespace that allows actual pages?
Definition Title.php:1036
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition Title.php:3075
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition Title.php:4432
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition Title.php:4707
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition Title.php:4164
checkPermissionHooks( $action, $user, $errors, $rigor, $short)
Check various permission hooks.
Definition Title.php:2086
bool $mForcedContentModel
If a content model was forced via setContentModel() this will be true to avoid having other code path...
Definition Title.php:103
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition Title.php:4523
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition Title.php:830
resetArticleID( $newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition Title.php:3344
isNewPage()
Check if this is a new page.
Definition Title.php:4102
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition Title.php:4496
isExternal()
Is this Title interwiki?
Definition Title.php:800
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition Title.php:130
isMainPage()
Is this the mainpage?
Definition Title.php:1184
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition Title.php:1374
string bool null $mDbPageLanguage
The page language code from the database, null if not saved in the database or false if not loaded,...
Definition Title.php:162
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition Title.php:4207
isSpecialPage()
Returns true if this is a special page.
Definition Title.php:1054
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition Title.php:2733
static newFromLinkTarget(LinkTarget $linkTarget)
Create a new Title from a LinkTarget.
Definition Title.php:239
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition Title.php:1600
isTalkPage()
Is this a talk page of some sort?
Definition Title.php:1293
getRootTitle()
Get the root page name title, i.e.
Definition Title.php:1515
bool int $mLatestID
ID of most recent revision.
Definition Title.php:91
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page.
Definition Title.php:3574
getDBkey()
Get the main part with underscores.
Definition Title.php:901
missingPermissionError( $action, $short)
Get a description array when the user doesn't have the right to perform $action (i....
Definition Title.php:2465
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object.
Definition Title.php:1421
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition Title.php:4092
checkActionPermissions( $action, $user, $errors, $rigor, $short)
Check action permissions not already checked in checkQuickPermissions.
Definition Title.php:2268
string $mFragment
Title fragment (i.e.
Definition Title.php:85
getRootText()
Get the root page name text without a namespace, i.e.
Definition Title.php:1495
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition Title.php:1703
static newFromTitleValue(TitleValue $titleValue)
Create a new Title from a TitleValue.
Definition Title.php:228
string $mPrefixedText
Text form including namespace/interwiki, initialised on demand.
Definition Title.php:133
bool string $mContentModel
ID of the page's content model, i.e.
Definition Title.php:97
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition Title.php:3312
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition Title.php:624
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition Title.php:4628
static HashBagOStuff $titleCache
Definition Title.php:41
getLinksFrom( $options=[], $table='pagelinks', $prefix='pl')
Get an array of Title objects linked from this Title Also stores the IDs in the link cache.
Definition Title.php:3509
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition Title.php:728
getLinksTo( $options=[], $table='pagelinks', $prefix='pl')
Get an array of Title objects linking to this Title Also stores the IDs in the link cache.
Definition Title.php:3451
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition Title.php:124
getPartialURL()
Get the URL-encoded form of the main part.
Definition Title.php:892
getBaseText()
Get the base page name without a namespace, i.e.
Definition Title.php:1530
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page,...
Definition Title.php:1153
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:883
getTouched( $db=null)
Get the last touched timestamp.
Definition Title.php:4509
getTitleValue()
Get a TitleValue object representing this Title.
Definition Title.php:860
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition Title.php:3990
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition Title.php:115
string $mUrlform
URL-encoded form of the main part.
Definition Title.php:67
isJsSubpage()
Is this a .js subpage of a user page?
Definition Title.php:1283
getFirstRevision( $flags=0)
Get the first revision of the page.
Definition Title.php:4067
string $mTextform
Text form (spaces not underscores) of the main part.
Definition Title.php:64
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:1329
static newFromIDs( $ids)
Make an array of titles from an array of IDs.
Definition Title.php:427
getUserPermissionsErrorsInternal( $action, $user, $rigor='secure', $short=false)
Can $user perform $action on this page? This is an internal function, with multiple levels of checks ...
Definition Title.php:2488
quickUserCan( $action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition Title.php:1917
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
Definition Title.php:379
__construct()
Definition Title.php:198
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition Title.php:4305
getBaseTitle()
Get the base page name title, i.e.
Definition Title.php:1555
static newMainPage()
Create a new Title for the Main Page.
Definition Title.php:559
getParentCategoryTree( $children=[])
Get a tree of parent categories.
Definition Title.php:3963
checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short)
Check permissions on special pages & namespaces.
Definition Title.php:2123
bool $mHasSubpages
Whether a page has any subpages.
Definition Title.php:155
isCssSubpage()
Is this a .css subpage of a user page?
Definition Title.php:1273
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition Title.php:4039
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition Title.php:121
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition Title.php:2940
static fixUrlQueryArgs( $query, $query2=false)
Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args get{Canonical,...
Definition Title.php:1630
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition Title.php:3880
loadRestrictions( $oldFashionedRestrictions=null)
Load restrictions from the page_restrictions table.
Definition Title.php:3010
getSquidURLs()
Definition Title.php:3638
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition Title.php:3249
checkPageRestrictions( $action, $user, $errors, $rigor, $short)
Check against page_restrictions table requirements on this page.
Definition Title.php:2187
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition Title.php:265
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition Title.php:4457
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition Title.php:118
static escapeFragmentForURL( $fragment)
Escape a text fragment, say from a link, for a URL.
Definition Title.php:754
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:3223
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition Title.php:1007
bool $mIsBigDeletion
Would deleting this page be a big deletion?
Definition Title.php:168
int $mNamespace
Namespace index, i.e.
Definition Title.php:76
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:538
null $mRedirect
Is the article at this title a redirect?
Definition Title.php:149
countAuthorsBetween( $old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition Title.php:4281
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition Title.php:770
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition Title.php:1880
checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition Title.php:2221
isDeleted()
Is there a version of this page in the deletion archive?
Definition Title.php:3172
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition Title.php:4767
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition Title.php:54
checkReadPermissions( $action, $user, $errors, $rigor, $short)
Check that the user is allowed to read this page.
Definition Title.php:2390
userCan( $action, $user=null, $rigor='secure')
Can $user perform $action on this page?
Definition Title.php:1930
array $mRestrictions
Array of groups allowed to edit this article.
Definition Title.php:109
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition Title.php:143
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move this page's subpages to be subpages of $nt.
Definition Title.php:3756
deleteTitleProtection()
Remove any title protection due to page existing.
Definition Title.php:2655
getSubpage( $text)
Get the title for a subpage of the current page.
Definition Title.php:1591
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition Title.php:2594
getEditURL()
Get the edit URL for this Title.
Definition Title.php:1894
getParentCategories()
Get categories to which this Title belongs and return an array of categories' names.
Definition Title.php:3928
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:514
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition Title.php:88
static getTitleCache()
Definition Title.php:365
getTemplateLinksFrom( $options=[])
Get an array of Title objects used on this Title as a template Also stores the IDs in the link cache.
Definition Title.php:3562
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition Title.php:843
isSubpage()
Is this a subpage?
Definition Title.php:1193
setFragment( $fragment)
Set the fragment for this title.
Definition Title.php:1394
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition Title.php:1737
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:934
isCssOrJsPage()
Could this page contain custom CSS or JavaScript for the global UI.
Definition Title.php:1234
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition Title.php:4112
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition Title.php:3610
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition Title.php:73
getInterwiki()
Get the interwiki prefix.
Definition Title.php:811
getEditNotices( $oldid=0)
Get a list of rendered edit notices for this page.
Definition Title.php:4811
__sleep()
Definition Title.php:4882
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition Title.php:972
getCascadeProtectionSources( $getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition Title.php:2782
mixed $mTitleProtection
Cached value for getTitleProtection (create protection)
Definition Title.php:136
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition Title.php:1570
string $mDbkeyform
Main part with underscores.
Definition Title.php:70
hasSourceText()
Does this page have source text?
Definition Title.php:4406
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition Title.php:3065
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1451
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition Title.php:3116
string bool $mOldRestrictions
Text form (spaces not underscores) of the main part.
Definition Title.php:112
canTalk()
Could this title have a corresponding talk page?
Definition Title.php:1027
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition Title.php:2055
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition Title.php:4344
getNamespaceKey( $prepend='nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition Title.php:4561
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition Title.php:4681
static newFromRow( $row)
Make a Title object from a DB row.
Definition Title.php:453
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:50
static selectFields()
Return the list of revision fields that should be selected to create a new page.
Definition WikiPage.php:286
Relational database abstraction object.
Definition Database.php:45
$res
Definition database.txt:21
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
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
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:57
when a variable name is used in a function
Definition design.txt:94
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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:18
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
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
const PROTO_CANONICAL
Definition Defines.php:221
const NS_USER
Definition Defines.php:64
const CONTENT_MODEL_CSS
Definition Defines.php:235
const NS_FILE
Definition Defines.php:68
const PROTO_CURRENT
Definition Defines.php:220
const NS_MAIN
Definition Defines.php:62
const NS_MEDIAWIKI
Definition Defines.php:70
const NS_SPECIAL
Definition Defines.php:51
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:233
const PROTO_HTTP
Definition Defines.php:217
const NS_MEDIA
Definition Defines.php:50
const PROTO_RELATIVE
Definition Defines.php:219
const NS_CATEGORY
Definition Defines.php:76
const CONTENT_MODEL_JAVASCRIPT
Definition Defines.php:234
the array() calling protocol came about after MediaWiki 1.4rc1.
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:249
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition hooks.txt:2578
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $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':DEPRECATED! Use HtmlPageLinkRendererBegin instead. 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:1954
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:268
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition hooks.txt:1018
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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 please use GetContentModels hook to make them known to core 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:1143
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:183
namespace and then decline to actually register it & $namespaces
Definition hooks.txt:934
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1102
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1100
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
either a unescaped string or a HtmlArmor object 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 unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
null for the local wiki Added in
Definition hooks.txt:1572
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2753
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached 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:2604
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:1966
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:864
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:1974
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1101
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
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:1601
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:1751
processing should stop and the error should be shown to the user * false
Definition hooks.txt:189
$wgActionPaths
Definition img_auth.php:46
$wgArticlePath
Definition img_auth.php:45
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Service interface for looking up Interwiki records.
getInterwiki()
The interwiki component of this LinkTarget.
getFragment()
Get the link fragment (i.e.
getNamespace()
Get the namespace index.
getText()
Returns the link in text form, without namespace prefix or fragment.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:40
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
addQuotes( $s)
Adds quotes and backslashes.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[])
A SELECT wrapper which returns a list of single field values from result rows.
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
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26
if(!isset( $args[0])) $lang