MediaWiki REL1_32
Title.php
Go to the documentation of this file.
1<?php
30
39class Title implements LinkTarget {
41 static private $titleCache = null;
42
48 const CACHE_MAX = 1000;
49
54 const GAID_FOR_UPDATE = 1;
55
61 // @{
62
64 public $mTextform = '';
65
67 public $mUrlform = '';
68
70 public $mDbkeyform = '';
71
73 protected $mUserCaseDBKey;
74
77
79 public $mInterwiki = '';
80
82 private $mLocalInterwiki = false;
83
85 public $mFragment = '';
86
88 public $mArticleID = -1;
89
91 protected $mLatestID = false;
92
97 private $mContentModel = false;
98
103 private $mForcedContentModel = false;
104
107
109 public $mRestrictions = [];
110
117 protected $mOldRestrictions = false;
118
121
124
126 protected $mRestrictionsExpiry = [];
127
130
133
135 public $mRestrictionsLoaded = false;
136
145 public $prefixedText = null;
146
149
156
158 protected $mLength = -1;
159
161 public $mRedirect = null;
162
165
168
170 private $mPageLanguage = false;
171
174 private $mDbPageLanguage = false;
175
177 private $mTitleValue = null;
178
180 private $mIsBigDeletion = null;
181 // @}
182
191 private static function getTitleFormatter() {
192 return MediaWikiServices::getInstance()->getTitleFormatter();
193 }
194
203 private static function getInterwikiLookup() {
204 return MediaWikiServices::getInstance()->getInterwikiLookup();
205 }
206
210 function __construct() {
211 }
212
221 public static function newFromDBkey( $key ) {
222 $t = new Title();
223 $t->mDbkeyform = $key;
224
225 try {
226 $t->secureAndSplit();
227 return $t;
228 } catch ( MalformedTitleException $ex ) {
229 return null;
230 }
231 }
232
240 public static function newFromTitleValue( TitleValue $titleValue ) {
241 return self::newFromLinkTarget( $titleValue );
242 }
243
251 public static function newFromLinkTarget( LinkTarget $linkTarget ) {
252 if ( $linkTarget instanceof Title ) {
253 // Special case if it's already a Title object
254 return $linkTarget;
255 }
256 return self::makeTitle(
257 $linkTarget->getNamespace(),
258 $linkTarget->getText(),
259 $linkTarget->getFragment(),
260 $linkTarget->getInterwiki()
261 );
262 }
263
280 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
281 // DWIM: Integers can be passed in here when page titles are used as array keys.
282 if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
283 throw new InvalidArgumentException( '$text must be a string.' );
284 }
285 if ( $text === null ) {
286 return null;
287 }
288
289 try {
290 return self::newFromTextThrow( strval( $text ), $defaultNamespace );
291 } catch ( MalformedTitleException $ex ) {
292 return null;
293 }
294 }
295
313 public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
314 if ( is_object( $text ) ) {
315 throw new MWException( '$text must be a string, given an object' );
316 } elseif ( $text === null ) {
317 // Legacy code relies on MalformedTitleException being thrown in this case
318 // (happens when URL with no title in it is parsed). TODO fix
319 throw new MalformedTitleException( 'title-invalid-empty' );
320 }
321
322 $titleCache = self::getTitleCache();
323
324 // Wiki pages often contain multiple links to the same page.
325 // Title normalization and parsing can become expensive on pages with many
326 // links, so we can save a little time by caching them.
327 // In theory these are value objects and won't get changed...
328 if ( $defaultNamespace == NS_MAIN ) {
329 $t = $titleCache->get( $text );
330 if ( $t ) {
331 return $t;
332 }
333 }
334
335 // Convert things like &eacute; &#257; or &#x3017; into normalized (T16952) text
336 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
337
338 $t = new Title();
339 $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
340 $t->mDefaultNamespace = intval( $defaultNamespace );
341
342 $t->secureAndSplit();
343 if ( $defaultNamespace == NS_MAIN ) {
344 $titleCache->set( $text, $t );
345 }
346 return $t;
347 }
348
364 public static function newFromURL( $url ) {
365 $t = new Title();
366
367 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
368 # but some URLs used it as a space replacement and they still come
369 # from some external search tools.
370 if ( strpos( self::legalChars(), '+' ) === false ) {
371 $url = strtr( $url, '+', ' ' );
372 }
373
374 $t->mDbkeyform = strtr( $url, ' ', '_' );
375
376 try {
377 $t->secureAndSplit();
378 return $t;
379 } catch ( MalformedTitleException $ex ) {
380 return null;
381 }
382 }
383
387 private static function getTitleCache() {
388 if ( self::$titleCache == null ) {
389 self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
390 }
391 return self::$titleCache;
392 }
393
401 protected static function getSelectFields() {
403
404 $fields = [
405 'page_namespace', 'page_title', 'page_id',
406 'page_len', 'page_is_redirect', 'page_latest',
407 ];
408
410 $fields[] = 'page_content_model';
411 }
412
413 if ( $wgPageLanguageUseDB ) {
414 $fields[] = 'page_lang';
415 }
416
417 return $fields;
418 }
419
427 public static function newFromID( $id, $flags = 0 ) {
428 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
429 $row = $db->selectRow(
430 'page',
431 self::getSelectFields(),
432 [ 'page_id' => $id ],
433 __METHOD__
434 );
435 if ( $row !== false ) {
436 $title = self::newFromRow( $row );
437 } else {
438 $title = null;
439 }
440 return $title;
441 }
442
449 public static function newFromIDs( $ids ) {
450 if ( !count( $ids ) ) {
451 return [];
452 }
454
455 $res = $dbr->select(
456 'page',
457 self::getSelectFields(),
458 [ 'page_id' => $ids ],
459 __METHOD__
460 );
461
462 $titles = [];
463 foreach ( $res as $row ) {
464 $titles[] = self::newFromRow( $row );
465 }
466 return $titles;
467 }
468
475 public static function newFromRow( $row ) {
476 $t = self::makeTitle( $row->page_namespace, $row->page_title );
477 $t->loadFromRow( $row );
478 return $t;
479 }
480
487 public function loadFromRow( $row ) {
488 if ( $row ) { // page found
489 if ( isset( $row->page_id ) ) {
490 $this->mArticleID = (int)$row->page_id;
491 }
492 if ( isset( $row->page_len ) ) {
493 $this->mLength = (int)$row->page_len;
494 }
495 if ( isset( $row->page_is_redirect ) ) {
496 $this->mRedirect = (bool)$row->page_is_redirect;
497 }
498 if ( isset( $row->page_latest ) ) {
499 $this->mLatestID = (int)$row->page_latest;
500 }
501 if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
502 $this->mContentModel = strval( $row->page_content_model );
503 } elseif ( !$this->mForcedContentModel ) {
504 $this->mContentModel = false; # initialized lazily in getContentModel()
505 }
506 if ( isset( $row->page_lang ) ) {
507 $this->mDbPageLanguage = (string)$row->page_lang;
508 }
509 if ( isset( $row->page_restrictions ) ) {
510 $this->mOldRestrictions = $row->page_restrictions;
511 }
512 } else { // page not found
513 $this->mArticleID = 0;
514 $this->mLength = 0;
515 $this->mRedirect = false;
516 $this->mLatestID = 0;
517 if ( !$this->mForcedContentModel ) {
518 $this->mContentModel = false; # initialized lazily in getContentModel()
519 }
520 }
521 }
522
545 public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
546 $t = new Title();
547 $t->mInterwiki = $interwiki;
548 $t->mFragment = $fragment;
549 $t->mNamespace = $ns = intval( $ns );
550 $t->mDbkeyform = strtr( $title, ' ', '_' );
551 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
552 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
553 $t->mTextform = strtr( $title, '_', ' ' );
554 $t->mContentModel = false; # initialized lazily in getContentModel()
555 return $t;
556 }
557
573 public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
574 // NOTE: ideally, this would just call makeTitle() and then isValid(),
575 // but presently, that means more overhead on a potential performance hotspot.
576
577 if ( !MWNamespace::exists( $ns ) ) {
578 return null;
579 }
580
581 $t = new Title();
582 $t->mDbkeyform = self::makeName( $ns, $title, $fragment, $interwiki, true );
583
584 try {
585 $t->secureAndSplit();
586 return $t;
587 } catch ( MalformedTitleException $ex ) {
588 return null;
589 }
590 }
591
597 public static function newMainPage() {
598 $title = self::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
599 // Don't give fatal errors if the message is broken
600 if ( !$title ) {
601 $title = self::newFromText( 'Main Page' );
602 }
603 return $title;
604 }
605
612 public static function nameOf( $id ) {
614
615 $s = $dbr->selectRow(
616 'page',
617 [ 'page_namespace', 'page_title' ],
618 [ 'page_id' => $id ],
619 __METHOD__
620 );
621 if ( $s === false ) {
622 return null;
623 }
624
625 $n = self::makeName( $s->page_namespace, $s->page_title );
626 return $n;
627 }
628
634 public static function legalChars() {
635 global $wgLegalTitleChars;
636 return $wgLegalTitleChars;
637 }
638
648 public static function convertByteClassToUnicodeClass( $byteClass ) {
649 $length = strlen( $byteClass );
650 // Input token queue
651 $x0 = $x1 = $x2 = '';
652 // Decoded queue
653 $d0 = $d1 = $d2 = '';
654 // Decoded integer codepoints
655 $ord0 = $ord1 = $ord2 = 0;
656 // Re-encoded queue
657 $r0 = $r1 = $r2 = '';
658 // Output
659 $out = '';
660 // Flags
661 $allowUnicode = false;
662 for ( $pos = 0; $pos < $length; $pos++ ) {
663 // Shift the queues down
664 $x2 = $x1;
665 $x1 = $x0;
666 $d2 = $d1;
667 $d1 = $d0;
668 $ord2 = $ord1;
669 $ord1 = $ord0;
670 $r2 = $r1;
671 $r1 = $r0;
672 // Load the current input token and decoded values
673 $inChar = $byteClass[$pos];
674 if ( $inChar == '\\' ) {
675 if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
676 $x0 = $inChar . $m[0];
677 $d0 = chr( hexdec( $m[1] ) );
678 $pos += strlen( $m[0] );
679 } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
680 $x0 = $inChar . $m[0];
681 $d0 = chr( octdec( $m[0] ) );
682 $pos += strlen( $m[0] );
683 } elseif ( $pos + 1 >= $length ) {
684 $x0 = $d0 = '\\';
685 } else {
686 $d0 = $byteClass[$pos + 1];
687 $x0 = $inChar . $d0;
688 $pos += 1;
689 }
690 } else {
691 $x0 = $d0 = $inChar;
692 }
693 $ord0 = ord( $d0 );
694 // Load the current re-encoded value
695 if ( $ord0 < 32 || $ord0 == 0x7f ) {
696 $r0 = sprintf( '\x%02x', $ord0 );
697 } elseif ( $ord0 >= 0x80 ) {
698 // Allow unicode if a single high-bit character appears
699 $r0 = sprintf( '\x%02x', $ord0 );
700 $allowUnicode = true;
701 } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
702 $r0 = '\\' . $d0;
703 } else {
704 $r0 = $d0;
705 }
706 // Do the output
707 if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
708 // Range
709 if ( $ord2 > $ord0 ) {
710 // Empty range
711 } elseif ( $ord0 >= 0x80 ) {
712 // Unicode range
713 $allowUnicode = true;
714 if ( $ord2 < 0x80 ) {
715 // Keep the non-unicode section of the range
716 $out .= "$r2-\\x7F";
717 }
718 } else {
719 // Normal range
720 $out .= "$r2-$r0";
721 }
722 // Reset state to the initial value
723 $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
724 } elseif ( $ord2 < 0x80 ) {
725 // ASCII character
726 $out .= $r2;
727 }
728 }
729 if ( $ord1 < 0x80 ) {
730 $out .= $r1;
731 }
732 if ( $ord0 < 0x80 ) {
733 $out .= $r0;
734 }
735 if ( $allowUnicode ) {
736 $out .= '\u0080-\uFFFF';
737 }
738 return $out;
739 }
740
752 public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
753 $canonicalNamespace = false
754 ) {
755 if ( $canonicalNamespace ) {
756 $namespace = MWNamespace::getCanonicalName( $ns );
757 } else {
758 $namespace = MediaWikiServices::getInstance()->getContentLanguage()->getNsText( $ns );
759 }
760 $name = $namespace == '' ? $title : "$namespace:$title";
761 if ( strval( $interwiki ) != '' ) {
762 $name = "$interwiki:$name";
763 }
764 if ( strval( $fragment ) != '' ) {
765 $name .= '#' . $fragment;
766 }
767 return $name;
768 }
769
778 static function escapeFragmentForURL( $fragment ) {
779 wfDeprecated( __METHOD__, '1.30' );
780 # Note that we don't urlencode the fragment. urlencoded Unicode
781 # fragments appear not to work in IE (at least up to 7) or in at least
782 # one version of Opera 9.x. The W3C validator, for one, doesn't seem
783 # to care if they aren't encoded.
784 return Sanitizer::escapeId( $fragment, 'noninitial' );
785 }
786
795 public static function compare( LinkTarget $a, LinkTarget $b ) {
796 return $a->getNamespace() <=> $b->getNamespace()
797 ?: strcmp( $a->getText(), $b->getText() );
798 }
799
814 public function isValid() {
815 if ( !MWNamespace::exists( $this->mNamespace ) ) {
816 return false;
817 }
818
819 try {
820 $parser = MediaWikiServices::getInstance()->getTitleParser();
821 $parser->parseTitle( $this->mDbkeyform, $this->mNamespace );
822 return true;
823 } catch ( MalformedTitleException $ex ) {
824 return false;
825 }
826 }
827
835 public function isLocal() {
836 if ( $this->isExternal() ) {
837 $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
838 if ( $iw ) {
839 return $iw->isLocal();
840 }
841 }
842 return true;
843 }
844
850 public function isExternal() {
851 return $this->mInterwiki !== '';
852 }
853
861 public function getInterwiki() {
862 return $this->mInterwiki;
863 }
864
870 public function wasLocalInterwiki() {
871 return $this->mLocalInterwiki;
872 }
873
880 public function isTrans() {
881 if ( !$this->isExternal() ) {
882 return false;
883 }
884
885 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
886 }
887
893 public function getTransWikiID() {
894 if ( !$this->isExternal() ) {
895 return false;
896 }
897
898 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
899 }
900
910 public function getTitleValue() {
911 if ( $this->mTitleValue === null ) {
912 try {
913 $this->mTitleValue = new TitleValue(
914 $this->mNamespace,
915 $this->mDbkeyform,
916 $this->mFragment,
917 $this->mInterwiki
918 );
919 } catch ( InvalidArgumentException $ex ) {
920 wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
921 $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
922 }
923 }
924
925 return $this->mTitleValue;
926 }
927
933 public function getText() {
934 return $this->mTextform;
935 }
936
942 public function getPartialURL() {
943 return $this->mUrlform;
944 }
945
951 public function getDBkey() {
952 return $this->mDbkeyform;
953 }
954
960 function getUserCaseDBKey() {
961 if ( !is_null( $this->mUserCaseDBKey ) ) {
962 return $this->mUserCaseDBKey;
963 } else {
964 // If created via makeTitle(), $this->mUserCaseDBKey is not set.
965 return $this->mDbkeyform;
966 }
967 }
968
974 public function getNamespace() {
975 return $this->mNamespace;
976 }
977
984 public function getContentModel( $flags = 0 ) {
985 if ( !$this->mForcedContentModel
986 && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE )
987 && $this->getArticleID( $flags )
988 ) {
989 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
990 $linkCache->addLinkObj( $this ); # in case we already had an article ID
991 $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
992 }
993
994 if ( !$this->mContentModel ) {
995 $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
996 }
997
998 return $this->mContentModel;
999 }
1000
1007 public function hasContentModel( $id ) {
1008 return $this->getContentModel() == $id;
1009 }
1010
1022 public function setContentModel( $model ) {
1023 $this->mContentModel = $model;
1024 $this->mForcedContentModel = true;
1025 }
1026
1032 public function getNsText() {
1033 if ( $this->isExternal() ) {
1034 // This probably shouldn't even happen, except for interwiki transclusion.
1035 // If possible, use the canonical name for the foreign namespace.
1036 $nsText = MWNamespace::getCanonicalName( $this->mNamespace );
1037 if ( $nsText !== false ) {
1038 return $nsText;
1039 }
1040 }
1041
1042 try {
1043 $formatter = self::getTitleFormatter();
1044 return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
1045 } catch ( InvalidArgumentException $ex ) {
1046 wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
1047 return false;
1048 }
1049 }
1050
1056 public function getSubjectNsText() {
1057 return MediaWikiServices::getInstance()->getContentLanguage()->
1058 getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1059 }
1060
1066 public function getTalkNsText() {
1067 return MediaWikiServices::getInstance()->getContentLanguage()->
1068 getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1069 }
1070
1078 public function canTalk() {
1079 return $this->canHaveTalkPage();
1080 }
1081
1090 public function canHaveTalkPage() {
1091 return MWNamespace::hasTalkNamespace( $this->mNamespace );
1092 }
1093
1099 public function canExist() {
1100 return $this->mNamespace >= NS_MAIN;
1101 }
1102
1108 public function isWatchable() {
1109 return !$this->isExternal() && MWNamespace::isWatchable( $this->mNamespace );
1110 }
1111
1117 public function isSpecialPage() {
1118 return $this->mNamespace == NS_SPECIAL;
1119 }
1120
1127 public function isSpecial( $name ) {
1128 if ( $this->isSpecialPage() ) {
1129 list( $thisName, /* $subpage */ ) =
1130 MediaWikiServices::getInstance()->getSpecialPageFactory()->
1131 resolveAlias( $this->mDbkeyform );
1132 if ( $name == $thisName ) {
1133 return true;
1134 }
1135 }
1136 return false;
1137 }
1138
1145 public function fixSpecialName() {
1146 if ( $this->isSpecialPage() ) {
1147 $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
1148 list( $canonicalName, $par ) = $spFactory->resolveAlias( $this->mDbkeyform );
1149 if ( $canonicalName ) {
1150 $localName = $spFactory->getLocalNameFor( $canonicalName, $par );
1151 if ( $localName != $this->mDbkeyform ) {
1152 return self::makeTitle( NS_SPECIAL, $localName );
1153 }
1154 }
1155 }
1156 return $this;
1157 }
1158
1169 public function inNamespace( $ns ) {
1170 return MWNamespace::equals( $this->mNamespace, $ns );
1171 }
1172
1180 public function inNamespaces( /* ... */ ) {
1181 $namespaces = func_get_args();
1182 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1184 }
1185
1186 foreach ( $namespaces as $ns ) {
1187 if ( $this->inNamespace( $ns ) ) {
1188 return true;
1189 }
1190 }
1191
1192 return false;
1193 }
1194
1208 public function hasSubjectNamespace( $ns ) {
1209 return MWNamespace::subjectEquals( $this->mNamespace, $ns );
1210 }
1211
1219 public function isContentPage() {
1220 return MWNamespace::isContent( $this->mNamespace );
1221 }
1222
1229 public function isMovable() {
1230 if ( !MWNamespace::isMovable( $this->mNamespace ) || $this->isExternal() ) {
1231 // Interwiki title or immovable namespace. Hooks don't get to override here
1232 return false;
1233 }
1234
1235 $result = true;
1236 Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1237 return $result;
1238 }
1239
1250 public function isMainPage() {
1251 return $this->equals( self::newMainPage() );
1252 }
1253
1259 public function isSubpage() {
1260 return MWNamespace::hasSubpages( $this->mNamespace )
1261 ? strpos( $this->getText(), '/' ) !== false
1262 : false;
1263 }
1264
1270 public function isConversionTable() {
1271 // @todo ConversionTable should become a separate content model.
1272
1273 return $this->mNamespace == NS_MEDIAWIKI &&
1274 strpos( $this->getText(), 'Conversiontable/' ) === 0;
1275 }
1276
1282 public function isWikitextPage() {
1283 return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1284 }
1285
1300 public function isSiteConfigPage() {
1301 return (
1302 $this->isSiteCssConfigPage()
1303 || $this->isSiteJsonConfigPage()
1304 || $this->isSiteJsConfigPage()
1305 );
1306 }
1307
1312 public function isCssOrJsPage() {
1313 wfDeprecated( __METHOD__, '1.31' );
1314 return ( NS_MEDIAWIKI == $this->mNamespace
1315 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1317 }
1318
1325 public function isUserConfigPage() {
1326 return (
1327 $this->isUserCssConfigPage()
1328 || $this->isUserJsonConfigPage()
1329 || $this->isUserJsConfigPage()
1330 );
1331 }
1332
1337 public function isCssJsSubpage() {
1338 wfDeprecated( __METHOD__, '1.31' );
1339 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1340 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1342 }
1343
1350 public function getSkinFromConfigSubpage() {
1351 $subpage = explode( '/', $this->mTextform );
1352 $subpage = $subpage[count( $subpage ) - 1];
1353 $lastdot = strrpos( $subpage, '.' );
1354 if ( $lastdot === false ) {
1355 return $subpage; # Never happens: only called for names ending in '.css'/'.json'/'.js'
1356 }
1357 return substr( $subpage, 0, $lastdot );
1358 }
1359
1364 public function getSkinFromCssJsSubpage() {
1365 wfDeprecated( __METHOD__, '1.31' );
1366 return $this->getSkinFromConfigSubpage();
1367 }
1368
1375 public function isUserCssConfigPage() {
1376 return (
1377 NS_USER == $this->mNamespace
1378 && $this->isSubpage()
1380 );
1381 }
1382
1387 public function isCssSubpage() {
1388 wfDeprecated( __METHOD__, '1.31' );
1389 return $this->isUserCssConfigPage();
1390 }
1391
1398 public function isUserJsonConfigPage() {
1399 return (
1400 NS_USER == $this->mNamespace
1401 && $this->isSubpage()
1403 );
1404 }
1405
1412 public function isUserJsConfigPage() {
1413 return (
1414 NS_USER == $this->mNamespace
1415 && $this->isSubpage()
1417 );
1418 }
1419
1424 public function isJsSubpage() {
1425 wfDeprecated( __METHOD__, '1.31' );
1426 return $this->isUserJsConfigPage();
1427 }
1428
1435 public function isSiteCssConfigPage() {
1436 return (
1437 NS_MEDIAWIKI == $this->mNamespace
1438 && (
1440 // paranoia - a MediaWiki: namespace page with mismatching extension and content
1441 // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1442 || substr( $this->mDbkeyform, -4 ) === '.css'
1443 )
1444 );
1445 }
1446
1453 public function isSiteJsonConfigPage() {
1454 return (
1455 NS_MEDIAWIKI == $this->mNamespace
1456 && (
1458 // paranoia - a MediaWiki: namespace page with mismatching extension and content
1459 // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1460 || substr( $this->mDbkeyform, -5 ) === '.json'
1461 )
1462 );
1463 }
1464
1471 public function isSiteJsConfigPage() {
1472 return (
1473 NS_MEDIAWIKI == $this->mNamespace
1474 && (
1476 // paranoia - a MediaWiki: namespace page with mismatching extension and content
1477 // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1478 || substr( $this->mDbkeyform, -3 ) === '.js'
1479 )
1480 );
1481 }
1482
1489 public function isRawHtmlMessage() {
1490 global $wgRawHtmlMessages;
1491
1492 if ( !$this->inNamespace( NS_MEDIAWIKI ) ) {
1493 return false;
1494 }
1495 $message = lcfirst( $this->getRootTitle()->getDBkey() );
1496 return in_array( $message, $wgRawHtmlMessages, true );
1497 }
1498
1504 public function isTalkPage() {
1505 return MWNamespace::isTalk( $this->mNamespace );
1506 }
1507
1513 public function getTalkPage() {
1514 return self::makeTitle( MWNamespace::getTalk( $this->mNamespace ), $this->mDbkeyform );
1515 }
1516
1526 public function getTalkPageIfDefined() {
1527 if ( !$this->canHaveTalkPage() ) {
1528 return null;
1529 }
1530
1531 return $this->getTalkPage();
1532 }
1533
1540 public function getSubjectPage() {
1541 // Is this the same title?
1542 $subjectNS = MWNamespace::getSubject( $this->mNamespace );
1543 if ( $this->mNamespace == $subjectNS ) {
1544 return $this;
1545 }
1546 return self::makeTitle( $subjectNS, $this->mDbkeyform );
1547 }
1548
1557 public function getOtherPage() {
1558 if ( $this->isSpecialPage() ) {
1559 throw new MWException( 'Special pages cannot have other pages' );
1560 }
1561 if ( $this->isTalkPage() ) {
1562 return $this->getSubjectPage();
1563 } else {
1564 if ( !$this->canHaveTalkPage() ) {
1565 throw new MWException( "{$this->getPrefixedText()} does not have an other page" );
1566 }
1567 return $this->getTalkPage();
1568 }
1569 }
1570
1576 public function getDefaultNamespace() {
1577 return $this->mDefaultNamespace;
1578 }
1579
1587 public function getFragment() {
1588 return $this->mFragment;
1589 }
1590
1597 public function hasFragment() {
1598 return $this->mFragment !== '';
1599 }
1600
1606 public function getFragmentForURL() {
1607 if ( !$this->hasFragment() ) {
1608 return '';
1609 } elseif ( $this->isExternal() ) {
1610 // Note: If the interwiki is unknown, it's treated as a namespace on the local wiki,
1611 // so we treat it like a local interwiki.
1612 $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1613 if ( $interwiki && !$interwiki->isLocal() ) {
1614 return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->mFragment );
1615 }
1616 }
1617
1618 return '#' . Sanitizer::escapeIdForLink( $this->mFragment );
1619 }
1620
1633 public function setFragment( $fragment ) {
1634 $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1635 }
1636
1644 public function createFragmentTarget( $fragment ) {
1645 return self::makeTitle(
1646 $this->mNamespace,
1647 $this->getText(),
1648 $fragment,
1649 $this->mInterwiki
1650 );
1651 }
1652
1660 private function prefix( $name ) {
1661 $p = '';
1662 if ( $this->isExternal() ) {
1663 $p = $this->mInterwiki . ':';
1664 }
1665
1666 if ( 0 != $this->mNamespace ) {
1667 $nsText = $this->getNsText();
1668
1669 if ( $nsText === false ) {
1670 // See T165149. Awkward, but better than erroneously linking to the main namespace.
1671 $nsText = MediaWikiServices::getInstance()->getContentLanguage()->
1672 getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}";
1673 }
1674
1675 $p .= $nsText . ':';
1676 }
1677 return $p . $name;
1678 }
1679
1686 public function getPrefixedDBkey() {
1687 $s = $this->prefix( $this->mDbkeyform );
1688 $s = strtr( $s, ' ', '_' );
1689 return $s;
1690 }
1691
1698 public function getPrefixedText() {
1699 if ( $this->prefixedText === null ) {
1700 $s = $this->prefix( $this->mTextform );
1701 $s = strtr( $s, '_', ' ' );
1702 $this->prefixedText = $s;
1703 }
1704 return $this->prefixedText;
1705 }
1706
1712 public function __toString() {
1713 return $this->getPrefixedText();
1714 }
1715
1722 public function getFullText() {
1723 $text = $this->getPrefixedText();
1724 if ( $this->hasFragment() ) {
1725 $text .= '#' . $this->mFragment;
1726 }
1727 return $text;
1728 }
1729
1742 public function getRootText() {
1743 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1744 return $this->getText();
1745 }
1746
1747 return strtok( $this->getText(), '/' );
1748 }
1749
1762 public function getRootTitle() {
1763 return self::makeTitle( $this->mNamespace, $this->getRootText() );
1764 }
1765
1777 public function getBaseText() {
1778 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1779 return $this->getText();
1780 }
1781
1782 $parts = explode( '/', $this->getText() );
1783 # Don't discard the real title if there's no subpage involved
1784 if ( count( $parts ) > 1 ) {
1785 unset( $parts[count( $parts ) - 1] );
1786 }
1787 return implode( '/', $parts );
1788 }
1789
1802 public function getBaseTitle() {
1803 return self::makeTitle( $this->mNamespace, $this->getBaseText() );
1804 }
1805
1817 public function getSubpageText() {
1818 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1819 return $this->mTextform;
1820 }
1821 $parts = explode( '/', $this->mTextform );
1822 return $parts[count( $parts ) - 1];
1823 }
1824
1838 public function getSubpage( $text ) {
1839 return self::makeTitleSafe( $this->mNamespace, $this->getText() . '/' . $text );
1840 }
1841
1847 public function getSubpageUrlForm() {
1848 $text = $this->getSubpageText();
1849 $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1850 return $text;
1851 }
1852
1858 public function getPrefixedURL() {
1859 $s = $this->prefix( $this->mDbkeyform );
1860 $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1861 return $s;
1862 }
1863
1877 private static function fixUrlQueryArgs( $query, $query2 = false ) {
1878 if ( $query2 !== false ) {
1879 wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1880 "method called with a second parameter is deprecated. Add your " .
1881 "parameter to an array passed as the first parameter.", "1.19" );
1882 }
1883 if ( is_array( $query ) ) {
1885 }
1886 if ( $query2 ) {
1887 if ( is_string( $query2 ) ) {
1888 // $query2 is a string, we will consider this to be
1889 // a deprecated $variant argument and add it to the query
1890 $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1891 } else {
1892 $query2 = wfArrayToCgi( $query2 );
1893 }
1894 // If we have $query content add a & to it first
1895 if ( $query ) {
1896 $query .= '&';
1897 }
1898 // Now append the queries together
1899 $query .= $query2;
1900 }
1901 return $query;
1902 }
1903
1915 public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1916 $query = self::fixUrlQueryArgs( $query, $query2 );
1917
1918 # Hand off all the decisions on urls to getLocalURL
1919 $url = $this->getLocalURL( $query );
1920
1921 # Expand the url to make it a full url. Note that getLocalURL has the
1922 # potential to output full urls for a variety of reasons, so we use
1923 # wfExpandUrl instead of simply prepending $wgServer
1924 $url = wfExpandUrl( $url, $proto );
1925
1926 # Finally, add the fragment.
1927 $url .= $this->getFragmentForURL();
1928 // Avoid PHP 7.1 warning from passing $this by reference
1929 $titleRef = $this;
1930 Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
1931 return $url;
1932 }
1933
1950 public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1951 $target = $this;
1952 if ( $this->isExternal() ) {
1953 $target = SpecialPage::getTitleFor(
1954 'GoToInterwiki',
1955 $this->getPrefixedDBkey()
1956 );
1957 }
1958 return $target->getFullURL( $query, false, $proto );
1959 }
1960
1984 public function getLocalURL( $query = '', $query2 = false ) {
1986
1987 $query = self::fixUrlQueryArgs( $query, $query2 );
1988
1989 $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1990 if ( $interwiki ) {
1991 $namespace = $this->getNsText();
1992 if ( $namespace != '' ) {
1993 # Can this actually happen? Interwikis shouldn't be parsed.
1994 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1995 $namespace .= ':';
1996 }
1997 $url = $interwiki->getURL( $namespace . $this->mDbkeyform );
1998 $url = wfAppendQuery( $url, $query );
1999 } else {
2000 $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
2001 if ( $query == '' ) {
2002 $url = str_replace( '$1', $dbkey, $wgArticlePath );
2003 // Avoid PHP 7.1 warning from passing $this by reference
2004 $titleRef = $this;
2005 Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
2006 } else {
2008 $url = false;
2009 $matches = [];
2010
2011 if ( !empty( $wgActionPaths )
2012 && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
2013 ) {
2014 $action = urldecode( $matches[2] );
2015 if ( isset( $wgActionPaths[$action] ) ) {
2016 $query = $matches[1];
2017 if ( isset( $matches[4] ) ) {
2018 $query .= $matches[4];
2019 }
2020 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
2021 if ( $query != '' ) {
2022 $url = wfAppendQuery( $url, $query );
2023 }
2024 }
2025 }
2026
2027 if ( $url === false
2029 && preg_match( '/^variant=([^&]*)$/', $query, $matches )
2030 && $this->getPageLanguage()->equals(
2031 MediaWikiServices::getInstance()->getContentLanguage() )
2032 && $this->getPageLanguage()->hasVariants()
2033 ) {
2034 $variant = urldecode( $matches[1] );
2035 if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
2036 // Only do the variant replacement if the given variant is a valid
2037 // variant for the page's language.
2038 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
2039 $url = str_replace( '$1', $dbkey, $url );
2040 }
2041 }
2042
2043 if ( $url === false ) {
2044 if ( $query == '-' ) {
2045 $query = '';
2046 }
2047 $url = "{$wgScript}?title={$dbkey}&{$query}";
2048 }
2049 }
2050 // Avoid PHP 7.1 warning from passing $this by reference
2051 $titleRef = $this;
2052 Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
2053
2054 // @todo FIXME: This causes breakage in various places when we
2055 // actually expected a local URL and end up with dupe prefixes.
2056 if ( $wgRequest->getVal( 'action' ) == 'render' ) {
2057 $url = $wgServer . $url;
2058 }
2059 }
2060 // Avoid PHP 7.1 warning from passing $this by reference
2061 $titleRef = $this;
2062 Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
2063 return $url;
2064 }
2065
2083 public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
2084 if ( $this->isExternal() || $proto !== false ) {
2085 $ret = $this->getFullURL( $query, $query2, $proto );
2086 } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
2087 $ret = $this->getFragmentForURL();
2088 } else {
2089 $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
2090 }
2091 return $ret;
2092 }
2093
2108 public function getInternalURL( $query = '', $query2 = false ) {
2110 $query = self::fixUrlQueryArgs( $query, $query2 );
2111 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
2112 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
2113 // Avoid PHP 7.1 warning from passing $this by reference
2114 $titleRef = $this;
2115 Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
2116 return $url;
2117 }
2118
2132 public function getCanonicalURL( $query = '', $query2 = false ) {
2133 $query = self::fixUrlQueryArgs( $query, $query2 );
2134 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
2135 // Avoid PHP 7.1 warning from passing $this by reference
2136 $titleRef = $this;
2137 Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
2138 return $url;
2139 }
2140
2146 public function getEditURL() {
2147 if ( $this->isExternal() ) {
2148 return '';
2149 }
2150 $s = $this->getLocalURL( 'action=edit' );
2151
2152 return $s;
2153 }
2154
2169 public function quickUserCan( $action, $user = null ) {
2170 return $this->userCan( $action, $user, false );
2171 }
2172
2182 public function userCan( $action, $user = null, $rigor = 'secure' ) {
2183 if ( !$user instanceof User ) {
2184 global $wgUser;
2185 $user = $wgUser;
2186 }
2187
2188 return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
2189 }
2190
2207 $action, $user, $rigor = 'secure', $ignoreErrors = []
2208 ) {
2209 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
2210
2211 // Remove the errors being ignored.
2212 foreach ( $errors as $index => $error ) {
2213 $errKey = is_array( $error ) ? $error[0] : $error;
2214
2215 if ( in_array( $errKey, $ignoreErrors ) ) {
2216 unset( $errors[$index] );
2217 }
2218 if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
2219 unset( $errors[$index] );
2220 }
2221 }
2222
2223 return $errors;
2224 }
2225
2237 private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
2238 if ( !Hooks::run( 'TitleQuickPermissions',
2239 [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
2240 ) {
2241 return $errors;
2242 }
2243
2244 if ( $action == 'create' ) {
2245 if (
2246 ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
2247 ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
2248 ) {
2249 $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
2250 }
2251 } elseif ( $action == 'move' ) {
2252 if ( !$user->isAllowed( 'move-rootuserpages' )
2253 && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2254 // Show user page-specific message only if the user can move other pages
2255 $errors[] = [ 'cant-move-user-page' ];
2256 }
2257
2258 // Check if user is allowed to move files if it's a file
2259 if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
2260 $errors[] = [ 'movenotallowedfile' ];
2261 }
2262
2263 // Check if user is allowed to move category pages if it's a category page
2264 if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
2265 $errors[] = [ 'cant-move-category-page' ];
2266 }
2267
2268 if ( !$user->isAllowed( 'move' ) ) {
2269 // User can't move anything
2270 $userCanMove = User::groupHasPermission( 'user', 'move' );
2271 $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
2272 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
2273 // custom message if logged-in users without any special rights can move
2274 $errors[] = [ 'movenologintext' ];
2275 } else {
2276 $errors[] = [ 'movenotallowed' ];
2277 }
2278 }
2279 } elseif ( $action == 'move-target' ) {
2280 if ( !$user->isAllowed( 'move' ) ) {
2281 // User can't move anything
2282 $errors[] = [ 'movenotallowed' ];
2283 } elseif ( !$user->isAllowed( 'move-rootuserpages' )
2284 && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2285 // Show user page-specific message only if the user can move other pages
2286 $errors[] = [ 'cant-move-to-user-page' ];
2287 } elseif ( !$user->isAllowed( 'move-categorypages' )
2288 && $this->mNamespace == NS_CATEGORY ) {
2289 // Show category page-specific message only if the user can move other pages
2290 $errors[] = [ 'cant-move-to-category-page' ];
2291 }
2292 } elseif ( !$user->isAllowed( $action ) ) {
2293 $errors[] = $this->missingPermissionError( $action, $short );
2294 }
2295
2296 return $errors;
2297 }
2298
2307 private function resultToError( $errors, $result ) {
2308 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2309 // A single array representing an error
2310 $errors[] = $result;
2311 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2312 // A nested array representing multiple errors
2313 $errors = array_merge( $errors, $result );
2314 } elseif ( $result !== '' && is_string( $result ) ) {
2315 // A string representing a message-id
2316 $errors[] = [ $result ];
2317 } elseif ( $result instanceof MessageSpecifier ) {
2318 // A message specifier representing an error
2319 $errors[] = [ $result ];
2320 } elseif ( $result === false ) {
2321 // a generic "We don't want them to do that"
2322 $errors[] = [ 'badaccess-group0' ];
2323 }
2324 return $errors;
2325 }
2326
2338 private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2339 // Use getUserPermissionsErrors instead
2340 $result = '';
2341 // Avoid PHP 7.1 warning from passing $this by reference
2342 $titleRef = $this;
2343 if ( !Hooks::run( 'userCan', [ &$titleRef, &$user, $action, &$result ] ) ) {
2344 return $result ? [] : [ [ 'badaccess-group0' ] ];
2345 }
2346 // Check getUserPermissionsErrors hook
2347 // Avoid PHP 7.1 warning from passing $this by reference
2348 $titleRef = $this;
2349 if ( !Hooks::run( 'getUserPermissionsErrors', [ &$titleRef, &$user, $action, &$result ] ) ) {
2350 $errors = $this->resultToError( $errors, $result );
2351 }
2352 // Check getUserPermissionsErrorsExpensive hook
2353 if (
2354 $rigor !== 'quick'
2355 && !( $short && count( $errors ) > 0 )
2356 && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$titleRef, &$user, $action, &$result ] )
2357 ) {
2358 $errors = $this->resultToError( $errors, $result );
2359 }
2360
2361 return $errors;
2362 }
2363
2375 private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2376 # Only 'createaccount' can be performed on special pages,
2377 # which don't actually exist in the DB.
2378 if ( $this->isSpecialPage() && $action !== 'createaccount' ) {
2379 $errors[] = [ 'ns-specialprotected' ];
2380 }
2381
2382 # Check $wgNamespaceProtection for restricted namespaces
2383 if ( $this->isNamespaceProtected( $user ) ) {
2384 $ns = $this->mNamespace == NS_MAIN ?
2385 wfMessage( 'nstab-main' )->text() : $this->getNsText();
2386 $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2387 [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2388 }
2389
2390 return $errors;
2391 }
2392
2404 private function checkSiteConfigPermissions( $action, $user, $errors, $rigor, $short ) {
2405 if ( $action != 'patrol' ) {
2406 $error = null;
2407 // Sitewide CSS/JSON/JS changes, like all NS_MEDIAWIKI changes, also require the
2408 // editinterface right. That's implemented as a restriction so no check needed here.
2409 if ( $this->isSiteCssConfigPage() && !$user->isAllowed( 'editsitecss' ) ) {
2410 $error = [ 'sitecssprotected', $action ];
2411 } elseif ( $this->isSiteJsonConfigPage() && !$user->isAllowed( 'editsitejson' ) ) {
2412 $error = [ 'sitejsonprotected', $action ];
2413 } elseif ( $this->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) {
2414 $error = [ 'sitejsprotected', $action ];
2415 } elseif ( $this->isRawHtmlMessage() ) {
2416 // Raw HTML can be used to deploy CSS or JS so require rights for both.
2417 if ( !$user->isAllowed( 'editsitejs' ) ) {
2418 $error = [ 'sitejsprotected', $action ];
2419 } elseif ( !$user->isAllowed( 'editsitecss' ) ) {
2420 $error = [ 'sitecssprotected', $action ];
2421 }
2422 }
2423
2424 if ( $error ) {
2425 if ( $user->isAllowed( 'editinterface' ) ) {
2426 // Most users / site admins will probably find out about the new, more restrictive
2427 // permissions by failing to edit something. Give them more info.
2428 // TODO remove this a few release cycles after 1.32
2429 $error = [ 'interfaceadmin-info', wfMessage( $error[0], $error[1] ) ];
2430 }
2431 $errors[] = $error;
2432 }
2433 }
2434
2435 return $errors;
2436 }
2437
2449 private function checkUserConfigPermissions( $action, $user, $errors, $rigor, $short ) {
2450 # Protect css/json/js subpages of user pages
2451 # XXX: this might be better using restrictions
2452
2453 if ( $action === 'patrol' ) {
2454 return $errors;
2455 }
2456
2457 if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2458 // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
2459 if (
2460 $this->isUserCssConfigPage()
2461 && !$user->isAllowedAny( 'editmyusercss', 'editusercss' )
2462 ) {
2463 $errors[] = [ 'mycustomcssprotected', $action ];
2464 } elseif (
2465 $this->isUserJsonConfigPage()
2466 && !$user->isAllowedAny( 'editmyuserjson', 'edituserjson' )
2467 ) {
2468 $errors[] = [ 'mycustomjsonprotected', $action ];
2469 } elseif (
2470 $this->isUserJsConfigPage()
2471 && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' )
2472 ) {
2473 $errors[] = [ 'mycustomjsprotected', $action ];
2474 }
2475 } else {
2476 // Users need editmyuser* to edit their own CSS/JSON/JS subpages, except for
2477 // deletion/suppression which cannot be used for attacks and we want to avoid the
2478 // situation where an unprivileged user can post abusive content on their subpages
2479 // and only very highly privileged users could remove it.
2480 if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
2481 if (
2482 $this->isUserCssConfigPage()
2483 && !$user->isAllowed( 'editusercss' )
2484 ) {
2485 $errors[] = [ 'customcssprotected', $action ];
2486 } elseif (
2487 $this->isUserJsonConfigPage()
2488 && !$user->isAllowed( 'edituserjson' )
2489 ) {
2490 $errors[] = [ 'customjsonprotected', $action ];
2491 } elseif (
2492 $this->isUserJsConfigPage()
2493 && !$user->isAllowed( 'edituserjs' )
2494 ) {
2495 $errors[] = [ 'customjsprotected', $action ];
2496 }
2497 }
2498 }
2499
2500 return $errors;
2501 }
2502
2516 private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2517 foreach ( $this->getRestrictions( $action ) as $right ) {
2518 // Backwards compatibility, rewrite sysop -> editprotected
2519 if ( $right == 'sysop' ) {
2520 $right = 'editprotected';
2521 }
2522 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2523 if ( $right == 'autoconfirmed' ) {
2524 $right = 'editsemiprotected';
2525 }
2526 if ( $right == '' ) {
2527 continue;
2528 }
2529 if ( !$user->isAllowed( $right ) ) {
2530 $errors[] = [ 'protectedpagetext', $right, $action ];
2531 } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2532 $errors[] = [ 'protectedpagetext', 'protect', $action ];
2533 }
2534 }
2535
2536 return $errors;
2537 }
2538
2550 private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2551 if ( $rigor !== 'quick' && !$this->isUserConfigPage() ) {
2552 # We /could/ use the protection level on the source page, but it's
2553 # fairly ugly as we have to establish a precedence hierarchy for pages
2554 # included by multiple cascade-protected pages. So just restrict
2555 # it to people with 'protect' permission, as they could remove the
2556 # protection anyway.
2557 list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2558 # Cascading protection depends on more than this page...
2559 # Several cascading protected pages may include this page...
2560 # Check each cascading level
2561 # This is only for protection restrictions, not for all actions
2562 if ( isset( $restrictions[$action] ) ) {
2563 foreach ( $restrictions[$action] as $right ) {
2564 // Backwards compatibility, rewrite sysop -> editprotected
2565 if ( $right == 'sysop' ) {
2566 $right = 'editprotected';
2567 }
2568 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2569 if ( $right == 'autoconfirmed' ) {
2570 $right = 'editsemiprotected';
2571 }
2572 if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2573 $pages = '';
2574 foreach ( $cascadingSources as $page ) {
2575 $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2576 }
2577 $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2578 }
2579 }
2580 }
2581 }
2582
2583 return $errors;
2584 }
2585
2597 private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2599
2600 if ( $action == 'protect' ) {
2601 if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2602 // If they can't edit, they shouldn't protect.
2603 $errors[] = [ 'protect-cantedit' ];
2604 }
2605 } elseif ( $action == 'create' ) {
2606 $title_protection = $this->getTitleProtection();
2607 if ( $title_protection ) {
2608 if ( $title_protection['permission'] == ''
2609 || !$user->isAllowed( $title_protection['permission'] )
2610 ) {
2611 $errors[] = [
2612 'titleprotected',
2613 User::whoIs( $title_protection['user'] ),
2614 $title_protection['reason']
2615 ];
2616 }
2617 }
2618 } elseif ( $action == 'move' ) {
2619 // Check for immobile pages
2620 if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2621 // Specific message for this case
2622 $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2623 } elseif ( !$this->isMovable() ) {
2624 // Less specific message for rarer cases
2625 $errors[] = [ 'immobile-source-page' ];
2626 }
2627 } elseif ( $action == 'move-target' ) {
2628 if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2629 $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2630 } elseif ( !$this->isMovable() ) {
2631 $errors[] = [ 'immobile-target-page' ];
2632 }
2633 } elseif ( $action == 'delete' ) {
2634 $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2635 if ( !$tempErrors ) {
2636 $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2637 $user, $tempErrors, $rigor, true );
2638 }
2639 if ( $tempErrors ) {
2640 // If protection keeps them from editing, they shouldn't be able to delete.
2641 $errors[] = [ 'deleteprotected' ];
2642 }
2643 if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2644 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2645 ) {
2646 $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2647 }
2648 } elseif ( $action === 'undelete' ) {
2649 if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2650 // Undeleting implies editing
2651 $errors[] = [ 'undelete-cantedit' ];
2652 }
2653 if ( !$this->exists()
2654 && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
2655 ) {
2656 // Undeleting where nothing currently exists implies creating
2657 $errors[] = [ 'undelete-cantcreate' ];
2658 }
2659 }
2660 return $errors;
2661 }
2662
2674 private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2676 // Account creation blocks handled at userlogin.
2677 // Unblocking handled in SpecialUnblock
2678 if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2679 return $errors;
2680 }
2681
2682 // Optimize for a very common case
2683 if ( $action === 'read' && !$wgBlockDisablesLogin ) {
2684 return $errors;
2685 }
2686
2688 && !$user->isEmailConfirmed()
2689 && $action === 'edit'
2690 ) {
2691 $errors[] = [ 'confirmedittext' ];
2692 }
2693
2694 $useSlave = ( $rigor !== 'secure' );
2695 if ( ( $action == 'edit' || $action == 'create' )
2696 && !$user->isBlockedFrom( $this, $useSlave )
2697 ) {
2698 // Don't block the user from editing their own talk page unless they've been
2699 // explicitly blocked from that too.
2700 } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2701 // @todo FIXME: Pass the relevant context into this function.
2702 $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2703 }
2704
2705 return $errors;
2706 }
2707
2719 private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2721
2722 $whitelisted = false;
2723 if ( User::isEveryoneAllowed( 'read' ) ) {
2724 # Shortcut for public wikis, allows skipping quite a bit of code
2725 $whitelisted = true;
2726 } elseif ( $user->isAllowed( 'read' ) ) {
2727 # If the user is allowed to read pages, he is allowed to read all pages
2728 $whitelisted = true;
2729 } elseif ( $this->isSpecial( 'Userlogin' )
2730 || $this->isSpecial( 'PasswordReset' )
2731 || $this->isSpecial( 'Userlogout' )
2732 ) {
2733 # Always grant access to the login page.
2734 # Even anons need to be able to log in.
2735 $whitelisted = true;
2736 } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2737 # Time to check the whitelist
2738 # Only do these checks is there's something to check against
2739 $name = $this->getPrefixedText();
2740 $dbName = $this->getPrefixedDBkey();
2741
2742 // Check for explicit whitelisting with and without underscores
2743 if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2744 $whitelisted = true;
2745 } elseif ( $this->mNamespace == NS_MAIN ) {
2746 # Old settings might have the title prefixed with
2747 # a colon for main-namespace pages
2748 if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2749 $whitelisted = true;
2750 }
2751 } elseif ( $this->isSpecialPage() ) {
2752 # If it's a special page, ditch the subpage bit and check again
2753 $name = $this->mDbkeyform;
2754 list( $name, /* $subpage */ ) =
2755 MediaWikiServices::getInstance()->getSpecialPageFactory()->
2756 resolveAlias( $name );
2757 if ( $name ) {
2758 $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2759 if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2760 $whitelisted = true;
2761 }
2762 }
2763 }
2764 }
2765
2766 if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2767 $name = $this->getPrefixedText();
2768 // Check for regex whitelisting
2769 foreach ( $wgWhitelistReadRegexp as $listItem ) {
2770 if ( preg_match( $listItem, $name ) ) {
2771 $whitelisted = true;
2772 break;
2773 }
2774 }
2775 }
2776
2777 if ( !$whitelisted ) {
2778 # If the title is not whitelisted, give extensions a chance to do so...
2779 Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2780 if ( !$whitelisted ) {
2781 $errors[] = $this->missingPermissionError( $action, $short );
2782 }
2783 }
2784
2785 return $errors;
2786 }
2787
2796 private function missingPermissionError( $action, $short ) {
2797 // We avoid expensive display logic for quickUserCan's and such
2798 if ( $short ) {
2799 return [ 'badaccess-group0' ];
2800 }
2801
2802 return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
2803 }
2804
2820 $action, $user, $rigor = 'secure', $short = false
2821 ) {
2822 if ( $rigor === true ) {
2823 $rigor = 'secure'; // b/c
2824 } elseif ( $rigor === false ) {
2825 $rigor = 'quick'; // b/c
2826 } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2827 throw new Exception( "Invalid rigor parameter '$rigor'." );
2828 }
2829
2830 # Read has special handling
2831 if ( $action == 'read' ) {
2832 $checks = [
2833 'checkPermissionHooks',
2834 'checkReadPermissions',
2835 'checkUserBlock', // for wgBlockDisablesLogin
2836 ];
2837 # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
2838 # or checkUserConfigPermissions here as it will lead to duplicate
2839 # error messages. This is okay to do since anywhere that checks for
2840 # create will also check for edit, and those checks are called for edit.
2841 } elseif ( $action == 'create' ) {
2842 $checks = [
2843 'checkQuickPermissions',
2844 'checkPermissionHooks',
2845 'checkPageRestrictions',
2846 'checkCascadingSourcesRestrictions',
2847 'checkActionPermissions',
2848 'checkUserBlock'
2849 ];
2850 } else {
2851 $checks = [
2852 'checkQuickPermissions',
2853 'checkPermissionHooks',
2854 'checkSpecialsAndNSPermissions',
2855 'checkSiteConfigPermissions',
2856 'checkUserConfigPermissions',
2857 'checkPageRestrictions',
2858 'checkCascadingSourcesRestrictions',
2859 'checkActionPermissions',
2860 'checkUserBlock'
2861 ];
2862 }
2863
2864 $errors = [];
2865 while ( count( $checks ) > 0 &&
2866 !( $short && count( $errors ) > 0 ) ) {
2867 $method = array_shift( $checks );
2868 $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2869 }
2870
2871 return $errors;
2872 }
2873
2881 public static function getFilteredRestrictionTypes( $exists = true ) {
2882 global $wgRestrictionTypes;
2883 $types = $wgRestrictionTypes;
2884 if ( $exists ) {
2885 # Remove the create restriction for existing titles
2886 $types = array_diff( $types, [ 'create' ] );
2887 } else {
2888 # Only the create and upload restrictions apply to non-existing titles
2889 $types = array_intersect( $types, [ 'create', 'upload' ] );
2890 }
2891 return $types;
2892 }
2893
2899 public function getRestrictionTypes() {
2900 if ( $this->isSpecialPage() ) {
2901 return [];
2902 }
2903
2904 $types = self::getFilteredRestrictionTypes( $this->exists() );
2905
2906 if ( $this->mNamespace != NS_FILE ) {
2907 # Remove the upload restriction for non-file titles
2908 $types = array_diff( $types, [ 'upload' ] );
2909 }
2910
2911 Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2912
2913 wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2914 $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2915
2916 return $types;
2917 }
2918
2926 public function getTitleProtection() {
2927 $protection = $this->getTitleProtectionInternal();
2928 if ( $protection ) {
2929 if ( $protection['permission'] == 'sysop' ) {
2930 $protection['permission'] = 'editprotected'; // B/C
2931 }
2932 if ( $protection['permission'] == 'autoconfirmed' ) {
2933 $protection['permission'] = 'editsemiprotected'; // B/C
2934 }
2935 }
2936 return $protection;
2937 }
2938
2949 protected function getTitleProtectionInternal() {
2950 // Can't protect pages in special namespaces
2951 if ( $this->mNamespace < 0 ) {
2952 return false;
2953 }
2954
2955 // Can't protect pages that exist.
2956 if ( $this->exists() ) {
2957 return false;
2958 }
2959
2960 if ( $this->mTitleProtection === null ) {
2961 $dbr = wfGetDB( DB_REPLICA );
2962 $commentStore = CommentStore::getStore();
2963 $commentQuery = $commentStore->getJoin( 'pt_reason' );
2964 $res = $dbr->select(
2965 [ 'protected_titles' ] + $commentQuery['tables'],
2966 [
2967 'user' => 'pt_user',
2968 'expiry' => 'pt_expiry',
2969 'permission' => 'pt_create_perm'
2970 ] + $commentQuery['fields'],
2971 [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2972 __METHOD__,
2973 [],
2974 $commentQuery['joins']
2975 );
2976
2977 // fetchRow returns false if there are no rows.
2978 $row = $dbr->fetchRow( $res );
2979 if ( $row ) {
2980 $this->mTitleProtection = [
2981 'user' => $row['user'],
2982 'expiry' => $dbr->decodeExpiry( $row['expiry'] ),
2983 'permission' => $row['permission'],
2984 'reason' => $commentStore->getComment( 'pt_reason', $row )->text,
2985 ];
2986 } else {
2987 $this->mTitleProtection = false;
2988 }
2989 }
2990 return $this->mTitleProtection;
2991 }
2992
2996 public function deleteTitleProtection() {
2997 $dbw = wfGetDB( DB_MASTER );
2998
2999 $dbw->delete(
3000 'protected_titles',
3001 [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
3002 __METHOD__
3003 );
3004 $this->mTitleProtection = false;
3005 }
3006
3014 public function isSemiProtected( $action = 'edit' ) {
3016
3017 $restrictions = $this->getRestrictions( $action );
3019 if ( !$restrictions || !$semi ) {
3020 // Not protected, or all protection is full protection
3021 return false;
3022 }
3023
3024 // Remap autoconfirmed to editsemiprotected for BC
3025 foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
3026 $semi[$key] = 'editsemiprotected';
3027 }
3028 foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
3029 $restrictions[$key] = 'editsemiprotected';
3030 }
3031
3032 return !array_diff( $restrictions, $semi );
3033 }
3034
3042 public function isProtected( $action = '' ) {
3043 global $wgRestrictionLevels;
3044
3045 $restrictionTypes = $this->getRestrictionTypes();
3046
3047 # Special pages have inherent protection
3048 if ( $this->isSpecialPage() ) {
3049 return true;
3050 }
3051
3052 # Check regular protection levels
3053 foreach ( $restrictionTypes as $type ) {
3054 if ( $action == $type || $action == '' ) {
3055 $r = $this->getRestrictions( $type );
3056 foreach ( $wgRestrictionLevels as $level ) {
3057 if ( in_array( $level, $r ) && $level != '' ) {
3058 return true;
3059 }
3060 }
3061 }
3062 }
3063
3064 return false;
3065 }
3066
3074 public function isNamespaceProtected( User $user ) {
3076
3077 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
3078 foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
3079 if ( $right != '' && !$user->isAllowed( $right ) ) {
3080 return true;
3081 }
3082 }
3083 }
3084 return false;
3085 }
3086
3092 public function isCascadeProtected() {
3093 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
3094 return ( $sources > 0 );
3095 }
3096
3106 public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
3107 return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
3108 }
3109
3123 public function getCascadeProtectionSources( $getPages = true ) {
3124 $pagerestrictions = [];
3125
3126 if ( $this->mCascadeSources !== null && $getPages ) {
3127 return [ $this->mCascadeSources, $this->mCascadingRestrictions ];
3128 } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
3129 return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
3130 }
3131
3132 $dbr = wfGetDB( DB_REPLICA );
3133
3134 if ( $this->mNamespace == NS_FILE ) {
3135 $tables = [ 'imagelinks', 'page_restrictions' ];
3136 $where_clauses = [
3137 'il_to' => $this->mDbkeyform,
3138 'il_from=pr_page',
3139 'pr_cascade' => 1
3140 ];
3141 } else {
3142 $tables = [ 'templatelinks', 'page_restrictions' ];
3143 $where_clauses = [
3144 'tl_namespace' => $this->mNamespace,
3145 'tl_title' => $this->mDbkeyform,
3146 'tl_from=pr_page',
3147 'pr_cascade' => 1
3148 ];
3149 }
3150
3151 if ( $getPages ) {
3152 $cols = [ 'pr_page', 'page_namespace', 'page_title',
3153 'pr_expiry', 'pr_type', 'pr_level' ];
3154 $where_clauses[] = 'page_id=pr_page';
3155 $tables[] = 'page';
3156 } else {
3157 $cols = [ 'pr_expiry' ];
3158 }
3159
3160 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
3161
3162 $sources = $getPages ? [] : false;
3163 $now = wfTimestampNow();
3164
3165 foreach ( $res as $row ) {
3166 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
3167 if ( $expiry > $now ) {
3168 if ( $getPages ) {
3169 $page_id = $row->pr_page;
3170 $page_ns = $row->page_namespace;
3171 $page_title = $row->page_title;
3172 $sources[$page_id] = self::makeTitle( $page_ns, $page_title );
3173 # Add groups needed for each restriction type if its not already there
3174 # Make sure this restriction type still exists
3175
3176 if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
3177 $pagerestrictions[$row->pr_type] = [];
3178 }
3179
3180 if (
3181 isset( $pagerestrictions[$row->pr_type] )
3182 && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
3183 ) {
3184 $pagerestrictions[$row->pr_type][] = $row->pr_level;
3185 }
3186 } else {
3187 $sources = true;
3188 }
3189 }
3190 }
3191
3192 if ( $getPages ) {
3193 $this->mCascadeSources = $sources;
3194 $this->mCascadingRestrictions = $pagerestrictions;
3195 } else {
3196 $this->mHasCascadingRestrictions = $sources;
3197 }
3198
3199 return [ $sources, $pagerestrictions ];
3200 }
3201
3209 public function areRestrictionsLoaded() {
3210 return $this->mRestrictionsLoaded;
3211 }
3212
3222 public function getRestrictions( $action ) {
3223 if ( !$this->mRestrictionsLoaded ) {
3224 $this->loadRestrictions();
3225 }
3226 return $this->mRestrictions[$action] ?? [];
3227 }
3228
3236 public function getAllRestrictions() {
3237 if ( !$this->mRestrictionsLoaded ) {
3238 $this->loadRestrictions();
3239 }
3240 return $this->mRestrictions;
3241 }
3242
3250 public function getRestrictionExpiry( $action ) {
3251 if ( !$this->mRestrictionsLoaded ) {
3252 $this->loadRestrictions();
3253 }
3254 return $this->mRestrictionsExpiry[$action] ?? false;
3255 }
3256
3263 if ( !$this->mRestrictionsLoaded ) {
3264 $this->loadRestrictions();
3265 }
3266
3267 return $this->mCascadeRestriction;
3268 }
3269
3281 public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
3282 $dbr = wfGetDB( DB_REPLICA );
3283
3284 $restrictionTypes = $this->getRestrictionTypes();
3285
3286 foreach ( $restrictionTypes as $type ) {
3287 $this->mRestrictions[$type] = [];
3288 $this->mRestrictionsExpiry[$type] = 'infinity';
3289 }
3290
3291 $this->mCascadeRestriction = false;
3292
3293 # Backwards-compatibility: also load the restrictions from the page record (old format).
3294 if ( $oldFashionedRestrictions !== null ) {
3295 $this->mOldRestrictions = $oldFashionedRestrictions;
3296 }
3297
3298 if ( $this->mOldRestrictions === false ) {
3299 $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
3300 [ 'page_id' => $this->getArticleID() ], __METHOD__ );
3301 }
3302
3303 if ( $this->mOldRestrictions != '' ) {
3304 foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
3305 $temp = explode( '=', trim( $restrict ) );
3306 if ( count( $temp ) == 1 ) {
3307 // old old format should be treated as edit/move restriction
3308 $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
3309 $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
3310 } else {
3311 $restriction = trim( $temp[1] );
3312 if ( $restriction != '' ) { // some old entries are empty
3313 $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
3314 }
3315 }
3316 }
3317 }
3318
3319 if ( count( $rows ) ) {
3320 # Current system - load second to make them override.
3321 $now = wfTimestampNow();
3322
3323 # Cycle through all the restrictions.
3324 foreach ( $rows as $row ) {
3325 // Don't take care of restrictions types that aren't allowed
3326 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
3327 continue;
3328 }
3329
3330 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
3331
3332 // Only apply the restrictions if they haven't expired!
3333 if ( !$expiry || $expiry > $now ) {
3334 $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
3335 $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
3336
3337 $this->mCascadeRestriction |= $row->pr_cascade;
3338 }
3339 }
3340 }
3341
3342 $this->mRestrictionsLoaded = true;
3343 }
3344
3353 public function loadRestrictions( $oldFashionedRestrictions = null ) {
3354 if ( $this->mRestrictionsLoaded ) {
3355 return;
3356 }
3357
3358 $id = $this->getArticleID();
3359 if ( $id ) {
3360 $cache = ObjectCache::getMainWANInstance();
3361 $fname = __METHOD__;
3362 $rows = $cache->getWithSetCallback(
3363 // Page protections always leave a new null revision
3364 $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ),
3365 $cache::TTL_DAY,
3366 function ( $curValue, &$ttl, array &$setOpts ) use ( $fname ) {
3367 $dbr = wfGetDB( DB_REPLICA );
3368
3369 $setOpts += Database::getCacheSetOptions( $dbr );
3370
3371 return iterator_to_array(
3372 $dbr->select(
3373 'page_restrictions',
3374 [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
3375 [ 'pr_page' => $this->getArticleID() ],
3376 $fname
3377 )
3378 );
3379 }
3380 );
3381
3382 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
3383 } else {
3384 $title_protection = $this->getTitleProtectionInternal();
3385
3386 if ( $title_protection ) {
3387 $now = wfTimestampNow();
3388 $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
3389
3390 if ( !$expiry || $expiry > $now ) {
3391 // Apply the restrictions
3392 $this->mRestrictionsExpiry['create'] = $expiry;
3393 $this->mRestrictions['create'] =
3394 explode( ',', trim( $title_protection['permission'] ) );
3395 } else { // Get rid of the old restrictions
3396 $this->mTitleProtection = false;
3397 }
3398 } else {
3399 $this->mRestrictionsExpiry['create'] = 'infinity';
3400 }
3401 $this->mRestrictionsLoaded = true;
3402 }
3403 }
3404
3409 public function flushRestrictions() {
3410 $this->mRestrictionsLoaded = false;
3411 $this->mTitleProtection = null;
3412 }
3413
3419 static function purgeExpiredRestrictions() {
3420 if ( wfReadOnly() ) {
3421 return;
3422 }
3423
3424 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
3425 wfGetDB( DB_MASTER ),
3426 __METHOD__,
3427 function ( IDatabase $dbw, $fname ) {
3428 $config = MediaWikiServices::getInstance()->getMainConfig();
3429 $ids = $dbw->selectFieldValues(
3430 'page_restrictions',
3431 'pr_id',
3432 [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3433 $fname,
3434 [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
3435 );
3436 if ( $ids ) {
3437 $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
3438 }
3439 }
3440 ) );
3441
3442 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
3443 wfGetDB( DB_MASTER ),
3444 __METHOD__,
3445 function ( IDatabase $dbw, $fname ) {
3446 $dbw->delete(
3447 'protected_titles',
3448 [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3449 $fname
3450 );
3451 }
3452 ) );
3453 }
3454
3460 public function hasSubpages() {
3461 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3462 # Duh
3463 return false;
3464 }
3465
3466 # We dynamically add a member variable for the purpose of this method
3467 # alone to cache the result. There's no point in having it hanging
3468 # around uninitialized in every Title object; therefore we only add it
3469 # if needed and don't declare it statically.
3470 if ( $this->mHasSubpages === null ) {
3471 $this->mHasSubpages = false;
3472 $subpages = $this->getSubpages( 1 );
3473 if ( $subpages instanceof TitleArray ) {
3474 $this->mHasSubpages = (bool)$subpages->count();
3475 }
3476 }
3477
3478 return $this->mHasSubpages;
3479 }
3480
3488 public function getSubpages( $limit = -1 ) {
3489 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3490 return [];
3491 }
3492
3493 $dbr = wfGetDB( DB_REPLICA );
3494 $conds['page_namespace'] = $this->mNamespace;
3495 $conds[] = 'page_title ' . $dbr->buildLike( $this->mDbkeyform . '/', $dbr->anyString() );
3496 $options = [];
3497 if ( $limit > -1 ) {
3498 $options['LIMIT'] = $limit;
3499 }
3501 $dbr->select( 'page',
3502 [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3503 $conds,
3504 __METHOD__,
3505 $options
3506 )
3507 );
3508 }
3509
3515 public function isDeleted() {
3516 if ( $this->mNamespace < 0 ) {
3517 $n = 0;
3518 } else {
3519 $dbr = wfGetDB( DB_REPLICA );
3520
3521 $n = $dbr->selectField( 'archive', 'COUNT(*)',
3522 [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
3523 __METHOD__
3524 );
3525 if ( $this->mNamespace == NS_FILE ) {
3526 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3527 [ 'fa_name' => $this->mDbkeyform ],
3528 __METHOD__
3529 );
3530 }
3531 }
3532 return (int)$n;
3533 }
3534
3540 public function isDeletedQuick() {
3541 if ( $this->mNamespace < 0 ) {
3542 return false;
3543 }
3544 $dbr = wfGetDB( DB_REPLICA );
3545 $deleted = (bool)$dbr->selectField( 'archive', '1',
3546 [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
3547 __METHOD__
3548 );
3549 if ( !$deleted && $this->mNamespace == NS_FILE ) {
3550 $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3551 [ 'fa_name' => $this->mDbkeyform ],
3552 __METHOD__
3553 );
3554 }
3555 return $deleted;
3556 }
3557
3566 public function getArticleID( $flags = 0 ) {
3567 if ( $this->mNamespace < 0 ) {
3568 $this->mArticleID = 0;
3569 return $this->mArticleID;
3570 }
3571 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3572 if ( $flags & self::GAID_FOR_UPDATE ) {
3573 $oldUpdate = $linkCache->forUpdate( true );
3574 $linkCache->clearLink( $this );
3575 $this->mArticleID = $linkCache->addLinkObj( $this );
3576 $linkCache->forUpdate( $oldUpdate );
3577 } else {
3578 if ( -1 == $this->mArticleID ) {
3579 $this->mArticleID = $linkCache->addLinkObj( $this );
3580 }
3581 }
3582 return $this->mArticleID;
3583 }
3584
3592 public function isRedirect( $flags = 0 ) {
3593 if ( !is_null( $this->mRedirect ) ) {
3594 return $this->mRedirect;
3595 }
3596 if ( !$this->getArticleID( $flags ) ) {
3597 $this->mRedirect = false;
3598 return $this->mRedirect;
3599 }
3600
3601 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3602 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3603 $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3604 if ( $cached === null ) {
3605 # Trust LinkCache's state over our own
3606 # LinkCache is telling us that the page doesn't exist, despite there being cached
3607 # data relating to an existing page in $this->mArticleID. Updaters should clear
3608 # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3609 # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3610 # LinkCache to refresh its data from the master.
3611 $this->mRedirect = false;
3612 return $this->mRedirect;
3613 }
3614
3615 $this->mRedirect = (bool)$cached;
3616
3617 return $this->mRedirect;
3618 }
3619
3627 public function getLength( $flags = 0 ) {
3628 if ( $this->mLength != -1 ) {
3629 return $this->mLength;
3630 }
3631 if ( !$this->getArticleID( $flags ) ) {
3632 $this->mLength = 0;
3633 return $this->mLength;
3634 }
3635 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3636 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3637 $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3638 if ( $cached === null ) {
3639 # Trust LinkCache's state over our own, as for isRedirect()
3640 $this->mLength = 0;
3641 return $this->mLength;
3642 }
3643
3644 $this->mLength = intval( $cached );
3645
3646 return $this->mLength;
3647 }
3648
3655 public function getLatestRevID( $flags = 0 ) {
3656 if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3657 return intval( $this->mLatestID );
3658 }
3659 if ( !$this->getArticleID( $flags ) ) {
3660 $this->mLatestID = 0;
3661 return $this->mLatestID;
3662 }
3663 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3664 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3665 $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3666 if ( $cached === null ) {
3667 # Trust LinkCache's state over our own, as for isRedirect()
3668 $this->mLatestID = 0;
3669 return $this->mLatestID;
3670 }
3671
3672 $this->mLatestID = intval( $cached );
3673
3674 return $this->mLatestID;
3675 }
3676
3687 public function resetArticleID( $newid ) {
3688 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3689 $linkCache->clearLink( $this );
3690
3691 if ( $newid === false ) {
3692 $this->mArticleID = -1;
3693 } else {
3694 $this->mArticleID = intval( $newid );
3695 }
3696 $this->mRestrictionsLoaded = false;
3697 $this->mRestrictions = [];
3698 $this->mOldRestrictions = false;
3699 $this->mRedirect = null;
3700 $this->mLength = -1;
3701 $this->mLatestID = false;
3702 $this->mContentModel = false;
3703 $this->mEstimateRevisions = null;
3704 $this->mPageLanguage = false;
3705 $this->mDbPageLanguage = false;
3706 $this->mIsBigDeletion = null;
3707 }
3708
3709 public static function clearCaches() {
3710 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3711 $linkCache->clear();
3712
3713 $titleCache = self::getTitleCache();
3714 $titleCache->clear();
3715 }
3716
3724 public static function capitalize( $text, $ns = NS_MAIN ) {
3725 if ( MWNamespace::isCapitalized( $ns ) ) {
3726 return MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $text );
3727 } else {
3728 return $text;
3729 }
3730 }
3731
3744 private function secureAndSplit() {
3745 // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3746 // the parsing code with Title, while avoiding massive refactoring.
3747 // @todo: get rid of secureAndSplit, refactor parsing code.
3748 // @note: getTitleParser() returns a TitleParser implementation which does not have a
3749 // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3750 $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3751 // MalformedTitleException can be thrown here
3752 $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace );
3753
3754 # Fill fields
3755 $this->setFragment( '#' . $parts['fragment'] );
3756 $this->mInterwiki = $parts['interwiki'];
3757 $this->mLocalInterwiki = $parts['local_interwiki'];
3758 $this->mNamespace = $parts['namespace'];
3759 $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3760
3761 $this->mDbkeyform = $parts['dbkey'];
3762 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3763 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3764
3765 # We already know that some pages won't be in the database!
3766 if ( $this->isExternal() || $this->isSpecialPage() ) {
3767 $this->mArticleID = 0;
3768 }
3769
3770 return true;
3771 }
3772
3785 public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3786 if ( count( $options ) > 0 ) {
3787 $db = wfGetDB( DB_MASTER );
3788 } else {
3789 $db = wfGetDB( DB_REPLICA );
3790 }
3791
3792 $res = $db->select(
3793 [ 'page', $table ],
3794 self::getSelectFields(),
3795 [
3796 "{$prefix}_from=page_id",
3797 "{$prefix}_namespace" => $this->mNamespace,
3798 "{$prefix}_title" => $this->mDbkeyform ],
3799 __METHOD__,
3800 $options
3801 );
3802
3803 $retVal = [];
3804 if ( $res->numRows() ) {
3805 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3806 foreach ( $res as $row ) {
3807 $titleObj = self::makeTitle( $row->page_namespace, $row->page_title );
3808 if ( $titleObj ) {
3809 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3810 $retVal[] = $titleObj;
3811 }
3812 }
3813 }
3814 return $retVal;
3815 }
3816
3827 public function getTemplateLinksTo( $options = [] ) {
3828 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3829 }
3830
3843 public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3844 $id = $this->getArticleID();
3845
3846 # If the page doesn't exist; there can't be any link from this page
3847 if ( !$id ) {
3848 return [];
3849 }
3850
3851 $db = wfGetDB( DB_REPLICA );
3852
3853 $blNamespace = "{$prefix}_namespace";
3854 $blTitle = "{$prefix}_title";
3855
3856 $pageQuery = WikiPage::getQueryInfo();
3857 $res = $db->select(
3858 [ $table, 'nestpage' => $pageQuery['tables'] ],
3859 array_merge(
3860 [ $blNamespace, $blTitle ],
3861 $pageQuery['fields']
3862 ),
3863 [ "{$prefix}_from" => $id ],
3864 __METHOD__,
3865 $options,
3866 [ 'nestpage' => [
3867 'LEFT JOIN',
3868 [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3869 ] ] + $pageQuery['joins']
3870 );
3871
3872 $retVal = [];
3873 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3874 foreach ( $res as $row ) {
3875 if ( $row->page_id ) {
3876 $titleObj = self::newFromRow( $row );
3877 } else {
3878 $titleObj = self::makeTitle( $row->$blNamespace, $row->$blTitle );
3879 $linkCache->addBadLinkObj( $titleObj );
3880 }
3881 $retVal[] = $titleObj;
3882 }
3883
3884 return $retVal;
3885 }
3886
3897 public function getTemplateLinksFrom( $options = [] ) {
3898 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3899 }
3900
3909 public function getBrokenLinksFrom() {
3910 if ( $this->getArticleID() == 0 ) {
3911 # All links from article ID 0 are false positives
3912 return [];
3913 }
3914
3915 $dbr = wfGetDB( DB_REPLICA );
3916 $res = $dbr->select(
3917 [ 'page', 'pagelinks' ],
3918 [ 'pl_namespace', 'pl_title' ],
3919 [
3920 'pl_from' => $this->getArticleID(),
3921 'page_namespace IS NULL'
3922 ],
3923 __METHOD__, [],
3924 [
3925 'page' => [
3926 'LEFT JOIN',
3927 [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3928 ]
3929 ]
3930 );
3931
3932 $retVal = [];
3933 foreach ( $res as $row ) {
3934 $retVal[] = self::makeTitle( $row->pl_namespace, $row->pl_title );
3935 }
3936 return $retVal;
3937 }
3938
3945 public function getCdnUrls() {
3946 $urls = [
3947 $this->getInternalURL(),
3948 $this->getInternalURL( 'action=history' )
3949 ];
3950
3951 $pageLang = $this->getPageLanguage();
3952 if ( $pageLang->hasVariants() ) {
3953 $variants = $pageLang->getVariants();
3954 foreach ( $variants as $vCode ) {
3955 $urls[] = $this->getInternalURL( $vCode );
3956 }
3957 }
3958
3959 // If we are looking at a css/js user subpage, purge the action=raw.
3960 if ( $this->isUserJsConfigPage() ) {
3961 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3962 } elseif ( $this->isUserJsonConfigPage() ) {
3963 $urls[] = $this->getInternalURL( 'action=raw&ctype=application/json' );
3964 } elseif ( $this->isUserCssConfigPage() ) {
3965 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3966 }
3967
3968 Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3969 return $urls;
3970 }
3971
3975 public function getSquidURLs() {
3976 return $this->getCdnUrls();
3977 }
3978
3982 public function purgeSquid() {
3983 DeferredUpdates::addUpdate(
3984 new CdnCacheUpdate( $this->getCdnUrls() ),
3985 DeferredUpdates::PRESEND
3986 );
3987 }
3988
3999 public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
4000 global $wgUser;
4001
4002 if ( !( $nt instanceof Title ) ) {
4003 // Normally we'd add this to $errors, but we'll get
4004 // lots of syntax errors if $nt is not an object
4005 return [ [ 'badtitletext' ] ];
4006 }
4007
4008 $mp = new MovePage( $this, $nt );
4009 $errors = $mp->isValidMove()->getErrorsArray();
4010 if ( $auth ) {
4011 $errors = wfMergeErrorArrays(
4012 $errors,
4013 $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
4014 );
4015 }
4016
4017 return $errors ?: true;
4018 }
4019
4026 protected function validateFileMoveOperation( $nt ) {
4027 global $wgUser;
4028
4029 $errors = [];
4030
4031 $destFile = wfLocalFile( $nt );
4032 $destFile->load( File::READ_LATEST );
4033 if ( !$wgUser->isAllowed( 'reupload-shared' )
4034 && !$destFile->exists() && wfFindFile( $nt )
4035 ) {
4036 $errors[] = [ 'file-exists-sharedrepo' ];
4037 }
4038
4039 return $errors;
4040 }
4041
4055 public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true,
4056 array $changeTags = []
4057 ) {
4058 global $wgUser;
4059 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
4060 if ( is_array( $err ) ) {
4061 // Auto-block user's IP if the account was "hard" blocked
4062 $wgUser->spreadAnyEditBlock();
4063 return $err;
4064 }
4065 // Check suppressredirect permission
4066 if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
4067 $createRedirect = true;
4068 }
4069
4070 $mp = new MovePage( $this, $nt );
4071 $status = $mp->move( $wgUser, $reason, $createRedirect, $changeTags );
4072 if ( $status->isOK() ) {
4073 return true;
4074 } else {
4075 return $status->getErrorsArray();
4076 }
4077 }
4078
4093 public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true,
4094 array $changeTags = []
4095 ) {
4096 global $wgMaximumMovedPages;
4097 // Check permissions
4098 if ( !$this->userCan( 'move-subpages' ) ) {
4099 return [
4100 [ 'cant-move-subpages' ],
4101 ];
4102 }
4103 // Do the source and target namespaces support subpages?
4104 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
4105 return [
4106 [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $this->mNamespace ) ],
4107 ];
4108 }
4109 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
4110 return [
4111 [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $nt->getNamespace() ) ],
4112 ];
4113 }
4114
4115 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
4116 $retval = [];
4117 $count = 0;
4118 foreach ( $subpages as $oldSubpage ) {
4119 $count++;
4120 if ( $count > $wgMaximumMovedPages ) {
4121 $retval[$oldSubpage->getPrefixedText()] = [
4122 [ 'movepage-max-pages', $wgMaximumMovedPages ],
4123 ];
4124 break;
4125 }
4126
4127 // We don't know whether this function was called before
4128 // or after moving the root page, so check both
4129 // $this and $nt
4130 if ( $oldSubpage->getArticleID() == $this->getArticleID()
4131 || $oldSubpage->getArticleID() == $nt->getArticleID()
4132 ) {
4133 // When moving a page to a subpage of itself,
4134 // don't move it twice
4135 continue;
4136 }
4137 $newPageName = preg_replace(
4138 '#^' . preg_quote( $this->mDbkeyform, '#' ) . '#',
4139 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
4140 $oldSubpage->getDBkey() );
4141 if ( $oldSubpage->isTalkPage() ) {
4142 $newNs = $nt->getTalkPage()->getNamespace();
4143 } else {
4144 $newNs = $nt->getSubjectPage()->getNamespace();
4145 }
4146 # T16385: we need makeTitleSafe because the new page names may
4147 # be longer than 255 characters.
4148 $newSubpage = self::makeTitleSafe( $newNs, $newPageName );
4149
4150 $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect, $changeTags );
4151 if ( $success === true ) {
4152 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
4153 } else {
4154 $retval[$oldSubpage->getPrefixedText()] = $success;
4155 }
4156 }
4157 return $retval;
4158 }
4159
4166 public function isSingleRevRedirect() {
4168
4169 $dbw = wfGetDB( DB_MASTER );
4170
4171 # Is it a redirect?
4172 $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
4173 if ( $wgContentHandlerUseDB ) {
4174 $fields[] = 'page_content_model';
4175 }
4176
4177 $row = $dbw->selectRow( 'page',
4178 $fields,
4179 $this->pageCond(),
4180 __METHOD__,
4181 [ 'FOR UPDATE' ]
4182 );
4183 # Cache some fields we may want
4184 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
4185 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
4186 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
4187 $this->mContentModel = $row && isset( $row->page_content_model )
4188 ? strval( $row->page_content_model )
4189 : false;
4190
4191 if ( !$this->mRedirect ) {
4192 return false;
4193 }
4194 # Does the article have a history?
4195 $row = $dbw->selectField( [ 'page', 'revision' ],
4196 'rev_id',
4197 [ 'page_namespace' => $this->mNamespace,
4198 'page_title' => $this->mDbkeyform,
4199 'page_id=rev_page',
4200 'page_latest != rev_id'
4201 ],
4202 __METHOD__,
4203 [ 'FOR UPDATE' ]
4204 );
4205 # Return true if there was no history
4206 return ( $row === false );
4207 }
4208
4217 public function isValidMoveTarget( $nt ) {
4218 # Is it an existing file?
4219 if ( $nt->getNamespace() == NS_FILE ) {
4220 $file = wfLocalFile( $nt );
4221 $file->load( File::READ_LATEST );
4222 if ( $file->exists() ) {
4223 wfDebug( __METHOD__ . ": file exists\n" );
4224 return false;
4225 }
4226 }
4227 # Is it a redirect with no history?
4228 if ( !$nt->isSingleRevRedirect() ) {
4229 wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
4230 return false;
4231 }
4232 # Get the article text
4233 $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
4234 if ( !is_object( $rev ) ) {
4235 return false;
4236 }
4237 $content = $rev->getContent();
4238 # Does the redirect point to the source?
4239 # Or is it a broken self-redirect, usually caused by namespace collisions?
4240 $redirTitle = $content ? $content->getRedirectTarget() : null;
4241
4242 if ( $redirTitle ) {
4243 if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
4244 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
4245 wfDebug( __METHOD__ . ": redirect points to other page\n" );
4246 return false;
4247 } else {
4248 return true;
4249 }
4250 } else {
4251 # Fail safe (not a redirect after all. strange.)
4252 wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
4253 " is a redirect, but it doesn't contain a valid redirect.\n" );
4254 return false;
4255 }
4256 }
4257
4265 public function getParentCategories() {
4266 $data = [];
4267
4268 $titleKey = $this->getArticleID();
4269
4270 if ( $titleKey === 0 ) {
4271 return $data;
4272 }
4273
4274 $dbr = wfGetDB( DB_REPLICA );
4275
4276 $res = $dbr->select(
4277 'categorylinks',
4278 'cl_to',
4279 [ 'cl_from' => $titleKey ],
4280 __METHOD__
4281 );
4282
4283 if ( $res->numRows() > 0 ) {
4284 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
4285 foreach ( $res as $row ) {
4286 // $data[] = Title::newFromText( $contLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
4287 $data[$contLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] =
4288 $this->getFullText();
4289 }
4290 }
4291 return $data;
4292 }
4293
4300 public function getParentCategoryTree( $children = [] ) {
4301 $stack = [];
4302 $parents = $this->getParentCategories();
4303
4304 if ( $parents ) {
4305 foreach ( $parents as $parent => $current ) {
4306 if ( array_key_exists( $parent, $children ) ) {
4307 # Circular reference
4308 $stack[$parent] = [];
4309 } else {
4310 $nt = self::newFromText( $parent );
4311 if ( $nt ) {
4312 $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
4313 }
4314 }
4315 }
4316 }
4317
4318 return $stack;
4319 }
4320
4327 public function pageCond() {
4328 if ( $this->mArticleID > 0 ) {
4329 // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
4330 return [ 'page_id' => $this->mArticleID ];
4331 } else {
4332 return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
4333 }
4334 }
4335
4343 private function getRelativeRevisionID( $revId, $flags, $dir ) {
4344 $revId = (int)$revId;
4345 if ( $dir === 'next' ) {
4346 $op = '>';
4347 $sort = 'ASC';
4348 } elseif ( $dir === 'prev' ) {
4349 $op = '<';
4350 $sort = 'DESC';
4351 } else {
4352 throw new InvalidArgumentException( '$dir must be "next" or "prev"' );
4353 }
4354
4355 if ( $flags & self::GAID_FOR_UPDATE ) {
4356 $db = wfGetDB( DB_MASTER );
4357 } else {
4358 $db = wfGetDB( DB_REPLICA, 'contributions' );
4359 }
4360
4361 // Intentionally not caring if the specified revision belongs to this
4362 // page. We only care about the timestamp.
4363 $ts = $db->selectField( 'revision', 'rev_timestamp', [ 'rev_id' => $revId ], __METHOD__ );
4364 if ( $ts === false ) {
4365 $ts = $db->selectField( 'archive', 'ar_timestamp', [ 'ar_rev_id' => $revId ], __METHOD__ );
4366 if ( $ts === false ) {
4367 // Or should this throw an InvalidArgumentException or something?
4368 return false;
4369 }
4370 }
4371 $ts = $db->addQuotes( $ts );
4372
4373 $revId = $db->selectField( 'revision', 'rev_id',
4374 [
4375 'rev_page' => $this->getArticleID( $flags ),
4376 "rev_timestamp $op $ts OR (rev_timestamp = $ts AND rev_id $op $revId)"
4377 ],
4378 __METHOD__,
4379 [
4380 'ORDER BY' => "rev_timestamp $sort, rev_id $sort",
4381 'IGNORE INDEX' => 'rev_timestamp', // Probably needed for T159319
4382 ]
4383 );
4384
4385 if ( $revId === false ) {
4386 return false;
4387 } else {
4388 return intval( $revId );
4389 }
4390 }
4391
4399 public function getPreviousRevisionID( $revId, $flags = 0 ) {
4400 return $this->getRelativeRevisionID( $revId, $flags, 'prev' );
4401 }
4402
4410 public function getNextRevisionID( $revId, $flags = 0 ) {
4411 return $this->getRelativeRevisionID( $revId, $flags, 'next' );
4412 }
4413
4420 public function getFirstRevision( $flags = 0 ) {
4421 $pageId = $this->getArticleID( $flags );
4422 if ( $pageId ) {
4423 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
4425 $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'],
4426 [ 'rev_page' => $pageId ],
4427 __METHOD__,
4428 [
4429 'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
4430 'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
4431 ],
4432 $revQuery['joins']
4433 );
4434 if ( $row ) {
4435 return new Revision( $row, 0, $this );
4436 }
4437 }
4438 return null;
4439 }
4440
4447 public function getEarliestRevTime( $flags = 0 ) {
4448 $rev = $this->getFirstRevision( $flags );
4449 return $rev ? $rev->getTimestamp() : null;
4450 }
4451
4457 public function isNewPage() {
4458 $dbr = wfGetDB( DB_REPLICA );
4459 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4460 }
4461
4467 public function isBigDeletion() {
4469
4470 if ( !$wgDeleteRevisionsLimit ) {
4471 return false;
4472 }
4473
4474 if ( $this->mIsBigDeletion === null ) {
4475 $dbr = wfGetDB( DB_REPLICA );
4476
4477 $revCount = $dbr->selectRowCount(
4478 'revision',
4479 '1',
4480 [ 'rev_page' => $this->getArticleID() ],
4481 __METHOD__,
4482 [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4483 );
4484
4485 $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4486 }
4487
4488 return $this->mIsBigDeletion;
4489 }
4490
4496 public function estimateRevisionCount() {
4497 if ( !$this->exists() ) {
4498 return 0;
4499 }
4500
4501 if ( $this->mEstimateRevisions === null ) {
4502 $dbr = wfGetDB( DB_REPLICA );
4503 $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4504 [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4505 }
4506
4507 return $this->mEstimateRevisions;
4508 }
4509
4519 public function countRevisionsBetween( $old, $new, $max = null ) {
4520 if ( !( $old instanceof Revision ) ) {
4521 $old = Revision::newFromTitle( $this, (int)$old );
4522 }
4523 if ( !( $new instanceof Revision ) ) {
4524 $new = Revision::newFromTitle( $this, (int)$new );
4525 }
4526 if ( !$old || !$new ) {
4527 return 0; // nothing to compare
4528 }
4529 $dbr = wfGetDB( DB_REPLICA );
4530 $conds = [
4531 'rev_page' => $this->getArticleID(),
4532 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4533 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4534 ];
4535 if ( $max !== null ) {
4536 return $dbr->selectRowCount( 'revision', '1',
4537 $conds,
4538 __METHOD__,
4539 [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4540 );
4541 } else {
4542 return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4543 }
4544 }
4545
4562 public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4563 if ( !( $old instanceof Revision ) ) {
4564 $old = Revision::newFromTitle( $this, (int)$old );
4565 }
4566 if ( !( $new instanceof Revision ) ) {
4567 $new = Revision::newFromTitle( $this, (int)$new );
4568 }
4569 // XXX: what if Revision objects are passed in, but they don't refer to this title?
4570 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4571 // in the sanity check below?
4572 if ( !$old || !$new ) {
4573 return null; // nothing to compare
4574 }
4575 $authors = [];
4576 $old_cmp = '>';
4577 $new_cmp = '<';
4579 if ( in_array( 'include_old', $options ) ) {
4580 $old_cmp = '>=';
4581 }
4582 if ( in_array( 'include_new', $options ) ) {
4583 $new_cmp = '<=';
4584 }
4585 if ( in_array( 'include_both', $options ) ) {
4586 $old_cmp = '>=';
4587 $new_cmp = '<=';
4588 }
4589 // No DB query needed if $old and $new are the same or successive revisions:
4590 if ( $old->getId() === $new->getId() ) {
4591 return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4592 [] :
4593 [ $old->getUserText( Revision::RAW ) ];
4594 } elseif ( $old->getId() === $new->getParentId() ) {
4595 if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4596 $authors[] = $old->getUserText( Revision::RAW );
4597 if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4598 $authors[] = $new->getUserText( Revision::RAW );
4599 }
4600 } elseif ( $old_cmp === '>=' ) {
4601 $authors[] = $old->getUserText( Revision::RAW );
4602 } elseif ( $new_cmp === '<=' ) {
4603 $authors[] = $new->getUserText( Revision::RAW );
4604 }
4605 return $authors;
4606 }
4607 $dbr = wfGetDB( DB_REPLICA );
4609 $authors = $dbr->selectFieldValues(
4610 $revQuery['tables'],
4611 $revQuery['fields']['rev_user_text'],
4612 [
4613 'rev_page' => $this->getArticleID(),
4614 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4615 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4616 ], __METHOD__,
4617 [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated
4618 $revQuery['joins']
4619 );
4620 return $authors;
4621 }
4622
4637 public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4638 $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4639 return $authors ? count( $authors ) : 0;
4640 }
4641
4648 public function equals( Title $title ) {
4649 // Note: === is necessary for proper matching of number-like titles.
4650 return $this->mInterwiki === $title->mInterwiki
4651 && $this->mNamespace == $title->mNamespace
4652 && $this->mDbkeyform === $title->mDbkeyform;
4653 }
4654
4661 public function isSubpageOf( Title $title ) {
4662 return $this->mInterwiki === $title->mInterwiki
4663 && $this->mNamespace == $title->mNamespace
4664 && strpos( $this->mDbkeyform, $title->mDbkeyform . '/' ) === 0;
4665 }
4666
4678 public function exists( $flags = 0 ) {
4679 $exists = $this->getArticleID( $flags ) != 0;
4680 Hooks::run( 'TitleExists', [ $this, &$exists ] );
4681 return $exists;
4682 }
4683
4700 public function isAlwaysKnown() {
4701 $isKnown = null;
4702
4713 Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4714
4715 if ( !is_null( $isKnown ) ) {
4716 return $isKnown;
4717 }
4718
4719 if ( $this->isExternal() ) {
4720 return true; // any interwiki link might be viewable, for all we know
4721 }
4722
4723 switch ( $this->mNamespace ) {
4724 case NS_MEDIA:
4725 case NS_FILE:
4726 // file exists, possibly in a foreign repo
4727 return (bool)wfFindFile( $this );
4728 case NS_SPECIAL:
4729 // valid special page
4730 return MediaWikiServices::getInstance()->getSpecialPageFactory()->
4731 exists( $this->mDbkeyform );
4732 case NS_MAIN:
4733 // selflink, possibly with fragment
4734 return $this->mDbkeyform == '';
4735 case NS_MEDIAWIKI:
4736 // known system message
4737 return $this->hasSourceText() !== false;
4738 default:
4739 return false;
4740 }
4741 }
4742
4754 public function isKnown() {
4755 return $this->isAlwaysKnown() || $this->exists();
4756 }
4757
4763 public function hasSourceText() {
4764 if ( $this->exists() ) {
4765 return true;
4766 }
4767
4768 if ( $this->mNamespace == NS_MEDIAWIKI ) {
4769 // If the page doesn't exist but is a known system message, default
4770 // message content will be displayed, same for language subpages-
4771 // Use always content language to avoid loading hundreds of languages
4772 // to get the link color.
4773 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
4774 list( $name, ) = MessageCache::singleton()->figureMessage(
4775 $contLang->lcfirst( $this->getText() )
4776 );
4777 $message = wfMessage( $name )->inLanguage( $contLang )->useDatabase( false );
4778 return $message->exists();
4779 }
4780
4781 return false;
4782 }
4783
4821 public function getDefaultMessageText() {
4822 if ( $this->mNamespace != NS_MEDIAWIKI ) { // Just in case
4823 return false;
4824 }
4825
4826 list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4827 MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $this->getText() )
4828 );
4829 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4830
4831 if ( $message->exists() ) {
4832 return $message->plain();
4833 } else {
4834 return false;
4835 }
4836 }
4837
4844 public function invalidateCache( $purgeTime = null ) {
4845 if ( wfReadOnly() ) {
4846 return false;
4847 } elseif ( $this->mArticleID === 0 ) {
4848 return true; // avoid gap locking if we know it's not there
4849 }
4850
4851 $dbw = wfGetDB( DB_MASTER );
4852 $dbw->onTransactionPreCommitOrIdle(
4853 function () use ( $dbw ) {
4854 ResourceLoaderWikiModule::invalidateModuleCache(
4855 $this, null, null, $dbw->getDomainId() );
4856 },
4857 __METHOD__
4858 );
4859
4860 $conds = $this->pageCond();
4861 DeferredUpdates::addUpdate(
4862 new AutoCommitUpdate(
4863 $dbw,
4864 __METHOD__,
4865 function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4866 $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4867 $dbw->update(
4868 'page',
4869 [ 'page_touched' => $dbTimestamp ],
4870 $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4871 $fname
4872 );
4873 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4874 }
4875 ),
4876 DeferredUpdates::PRESEND
4877 );
4878
4879 return true;
4880 }
4881
4887 public function touchLinks() {
4888 DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
4889 if ( $this->mNamespace == NS_CATEGORY ) {
4890 DeferredUpdates::addUpdate(
4891 new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
4892 );
4893 }
4894 }
4895
4902 public function getTouched( $db = null ) {
4903 if ( $db === null ) {
4904 $db = wfGetDB( DB_REPLICA );
4905 }
4906 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4907 return $touched;
4908 }
4909
4916 public function getNotificationTimestamp( $user = null ) {
4917 global $wgUser;
4918
4919 // Assume current user if none given
4920 if ( !$user ) {
4921 $user = $wgUser;
4922 }
4923 // Check cache first
4924 $uid = $user->getId();
4925 if ( !$uid ) {
4926 return false;
4927 }
4928 // avoid isset here, as it'll return false for null entries
4929 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4930 return $this->mNotificationTimestamp[$uid];
4931 }
4932 // Don't cache too much!
4933 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4934 $this->mNotificationTimestamp = [];
4935 }
4936
4937 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4938 $watchedItem = $store->getWatchedItem( $user, $this );
4939 if ( $watchedItem ) {
4940 $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4941 } else {
4942 $this->mNotificationTimestamp[$uid] = false;
4943 }
4944
4945 return $this->mNotificationTimestamp[$uid];
4946 }
4947
4954 public function getNamespaceKey( $prepend = 'nstab-' ) {
4955 // Gets the subject namespace of this title
4956 $subjectNS = MWNamespace::getSubject( $this->mNamespace );
4957 // Prefer canonical namespace name for HTML IDs
4958 $namespaceKey = MWNamespace::getCanonicalName( $subjectNS );
4959 if ( $namespaceKey === false ) {
4960 // Fallback to localised text
4961 $namespaceKey = $this->getSubjectNsText();
4962 }
4963 // Makes namespace key lowercase
4964 $namespaceKey = MediaWikiServices::getInstance()->getContentLanguage()->lc( $namespaceKey );
4965 // Uses main
4966 if ( $namespaceKey == '' ) {
4967 $namespaceKey = 'main';
4968 }
4969 // Changes file to image for backwards compatibility
4970 if ( $namespaceKey == 'file' ) {
4971 $namespaceKey = 'image';
4972 }
4973 return $prepend . $namespaceKey;
4974 }
4975
4982 public function getRedirectsHere( $ns = null ) {
4983 $redirs = [];
4984
4985 $dbr = wfGetDB( DB_REPLICA );
4986 $where = [
4987 'rd_namespace' => $this->mNamespace,
4988 'rd_title' => $this->mDbkeyform,
4989 'rd_from = page_id'
4990 ];
4991 if ( $this->isExternal() ) {
4992 $where['rd_interwiki'] = $this->mInterwiki;
4993 } else {
4994 $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4995 }
4996 if ( !is_null( $ns ) ) {
4997 $where['page_namespace'] = $ns;
4998 }
4999
5000 $res = $dbr->select(
5001 [ 'redirect', 'page' ],
5002 [ 'page_namespace', 'page_title' ],
5003 $where,
5004 __METHOD__
5005 );
5006
5007 foreach ( $res as $row ) {
5008 $redirs[] = self::newFromRow( $row );
5009 }
5010 return $redirs;
5011 }
5012
5018 public function isValidRedirectTarget() {
5020
5021 if ( $this->isSpecialPage() ) {
5022 // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
5023 if ( $this->isSpecial( 'Userlogout' ) ) {
5024 return false;
5025 }
5026
5027 foreach ( $wgInvalidRedirectTargets as $target ) {
5028 if ( $this->isSpecial( $target ) ) {
5029 return false;
5030 }
5031 }
5032 }
5033
5034 return true;
5035 }
5036
5042 public function getBacklinkCache() {
5043 return BacklinkCache::get( $this );
5044 }
5045
5051 public function canUseNoindex() {
5053
5054 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
5055 ? MWNamespace::getContentNamespaces()
5057
5058 return !in_array( $this->mNamespace, $bannedNamespaces );
5059 }
5060
5071 public function getCategorySortkey( $prefix = '' ) {
5072 $unprefixed = $this->getText();
5073
5074 // Anything that uses this hook should only depend
5075 // on the Title object passed in, and should probably
5076 // tell the users to run updateCollations.php --force
5077 // in order to re-sort existing category relations.
5078 Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
5079 if ( $prefix !== '' ) {
5080 # Separate with a line feed, so the unprefixed part is only used as
5081 # a tiebreaker when two pages have the exact same prefix.
5082 # In UCA, tab is the only character that can sort above LF
5083 # so we strip both of them from the original prefix.
5084 $prefix = strtr( $prefix, "\n\t", ' ' );
5085 return "$prefix\n$unprefixed";
5086 }
5087 return $unprefixed;
5088 }
5089
5097 private function getDbPageLanguageCode() {
5098 global $wgPageLanguageUseDB;
5099
5100 // check, if the page language could be saved in the database, and if so and
5101 // the value is not requested already, lookup the page language using LinkCache
5102 if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
5103 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
5104 $linkCache->addLinkObj( $this );
5105 $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
5106 }
5107
5108 return $this->mDbPageLanguage;
5109 }
5110
5119 public function getPageLanguage() {
5120 global $wgLang, $wgLanguageCode;
5121 if ( $this->isSpecialPage() ) {
5122 // special pages are in the user language
5123 return $wgLang;
5124 }
5125
5126 // Checking if DB language is set
5127 $dbPageLanguage = $this->getDbPageLanguageCode();
5128 if ( $dbPageLanguage ) {
5129 return wfGetLangObj( $dbPageLanguage );
5130 }
5131
5132 if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
5133 // Note that this may depend on user settings, so the cache should
5134 // be only per-request.
5135 // NOTE: ContentHandler::getPageLanguage() may need to load the
5136 // content to determine the page language!
5137 // Checking $wgLanguageCode hasn't changed for the benefit of unit
5138 // tests.
5139 $contentHandler = ContentHandler::getForTitle( $this );
5140 $langObj = $contentHandler->getPageLanguage( $this );
5141 $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
5142 } else {
5143 $langObj = Language::factory( $this->mPageLanguage[0] );
5144 }
5145
5146 return $langObj;
5147 }
5148
5157 public function getPageViewLanguage() {
5158 global $wgLang;
5159
5160 if ( $this->isSpecialPage() ) {
5161 // If the user chooses a variant, the content is actually
5162 // in a language whose code is the variant code.
5163 $variant = $wgLang->getPreferredVariant();
5164 if ( $wgLang->getCode() !== $variant ) {
5165 return Language::factory( $variant );
5166 }
5167
5168 return $wgLang;
5169 }
5170
5171 // Checking if DB language is set
5172 $dbPageLanguage = $this->getDbPageLanguageCode();
5173 if ( $dbPageLanguage ) {
5174 $pageLang = wfGetLangObj( $dbPageLanguage );
5175 $variant = $pageLang->getPreferredVariant();
5176 if ( $pageLang->getCode() !== $variant ) {
5177 $pageLang = Language::factory( $variant );
5178 }
5179
5180 return $pageLang;
5181 }
5182
5183 // @note Can't be cached persistently, depends on user settings.
5184 // @note ContentHandler::getPageViewLanguage() may need to load the
5185 // content to determine the page language!
5186 $contentHandler = ContentHandler::getForTitle( $this );
5187 $pageLang = $contentHandler->getPageViewLanguage( $this );
5188 return $pageLang;
5189 }
5190
5201 public function getEditNotices( $oldid = 0 ) {
5202 $notices = [];
5203
5204 // Optional notice for the entire namespace
5205 $editnotice_ns = 'editnotice-' . $this->mNamespace;
5206 $msg = wfMessage( $editnotice_ns );
5207 if ( $msg->exists() ) {
5208 $html = $msg->parseAsBlock();
5209 // Edit notices may have complex logic, but output nothing (T91715)
5210 if ( trim( $html ) !== '' ) {
5211 $notices[$editnotice_ns] = Html::rawElement(
5212 'div',
5213 [ 'class' => [
5214 'mw-editnotice',
5215 'mw-editnotice-namespace',
5216 Sanitizer::escapeClass( "mw-$editnotice_ns" )
5217 ] ],
5218 $html
5219 );
5220 }
5221 }
5222
5223 if ( MWNamespace::hasSubpages( $this->mNamespace ) ) {
5224 // Optional notice for page itself and any parent page
5225 $parts = explode( '/', $this->mDbkeyform );
5226 $editnotice_base = $editnotice_ns;
5227 while ( count( $parts ) > 0 ) {
5228 $editnotice_base .= '-' . array_shift( $parts );
5229 $msg = wfMessage( $editnotice_base );
5230 if ( $msg->exists() ) {
5231 $html = $msg->parseAsBlock();
5232 if ( trim( $html ) !== '' ) {
5233 $notices[$editnotice_base] = Html::rawElement(
5234 'div',
5235 [ 'class' => [
5236 'mw-editnotice',
5237 'mw-editnotice-base',
5238 Sanitizer::escapeClass( "mw-$editnotice_base" )
5239 ] ],
5240 $html
5241 );
5242 }
5243 }
5244 }
5245 } else {
5246 // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
5247 $editnoticeText = $editnotice_ns . '-' . strtr( $this->mDbkeyform, '/', '-' );
5248 $msg = wfMessage( $editnoticeText );
5249 if ( $msg->exists() ) {
5250 $html = $msg->parseAsBlock();
5251 if ( trim( $html ) !== '' ) {
5252 $notices[$editnoticeText] = Html::rawElement(
5253 'div',
5254 [ 'class' => [
5255 'mw-editnotice',
5256 'mw-editnotice-page',
5257 Sanitizer::escapeClass( "mw-$editnoticeText" )
5258 ] ],
5259 $html
5260 );
5261 }
5262 }
5263 }
5264
5265 Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
5266 return $notices;
5267 }
5268
5272 public function __sleep() {
5273 return [
5274 'mNamespace',
5275 'mDbkeyform',
5276 'mFragment',
5277 'mInterwiki',
5278 'mLocalInterwiki',
5279 'mUserCaseDBKey',
5280 'mDefaultNamespace',
5281 ];
5282 }
5283
5284 public function __wakeup() {
5285 $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
5286 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
5287 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
5288 }
5289
5290}
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
$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.
string[] $wgRawHtmlMessages
List of messages which might contain raw HTML.
$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.
wfLocalFile( $title)
Get an object referring to a locally registered file.
wfMergeErrorArrays(... $args)
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
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.
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition Setup.php:121
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:747
$wgLang
Definition Setup.php:910
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.
Handles purging appropriate CDN URLs given a title (or titles)
Class to invalidate the HTML cache of all the pages linking to a given title.
MediaWiki exception.
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
Handles a simple LRU key/value map with a maximum number of entries.
set( $key, $value, $rank=self::RANK_TOP)
Set a key/value pair.
get( $key, $maxAge=0.0)
Get the value for a key.
clear( $keys=null)
Clear one or several cache entries, or all cache entries.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Handles the backend logic of moving a page from one title to another.
Definition MovePage.php:30
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object.
Definition Revision.php:521
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:133
const RAW
Definition Revision.php:57
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
The TitleArray class only exists to provide the newFromResult method at pre- sent.
static newFromResult( $res)
Represents a page (or page fragment) title within MediaWiki.
Represents a title within MediaWiki.
Definition Title.php:39
string $mInterwiki
Interwiki prefix.
Definition Title.php:79
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition Title.php:427
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition Title.php:1180
checkUserConfigPermissions( $action, $user, $errors, $rigor, $short)
Check CSS/JSON/JS sub-page permissions.
Definition Title.php:2449
getSubpages( $limit=-1)
Get all subpages of this page.
Definition Title.php:3488
isWatchable()
Can this title be added to a user's watchlist?
Definition Title.php:1108
getTalkPageIfDefined()
Get a Title object associated with the talk page of this article, if such a talk page can exist.
Definition Title.php:1526
getNamespace()
Get the namespace index, i.e.
Definition Title.php:974
estimateRevisionCount()
Get the approximate revision count of this page.
Definition Title.php:4496
__wakeup()
Text form (spaces not underscores) of the main part.
Definition Title.php:5284
static newFromDBkey( $key)
Create a new Title from a prefixed DB key.
Definition Title.php:221
isProtected( $action='')
Does the title correspond to a protected article?
Definition Title.php:3042
getTitleProtectionInternal()
Fetch title protection settings.
Definition Title.php:2949
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:2083
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition Title.php:170
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition Title.php:132
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition Title.php:4166
wasLocalInterwiki()
Was this a local interwiki link?
Definition Title.php:870
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition Title.php:2108
purgeSquid()
Purge all applicable CDN URLs.
Definition Title.php:3982
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition Title.php:1915
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition Title.php:3222
isKnown()
Does this title refer to a page that can (or might) be meaningfully viewed? In particular,...
Definition Title.php:4754
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition Title.php:106
getBacklinkCache()
Get a backlink cache object.
Definition Title.php:5042
static getInterwikiLookup()
B/C kludge: provide an InterwikiLookup for use by Title.
Definition Title.php:203
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition Title.php:191
inNamespace( $ns)
Returns true if the title is inside the specified namespace.
Definition Title.php:1169
equals(Title $title)
Compare with another title.
Definition Title.php:4648
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition Title.php:3540
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition Title.php:3724
checkQuickPermissions( $action, $user, $errors, $rigor, $short)
Permissions checks that fail most often, and which are easiest to test.
Definition Title.php:2237
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition Title.php:1513
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition Title.php:3744
isSiteJsonConfigPage()
Is this a sitewide JSON "config" page?
Definition Title.php:1453
isSiteCssConfigPage()
Is this a sitewide CSS "config" page?
Definition Title.php:1435
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition Title.php:3236
hasContentModel( $id)
Convenience method for checking a title's content model name.
Definition Title.php:1007
getSkinFromCssJsSubpage()
Definition Title.php:1364
static clearCaches()
Text form (spaces not underscores) of the main part.
Definition Title.php:3709
createFragmentTarget( $fragment)
Creates a new Title for a different fragment of the same page.
Definition Title.php:1644
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition Title.php:1576
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move a title to a new location.
Definition Title.php:4055
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition Title.php:1270
static MapCacheLRU $titleCache
Definition Title.php:41
getFragment()
Get the Title fragment (i.e.
Definition Title.php:1587
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not.
Definition Title.php:3092
static getFilteredRestrictionTypes( $exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition Title.php:2881
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition Title.php:1858
TitleValue $mTitleValue
A corresponding TitleValue object.
Definition Title.php:177
checkUserBlock( $action, $user, $errors, $rigor, $short)
Check that the user isn't blocked from editing.
Definition Title.php:2674
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition Title.php:1282
validateFileMoveOperation( $nt)
Check if the requested move target is a valid file move target.
Definition Title.php:4026
getTalkNsText()
Get the namespace text of the talk page.
Definition Title.php:1066
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition Title.php:3262
hasFragment()
Check if a Title fragment is set.
Definition Title.php:1597
static nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition Title.php:612
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition Title.php:1127
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition Title.php:4982
getLength( $flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition Title.php:3627
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition Title.php:164
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition Title.php:3999
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition Title.php:1722
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition Title.php:3209
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition Title.php:5051
exists( $flags=0)
Check if page exists.
Definition Title.php:4678
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition Title.php:364
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,...
Definition Title.php:313
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition Title.php:835
int $mLength
The page length, 0 for special pages.
Definition Title.php:158
loadFromRow( $row)
Load Title object fields from a DB row.
Definition Title.php:487
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition Title.php:5119
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition Title.php:82
getUserCaseDBKey()
Get the DB key with the initial letter case as specified by the user.
Definition Title.php:960
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable.
Definition Title.php:1229
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles.
Definition Title.php:48
getRestrictionExpiry( $action)
Get the expiry time for the restriction against a given action.
Definition Title.php:3250
getUserPermissionsErrors( $action, $user, $rigor='secure', $ignoreErrors=[])
Can $user perform $action on this page?
Definition Title.php:2206
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition Title.php:1540
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:3827
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias,...
Definition Title.php:1145
getRestrictionTypes()
Returns restriction types for the current Title.
Definition Title.php:2899
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition Title.php:634
__toString()
Return a string representation of this title.
Definition Title.php:1712
hasSubjectNamespace( $ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition Title.php:1208
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition Title.php:3014
isCssJsSubpage()
Definition Title.php:1337
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1686
areCascadeProtectionSourcesLoaded( $getPages=true)
Determines whether cascading protection sources have already been loaded from the database.
Definition Title.php:3106
getPreviousRevisionID( $revId, $flags=0)
Get the revision ID of the previous revision.
Definition Title.php:4399
getNsText()
Get the namespace text.
Definition Title.php:1032
canExist()
Is this in a namespace that allows actual pages?
Definition Title.php:1099
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition Title.php:3419
getDefaultMessageText()
Get the default (plain) message contents for an page that overrides an interface message key.
Definition Title.php:4821
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition Title.php:5097
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition Title.php:4519
checkPermissionHooks( $action, $user, $errors, $rigor, $short)
Check various permission hooks.
Definition Title.php:2338
bool $mForcedContentModel
If a content model was forced via setContentModel() this will be true to avoid having other code path...
Definition Title.php:103
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition Title.php:4916
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition Title.php:880
resetArticleID( $newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition Title.php:3687
isRawHtmlMessage()
Is this a message which can contain raw HTML?
Definition Title.php:1489
isSiteJsConfigPage()
Is this a sitewide JS "config" page?
Definition Title.php:1471
isNewPage()
Check if this is a new page.
Definition Title.php:4457
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition Title.php:4887
isExternal()
Is this Title interwiki?
Definition Title.php:850
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition Title.php:135
isMainPage()
Is this the mainpage?
Definition Title.php:1250
isUserConfigPage()
Is this a "config" (.css, .json, or .js) sub-page of a user page?
Definition Title.php:1325
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition Title.php:1606
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:174
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition Title.php:4562
isSpecialPage()
Returns true if this is a special page.
Definition Title.php:1117
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition Title.php:3074
isUserJsConfigPage()
Is this a JS "config" sub-page of a user page?
Definition Title.php:1412
static newFromLinkTarget(LinkTarget $linkTarget)
Create a new Title from a LinkTarget.
Definition Title.php:251
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition Title.php:1847
canHaveTalkPage()
Can this title have a corresponding talk page?
Definition Title.php:1090
isTalkPage()
Is this a talk page of some sort?
Definition Title.php:1504
getRootTitle()
Get the root page name title, i.e.
Definition Title.php:1762
bool int $mLatestID
ID of most recent revision.
Definition Title.php:91
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page.
Definition Title.php:3909
getDBkey()
Get the main part with underscores.
Definition Title.php:951
missingPermissionError( $action, $short)
Get a description array when the user doesn't have the right to perform $action (i....
Definition Title.php:2796
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object.
Definition Title.php:1660
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition Title.php:4447
checkActionPermissions( $action, $user, $errors, $rigor, $short)
Check action permissions not already checked in checkQuickPermissions.
Definition Title.php:2597
string $mFragment
Title fragment (i.e.
Definition Title.php:85
getRootText()
Get the root page name text without a namespace, i.e.
Definition Title.php:1742
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition Title.php:1950
static newFromTitleValue(TitleValue $titleValue)
Create a new Title from a TitleValue.
Definition Title.php:240
bool string $mContentModel
ID of the page's content model, i.e.
Definition Title.php:97
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition Title.php:3655
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition Title.php:648
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition Title.php:5018
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:3843
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition Title.php:752
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:3785
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition Title.php:129
getPartialURL()
Get the URL-encoded form of the main part.
Definition Title.php:942
getBaseText()
Get the base page name without a namespace, i.e.
Definition Title.php:1777
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page,...
Definition Title.php:1219
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:933
getTouched( $db=null)
Get the last touched timestamp.
Definition Title.php:4902
getTitleValue()
Get a TitleValue object representing this Title.
Definition Title.php:910
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition Title.php:4327
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition Title.php:120
string $mUrlform
URL-encoded form of the main part.
Definition Title.php:67
isJsSubpage()
Definition Title.php:1424
getFirstRevision( $flags=0)
Get the first revision of the page.
Definition Title.php:4420
string $mTextform
Text form (spaces not underscores) of the main part.
Definition Title.php:64
getOtherPage()
Get the other title for this page, if this is a subject page get the talk page, if it is a subject pa...
Definition Title.php:1557
static newFromIDs( $ids)
Make an array of titles from an array of IDs.
Definition Title.php:449
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:2819
quickUserCan( $action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition Title.php:2169
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
Definition Title.php:401
__construct()
Definition Title.php:210
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition Title.php:4661
getBaseTitle()
Get the base page name title, i.e.
Definition Title.php:1802
static newMainPage()
Create a new Title for the Main Page.
Definition Title.php:597
getParentCategoryTree( $children=[])
Get a tree of parent categories.
Definition Title.php:4300
checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short)
Check permissions on special pages & namespaces.
Definition Title.php:2375
bool $mHasSubpages
Whether a page has any subpages.
Definition Title.php:167
isCssSubpage()
Definition Title.php:1387
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition Title.php:4410
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition Title.php:126
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition Title.php:3281
static fixUrlQueryArgs( $query, $query2=false)
Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args get{Canonical,...
Definition Title.php:1877
isUserJsonConfigPage()
Is this a JSON "config" sub-page of a user page?
Definition Title.php:1398
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition Title.php:4217
loadRestrictions( $oldFashionedRestrictions=null)
Load restrictions from the page_restrictions table.
Definition Title.php:3353
getSquidURLs()
Definition Title.php:3975
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition Title.php:3592
checkPageRestrictions( $action, $user, $errors, $rigor, $short)
Check against page_restrictions table requirements on this page.
Definition Title.php:2516
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition Title.php:280
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition Title.php:4844
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition Title.php:123
static escapeFragmentForURL( $fragment)
Escape a text fragment, say from a link, for a URL.
Definition Title.php:778
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:3566
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition Title.php:1056
bool $mIsBigDeletion
Would deleting this page be a big deletion?
Definition Title.php:180
int $mNamespace
Namespace index, i.e.
Definition Title.php:76
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:573
null $mRedirect
Is the article at this title a redirect?
Definition Title.php:161
countAuthorsBetween( $old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition Title.php:4637
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition Title.php:795
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition Title.php:2132
checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition Title.php:2550
isDeleted()
Is there a version of this page in the deletion archive?
Definition Title.php:3515
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition Title.php:5157
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition Title.php:54
getSkinFromConfigSubpage()
Trim down a .css, .json, or .js subpage title to get the corresponding skin name.
Definition Title.php:1350
checkReadPermissions( $action, $user, $errors, $rigor, $short)
Check that the user is allowed to read this page.
Definition Title.php:2719
userCan( $action, $user=null, $rigor='secure')
Can $user perform $action on this page?
Definition Title.php:2182
array $mRestrictions
Array of groups allowed to edit this article.
Definition Title.php:109
isSiteConfigPage()
Could this MediaWiki namespace page contain custom CSS, JSON, or JavaScript for the global UI.
Definition Title.php:1300
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition Title.php:155
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move this page's subpages to be subpages of $nt.
Definition Title.php:4093
getRelativeRevisionID( $revId, $flags, $dir)
Get next/previous revision ID relative to another revision ID.
Definition Title.php:4343
deleteTitleProtection()
Remove any title protection due to page existing.
Definition Title.php:2996
getSubpage( $text)
Get the title for a subpage of the current page.
Definition Title.php:1838
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition Title.php:2926
getEditURL()
Get the edit URL for this Title.
Definition Title.php:2146
getParentCategories()
Get categories to which this Title belongs and return an array of categories' names.
Definition Title.php:4265
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:545
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition Title.php:88
static getTitleCache()
Definition Title.php:387
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:3897
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition Title.php:893
isSubpage()
Is this a subpage?
Definition Title.php:1259
isValid()
Returns true if the title is valid, false if it is invalid.
Definition Title.php:814
setFragment( $fragment)
Set the fragment for this title.
Definition Title.php:1633
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition Title.php:1984
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:984
isCssOrJsPage()
Definition Title.php:1312
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition Title.php:4467
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition Title.php:3945
checkSiteConfigPermissions( $action, $user, $errors, $rigor, $short)
Check sitewide CSS/JSON/JS permissions.
Definition Title.php:2404
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition Title.php:73
getInterwiki()
Get the interwiki prefix.
Definition Title.php:861
getEditNotices( $oldid=0)
Get a list of rendered edit notices for this page.
Definition Title.php:5201
__sleep()
Definition Title.php:5272
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition Title.php:1022
getCascadeProtectionSources( $getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition Title.php:3123
mixed $mTitleProtection
Cached value for getTitleProtection (create protection)
Definition Title.php:148
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition Title.php:1817
string $mDbkeyform
Main part with underscores.
Definition Title.php:70
hasSourceText()
Does this page have source text?
Definition Title.php:4763
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition Title.php:3409
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1698
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition Title.php:3460
string $prefixedText
Text form including namespace/interwiki, initialised on demand.
Definition Title.php:145
string bool $mOldRestrictions
Comma-separated set of permission keys indicating who can move or edit the page from the page table,...
Definition Title.php:117
canTalk()
Can this title have a corresponding talk page?
Definition Title.php:1078
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition Title.php:2307
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition Title.php:4700
getNamespaceKey( $prepend='nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition Title.php:4954
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition Title.php:5071
static newFromRow( $row)
Make a Title object from a DB row.
Definition Title.php:475
isUserCssConfigPage()
Is this a CSS "config" sub-page of a user page?
Definition Title.php:1375
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:47
static isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition User.php:5033
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition User.php:5013
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition User.php:891
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition User.php:5696
Relational database abstraction object.
Definition Database.php:48
$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
namespace being checked & $result
Definition hooks.txt:2385
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition hooks.txt:2857
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition hooks.txt:1873
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:266
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:181
namespace and then decline to actually register it & $namespaces
Definition hooks.txt:964
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 & $options
Definition hooks.txt:2050
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:994
this hook is for auditing only RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition hooks.txt:1035
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:2054
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 use $formDescriptor instead 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
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:894
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:2062
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, 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. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy: boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition hooks.txt:1071
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:1656
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of or an object and a method hook function The function part of a third party developers and local administrators to define code that will be run at certain points in the mainline and to modify the data run by that mainline code Hooks can keep mainline code and make it easier to write extensions Hooks are a principled alternative to local patches for two options in MediaWiki One reverses the order of a title before displaying the article
Definition hooks.txt:33
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:1818
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
const PROTO_CANONICAL
Definition Defines.php:223
const CONTENT_MODEL_CSS
Definition Defines.php:237
const NS_FILE
Definition Defines.php:70
const PROTO_CURRENT
Definition Defines.php:222
const NS_MAIN
Definition Defines.php:64
const NS_MEDIAWIKI
Definition Defines.php:72
const NS_SPECIAL
Definition Defines.php:53
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:235
const CONTENT_MODEL_JSON
Definition Defines.php:239
const PROTO_HTTP
Definition Defines.php:219
const NS_MEDIA
Definition Defines.php:52
const PROTO_RELATIVE
Definition Defines.php:221
const NS_CATEGORY
Definition Defines.php:78
const CONTENT_MODEL_JAVASCRIPT
Definition Defines.php:236
$wgActionPaths
Definition img_auth.php:47
$wgArticlePath
Definition img_auth.php:46
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 function
Definition injection.txt:30
Service interface for looking up Interwiki records.
getInterwiki()
The interwiki component of this LinkTarget.
getFragment()
Get the link fragment (i.e.
getNamespace()
Get the namespace index.
getText()
Returns the link in text form, without namespace prefix or fragment.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
addQuotes( $s)
Adds quotes and backslashes.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
linkcache txt The LinkCache class maintains a list of article titles and the information about whether or not the article exists in the database This is used to mark up links when displaying a page If the same link appears more than once on any page then it only has to be looked up once In most cases link lookups are done in batches with the LinkBatch class or the equivalent in so the link cache is mostly useful for short snippets of parsed and for links in the navigation areas of the skin The link cache was formerly used to track links used in a document for the purposes of updating the link tables This application is now deprecated To create a you can use the following $titles
Definition linkcache.txt:17
$cache
Definition mcc.php:33
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$parent
$content
$sort
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26
if(!isset( $args[0])) $lang