MediaWiki REL1_28
Title.php
Go to the documentation of this file.
1<?php
27
36class Title implements LinkTarget {
38 static private $titleCache = null;
39
45 const CACHE_MAX = 1000;
46
51 const GAID_FOR_UPDATE = 1;
52
58 // @{
59
61 public $mTextform = '';
62
64 public $mUrlform = '';
65
67 public $mDbkeyform = '';
68
70 protected $mUserCaseDBKey;
71
74
76 public $mInterwiki = '';
77
79 private $mLocalInterwiki = false;
80
82 public $mFragment = '';
83
85 public $mArticleID = -1;
86
88 protected $mLatestID = false;
89
94 private $mContentModel = false;
95
100 private $mForcedContentModel = false;
101
104
106 public $mRestrictions = [];
107
109 protected $mOldRestrictions = false;
110
113
116
118 protected $mRestrictionsExpiry = [];
119
122
125
127 public $mRestrictionsLoaded = false;
128
130 protected $mPrefixedText = null;
131
134
141
143 protected $mLength = -1;
144
146 public $mRedirect = null;
147
150
153
155 private $mPageLanguage = false;
156
159 private $mDbPageLanguage = false;
160
162 private $mTitleValue = null;
163
165 private $mIsBigDeletion = null;
166 // @}
167
176 private static function getTitleFormatter() {
177 return MediaWikiServices::getInstance()->getTitleFormatter();
178 }
179
188 private static function getInterwikiLookup() {
189 return MediaWikiServices::getInstance()->getInterwikiLookup();
190 }
191
195 function __construct() {
196 }
197
206 public static function newFromDBkey( $key ) {
207 $t = new Title();
208 $t->mDbkeyform = $key;
209
210 try {
211 $t->secureAndSplit();
212 return $t;
213 } catch ( MalformedTitleException $ex ) {
214 return null;
215 }
216 }
217
225 public static function newFromTitleValue( TitleValue $titleValue ) {
226 return self::newFromLinkTarget( $titleValue );
227 }
228
236 public static function newFromLinkTarget( LinkTarget $linkTarget ) {
237 if ( $linkTarget instanceof Title ) {
238 // Special case if it's already a Title object
239 return $linkTarget;
240 }
241 return self::makeTitle(
242 $linkTarget->getNamespace(),
243 $linkTarget->getText(),
244 $linkTarget->getFragment(),
245 $linkTarget->getInterwiki()
246 );
247 }
248
262 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
263 // DWIM: Integers can be passed in here when page titles are used as array keys.
264 if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
265 throw new InvalidArgumentException( '$text must be a string.' );
266 }
267 if ( $text === null ) {
268 return null;
269 }
270
271 try {
272 return Title::newFromTextThrow( strval( $text ), $defaultNamespace );
273 } catch ( MalformedTitleException $ex ) {
274 return null;
275 }
276 }
277
292 public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
293 if ( is_object( $text ) ) {
294 throw new MWException( '$text must be a string, given an object' );
295 }
296
297 $titleCache = self::getTitleCache();
298
299 // Wiki pages often contain multiple links to the same page.
300 // Title normalization and parsing can become expensive on pages with many
301 // links, so we can save a little time by caching them.
302 // In theory these are value objects and won't get changed...
303 if ( $defaultNamespace == NS_MAIN ) {
304 $t = $titleCache->get( $text );
305 if ( $t ) {
306 return $t;
307 }
308 }
309
310 // Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
311 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
312
313 $t = new Title();
314 $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
315 $t->mDefaultNamespace = intval( $defaultNamespace );
316
317 $t->secureAndSplit();
318 if ( $defaultNamespace == NS_MAIN ) {
319 $titleCache->set( $text, $t );
320 }
321 return $t;
322 }
323
339 public static function newFromURL( $url ) {
340 $t = new Title();
341
342 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
343 # but some URLs used it as a space replacement and they still come
344 # from some external search tools.
345 if ( strpos( self::legalChars(), '+' ) === false ) {
346 $url = strtr( $url, '+', ' ' );
347 }
348
349 $t->mDbkeyform = strtr( $url, ' ', '_' );
350
351 try {
352 $t->secureAndSplit();
353 return $t;
354 } catch ( MalformedTitleException $ex ) {
355 return null;
356 }
357 }
358
362 private static function getTitleCache() {
363 if ( self::$titleCache == null ) {
364 self::$titleCache = new HashBagOStuff( [ 'maxKeys' => self::CACHE_MAX ] );
365 }
366 return self::$titleCache;
367 }
368
376 protected static function getSelectFields() {
378
379 $fields = [
380 'page_namespace', 'page_title', 'page_id',
381 'page_len', 'page_is_redirect', 'page_latest',
382 ];
383
385 $fields[] = 'page_content_model';
386 }
387
388 if ( $wgPageLanguageUseDB ) {
389 $fields[] = 'page_lang';
390 }
391
392 return $fields;
393 }
394
402 public static function newFromID( $id, $flags = 0 ) {
403 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
404 $row = $db->selectRow(
405 'page',
406 self::getSelectFields(),
407 [ 'page_id' => $id ],
408 __METHOD__
409 );
410 if ( $row !== false ) {
411 $title = Title::newFromRow( $row );
412 } else {
413 $title = null;
414 }
415 return $title;
416 }
417
424 public static function newFromIDs( $ids ) {
425 if ( !count( $ids ) ) {
426 return [];
427 }
429
430 $res = $dbr->select(
431 'page',
432 self::getSelectFields(),
433 [ 'page_id' => $ids ],
434 __METHOD__
435 );
436
437 $titles = [];
438 foreach ( $res as $row ) {
439 $titles[] = Title::newFromRow( $row );
440 }
441 return $titles;
442 }
443
450 public static function newFromRow( $row ) {
451 $t = self::makeTitle( $row->page_namespace, $row->page_title );
452 $t->loadFromRow( $row );
453 return $t;
454 }
455
462 public function loadFromRow( $row ) {
463 if ( $row ) { // page found
464 if ( isset( $row->page_id ) ) {
465 $this->mArticleID = (int)$row->page_id;
466 }
467 if ( isset( $row->page_len ) ) {
468 $this->mLength = (int)$row->page_len;
469 }
470 if ( isset( $row->page_is_redirect ) ) {
471 $this->mRedirect = (bool)$row->page_is_redirect;
472 }
473 if ( isset( $row->page_latest ) ) {
474 $this->mLatestID = (int)$row->page_latest;
475 }
476 if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
477 $this->mContentModel = strval( $row->page_content_model );
478 } elseif ( !$this->mForcedContentModel ) {
479 $this->mContentModel = false; # initialized lazily in getContentModel()
480 }
481 if ( isset( $row->page_lang ) ) {
482 $this->mDbPageLanguage = (string)$row->page_lang;
483 }
484 if ( isset( $row->page_restrictions ) ) {
485 $this->mOldRestrictions = $row->page_restrictions;
486 }
487 } else { // page not found
488 $this->mArticleID = 0;
489 $this->mLength = 0;
490 $this->mRedirect = false;
491 $this->mLatestID = 0;
492 if ( !$this->mForcedContentModel ) {
493 $this->mContentModel = false; # initialized lazily in getContentModel()
494 }
495 }
496 }
497
511 public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
512 $t = new Title();
513 $t->mInterwiki = $interwiki;
514 $t->mFragment = $fragment;
515 $t->mNamespace = $ns = intval( $ns );
516 $t->mDbkeyform = strtr( $title, ' ', '_' );
517 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
518 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
519 $t->mTextform = strtr( $title, '_', ' ' );
520 $t->mContentModel = false; # initialized lazily in getContentModel()
521 return $t;
522 }
523
535 public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
536 if ( !MWNamespace::exists( $ns ) ) {
537 return null;
538 }
539
540 $t = new Title();
541 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
542
543 try {
544 $t->secureAndSplit();
545 return $t;
546 } catch ( MalformedTitleException $ex ) {
547 return null;
548 }
549 }
550
556 public static function newMainPage() {
557 $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
558 // Don't give fatal errors if the message is broken
559 if ( !$title ) {
560 $title = Title::newFromText( 'Main Page' );
561 }
562 return $title;
563 }
564
571 public static function nameOf( $id ) {
573
574 $s = $dbr->selectRow(
575 'page',
576 [ 'page_namespace', 'page_title' ],
577 [ 'page_id' => $id ],
578 __METHOD__
579 );
580 if ( $s === false ) {
581 return null;
582 }
583
584 $n = self::makeName( $s->page_namespace, $s->page_title );
585 return $n;
586 }
587
593 public static function legalChars() {
595 return $wgLegalTitleChars;
596 }
597
607 static function getTitleInvalidRegex() {
608 wfDeprecated( __METHOD__, '1.25' );
610 }
611
621 public static function convertByteClassToUnicodeClass( $byteClass ) {
622 $length = strlen( $byteClass );
623 // Input token queue
624 $x0 = $x1 = $x2 = '';
625 // Decoded queue
626 $d0 = $d1 = $d2 = '';
627 // Decoded integer codepoints
628 $ord0 = $ord1 = $ord2 = 0;
629 // Re-encoded queue
630 $r0 = $r1 = $r2 = '';
631 // Output
632 $out = '';
633 // Flags
634 $allowUnicode = false;
635 for ( $pos = 0; $pos < $length; $pos++ ) {
636 // Shift the queues down
637 $x2 = $x1;
638 $x1 = $x0;
639 $d2 = $d1;
640 $d1 = $d0;
641 $ord2 = $ord1;
642 $ord1 = $ord0;
643 $r2 = $r1;
644 $r1 = $r0;
645 // Load the current input token and decoded values
646 $inChar = $byteClass[$pos];
647 if ( $inChar == '\\' ) {
648 if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
649 $x0 = $inChar . $m[0];
650 $d0 = chr( hexdec( $m[1] ) );
651 $pos += strlen( $m[0] );
652 } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
653 $x0 = $inChar . $m[0];
654 $d0 = chr( octdec( $m[0] ) );
655 $pos += strlen( $m[0] );
656 } elseif ( $pos + 1 >= $length ) {
657 $x0 = $d0 = '\\';
658 } else {
659 $d0 = $byteClass[$pos + 1];
660 $x0 = $inChar . $d0;
661 $pos += 1;
662 }
663 } else {
664 $x0 = $d0 = $inChar;
665 }
666 $ord0 = ord( $d0 );
667 // Load the current re-encoded value
668 if ( $ord0 < 32 || $ord0 == 0x7f ) {
669 $r0 = sprintf( '\x%02x', $ord0 );
670 } elseif ( $ord0 >= 0x80 ) {
671 // Allow unicode if a single high-bit character appears
672 $r0 = sprintf( '\x%02x', $ord0 );
673 $allowUnicode = true;
674 } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
675 $r0 = '\\' . $d0;
676 } else {
677 $r0 = $d0;
678 }
679 // Do the output
680 if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
681 // Range
682 if ( $ord2 > $ord0 ) {
683 // Empty range
684 } elseif ( $ord0 >= 0x80 ) {
685 // Unicode range
686 $allowUnicode = true;
687 if ( $ord2 < 0x80 ) {
688 // Keep the non-unicode section of the range
689 $out .= "$r2-\\x7F";
690 }
691 } else {
692 // Normal range
693 $out .= "$r2-$r0";
694 }
695 // Reset state to the initial value
696 $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
697 } elseif ( $ord2 < 0x80 ) {
698 // ASCII character
699 $out .= $r2;
700 }
701 }
702 if ( $ord1 < 0x80 ) {
703 $out .= $r1;
704 }
705 if ( $ord0 < 0x80 ) {
706 $out .= $r0;
707 }
708 if ( $allowUnicode ) {
709 $out .= '\u0080-\uFFFF';
710 }
711 return $out;
712 }
713
725 public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
726 $canonicalNamespace = false
727 ) {
729
730 if ( $canonicalNamespace ) {
731 $namespace = MWNamespace::getCanonicalName( $ns );
732 } else {
733 $namespace = $wgContLang->getNsText( $ns );
734 }
735 $name = $namespace == '' ? $title : "$namespace:$title";
736 if ( strval( $interwiki ) != '' ) {
737 $name = "$interwiki:$name";
738 }
739 if ( strval( $fragment ) != '' ) {
740 $name .= '#' . $fragment;
741 }
742 return $name;
743 }
744
751 static function escapeFragmentForURL( $fragment ) {
752 # Note that we don't urlencode the fragment. urlencoded Unicode
753 # fragments appear not to work in IE (at least up to 7) or in at least
754 # one version of Opera 9.x. The W3C validator, for one, doesn't seem
755 # to care if they aren't encoded.
756 return Sanitizer::escapeId( $fragment, 'noninitial' );
757 }
758
767 public static function compare( LinkTarget $a, LinkTarget $b ) {
768 if ( $a->getNamespace() == $b->getNamespace() ) {
769 return strcmp( $a->getText(), $b->getText() );
770 } else {
771 return $a->getNamespace() - $b->getNamespace();
772 }
773 }
774
782 public function isLocal() {
783 if ( $this->isExternal() ) {
784 $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
785 if ( $iw ) {
786 return $iw->isLocal();
787 }
788 }
789 return true;
790 }
791
797 public function isExternal() {
798 return $this->mInterwiki !== '';
799 }
800
808 public function getInterwiki() {
809 return $this->mInterwiki;
810 }
811
817 public function wasLocalInterwiki() {
818 return $this->mLocalInterwiki;
819 }
820
827 public function isTrans() {
828 if ( !$this->isExternal() ) {
829 return false;
830 }
831
832 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
833 }
834
840 public function getTransWikiID() {
841 if ( !$this->isExternal() ) {
842 return false;
843 }
844
845 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
846 }
847
857 public function getTitleValue() {
858 if ( $this->mTitleValue === null ) {
859 try {
860 $this->mTitleValue = new TitleValue(
861 $this->getNamespace(),
862 $this->getDBkey(),
863 $this->getFragment(),
864 $this->getInterwiki()
865 );
866 } catch ( InvalidArgumentException $ex ) {
867 wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
868 $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
869 }
870 }
871
872 return $this->mTitleValue;
873 }
874
880 public function getText() {
881 return $this->mTextform;
882 }
883
889 public function getPartialURL() {
890 return $this->mUrlform;
891 }
892
898 public function getDBkey() {
899 return $this->mDbkeyform;
900 }
901
907 function getUserCaseDBKey() {
908 if ( !is_null( $this->mUserCaseDBKey ) ) {
909 return $this->mUserCaseDBKey;
910 } else {
911 // If created via makeTitle(), $this->mUserCaseDBKey is not set.
912 return $this->mDbkeyform;
913 }
914 }
915
921 public function getNamespace() {
922 return $this->mNamespace;
923 }
924
931 public function getContentModel( $flags = 0 ) {
932 if ( !$this->mForcedContentModel
933 && ( !$this->mContentModel || $flags === Title::GAID_FOR_UPDATE )
934 && $this->getArticleID( $flags )
935 ) {
936 $linkCache = LinkCache::singleton();
937 $linkCache->addLinkObj( $this ); # in case we already had an article ID
938 $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
939 }
940
941 if ( !$this->mContentModel ) {
942 $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
943 }
944
945 return $this->mContentModel;
946 }
947
954 public function hasContentModel( $id ) {
955 return $this->getContentModel() == $id;
956 }
957
969 public function setContentModel( $model ) {
970 $this->mContentModel = $model;
971 $this->mForcedContentModel = true;
972 }
973
979 public function getNsText() {
980 if ( $this->isExternal() ) {
981 // This probably shouldn't even happen,
982 // but for interwiki transclusion it sometimes does.
983 // Use the canonical namespaces if possible to try to
984 // resolve a foreign namespace.
985 if ( MWNamespace::exists( $this->mNamespace ) ) {
986 return MWNamespace::getCanonicalName( $this->mNamespace );
987 }
988 }
989
990 try {
991 $formatter = self::getTitleFormatter();
992 return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
993 } catch ( InvalidArgumentException $ex ) {
994 wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
995 return false;
996 }
997 }
998
1004 public function getSubjectNsText() {
1006 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1007 }
1008
1014 public function getTalkNsText() {
1016 return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1017 }
1018
1024 public function canTalk() {
1025 return MWNamespace::canTalk( $this->mNamespace );
1026 }
1027
1033 public function canExist() {
1034 return $this->mNamespace >= NS_MAIN;
1035 }
1036
1042 public function isWatchable() {
1043 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
1044 }
1045
1051 public function isSpecialPage() {
1052 return $this->getNamespace() == NS_SPECIAL;
1053 }
1054
1061 public function isSpecial( $name ) {
1062 if ( $this->isSpecialPage() ) {
1063 list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
1064 if ( $name == $thisName ) {
1065 return true;
1066 }
1067 }
1068 return false;
1069 }
1070
1077 public function fixSpecialName() {
1078 if ( $this->isSpecialPage() ) {
1079 list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
1080 if ( $canonicalName ) {
1081 $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
1082 if ( $localName != $this->mDbkeyform ) {
1083 return Title::makeTitle( NS_SPECIAL, $localName );
1084 }
1085 }
1086 }
1087 return $this;
1088 }
1089
1100 public function inNamespace( $ns ) {
1101 return MWNamespace::equals( $this->getNamespace(), $ns );
1102 }
1103
1111 public function inNamespaces( /* ... */ ) {
1112 $namespaces = func_get_args();
1113 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1115 }
1116
1117 foreach ( $namespaces as $ns ) {
1118 if ( $this->inNamespace( $ns ) ) {
1119 return true;
1120 }
1121 }
1122
1123 return false;
1124 }
1125
1139 public function hasSubjectNamespace( $ns ) {
1140 return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
1141 }
1142
1150 public function isContentPage() {
1151 return MWNamespace::isContent( $this->getNamespace() );
1152 }
1153
1160 public function isMovable() {
1161 if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
1162 // Interwiki title or immovable namespace. Hooks don't get to override here
1163 return false;
1164 }
1165
1166 $result = true;
1167 Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1168 return $result;
1169 }
1170
1181 public function isMainPage() {
1182 return $this->equals( Title::newMainPage() );
1183 }
1184
1190 public function isSubpage() {
1191 return MWNamespace::hasSubpages( $this->mNamespace )
1192 ? strpos( $this->getText(), '/' ) !== false
1193 : false;
1194 }
1195
1201 public function isConversionTable() {
1202 // @todo ConversionTable should become a separate content model.
1203
1204 return $this->getNamespace() == NS_MEDIAWIKI &&
1205 strpos( $this->getText(), 'Conversiontable/' ) === 0;
1206 }
1207
1213 public function isWikitextPage() {
1214 return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1215 }
1216
1231 public function isCssOrJsPage() {
1232 $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
1233 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1235
1236 # @note This hook is also called in ContentHandler::getDefaultModel.
1237 # It's called here again to make sure hook functions can force this
1238 # method to return true even outside the MediaWiki namespace.
1239
1240 Hooks::run( 'TitleIsCssOrJsPage', [ $this, &$isCssOrJsPage ], '1.25' );
1241
1242 return $isCssOrJsPage;
1243 }
1244
1250 public function isCssJsSubpage() {
1251 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1252 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1254 }
1255
1261 public function getSkinFromCssJsSubpage() {
1262 $subpage = explode( '/', $this->mTextform );
1263 $subpage = $subpage[count( $subpage ) - 1];
1264 $lastdot = strrpos( $subpage, '.' );
1265 if ( $lastdot === false ) {
1266 return $subpage; # Never happens: only called for names ending in '.css' or '.js'
1267 }
1268 return substr( $subpage, 0, $lastdot );
1269 }
1270
1276 public function isCssSubpage() {
1277 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1278 && $this->hasContentModel( CONTENT_MODEL_CSS ) );
1279 }
1280
1286 public function isJsSubpage() {
1287 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1289 }
1290
1296 public function isTalkPage() {
1297 return MWNamespace::isTalk( $this->getNamespace() );
1298 }
1299
1305 public function getTalkPage() {
1306 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
1307 }
1308
1315 public function getSubjectPage() {
1316 // Is this the same title?
1317 $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
1318 if ( $this->getNamespace() == $subjectNS ) {
1319 return $this;
1320 }
1321 return Title::makeTitle( $subjectNS, $this->getDBkey() );
1322 }
1323
1332 public function getOtherPage() {
1333 if ( $this->isSpecialPage() ) {
1334 throw new MWException( 'Special pages cannot have other pages' );
1335 }
1336 if ( $this->isTalkPage() ) {
1337 return $this->getSubjectPage();
1338 } else {
1339 return $this->getTalkPage();
1340 }
1341 }
1342
1348 public function getDefaultNamespace() {
1349 return $this->mDefaultNamespace;
1350 }
1351
1359 public function getFragment() {
1360 return $this->mFragment;
1361 }
1362
1369 public function hasFragment() {
1370 return $this->mFragment !== '';
1371 }
1372
1377 public function getFragmentForURL() {
1378 if ( !$this->hasFragment() ) {
1379 return '';
1380 } else {
1381 return '#' . Title::escapeFragmentForURL( $this->getFragment() );
1382 }
1383 }
1384
1397 public function setFragment( $fragment ) {
1398 $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1399 }
1400
1408 public function createFragmentTarget( $fragment ) {
1409 return self::makeTitle(
1410 $this->getNamespace(),
1411 $this->getText(),
1412 $fragment,
1413 $this->getInterwiki()
1414 );
1415
1416 }
1417
1425 private function prefix( $name ) {
1426 $p = '';
1427 if ( $this->isExternal() ) {
1428 $p = $this->mInterwiki . ':';
1429 }
1430
1431 if ( 0 != $this->mNamespace ) {
1432 $p .= $this->getNsText() . ':';
1433 }
1434 return $p . $name;
1435 }
1436
1443 public function getPrefixedDBkey() {
1444 $s = $this->prefix( $this->mDbkeyform );
1445 $s = strtr( $s, ' ', '_' );
1446 return $s;
1447 }
1448
1455 public function getPrefixedText() {
1456 if ( $this->mPrefixedText === null ) {
1457 $s = $this->prefix( $this->mTextform );
1458 $s = strtr( $s, '_', ' ' );
1459 $this->mPrefixedText = $s;
1460 }
1461 return $this->mPrefixedText;
1462 }
1463
1469 public function __toString() {
1470 return $this->getPrefixedText();
1471 }
1472
1479 public function getFullText() {
1480 $text = $this->getPrefixedText();
1481 if ( $this->hasFragment() ) {
1482 $text .= '#' . $this->getFragment();
1483 }
1484 return $text;
1485 }
1486
1499 public function getRootText() {
1500 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1501 return $this->getText();
1502 }
1503
1504 return strtok( $this->getText(), '/' );
1505 }
1506
1519 public function getRootTitle() {
1520 return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
1521 }
1522
1534 public function getBaseText() {
1535 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1536 return $this->getText();
1537 }
1538
1539 $parts = explode( '/', $this->getText() );
1540 # Don't discard the real title if there's no subpage involved
1541 if ( count( $parts ) > 1 ) {
1542 unset( $parts[count( $parts ) - 1] );
1543 }
1544 return implode( '/', $parts );
1545 }
1546
1559 public function getBaseTitle() {
1560 return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
1561 }
1562
1574 public function getSubpageText() {
1575 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1576 return $this->mTextform;
1577 }
1578 $parts = explode( '/', $this->mTextform );
1579 return $parts[count( $parts ) - 1];
1580 }
1581
1595 public function getSubpage( $text ) {
1596 return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
1597 }
1598
1604 public function getSubpageUrlForm() {
1605 $text = $this->getSubpageText();
1606 $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1607 return $text;
1608 }
1609
1615 public function getPrefixedURL() {
1616 $s = $this->prefix( $this->mDbkeyform );
1617 $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1618 return $s;
1619 }
1620
1634 private static function fixUrlQueryArgs( $query, $query2 = false ) {
1635 if ( $query2 !== false ) {
1636 wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1637 "method called with a second parameter is deprecated. Add your " .
1638 "parameter to an array passed as the first parameter.", "1.19" );
1639 }
1640 if ( is_array( $query ) ) {
1642 }
1643 if ( $query2 ) {
1644 if ( is_string( $query2 ) ) {
1645 // $query2 is a string, we will consider this to be
1646 // a deprecated $variant argument and add it to the query
1647 $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1648 } else {
1649 $query2 = wfArrayToCgi( $query2 );
1650 }
1651 // If we have $query content add a & to it first
1652 if ( $query ) {
1653 $query .= '&';
1654 }
1655 // Now append the queries together
1656 $query .= $query2;
1657 }
1658 return $query;
1659 }
1660
1672 public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1673 $query = self::fixUrlQueryArgs( $query, $query2 );
1674
1675 # Hand off all the decisions on urls to getLocalURL
1676 $url = $this->getLocalURL( $query );
1677
1678 # Expand the url to make it a full url. Note that getLocalURL has the
1679 # potential to output full urls for a variety of reasons, so we use
1680 # wfExpandUrl instead of simply prepending $wgServer
1681 $url = wfExpandUrl( $url, $proto );
1682
1683 # Finally, add the fragment.
1684 $url .= $this->getFragmentForURL();
1685
1686 Hooks::run( 'GetFullURL', [ &$this, &$url, $query ] );
1687 return $url;
1688 }
1689
1706 public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1707 $target = $this;
1708 if ( $this->isExternal() ) {
1709 $target = SpecialPage::getTitleFor(
1710 'GoToInterwiki',
1711 $this->getPrefixedDBKey()
1712 );
1713 }
1714 return $target->getFullUrl( $query, false, $proto );
1715 }
1716
1740 public function getLocalURL( $query = '', $query2 = false ) {
1742
1743 $query = self::fixUrlQueryArgs( $query, $query2 );
1744
1745 $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1746 if ( $interwiki ) {
1747 $namespace = $this->getNsText();
1748 if ( $namespace != '' ) {
1749 # Can this actually happen? Interwikis shouldn't be parsed.
1750 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1751 $namespace .= ':';
1752 }
1753 $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1754 $url = wfAppendQuery( $url, $query );
1755 } else {
1756 $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1757 if ( $query == '' ) {
1758 $url = str_replace( '$1', $dbkey, $wgArticlePath );
1759 Hooks::run( 'GetLocalURL::Article', [ &$this, &$url ] );
1760 } else {
1762 $url = false;
1763 $matches = [];
1764
1765 if ( !empty( $wgActionPaths )
1766 && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1767 ) {
1768 $action = urldecode( $matches[2] );
1769 if ( isset( $wgActionPaths[$action] ) ) {
1770 $query = $matches[1];
1771 if ( isset( $matches[4] ) ) {
1772 $query .= $matches[4];
1773 }
1774 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1775 if ( $query != '' ) {
1776 $url = wfAppendQuery( $url, $query );
1777 }
1778 }
1779 }
1780
1781 if ( $url === false
1783 && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1784 && $this->getPageLanguage()->equals( $wgContLang )
1785 && $this->getPageLanguage()->hasVariants()
1786 ) {
1787 $variant = urldecode( $matches[1] );
1788 if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1789 // Only do the variant replacement if the given variant is a valid
1790 // variant for the page's language.
1791 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1792 $url = str_replace( '$1', $dbkey, $url );
1793 }
1794 }
1795
1796 if ( $url === false ) {
1797 if ( $query == '-' ) {
1798 $query = '';
1799 }
1800 $url = "{$wgScript}?title={$dbkey}&{$query}";
1801 }
1802 }
1803
1804 Hooks::run( 'GetLocalURL::Internal', [ &$this, &$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 Hooks::run( 'GetLocalURL', [ &$this, &$url, $query ] );
1813 return $url;
1814 }
1815
1833 public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
1834 if ( $this->isExternal() || $proto !== false ) {
1835 $ret = $this->getFullURL( $query, $query2, $proto );
1836 } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
1837 $ret = $this->getFragmentForURL();
1838 } else {
1839 $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
1840 }
1841 return $ret;
1842 }
1843
1856 public function getInternalURL( $query = '', $query2 = false ) {
1858 $query = self::fixUrlQueryArgs( $query, $query2 );
1859 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
1860 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
1861 Hooks::run( 'GetInternalURL', [ &$this, &$url, $query ] );
1862 return $url;
1863 }
1864
1876 public function getCanonicalURL( $query = '', $query2 = false ) {
1877 $query = self::fixUrlQueryArgs( $query, $query2 );
1878 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
1879 Hooks::run( 'GetCanonicalURL', [ &$this, &$url, $query ] );
1880 return $url;
1881 }
1882
1888 public function getEditURL() {
1889 if ( $this->isExternal() ) {
1890 return '';
1891 }
1892 $s = $this->getLocalURL( 'action=edit' );
1893
1894 return $s;
1895 }
1896
1911 public function quickUserCan( $action, $user = null ) {
1912 return $this->userCan( $action, $user, false );
1913 }
1914
1924 public function userCan( $action, $user = null, $rigor = 'secure' ) {
1925 if ( !$user instanceof User ) {
1927 $user = $wgUser;
1928 }
1929
1930 return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
1931 }
1932
1949 $action, $user, $rigor = 'secure', $ignoreErrors = []
1950 ) {
1951 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
1952
1953 // Remove the errors being ignored.
1954 foreach ( $errors as $index => $error ) {
1955 $errKey = is_array( $error ) ? $error[0] : $error;
1956
1957 if ( in_array( $errKey, $ignoreErrors ) ) {
1958 unset( $errors[$index] );
1959 }
1960 if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
1961 unset( $errors[$index] );
1962 }
1963 }
1964
1965 return $errors;
1966 }
1967
1979 private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
1980 if ( !Hooks::run( 'TitleQuickPermissions',
1981 [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
1982 ) {
1983 return $errors;
1984 }
1985
1986 if ( $action == 'create' ) {
1987 if (
1988 ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1989 ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
1990 ) {
1991 $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
1992 }
1993 } elseif ( $action == 'move' ) {
1994 if ( !$user->isAllowed( 'move-rootuserpages' )
1995 && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1996 // Show user page-specific message only if the user can move other pages
1997 $errors[] = [ 'cant-move-user-page' ];
1998 }
1999
2000 // Check if user is allowed to move files if it's a file
2001 if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
2002 $errors[] = [ 'movenotallowedfile' ];
2003 }
2004
2005 // Check if user is allowed to move category pages if it's a category page
2006 if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
2007 $errors[] = [ 'cant-move-category-page' ];
2008 }
2009
2010 if ( !$user->isAllowed( 'move' ) ) {
2011 // User can't move anything
2012 $userCanMove = User::groupHasPermission( 'user', 'move' );
2013 $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
2014 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
2015 // custom message if logged-in users without any special rights can move
2016 $errors[] = [ 'movenologintext' ];
2017 } else {
2018 $errors[] = [ 'movenotallowed' ];
2019 }
2020 }
2021 } elseif ( $action == 'move-target' ) {
2022 if ( !$user->isAllowed( 'move' ) ) {
2023 // User can't move anything
2024 $errors[] = [ 'movenotallowed' ];
2025 } elseif ( !$user->isAllowed( 'move-rootuserpages' )
2026 && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2027 // Show user page-specific message only if the user can move other pages
2028 $errors[] = [ 'cant-move-to-user-page' ];
2029 } elseif ( !$user->isAllowed( 'move-categorypages' )
2030 && $this->mNamespace == NS_CATEGORY ) {
2031 // Show category page-specific message only if the user can move other pages
2032 $errors[] = [ 'cant-move-to-category-page' ];
2033 }
2034 } elseif ( !$user->isAllowed( $action ) ) {
2035 $errors[] = $this->missingPermissionError( $action, $short );
2036 }
2037
2038 return $errors;
2039 }
2040
2049 private function resultToError( $errors, $result ) {
2050 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2051 // A single array representing an error
2052 $errors[] = $result;
2053 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2054 // A nested array representing multiple errors
2055 $errors = array_merge( $errors, $result );
2056 } elseif ( $result !== '' && is_string( $result ) ) {
2057 // A string representing a message-id
2058 $errors[] = [ $result ];
2059 } elseif ( $result instanceof MessageSpecifier ) {
2060 // A message specifier representing an error
2061 $errors[] = [ $result ];
2062 } elseif ( $result === false ) {
2063 // a generic "We don't want them to do that"
2064 $errors[] = [ 'badaccess-group0' ];
2065 }
2066 return $errors;
2067 }
2068
2080 private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2081 // Use getUserPermissionsErrors instead
2082 $result = '';
2083 if ( !Hooks::run( 'userCan', [ &$this, &$user, $action, &$result ] ) ) {
2084 return $result ? [] : [ [ 'badaccess-group0' ] ];
2085 }
2086 // Check getUserPermissionsErrors hook
2087 if ( !Hooks::run( 'getUserPermissionsErrors', [ &$this, &$user, $action, &$result ] ) ) {
2088 $errors = $this->resultToError( $errors, $result );
2089 }
2090 // Check getUserPermissionsErrorsExpensive hook
2091 if (
2092 $rigor !== 'quick'
2093 && !( $short && count( $errors ) > 0 )
2094 && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$this, &$user, $action, &$result ] )
2095 ) {
2096 $errors = $this->resultToError( $errors, $result );
2097 }
2098
2099 return $errors;
2100 }
2101
2113 private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2114 # Only 'createaccount' can be performed on special pages,
2115 # which don't actually exist in the DB.
2116 if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
2117 $errors[] = [ 'ns-specialprotected' ];
2118 }
2119
2120 # Check $wgNamespaceProtection for restricted namespaces
2121 if ( $this->isNamespaceProtected( $user ) ) {
2122 $ns = $this->mNamespace == NS_MAIN ?
2123 wfMessage( 'nstab-main' )->text() : $this->getNsText();
2124 $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2125 [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2126 }
2127
2128 return $errors;
2129 }
2130
2142 private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
2143 # Protect css/js subpages of user pages
2144 # XXX: this might be better using restrictions
2145 # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
2146 if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
2147 if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2148 if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
2149 $errors[] = [ 'mycustomcssprotected', $action ];
2150 } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
2151 $errors[] = [ 'mycustomjsprotected', $action ];
2152 }
2153 } else {
2154 if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
2155 $errors[] = [ 'customcssprotected', $action ];
2156 } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
2157 $errors[] = [ 'customjsprotected', $action ];
2158 }
2159 }
2160 }
2161
2162 return $errors;
2163 }
2164
2178 private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2179 foreach ( $this->getRestrictions( $action ) as $right ) {
2180 // Backwards compatibility, rewrite sysop -> editprotected
2181 if ( $right == 'sysop' ) {
2182 $right = 'editprotected';
2183 }
2184 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2185 if ( $right == 'autoconfirmed' ) {
2186 $right = 'editsemiprotected';
2187 }
2188 if ( $right == '' ) {
2189 continue;
2190 }
2191 if ( !$user->isAllowed( $right ) ) {
2192 $errors[] = [ 'protectedpagetext', $right, $action ];
2193 } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2194 $errors[] = [ 'protectedpagetext', 'protect', $action ];
2195 }
2196 }
2197
2198 return $errors;
2199 }
2200
2212 private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2213 if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
2214 # We /could/ use the protection level on the source page, but it's
2215 # fairly ugly as we have to establish a precedence hierarchy for pages
2216 # included by multiple cascade-protected pages. So just restrict
2217 # it to people with 'protect' permission, as they could remove the
2218 # protection anyway.
2219 list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2220 # Cascading protection depends on more than this page...
2221 # Several cascading protected pages may include this page...
2222 # Check each cascading level
2223 # This is only for protection restrictions, not for all actions
2224 if ( isset( $restrictions[$action] ) ) {
2225 foreach ( $restrictions[$action] as $right ) {
2226 // Backwards compatibility, rewrite sysop -> editprotected
2227 if ( $right == 'sysop' ) {
2228 $right = 'editprotected';
2229 }
2230 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2231 if ( $right == 'autoconfirmed' ) {
2232 $right = 'editsemiprotected';
2233 }
2234 if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2235 $pages = '';
2236 foreach ( $cascadingSources as $page ) {
2237 $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2238 }
2239 $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2240 }
2241 }
2242 }
2243 }
2244
2245 return $errors;
2246 }
2247
2259 private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2261
2262 if ( $action == 'protect' ) {
2263 if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2264 // If they can't edit, they shouldn't protect.
2265 $errors[] = [ 'protect-cantedit' ];
2266 }
2267 } elseif ( $action == 'create' ) {
2268 $title_protection = $this->getTitleProtection();
2269 if ( $title_protection ) {
2270 if ( $title_protection['permission'] == ''
2271 || !$user->isAllowed( $title_protection['permission'] )
2272 ) {
2273 $errors[] = [
2274 'titleprotected',
2275 User::whoIs( $title_protection['user'] ),
2276 $title_protection['reason']
2277 ];
2278 }
2279 }
2280 } elseif ( $action == 'move' ) {
2281 // Check for immobile pages
2282 if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2283 // Specific message for this case
2284 $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2285 } elseif ( !$this->isMovable() ) {
2286 // Less specific message for rarer cases
2287 $errors[] = [ 'immobile-source-page' ];
2288 }
2289 } elseif ( $action == 'move-target' ) {
2290 if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2291 $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2292 } elseif ( !$this->isMovable() ) {
2293 $errors[] = [ 'immobile-target-page' ];
2294 }
2295 } elseif ( $action == 'delete' ) {
2296 $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2297 if ( !$tempErrors ) {
2298 $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2299 $user, $tempErrors, $rigor, true );
2300 }
2301 if ( $tempErrors ) {
2302 // If protection keeps them from editing, they shouldn't be able to delete.
2303 $errors[] = [ 'deleteprotected' ];
2304 }
2305 if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2306 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2307 ) {
2308 $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2309 }
2310 } elseif ( $action === 'undelete' ) {
2311 if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2312 // Undeleting implies editing
2313 $errors[] = [ 'undelete-cantedit' ];
2314 }
2315 if ( !$this->exists()
2316 && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
2317 ) {
2318 // Undeleting where nothing currently exists implies creating
2319 $errors[] = [ 'undelete-cantcreate' ];
2320 }
2321 }
2322 return $errors;
2323 }
2324
2336 private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2338 // Account creation blocks handled at userlogin.
2339 // Unblocking handled in SpecialUnblock
2340 if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2341 return $errors;
2342 }
2343
2344 // Optimize for a very common case
2345 if ( $action === 'read' && !$wgBlockDisablesLogin ) {
2346 return $errors;
2347 }
2348
2349 if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
2350 $errors[] = [ 'confirmedittext' ];
2351 }
2352
2353 $useSlave = ( $rigor !== 'secure' );
2354 if ( ( $action == 'edit' || $action == 'create' )
2355 && !$user->isBlockedFrom( $this, $useSlave )
2356 ) {
2357 // Don't block the user from editing their own talk page unless they've been
2358 // explicitly blocked from that too.
2359 } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2360 // @todo FIXME: Pass the relevant context into this function.
2361 $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2362 }
2363
2364 return $errors;
2365 }
2366
2378 private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2380
2381 $whitelisted = false;
2382 if ( User::isEveryoneAllowed( 'read' ) ) {
2383 # Shortcut for public wikis, allows skipping quite a bit of code
2384 $whitelisted = true;
2385 } elseif ( $user->isAllowed( 'read' ) ) {
2386 # If the user is allowed to read pages, he is allowed to read all pages
2387 $whitelisted = true;
2388 } elseif ( $this->isSpecial( 'Userlogin' )
2389 || $this->isSpecial( 'PasswordReset' )
2390 || $this->isSpecial( 'Userlogout' )
2391 ) {
2392 # Always grant access to the login page.
2393 # Even anons need to be able to log in.
2394 $whitelisted = true;
2395 } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2396 # Time to check the whitelist
2397 # Only do these checks is there's something to check against
2398 $name = $this->getPrefixedText();
2399 $dbName = $this->getPrefixedDBkey();
2400
2401 // Check for explicit whitelisting with and without underscores
2402 if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2403 $whitelisted = true;
2404 } elseif ( $this->getNamespace() == NS_MAIN ) {
2405 # Old settings might have the title prefixed with
2406 # a colon for main-namespace pages
2407 if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2408 $whitelisted = true;
2409 }
2410 } elseif ( $this->isSpecialPage() ) {
2411 # If it's a special page, ditch the subpage bit and check again
2412 $name = $this->getDBkey();
2413 list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2414 if ( $name ) {
2415 $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2416 if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2417 $whitelisted = true;
2418 }
2419 }
2420 }
2421 }
2422
2423 if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2424 $name = $this->getPrefixedText();
2425 // Check for regex whitelisting
2426 foreach ( $wgWhitelistReadRegexp as $listItem ) {
2427 if ( preg_match( $listItem, $name ) ) {
2428 $whitelisted = true;
2429 break;
2430 }
2431 }
2432 }
2433
2434 if ( !$whitelisted ) {
2435 # If the title is not whitelisted, give extensions a chance to do so...
2436 Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2437 if ( !$whitelisted ) {
2438 $errors[] = $this->missingPermissionError( $action, $short );
2439 }
2440 }
2441
2442 return $errors;
2443 }
2444
2453 private function missingPermissionError( $action, $short ) {
2454 // We avoid expensive display logic for quickUserCan's and such
2455 if ( $short ) {
2456 return [ 'badaccess-group0' ];
2457 }
2458
2459 $groups = array_map( [ 'User', 'makeGroupLinkWiki' ],
2460 User::getGroupsWithPermission( $action ) );
2461
2462 if ( count( $groups ) ) {
2464 return [
2465 'badaccess-groups',
2466 $wgLang->commaList( $groups ),
2467 count( $groups )
2468 ];
2469 } else {
2470 return [ 'badaccess-group0' ];
2471 }
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 // Can't protect pages in special namespaces
2596 if ( $this->getNamespace() < 0 ) {
2597 return false;
2598 }
2599
2600 // Can't protect pages that exist.
2601 if ( $this->exists() ) {
2602 return false;
2603 }
2604
2605 if ( $this->mTitleProtection === null ) {
2606 $dbr = wfGetDB( DB_REPLICA );
2607 $res = $dbr->select(
2608 'protected_titles',
2609 [
2610 'user' => 'pt_user',
2611 'reason' => 'pt_reason',
2612 'expiry' => 'pt_expiry',
2613 'permission' => 'pt_create_perm'
2614 ],
2615 [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2616 __METHOD__
2617 );
2618
2619 // fetchRow returns false if there are no rows.
2620 $row = $dbr->fetchRow( $res );
2621 if ( $row ) {
2622 if ( $row['permission'] == 'sysop' ) {
2623 $row['permission'] = 'editprotected'; // B/C
2624 }
2625 if ( $row['permission'] == 'autoconfirmed' ) {
2626 $row['permission'] = 'editsemiprotected'; // B/C
2627 }
2628 $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2629 }
2630 $this->mTitleProtection = $row;
2631 }
2632 return $this->mTitleProtection;
2633 }
2634
2638 public function deleteTitleProtection() {
2639 $dbw = wfGetDB( DB_MASTER );
2640
2641 $dbw->delete(
2642 'protected_titles',
2643 [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2644 __METHOD__
2645 );
2646 $this->mTitleProtection = false;
2647 }
2648
2656 public function isSemiProtected( $action = 'edit' ) {
2658
2659 $restrictions = $this->getRestrictions( $action );
2661 if ( !$restrictions || !$semi ) {
2662 // Not protected, or all protection is full protection
2663 return false;
2664 }
2665
2666 // Remap autoconfirmed to editsemiprotected for BC
2667 foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2668 $semi[$key] = 'editsemiprotected';
2669 }
2670 foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2671 $restrictions[$key] = 'editsemiprotected';
2672 }
2673
2674 return !array_diff( $restrictions, $semi );
2675 }
2676
2684 public function isProtected( $action = '' ) {
2686
2687 $restrictionTypes = $this->getRestrictionTypes();
2688
2689 # Special pages have inherent protection
2690 if ( $this->isSpecialPage() ) {
2691 return true;
2692 }
2693
2694 # Check regular protection levels
2695 foreach ( $restrictionTypes as $type ) {
2696 if ( $action == $type || $action == '' ) {
2697 $r = $this->getRestrictions( $type );
2698 foreach ( $wgRestrictionLevels as $level ) {
2699 if ( in_array( $level, $r ) && $level != '' ) {
2700 return true;
2701 }
2702 }
2703 }
2704 }
2705
2706 return false;
2707 }
2708
2716 public function isNamespaceProtected( User $user ) {
2718
2719 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2720 foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2721 if ( $right != '' && !$user->isAllowed( $right ) ) {
2722 return true;
2723 }
2724 }
2725 }
2726 return false;
2727 }
2728
2734 public function isCascadeProtected() {
2735 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2736 return ( $sources > 0 );
2737 }
2738
2748 public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2749 return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2750 }
2751
2765 public function getCascadeProtectionSources( $getPages = true ) {
2766 $pagerestrictions = [];
2767
2768 if ( $this->mCascadeSources !== null && $getPages ) {
2769 return [ $this->mCascadeSources, $this->mCascadingRestrictions ];
2770 } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2771 return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2772 }
2773
2774 $dbr = wfGetDB( DB_REPLICA );
2775
2776 if ( $this->getNamespace() == NS_FILE ) {
2777 $tables = [ 'imagelinks', 'page_restrictions' ];
2778 $where_clauses = [
2779 'il_to' => $this->getDBkey(),
2780 'il_from=pr_page',
2781 'pr_cascade' => 1
2782 ];
2783 } else {
2784 $tables = [ 'templatelinks', 'page_restrictions' ];
2785 $where_clauses = [
2786 'tl_namespace' => $this->getNamespace(),
2787 'tl_title' => $this->getDBkey(),
2788 'tl_from=pr_page',
2789 'pr_cascade' => 1
2790 ];
2791 }
2792
2793 if ( $getPages ) {
2794 $cols = [ 'pr_page', 'page_namespace', 'page_title',
2795 'pr_expiry', 'pr_type', 'pr_level' ];
2796 $where_clauses[] = 'page_id=pr_page';
2797 $tables[] = 'page';
2798 } else {
2799 $cols = [ 'pr_expiry' ];
2800 }
2801
2802 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2803
2804 $sources = $getPages ? [] : false;
2805 $now = wfTimestampNow();
2806
2807 foreach ( $res as $row ) {
2808 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2809 if ( $expiry > $now ) {
2810 if ( $getPages ) {
2811 $page_id = $row->pr_page;
2812 $page_ns = $row->page_namespace;
2813 $page_title = $row->page_title;
2814 $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2815 # Add groups needed for each restriction type if its not already there
2816 # Make sure this restriction type still exists
2817
2818 if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2819 $pagerestrictions[$row->pr_type] = [];
2820 }
2821
2822 if (
2823 isset( $pagerestrictions[$row->pr_type] )
2824 && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2825 ) {
2826 $pagerestrictions[$row->pr_type][] = $row->pr_level;
2827 }
2828 } else {
2829 $sources = true;
2830 }
2831 }
2832 }
2833
2834 if ( $getPages ) {
2835 $this->mCascadeSources = $sources;
2836 $this->mCascadingRestrictions = $pagerestrictions;
2837 } else {
2838 $this->mHasCascadingRestrictions = $sources;
2839 }
2840
2841 return [ $sources, $pagerestrictions ];
2842 }
2843
2851 public function areRestrictionsLoaded() {
2852 return $this->mRestrictionsLoaded;
2853 }
2854
2864 public function getRestrictions( $action ) {
2865 if ( !$this->mRestrictionsLoaded ) {
2866 $this->loadRestrictions();
2867 }
2868 return isset( $this->mRestrictions[$action] )
2869 ? $this->mRestrictions[$action]
2870 : [];
2871 }
2872
2880 public function getAllRestrictions() {
2881 if ( !$this->mRestrictionsLoaded ) {
2882 $this->loadRestrictions();
2883 }
2884 return $this->mRestrictions;
2885 }
2886
2894 public function getRestrictionExpiry( $action ) {
2895 if ( !$this->mRestrictionsLoaded ) {
2896 $this->loadRestrictions();
2897 }
2898 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2899 }
2900
2907 if ( !$this->mRestrictionsLoaded ) {
2908 $this->loadRestrictions();
2909 }
2910
2911 return $this->mCascadeRestriction;
2912 }
2913
2923 public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2924 $dbr = wfGetDB( DB_REPLICA );
2925
2926 $restrictionTypes = $this->getRestrictionTypes();
2927
2928 foreach ( $restrictionTypes as $type ) {
2929 $this->mRestrictions[$type] = [];
2930 $this->mRestrictionsExpiry[$type] = 'infinity';
2931 }
2932
2933 $this->mCascadeRestriction = false;
2934
2935 # Backwards-compatibility: also load the restrictions from the page record (old format).
2936 if ( $oldFashionedRestrictions !== null ) {
2937 $this->mOldRestrictions = $oldFashionedRestrictions;
2938 }
2939
2940 if ( $this->mOldRestrictions === false ) {
2941 $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2942 [ 'page_id' => $this->getArticleID() ], __METHOD__ );
2943 }
2944
2945 if ( $this->mOldRestrictions != '' ) {
2946 foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2947 $temp = explode( '=', trim( $restrict ) );
2948 if ( count( $temp ) == 1 ) {
2949 // old old format should be treated as edit/move restriction
2950 $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2951 $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2952 } else {
2953 $restriction = trim( $temp[1] );
2954 if ( $restriction != '' ) { // some old entries are empty
2955 $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2956 }
2957 }
2958 }
2959 }
2960
2961 if ( count( $rows ) ) {
2962 # Current system - load second to make them override.
2963 $now = wfTimestampNow();
2964
2965 # Cycle through all the restrictions.
2966 foreach ( $rows as $row ) {
2967
2968 // Don't take care of restrictions types that aren't allowed
2969 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2970 continue;
2971 }
2972
2973 // This code should be refactored, now that it's being used more generally,
2974 // But I don't really see any harm in leaving it in Block for now -werdna
2975 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2976
2977 // Only apply the restrictions if they haven't expired!
2978 if ( !$expiry || $expiry > $now ) {
2979 $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2980 $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2981
2982 $this->mCascadeRestriction |= $row->pr_cascade;
2983 }
2984 }
2985 }
2986
2987 $this->mRestrictionsLoaded = true;
2988 }
2989
2996 public function loadRestrictions( $oldFashionedRestrictions = null ) {
2997 if ( $this->mRestrictionsLoaded ) {
2998 return;
2999 }
3000
3001 $id = $this->getArticleID();
3002 if ( $id ) {
3003 $cache = ObjectCache::getMainWANInstance();
3004 $rows = $cache->getWithSetCallback(
3005 // Page protections always leave a new null revision
3006 $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ),
3007 $cache::TTL_DAY,
3008 function ( $curValue, &$ttl, array &$setOpts ) {
3009 $dbr = wfGetDB( DB_REPLICA );
3010
3011 $setOpts += Database::getCacheSetOptions( $dbr );
3012
3013 return iterator_to_array(
3014 $dbr->select(
3015 'page_restrictions',
3016 [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
3017 [ 'pr_page' => $this->getArticleID() ],
3018 __METHOD__
3019 )
3020 );
3021 }
3022 );
3023
3024 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
3025 } else {
3026 $title_protection = $this->getTitleProtection();
3027
3028 if ( $title_protection ) {
3029 $now = wfTimestampNow();
3030 $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
3031
3032 if ( !$expiry || $expiry > $now ) {
3033 // Apply the restrictions
3034 $this->mRestrictionsExpiry['create'] = $expiry;
3035 $this->mRestrictions['create'] =
3036 explode( ',', trim( $title_protection['permission'] ) );
3037 } else { // Get rid of the old restrictions
3038 $this->mTitleProtection = false;
3039 }
3040 } else {
3041 $this->mRestrictionsExpiry['create'] = 'infinity';
3042 }
3043 $this->mRestrictionsLoaded = true;
3044 }
3045 }
3046
3051 public function flushRestrictions() {
3052 $this->mRestrictionsLoaded = false;
3053 $this->mTitleProtection = null;
3054 }
3055
3061 static function purgeExpiredRestrictions() {
3062 if ( wfReadOnly() ) {
3063 return;
3064 }
3065
3066 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
3067 wfGetDB( DB_MASTER ),
3068 __METHOD__,
3069 function ( IDatabase $dbw, $fname ) {
3070 $config = MediaWikiServices::getInstance()->getMainConfig();
3071 $ids = $dbw->selectFieldValues(
3072 'page_restrictions',
3073 'pr_id',
3074 [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3075 $fname,
3076 [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
3077 );
3078 if ( $ids ) {
3079 $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
3080 }
3081 }
3082 ) );
3083
3084 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
3085 wfGetDB( DB_MASTER ),
3086 __METHOD__,
3087 function ( IDatabase $dbw, $fname ) {
3088 $dbw->delete(
3089 'protected_titles',
3090 [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3091 $fname
3092 );
3093 }
3094 ) );
3095 }
3096
3102 public function hasSubpages() {
3103 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3104 # Duh
3105 return false;
3106 }
3107
3108 # We dynamically add a member variable for the purpose of this method
3109 # alone to cache the result. There's no point in having it hanging
3110 # around uninitialized in every Title object; therefore we only add it
3111 # if needed and don't declare it statically.
3112 if ( $this->mHasSubpages === null ) {
3113 $this->mHasSubpages = false;
3114 $subpages = $this->getSubpages( 1 );
3115 if ( $subpages instanceof TitleArray ) {
3116 $this->mHasSubpages = (bool)$subpages->count();
3117 }
3118 }
3119
3120 return $this->mHasSubpages;
3121 }
3122
3130 public function getSubpages( $limit = -1 ) {
3131 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3132 return [];
3133 }
3134
3135 $dbr = wfGetDB( DB_REPLICA );
3136 $conds['page_namespace'] = $this->getNamespace();
3137 $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3138 $options = [];
3139 if ( $limit > -1 ) {
3140 $options['LIMIT'] = $limit;
3141 }
3142 $this->mSubpages = TitleArray::newFromResult(
3143 $dbr->select( 'page',
3144 [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3145 $conds,
3146 __METHOD__,
3147 $options
3148 )
3149 );
3150 return $this->mSubpages;
3151 }
3152
3158 public function isDeleted() {
3159 if ( $this->getNamespace() < 0 ) {
3160 $n = 0;
3161 } else {
3162 $dbr = wfGetDB( DB_REPLICA );
3163
3164 $n = $dbr->selectField( 'archive', 'COUNT(*)',
3165 [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3166 __METHOD__
3167 );
3168 if ( $this->getNamespace() == NS_FILE ) {
3169 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3170 [ 'fa_name' => $this->getDBkey() ],
3171 __METHOD__
3172 );
3173 }
3174 }
3175 return (int)$n;
3176 }
3177
3183 public function isDeletedQuick() {
3184 if ( $this->getNamespace() < 0 ) {
3185 return false;
3186 }
3187 $dbr = wfGetDB( DB_REPLICA );
3188 $deleted = (bool)$dbr->selectField( 'archive', '1',
3189 [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3190 __METHOD__
3191 );
3192 if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3193 $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3194 [ 'fa_name' => $this->getDBkey() ],
3195 __METHOD__
3196 );
3197 }
3198 return $deleted;
3199 }
3200
3209 public function getArticleID( $flags = 0 ) {
3210 if ( $this->getNamespace() < 0 ) {
3211 $this->mArticleID = 0;
3212 return $this->mArticleID;
3213 }
3214 $linkCache = LinkCache::singleton();
3215 if ( $flags & self::GAID_FOR_UPDATE ) {
3216 $oldUpdate = $linkCache->forUpdate( true );
3217 $linkCache->clearLink( $this );
3218 $this->mArticleID = $linkCache->addLinkObj( $this );
3219 $linkCache->forUpdate( $oldUpdate );
3220 } else {
3221 if ( -1 == $this->mArticleID ) {
3222 $this->mArticleID = $linkCache->addLinkObj( $this );
3223 }
3224 }
3225 return $this->mArticleID;
3226 }
3227
3235 public function isRedirect( $flags = 0 ) {
3236 if ( !is_null( $this->mRedirect ) ) {
3237 return $this->mRedirect;
3238 }
3239 if ( !$this->getArticleID( $flags ) ) {
3240 $this->mRedirect = false;
3241 return $this->mRedirect;
3242 }
3243
3244 $linkCache = LinkCache::singleton();
3245 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3246 $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3247 if ( $cached === null ) {
3248 # Trust LinkCache's state over our own
3249 # LinkCache is telling us that the page doesn't exist, despite there being cached
3250 # data relating to an existing page in $this->mArticleID. Updaters should clear
3251 # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3252 # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3253 # LinkCache to refresh its data from the master.
3254 $this->mRedirect = false;
3255 return $this->mRedirect;
3256 }
3257
3258 $this->mRedirect = (bool)$cached;
3259
3260 return $this->mRedirect;
3261 }
3262
3270 public function getLength( $flags = 0 ) {
3271 if ( $this->mLength != -1 ) {
3272 return $this->mLength;
3273 }
3274 if ( !$this->getArticleID( $flags ) ) {
3275 $this->mLength = 0;
3276 return $this->mLength;
3277 }
3278 $linkCache = LinkCache::singleton();
3279 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3280 $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3281 if ( $cached === null ) {
3282 # Trust LinkCache's state over our own, as for isRedirect()
3283 $this->mLength = 0;
3284 return $this->mLength;
3285 }
3286
3287 $this->mLength = intval( $cached );
3288
3289 return $this->mLength;
3290 }
3291
3298 public function getLatestRevID( $flags = 0 ) {
3299 if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3300 return intval( $this->mLatestID );
3301 }
3302 if ( !$this->getArticleID( $flags ) ) {
3303 $this->mLatestID = 0;
3304 return $this->mLatestID;
3305 }
3306 $linkCache = LinkCache::singleton();
3307 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3308 $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3309 if ( $cached === null ) {
3310 # Trust LinkCache's state over our own, as for isRedirect()
3311 $this->mLatestID = 0;
3312 return $this->mLatestID;
3313 }
3314
3315 $this->mLatestID = intval( $cached );
3316
3317 return $this->mLatestID;
3318 }
3319
3330 public function resetArticleID( $newid ) {
3331 $linkCache = LinkCache::singleton();
3332 $linkCache->clearLink( $this );
3333
3334 if ( $newid === false ) {
3335 $this->mArticleID = -1;
3336 } else {
3337 $this->mArticleID = intval( $newid );
3338 }
3339 $this->mRestrictionsLoaded = false;
3340 $this->mRestrictions = [];
3341 $this->mOldRestrictions = false;
3342 $this->mRedirect = null;
3343 $this->mLength = -1;
3344 $this->mLatestID = false;
3345 $this->mContentModel = false;
3346 $this->mEstimateRevisions = null;
3347 $this->mPageLanguage = false;
3348 $this->mDbPageLanguage = false;
3349 $this->mIsBigDeletion = null;
3350 }
3351
3352 public static function clearCaches() {
3353 $linkCache = LinkCache::singleton();
3354 $linkCache->clear();
3355
3356 $titleCache = self::getTitleCache();
3357 $titleCache->clear();
3358 }
3359
3367 public static function capitalize( $text, $ns = NS_MAIN ) {
3369
3370 if ( MWNamespace::isCapitalized( $ns ) ) {
3371 return $wgContLang->ucfirst( $text );
3372 } else {
3373 return $text;
3374 }
3375 }
3376
3389 private function secureAndSplit() {
3390 # Initialisation
3391 $this->mInterwiki = '';
3392 $this->mFragment = '';
3393 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3394
3395 $dbkey = $this->mDbkeyform;
3396
3397 // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3398 // the parsing code with Title, while avoiding massive refactoring.
3399 // @todo: get rid of secureAndSplit, refactor parsing code.
3400 // @note: getTitleParser() returns a TitleParser implementation which does not have a
3401 // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3402 $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3403 // MalformedTitleException can be thrown here
3404 $parts = $titleCodec->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3405
3406 # Fill fields
3407 $this->setFragment( '#' . $parts['fragment'] );
3408 $this->mInterwiki = $parts['interwiki'];
3409 $this->mLocalInterwiki = $parts['local_interwiki'];
3410 $this->mNamespace = $parts['namespace'];
3411 $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3412
3413 $this->mDbkeyform = $parts['dbkey'];
3414 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3415 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3416
3417 # We already know that some pages won't be in the database!
3418 if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3419 $this->mArticleID = 0;
3420 }
3421
3422 return true;
3423 }
3424
3437 public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3438 if ( count( $options ) > 0 ) {
3439 $db = wfGetDB( DB_MASTER );
3440 } else {
3441 $db = wfGetDB( DB_REPLICA );
3442 }
3443
3444 $res = $db->select(
3445 [ 'page', $table ],
3446 self::getSelectFields(),
3447 [
3448 "{$prefix}_from=page_id",
3449 "{$prefix}_namespace" => $this->getNamespace(),
3450 "{$prefix}_title" => $this->getDBkey() ],
3451 __METHOD__,
3452 $options
3453 );
3454
3455 $retVal = [];
3456 if ( $res->numRows() ) {
3457 $linkCache = LinkCache::singleton();
3458 foreach ( $res as $row ) {
3459 $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3460 if ( $titleObj ) {
3461 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3462 $retVal[] = $titleObj;
3463 }
3464 }
3465 }
3466 return $retVal;
3467 }
3468
3479 public function getTemplateLinksTo( $options = [] ) {
3480 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3481 }
3482
3495 public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3496 $id = $this->getArticleID();
3497
3498 # If the page doesn't exist; there can't be any link from this page
3499 if ( !$id ) {
3500 return [];
3501 }
3502
3503 $db = wfGetDB( DB_REPLICA );
3504
3505 $blNamespace = "{$prefix}_namespace";
3506 $blTitle = "{$prefix}_title";
3507
3508 $res = $db->select(
3509 [ $table, 'page' ],
3510 array_merge(
3511 [ $blNamespace, $blTitle ],
3513 ),
3514 [ "{$prefix}_from" => $id ],
3515 __METHOD__,
3516 $options,
3517 [ 'page' => [
3518 'LEFT JOIN',
3519 [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3520 ] ]
3521 );
3522
3523 $retVal = [];
3524 $linkCache = LinkCache::singleton();
3525 foreach ( $res as $row ) {
3526 if ( $row->page_id ) {
3527 $titleObj = Title::newFromRow( $row );
3528 } else {
3529 $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3530 $linkCache->addBadLinkObj( $titleObj );
3531 }
3532 $retVal[] = $titleObj;
3533 }
3534
3535 return $retVal;
3536 }
3537
3548 public function getTemplateLinksFrom( $options = [] ) {
3549 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3550 }
3551
3560 public function getBrokenLinksFrom() {
3561 if ( $this->getArticleID() == 0 ) {
3562 # All links from article ID 0 are false positives
3563 return [];
3564 }
3565
3566 $dbr = wfGetDB( DB_REPLICA );
3567 $res = $dbr->select(
3568 [ 'page', 'pagelinks' ],
3569 [ 'pl_namespace', 'pl_title' ],
3570 [
3571 'pl_from' => $this->getArticleID(),
3572 'page_namespace IS NULL'
3573 ],
3574 __METHOD__, [],
3575 [
3576 'page' => [
3577 'LEFT JOIN',
3578 [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3579 ]
3580 ]
3581 );
3582
3583 $retVal = [];
3584 foreach ( $res as $row ) {
3585 $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3586 }
3587 return $retVal;
3588 }
3589
3596 public function getCdnUrls() {
3597 $urls = [
3598 $this->getInternalURL(),
3599 $this->getInternalURL( 'action=history' )
3600 ];
3601
3602 $pageLang = $this->getPageLanguage();
3603 if ( $pageLang->hasVariants() ) {
3604 $variants = $pageLang->getVariants();
3605 foreach ( $variants as $vCode ) {
3606 $urls[] = $this->getInternalURL( $vCode );
3607 }
3608 }
3609
3610 // If we are looking at a css/js user subpage, purge the action=raw.
3611 if ( $this->isJsSubpage() ) {
3612 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3613 } elseif ( $this->isCssSubpage() ) {
3614 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3615 }
3616
3617 Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3618 return $urls;
3619 }
3620
3624 public function getSquidURLs() {
3625 return $this->getCdnUrls();
3626 }
3627
3631 public function purgeSquid() {
3632 DeferredUpdates::addUpdate(
3633 new CdnCacheUpdate( $this->getCdnUrls() ),
3634 DeferredUpdates::PRESEND
3635 );
3636 }
3637
3645 public function moveNoAuth( &$nt ) {
3646 wfDeprecated( __METHOD__, '1.25' );
3647 return $this->moveTo( $nt, false );
3648 }
3649
3660 public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3662
3663 if ( !( $nt instanceof Title ) ) {
3664 // Normally we'd add this to $errors, but we'll get
3665 // lots of syntax errors if $nt is not an object
3666 return [ [ 'badtitletext' ] ];
3667 }
3668
3669 $mp = new MovePage( $this, $nt );
3670 $errors = $mp->isValidMove()->getErrorsArray();
3671 if ( $auth ) {
3672 $errors = wfMergeErrorArrays(
3673 $errors,
3674 $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3675 );
3676 }
3677
3678 return $errors ?: true;
3679 }
3680
3687 protected function validateFileMoveOperation( $nt ) {
3689
3690 $errors = [];
3691
3692 $destFile = wfLocalFile( $nt );
3693 $destFile->load( File::READ_LATEST );
3694 if ( !$wgUser->isAllowed( 'reupload-shared' )
3695 && !$destFile->exists() && wfFindFile( $nt )
3696 ) {
3697 $errors[] = [ 'file-exists-sharedrepo' ];
3698 }
3699
3700 return $errors;
3701 }
3702
3715 public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
3717 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3718 if ( is_array( $err ) ) {
3719 // Auto-block user's IP if the account was "hard" blocked
3720 $wgUser->spreadAnyEditBlock();
3721 return $err;
3722 }
3723 // Check suppressredirect permission
3724 if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3725 $createRedirect = true;
3726 }
3727
3728 $mp = new MovePage( $this, $nt );
3729 $status = $mp->move( $wgUser, $reason, $createRedirect );
3730 if ( $status->isOK() ) {
3731 return true;
3732 } else {
3733 return $status->getErrorsArray();
3734 }
3735 }
3736
3749 public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
3751 // Check permissions
3752 if ( !$this->userCan( 'move-subpages' ) ) {
3753 return [ 'cant-move-subpages' ];
3754 }
3755 // Do the source and target namespaces support subpages?
3756 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3757 return [ 'namespace-nosubpages',
3758 MWNamespace::getCanonicalName( $this->getNamespace() ) ];
3759 }
3760 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3761 return [ 'namespace-nosubpages',
3762 MWNamespace::getCanonicalName( $nt->getNamespace() ) ];
3763 }
3764
3765 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3766 $retval = [];
3767 $count = 0;
3768 foreach ( $subpages as $oldSubpage ) {
3769 $count++;
3770 if ( $count > $wgMaximumMovedPages ) {
3771 $retval[$oldSubpage->getPrefixedText()] =
3772 [ 'movepage-max-pages',
3774 break;
3775 }
3776
3777 // We don't know whether this function was called before
3778 // or after moving the root page, so check both
3779 // $this and $nt
3780 if ( $oldSubpage->getArticleID() == $this->getArticleID()
3781 || $oldSubpage->getArticleID() == $nt->getArticleID()
3782 ) {
3783 // When moving a page to a subpage of itself,
3784 // don't move it twice
3785 continue;
3786 }
3787 $newPageName = preg_replace(
3788 '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3789 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
3790 $oldSubpage->getDBkey() );
3791 if ( $oldSubpage->isTalkPage() ) {
3792 $newNs = $nt->getTalkPage()->getNamespace();
3793 } else {
3794 $newNs = $nt->getSubjectPage()->getNamespace();
3795 }
3796 # Bug 14385: we need makeTitleSafe because the new page names may
3797 # be longer than 255 characters.
3798 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3799
3800 $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
3801 if ( $success === true ) {
3802 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3803 } else {
3804 $retval[$oldSubpage->getPrefixedText()] = $success;
3805 }
3806 }
3807 return $retval;
3808 }
3809
3816 public function isSingleRevRedirect() {
3818
3819 $dbw = wfGetDB( DB_MASTER );
3820
3821 # Is it a redirect?
3822 $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3823 if ( $wgContentHandlerUseDB ) {
3824 $fields[] = 'page_content_model';
3825 }
3826
3827 $row = $dbw->selectRow( 'page',
3828 $fields,
3829 $this->pageCond(),
3830 __METHOD__,
3831 [ 'FOR UPDATE' ]
3832 );
3833 # Cache some fields we may want
3834 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3835 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3836 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3837 $this->mContentModel = $row && isset( $row->page_content_model )
3838 ? strval( $row->page_content_model )
3839 : false;
3840
3841 if ( !$this->mRedirect ) {
3842 return false;
3843 }
3844 # Does the article have a history?
3845 $row = $dbw->selectField( [ 'page', 'revision' ],
3846 'rev_id',
3847 [ 'page_namespace' => $this->getNamespace(),
3848 'page_title' => $this->getDBkey(),
3849 'page_id=rev_page',
3850 'page_latest != rev_id'
3851 ],
3852 __METHOD__,
3853 [ 'FOR UPDATE' ]
3854 );
3855 # Return true if there was no history
3856 return ( $row === false );
3857 }
3858
3867 public function isValidMoveTarget( $nt ) {
3868 # Is it an existing file?
3869 if ( $nt->getNamespace() == NS_FILE ) {
3870 $file = wfLocalFile( $nt );
3871 $file->load( File::READ_LATEST );
3872 if ( $file->exists() ) {
3873 wfDebug( __METHOD__ . ": file exists\n" );
3874 return false;
3875 }
3876 }
3877 # Is it a redirect with no history?
3878 if ( !$nt->isSingleRevRedirect() ) {
3879 wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3880 return false;
3881 }
3882 # Get the article text
3883 $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
3884 if ( !is_object( $rev ) ) {
3885 return false;
3886 }
3887 $content = $rev->getContent();
3888 # Does the redirect point to the source?
3889 # Or is it a broken self-redirect, usually caused by namespace collisions?
3890 $redirTitle = $content ? $content->getRedirectTarget() : null;
3891
3892 if ( $redirTitle ) {
3893 if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3894 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3895 wfDebug( __METHOD__ . ": redirect points to other page\n" );
3896 return false;
3897 } else {
3898 return true;
3899 }
3900 } else {
3901 # Fail safe (not a redirect after all. strange.)
3902 wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3903 " is a redirect, but it doesn't contain a valid redirect.\n" );
3904 return false;
3905 }
3906 }
3907
3915 public function getParentCategories() {
3917
3918 $data = [];
3919
3920 $titleKey = $this->getArticleID();
3921
3922 if ( $titleKey === 0 ) {
3923 return $data;
3924 }
3925
3926 $dbr = wfGetDB( DB_REPLICA );
3927
3928 $res = $dbr->select(
3929 'categorylinks',
3930 'cl_to',
3931 [ 'cl_from' => $titleKey ],
3932 __METHOD__
3933 );
3934
3935 if ( $res->numRows() > 0 ) {
3936 foreach ( $res as $row ) {
3937 // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3938 $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3939 }
3940 }
3941 return $data;
3942 }
3943
3950 public function getParentCategoryTree( $children = [] ) {
3951 $stack = [];
3952 $parents = $this->getParentCategories();
3953
3954 if ( $parents ) {
3955 foreach ( $parents as $parent => $current ) {
3956 if ( array_key_exists( $parent, $children ) ) {
3957 # Circular reference
3958 $stack[$parent] = [];
3959 } else {
3960 $nt = Title::newFromText( $parent );
3961 if ( $nt ) {
3962 $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3963 }
3964 }
3965 }
3966 }
3967
3968 return $stack;
3969 }
3970
3977 public function pageCond() {
3978 if ( $this->mArticleID > 0 ) {
3979 // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3980 return [ 'page_id' => $this->mArticleID ];
3981 } else {
3982 return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3983 }
3984 }
3985
3993 public function getPreviousRevisionID( $revId, $flags = 0 ) {
3994 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
3995 $revId = $db->selectField( 'revision', 'rev_id',
3996 [
3997 'rev_page' => $this->getArticleID( $flags ),
3998 'rev_id < ' . intval( $revId )
3999 ],
4000 __METHOD__,
4001 [ 'ORDER BY' => 'rev_id DESC' ]
4002 );
4003
4004 if ( $revId === false ) {
4005 return false;
4006 } else {
4007 return intval( $revId );
4008 }
4009 }
4010
4018 public function getNextRevisionID( $revId, $flags = 0 ) {
4019 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
4020 $revId = $db->selectField( 'revision', 'rev_id',
4021 [
4022 'rev_page' => $this->getArticleID( $flags ),
4023 'rev_id > ' . intval( $revId )
4024 ],
4025 __METHOD__,
4026 [ 'ORDER BY' => 'rev_id' ]
4027 );
4028
4029 if ( $revId === false ) {
4030 return false;
4031 } else {
4032 return intval( $revId );
4033 }
4034 }
4035
4042 public function getFirstRevision( $flags = 0 ) {
4043 $pageId = $this->getArticleID( $flags );
4044 if ( $pageId ) {
4045 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
4046 $row = $db->selectRow( 'revision', Revision::selectFields(),
4047 [ 'rev_page' => $pageId ],
4048 __METHOD__,
4049 [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ]
4050 );
4051 if ( $row ) {
4052 return new Revision( $row );
4053 }
4054 }
4055 return null;
4056 }
4057
4064 public function getEarliestRevTime( $flags = 0 ) {
4065 $rev = $this->getFirstRevision( $flags );
4066 return $rev ? $rev->getTimestamp() : null;
4067 }
4068
4074 public function isNewPage() {
4075 $dbr = wfGetDB( DB_REPLICA );
4076 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4077 }
4078
4084 public function isBigDeletion() {
4086
4087 if ( !$wgDeleteRevisionsLimit ) {
4088 return false;
4089 }
4090
4091 if ( $this->mIsBigDeletion === null ) {
4092 $dbr = wfGetDB( DB_REPLICA );
4093
4094 $revCount = $dbr->selectRowCount(
4095 'revision',
4096 '1',
4097 [ 'rev_page' => $this->getArticleID() ],
4098 __METHOD__,
4099 [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4100 );
4101
4102 $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4103 }
4104
4105 return $this->mIsBigDeletion;
4106 }
4107
4113 public function estimateRevisionCount() {
4114 if ( !$this->exists() ) {
4115 return 0;
4116 }
4117
4118 if ( $this->mEstimateRevisions === null ) {
4119 $dbr = wfGetDB( DB_REPLICA );
4120 $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4121 [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4122 }
4123
4124 return $this->mEstimateRevisions;
4125 }
4126
4136 public function countRevisionsBetween( $old, $new, $max = null ) {
4137 if ( !( $old instanceof Revision ) ) {
4138 $old = Revision::newFromTitle( $this, (int)$old );
4139 }
4140 if ( !( $new instanceof Revision ) ) {
4141 $new = Revision::newFromTitle( $this, (int)$new );
4142 }
4143 if ( !$old || !$new ) {
4144 return 0; // nothing to compare
4145 }
4146 $dbr = wfGetDB( DB_REPLICA );
4147 $conds = [
4148 'rev_page' => $this->getArticleID(),
4149 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4150 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4151 ];
4152 if ( $max !== null ) {
4153 return $dbr->selectRowCount( 'revision', '1',
4154 $conds,
4155 __METHOD__,
4156 [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4157 );
4158 } else {
4159 return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4160 }
4161 }
4162
4179 public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4180 if ( !( $old instanceof Revision ) ) {
4181 $old = Revision::newFromTitle( $this, (int)$old );
4182 }
4183 if ( !( $new instanceof Revision ) ) {
4184 $new = Revision::newFromTitle( $this, (int)$new );
4185 }
4186 // XXX: what if Revision objects are passed in, but they don't refer to this title?
4187 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4188 // in the sanity check below?
4189 if ( !$old || !$new ) {
4190 return null; // nothing to compare
4191 }
4192 $authors = [];
4193 $old_cmp = '>';
4194 $new_cmp = '<';
4196 if ( in_array( 'include_old', $options ) ) {
4197 $old_cmp = '>=';
4198 }
4199 if ( in_array( 'include_new', $options ) ) {
4200 $new_cmp = '<=';
4201 }
4202 if ( in_array( 'include_both', $options ) ) {
4203 $old_cmp = '>=';
4204 $new_cmp = '<=';
4205 }
4206 // No DB query needed if $old and $new are the same or successive revisions:
4207 if ( $old->getId() === $new->getId() ) {
4208 return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4209 [] :
4210 [ $old->getUserText( Revision::RAW ) ];
4211 } elseif ( $old->getId() === $new->getParentId() ) {
4212 if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4213 $authors[] = $old->getUserText( Revision::RAW );
4214 if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4215 $authors[] = $new->getUserText( Revision::RAW );
4216 }
4217 } elseif ( $old_cmp === '>=' ) {
4218 $authors[] = $old->getUserText( Revision::RAW );
4219 } elseif ( $new_cmp === '<=' ) {
4220 $authors[] = $new->getUserText( Revision::RAW );
4221 }
4222 return $authors;
4223 }
4224 $dbr = wfGetDB( DB_REPLICA );
4225 $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4226 [
4227 'rev_page' => $this->getArticleID(),
4228 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4229 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4230 ], __METHOD__,
4231 [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
4232 );
4233 foreach ( $res as $row ) {
4234 $authors[] = $row->rev_user_text;
4235 }
4236 return $authors;
4237 }
4238
4253 public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4254 $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4255 return $authors ? count( $authors ) : 0;
4256 }
4257
4264 public function equals( Title $title ) {
4265 // Note: === is necessary for proper matching of number-like titles.
4266 return $this->getInterwiki() === $title->getInterwiki()
4267 && $this->getNamespace() == $title->getNamespace()
4268 && $this->getDBkey() === $title->getDBkey();
4269 }
4270
4277 public function isSubpageOf( Title $title ) {
4278 return $this->getInterwiki() === $title->getInterwiki()
4279 && $this->getNamespace() == $title->getNamespace()
4280 && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4281 }
4282
4294 public function exists( $flags = 0 ) {
4295 $exists = $this->getArticleID( $flags ) != 0;
4296 Hooks::run( 'TitleExists', [ $this, &$exists ] );
4297 return $exists;
4298 }
4299
4316 public function isAlwaysKnown() {
4317 $isKnown = null;
4318
4329 Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4330
4331 if ( !is_null( $isKnown ) ) {
4332 return $isKnown;
4333 }
4334
4335 if ( $this->isExternal() ) {
4336 return true; // any interwiki link might be viewable, for all we know
4337 }
4338
4339 switch ( $this->mNamespace ) {
4340 case NS_MEDIA:
4341 case NS_FILE:
4342 // file exists, possibly in a foreign repo
4343 return (bool)wfFindFile( $this );
4344 case NS_SPECIAL:
4345 // valid special page
4346 return SpecialPageFactory::exists( $this->getDBkey() );
4347 case NS_MAIN:
4348 // selflink, possibly with fragment
4349 return $this->mDbkeyform == '';
4350 case NS_MEDIAWIKI:
4351 // known system message
4352 return $this->hasSourceText() !== false;
4353 default:
4354 return false;
4355 }
4356 }
4357
4369 public function isKnown() {
4370 return $this->isAlwaysKnown() || $this->exists();
4371 }
4372
4378 public function hasSourceText() {
4379 if ( $this->exists() ) {
4380 return true;
4381 }
4382
4383 if ( $this->mNamespace == NS_MEDIAWIKI ) {
4384 // If the page doesn't exist but is a known system message, default
4385 // message content will be displayed, same for language subpages-
4386 // Use always content language to avoid loading hundreds of languages
4387 // to get the link color.
4389 list( $name, ) = MessageCache::singleton()->figureMessage(
4390 $wgContLang->lcfirst( $this->getText() )
4391 );
4392 $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4393 return $message->exists();
4394 }
4395
4396 return false;
4397 }
4398
4404 public function getDefaultMessageText() {
4406
4407 if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4408 return false;
4409 }
4410
4411 list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4412 $wgContLang->lcfirst( $this->getText() )
4413 );
4414 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4415
4416 if ( $message->exists() ) {
4417 return $message->plain();
4418 } else {
4419 return false;
4420 }
4421 }
4422
4429 public function invalidateCache( $purgeTime = null ) {
4430 if ( wfReadOnly() ) {
4431 return false;
4432 } elseif ( $this->mArticleID === 0 ) {
4433 return true; // avoid gap locking if we know it's not there
4434 }
4435
4436 $dbw = wfGetDB( DB_MASTER );
4437 $dbw->onTransactionPreCommitOrIdle( function () {
4439 } );
4440
4441 $conds = $this->pageCond();
4442 DeferredUpdates::addUpdate(
4443 new AutoCommitUpdate(
4444 $dbw,
4445 __METHOD__,
4446 function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4447 $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4448 $dbw->update(
4449 'page',
4450 [ 'page_touched' => $dbTimestamp ],
4451 $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4452 $fname
4453 );
4454 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4455 }
4456 ),
4457 DeferredUpdates::PRESEND
4458 );
4459
4460 return true;
4461 }
4462
4468 public function touchLinks() {
4469 DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4470 if ( $this->getNamespace() == NS_CATEGORY ) {
4471 DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4472 }
4473 }
4474
4481 public function getTouched( $db = null ) {
4482 if ( $db === null ) {
4483 $db = wfGetDB( DB_REPLICA );
4484 }
4485 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4486 return $touched;
4487 }
4488
4495 public function getNotificationTimestamp( $user = null ) {
4497
4498 // Assume current user if none given
4499 if ( !$user ) {
4500 $user = $wgUser;
4501 }
4502 // Check cache first
4503 $uid = $user->getId();
4504 if ( !$uid ) {
4505 return false;
4506 }
4507 // avoid isset here, as it'll return false for null entries
4508 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4509 return $this->mNotificationTimestamp[$uid];
4510 }
4511 // Don't cache too much!
4512 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4513 $this->mNotificationTimestamp = [];
4514 }
4515
4516 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4517 $watchedItem = $store->getWatchedItem( $user, $this );
4518 if ( $watchedItem ) {
4519 $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4520 } else {
4521 $this->mNotificationTimestamp[$uid] = false;
4522 }
4523
4524 return $this->mNotificationTimestamp[$uid];
4525 }
4526
4533 public function getNamespaceKey( $prepend = 'nstab-' ) {
4535 // Gets the subject namespace if this title
4536 $namespace = MWNamespace::getSubject( $this->getNamespace() );
4537 // Checks if canonical namespace name exists for namespace
4538 if ( MWNamespace::exists( $this->getNamespace() ) ) {
4539 // Uses canonical namespace name
4540 $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4541 } else {
4542 // Uses text of namespace
4543 $namespaceKey = $this->getSubjectNsText();
4544 }
4545 // Makes namespace key lowercase
4546 $namespaceKey = $wgContLang->lc( $namespaceKey );
4547 // Uses main
4548 if ( $namespaceKey == '' ) {
4549 $namespaceKey = 'main';
4550 }
4551 // Changes file to image for backwards compatibility
4552 if ( $namespaceKey == 'file' ) {
4553 $namespaceKey = 'image';
4554 }
4555 return $prepend . $namespaceKey;
4556 }
4557
4564 public function getRedirectsHere( $ns = null ) {
4565 $redirs = [];
4566
4567 $dbr = wfGetDB( DB_REPLICA );
4568 $where = [
4569 'rd_namespace' => $this->getNamespace(),
4570 'rd_title' => $this->getDBkey(),
4571 'rd_from = page_id'
4572 ];
4573 if ( $this->isExternal() ) {
4574 $where['rd_interwiki'] = $this->getInterwiki();
4575 } else {
4576 $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4577 }
4578 if ( !is_null( $ns ) ) {
4579 $where['page_namespace'] = $ns;
4580 }
4581
4582 $res = $dbr->select(
4583 [ 'redirect', 'page' ],
4584 [ 'page_namespace', 'page_title' ],
4585 $where,
4586 __METHOD__
4587 );
4588
4589 foreach ( $res as $row ) {
4590 $redirs[] = self::newFromRow( $row );
4591 }
4592 return $redirs;
4593 }
4594
4600 public function isValidRedirectTarget() {
4602
4603 if ( $this->isSpecialPage() ) {
4604 // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4605 if ( $this->isSpecial( 'Userlogout' ) ) {
4606 return false;
4607 }
4608
4609 foreach ( $wgInvalidRedirectTargets as $target ) {
4610 if ( $this->isSpecial( $target ) ) {
4611 return false;
4612 }
4613 }
4614 }
4615
4616 return true;
4617 }
4618
4624 public function getBacklinkCache() {
4625 return BacklinkCache::get( $this );
4626 }
4627
4633 public function canUseNoindex() {
4635
4636 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4637 ? MWNamespace::getContentNamespaces()
4639
4640 return !in_array( $this->mNamespace, $bannedNamespaces );
4641
4642 }
4643
4654 public function getCategorySortkey( $prefix = '' ) {
4655 $unprefixed = $this->getText();
4656
4657 // Anything that uses this hook should only depend
4658 // on the Title object passed in, and should probably
4659 // tell the users to run updateCollations.php --force
4660 // in order to re-sort existing category relations.
4661 Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4662 if ( $prefix !== '' ) {
4663 # Separate with a line feed, so the unprefixed part is only used as
4664 # a tiebreaker when two pages have the exact same prefix.
4665 # In UCA, tab is the only character that can sort above LF
4666 # so we strip both of them from the original prefix.
4667 $prefix = strtr( $prefix, "\n\t", ' ' );
4668 return "$prefix\n$unprefixed";
4669 }
4670 return $unprefixed;
4671 }
4672
4680 private function getDbPageLanguageCode() {
4682
4683 // check, if the page language could be saved in the database, and if so and
4684 // the value is not requested already, lookup the page language using LinkCache
4685 if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4686 $linkCache = LinkCache::singleton();
4687 $linkCache->addLinkObj( $this );
4688 $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4689 }
4690
4691 return $this->mDbPageLanguage;
4692 }
4693
4702 public function getPageLanguage() {
4704 if ( $this->isSpecialPage() ) {
4705 // special pages are in the user language
4706 return $wgLang;
4707 }
4708
4709 // Checking if DB language is set
4710 $dbPageLanguage = $this->getDbPageLanguageCode();
4711 if ( $dbPageLanguage ) {
4712 return wfGetLangObj( $dbPageLanguage );
4713 }
4714
4715 if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4716 // Note that this may depend on user settings, so the cache should
4717 // be only per-request.
4718 // NOTE: ContentHandler::getPageLanguage() may need to load the
4719 // content to determine the page language!
4720 // Checking $wgLanguageCode hasn't changed for the benefit of unit
4721 // tests.
4722 $contentHandler = ContentHandler::getForTitle( $this );
4723 $langObj = $contentHandler->getPageLanguage( $this );
4724 $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4725 } else {
4726 $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4727 }
4728
4729 return $langObj;
4730 }
4731
4740 public function getPageViewLanguage() {
4742
4743 if ( $this->isSpecialPage() ) {
4744 // If the user chooses a variant, the content is actually
4745 // in a language whose code is the variant code.
4746 $variant = $wgLang->getPreferredVariant();
4747 if ( $wgLang->getCode() !== $variant ) {
4748 return Language::factory( $variant );
4749 }
4750
4751 return $wgLang;
4752 }
4753
4754 // Checking if DB language is set
4755 $dbPageLanguage = $this->getDbPageLanguageCode();
4756 if ( $dbPageLanguage ) {
4757 $pageLang = wfGetLangObj( $dbPageLanguage );
4758 $variant = $pageLang->getPreferredVariant();
4759 if ( $pageLang->getCode() !== $variant ) {
4760 $pageLang = Language::factory( $variant );
4761 }
4762
4763 return $pageLang;
4764 }
4765
4766 // @note Can't be cached persistently, depends on user settings.
4767 // @note ContentHandler::getPageViewLanguage() may need to load the
4768 // content to determine the page language!
4769 $contentHandler = ContentHandler::getForTitle( $this );
4770 $pageLang = $contentHandler->getPageViewLanguage( $this );
4771 return $pageLang;
4772 }
4773
4784 public function getEditNotices( $oldid = 0 ) {
4785 $notices = [];
4786
4787 // Optional notice for the entire namespace
4788 $editnotice_ns = 'editnotice-' . $this->getNamespace();
4789 $msg = wfMessage( $editnotice_ns );
4790 if ( $msg->exists() ) {
4791 $html = $msg->parseAsBlock();
4792 // Edit notices may have complex logic, but output nothing (T91715)
4793 if ( trim( $html ) !== '' ) {
4794 $notices[$editnotice_ns] = Html::rawElement(
4795 'div',
4796 [ 'class' => [
4797 'mw-editnotice',
4798 'mw-editnotice-namespace',
4799 Sanitizer::escapeClass( "mw-$editnotice_ns" )
4800 ] ],
4801 $html
4802 );
4803 }
4804 }
4805
4806 if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4807 // Optional notice for page itself and any parent page
4808 $parts = explode( '/', $this->getDBkey() );
4809 $editnotice_base = $editnotice_ns;
4810 while ( count( $parts ) > 0 ) {
4811 $editnotice_base .= '-' . array_shift( $parts );
4812 $msg = wfMessage( $editnotice_base );
4813 if ( $msg->exists() ) {
4814 $html = $msg->parseAsBlock();
4815 if ( trim( $html ) !== '' ) {
4816 $notices[$editnotice_base] = Html::rawElement(
4817 'div',
4818 [ 'class' => [
4819 'mw-editnotice',
4820 'mw-editnotice-base',
4821 Sanitizer::escapeClass( "mw-$editnotice_base" )
4822 ] ],
4823 $html
4824 );
4825 }
4826 }
4827 }
4828 } else {
4829 // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4830 $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4831 $msg = wfMessage( $editnoticeText );
4832 if ( $msg->exists() ) {
4833 $html = $msg->parseAsBlock();
4834 if ( trim( $html ) !== '' ) {
4835 $notices[$editnoticeText] = Html::rawElement(
4836 'div',
4837 [ 'class' => [
4838 'mw-editnotice',
4839 'mw-editnotice-page',
4840 Sanitizer::escapeClass( "mw-$editnoticeText" )
4841 ] ],
4842 $html
4843 );
4844 }
4845 }
4846 }
4847
4848 Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4849 return $notices;
4850 }
4851
4855 public function __sleep() {
4856 return [
4857 'mNamespace',
4858 'mDbkeyform',
4859 'mFragment',
4860 'mInterwiki',
4861 'mLocalInterwiki',
4862 'mUserCaseDBKey',
4863 'mDefaultNamespace',
4864 ];
4865 }
4866
4867 public function __wakeup() {
4868 $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4869 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4870 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4871 }
4872
4873}
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:806
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:664
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:442
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:128
const RAW
Definition Revision.php:94
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:36
string $mInterwiki
Interwiki prefix.
Definition Title.php:76
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition Title.php:402
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition Title.php:1111
getSubpages( $limit=-1)
Get all subpages of this page.
Definition Title.php:3130
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Definition Title.php:607
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true)
Move a title to a new location.
Definition Title.php:3715
isWatchable()
Can this title be added to a user's watchlist?
Definition Title.php:1042
getNamespace()
Get the namespace index, i.e.
Definition Title.php:921
estimateRevisionCount()
Get the approximate revision count of this page.
Definition Title.php:4113
__wakeup()
Text form (spaces not underscores) of the main part.
Definition Title.php:4867
static newFromDBkey( $key)
Create a new Title from a prefixed DB key.
Definition Title.php:206
isProtected( $action='')
Does the title correspond to a protected article?
Definition Title.php:2684
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:1833
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition Title.php:155
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition Title.php:124
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition Title.php:3816
wasLocalInterwiki()
Was this a local interwiki link?
Definition Title.php:817
checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short)
Check CSS/JS sub-page permissions.
Definition Title.php:2142
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition Title.php:1856
purgeSquid()
Purge all applicable CDN URLs.
Definition Title.php:3631
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition Title.php:1672
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition Title.php:2864
isKnown()
Does this title refer to a page that can (or might) be meaningfully viewed? In particular,...
Definition Title.php:4369
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition Title.php:103
getBacklinkCache()
Get a backlink cache object.
Definition Title.php:4624
static getInterwikiLookup()
B/C kludge: provide an InterwikiLookup for use by Title.
Definition Title.php:188
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition Title.php:176
inNamespace( $ns)
Returns true if the title is inside the specified namespace.
Definition Title.php:1100
equals(Title $title)
Compare with another title.
Definition Title.php:4264
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition Title.php:3183
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition Title.php:3367
checkQuickPermissions( $action, $user, $errors, $rigor, $short)
Permissions checks that fail most often, and which are easiest to test.
Definition Title.php:1979
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition Title.php:1305
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition Title.php:3389
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition Title.php:2880
hasContentModel( $id)
Convenience method for checking a title's content model name.
Definition Title.php:954
getSkinFromCssJsSubpage()
Trim down a .css or .js subpage title to get the corresponding skin name.
Definition Title.php:1261
static clearCaches()
Text form (spaces not underscores) of the main part.
Definition Title.php:3352
createFragmentTarget( $fragment)
Creates a new Title for a different fragment of the same page.
Definition Title.php:1408
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition Title.php:1348
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition Title.php:1201
getFragment()
Get the Title fragment (i.e.
Definition Title.php:1359
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not.
Definition Title.php:2734
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:1615
TitleValue $mTitleValue
A corresponding TitleValue object.
Definition Title.php:162
checkUserBlock( $action, $user, $errors, $rigor, $short)
Check that the user isn't blocked from editing.
Definition Title.php:2336
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition Title.php:1213
validateFileMoveOperation( $nt)
Check if the requested move target is a valid file move target.
Definition Title.php:3687
getTalkNsText()
Get the namespace text of the talk page.
Definition Title.php:1014
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition Title.php:2906
hasFragment()
Check if a Title fragment is set.
Definition Title.php:1369
static nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition Title.php:571
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition Title.php:1061
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition Title.php:4564
getLength( $flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition Title.php:3270
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition Title.php:149
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition Title.php:3660
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition Title.php:1479
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition Title.php:2851
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition Title.php:4633
exists( $flags=0)
Check if page exists.
Definition Title.php:4294
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition Title.php:339
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,...
Definition Title.php:292
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition Title.php:782
int $mLength
The page length, 0 for special pages.
Definition Title.php:143
loadFromRow( $row)
Load Title object fields from a DB row.
Definition Title.php:462
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition Title.php:4702
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition Title.php:79
getUserCaseDBKey()
Get the DB key with the initial letter case as specified by the user.
Definition Title.php:907
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable.
Definition Title.php:1160
moveNoAuth(&$nt)
Move this page without authentication.
Definition Title.php:3645
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles.
Definition Title.php:45
getRestrictionExpiry( $action)
Get the expiry time for the restriction against a given action.
Definition Title.php:2894
getUserPermissionsErrors( $action, $user, $rigor='secure', $ignoreErrors=[])
Can $user perform $action on this page?
Definition Title.php:1948
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition Title.php:1315
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:3479
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias,...
Definition Title.php:1077
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:593
__toString()
Return a string representation of this title.
Definition Title.php:1469
hasSubjectNamespace( $ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition Title.php:1139
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition Title.php:2656
isCssJsSubpage()
Is this a .css or .js subpage of a user page?
Definition Title.php:1250
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1443
areCascadeProtectionSourcesLoaded( $getPages=true)
Determines whether cascading protection sources have already been loaded from the database.
Definition Title.php:2748
getPreviousRevisionID( $revId, $flags=0)
Get the revision ID of the previous revision.
Definition Title.php:3993
getNsText()
Get the namespace text.
Definition Title.php:979
canExist()
Is this in a namespace that allows actual pages?
Definition Title.php:1033
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition Title.php:3061
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition Title.php:4404
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition Title.php:4680
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition Title.php:4136
checkPermissionHooks( $action, $user, $errors, $rigor, $short)
Check various permission hooks.
Definition Title.php:2080
bool $mForcedContentModel
If a content model was forced via setContentModel() this will be true to avoid having other code path...
Definition Title.php:100
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition Title.php:4495
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition Title.php:827
resetArticleID( $newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition Title.php:3330
isNewPage()
Check if this is a new page.
Definition Title.php:4074
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition Title.php:4468
isExternal()
Is this Title interwiki?
Definition Title.php:797
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition Title.php:127
isMainPage()
Is this the mainpage?
Definition Title.php:1181
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition Title.php:1377
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:159
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition Title.php:4179
isSpecialPage()
Returns true if this is a special page.
Definition Title.php:1051
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition Title.php:2716
static newFromLinkTarget(LinkTarget $linkTarget)
Create a new Title from a LinkTarget.
Definition Title.php:236
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition Title.php:1604
isTalkPage()
Is this a talk page of some sort?
Definition Title.php:1296
getRootTitle()
Get the root page name title, i.e.
Definition Title.php:1519
bool int $mLatestID
ID of most recent revision.
Definition Title.php:88
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page.
Definition Title.php:3560
getDBkey()
Get the main part with underscores.
Definition Title.php:898
missingPermissionError( $action, $short)
Get a description array when the user doesn't have the right to perform $action (i....
Definition Title.php:2453
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object.
Definition Title.php:1425
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition Title.php:4064
checkActionPermissions( $action, $user, $errors, $rigor, $short)
Check action permissions not already checked in checkQuickPermissions.
Definition Title.php:2259
string $mFragment
Title fragment (i.e.
Definition Title.php:82
getRootText()
Get the root page name text without a namespace, i.e.
Definition Title.php:1499
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition Title.php:1706
static newFromTitleValue(TitleValue $titleValue)
Create a new Title from a TitleValue.
Definition Title.php:225
string $mPrefixedText
Text form including namespace/interwiki, initialised on demand.
Definition Title.php:130
bool string $mContentModel
ID of the page's content model, i.e.
Definition Title.php:94
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition Title.php:3298
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition Title.php:621
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition Title.php:4600
static HashBagOStuff $titleCache
Definition Title.php:38
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:3495
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition Title.php:725
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:3437
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition Title.php:121
getPartialURL()
Get the URL-encoded form of the main part.
Definition Title.php:889
getBaseText()
Get the base page name without a namespace, i.e.
Definition Title.php:1534
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page,...
Definition Title.php:1150
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:880
getTouched( $db=null)
Get the last touched timestamp.
Definition Title.php:4481
getTitleValue()
Get a TitleValue object representing this Title.
Definition Title.php:857
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition Title.php:3977
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition Title.php:112
string $mUrlform
URL-encoded form of the main part.
Definition Title.php:64
isJsSubpage()
Is this a .js subpage of a user page?
Definition Title.php:1286
getFirstRevision( $flags=0)
Get the first revision of the page.
Definition Title.php:4042
string $mTextform
Text form (spaces not underscores) of the main part.
Definition Title.php:61
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:1332
static newFromIDs( $ids)
Make an array of titles from an array of IDs.
Definition Title.php:424
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:1911
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
Definition Title.php:376
__construct()
Definition Title.php:195
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition Title.php:4277
getBaseTitle()
Get the base page name title, i.e.
Definition Title.php:1559
static newMainPage()
Create a new Title for the Main Page.
Definition Title.php:556
getParentCategoryTree( $children=[])
Get a tree of parent categories.
Definition Title.php:3950
checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short)
Check permissions on special pages & namespaces.
Definition Title.php:2113
bool $mHasSubpages
Whether a page has any subpages.
Definition Title.php:152
isCssSubpage()
Is this a .css subpage of a user page?
Definition Title.php:1276
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition Title.php:4018
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition Title.php:118
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition Title.php:2923
static fixUrlQueryArgs( $query, $query2=false)
Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args get{Canonical,...
Definition Title.php:1634
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition Title.php:3867
loadRestrictions( $oldFashionedRestrictions=null)
Load restrictions from the page_restrictions table.
Definition Title.php:2996
getSquidURLs()
Definition Title.php:3624
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition Title.php:3235
checkPageRestrictions( $action, $user, $errors, $rigor, $short)
Check against page_restrictions table requirements on this page.
Definition Title.php:2178
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition Title.php:262
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition Title.php:4429
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition Title.php:115
static escapeFragmentForURL( $fragment)
Escape a text fragment, say from a link, for a URL.
Definition Title.php:751
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:3209
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition Title.php:1004
bool $mIsBigDeletion
Would deleting this page be a big deletion?
Definition Title.php:165
int $mNamespace
Namespace index, i.e.
Definition Title.php:73
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:535
null $mRedirect
Is the article at this title a redirect?
Definition Title.php:146
countAuthorsBetween( $old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition Title.php:4253
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition Title.php:767
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition Title.php:1876
checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition Title.php:2212
isDeleted()
Is there a version of this page in the deletion archive?
Definition Title.php:3158
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition Title.php:4740
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition Title.php:51
checkReadPermissions( $action, $user, $errors, $rigor, $short)
Check that the user is allowed to read this page.
Definition Title.php:2378
userCan( $action, $user=null, $rigor='secure')
Can $user perform $action on this page?
Definition Title.php:1924
array $mRestrictions
Array of groups allowed to edit this article.
Definition Title.php:106
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition Title.php:140
deleteTitleProtection()
Remove any title protection due to page existing.
Definition Title.php:2638
getSubpage( $text)
Get the title for a subpage of the current page.
Definition Title.php:1595
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:1888
getParentCategories()
Get categories to which this Title belongs and return an array of categories' names.
Definition Title.php:3915
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:511
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition Title.php:85
static getTitleCache()
Definition Title.php:362
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:3548
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition Title.php:840
isSubpage()
Is this a subpage?
Definition Title.php:1190
setFragment( $fragment)
Set the fragment for this title.
Definition Title.php:1397
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition Title.php:1740
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:931
isCssOrJsPage()
Could this page contain custom CSS or JavaScript for the global UI.
Definition Title.php:1231
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition Title.php:4084
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition Title.php:3596
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition Title.php:70
getInterwiki()
Get the interwiki prefix.
Definition Title.php:808
getEditNotices( $oldid=0)
Get a list of rendered edit notices for this page.
Definition Title.php:4784
__sleep()
Definition Title.php:4855
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition Title.php:969
getCascadeProtectionSources( $getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition Title.php:2765
mixed $mTitleProtection
Cached value for getTitleProtection (create protection)
Definition Title.php:133
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition Title.php:1574
string $mDbkeyform
Main part with underscores.
Definition Title.php:67
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true)
Move this page's subpages to be subpages of $nt.
Definition Title.php:3749
hasSourceText()
Does this page have source text?
Definition Title.php:4378
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition Title.php:3051
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1455
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition Title.php:3102
string bool $mOldRestrictions
Text form (spaces not underscores) of the main part.
Definition Title.php:109
canTalk()
Could this title have a corresponding talk page?
Definition Title.php:1024
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition Title.php:2049
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition Title.php:4316
getNamespaceKey( $prepend='nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition Title.php:4533
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition Title.php:4654
static newFromRow( $row)
Make a Title object from a DB row.
Definition Title.php:450
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
static selectFields()
Return the list of revision fields that should be selected to create a new page.
Definition WikiPage.php:281
$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:227
const NS_USER
Definition Defines.php:58
const CONTENT_MODEL_CSS
Definition Defines.php:241
const NS_FILE
Definition Defines.php:62
const PROTO_CURRENT
Definition Defines.php:226
const NS_MAIN
Definition Defines.php:56
const NS_MEDIAWIKI
Definition Defines.php:64
const NS_SPECIAL
Definition Defines.php:45
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:239
const PROTO_HTTP
Definition Defines.php:223
const NS_MEDIA
Definition Defines.php:44
const PROTO_RELATIVE
Definition Defines.php:225
const NS_CATEGORY
Definition Defines.php:70
const CONTENT_MODEL_JAVASCRIPT
Definition Defines.php:240
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context $revId
Definition hooks.txt:1095
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1049
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
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2568
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition hooks.txt:1028
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':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:1937
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 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:956
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:986
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1096
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition hooks.txt:1094
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:1558
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2710
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1135
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:1949
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:886
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:1957
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' 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:2534
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:1595
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:1734
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
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:34
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
addQuotes( $s)
Adds quotes and backslashes.
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
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.
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.
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:22
const DB_MASTER
Definition defines.php:23
if(!isset( $args[0])) $lang