MediaWiki REL1_33
Title.php
Go to the documentation of this file.
1<?php
31
40class Title implements LinkTarget, IDBAccessObject {
42 private static $titleCache = null;
43
49 const CACHE_MAX = 1000;
50
55 const GAID_FOR_UPDATE = 1;
56
64 const NEW_CLONE = 'clone';
65
71 // @{
72
74 public $mTextform = '';
75
77 public $mUrlform = '';
78
80 public $mDbkeyform = '';
81
83 protected $mUserCaseDBKey;
84
87
89 public $mInterwiki = '';
90
92 private $mLocalInterwiki = false;
93
95 public $mFragment = '';
96
98 public $mArticleID = -1;
99
101 protected $mLatestID = false;
102
107 private $mContentModel = false;
108
113 private $mForcedContentModel = false;
114
117
119 public $mRestrictions = [];
120
127 protected $mOldRestrictions = false;
128
131
134
136 protected $mRestrictionsExpiry = [];
137
140
143
145 public $mRestrictionsLoaded = false;
146
155 public $prefixedText = null;
156
159
166
168 protected $mLength = -1;
169
171 public $mRedirect = null;
172
175
178
180 private $mPageLanguage = false;
181
184 private $mDbPageLanguage = false;
185
187 private $mTitleValue = null;
188
190 private $mIsBigDeletion = null;
191 // @}
192
201 private static function getTitleFormatter() {
202 return MediaWikiServices::getInstance()->getTitleFormatter();
203 }
204
213 private static function getInterwikiLookup() {
214 return MediaWikiServices::getInstance()->getInterwikiLookup();
215 }
216
220 function __construct() {
221 }
222
231 public static function newFromDBkey( $key ) {
232 $t = new self();
233 $t->mDbkeyform = $key;
234
235 try {
236 $t->secureAndSplit();
237 return $t;
238 } catch ( MalformedTitleException $ex ) {
239 return null;
240 }
241 }
242
254 public static function newFromTitleValue( TitleValue $titleValue, $forceClone = '' ) {
255 return self::newFromLinkTarget( $titleValue, $forceClone );
256 }
257
269 public static function newFromLinkTarget( LinkTarget $linkTarget, $forceClone = '' ) {
270 if ( $linkTarget instanceof Title ) {
271 // Special case if it's already a Title object
272 if ( $forceClone === self::NEW_CLONE ) {
273 return clone $linkTarget;
274 } else {
275 return $linkTarget;
276 }
277 }
278 return self::makeTitle(
279 $linkTarget->getNamespace(),
280 $linkTarget->getText(),
281 $linkTarget->getFragment(),
282 $linkTarget->getInterwiki()
283 );
284 }
285
306 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
307 // DWIM: Integers can be passed in here when page titles are used as array keys.
308 if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
309 throw new InvalidArgumentException( '$text must be a string.' );
310 }
311 if ( $text === null ) {
312 return null;
313 }
314
315 try {
316 return self::newFromTextThrow( (string)$text, $defaultNamespace );
317 } catch ( MalformedTitleException $ex ) {
318 return null;
319 }
320 }
321
343 public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
344 if ( is_object( $text ) ) {
345 throw new MWException( '$text must be a string, given an object' );
346 } elseif ( $text === null ) {
347 // Legacy code relies on MalformedTitleException being thrown in this case
348 // (happens when URL with no title in it is parsed). TODO fix
349 throw new MalformedTitleException( 'title-invalid-empty' );
350 }
351
352 $titleCache = self::getTitleCache();
353
354 // Wiki pages often contain multiple links to the same page.
355 // Title normalization and parsing can become expensive on pages with many
356 // links, so we can save a little time by caching them.
357 // In theory these are value objects and won't get changed...
358 if ( $defaultNamespace == NS_MAIN ) {
359 $t = $titleCache->get( $text );
360 if ( $t ) {
361 return $t;
362 }
363 }
364
365 // Convert things like &eacute; &#257; or &#x3017; into normalized (T16952) text
366 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
367
368 $t = new Title();
369 $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
370 $t->mDefaultNamespace = (int)$defaultNamespace;
371
372 $t->secureAndSplit();
373 if ( $defaultNamespace == NS_MAIN ) {
374 $titleCache->set( $text, $t );
375 }
376 return $t;
377 }
378
394 public static function newFromURL( $url ) {
395 $t = new Title();
396
397 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
398 # but some URLs used it as a space replacement and they still come
399 # from some external search tools.
400 if ( strpos( self::legalChars(), '+' ) === false ) {
401 $url = strtr( $url, '+', ' ' );
402 }
403
404 $t->mDbkeyform = strtr( $url, ' ', '_' );
405
406 try {
407 $t->secureAndSplit();
408 return $t;
409 } catch ( MalformedTitleException $ex ) {
410 return null;
411 }
412 }
413
417 private static function getTitleCache() {
418 if ( self::$titleCache === null ) {
419 self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
420 }
421 return self::$titleCache;
422 }
423
431 protected static function getSelectFields() {
433
434 $fields = [
435 'page_namespace', 'page_title', 'page_id',
436 'page_len', 'page_is_redirect', 'page_latest',
437 ];
438
440 $fields[] = 'page_content_model';
441 }
442
443 if ( $wgPageLanguageUseDB ) {
444 $fields[] = 'page_lang';
445 }
446
447 return $fields;
448 }
449
457 public static function newFromID( $id, $flags = 0 ) {
458 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
459 $row = $db->selectRow(
460 'page',
461 self::getSelectFields(),
462 [ 'page_id' => $id ],
463 __METHOD__
464 );
465 if ( $row !== false ) {
466 $title = self::newFromRow( $row );
467 } else {
468 $title = null;
469 }
470
471 return $title;
472 }
473
480 public static function newFromIDs( $ids ) {
481 if ( !count( $ids ) ) {
482 return [];
483 }
485
486 $res = $dbr->select(
487 'page',
488 self::getSelectFields(),
489 [ 'page_id' => $ids ],
490 __METHOD__
491 );
492
493 $titles = [];
494 foreach ( $res as $row ) {
495 $titles[] = self::newFromRow( $row );
496 }
497 return $titles;
498 }
499
506 public static function newFromRow( $row ) {
507 $t = self::makeTitle( $row->page_namespace, $row->page_title );
508 $t->loadFromRow( $row );
509 return $t;
510 }
511
518 public function loadFromRow( $row ) {
519 if ( $row ) { // page found
520 if ( isset( $row->page_id ) ) {
521 $this->mArticleID = (int)$row->page_id;
522 }
523 if ( isset( $row->page_len ) ) {
524 $this->mLength = (int)$row->page_len;
525 }
526 if ( isset( $row->page_is_redirect ) ) {
527 $this->mRedirect = (bool)$row->page_is_redirect;
528 }
529 if ( isset( $row->page_latest ) ) {
530 $this->mLatestID = (int)$row->page_latest;
531 }
532 if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) {
533 $this->mContentModel = (string)$row->page_content_model;
534 } elseif ( !$this->mForcedContentModel ) {
535 $this->mContentModel = false; # initialized lazily in getContentModel()
536 }
537 if ( isset( $row->page_lang ) ) {
538 $this->mDbPageLanguage = (string)$row->page_lang;
539 }
540 if ( isset( $row->page_restrictions ) ) {
541 $this->mOldRestrictions = $row->page_restrictions;
542 }
543 } else { // page not found
544 $this->mArticleID = 0;
545 $this->mLength = 0;
546 $this->mRedirect = false;
547 $this->mLatestID = 0;
548 if ( !$this->mForcedContentModel ) {
549 $this->mContentModel = false; # initialized lazily in getContentModel()
550 }
551 }
552 }
553
576 public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
577 $t = new Title();
578 $t->mInterwiki = $interwiki;
579 $t->mFragment = $fragment;
580 $t->mNamespace = $ns = (int)$ns;
581 $t->mDbkeyform = strtr( $title, ' ', '_' );
582 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
583 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
584 $t->mTextform = strtr( $title, '_', ' ' );
585 $t->mContentModel = false; # initialized lazily in getContentModel()
586 return $t;
587 }
588
604 public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
605 // NOTE: ideally, this would just call makeTitle() and then isValid(),
606 // but presently, that means more overhead on a potential performance hotspot.
607
608 if ( !MWNamespace::exists( $ns ) ) {
609 return null;
610 }
611
612 $t = new Title();
613 $t->mDbkeyform = self::makeName( $ns, $title, $fragment, $interwiki, true );
614
615 try {
616 $t->secureAndSplit();
617 return $t;
618 } catch ( MalformedTitleException $ex ) {
619 return null;
620 }
621 }
622
632 public static function newMainPage() {
633 $title = self::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
634 // Don't give fatal errors if the message is broken
635 if ( !$title ) {
636 $title = self::newFromText( 'Main Page' );
637 }
638 return $title;
639 }
640
647 public static function nameOf( $id ) {
649
650 $s = $dbr->selectRow(
651 'page',
652 [ 'page_namespace', 'page_title' ],
653 [ 'page_id' => $id ],
654 __METHOD__
655 );
656 if ( $s === false ) {
657 return null;
658 }
659
660 $n = self::makeName( $s->page_namespace, $s->page_title );
661 return $n;
662 }
663
669 public static function legalChars() {
670 global $wgLegalTitleChars;
671 return $wgLegalTitleChars;
672 }
673
683 public static function convertByteClassToUnicodeClass( $byteClass ) {
684 $length = strlen( $byteClass );
685 // Input token queue
686 $x0 = $x1 = $x2 = '';
687 // Decoded queue
688 $d0 = $d1 = $d2 = '';
689 // Decoded integer codepoints
690 $ord0 = $ord1 = $ord2 = 0;
691 // Re-encoded queue
692 $r0 = $r1 = $r2 = '';
693 // Output
694 $out = '';
695 // Flags
696 $allowUnicode = false;
697 for ( $pos = 0; $pos < $length; $pos++ ) {
698 // Shift the queues down
699 $x2 = $x1;
700 $x1 = $x0;
701 $d2 = $d1;
702 $d1 = $d0;
703 $ord2 = $ord1;
704 $ord1 = $ord0;
705 $r2 = $r1;
706 $r1 = $r0;
707 // Load the current input token and decoded values
708 $inChar = $byteClass[$pos];
709 if ( $inChar == '\\' ) {
710 if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
711 $x0 = $inChar . $m[0];
712 $d0 = chr( hexdec( $m[1] ) );
713 $pos += strlen( $m[0] );
714 } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
715 $x0 = $inChar . $m[0];
716 $d0 = chr( octdec( $m[0] ) );
717 $pos += strlen( $m[0] );
718 } elseif ( $pos + 1 >= $length ) {
719 $x0 = $d0 = '\\';
720 } else {
721 $d0 = $byteClass[$pos + 1];
722 $x0 = $inChar . $d0;
723 $pos += 1;
724 }
725 } else {
726 $x0 = $d0 = $inChar;
727 }
728 $ord0 = ord( $d0 );
729 // Load the current re-encoded value
730 if ( $ord0 < 32 || $ord0 == 0x7f ) {
731 $r0 = sprintf( '\x%02x', $ord0 );
732 } elseif ( $ord0 >= 0x80 ) {
733 // Allow unicode if a single high-bit character appears
734 $r0 = sprintf( '\x%02x', $ord0 );
735 $allowUnicode = true;
736 // @phan-suppress-next-line PhanParamSuspiciousOrder false positive
737 } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
738 $r0 = '\\' . $d0;
739 } else {
740 $r0 = $d0;
741 }
742 // Do the output
743 if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
744 // Range
745 if ( $ord2 > $ord0 ) {
746 // Empty range
747 } elseif ( $ord0 >= 0x80 ) {
748 // Unicode range
749 $allowUnicode = true;
750 if ( $ord2 < 0x80 ) {
751 // Keep the non-unicode section of the range
752 $out .= "$r2-\\x7F";
753 }
754 } else {
755 // Normal range
756 $out .= "$r2-$r0";
757 }
758 // Reset state to the initial value
759 $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
760 } elseif ( $ord2 < 0x80 ) {
761 // ASCII character
762 $out .= $r2;
763 }
764 }
765 if ( $ord1 < 0x80 ) {
766 $out .= $r1;
767 }
768 if ( $ord0 < 0x80 ) {
769 $out .= $r0;
770 }
771 if ( $allowUnicode ) {
772 $out .= '\u0080-\uFFFF';
773 }
774 return $out;
775 }
776
788 public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
789 $canonicalNamespace = false
790 ) {
791 if ( $canonicalNamespace ) {
792 $namespace = MWNamespace::getCanonicalName( $ns );
793 } else {
794 $namespace = MediaWikiServices::getInstance()->getContentLanguage()->getNsText( $ns );
795 }
796 $name = $namespace == '' ? $title : "$namespace:$title";
797 if ( strval( $interwiki ) != '' ) {
798 $name = "$interwiki:$name";
799 }
800 if ( strval( $fragment ) != '' ) {
801 $name .= '#' . $fragment;
802 }
803 return $name;
804 }
805
814 public static function compare( LinkTarget $a, LinkTarget $b ) {
815 return $a->getNamespace() <=> $b->getNamespace()
816 ?: strcmp( $a->getText(), $b->getText() );
817 }
818
833 public function isValid() {
834 if ( !MWNamespace::exists( $this->mNamespace ) ) {
835 return false;
836 }
837
838 try {
839 $parser = MediaWikiServices::getInstance()->getTitleParser();
840 $parser->parseTitle( $this->mDbkeyform, $this->mNamespace );
841 return true;
842 } catch ( MalformedTitleException $ex ) {
843 return false;
844 }
845 }
846
854 public function isLocal() {
855 if ( $this->isExternal() ) {
856 $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki );
857 if ( $iw ) {
858 return $iw->isLocal();
859 }
860 }
861 return true;
862 }
863
869 public function isExternal() {
870 return $this->mInterwiki !== '';
871 }
872
880 public function getInterwiki() {
881 return $this->mInterwiki;
882 }
883
889 public function wasLocalInterwiki() {
890 return $this->mLocalInterwiki;
891 }
892
899 public function isTrans() {
900 if ( !$this->isExternal() ) {
901 return false;
902 }
903
904 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable();
905 }
906
912 public function getTransWikiID() {
913 if ( !$this->isExternal() ) {
914 return false;
915 }
916
917 return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID();
918 }
919
929 public function getTitleValue() {
930 if ( $this->mTitleValue === null ) {
931 try {
932 $this->mTitleValue = new TitleValue(
933 $this->mNamespace,
934 $this->mDbkeyform,
935 $this->mFragment,
936 $this->mInterwiki
937 );
938 } catch ( InvalidArgumentException $ex ) {
939 wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
940 $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
941 }
942 }
943
944 return $this->mTitleValue;
945 }
946
952 public function getText() {
953 return $this->mTextform;
954 }
955
961 public function getPartialURL() {
962 return $this->mUrlform;
963 }
964
970 public function getDBkey() {
971 return $this->mDbkeyform;
972 }
973
980 function getUserCaseDBKey() {
981 if ( !is_null( $this->mUserCaseDBKey ) ) {
982 return $this->mUserCaseDBKey;
983 } else {
984 // If created via makeTitle(), $this->mUserCaseDBKey is not set.
985 return $this->mDbkeyform;
986 }
987 }
988
994 public function getNamespace() {
995 return $this->mNamespace;
996 }
997
1006 public function getContentModel( $flags = 0 ) {
1007 if ( !$this->mForcedContentModel
1008 && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE )
1009 && $this->getArticleID( $flags )
1010 ) {
1011 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1012 $linkCache->addLinkObj( $this ); # in case we already had an article ID
1013 $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
1014 }
1015
1016 if ( !$this->mContentModel ) {
1017 $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
1018 }
1019
1020 return $this->mContentModel;
1021 }
1022
1029 public function hasContentModel( $id ) {
1030 return $this->getContentModel() == $id;
1031 }
1032
1044 public function setContentModel( $model ) {
1045 $this->mContentModel = $model;
1046 $this->mForcedContentModel = true;
1047 }
1048
1054 public function getNsText() {
1055 if ( $this->isExternal() ) {
1056 // This probably shouldn't even happen, except for interwiki transclusion.
1057 // If possible, use the canonical name for the foreign namespace.
1058 $nsText = MWNamespace::getCanonicalName( $this->mNamespace );
1059 if ( $nsText !== false ) {
1060 return $nsText;
1061 }
1062 }
1063
1064 try {
1065 $formatter = self::getTitleFormatter();
1066 return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
1067 } catch ( InvalidArgumentException $ex ) {
1068 wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
1069 return false;
1070 }
1071 }
1072
1078 public function getSubjectNsText() {
1079 return MediaWikiServices::getInstance()->getContentLanguage()->
1080 getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1081 }
1082
1088 public function getTalkNsText() {
1089 return MediaWikiServices::getInstance()->getContentLanguage()->
1090 getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1091 }
1092
1101 public function canHaveTalkPage() {
1102 return MWNamespace::hasTalkNamespace( $this->mNamespace );
1103 }
1104
1110 public function canExist() {
1111 return $this->mNamespace >= NS_MAIN;
1112 }
1113
1119 public function isWatchable() {
1120 return !$this->isExternal() && MWNamespace::isWatchable( $this->mNamespace );
1121 }
1122
1128 public function isSpecialPage() {
1129 return $this->mNamespace == NS_SPECIAL;
1130 }
1131
1138 public function isSpecial( $name ) {
1139 if ( $this->isSpecialPage() ) {
1140 list( $thisName, /* $subpage */ ) =
1141 MediaWikiServices::getInstance()->getSpecialPageFactory()->
1142 resolveAlias( $this->mDbkeyform );
1143 if ( $name == $thisName ) {
1144 return true;
1145 }
1146 }
1147 return false;
1148 }
1149
1156 public function fixSpecialName() {
1157 if ( $this->isSpecialPage() ) {
1158 $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory();
1159 list( $canonicalName, $par ) = $spFactory->resolveAlias( $this->mDbkeyform );
1160 if ( $canonicalName ) {
1161 $localName = $spFactory->getLocalNameFor( $canonicalName, $par );
1162 if ( $localName != $this->mDbkeyform ) {
1163 return self::makeTitle( NS_SPECIAL, $localName );
1164 }
1165 }
1166 }
1167 return $this;
1168 }
1169
1180 public function inNamespace( $ns ) {
1181 return MWNamespace::equals( $this->mNamespace, $ns );
1182 }
1183
1191 public function inNamespaces( /* ... */ ) {
1192 $namespaces = func_get_args();
1193 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1195 }
1196
1197 foreach ( $namespaces as $ns ) {
1198 if ( $this->inNamespace( $ns ) ) {
1199 return true;
1200 }
1201 }
1202
1203 return false;
1204 }
1205
1219 public function hasSubjectNamespace( $ns ) {
1220 return MWNamespace::subjectEquals( $this->mNamespace, $ns );
1221 }
1222
1230 public function isContentPage() {
1231 return MWNamespace::isContent( $this->mNamespace );
1232 }
1233
1240 public function isMovable() {
1241 if ( !MWNamespace::isMovable( $this->mNamespace ) || $this->isExternal() ) {
1242 // Interwiki title or immovable namespace. Hooks don't get to override here
1243 return false;
1244 }
1245
1246 $result = true;
1247 Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1248 return $result;
1249 }
1250
1261 public function isMainPage() {
1262 return $this->equals( self::newMainPage() );
1263 }
1264
1270 public function isSubpage() {
1271 return MWNamespace::hasSubpages( $this->mNamespace )
1272 ? strpos( $this->getText(), '/' ) !== false
1273 : false;
1274 }
1275
1281 public function isConversionTable() {
1282 // @todo ConversionTable should become a separate content model.
1283
1284 return $this->mNamespace == NS_MEDIAWIKI &&
1285 strpos( $this->getText(), 'Conversiontable/' ) === 0;
1286 }
1287
1293 public function isWikitextPage() {
1294 return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1295 }
1296
1311 public function isSiteConfigPage() {
1312 return (
1313 $this->isSiteCssConfigPage()
1314 || $this->isSiteJsonConfigPage()
1315 || $this->isSiteJsConfigPage()
1316 );
1317 }
1318
1325 public function isUserConfigPage() {
1326 return (
1327 $this->isUserCssConfigPage()
1328 || $this->isUserJsonConfigPage()
1329 || $this->isUserJsConfigPage()
1330 );
1331 }
1332
1339 public function getSkinFromConfigSubpage() {
1340 $subpage = explode( '/', $this->mTextform );
1341 $subpage = $subpage[count( $subpage ) - 1];
1342 $lastdot = strrpos( $subpage, '.' );
1343 if ( $lastdot === false ) {
1344 return $subpage; # Never happens: only called for names ending in '.css'/'.json'/'.js'
1345 }
1346 return substr( $subpage, 0, $lastdot );
1347 }
1348
1355 public function isUserCssConfigPage() {
1356 return (
1357 NS_USER == $this->mNamespace
1358 && $this->isSubpage()
1360 );
1361 }
1362
1369 public function isUserJsonConfigPage() {
1370 return (
1371 NS_USER == $this->mNamespace
1372 && $this->isSubpage()
1374 );
1375 }
1376
1383 public function isUserJsConfigPage() {
1384 return (
1385 NS_USER == $this->mNamespace
1386 && $this->isSubpage()
1388 );
1389 }
1390
1397 public function isSiteCssConfigPage() {
1398 return (
1399 NS_MEDIAWIKI == $this->mNamespace
1400 && (
1402 // paranoia - a MediaWiki: namespace page with mismatching extension and content
1403 // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1404 || substr( $this->mDbkeyform, -4 ) === '.css'
1405 )
1406 );
1407 }
1408
1415 public function isSiteJsonConfigPage() {
1416 return (
1417 NS_MEDIAWIKI == $this->mNamespace
1418 && (
1420 // paranoia - a MediaWiki: namespace page with mismatching extension and content
1421 // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1422 || substr( $this->mDbkeyform, -5 ) === '.json'
1423 )
1424 );
1425 }
1426
1433 public function isSiteJsConfigPage() {
1434 return (
1435 NS_MEDIAWIKI == $this->mNamespace
1436 && (
1438 // paranoia - a MediaWiki: namespace page with mismatching extension and content
1439 // model is probably by mistake and might get handled incorrectly (see e.g. T112937)
1440 || substr( $this->mDbkeyform, -3 ) === '.js'
1441 )
1442 );
1443 }
1444
1451 public function isRawHtmlMessage() {
1452 global $wgRawHtmlMessages;
1453
1454 if ( !$this->inNamespace( NS_MEDIAWIKI ) ) {
1455 return false;
1456 }
1457 $message = lcfirst( $this->getRootTitle()->getDBkey() );
1458 return in_array( $message, $wgRawHtmlMessages, true );
1459 }
1460
1466 public function isTalkPage() {
1467 return MWNamespace::isTalk( $this->mNamespace );
1468 }
1469
1475 public function getTalkPage() {
1476 return self::makeTitle( MWNamespace::getTalk( $this->mNamespace ), $this->mDbkeyform );
1477 }
1478
1488 public function getTalkPageIfDefined() {
1489 if ( !$this->canHaveTalkPage() ) {
1490 return null;
1491 }
1492
1493 return $this->getTalkPage();
1494 }
1495
1502 public function getSubjectPage() {
1503 // Is this the same title?
1504 $subjectNS = MWNamespace::getSubject( $this->mNamespace );
1505 if ( $this->mNamespace == $subjectNS ) {
1506 return $this;
1507 }
1508 return self::makeTitle( $subjectNS, $this->mDbkeyform );
1509 }
1510
1519 public function getOtherPage() {
1520 if ( $this->isSpecialPage() ) {
1521 throw new MWException( 'Special pages cannot have other pages' );
1522 }
1523 if ( $this->isTalkPage() ) {
1524 return $this->getSubjectPage();
1525 } else {
1526 if ( !$this->canHaveTalkPage() ) {
1527 throw new MWException( "{$this->getPrefixedText()} does not have an other page" );
1528 }
1529 return $this->getTalkPage();
1530 }
1531 }
1532
1538 public function getDefaultNamespace() {
1539 return $this->mDefaultNamespace;
1540 }
1541
1549 public function getFragment() {
1550 return $this->mFragment;
1551 }
1552
1559 public function hasFragment() {
1560 return $this->mFragment !== '';
1561 }
1562
1568 public function getFragmentForURL() {
1569 if ( !$this->hasFragment() ) {
1570 return '';
1571 } elseif ( $this->isExternal() ) {
1572 // Note: If the interwiki is unknown, it's treated as a namespace on the local wiki,
1573 // so we treat it like a local interwiki.
1574 $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1575 if ( $interwiki && !$interwiki->isLocal() ) {
1576 return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->mFragment );
1577 }
1578 }
1579
1580 return '#' . Sanitizer::escapeIdForLink( $this->mFragment );
1581 }
1582
1595 public function setFragment( $fragment ) {
1596 $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1597 }
1598
1606 public function createFragmentTarget( $fragment ) {
1607 return self::makeTitle(
1608 $this->mNamespace,
1609 $this->getText(),
1610 $fragment,
1611 $this->mInterwiki
1612 );
1613 }
1614
1622 private function prefix( $name ) {
1623 $p = '';
1624 if ( $this->isExternal() ) {
1625 $p = $this->mInterwiki . ':';
1626 }
1627
1628 if ( $this->mNamespace != 0 ) {
1629 $nsText = $this->getNsText();
1630
1631 if ( $nsText === false ) {
1632 // See T165149. Awkward, but better than erroneously linking to the main namespace.
1633 $nsText = MediaWikiServices::getInstance()->getContentLanguage()->
1634 getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}";
1635 }
1636
1637 $p .= $nsText . ':';
1638 }
1639 return $p . $name;
1640 }
1641
1648 public function getPrefixedDBkey() {
1649 $s = $this->prefix( $this->mDbkeyform );
1650 $s = strtr( $s, ' ', '_' );
1651 return $s;
1652 }
1653
1660 public function getPrefixedText() {
1661 if ( $this->prefixedText === null ) {
1662 $s = $this->prefix( $this->mTextform );
1663 $s = strtr( $s, '_', ' ' );
1664 $this->prefixedText = $s;
1665 }
1666 return $this->prefixedText;
1667 }
1668
1674 public function __toString() {
1675 return $this->getPrefixedText();
1676 }
1677
1684 public function getFullText() {
1685 $text = $this->getPrefixedText();
1686 if ( $this->hasFragment() ) {
1687 $text .= '#' . $this->mFragment;
1688 }
1689 return $text;
1690 }
1691
1704 public function getRootText() {
1705 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1706 return $this->getText();
1707 }
1708
1709 return strtok( $this->getText(), '/' );
1710 }
1711
1724 public function getRootTitle() {
1725 return self::makeTitle( $this->mNamespace, $this->getRootText() );
1726 }
1727
1739 public function getBaseText() {
1740 $text = $this->getText();
1741 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1742 return $text;
1743 }
1744
1745 $lastSlashPos = strrpos( $text, '/' );
1746 // Don't discard the real title if there's no subpage involved
1747 if ( $lastSlashPos === false ) {
1748 return $text;
1749 }
1750
1751 return substr( $text, 0, $lastSlashPos );
1752 }
1753
1766 public function getBaseTitle() {
1767 return self::makeTitle( $this->mNamespace, $this->getBaseText() );
1768 }
1769
1781 public function getSubpageText() {
1782 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1783 return $this->mTextform;
1784 }
1785 $parts = explode( '/', $this->mTextform );
1786 return $parts[count( $parts ) - 1];
1787 }
1788
1802 public function getSubpage( $text ) {
1803 return self::makeTitleSafe( $this->mNamespace, $this->getText() . '/' . $text );
1804 }
1805
1811 public function getSubpageUrlForm() {
1812 $text = $this->getSubpageText();
1813 $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1814 return $text;
1815 }
1816
1822 public function getPrefixedURL() {
1823 $s = $this->prefix( $this->mDbkeyform );
1824 $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1825 return $s;
1826 }
1827
1841 private static function fixUrlQueryArgs( $query, $query2 = false ) {
1842 if ( $query2 !== false ) {
1843 wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1844 "method called with a second parameter is deprecated. Add your " .
1845 "parameter to an array passed as the first parameter.", "1.19" );
1846 }
1847 if ( is_array( $query ) ) {
1849 }
1850 if ( $query2 ) {
1851 if ( is_string( $query2 ) ) {
1852 // $query2 is a string, we will consider this to be
1853 // a deprecated $variant argument and add it to the query
1854 $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1855 } else {
1856 $query2 = wfArrayToCgi( $query2 );
1857 }
1858 // If we have $query content add a & to it first
1859 if ( $query ) {
1860 $query .= '&';
1861 }
1862 // Now append the queries together
1863 $query .= $query2;
1864 }
1865 return $query;
1866 }
1867
1879 public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1880 $query = self::fixUrlQueryArgs( $query, $query2 );
1881
1882 # Hand off all the decisions on urls to getLocalURL
1883 $url = $this->getLocalURL( $query );
1884
1885 # Expand the url to make it a full url. Note that getLocalURL has the
1886 # potential to output full urls for a variety of reasons, so we use
1887 # wfExpandUrl instead of simply prepending $wgServer
1888 $url = wfExpandUrl( $url, $proto );
1889
1890 # Finally, add the fragment.
1891 $url .= $this->getFragmentForURL();
1892 // Avoid PHP 7.1 warning from passing $this by reference
1893 $titleRef = $this;
1894 Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
1895 return $url;
1896 }
1897
1914 public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1915 $target = $this;
1916 if ( $this->isExternal() ) {
1917 $target = SpecialPage::getTitleFor(
1918 'GoToInterwiki',
1919 $this->getPrefixedDBkey()
1920 );
1921 }
1922 return $target->getFullURL( $query, false, $proto );
1923 }
1924
1948 public function getLocalURL( $query = '', $query2 = false ) {
1950
1951 $query = self::fixUrlQueryArgs( $query, $query2 );
1952
1953 $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
1954 if ( $interwiki ) {
1955 $namespace = $this->getNsText();
1956 if ( $namespace != '' ) {
1957 # Can this actually happen? Interwikis shouldn't be parsed.
1958 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1959 $namespace .= ':';
1960 }
1961 $url = $interwiki->getURL( $namespace . $this->mDbkeyform );
1962 $url = wfAppendQuery( $url, $query );
1963 } else {
1964 $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1965 if ( $query == '' ) {
1966 $url = str_replace( '$1', $dbkey, $wgArticlePath );
1967 // Avoid PHP 7.1 warning from passing $this by reference
1968 $titleRef = $this;
1969 Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
1970 } else {
1972 $url = false;
1973 $matches = [];
1974
1975 if ( !empty( $wgActionPaths )
1976 && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1977 ) {
1978 $action = urldecode( $matches[2] );
1979 if ( isset( $wgActionPaths[$action] ) ) {
1980 $query = $matches[1];
1981 if ( isset( $matches[4] ) ) {
1982 $query .= $matches[4];
1983 }
1984 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1985 if ( $query != '' ) {
1986 $url = wfAppendQuery( $url, $query );
1987 }
1988 }
1989 }
1990
1991 if ( $url === false
1993 && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1994 && $this->getPageLanguage()->equals(
1995 MediaWikiServices::getInstance()->getContentLanguage() )
1996 && $this->getPageLanguage()->hasVariants()
1997 ) {
1998 $variant = urldecode( $matches[1] );
1999 if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
2000 // Only do the variant replacement if the given variant is a valid
2001 // variant for the page's language.
2002 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
2003 $url = str_replace( '$1', $dbkey, $url );
2004 }
2005 }
2006
2007 if ( $url === false ) {
2008 if ( $query == '-' ) {
2009 $query = '';
2010 }
2011 $url = "{$wgScript}?title={$dbkey}&{$query}";
2012 }
2013 }
2014 // Avoid PHP 7.1 warning from passing $this by reference
2015 $titleRef = $this;
2016 Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
2017
2018 // @todo FIXME: This causes breakage in various places when we
2019 // actually expected a local URL and end up with dupe prefixes.
2020 if ( $wgRequest->getVal( 'action' ) == 'render' ) {
2021 $url = $wgServer . $url;
2022 }
2023 }
2024 // Avoid PHP 7.1 warning from passing $this by reference
2025 $titleRef = $this;
2026 Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
2027 return $url;
2028 }
2029
2047 public function getLinkURL( $query = '', $query2 = false, $proto = false ) {
2048 if ( $this->isExternal() || $proto !== false ) {
2049 $ret = $this->getFullURL( $query, $query2, $proto );
2050 } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
2051 $ret = $this->getFragmentForURL();
2052 } else {
2053 $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
2054 }
2055 return $ret;
2056 }
2057
2072 public function getInternalURL( $query = '', $query2 = false ) {
2074 $query = self::fixUrlQueryArgs( $query, $query2 );
2075 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
2076 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
2077 // Avoid PHP 7.1 warning from passing $this by reference
2078 $titleRef = $this;
2079 Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
2080 return $url;
2081 }
2082
2096 public function getCanonicalURL( $query = '', $query2 = false ) {
2097 $query = self::fixUrlQueryArgs( $query, $query2 );
2098 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
2099 // Avoid PHP 7.1 warning from passing $this by reference
2100 $titleRef = $this;
2101 Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
2102 return $url;
2103 }
2104
2110 public function getEditURL() {
2111 if ( $this->isExternal() ) {
2112 return '';
2113 }
2114 $s = $this->getLocalURL( 'action=edit' );
2115
2116 return $s;
2117 }
2118
2139 public function quickUserCan( $action, $user = null ) {
2140 return $this->userCan( $action, $user, false );
2141 }
2142
2158 public function userCan( $action, $user = null, $rigor = PermissionManager::RIGOR_SECURE ) {
2159 if ( !$user instanceof User ) {
2160 global $wgUser;
2161 $user = $wgUser;
2162 }
2163
2164 // TODO: this is for b/c, eventually will be removed
2165 if ( $rigor === true ) {
2166 $rigor = PermissionManager::RIGOR_SECURE; // b/c
2167 } elseif ( $rigor === false ) {
2168 $rigor = PermissionManager::RIGOR_QUICK; // b/c
2169 }
2170
2171 return MediaWikiServices::getInstance()->getPermissionManager()
2172 ->userCan( $action, $user, $this, $rigor );
2173 }
2174
2197 $action, $user, $rigor = PermissionManager::RIGOR_SECURE, $ignoreErrors = []
2198 ) {
2199 // TODO: this is for b/c, eventually will be removed
2200 if ( $rigor === true ) {
2201 $rigor = PermissionManager::RIGOR_SECURE; // b/c
2202 } elseif ( $rigor === false ) {
2203 $rigor = PermissionManager::RIGOR_QUICK; // b/c
2204 }
2205
2206 return MediaWikiServices::getInstance()->getPermissionManager()
2207 ->getPermissionErrors( $action, $user, $this, $rigor, $ignoreErrors );
2208 }
2209
2218 private function resultToError( $errors, $result ) {
2219 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2220 // A single array representing an error
2221 $errors[] = $result;
2222 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2223 // A nested array representing multiple errors
2224 $errors = array_merge( $errors, $result );
2225 } elseif ( $result !== '' && is_string( $result ) ) {
2226 // A string representing a message-id
2227 $errors[] = [ $result ];
2228 } elseif ( $result instanceof MessageSpecifier ) {
2229 // A message specifier representing an error
2230 $errors[] = [ $result ];
2231 } elseif ( $result === false ) {
2232 // a generic "We don't want them to do that"
2233 $errors[] = [ 'badaccess-group0' ];
2234 }
2235 return $errors;
2236 }
2237
2245 public static function getFilteredRestrictionTypes( $exists = true ) {
2246 global $wgRestrictionTypes;
2247 $types = $wgRestrictionTypes;
2248 if ( $exists ) {
2249 # Remove the create restriction for existing titles
2250 $types = array_diff( $types, [ 'create' ] );
2251 } else {
2252 # Only the create and upload restrictions apply to non-existing titles
2253 $types = array_intersect( $types, [ 'create', 'upload' ] );
2254 }
2255 return $types;
2256 }
2257
2263 public function getRestrictionTypes() {
2264 if ( $this->isSpecialPage() ) {
2265 return [];
2266 }
2267
2268 $types = self::getFilteredRestrictionTypes( $this->exists() );
2269
2270 if ( $this->mNamespace != NS_FILE ) {
2271 # Remove the upload restriction for non-file titles
2272 $types = array_diff( $types, [ 'upload' ] );
2273 }
2274
2275 Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2276
2277 wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2278 $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2279
2280 return $types;
2281 }
2282
2290 public function getTitleProtection() {
2291 $protection = $this->getTitleProtectionInternal();
2292 if ( $protection ) {
2293 if ( $protection['permission'] == 'sysop' ) {
2294 $protection['permission'] = 'editprotected'; // B/C
2295 }
2296 if ( $protection['permission'] == 'autoconfirmed' ) {
2297 $protection['permission'] = 'editsemiprotected'; // B/C
2298 }
2299 }
2300 return $protection;
2301 }
2302
2313 protected function getTitleProtectionInternal() {
2314 // Can't protect pages in special namespaces
2315 if ( $this->mNamespace < 0 ) {
2316 return false;
2317 }
2318
2319 // Can't protect pages that exist.
2320 if ( $this->exists() ) {
2321 return false;
2322 }
2323
2324 if ( $this->mTitleProtection === null ) {
2325 $dbr = wfGetDB( DB_REPLICA );
2326 $commentStore = CommentStore::getStore();
2327 $commentQuery = $commentStore->getJoin( 'pt_reason' );
2328 $res = $dbr->select(
2329 [ 'protected_titles' ] + $commentQuery['tables'],
2330 [
2331 'user' => 'pt_user',
2332 'expiry' => 'pt_expiry',
2333 'permission' => 'pt_create_perm'
2334 ] + $commentQuery['fields'],
2335 [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2336 __METHOD__,
2337 [],
2338 $commentQuery['joins']
2339 );
2340
2341 // fetchRow returns false if there are no rows.
2342 $row = $dbr->fetchRow( $res );
2343 if ( $row ) {
2344 $this->mTitleProtection = [
2345 'user' => $row['user'],
2346 'expiry' => $dbr->decodeExpiry( $row['expiry'] ),
2347 'permission' => $row['permission'],
2348 'reason' => $commentStore->getComment( 'pt_reason', $row )->text,
2349 ];
2350 } else {
2351 $this->mTitleProtection = false;
2352 }
2353 }
2354 return $this->mTitleProtection;
2355 }
2356
2360 public function deleteTitleProtection() {
2361 $dbw = wfGetDB( DB_MASTER );
2362
2363 $dbw->delete(
2364 'protected_titles',
2365 [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ],
2366 __METHOD__
2367 );
2368 $this->mTitleProtection = false;
2369 }
2370
2378 public function isSemiProtected( $action = 'edit' ) {
2380
2381 $restrictions = $this->getRestrictions( $action );
2383 if ( !$restrictions || !$semi ) {
2384 // Not protected, or all protection is full protection
2385 return false;
2386 }
2387
2388 // Remap autoconfirmed to editsemiprotected for BC
2389 foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2390 $semi[$key] = 'editsemiprotected';
2391 }
2392 foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2393 $restrictions[$key] = 'editsemiprotected';
2394 }
2395
2396 return !array_diff( $restrictions, $semi );
2397 }
2398
2406 public function isProtected( $action = '' ) {
2407 global $wgRestrictionLevels;
2408
2409 $restrictionTypes = $this->getRestrictionTypes();
2410
2411 # Special pages have inherent protection
2412 if ( $this->isSpecialPage() ) {
2413 return true;
2414 }
2415
2416 # Check regular protection levels
2417 foreach ( $restrictionTypes as $type ) {
2418 if ( $action == $type || $action == '' ) {
2419 $r = $this->getRestrictions( $type );
2420 foreach ( $wgRestrictionLevels as $level ) {
2421 if ( in_array( $level, $r ) && $level != '' ) {
2422 return true;
2423 }
2424 }
2425 }
2426 }
2427
2428 return false;
2429 }
2430
2438 public function isNamespaceProtected( User $user ) {
2440
2441 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2442 foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2443 if ( $right != '' && !$user->isAllowed( $right ) ) {
2444 return true;
2445 }
2446 }
2447 }
2448 return false;
2449 }
2450
2456 public function isCascadeProtected() {
2457 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2458 return ( $sources > 0 );
2459 }
2460
2470 public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2471 return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2472 }
2473
2487 public function getCascadeProtectionSources( $getPages = true ) {
2488 $pagerestrictions = [];
2489
2490 if ( $this->mCascadeSources !== null && $getPages ) {
2491 return [ $this->mCascadeSources, $this->mCascadingRestrictions ];
2492 } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2493 return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2494 }
2495
2496 $dbr = wfGetDB( DB_REPLICA );
2497
2498 if ( $this->mNamespace == NS_FILE ) {
2499 $tables = [ 'imagelinks', 'page_restrictions' ];
2500 $where_clauses = [
2501 'il_to' => $this->mDbkeyform,
2502 'il_from=pr_page',
2503 'pr_cascade' => 1
2504 ];
2505 } else {
2506 $tables = [ 'templatelinks', 'page_restrictions' ];
2507 $where_clauses = [
2508 'tl_namespace' => $this->mNamespace,
2509 'tl_title' => $this->mDbkeyform,
2510 'tl_from=pr_page',
2511 'pr_cascade' => 1
2512 ];
2513 }
2514
2515 if ( $getPages ) {
2516 $cols = [ 'pr_page', 'page_namespace', 'page_title',
2517 'pr_expiry', 'pr_type', 'pr_level' ];
2518 $where_clauses[] = 'page_id=pr_page';
2519 $tables[] = 'page';
2520 } else {
2521 $cols = [ 'pr_expiry' ];
2522 }
2523
2524 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2525
2526 $sources = $getPages ? [] : false;
2527 $now = wfTimestampNow();
2528
2529 foreach ( $res as $row ) {
2530 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2531 if ( $expiry > $now ) {
2532 if ( $getPages ) {
2533 $page_id = $row->pr_page;
2534 $page_ns = $row->page_namespace;
2535 $page_title = $row->page_title;
2536 $sources[$page_id] = self::makeTitle( $page_ns, $page_title );
2537 # Add groups needed for each restriction type if its not already there
2538 # Make sure this restriction type still exists
2539
2540 if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2541 $pagerestrictions[$row->pr_type] = [];
2542 }
2543
2544 if (
2545 isset( $pagerestrictions[$row->pr_type] )
2546 && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2547 ) {
2548 $pagerestrictions[$row->pr_type][] = $row->pr_level;
2549 }
2550 } else {
2551 $sources = true;
2552 }
2553 }
2554 }
2555
2556 if ( $getPages ) {
2557 $this->mCascadeSources = $sources;
2558 $this->mCascadingRestrictions = $pagerestrictions;
2559 } else {
2560 $this->mHasCascadingRestrictions = $sources;
2561 }
2562
2563 return [ $sources, $pagerestrictions ];
2564 }
2565
2573 public function areRestrictionsLoaded() {
2574 return $this->mRestrictionsLoaded;
2575 }
2576
2586 public function getRestrictions( $action ) {
2587 if ( !$this->mRestrictionsLoaded ) {
2588 $this->loadRestrictions();
2589 }
2590 return $this->mRestrictions[$action] ?? [];
2591 }
2592
2600 public function getAllRestrictions() {
2601 if ( !$this->mRestrictionsLoaded ) {
2602 $this->loadRestrictions();
2603 }
2604 return $this->mRestrictions;
2605 }
2606
2614 public function getRestrictionExpiry( $action ) {
2615 if ( !$this->mRestrictionsLoaded ) {
2616 $this->loadRestrictions();
2617 }
2618 return $this->mRestrictionsExpiry[$action] ?? false;
2619 }
2620
2627 if ( !$this->mRestrictionsLoaded ) {
2628 $this->loadRestrictions();
2629 }
2630
2631 return $this->mCascadeRestriction;
2632 }
2633
2645 public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2646 // This function will only read rows from a table that we migrated away
2647 // from before adding READ_LATEST support to loadRestrictions, so we
2648 // don't need to support reading from DB_MASTER here.
2649 $dbr = wfGetDB( DB_REPLICA );
2650
2651 $restrictionTypes = $this->getRestrictionTypes();
2652
2653 foreach ( $restrictionTypes as $type ) {
2654 $this->mRestrictions[$type] = [];
2655 $this->mRestrictionsExpiry[$type] = 'infinity';
2656 }
2657
2658 $this->mCascadeRestriction = false;
2659
2660 # Backwards-compatibility: also load the restrictions from the page record (old format).
2661 if ( $oldFashionedRestrictions !== null ) {
2662 $this->mOldRestrictions = $oldFashionedRestrictions;
2663 }
2664
2665 if ( $this->mOldRestrictions === false ) {
2666 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
2667 $linkCache->addLinkObj( $this ); # in case we already had an article ID
2668 $this->mOldRestrictions = $linkCache->getGoodLinkFieldObj( $this, 'restrictions' );
2669 }
2670
2671 if ( $this->mOldRestrictions != '' ) {
2672 foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2673 $temp = explode( '=', trim( $restrict ) );
2674 if ( count( $temp ) == 1 ) {
2675 // old old format should be treated as edit/move restriction
2676 $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2677 $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2678 } else {
2679 $restriction = trim( $temp[1] );
2680 if ( $restriction != '' ) { // some old entries are empty
2681 $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2682 }
2683 }
2684 }
2685 }
2686
2687 if ( count( $rows ) ) {
2688 # Current system - load second to make them override.
2689 $now = wfTimestampNow();
2690
2691 # Cycle through all the restrictions.
2692 foreach ( $rows as $row ) {
2693 // Don't take care of restrictions types that aren't allowed
2694 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2695 continue;
2696 }
2697
2698 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2699
2700 // Only apply the restrictions if they haven't expired!
2701 if ( !$expiry || $expiry > $now ) {
2702 $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
2703 $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
2704
2705 $this->mCascadeRestriction |= $row->pr_cascade;
2706 }
2707 }
2708 }
2709
2710 $this->mRestrictionsLoaded = true;
2711 }
2712
2723 public function loadRestrictions( $oldFashionedRestrictions = null, $flags = 0 ) {
2724 $readLatest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
2725 if ( $this->mRestrictionsLoaded && !$readLatest ) {
2726 return;
2727 }
2728
2729 // TODO: should probably pass $flags into getArticleID, but it seems hacky
2730 // to mix READ_LATEST and GAID_FOR_UPDATE, even if they have the same value.
2731 // Maybe deprecate GAID_FOR_UPDATE now that we implement IDBAccessObject?
2732 $id = $this->getArticleID();
2733 if ( $id ) {
2734 $fname = __METHOD__;
2735 $loadRestrictionsFromDb = function ( IDatabase $dbr ) use ( $fname, $id ) {
2736 return iterator_to_array(
2737 $dbr->select(
2738 'page_restrictions',
2739 [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
2740 [ 'pr_page' => $id ],
2741 $fname
2742 )
2743 );
2744 };
2745
2746 if ( $readLatest ) {
2747 $dbr = wfGetDB( DB_MASTER );
2748 $rows = $loadRestrictionsFromDb( $dbr );
2749 } else {
2750 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
2751 $rows = $cache->getWithSetCallback(
2752 // Page protections always leave a new null revision
2753 $cache->makeKey( 'page-restrictions', 'v1', $id, $this->getLatestRevID() ),
2754 $cache::TTL_DAY,
2755 function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
2756 $dbr = wfGetDB( DB_REPLICA );
2757
2758 $setOpts += Database::getCacheSetOptions( $dbr );
2759 $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
2760 if ( $lb->hasOrMadeRecentMasterChanges() ) {
2761 // @TODO: cleanup Title cache and caller assumption mess in general
2762 $ttl = WANObjectCache::TTL_UNCACHEABLE;
2763 }
2764
2765 return $loadRestrictionsFromDb( $dbr );
2766 }
2767 );
2768 }
2769
2770 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2771 } else {
2772 $title_protection = $this->getTitleProtectionInternal();
2773
2774 if ( $title_protection ) {
2775 $now = wfTimestampNow();
2776 $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
2777
2778 if ( !$expiry || $expiry > $now ) {
2779 // Apply the restrictions
2780 $this->mRestrictionsExpiry['create'] = $expiry;
2781 $this->mRestrictions['create'] =
2782 explode( ',', trim( $title_protection['permission'] ) );
2783 } else { // Get rid of the old restrictions
2784 $this->mTitleProtection = false;
2785 }
2786 } else {
2787 $this->mRestrictionsExpiry['create'] = 'infinity';
2788 }
2789 $this->mRestrictionsLoaded = true;
2790 }
2791 }
2792
2797 public function flushRestrictions() {
2798 $this->mRestrictionsLoaded = false;
2799 $this->mTitleProtection = null;
2800 }
2801
2807 static function purgeExpiredRestrictions() {
2808 if ( wfReadOnly() ) {
2809 return;
2810 }
2811
2812 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
2813 wfGetDB( DB_MASTER ),
2814 __METHOD__,
2815 function ( IDatabase $dbw, $fname ) {
2816 $config = MediaWikiServices::getInstance()->getMainConfig();
2817 $ids = $dbw->selectFieldValues(
2818 'page_restrictions',
2819 'pr_id',
2820 [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
2821 $fname,
2822 [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470
2823 );
2824 if ( $ids ) {
2825 $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname );
2826 }
2827 }
2828 ) );
2829
2830 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
2831 wfGetDB( DB_MASTER ),
2832 __METHOD__,
2833 function ( IDatabase $dbw, $fname ) {
2834 $dbw->delete(
2835 'protected_titles',
2836 [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
2837 $fname
2838 );
2839 }
2840 ) );
2841 }
2842
2848 public function hasSubpages() {
2849 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
2850 # Duh
2851 return false;
2852 }
2853
2854 # We dynamically add a member variable for the purpose of this method
2855 # alone to cache the result. There's no point in having it hanging
2856 # around uninitialized in every Title object; therefore we only add it
2857 # if needed and don't declare it statically.
2858 if ( $this->mHasSubpages === null ) {
2859 $this->mHasSubpages = false;
2860 $subpages = $this->getSubpages( 1 );
2861 if ( $subpages instanceof TitleArray ) {
2862 $this->mHasSubpages = (bool)$subpages->count();
2863 }
2864 }
2865
2866 return $this->mHasSubpages;
2867 }
2868
2876 public function getSubpages( $limit = -1 ) {
2877 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
2878 return [];
2879 }
2880
2881 $dbr = wfGetDB( DB_REPLICA );
2882 $conds['page_namespace'] = $this->mNamespace;
2883 $conds[] = 'page_title ' . $dbr->buildLike( $this->mDbkeyform . '/', $dbr->anyString() );
2884 $options = [];
2885 if ( $limit > -1 ) {
2886 $options['LIMIT'] = $limit;
2887 }
2889 $dbr->select( 'page',
2890 [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
2891 $conds,
2892 __METHOD__,
2893 $options
2894 )
2895 );
2896 }
2897
2903 public function isDeleted() {
2904 if ( $this->mNamespace < 0 ) {
2905 $n = 0;
2906 } else {
2907 $dbr = wfGetDB( DB_REPLICA );
2908
2909 $n = $dbr->selectField( 'archive', 'COUNT(*)',
2910 [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
2911 __METHOD__
2912 );
2913 if ( $this->mNamespace == NS_FILE ) {
2914 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
2915 [ 'fa_name' => $this->mDbkeyform ],
2916 __METHOD__
2917 );
2918 }
2919 }
2920 return (int)$n;
2921 }
2922
2928 public function isDeletedQuick() {
2929 if ( $this->mNamespace < 0 ) {
2930 return false;
2931 }
2932 $dbr = wfGetDB( DB_REPLICA );
2933 $deleted = (bool)$dbr->selectField( 'archive', '1',
2934 [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ],
2935 __METHOD__
2936 );
2937 if ( !$deleted && $this->mNamespace == NS_FILE ) {
2938 $deleted = (bool)$dbr->selectField( 'filearchive', '1',
2939 [ 'fa_name' => $this->mDbkeyform ],
2940 __METHOD__
2941 );
2942 }
2943 return $deleted;
2944 }
2945
2954 public function getArticleID( $flags = 0 ) {
2955 if ( $this->mNamespace < 0 ) {
2956 $this->mArticleID = 0;
2957 return $this->mArticleID;
2958 }
2959 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
2960 if ( $flags & self::GAID_FOR_UPDATE ) {
2961 $oldUpdate = $linkCache->forUpdate( true );
2962 $linkCache->clearLink( $this );
2963 $this->mArticleID = $linkCache->addLinkObj( $this );
2964 $linkCache->forUpdate( $oldUpdate );
2965 } elseif ( $this->mArticleID == -1 ) {
2966 $this->mArticleID = $linkCache->addLinkObj( $this );
2967 }
2968 return $this->mArticleID;
2969 }
2970
2978 public function isRedirect( $flags = 0 ) {
2979 if ( !is_null( $this->mRedirect ) ) {
2980 return $this->mRedirect;
2981 }
2982 if ( !$this->getArticleID( $flags ) ) {
2983 $this->mRedirect = false;
2984 return $this->mRedirect;
2985 }
2986
2987 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
2988 $linkCache->addLinkObj( $this ); # in case we already had an article ID
2989 $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
2990 if ( $cached === null ) {
2991 # Trust LinkCache's state over our own
2992 # LinkCache is telling us that the page doesn't exist, despite there being cached
2993 # data relating to an existing page in $this->mArticleID. Updaters should clear
2994 # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
2995 # set, then LinkCache will definitely be up to date here, since getArticleID() forces
2996 # LinkCache to refresh its data from the master.
2997 $this->mRedirect = false;
2998 return $this->mRedirect;
2999 }
3000
3001 $this->mRedirect = (bool)$cached;
3002
3003 return $this->mRedirect;
3004 }
3005
3013 public function getLength( $flags = 0 ) {
3014 if ( $this->mLength != -1 ) {
3015 return $this->mLength;
3016 }
3017 if ( !$this->getArticleID( $flags ) ) {
3018 $this->mLength = 0;
3019 return $this->mLength;
3020 }
3021 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3022 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3023 $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3024 if ( $cached === null ) {
3025 # Trust LinkCache's state over our own, as for isRedirect()
3026 $this->mLength = 0;
3027 return $this->mLength;
3028 }
3029
3030 $this->mLength = intval( $cached );
3031
3032 return $this->mLength;
3033 }
3034
3041 public function getLatestRevID( $flags = 0 ) {
3042 if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3043 return intval( $this->mLatestID );
3044 }
3045 if ( !$this->getArticleID( $flags ) ) {
3046 $this->mLatestID = 0;
3047 return $this->mLatestID;
3048 }
3049 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3050 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3051 $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3052 if ( $cached === null ) {
3053 # Trust LinkCache's state over our own, as for isRedirect()
3054 $this->mLatestID = 0;
3055 return $this->mLatestID;
3056 }
3057
3058 $this->mLatestID = intval( $cached );
3059
3060 return $this->mLatestID;
3061 }
3062
3073 public function resetArticleID( $newid ) {
3074 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3075 $linkCache->clearLink( $this );
3076
3077 if ( $newid === false ) {
3078 $this->mArticleID = -1;
3079 } else {
3080 $this->mArticleID = intval( $newid );
3081 }
3082 $this->mRestrictionsLoaded = false;
3083 $this->mRestrictions = [];
3084 $this->mOldRestrictions = false;
3085 $this->mRedirect = null;
3086 $this->mLength = -1;
3087 $this->mLatestID = false;
3088 $this->mContentModel = false;
3089 $this->mEstimateRevisions = null;
3090 $this->mPageLanguage = false;
3091 $this->mDbPageLanguage = false;
3092 $this->mIsBigDeletion = null;
3093 }
3094
3095 public static function clearCaches() {
3096 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3097 $linkCache->clear();
3098
3099 $titleCache = self::getTitleCache();
3100 $titleCache->clear();
3101 }
3102
3110 public static function capitalize( $text, $ns = NS_MAIN ) {
3111 if ( MWNamespace::isCapitalized( $ns ) ) {
3112 return MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $text );
3113 } else {
3114 return $text;
3115 }
3116 }
3117
3130 private function secureAndSplit() {
3131 // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3132 // the parsing code with Title, while avoiding massive refactoring.
3133 // @todo: get rid of secureAndSplit, refactor parsing code.
3134 // @note: getTitleParser() returns a TitleParser implementation which does not have a
3135 // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does
3137 $titleCodec = MediaWikiServices::getInstance()->getTitleParser();
3138 // MalformedTitleException can be thrown here
3139 $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace );
3140
3141 # Fill fields
3142 $this->setFragment( '#' . $parts['fragment'] );
3143 $this->mInterwiki = $parts['interwiki'];
3144 $this->mLocalInterwiki = $parts['local_interwiki'];
3145 $this->mNamespace = $parts['namespace'];
3146 $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3147
3148 $this->mDbkeyform = $parts['dbkey'];
3149 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3150 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3151
3152 # We already know that some pages won't be in the database!
3153 if ( $this->isExternal() || $this->isSpecialPage() ) {
3154 $this->mArticleID = 0;
3155 }
3156
3157 return true;
3158 }
3159
3172 public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3173 if ( count( $options ) > 0 ) {
3174 $db = wfGetDB( DB_MASTER );
3175 } else {
3176 $db = wfGetDB( DB_REPLICA );
3177 }
3178
3179 $res = $db->select(
3180 [ 'page', $table ],
3181 self::getSelectFields(),
3182 [
3183 "{$prefix}_from=page_id",
3184 "{$prefix}_namespace" => $this->mNamespace,
3185 "{$prefix}_title" => $this->mDbkeyform ],
3186 __METHOD__,
3187 $options
3188 );
3189
3190 $retVal = [];
3191 if ( $res->numRows() ) {
3192 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3193 foreach ( $res as $row ) {
3194 $titleObj = self::makeTitle( $row->page_namespace, $row->page_title );
3195 if ( $titleObj ) {
3196 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3197 $retVal[] = $titleObj;
3198 }
3199 }
3200 }
3201 return $retVal;
3202 }
3203
3214 public function getTemplateLinksTo( $options = [] ) {
3215 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3216 }
3217
3230 public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3231 $id = $this->getArticleID();
3232
3233 # If the page doesn't exist; there can't be any link from this page
3234 if ( !$id ) {
3235 return [];
3236 }
3237
3238 $db = wfGetDB( DB_REPLICA );
3239
3240 $blNamespace = "{$prefix}_namespace";
3241 $blTitle = "{$prefix}_title";
3242
3243 $pageQuery = WikiPage::getQueryInfo();
3244 $res = $db->select(
3245 [ $table, 'nestpage' => $pageQuery['tables'] ],
3246 array_merge(
3247 [ $blNamespace, $blTitle ],
3248 $pageQuery['fields']
3249 ),
3250 [ "{$prefix}_from" => $id ],
3251 __METHOD__,
3252 $options,
3253 [ 'nestpage' => [
3254 'LEFT JOIN',
3255 [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3256 ] ] + $pageQuery['joins']
3257 );
3258
3259 $retVal = [];
3260 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3261 foreach ( $res as $row ) {
3262 if ( $row->page_id ) {
3263 $titleObj = self::newFromRow( $row );
3264 } else {
3265 $titleObj = self::makeTitle( $row->$blNamespace, $row->$blTitle );
3266 $linkCache->addBadLinkObj( $titleObj );
3267 }
3268 $retVal[] = $titleObj;
3269 }
3270
3271 return $retVal;
3272 }
3273
3284 public function getTemplateLinksFrom( $options = [] ) {
3285 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3286 }
3287
3296 public function getBrokenLinksFrom() {
3297 if ( $this->getArticleID() == 0 ) {
3298 # All links from article ID 0 are false positives
3299 return [];
3300 }
3301
3302 $dbr = wfGetDB( DB_REPLICA );
3303 $res = $dbr->select(
3304 [ 'page', 'pagelinks' ],
3305 [ 'pl_namespace', 'pl_title' ],
3306 [
3307 'pl_from' => $this->getArticleID(),
3308 'page_namespace IS NULL'
3309 ],
3310 __METHOD__, [],
3311 [
3312 'page' => [
3313 'LEFT JOIN',
3314 [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3315 ]
3316 ]
3317 );
3318
3319 $retVal = [];
3320 foreach ( $res as $row ) {
3321 $retVal[] = self::makeTitle( $row->pl_namespace, $row->pl_title );
3322 }
3323 return $retVal;
3324 }
3325
3332 public function getCdnUrls() {
3333 $urls = [
3334 $this->getInternalURL(),
3335 $this->getInternalURL( 'action=history' )
3336 ];
3337
3338 $pageLang = $this->getPageLanguage();
3339 if ( $pageLang->hasVariants() ) {
3340 $variants = $pageLang->getVariants();
3341 foreach ( $variants as $vCode ) {
3342 $urls[] = $this->getInternalURL( $vCode );
3343 }
3344 }
3345
3346 // If we are looking at a css/js user subpage, purge the action=raw.
3347 if ( $this->isUserJsConfigPage() ) {
3348 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3349 } elseif ( $this->isUserJsonConfigPage() ) {
3350 $urls[] = $this->getInternalURL( 'action=raw&ctype=application/json' );
3351 } elseif ( $this->isUserCssConfigPage() ) {
3352 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3353 }
3354
3355 Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3356 return $urls;
3357 }
3358
3362 public function purgeSquid() {
3363 DeferredUpdates::addUpdate(
3364 new CdnCacheUpdate( $this->getCdnUrls() ),
3365 DeferredUpdates::PRESEND
3366 );
3367 }
3368
3379 public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3380 global $wgUser;
3381
3382 if ( !( $nt instanceof Title ) ) {
3383 // Normally we'd add this to $errors, but we'll get
3384 // lots of syntax errors if $nt is not an object
3385 return [ [ 'badtitletext' ] ];
3386 }
3387
3388 $mp = new MovePage( $this, $nt );
3389 $errors = $mp->isValidMove()->getErrorsArray();
3390 if ( $auth ) {
3391 $errors = wfMergeErrorArrays(
3392 $errors,
3393 $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3394 );
3395 }
3396
3397 return $errors ?: true;
3398 }
3399
3413 public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true,
3414 array $changeTags = []
3415 ) {
3416 global $wgUser;
3417 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3418 if ( is_array( $err ) ) {
3419 // Auto-block user's IP if the account was "hard" blocked
3420 $wgUser->spreadAnyEditBlock();
3421 return $err;
3422 }
3423 // Check suppressredirect permission
3424 if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3425 $createRedirect = true;
3426 }
3427
3428 $mp = new MovePage( $this, $nt );
3429 $status = $mp->move( $wgUser, $reason, $createRedirect, $changeTags );
3430 if ( $status->isOK() ) {
3431 return true;
3432 } else {
3433 return $status->getErrorsArray();
3434 }
3435 }
3436
3451 public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true,
3452 array $changeTags = []
3453 ) {
3454 global $wgMaximumMovedPages;
3455 // Check permissions
3456 if ( !$this->userCan( 'move-subpages' ) ) {
3457 return [
3458 [ 'cant-move-subpages' ],
3459 ];
3460 }
3461 // Do the source and target namespaces support subpages?
3462 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3463 return [
3464 [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $this->mNamespace ) ],
3465 ];
3466 }
3467 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3468 return [
3469 [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $nt->getNamespace() ) ],
3470 ];
3471 }
3472
3473 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3474 $retval = [];
3475 $count = 0;
3476 foreach ( $subpages as $oldSubpage ) {
3477 $count++;
3478 if ( $count > $wgMaximumMovedPages ) {
3479 $retval[$oldSubpage->getPrefixedText()] = [
3480 [ 'movepage-max-pages', $wgMaximumMovedPages ],
3481 ];
3482 break;
3483 }
3484
3485 // We don't know whether this function was called before
3486 // or after moving the root page, so check both
3487 // $this and $nt
3488 if ( $oldSubpage->getArticleID() == $this->getArticleID()
3489 || $oldSubpage->getArticleID() == $nt->getArticleID()
3490 ) {
3491 // When moving a page to a subpage of itself,
3492 // don't move it twice
3493 continue;
3494 }
3495 $newPageName = preg_replace(
3496 '#^' . preg_quote( $this->mDbkeyform, '#' ) . '#',
3497 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
3498 $oldSubpage->getDBkey() );
3499 if ( $oldSubpage->isTalkPage() ) {
3500 $newNs = $nt->getTalkPage()->getNamespace();
3501 } else {
3502 $newNs = $nt->getSubjectPage()->getNamespace();
3503 }
3504 # T16385: we need makeTitleSafe because the new page names may
3505 # be longer than 255 characters.
3506 $newSubpage = self::makeTitleSafe( $newNs, $newPageName );
3507
3508 $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect, $changeTags );
3509 if ( $success === true ) {
3510 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3511 } else {
3512 $retval[$oldSubpage->getPrefixedText()] = $success;
3513 }
3514 }
3515 return $retval;
3516 }
3517
3524 public function isSingleRevRedirect() {
3526
3527 $dbw = wfGetDB( DB_MASTER );
3528
3529 # Is it a redirect?
3530 $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3531 if ( $wgContentHandlerUseDB ) {
3532 $fields[] = 'page_content_model';
3533 }
3534
3535 $row = $dbw->selectRow( 'page',
3536 $fields,
3537 $this->pageCond(),
3538 __METHOD__,
3539 [ 'FOR UPDATE' ]
3540 );
3541 # Cache some fields we may want
3542 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3543 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3544 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3545 $this->mContentModel = $row && isset( $row->page_content_model )
3546 ? strval( $row->page_content_model )
3547 : false;
3548
3549 if ( !$this->mRedirect ) {
3550 return false;
3551 }
3552 # Does the article have a history?
3553 $row = $dbw->selectField( [ 'page', 'revision' ],
3554 'rev_id',
3555 [ 'page_namespace' => $this->mNamespace,
3556 'page_title' => $this->mDbkeyform,
3557 'page_id=rev_page',
3558 'page_latest != rev_id'
3559 ],
3560 __METHOD__,
3561 [ 'FOR UPDATE' ]
3562 );
3563 # Return true if there was no history
3564 return ( $row === false );
3565 }
3566
3575 public function isValidMoveTarget( $nt ) {
3576 # Is it an existing file?
3577 if ( $nt->getNamespace() == NS_FILE ) {
3578 $file = wfLocalFile( $nt );
3579 $file->load( File::READ_LATEST );
3580 if ( $file->exists() ) {
3581 wfDebug( __METHOD__ . ": file exists\n" );
3582 return false;
3583 }
3584 }
3585 # Is it a redirect with no history?
3586 if ( !$nt->isSingleRevRedirect() ) {
3587 wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3588 return false;
3589 }
3590 # Get the article text
3591 $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
3592 if ( !is_object( $rev ) ) {
3593 return false;
3594 }
3595 $content = $rev->getContent();
3596 # Does the redirect point to the source?
3597 # Or is it a broken self-redirect, usually caused by namespace collisions?
3598 $redirTitle = $content ? $content->getRedirectTarget() : null;
3599
3600 if ( $redirTitle ) {
3601 if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3602 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3603 wfDebug( __METHOD__ . ": redirect points to other page\n" );
3604 return false;
3605 } else {
3606 return true;
3607 }
3608 } else {
3609 # Fail safe (not a redirect after all. strange.)
3610 wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3611 " is a redirect, but it doesn't contain a valid redirect.\n" );
3612 return false;
3613 }
3614 }
3615
3623 public function getParentCategories() {
3624 $data = [];
3625
3626 $titleKey = $this->getArticleID();
3627
3628 if ( $titleKey === 0 ) {
3629 return $data;
3630 }
3631
3632 $dbr = wfGetDB( DB_REPLICA );
3633
3634 $res = $dbr->select(
3635 'categorylinks',
3636 'cl_to',
3637 [ 'cl_from' => $titleKey ],
3638 __METHOD__
3639 );
3640
3641 if ( $res->numRows() > 0 ) {
3642 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3643 foreach ( $res as $row ) {
3644 // $data[] = Title::newFromText( $contLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3645 $data[$contLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] =
3646 $this->getFullText();
3647 }
3648 }
3649 return $data;
3650 }
3651
3658 public function getParentCategoryTree( $children = [] ) {
3659 $stack = [];
3660 $parents = $this->getParentCategories();
3661
3662 if ( $parents ) {
3663 foreach ( $parents as $parent => $current ) {
3664 if ( array_key_exists( $parent, $children ) ) {
3665 # Circular reference
3666 $stack[$parent] = [];
3667 } else {
3668 $nt = self::newFromText( $parent );
3669 if ( $nt ) {
3670 $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3671 }
3672 }
3673 }
3674 }
3675
3676 return $stack;
3677 }
3678
3685 public function pageCond() {
3686 if ( $this->mArticleID > 0 ) {
3687 // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3688 return [ 'page_id' => $this->mArticleID ];
3689 } else {
3690 return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3691 }
3692 }
3693
3701 private function getRelativeRevisionID( $revId, $flags, $dir ) {
3702 $revId = (int)$revId;
3703 if ( $dir === 'next' ) {
3704 $op = '>';
3705 $sort = 'ASC';
3706 } elseif ( $dir === 'prev' ) {
3707 $op = '<';
3708 $sort = 'DESC';
3709 } else {
3710 throw new InvalidArgumentException( '$dir must be "next" or "prev"' );
3711 }
3712
3713 if ( $flags & self::GAID_FOR_UPDATE ) {
3714 $db = wfGetDB( DB_MASTER );
3715 } else {
3716 $db = wfGetDB( DB_REPLICA, 'contributions' );
3717 }
3718
3719 // Intentionally not caring if the specified revision belongs to this
3720 // page. We only care about the timestamp.
3721 $ts = $db->selectField( 'revision', 'rev_timestamp', [ 'rev_id' => $revId ], __METHOD__ );
3722 if ( $ts === false ) {
3723 $ts = $db->selectField( 'archive', 'ar_timestamp', [ 'ar_rev_id' => $revId ], __METHOD__ );
3724 if ( $ts === false ) {
3725 // Or should this throw an InvalidArgumentException or something?
3726 return false;
3727 }
3728 }
3729 $ts = $db->addQuotes( $ts );
3730
3731 $revId = $db->selectField( 'revision', 'rev_id',
3732 [
3733 'rev_page' => $this->getArticleID( $flags ),
3734 "rev_timestamp $op $ts OR (rev_timestamp = $ts AND rev_id $op $revId)"
3735 ],
3736 __METHOD__,
3737 [
3738 'ORDER BY' => "rev_timestamp $sort, rev_id $sort",
3739 'IGNORE INDEX' => 'rev_timestamp', // Probably needed for T159319
3740 ]
3741 );
3742
3743 if ( $revId === false ) {
3744 return false;
3745 } else {
3746 return intval( $revId );
3747 }
3748 }
3749
3757 public function getPreviousRevisionID( $revId, $flags = 0 ) {
3758 return $this->getRelativeRevisionID( $revId, $flags, 'prev' );
3759 }
3760
3768 public function getNextRevisionID( $revId, $flags = 0 ) {
3769 return $this->getRelativeRevisionID( $revId, $flags, 'next' );
3770 }
3771
3778 public function getFirstRevision( $flags = 0 ) {
3779 $pageId = $this->getArticleID( $flags );
3780 if ( $pageId ) {
3781 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA );
3783 $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'],
3784 [ 'rev_page' => $pageId ],
3785 __METHOD__,
3786 [
3787 'ORDER BY' => 'rev_timestamp ASC, rev_id ASC',
3788 'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319
3789 ],
3790 $revQuery['joins']
3791 );
3792 if ( $row ) {
3793 return new Revision( $row, 0, $this );
3794 }
3795 }
3796 return null;
3797 }
3798
3805 public function getEarliestRevTime( $flags = 0 ) {
3806 $rev = $this->getFirstRevision( $flags );
3807 return $rev ? $rev->getTimestamp() : null;
3808 }
3809
3815 public function isNewPage() {
3816 $dbr = wfGetDB( DB_REPLICA );
3817 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
3818 }
3819
3825 public function isBigDeletion() {
3827
3828 if ( !$wgDeleteRevisionsLimit ) {
3829 return false;
3830 }
3831
3832 if ( $this->mIsBigDeletion === null ) {
3833 $dbr = wfGetDB( DB_REPLICA );
3834
3835 $revCount = $dbr->selectRowCount(
3836 'revision',
3837 '1',
3838 [ 'rev_page' => $this->getArticleID() ],
3839 __METHOD__,
3840 [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
3841 );
3842
3843 $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
3844 }
3845
3846 return $this->mIsBigDeletion;
3847 }
3848
3854 public function estimateRevisionCount() {
3855 if ( !$this->exists() ) {
3856 return 0;
3857 }
3858
3859 if ( $this->mEstimateRevisions === null ) {
3860 $dbr = wfGetDB( DB_REPLICA );
3861 $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
3862 [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
3863 }
3864
3865 return $this->mEstimateRevisions;
3866 }
3867
3877 public function countRevisionsBetween( $old, $new, $max = null ) {
3878 if ( !( $old instanceof Revision ) ) {
3879 $old = Revision::newFromTitle( $this, (int)$old );
3880 }
3881 if ( !( $new instanceof Revision ) ) {
3882 $new = Revision::newFromTitle( $this, (int)$new );
3883 }
3884 if ( !$old || !$new ) {
3885 return 0; // nothing to compare
3886 }
3887 $dbr = wfGetDB( DB_REPLICA );
3888 $conds = [
3889 'rev_page' => $this->getArticleID(),
3890 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
3891 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
3892 ];
3893 if ( $max !== null ) {
3894 return $dbr->selectRowCount( 'revision', '1',
3895 $conds,
3896 __METHOD__,
3897 [ 'LIMIT' => $max + 1 ] // extra to detect truncation
3898 );
3899 } else {
3900 return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
3901 }
3902 }
3903
3920 public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
3921 if ( !( $old instanceof Revision ) ) {
3922 $old = Revision::newFromTitle( $this, (int)$old );
3923 }
3924 if ( !( $new instanceof Revision ) ) {
3925 $new = Revision::newFromTitle( $this, (int)$new );
3926 }
3927 // XXX: what if Revision objects are passed in, but they don't refer to this title?
3928 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
3929 // in the sanity check below?
3930 if ( !$old || !$new ) {
3931 return null; // nothing to compare
3932 }
3933 $authors = [];
3934 $old_cmp = '>';
3935 $new_cmp = '<';
3937 if ( in_array( 'include_old', $options ) ) {
3938 $old_cmp = '>=';
3939 }
3940 if ( in_array( 'include_new', $options ) ) {
3941 $new_cmp = '<=';
3942 }
3943 if ( in_array( 'include_both', $options ) ) {
3944 $old_cmp = '>=';
3945 $new_cmp = '<=';
3946 }
3947 // No DB query needed if $old and $new are the same or successive revisions:
3948 if ( $old->getId() === $new->getId() ) {
3949 return ( $old_cmp === '>' && $new_cmp === '<' ) ?
3950 [] :
3951 [ $old->getUserText( Revision::RAW ) ];
3952 } elseif ( $old->getId() === $new->getParentId() ) {
3953 if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
3954 $authors[] = $old->getUserText( Revision::RAW );
3955 if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
3956 $authors[] = $new->getUserText( Revision::RAW );
3957 }
3958 } elseif ( $old_cmp === '>=' ) {
3959 $authors[] = $old->getUserText( Revision::RAW );
3960 } elseif ( $new_cmp === '<=' ) {
3961 $authors[] = $new->getUserText( Revision::RAW );
3962 }
3963 return $authors;
3964 }
3965 $dbr = wfGetDB( DB_REPLICA );
3967 $authors = $dbr->selectFieldValues(
3968 $revQuery['tables'],
3969 $revQuery['fields']['rev_user_text'],
3970 [
3971 'rev_page' => $this->getArticleID(),
3972 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
3973 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
3974 ], __METHOD__,
3975 [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated
3976 $revQuery['joins']
3977 );
3978 return $authors;
3979 }
3980
3995 public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
3996 $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
3997 return $authors ? count( $authors ) : 0;
3998 }
3999
4006 public function equals( Title $title ) {
4007 // Note: === is necessary for proper matching of number-like titles.
4008 return $this->mInterwiki === $title->mInterwiki
4009 && $this->mNamespace == $title->mNamespace
4010 && $this->mDbkeyform === $title->mDbkeyform;
4011 }
4012
4019 public function isSubpageOf( Title $title ) {
4020 return $this->mInterwiki === $title->mInterwiki
4021 && $this->mNamespace == $title->mNamespace
4022 && strpos( $this->mDbkeyform, $title->mDbkeyform . '/' ) === 0;
4023 }
4024
4036 public function exists( $flags = 0 ) {
4037 $exists = $this->getArticleID( $flags ) != 0;
4038 Hooks::run( 'TitleExists', [ $this, &$exists ] );
4039 return $exists;
4040 }
4041
4058 public function isAlwaysKnown() {
4059 $isKnown = null;
4060
4071 Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4072
4073 if ( !is_null( $isKnown ) ) {
4074 return $isKnown;
4075 }
4076
4077 if ( $this->isExternal() ) {
4078 return true; // any interwiki link might be viewable, for all we know
4079 }
4080
4081 switch ( $this->mNamespace ) {
4082 case NS_MEDIA:
4083 case NS_FILE:
4084 // file exists, possibly in a foreign repo
4085 return (bool)wfFindFile( $this );
4086 case NS_SPECIAL:
4087 // valid special page
4088 return MediaWikiServices::getInstance()->getSpecialPageFactory()->
4089 exists( $this->mDbkeyform );
4090 case NS_MAIN:
4091 // selflink, possibly with fragment
4092 return $this->mDbkeyform == '';
4093 case NS_MEDIAWIKI:
4094 // known system message
4095 return $this->hasSourceText() !== false;
4096 default:
4097 return false;
4098 }
4099 }
4100
4112 public function isKnown() {
4113 return $this->isAlwaysKnown() || $this->exists();
4114 }
4115
4121 public function hasSourceText() {
4122 if ( $this->exists() ) {
4123 return true;
4124 }
4125
4126 if ( $this->mNamespace == NS_MEDIAWIKI ) {
4127 // If the page doesn't exist but is a known system message, default
4128 // message content will be displayed, same for language subpages-
4129 // Use always content language to avoid loading hundreds of languages
4130 // to get the link color.
4131 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
4132 list( $name, ) = MessageCache::singleton()->figureMessage(
4133 $contLang->lcfirst( $this->getText() )
4134 );
4135 $message = wfMessage( $name )->inLanguage( $contLang )->useDatabase( false );
4136 return $message->exists();
4137 }
4138
4139 return false;
4140 }
4141
4179 public function getDefaultMessageText() {
4180 if ( $this->mNamespace != NS_MEDIAWIKI ) { // Just in case
4181 return false;
4182 }
4183
4184 list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4185 MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $this->getText() )
4186 );
4187 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4188
4189 if ( $message->exists() ) {
4190 return $message->plain();
4191 } else {
4192 return false;
4193 }
4194 }
4195
4202 public function invalidateCache( $purgeTime = null ) {
4203 if ( wfReadOnly() ) {
4204 return false;
4205 } elseif ( $this->mArticleID === 0 ) {
4206 return true; // avoid gap locking if we know it's not there
4207 }
4208
4209 $dbw = wfGetDB( DB_MASTER );
4210 $dbw->onTransactionPreCommitOrIdle(
4211 function () use ( $dbw ) {
4212 ResourceLoaderWikiModule::invalidateModuleCache(
4213 $this, null, null, $dbw->getDomainID() );
4214 },
4215 __METHOD__
4216 );
4217
4218 $conds = $this->pageCond();
4219 DeferredUpdates::addUpdate(
4220 new AutoCommitUpdate(
4221 $dbw,
4222 __METHOD__,
4223 function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) {
4224 $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4225 $dbw->update(
4226 'page',
4227 [ 'page_touched' => $dbTimestamp ],
4228 $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4229 $fname
4230 );
4231 MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this );
4232 }
4233 ),
4234 DeferredUpdates::PRESEND
4235 );
4236
4237 return true;
4238 }
4239
4245 public function touchLinks() {
4246 DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
4247 if ( $this->mNamespace == NS_CATEGORY ) {
4248 DeferredUpdates::addUpdate(
4249 new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
4250 );
4251 }
4252 }
4253
4260 public function getTouched( $db = null ) {
4261 if ( $db === null ) {
4262 $db = wfGetDB( DB_REPLICA );
4263 }
4264 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4265 return $touched;
4266 }
4267
4274 public function getNotificationTimestamp( $user = null ) {
4275 global $wgUser;
4276
4277 // Assume current user if none given
4278 if ( !$user ) {
4279 $user = $wgUser;
4280 }
4281 // Check cache first
4282 $uid = $user->getId();
4283 if ( !$uid ) {
4284 return false;
4285 }
4286 // avoid isset here, as it'll return false for null entries
4287 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4288 return $this->mNotificationTimestamp[$uid];
4289 }
4290 // Don't cache too much!
4291 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4292 $this->mNotificationTimestamp = [];
4293 }
4294
4295 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
4296 $watchedItem = $store->getWatchedItem( $user, $this );
4297 if ( $watchedItem ) {
4298 $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4299 } else {
4300 $this->mNotificationTimestamp[$uid] = false;
4301 }
4302
4303 return $this->mNotificationTimestamp[$uid];
4304 }
4305
4312 public function getNamespaceKey( $prepend = 'nstab-' ) {
4313 // Gets the subject namespace of this title
4314 $subjectNS = MWNamespace::getSubject( $this->mNamespace );
4315 // Prefer canonical namespace name for HTML IDs
4316 $namespaceKey = MWNamespace::getCanonicalName( $subjectNS );
4317 if ( $namespaceKey === false ) {
4318 // Fallback to localised text
4319 $namespaceKey = $this->getSubjectNsText();
4320 }
4321 // Makes namespace key lowercase
4322 $namespaceKey = MediaWikiServices::getInstance()->getContentLanguage()->lc( $namespaceKey );
4323 // Uses main
4324 if ( $namespaceKey == '' ) {
4325 $namespaceKey = 'main';
4326 }
4327 // Changes file to image for backwards compatibility
4328 if ( $namespaceKey == 'file' ) {
4329 $namespaceKey = 'image';
4330 }
4331 return $prepend . $namespaceKey;
4332 }
4333
4340 public function getRedirectsHere( $ns = null ) {
4341 $redirs = [];
4342
4343 $dbr = wfGetDB( DB_REPLICA );
4344 $where = [
4345 'rd_namespace' => $this->mNamespace,
4346 'rd_title' => $this->mDbkeyform,
4347 'rd_from = page_id'
4348 ];
4349 if ( $this->isExternal() ) {
4350 $where['rd_interwiki'] = $this->mInterwiki;
4351 } else {
4352 $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4353 }
4354 if ( !is_null( $ns ) ) {
4355 $where['page_namespace'] = $ns;
4356 }
4357
4358 $res = $dbr->select(
4359 [ 'redirect', 'page' ],
4360 [ 'page_namespace', 'page_title' ],
4361 $where,
4362 __METHOD__
4363 );
4364
4365 foreach ( $res as $row ) {
4366 $redirs[] = self::newFromRow( $row );
4367 }
4368 return $redirs;
4369 }
4370
4376 public function isValidRedirectTarget() {
4378
4379 if ( $this->isSpecialPage() ) {
4380 // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4381 if ( $this->isSpecial( 'Userlogout' ) ) {
4382 return false;
4383 }
4384
4385 foreach ( $wgInvalidRedirectTargets as $target ) {
4386 if ( $this->isSpecial( $target ) ) {
4387 return false;
4388 }
4389 }
4390 }
4391
4392 return true;
4393 }
4394
4400 public function getBacklinkCache() {
4401 return BacklinkCache::get( $this );
4402 }
4403
4409 public function canUseNoindex() {
4411
4412 $bannedNamespaces = $wgExemptFromUserRobotsControl ?? MWNamespace::getContentNamespaces();
4413
4414 return !in_array( $this->mNamespace, $bannedNamespaces );
4415 }
4416
4427 public function getCategorySortkey( $prefix = '' ) {
4428 $unprefixed = $this->getText();
4429
4430 // Anything that uses this hook should only depend
4431 // on the Title object passed in, and should probably
4432 // tell the users to run updateCollations.php --force
4433 // in order to re-sort existing category relations.
4434 Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4435 if ( $prefix !== '' ) {
4436 # Separate with a line feed, so the unprefixed part is only used as
4437 # a tiebreaker when two pages have the exact same prefix.
4438 # In UCA, tab is the only character that can sort above LF
4439 # so we strip both of them from the original prefix.
4440 $prefix = strtr( $prefix, "\n\t", ' ' );
4441 return "$prefix\n$unprefixed";
4442 }
4443 return $unprefixed;
4444 }
4445
4453 private function getDbPageLanguageCode() {
4454 global $wgPageLanguageUseDB;
4455
4456 // check, if the page language could be saved in the database, and if so and
4457 // the value is not requested already, lookup the page language using LinkCache
4458 if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4459 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
4460 $linkCache->addLinkObj( $this );
4461 $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4462 }
4463
4464 return $this->mDbPageLanguage;
4465 }
4466
4475 public function getPageLanguage() {
4476 global $wgLang, $wgLanguageCode;
4477 if ( $this->isSpecialPage() ) {
4478 // special pages are in the user language
4479 return $wgLang;
4480 }
4481
4482 // Checking if DB language is set
4483 $dbPageLanguage = $this->getDbPageLanguageCode();
4484 if ( $dbPageLanguage ) {
4485 return wfGetLangObj( $dbPageLanguage );
4486 }
4487
4488 if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4489 // Note that this may depend on user settings, so the cache should
4490 // be only per-request.
4491 // NOTE: ContentHandler::getPageLanguage() may need to load the
4492 // content to determine the page language!
4493 // Checking $wgLanguageCode hasn't changed for the benefit of unit
4494 // tests.
4495 $contentHandler = ContentHandler::getForTitle( $this );
4496 $langObj = $contentHandler->getPageLanguage( $this );
4497 $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4498 } else {
4499 $langObj = Language::factory( $this->mPageLanguage[0] );
4500 }
4501
4502 return $langObj;
4503 }
4504
4513 public function getPageViewLanguage() {
4514 global $wgLang;
4515
4516 if ( $this->isSpecialPage() ) {
4517 // If the user chooses a variant, the content is actually
4518 // in a language whose code is the variant code.
4519 $variant = $wgLang->getPreferredVariant();
4520 if ( $wgLang->getCode() !== $variant ) {
4521 return Language::factory( $variant );
4522 }
4523
4524 return $wgLang;
4525 }
4526
4527 // Checking if DB language is set
4528 $dbPageLanguage = $this->getDbPageLanguageCode();
4529 if ( $dbPageLanguage ) {
4530 $pageLang = wfGetLangObj( $dbPageLanguage );
4531 $variant = $pageLang->getPreferredVariant();
4532 if ( $pageLang->getCode() !== $variant ) {
4533 $pageLang = Language::factory( $variant );
4534 }
4535
4536 return $pageLang;
4537 }
4538
4539 // @note Can't be cached persistently, depends on user settings.
4540 // @note ContentHandler::getPageViewLanguage() may need to load the
4541 // content to determine the page language!
4542 $contentHandler = ContentHandler::getForTitle( $this );
4543 $pageLang = $contentHandler->getPageViewLanguage( $this );
4544 return $pageLang;
4545 }
4546
4557 public function getEditNotices( $oldid = 0 ) {
4558 $notices = [];
4559
4560 // Optional notice for the entire namespace
4561 $editnotice_ns = 'editnotice-' . $this->mNamespace;
4562 $msg = wfMessage( $editnotice_ns );
4563 if ( $msg->exists() ) {
4564 $html = $msg->parseAsBlock();
4565 // Edit notices may have complex logic, but output nothing (T91715)
4566 if ( trim( $html ) !== '' ) {
4567 $notices[$editnotice_ns] = Html::rawElement(
4568 'div',
4569 [ 'class' => [
4570 'mw-editnotice',
4571 'mw-editnotice-namespace',
4572 Sanitizer::escapeClass( "mw-$editnotice_ns" )
4573 ] ],
4574 $html
4575 );
4576 }
4577 }
4578
4579 if ( MWNamespace::hasSubpages( $this->mNamespace ) ) {
4580 // Optional notice for page itself and any parent page
4581 $editnotice_base = $editnotice_ns;
4582 foreach ( explode( '/', $this->mDbkeyform ) as $part ) {
4583 $editnotice_base .= '-' . $part;
4584 $msg = wfMessage( $editnotice_base );
4585 if ( $msg->exists() ) {
4586 $html = $msg->parseAsBlock();
4587 if ( trim( $html ) !== '' ) {
4588 $notices[$editnotice_base] = Html::rawElement(
4589 'div',
4590 [ 'class' => [
4591 'mw-editnotice',
4592 'mw-editnotice-base',
4593 Sanitizer::escapeClass( "mw-$editnotice_base" )
4594 ] ],
4595 $html
4596 );
4597 }
4598 }
4599 }
4600 } else {
4601 // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4602 $editnoticeText = $editnotice_ns . '-' . strtr( $this->mDbkeyform, '/', '-' );
4603 $msg = wfMessage( $editnoticeText );
4604 if ( $msg->exists() ) {
4605 $html = $msg->parseAsBlock();
4606 if ( trim( $html ) !== '' ) {
4607 $notices[$editnoticeText] = Html::rawElement(
4608 'div',
4609 [ 'class' => [
4610 'mw-editnotice',
4611 'mw-editnotice-page',
4612 Sanitizer::escapeClass( "mw-$editnoticeText" )
4613 ] ],
4614 $html
4615 );
4616 }
4617 }
4618 }
4619
4620 Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4621 return $notices;
4622 }
4623
4627 public function __sleep() {
4628 return [
4629 'mNamespace',
4630 'mDbkeyform',
4631 'mFragment',
4632 'mInterwiki',
4633 'mLocalInterwiki',
4634 'mUserCaseDBKey',
4635 'mDefaultNamespace',
4636 ];
4637 }
4638
4639 public function __wakeup() {
4640 $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4641 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4642 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4643 }
4644
4645}
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
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...
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
$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:123
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:728
$wgLang
Definition Setup.php:875
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.
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Handles the backend logic of moving a page from one title to another.
Definition MovePage.php:32
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object.
Definition Revision.php:511
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:137
const RAW
Definition Revision.php:56
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:40
string $mInterwiki
Interwiki prefix.
Definition Title.php:89
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition Title.php:457
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition Title.php:1191
getSubpages( $limit=-1)
Get all subpages of this page.
Definition Title.php:2876
isWatchable()
Can this title be added to a user's watchlist?
Definition Title.php:1119
getTalkPageIfDefined()
Get a Title object associated with the talk page of this article, if such a talk page can exist.
Definition Title.php:1488
getNamespace()
Get the namespace index, i.e.
Definition Title.php:994
estimateRevisionCount()
Get the approximate revision count of this page.
Definition Title.php:3854
__wakeup()
Text form (spaces not underscores) of the main part.
Definition Title.php:4639
static newFromDBkey( $key)
Create a new Title from a prefixed DB key.
Definition Title.php:231
isProtected( $action='')
Does the title correspond to a protected article?
Definition Title.php:2406
getUserPermissionsErrors( $action, $user, $rigor=PermissionManager::RIGOR_SECURE, $ignoreErrors=[])
Can $user perform $action on this page?
Definition Title.php:2196
const NEW_CLONE
Flag for use with factory methods like newFromLinkTarget() that have a $forceClone parameter.
Definition Title.php:64
getTitleProtectionInternal()
Fetch title protection settings.
Definition Title.php:2313
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:2047
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition Title.php:180
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition Title.php:142
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition Title.php:3524
wasLocalInterwiki()
Was this a local interwiki link?
Definition Title.php:889
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition Title.php:2072
purgeSquid()
Purge all applicable CDN URLs.
Definition Title.php:3362
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition Title.php:1879
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition Title.php:2586
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition Title.php:269
isKnown()
Does this title refer to a page that can (or might) be meaningfully viewed? In particular,...
Definition Title.php:4112
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition Title.php:116
getBacklinkCache()
Get a backlink cache object.
Definition Title.php:4400
static getInterwikiLookup()
B/C kludge: provide an InterwikiLookup for use by Title.
Definition Title.php:213
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition Title.php:201
inNamespace( $ns)
Returns true if the title is inside the specified namespace.
Definition Title.php:1180
equals(Title $title)
Compare with another title.
Definition Title.php:4006
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition Title.php:2928
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition Title.php:3110
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition Title.php:1475
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition Title.php:3130
isSiteJsonConfigPage()
Is this a sitewide JSON "config" page?
Definition Title.php:1415
isSiteCssConfigPage()
Is this a sitewide CSS "config" page?
Definition Title.php:1397
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition Title.php:2600
hasContentModel( $id)
Convenience method for checking a title's content model name.
Definition Title.php:1029
static clearCaches()
Text form (spaces not underscores) of the main part.
Definition Title.php:3095
createFragmentTarget( $fragment)
Creates a new Title for a different fragment of the same page.
Definition Title.php:1606
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition Title.php:1538
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move a title to a new location.
Definition Title.php:3413
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition Title.php:1281
getFragment()
Get the Title fragment (i.e.
Definition Title.php:1549
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not.
Definition Title.php:2456
static getFilteredRestrictionTypes( $exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition Title.php:2245
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition Title.php:1822
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition Title.php:1293
getTalkNsText()
Get the namespace text of the talk page.
Definition Title.php:1088
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition Title.php:2626
hasFragment()
Check if a Title fragment is set.
Definition Title.php:1559
static nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition Title.php:647
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition Title.php:1138
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition Title.php:4340
getLength( $flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition Title.php:3013
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition Title.php:174
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition Title.php:3379
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition Title.php:1684
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition Title.php:2573
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition Title.php:4409
exists( $flags=0)
Check if page exists.
Definition Title.php:4036
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition Title.php:394
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,...
Definition Title.php:343
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition Title.php:854
int $mLength
The page length, 0 for special pages.
Definition Title.php:168
loadFromRow( $row)
Load Title object fields from a DB row.
Definition Title.php:518
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition Title.php:4475
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition Title.php:92
getUserCaseDBKey()
Get the DB key with the initial letter case as specified by the user.
Definition Title.php:980
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable.
Definition Title.php:1240
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles.
Definition Title.php:49
getRestrictionExpiry( $action)
Get the expiry time for the restriction against a given action.
Definition Title.php:2614
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition Title.php:1502
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:3214
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias,...
Definition Title.php:1156
getRestrictionTypes()
Returns restriction types for the current Title.
Definition Title.php:2263
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition Title.php:669
__toString()
Return a string representation of this title.
Definition Title.php:1674
hasSubjectNamespace( $ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition Title.php:1219
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition Title.php:2378
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1648
areCascadeProtectionSourcesLoaded( $getPages=true)
Determines whether cascading protection sources have already been loaded from the database.
Definition Title.php:2470
getPreviousRevisionID( $revId, $flags=0)
Get the revision ID of the previous revision.
Definition Title.php:3757
getNsText()
Get the namespace text.
Definition Title.php:1054
canExist()
Is this in a namespace that allows actual pages?
Definition Title.php:1110
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition Title.php:2807
getDefaultMessageText()
Get the default (plain) message contents for an page that overrides an interface message key.
Definition Title.php:4179
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition Title.php:4453
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition Title.php:3877
bool $mForcedContentModel
If a content model was forced via setContentModel() this will be true to avoid having other code path...
Definition Title.php:113
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition Title.php:4274
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition Title.php:899
resetArticleID( $newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition Title.php:3073
isRawHtmlMessage()
Is this a message which can contain raw HTML?
Definition Title.php:1451
isSiteJsConfigPage()
Is this a sitewide JS "config" page?
Definition Title.php:1433
isNewPage()
Check if this is a new page.
Definition Title.php:3815
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition Title.php:4245
isExternal()
Is this Title interwiki?
Definition Title.php:869
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition Title.php:145
isMainPage()
Is this the mainpage?
Definition Title.php:1261
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:1568
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:184
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition Title.php:3920
loadRestrictions( $oldFashionedRestrictions=null, $flags=0)
Load restrictions from the page_restrictions table.
Definition Title.php:2723
isSpecialPage()
Returns true if this is a special page.
Definition Title.php:1128
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition Title.php:2438
isUserJsConfigPage()
Is this a JS "config" sub-page of a user page?
Definition Title.php:1383
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition Title.php:1811
canHaveTalkPage()
Can this title have a corresponding talk page?
Definition Title.php:1101
isTalkPage()
Is this a talk page of some sort?
Definition Title.php:1466
getRootTitle()
Get the root page name title, i.e.
Definition Title.php:1724
bool int $mLatestID
ID of most recent revision.
Definition Title.php:101
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page.
Definition Title.php:3296
getDBkey()
Get the main part with underscores.
Definition Title.php:970
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object.
Definition Title.php:1622
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition Title.php:3805
string $mFragment
Title fragment (i.e.
Definition Title.php:95
getRootText()
Get the root page name text without a namespace, i.e.
Definition Title.php:1704
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition Title.php:1914
bool string $mContentModel
ID of the page's content model, i.e.
Definition Title.php:107
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition Title.php:3041
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition Title.php:683
userCan( $action, $user=null, $rigor=PermissionManager::RIGOR_SECURE)
Can $user perform $action on this page?
Definition Title.php:2158
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition Title.php:4376
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:3230
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition Title.php:788
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:3172
string null $prefixedText
Text form including namespace/interwiki, initialised on demand.
Definition Title.php:155
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition Title.php:139
getPartialURL()
Get the URL-encoded form of the main part.
Definition Title.php:961
getBaseText()
Get the base page name without a namespace, i.e.
Definition Title.php:1739
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page,...
Definition Title.php:1230
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:952
getTouched( $db=null)
Get the last touched timestamp.
Definition Title.php:4260
getTitleValue()
Get a TitleValue object representing this Title.
Definition Title.php:929
static MapCacheLRU null $titleCache
Definition Title.php:42
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition Title.php:3685
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition Title.php:130
string $mUrlform
URL-encoded form of the main part.
Definition Title.php:77
getFirstRevision( $flags=0)
Get the first revision of the page.
Definition Title.php:3778
string $mTextform
Text form (spaces not underscores) of the main part.
Definition Title.php:74
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:1519
static newFromIDs( $ids)
Make an array of titles from an array of IDs.
Definition Title.php:480
quickUserCan( $action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition Title.php:2139
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
Definition Title.php:431
__construct()
Definition Title.php:220
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition Title.php:4019
getBaseTitle()
Get the base page name title, i.e.
Definition Title.php:1766
static newMainPage()
Create a new Title for the Main Page.
Definition Title.php:632
getParentCategoryTree( $children=[])
Get a tree of parent categories.
Definition Title.php:3658
bool $mHasSubpages
Whether a page has any subpages.
Definition Title.php:177
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition Title.php:3768
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition Title.php:136
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition Title.php:2645
static fixUrlQueryArgs( $query, $query2=false)
Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args get{Canonical,...
Definition Title.php:1841
isUserJsonConfigPage()
Is this a JSON "config" sub-page of a user page?
Definition Title.php:1369
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition Title.php:3575
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition Title.php:2978
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition Title.php:306
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition Title.php:4202
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition Title.php:133
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:2954
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition Title.php:1078
int $mNamespace
Namespace index, i.e.
Definition Title.php:86
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:604
null $mRedirect
Is the article at this title a redirect?
Definition Title.php:171
countAuthorsBetween( $old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition Title.php:3995
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition Title.php:814
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition Title.php:2096
isDeleted()
Is there a version of this page in the deletion archive?
Definition Title.php:2903
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition Title.php:4513
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition Title.php:55
getSkinFromConfigSubpage()
Trim down a .css, .json, or .js subpage title to get the corresponding skin name.
Definition Title.php:1339
array $mRestrictions
Array of groups allowed to edit this article.
Definition Title.php:119
isSiteConfigPage()
Could this MediaWiki namespace page contain custom CSS, JSON, or JavaScript for the global UI.
Definition Title.php:1311
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition Title.php:165
static newFromTitleValue(TitleValue $titleValue, $forceClone='')
Returns a Title given a TitleValue.
Definition Title.php:254
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true, array $changeTags=[])
Move this page's subpages to be subpages of $nt.
Definition Title.php:3451
getRelativeRevisionID( $revId, $flags, $dir)
Get next/previous revision ID relative to another revision ID.
Definition Title.php:3701
deleteTitleProtection()
Remove any title protection due to page existing.
Definition Title.php:2360
getSubpage( $text)
Get the title for a subpage of the current page.
Definition Title.php:1802
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition Title.php:2290
getEditURL()
Get the edit URL for this Title.
Definition Title.php:2110
getParentCategories()
Get categories to which this Title belongs and return an array of categories' names.
Definition Title.php:3623
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:576
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition Title.php:98
static getTitleCache()
Definition Title.php:417
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:3284
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition Title.php:912
isSubpage()
Is this a subpage?
Definition Title.php:1270
isValid()
Returns true if the title is valid, false if it is invalid.
Definition Title.php:833
setFragment( $fragment)
Set the fragment for this title.
Definition Title.php:1595
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition Title.php:1948
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:1006
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition Title.php:3825
bool null $mIsBigDeletion
Would deleting this page be a big deletion?
Definition Title.php:190
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition Title.php:3332
TitleValue null $mTitleValue
A corresponding TitleValue object.
Definition Title.php:187
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition Title.php:83
getInterwiki()
Get the interwiki prefix.
Definition Title.php:880
getEditNotices( $oldid=0)
Get a list of rendered edit notices for this page.
Definition Title.php:4557
__sleep()
Definition Title.php:4627
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition Title.php:1044
getCascadeProtectionSources( $getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition Title.php:2487
mixed $mTitleProtection
Cached value for getTitleProtection (create protection)
Definition Title.php:158
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition Title.php:1781
string $mDbkeyform
Main part with underscores.
Definition Title.php:80
hasSourceText()
Does this page have source text?
Definition Title.php:4121
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition Title.php:2797
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1660
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition Title.php:2848
string bool $mOldRestrictions
Comma-separated set of permission keys indicating who can move or edit the page from the page table,...
Definition Title.php:127
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition Title.php:2218
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition Title.php:4058
getNamespaceKey( $prepend='nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition Title.php:4312
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition Title.php:4427
static newFromRow( $row)
Make a Title object from a DB row.
Definition Title.php:506
isUserCssConfigPage()
Is this a CSS "config" sub-page of a user page?
Definition Title.php:1355
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
Relational database abstraction object.
Definition Database.php:49
$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
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
const PROTO_CANONICAL
Definition Defines.php:232
const NS_USER
Definition Defines.php:75
const CONTENT_MODEL_CSS
Definition Defines.php:246
const NS_FILE
Definition Defines.php:79
const PROTO_CURRENT
Definition Defines.php:231
const NS_MAIN
Definition Defines.php:73
const NS_MEDIAWIKI
Definition Defines.php:81
const NS_SPECIAL
Definition Defines.php:62
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:244
const CONTENT_MODEL_JSON
Definition Defines.php:248
const PROTO_HTTP
Definition Defines.php:228
const NS_MEDIA
Definition Defines.php:61
const PROTO_RELATIVE
Definition Defines.php:230
const NS_CATEGORY
Definition Defines.php:87
const CONTENT_MODEL_JAVASCRIPT
Definition Defines.php:245
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:2818
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition hooks.txt:1834
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition hooks.txt:1991
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:855
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:925
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:1266
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:1999
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:955
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:996
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition hooks.txt:783
null for the local wiki Added in
Definition hooks.txt:1588
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:2003
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
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:2011
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:271
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:1617
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
return true to allow those checks to and false if checking is done & $user
Definition hooks.txt:1510
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:1779
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
$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
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for database access objects.
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(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition router.php:42
if(!isset( $args[0])) $lang