MediaWiki REL1_27
Title.php
Go to the documentation of this file.
1<?php
25
34class Title implements LinkTarget {
36 static private $titleCache = null;
37
43 const CACHE_MAX = 1000;
44
49 const GAID_FOR_UPDATE = 1;
50
56 // @{
57
59 public $mTextform = '';
60
62 public $mUrlform = '';
63
65 public $mDbkeyform = '';
66
68 protected $mUserCaseDBKey;
69
72
74 public $mInterwiki = '';
75
77 private $mLocalInterwiki = false;
78
80 public $mFragment = '';
81
83 public $mArticleID = -1;
84
86 protected $mLatestID = false;
87
92 public $mContentModel = false;
93
96
98 public $mRestrictions = [];
99
101 protected $mOldRestrictions = false;
102
105
108
110 protected $mRestrictionsExpiry = [];
111
114
117
119 public $mRestrictionsLoaded = false;
120
122 protected $mPrefixedText = null;
123
126
133
135 protected $mLength = -1;
136
138 public $mRedirect = null;
139
142
145
147 private $mPageLanguage = false;
148
151 private $mDbPageLanguage = false;
152
154 private $mTitleValue = null;
155
157 private $mIsBigDeletion = null;
158 // @}
159
168 private static function getMediaWikiTitleCodec() {
170
171 static $titleCodec = null;
172 static $titleCodecFingerprint = null;
173
174 // $wgContLang and $wgLocalInterwikis may change (especially while testing),
175 // make sure we are using the right one. To detect changes over the course
176 // of a request, we remember a fingerprint of the config used to create the
177 // codec singleton, and re-create it if the fingerprint doesn't match.
178 $fingerprint = spl_object_hash( $wgContLang ) . '|' . implode( '+', $wgLocalInterwikis );
179
180 if ( $fingerprint !== $titleCodecFingerprint ) {
181 $titleCodec = null;
182 }
183
184 if ( !$titleCodec ) {
185 $titleCodec = new MediaWikiTitleCodec(
189 );
190 $titleCodecFingerprint = $fingerprint;
191 }
192
193 return $titleCodec;
194 }
195
204 private static function getTitleFormatter() {
205 // NOTE: we know that getMediaWikiTitleCodec() returns a MediaWikiTitleCodec,
206 // which implements TitleFormatter.
208 }
209
210 function __construct() {
211 }
212
221 public static function newFromDBkey( $key ) {
222 $t = new Title();
223 $t->mDbkeyform = $key;
224
225 try {
226 $t->secureAndSplit();
227 return $t;
228 } catch ( MalformedTitleException $ex ) {
229 return null;
230 }
231 }
232
240 public static function newFromTitleValue( TitleValue $titleValue ) {
241 return self::newFromLinkTarget( $titleValue );
242 }
243
251 public static function newFromLinkTarget( LinkTarget $linkTarget ) {
252 if ( $linkTarget instanceof Title ) {
253 // Special case if it's already a Title object
254 return $linkTarget;
255 }
256 return self::makeTitle(
257 $linkTarget->getNamespace(),
258 $linkTarget->getText(),
259 $linkTarget->getFragment(),
260 $linkTarget->getInterwiki()
261 );
262 }
263
277 public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
278 // DWIM: Integers can be passed in here when page titles are used as array keys.
279 if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) {
280 throw new InvalidArgumentException( '$text must be a string.' );
281 }
282 if ( $text === null ) {
283 return null;
284 }
285
286 try {
287 return Title::newFromTextThrow( strval( $text ), $defaultNamespace );
288 } catch ( MalformedTitleException $ex ) {
289 return null;
290 }
291 }
292
307 public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
308 if ( is_object( $text ) ) {
309 throw new MWException( '$text must be a string, given an object' );
310 }
311
313
314 // Wiki pages often contain multiple links to the same page.
315 // Title normalization and parsing can become expensive on pages with many
316 // links, so we can save a little time by caching them.
317 // In theory these are value objects and won't get changed...
318 if ( $defaultNamespace == NS_MAIN ) {
319 $t = $titleCache->get( $text );
320 if ( $t ) {
321 return $t;
322 }
323 }
324
325 // Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
326 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
327
328 $t = new Title();
329 $t->mDbkeyform = strtr( $filteredText, ' ', '_' );
330 $t->mDefaultNamespace = intval( $defaultNamespace );
331
332 $t->secureAndSplit();
333 if ( $defaultNamespace == NS_MAIN ) {
334 $titleCache->set( $text, $t );
335 }
336 return $t;
337 }
338
354 public static function newFromURL( $url ) {
355 $t = new Title();
356
357 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
358 # but some URLs used it as a space replacement and they still come
359 # from some external search tools.
360 if ( strpos( self::legalChars(), '+' ) === false ) {
361 $url = strtr( $url, '+', ' ' );
362 }
363
364 $t->mDbkeyform = strtr( $url, ' ', '_' );
365
366 try {
367 $t->secureAndSplit();
368 return $t;
369 } catch ( MalformedTitleException $ex ) {
370 return null;
371 }
372 }
373
377 private static function getTitleCache() {
378 if ( self::$titleCache == null ) {
379 self::$titleCache = new HashBagOStuff( [ 'maxKeys' => self::CACHE_MAX ] );
380 }
381 return self::$titleCache;
382 }
383
391 protected static function getSelectFields() {
393
394 $fields = [
395 'page_namespace', 'page_title', 'page_id',
396 'page_len', 'page_is_redirect', 'page_latest',
397 ];
398
400 $fields[] = 'page_content_model';
401 }
402
403 if ( $wgPageLanguageUseDB ) {
404 $fields[] = 'page_lang';
405 }
406
407 return $fields;
408 }
409
417 public static function newFromID( $id, $flags = 0 ) {
419 $row = $db->selectRow(
420 'page',
421 self::getSelectFields(),
422 [ 'page_id' => $id ],
423 __METHOD__
424 );
425 if ( $row !== false ) {
426 $title = Title::newFromRow( $row );
427 } else {
428 $title = null;
429 }
430 return $title;
431 }
432
439 public static function newFromIDs( $ids ) {
440 if ( !count( $ids ) ) {
441 return [];
442 }
443 $dbr = wfGetDB( DB_SLAVE );
444
445 $res = $dbr->select(
446 'page',
447 self::getSelectFields(),
448 [ 'page_id' => $ids ],
449 __METHOD__
450 );
451
452 $titles = [];
453 foreach ( $res as $row ) {
454 $titles[] = Title::newFromRow( $row );
455 }
456 return $titles;
457 }
458
465 public static function newFromRow( $row ) {
466 $t = self::makeTitle( $row->page_namespace, $row->page_title );
467 $t->loadFromRow( $row );
468 return $t;
469 }
470
477 public function loadFromRow( $row ) {
478 if ( $row ) { // page found
479 if ( isset( $row->page_id ) ) {
480 $this->mArticleID = (int)$row->page_id;
481 }
482 if ( isset( $row->page_len ) ) {
483 $this->mLength = (int)$row->page_len;
484 }
485 if ( isset( $row->page_is_redirect ) ) {
486 $this->mRedirect = (bool)$row->page_is_redirect;
487 }
488 if ( isset( $row->page_latest ) ) {
489 $this->mLatestID = (int)$row->page_latest;
490 }
491 if ( isset( $row->page_content_model ) ) {
492 $this->mContentModel = strval( $row->page_content_model );
493 } else {
494 $this->mContentModel = false; # initialized lazily in getContentModel()
495 }
496 if ( isset( $row->page_lang ) ) {
497 $this->mDbPageLanguage = (string)$row->page_lang;
498 }
499 if ( isset( $row->page_restrictions ) ) {
500 $this->mOldRestrictions = $row->page_restrictions;
501 }
502 } else { // page not found
503 $this->mArticleID = 0;
504 $this->mLength = 0;
505 $this->mRedirect = false;
506 $this->mLatestID = 0;
507 $this->mContentModel = false; # initialized lazily in getContentModel()
508 }
509 }
510
524 public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
525 $t = new Title();
526 $t->mInterwiki = $interwiki;
527 $t->mFragment = $fragment;
528 $t->mNamespace = $ns = intval( $ns );
529 $t->mDbkeyform = strtr( $title, ' ', '_' );
530 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
531 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
532 $t->mTextform = strtr( $title, '_', ' ' );
533 $t->mContentModel = false; # initialized lazily in getContentModel()
534 return $t;
535 }
536
548 public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
549 if ( !MWNamespace::exists( $ns ) ) {
550 return null;
551 }
552
553 $t = new Title();
554 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
555
556 try {
557 $t->secureAndSplit();
558 return $t;
559 } catch ( MalformedTitleException $ex ) {
560 return null;
561 }
562 }
563
569 public static function newMainPage() {
570 $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
571 // Don't give fatal errors if the message is broken
572 if ( !$title ) {
573 $title = Title::newFromText( 'Main Page' );
574 }
575 return $title;
576 }
577
584 public static function nameOf( $id ) {
585 $dbr = wfGetDB( DB_SLAVE );
586
587 $s = $dbr->selectRow(
588 'page',
589 [ 'page_namespace', 'page_title' ],
590 [ 'page_id' => $id ],
591 __METHOD__
592 );
593 if ( $s === false ) {
594 return null;
595 }
596
597 $n = self::makeName( $s->page_namespace, $s->page_title );
598 return $n;
599 }
600
606 public static function legalChars() {
608 return $wgLegalTitleChars;
609 }
610
620 static function getTitleInvalidRegex() {
621 wfDeprecated( __METHOD__, '1.25' );
623 }
624
634 public static function convertByteClassToUnicodeClass( $byteClass ) {
635 $length = strlen( $byteClass );
636 // Input token queue
637 $x0 = $x1 = $x2 = '';
638 // Decoded queue
639 $d0 = $d1 = $d2 = '';
640 // Decoded integer codepoints
641 $ord0 = $ord1 = $ord2 = 0;
642 // Re-encoded queue
643 $r0 = $r1 = $r2 = '';
644 // Output
645 $out = '';
646 // Flags
647 $allowUnicode = false;
648 for ( $pos = 0; $pos < $length; $pos++ ) {
649 // Shift the queues down
650 $x2 = $x1;
651 $x1 = $x0;
652 $d2 = $d1;
653 $d1 = $d0;
654 $ord2 = $ord1;
655 $ord1 = $ord0;
656 $r2 = $r1;
657 $r1 = $r0;
658 // Load the current input token and decoded values
659 $inChar = $byteClass[$pos];
660 if ( $inChar == '\\' ) {
661 if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
662 $x0 = $inChar . $m[0];
663 $d0 = chr( hexdec( $m[1] ) );
664 $pos += strlen( $m[0] );
665 } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
666 $x0 = $inChar . $m[0];
667 $d0 = chr( octdec( $m[0] ) );
668 $pos += strlen( $m[0] );
669 } elseif ( $pos + 1 >= $length ) {
670 $x0 = $d0 = '\\';
671 } else {
672 $d0 = $byteClass[$pos + 1];
673 $x0 = $inChar . $d0;
674 $pos += 1;
675 }
676 } else {
677 $x0 = $d0 = $inChar;
678 }
679 $ord0 = ord( $d0 );
680 // Load the current re-encoded value
681 if ( $ord0 < 32 || $ord0 == 0x7f ) {
682 $r0 = sprintf( '\x%02x', $ord0 );
683 } elseif ( $ord0 >= 0x80 ) {
684 // Allow unicode if a single high-bit character appears
685 $r0 = sprintf( '\x%02x', $ord0 );
686 $allowUnicode = true;
687 } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
688 $r0 = '\\' . $d0;
689 } else {
690 $r0 = $d0;
691 }
692 // Do the output
693 if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
694 // Range
695 if ( $ord2 > $ord0 ) {
696 // Empty range
697 } elseif ( $ord0 >= 0x80 ) {
698 // Unicode range
699 $allowUnicode = true;
700 if ( $ord2 < 0x80 ) {
701 // Keep the non-unicode section of the range
702 $out .= "$r2-\\x7F";
703 }
704 } else {
705 // Normal range
706 $out .= "$r2-$r0";
707 }
708 // Reset state to the initial value
709 $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
710 } elseif ( $ord2 < 0x80 ) {
711 // ASCII character
712 $out .= $r2;
713 }
714 }
715 if ( $ord1 < 0x80 ) {
716 $out .= $r1;
717 }
718 if ( $ord0 < 0x80 ) {
719 $out .= $r0;
720 }
721 if ( $allowUnicode ) {
722 $out .= '\u0080-\uFFFF';
723 }
724 return $out;
725 }
726
738 public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
739 $canonicalNamespace = false
740 ) {
742
743 if ( $canonicalNamespace ) {
744 $namespace = MWNamespace::getCanonicalName( $ns );
745 } else {
746 $namespace = $wgContLang->getNsText( $ns );
747 }
748 $name = $namespace == '' ? $title : "$namespace:$title";
749 if ( strval( $interwiki ) != '' ) {
750 $name = "$interwiki:$name";
751 }
752 if ( strval( $fragment ) != '' ) {
753 $name .= '#' . $fragment;
754 }
755 return $name;
756 }
757
764 static function escapeFragmentForURL( $fragment ) {
765 # Note that we don't urlencode the fragment. urlencoded Unicode
766 # fragments appear not to work in IE (at least up to 7) or in at least
767 # one version of Opera 9.x. The W3C validator, for one, doesn't seem
768 # to care if they aren't encoded.
769 return Sanitizer::escapeId( $fragment, 'noninitial' );
770 }
771
780 public static function compare( $a, $b ) {
781 if ( $a->getNamespace() == $b->getNamespace() ) {
782 return strcmp( $a->getText(), $b->getText() );
783 } else {
784 return $a->getNamespace() - $b->getNamespace();
785 }
786 }
787
795 public function isLocal() {
796 if ( $this->isExternal() ) {
797 $iw = Interwiki::fetch( $this->mInterwiki );
798 if ( $iw ) {
799 return $iw->isLocal();
800 }
801 }
802 return true;
803 }
804
810 public function isExternal() {
811 return $this->mInterwiki !== '';
812 }
813
821 public function getInterwiki() {
822 return $this->mInterwiki;
823 }
824
830 public function wasLocalInterwiki() {
832 }
833
840 public function isTrans() {
841 if ( !$this->isExternal() ) {
842 return false;
843 }
844
845 return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
846 }
847
853 public function getTransWikiID() {
854 if ( !$this->isExternal() ) {
855 return false;
856 }
857
858 return Interwiki::fetch( $this->mInterwiki )->getWikiID();
859 }
860
870 public function getTitleValue() {
871 if ( $this->mTitleValue === null ) {
872 try {
873 $this->mTitleValue = new TitleValue(
874 $this->getNamespace(),
875 $this->getDBkey(),
876 $this->getFragment(),
877 $this->getInterwiki()
878 );
879 } catch ( InvalidArgumentException $ex ) {
880 wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
881 $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
882 }
883 }
884
885 return $this->mTitleValue;
886 }
887
893 public function getText() {
894 return $this->mTextform;
895 }
896
902 public function getPartialURL() {
903 return $this->mUrlform;
904 }
905
911 public function getDBkey() {
912 return $this->mDbkeyform;
913 }
914
920 function getUserCaseDBKey() {
921 if ( !is_null( $this->mUserCaseDBKey ) ) {
923 } else {
924 // If created via makeTitle(), $this->mUserCaseDBKey is not set.
925 return $this->mDbkeyform;
926 }
927 }
928
934 public function getNamespace() {
935 return $this->mNamespace;
936 }
937
944 public function getContentModel( $flags = 0 ) {
945 if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
946 $linkCache = LinkCache::singleton();
947 $linkCache->addLinkObj( $this ); # in case we already had an article ID
948 $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
949 }
950
951 if ( !$this->mContentModel ) {
952 $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
953 }
954
956 }
957
964 public function hasContentModel( $id ) {
965 return $this->getContentModel() == $id;
966 }
967
973 public function getNsText() {
974 if ( $this->isExternal() ) {
975 // This probably shouldn't even happen,
976 // but for interwiki transclusion it sometimes does.
977 // Use the canonical namespaces if possible to try to
978 // resolve a foreign namespace.
979 if ( MWNamespace::exists( $this->mNamespace ) ) {
980 return MWNamespace::getCanonicalName( $this->mNamespace );
981 }
982 }
983
984 try {
985 $formatter = self::getTitleFormatter();
986 return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
987 } catch ( InvalidArgumentException $ex ) {
988 wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
989 return false;
990 }
991 }
992
998 public function getSubjectNsText() {
1000 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
1001 }
1002
1008 public function getTalkNsText() {
1010 return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
1011 }
1012
1018 public function canTalk() {
1019 return MWNamespace::canTalk( $this->mNamespace );
1020 }
1021
1027 public function canExist() {
1028 return $this->mNamespace >= NS_MAIN;
1029 }
1030
1036 public function isWatchable() {
1037 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
1038 }
1039
1045 public function isSpecialPage() {
1046 return $this->getNamespace() == NS_SPECIAL;
1047 }
1048
1055 public function isSpecial( $name ) {
1056 if ( $this->isSpecialPage() ) {
1057 list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
1058 if ( $name == $thisName ) {
1059 return true;
1060 }
1061 }
1062 return false;
1063 }
1064
1071 public function fixSpecialName() {
1072 if ( $this->isSpecialPage() ) {
1073 list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
1074 if ( $canonicalName ) {
1075 $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
1076 if ( $localName != $this->mDbkeyform ) {
1077 return Title::makeTitle( NS_SPECIAL, $localName );
1078 }
1079 }
1080 }
1081 return $this;
1082 }
1083
1094 public function inNamespace( $ns ) {
1095 return MWNamespace::equals( $this->getNamespace(), $ns );
1096 }
1097
1105 public function inNamespaces( /* ... */ ) {
1106 $namespaces = func_get_args();
1107 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
1109 }
1110
1111 foreach ( $namespaces as $ns ) {
1112 if ( $this->inNamespace( $ns ) ) {
1113 return true;
1114 }
1115 }
1116
1117 return false;
1118 }
1119
1133 public function hasSubjectNamespace( $ns ) {
1134 return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
1135 }
1136
1144 public function isContentPage() {
1145 return MWNamespace::isContent( $this->getNamespace() );
1146 }
1147
1154 public function isMovable() {
1155 if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
1156 // Interwiki title or immovable namespace. Hooks don't get to override here
1157 return false;
1158 }
1159
1160 $result = true;
1161 Hooks::run( 'TitleIsMovable', [ $this, &$result ] );
1162 return $result;
1163 }
1164
1175 public function isMainPage() {
1176 return $this->equals( Title::newMainPage() );
1177 }
1178
1184 public function isSubpage() {
1185 return MWNamespace::hasSubpages( $this->mNamespace )
1186 ? strpos( $this->getText(), '/' ) !== false
1187 : false;
1188 }
1189
1195 public function isConversionTable() {
1196 // @todo ConversionTable should become a separate content model.
1197
1198 return $this->getNamespace() == NS_MEDIAWIKI &&
1199 strpos( $this->getText(), 'Conversiontable/' ) === 0;
1200 }
1201
1207 public function isWikitextPage() {
1208 return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
1209 }
1210
1225 public function isCssOrJsPage() {
1226 $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
1227 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1229
1230 # @note This hook is also called in ContentHandler::getDefaultModel.
1231 # It's called here again to make sure hook functions can force this
1232 # method to return true even outside the MediaWiki namespace.
1233
1234 Hooks::run( 'TitleIsCssOrJsPage', [ $this, &$isCssOrJsPage ], '1.25' );
1235
1236 return $isCssOrJsPage;
1237 }
1238
1244 public function isCssJsSubpage() {
1245 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1246 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
1248 }
1249
1255 public function getSkinFromCssJsSubpage() {
1256 $subpage = explode( '/', $this->mTextform );
1257 $subpage = $subpage[count( $subpage ) - 1];
1258 $lastdot = strrpos( $subpage, '.' );
1259 if ( $lastdot === false ) {
1260 return $subpage; # Never happens: only called for names ending in '.css' or '.js'
1261 }
1262 return substr( $subpage, 0, $lastdot );
1263 }
1264
1270 public function isCssSubpage() {
1271 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1272 && $this->hasContentModel( CONTENT_MODEL_CSS ) );
1273 }
1274
1280 public function isJsSubpage() {
1281 return ( NS_USER == $this->mNamespace && $this->isSubpage()
1283 }
1284
1290 public function isTalkPage() {
1291 return MWNamespace::isTalk( $this->getNamespace() );
1292 }
1293
1299 public function getTalkPage() {
1300 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
1301 }
1302
1309 public function getSubjectPage() {
1310 // Is this the same title?
1311 $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
1312 if ( $this->getNamespace() == $subjectNS ) {
1313 return $this;
1314 }
1315 return Title::makeTitle( $subjectNS, $this->getDBkey() );
1316 }
1317
1326 public function getOtherPage() {
1327 if ( $this->isSpecialPage() ) {
1328 throw new MWException( 'Special pages cannot have other pages' );
1329 }
1330 if ( $this->isTalkPage() ) {
1331 return $this->getSubjectPage();
1332 } else {
1333 return $this->getTalkPage();
1334 }
1335 }
1336
1342 public function getDefaultNamespace() {
1344 }
1345
1353 public function getFragment() {
1354 return $this->mFragment;
1355 }
1356
1363 public function hasFragment() {
1364 return $this->mFragment !== '';
1365 }
1366
1371 public function getFragmentForURL() {
1372 if ( !$this->hasFragment() ) {
1373 return '';
1374 } else {
1375 return '#' . Title::escapeFragmentForURL( $this->getFragment() );
1376 }
1377 }
1378
1391 public function setFragment( $fragment ) {
1392 $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' );
1393 }
1394
1402 public function createFragmentTarget( $fragment ) {
1403 return self::makeTitle(
1404 $this->getNamespace(),
1405 $this->getText(),
1406 $fragment,
1407 $this->getInterwiki()
1408 );
1409
1410 }
1411
1419 private function prefix( $name ) {
1420 $p = '';
1421 if ( $this->isExternal() ) {
1422 $p = $this->mInterwiki . ':';
1423 }
1424
1425 if ( 0 != $this->mNamespace ) {
1426 $p .= $this->getNsText() . ':';
1427 }
1428 return $p . $name;
1429 }
1430
1437 public function getPrefixedDBkey() {
1438 $s = $this->prefix( $this->mDbkeyform );
1439 $s = strtr( $s, ' ', '_' );
1440 return $s;
1441 }
1442
1449 public function getPrefixedText() {
1450 if ( $this->mPrefixedText === null ) {
1451 $s = $this->prefix( $this->mTextform );
1452 $s = strtr( $s, '_', ' ' );
1453 $this->mPrefixedText = $s;
1454 }
1455 return $this->mPrefixedText;
1456 }
1457
1463 public function __toString() {
1464 return $this->getPrefixedText();
1465 }
1466
1473 public function getFullText() {
1474 $text = $this->getPrefixedText();
1475 if ( $this->hasFragment() ) {
1476 $text .= '#' . $this->getFragment();
1477 }
1478 return $text;
1479 }
1480
1493 public function getRootText() {
1494 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1495 return $this->getText();
1496 }
1497
1498 return strtok( $this->getText(), '/' );
1499 }
1500
1513 public function getRootTitle() {
1514 return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
1515 }
1516
1528 public function getBaseText() {
1529 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1530 return $this->getText();
1531 }
1532
1533 $parts = explode( '/', $this->getText() );
1534 # Don't discard the real title if there's no subpage involved
1535 if ( count( $parts ) > 1 ) {
1536 unset( $parts[count( $parts ) - 1] );
1537 }
1538 return implode( '/', $parts );
1539 }
1540
1553 public function getBaseTitle() {
1554 return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
1555 }
1556
1568 public function getSubpageText() {
1569 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
1570 return $this->mTextform;
1571 }
1572 $parts = explode( '/', $this->mTextform );
1573 return $parts[count( $parts ) - 1];
1574 }
1575
1589 public function getSubpage( $text ) {
1590 return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
1591 }
1592
1598 public function getSubpageUrlForm() {
1599 $text = $this->getSubpageText();
1600 $text = wfUrlencode( strtr( $text, ' ', '_' ) );
1601 return $text;
1602 }
1603
1609 public function getPrefixedURL() {
1610 $s = $this->prefix( $this->mDbkeyform );
1611 $s = wfUrlencode( strtr( $s, ' ', '_' ) );
1612 return $s;
1613 }
1614
1628 private static function fixUrlQueryArgs( $query, $query2 = false ) {
1629 if ( $query2 !== false ) {
1630 wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
1631 "method called with a second parameter is deprecated. Add your " .
1632 "parameter to an array passed as the first parameter.", "1.19" );
1633 }
1634 if ( is_array( $query ) ) {
1636 }
1637 if ( $query2 ) {
1638 if ( is_string( $query2 ) ) {
1639 // $query2 is a string, we will consider this to be
1640 // a deprecated $variant argument and add it to the query
1641 $query2 = wfArrayToCgi( [ 'variant' => $query2 ] );
1642 } else {
1643 $query2 = wfArrayToCgi( $query2 );
1644 }
1645 // If we have $query content add a & to it first
1646 if ( $query ) {
1647 $query .= '&';
1648 }
1649 // Now append the queries together
1650 $query .= $query2;
1651 }
1652 return $query;
1653 }
1654
1666 public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1667 $query = self::fixUrlQueryArgs( $query, $query2 );
1668
1669 # Hand off all the decisions on urls to getLocalURL
1670 $url = $this->getLocalURL( $query );
1671
1672 # Expand the url to make it a full url. Note that getLocalURL has the
1673 # potential to output full urls for a variety of reasons, so we use
1674 # wfExpandUrl instead of simply prepending $wgServer
1675 $url = wfExpandUrl( $url, $proto );
1676
1677 # Finally, add the fragment.
1678 $url .= $this->getFragmentForURL();
1679 // Avoid PHP 7.1 warning from passing $this by reference
1680 $titleRef = $this;
1681 Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] );
1682 return $url;
1683 }
1684
1701 public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) {
1702 $target = $this;
1703 if ( $this->isExternal() ) {
1704 $target = SpecialPage::getTitleFor(
1705 'GoToInterwiki',
1706 $this->getPrefixedDBKey()
1707 );
1708 }
1709 return $target->getFullUrl( $query, false, $proto );
1710 }
1711
1735 public function getLocalURL( $query = '', $query2 = false ) {
1737
1738 $query = self::fixUrlQueryArgs( $query, $query2 );
1739
1740 $interwiki = Interwiki::fetch( $this->mInterwiki );
1741 if ( $interwiki ) {
1742 $namespace = $this->getNsText();
1743 if ( $namespace != '' ) {
1744 # Can this actually happen? Interwikis shouldn't be parsed.
1745 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
1746 $namespace .= ':';
1747 }
1748 $url = $interwiki->getURL( $namespace . $this->getDBkey() );
1749 $url = wfAppendQuery( $url, $query );
1750 } else {
1751 $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
1752 if ( $query == '' ) {
1753 $url = str_replace( '$1', $dbkey, $wgArticlePath );
1754 // Avoid PHP 7.1 warning from passing $this by reference
1755 $titleRef = $this;
1756 Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] );
1757 } else {
1759 $url = false;
1760 $matches = [];
1761
1762 if ( !empty( $wgActionPaths )
1763 && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
1764 ) {
1765 $action = urldecode( $matches[2] );
1766 if ( isset( $wgActionPaths[$action] ) ) {
1767 $query = $matches[1];
1768 if ( isset( $matches[4] ) ) {
1769 $query .= $matches[4];
1770 }
1771 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
1772 if ( $query != '' ) {
1773 $url = wfAppendQuery( $url, $query );
1774 }
1775 }
1776 }
1777
1778 if ( $url === false
1780 && $wgContLang->getCode() === $this->getPageLanguage()->getCode()
1781 && $this->getPageLanguage()->hasVariants()
1782 && preg_match( '/^variant=([^&]*)$/', $query, $matches )
1783 ) {
1784 $variant = urldecode( $matches[1] );
1785 if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
1786 // Only do the variant replacement if the given variant is a valid
1787 // variant for the page's language.
1788 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
1789 $url = str_replace( '$1', $dbkey, $url );
1790 }
1791 }
1792
1793 if ( $url === false ) {
1794 if ( $query == '-' ) {
1795 $query = '';
1796 }
1797 $url = "{$wgScript}?title={$dbkey}&{$query}";
1798 }
1799 }
1800 // Avoid PHP 7.1 warning from passing $this by reference
1801 $titleRef = $this;
1802 Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] );
1803
1804 // @todo FIXME: This causes breakage in various places when we
1805 // actually expected a local URL and end up with dupe prefixes.
1806 if ( $wgRequest->getVal( 'action' ) == 'render' ) {
1807 $url = $wgServer . $url;
1808 }
1809 }
1810 // Avoid PHP 7.1 warning from passing $this by reference
1811 $titleRef = $this;
1812 Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] );
1813 return $url;
1814 }
1815
1832 public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
1833 if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
1834 $ret = $this->getFullURL( $query, $query2, $proto );
1835 } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
1836 $ret = $this->getFragmentForURL();
1837 } else {
1838 $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
1839 }
1840 return $ret;
1841 }
1842
1855 public function getInternalURL( $query = '', $query2 = false ) {
1857 $query = self::fixUrlQueryArgs( $query, $query2 );
1858 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
1859 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
1860 // Avoid PHP 7.1 warning from passing $this by reference
1861 $titleRef = $this;
1862 Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] );
1863 return $url;
1864 }
1865
1877 public function getCanonicalURL( $query = '', $query2 = false ) {
1878 $query = self::fixUrlQueryArgs( $query, $query2 );
1879 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
1880 // Avoid PHP 7.1 warning from passing $this by reference
1881 $titleRef = $this;
1882 Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] );
1883 return $url;
1884 }
1885
1891 public function getEditURL() {
1892 if ( $this->isExternal() ) {
1893 return '';
1894 }
1895 $s = $this->getLocalURL( 'action=edit' );
1896
1897 return $s;
1898 }
1899
1914 public function quickUserCan( $action, $user = null ) {
1915 return $this->userCan( $action, $user, false );
1916 }
1917
1927 public function userCan( $action, $user = null, $rigor = 'secure' ) {
1928 if ( !$user instanceof User ) {
1930 $user = $wgUser;
1931 }
1932
1933 return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
1934 }
1935
1952 $action, $user, $rigor = 'secure', $ignoreErrors = []
1953 ) {
1954 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
1955
1956 // Remove the errors being ignored.
1957 foreach ( $errors as $index => $error ) {
1958 $errKey = is_array( $error ) ? $error[0] : $error;
1959
1960 if ( in_array( $errKey, $ignoreErrors ) ) {
1961 unset( $errors[$index] );
1962 }
1963 if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
1964 unset( $errors[$index] );
1965 }
1966 }
1967
1968 return $errors;
1969 }
1970
1982 private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
1983 if ( !Hooks::run( 'TitleQuickPermissions',
1984 [ $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ] )
1985 ) {
1986 return $errors;
1987 }
1988
1989 if ( $action == 'create' ) {
1990 if (
1991 ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
1992 ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
1993 ) {
1994 $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
1995 }
1996 } elseif ( $action == 'move' ) {
1997 if ( !$user->isAllowed( 'move-rootuserpages' )
1998 && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
1999 // Show user page-specific message only if the user can move other pages
2000 $errors[] = [ 'cant-move-user-page' ];
2001 }
2002
2003 // Check if user is allowed to move files if it's a file
2004 if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
2005 $errors[] = [ 'movenotallowedfile' ];
2006 }
2007
2008 // Check if user is allowed to move category pages if it's a category page
2009 if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
2010 $errors[] = [ 'cant-move-category-page' ];
2011 }
2012
2013 if ( !$user->isAllowed( 'move' ) ) {
2014 // User can't move anything
2015 $userCanMove = User::groupHasPermission( 'user', 'move' );
2016 $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
2017 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
2018 // custom message if logged-in users without any special rights can move
2019 $errors[] = [ 'movenologintext' ];
2020 } else {
2021 $errors[] = [ 'movenotallowed' ];
2022 }
2023 }
2024 } elseif ( $action == 'move-target' ) {
2025 if ( !$user->isAllowed( 'move' ) ) {
2026 // User can't move anything
2027 $errors[] = [ 'movenotallowed' ];
2028 } elseif ( !$user->isAllowed( 'move-rootuserpages' )
2029 && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
2030 // Show user page-specific message only if the user can move other pages
2031 $errors[] = [ 'cant-move-to-user-page' ];
2032 } elseif ( !$user->isAllowed( 'move-categorypages' )
2033 && $this->mNamespace == NS_CATEGORY ) {
2034 // Show category page-specific message only if the user can move other pages
2035 $errors[] = [ 'cant-move-to-category-page' ];
2036 }
2037 } elseif ( !$user->isAllowed( $action ) ) {
2038 $errors[] = $this->missingPermissionError( $action, $short );
2039 }
2040
2041 return $errors;
2042 }
2043
2052 private function resultToError( $errors, $result ) {
2053 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
2054 // A single array representing an error
2055 $errors[] = $result;
2056 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
2057 // A nested array representing multiple errors
2058 $errors = array_merge( $errors, $result );
2059 } elseif ( $result !== '' && is_string( $result ) ) {
2060 // A string representing a message-id
2061 $errors[] = [ $result ];
2062 } elseif ( $result instanceof MessageSpecifier ) {
2063 // A message specifier representing an error
2064 $errors[] = [ $result ];
2065 } elseif ( $result === false ) {
2066 // a generic "We don't want them to do that"
2067 $errors[] = [ 'badaccess-group0' ];
2068 }
2069 return $errors;
2070 }
2071
2083 private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
2084 // Use getUserPermissionsErrors instead
2085 $result = '';
2086 // Avoid PHP 7.1 warning from passing $this by reference
2087 $titleRef = $this;
2088 if ( !Hooks::run( 'userCan', [ &$titleRef, &$user, $action, &$result ] ) ) {
2089 return $result ? [] : [ [ 'badaccess-group0' ] ];
2090 }
2091 // Check getUserPermissionsErrors hook
2092 // Avoid PHP 7.1 warning from passing $this by reference
2093 $titleRef = $this;
2094 if ( !Hooks::run( 'getUserPermissionsErrors', [ &$titleRef, &$user, $action, &$result ] ) ) {
2095 $errors = $this->resultToError( $errors, $result );
2096 }
2097 // Check getUserPermissionsErrorsExpensive hook
2098 if (
2099 $rigor !== 'quick'
2100 && !( $short && count( $errors ) > 0 )
2101 && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$titleRef, &$user, $action, &$result ] )
2102 ) {
2103 $errors = $this->resultToError( $errors, $result );
2104 }
2105
2106 return $errors;
2107 }
2108
2120 private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
2121 # Only 'createaccount' can be performed on special pages,
2122 # which don't actually exist in the DB.
2123 if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
2124 $errors[] = [ 'ns-specialprotected' ];
2125 }
2126
2127 # Check $wgNamespaceProtection for restricted namespaces
2128 if ( $this->isNamespaceProtected( $user ) ) {
2129 $ns = $this->mNamespace == NS_MAIN ?
2130 wfMessage( 'nstab-main' )->text() : $this->getNsText();
2131 $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
2132 [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
2133 }
2134
2135 return $errors;
2136 }
2137
2149 private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
2150 # Protect css/js subpages of user pages
2151 # XXX: this might be better using restrictions
2152 # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
2153 if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
2154 if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
2155 if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
2156 $errors[] = [ 'mycustomcssprotected', $action ];
2157 } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
2158 $errors[] = [ 'mycustomjsprotected', $action ];
2159 }
2160 } else {
2161 if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
2162 $errors[] = [ 'customcssprotected', $action ];
2163 } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
2164 $errors[] = [ 'customjsprotected', $action ];
2165 }
2166 }
2167 }
2168
2169 return $errors;
2170 }
2171
2185 private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
2186 foreach ( $this->getRestrictions( $action ) as $right ) {
2187 // Backwards compatibility, rewrite sysop -> editprotected
2188 if ( $right == 'sysop' ) {
2189 $right = 'editprotected';
2190 }
2191 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2192 if ( $right == 'autoconfirmed' ) {
2193 $right = 'editsemiprotected';
2194 }
2195 if ( $right == '' ) {
2196 continue;
2197 }
2198 if ( !$user->isAllowed( $right ) ) {
2199 $errors[] = [ 'protectedpagetext', $right, $action ];
2200 } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
2201 $errors[] = [ 'protectedpagetext', 'protect', $action ];
2202 }
2203 }
2204
2205 return $errors;
2206 }
2207
2219 private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
2220 if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
2221 # We /could/ use the protection level on the source page, but it's
2222 # fairly ugly as we have to establish a precedence hierarchy for pages
2223 # included by multiple cascade-protected pages. So just restrict
2224 # it to people with 'protect' permission, as they could remove the
2225 # protection anyway.
2226 list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
2227 # Cascading protection depends on more than this page...
2228 # Several cascading protected pages may include this page...
2229 # Check each cascading level
2230 # This is only for protection restrictions, not for all actions
2231 if ( isset( $restrictions[$action] ) ) {
2232 foreach ( $restrictions[$action] as $right ) {
2233 // Backwards compatibility, rewrite sysop -> editprotected
2234 if ( $right == 'sysop' ) {
2235 $right = 'editprotected';
2236 }
2237 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
2238 if ( $right == 'autoconfirmed' ) {
2239 $right = 'editsemiprotected';
2240 }
2241 if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
2242 $pages = '';
2243 foreach ( $cascadingSources as $page ) {
2244 $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
2245 }
2246 $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $pages, $action ];
2247 }
2248 }
2249 }
2250 }
2251
2252 return $errors;
2253 }
2254
2266 private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
2268
2269 if ( $action == 'protect' ) {
2270 if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2271 // If they can't edit, they shouldn't protect.
2272 $errors[] = [ 'protect-cantedit' ];
2273 }
2274 } elseif ( $action == 'create' ) {
2275 $title_protection = $this->getTitleProtection();
2276 if ( $title_protection ) {
2277 if ( $title_protection['permission'] == ''
2278 || !$user->isAllowed( $title_protection['permission'] )
2279 ) {
2280 $errors[] = [
2281 'titleprotected',
2282 User::whoIs( $title_protection['user'] ),
2283 $title_protection['reason']
2284 ];
2285 }
2286 }
2287 } elseif ( $action == 'move' ) {
2288 // Check for immobile pages
2289 if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2290 // Specific message for this case
2291 $errors[] = [ 'immobile-source-namespace', $this->getNsText() ];
2292 } elseif ( !$this->isMovable() ) {
2293 // Less specific message for rarer cases
2294 $errors[] = [ 'immobile-source-page' ];
2295 }
2296 } elseif ( $action == 'move-target' ) {
2297 if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
2298 $errors[] = [ 'immobile-target-namespace', $this->getNsText() ];
2299 } elseif ( !$this->isMovable() ) {
2300 $errors[] = [ 'immobile-target-page' ];
2301 }
2302 } elseif ( $action == 'delete' ) {
2303 $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true );
2304 if ( !$tempErrors ) {
2305 $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
2306 $user, $tempErrors, $rigor, true );
2307 }
2308 if ( $tempErrors ) {
2309 // If protection keeps them from editing, they shouldn't be able to delete.
2310 $errors[] = [ 'deleteprotected' ];
2311 }
2312 if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
2313 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
2314 ) {
2315 $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
2316 }
2317 } elseif ( $action === 'undelete' ) {
2318 if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
2319 // Undeleting implies editing
2320 $errors[] = [ 'undelete-cantedit' ];
2321 }
2322 if ( !$this->exists()
2323 && count( $this->getUserPermissionsErrorsInternal( 'create', $user, $rigor, true ) )
2324 ) {
2325 // Undeleting where nothing currently exists implies creating
2326 $errors[] = [ 'undelete-cantcreate' ];
2327 }
2328 }
2329 return $errors;
2330 }
2331
2343 private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
2345 // Account creation blocks handled at userlogin.
2346 // Unblocking handled in SpecialUnblock
2347 if ( $rigor === 'quick' || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
2348 return $errors;
2349 }
2350
2351 // Optimize for a very common case
2352 if ( $action === 'read' && !$wgBlockDisablesLogin ) {
2353 return $errors;
2354 }
2355
2357 && !$user->isEmailConfirmed()
2358 && $action === 'edit'
2359 ) {
2360 $errors[] = [ 'confirmedittext' ];
2361 }
2362
2363 $useSlave = ( $rigor !== 'secure' );
2364 if ( ( $action == 'edit' || $action == 'create' )
2365 && !$user->isBlockedFrom( $this, $useSlave )
2366 ) {
2367 // Don't block the user from editing their own talk page unless they've been
2368 // explicitly blocked from that too.
2369 } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
2370 // @todo FIXME: Pass the relevant context into this function.
2371 $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
2372 }
2373
2374 return $errors;
2375 }
2376
2388 private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
2390
2391 $whitelisted = false;
2392 if ( User::isEveryoneAllowed( 'read' ) ) {
2393 # Shortcut for public wikis, allows skipping quite a bit of code
2394 $whitelisted = true;
2395 } elseif ( $user->isAllowed( 'read' ) ) {
2396 # If the user is allowed to read pages, he is allowed to read all pages
2397 $whitelisted = true;
2398 } elseif ( $this->isSpecial( 'Userlogin' )
2399 || $this->isSpecial( 'ChangePassword' )
2400 || $this->isSpecial( 'PasswordReset' )
2401 ) {
2402 # Always grant access to the login page.
2403 # Even anons need to be able to log in.
2404 $whitelisted = true;
2405 } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
2406 # Time to check the whitelist
2407 # Only do these checks is there's something to check against
2408 $name = $this->getPrefixedText();
2409 $dbName = $this->getPrefixedDBkey();
2410
2411 // Check for explicit whitelisting with and without underscores
2412 if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
2413 $whitelisted = true;
2414 } elseif ( $this->getNamespace() == NS_MAIN ) {
2415 # Old settings might have the title prefixed with
2416 # a colon for main-namespace pages
2417 if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
2418 $whitelisted = true;
2419 }
2420 } elseif ( $this->isSpecialPage() ) {
2421 # If it's a special page, ditch the subpage bit and check again
2422 $name = $this->getDBkey();
2423 list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
2424 if ( $name ) {
2425 $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
2426 if ( in_array( $pure, $wgWhitelistRead, true ) ) {
2427 $whitelisted = true;
2428 }
2429 }
2430 }
2431 }
2432
2433 if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
2434 $name = $this->getPrefixedText();
2435 // Check for regex whitelisting
2436 foreach ( $wgWhitelistReadRegexp as $listItem ) {
2437 if ( preg_match( $listItem, $name ) ) {
2438 $whitelisted = true;
2439 break;
2440 }
2441 }
2442 }
2443
2444 if ( !$whitelisted ) {
2445 # If the title is not whitelisted, give extensions a chance to do so...
2446 Hooks::run( 'TitleReadWhitelist', [ $this, $user, &$whitelisted ] );
2447 if ( !$whitelisted ) {
2448 $errors[] = $this->missingPermissionError( $action, $short );
2449 }
2450 }
2451
2452 return $errors;
2453 }
2454
2463 private function missingPermissionError( $action, $short ) {
2464 // We avoid expensive display logic for quickUserCan's and such
2465 if ( $short ) {
2466 return [ 'badaccess-group0' ];
2467 }
2468
2469 $groups = array_map( [ 'User', 'makeGroupLinkWiki' ],
2470 User::getGroupsWithPermission( $action ) );
2471
2472 if ( count( $groups ) ) {
2474 return [
2475 'badaccess-groups',
2476 $wgLang->commaList( $groups ),
2477 count( $groups )
2478 ];
2479 } else {
2480 return [ 'badaccess-group0' ];
2481 }
2482 }
2483
2499 $action, $user, $rigor = 'secure', $short = false
2500 ) {
2501 if ( $rigor === true ) {
2502 $rigor = 'secure'; // b/c
2503 } elseif ( $rigor === false ) {
2504 $rigor = 'quick'; // b/c
2505 } elseif ( !in_array( $rigor, [ 'quick', 'full', 'secure' ] ) ) {
2506 throw new Exception( "Invalid rigor parameter '$rigor'." );
2507 }
2508
2509 # Read has special handling
2510 if ( $action == 'read' ) {
2511 $checks = [
2512 'checkPermissionHooks',
2513 'checkReadPermissions',
2514 'checkUserBlock', // for wgBlockDisablesLogin
2515 ];
2516 # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
2517 # here as it will lead to duplicate error messages. This is okay to do
2518 # since anywhere that checks for create will also check for edit, and
2519 # those checks are called for edit.
2520 } elseif ( $action == 'create' ) {
2521 $checks = [
2522 'checkQuickPermissions',
2523 'checkPermissionHooks',
2524 'checkPageRestrictions',
2525 'checkCascadingSourcesRestrictions',
2526 'checkActionPermissions',
2527 'checkUserBlock'
2528 ];
2529 } else {
2530 $checks = [
2531 'checkQuickPermissions',
2532 'checkPermissionHooks',
2533 'checkSpecialsAndNSPermissions',
2534 'checkCSSandJSPermissions',
2535 'checkPageRestrictions',
2536 'checkCascadingSourcesRestrictions',
2537 'checkActionPermissions',
2538 'checkUserBlock'
2539 ];
2540 }
2541
2542 $errors = [];
2543 while ( count( $checks ) > 0 &&
2544 !( $short && count( $errors ) > 0 ) ) {
2545 $method = array_shift( $checks );
2546 $errors = $this->$method( $action, $user, $errors, $rigor, $short );
2547 }
2548
2549 return $errors;
2550 }
2551
2559 public static function getFilteredRestrictionTypes( $exists = true ) {
2561 $types = $wgRestrictionTypes;
2562 if ( $exists ) {
2563 # Remove the create restriction for existing titles
2564 $types = array_diff( $types, [ 'create' ] );
2565 } else {
2566 # Only the create and upload restrictions apply to non-existing titles
2567 $types = array_intersect( $types, [ 'create', 'upload' ] );
2568 }
2569 return $types;
2570 }
2571
2577 public function getRestrictionTypes() {
2578 if ( $this->isSpecialPage() ) {
2579 return [];
2580 }
2581
2582 $types = self::getFilteredRestrictionTypes( $this->exists() );
2583
2584 if ( $this->getNamespace() != NS_FILE ) {
2585 # Remove the upload restriction for non-file titles
2586 $types = array_diff( $types, [ 'upload' ] );
2587 }
2588
2589 Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] );
2590
2591 wfDebug( __METHOD__ . ': applicable restrictions to [[' .
2592 $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
2593
2594 return $types;
2595 }
2596
2604 public function getTitleProtection() {
2605 // Can't protect pages in special namespaces
2606 if ( $this->getNamespace() < 0 ) {
2607 return false;
2608 }
2609
2610 // Can't protect pages that exist.
2611 if ( $this->exists() ) {
2612 return false;
2613 }
2614
2615 if ( $this->mTitleProtection === null ) {
2616 $dbr = wfGetDB( DB_SLAVE );
2617 $res = $dbr->select(
2618 'protected_titles',
2619 [
2620 'user' => 'pt_user',
2621 'reason' => 'pt_reason',
2622 'expiry' => 'pt_expiry',
2623 'permission' => 'pt_create_perm'
2624 ],
2625 [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2626 __METHOD__
2627 );
2628
2629 // fetchRow returns false if there are no rows.
2630 $row = $dbr->fetchRow( $res );
2631 if ( $row ) {
2632 if ( $row['permission'] == 'sysop' ) {
2633 $row['permission'] = 'editprotected'; // B/C
2634 }
2635 if ( $row['permission'] == 'autoconfirmed' ) {
2636 $row['permission'] = 'editsemiprotected'; // B/C
2637 }
2638 $row['expiry'] = $dbr->decodeExpiry( $row['expiry'] );
2639 }
2640 $this->mTitleProtection = $row;
2641 }
2643 }
2644
2648 public function deleteTitleProtection() {
2649 $dbw = wfGetDB( DB_MASTER );
2650
2651 $dbw->delete(
2652 'protected_titles',
2653 [ 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ],
2654 __METHOD__
2655 );
2656 $this->mTitleProtection = false;
2657 }
2658
2666 public function isSemiProtected( $action = 'edit' ) {
2668
2669 $restrictions = $this->getRestrictions( $action );
2671 if ( !$restrictions || !$semi ) {
2672 // Not protected, or all protection is full protection
2673 return false;
2674 }
2675
2676 // Remap autoconfirmed to editsemiprotected for BC
2677 foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
2678 $semi[$key] = 'editsemiprotected';
2679 }
2680 foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
2681 $restrictions[$key] = 'editsemiprotected';
2682 }
2683
2684 return !array_diff( $restrictions, $semi );
2685 }
2686
2694 public function isProtected( $action = '' ) {
2696
2697 $restrictionTypes = $this->getRestrictionTypes();
2698
2699 # Special pages have inherent protection
2700 if ( $this->isSpecialPage() ) {
2701 return true;
2702 }
2703
2704 # Check regular protection levels
2705 foreach ( $restrictionTypes as $type ) {
2706 if ( $action == $type || $action == '' ) {
2707 $r = $this->getRestrictions( $type );
2708 foreach ( $wgRestrictionLevels as $level ) {
2709 if ( in_array( $level, $r ) && $level != '' ) {
2710 return true;
2711 }
2712 }
2713 }
2714 }
2715
2716 return false;
2717 }
2718
2726 public function isNamespaceProtected( User $user ) {
2728
2729 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
2730 foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
2731 if ( $right != '' && !$user->isAllowed( $right ) ) {
2732 return true;
2733 }
2734 }
2735 }
2736 return false;
2737 }
2738
2744 public function isCascadeProtected() {
2745 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
2746 return ( $sources > 0 );
2747 }
2748
2758 public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
2759 return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
2760 }
2761
2775 public function getCascadeProtectionSources( $getPages = true ) {
2776 $pagerestrictions = [];
2777
2778 if ( $this->mCascadeSources !== null && $getPages ) {
2780 } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
2781 return [ $this->mHasCascadingRestrictions, $pagerestrictions ];
2782 }
2783
2784 $dbr = wfGetDB( DB_SLAVE );
2785
2786 if ( $this->getNamespace() == NS_FILE ) {
2787 $tables = [ 'imagelinks', 'page_restrictions' ];
2788 $where_clauses = [
2789 'il_to' => $this->getDBkey(),
2790 'il_from=pr_page',
2791 'pr_cascade' => 1
2792 ];
2793 } else {
2794 $tables = [ 'templatelinks', 'page_restrictions' ];
2795 $where_clauses = [
2796 'tl_namespace' => $this->getNamespace(),
2797 'tl_title' => $this->getDBkey(),
2798 'tl_from=pr_page',
2799 'pr_cascade' => 1
2800 ];
2801 }
2802
2803 if ( $getPages ) {
2804 $cols = [ 'pr_page', 'page_namespace', 'page_title',
2805 'pr_expiry', 'pr_type', 'pr_level' ];
2806 $where_clauses[] = 'page_id=pr_page';
2807 $tables[] = 'page';
2808 } else {
2809 $cols = [ 'pr_expiry' ];
2810 }
2811
2812 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
2813
2814 $sources = $getPages ? [] : false;
2815 $now = wfTimestampNow();
2816
2817 foreach ( $res as $row ) {
2818 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
2819 if ( $expiry > $now ) {
2820 if ( $getPages ) {
2821 $page_id = $row->pr_page;
2822 $page_ns = $row->page_namespace;
2823 $page_title = $row->page_title;
2824 $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
2825 # Add groups needed for each restriction type if its not already there
2826 # Make sure this restriction type still exists
2827
2828 if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
2829 $pagerestrictions[$row->pr_type] = [];
2830 }
2831
2832 if (
2833 isset( $pagerestrictions[$row->pr_type] )
2834 && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
2835 ) {
2836 $pagerestrictions[$row->pr_type][] = $row->pr_level;
2837 }
2838 } else {
2839 $sources = true;
2840 }
2841 }
2842 }
2843
2844 if ( $getPages ) {
2845 $this->mCascadeSources = $sources;
2846 $this->mCascadingRestrictions = $pagerestrictions;
2847 } else {
2848 $this->mHasCascadingRestrictions = $sources;
2849 }
2850
2851 return [ $sources, $pagerestrictions ];
2852 }
2853
2861 public function areRestrictionsLoaded() {
2863 }
2864
2874 public function getRestrictions( $action ) {
2875 if ( !$this->mRestrictionsLoaded ) {
2876 $this->loadRestrictions();
2877 }
2878 return isset( $this->mRestrictions[$action] )
2879 ? $this->mRestrictions[$action]
2880 : [];
2881 }
2882
2890 public function getAllRestrictions() {
2891 if ( !$this->mRestrictionsLoaded ) {
2892 $this->loadRestrictions();
2893 }
2894 return $this->mRestrictions;
2895 }
2896
2904 public function getRestrictionExpiry( $action ) {
2905 if ( !$this->mRestrictionsLoaded ) {
2906 $this->loadRestrictions();
2907 }
2908 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
2909 }
2910
2917 if ( !$this->mRestrictionsLoaded ) {
2918 $this->loadRestrictions();
2919 }
2920
2922 }
2923
2931 private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
2932 $rows = [];
2933
2934 foreach ( $res as $row ) {
2935 $rows[] = $row;
2936 }
2937
2938 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
2939 }
2940
2950 public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
2951 $dbr = wfGetDB( DB_SLAVE );
2952
2953 $restrictionTypes = $this->getRestrictionTypes();
2954
2955 foreach ( $restrictionTypes as $type ) {
2956 $this->mRestrictions[$type] = [];
2957 $this->mRestrictionsExpiry[$type] = 'infinity';
2958 }
2959
2960 $this->mCascadeRestriction = false;
2961
2962 # Backwards-compatibility: also load the restrictions from the page record (old format).
2963 if ( $oldFashionedRestrictions !== null ) {
2964 $this->mOldRestrictions = $oldFashionedRestrictions;
2965 }
2966
2967 if ( $this->mOldRestrictions === false ) {
2968 $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
2969 [ 'page_id' => $this->getArticleID() ], __METHOD__ );
2970 }
2971
2972 if ( $this->mOldRestrictions != '' ) {
2973 foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
2974 $temp = explode( '=', trim( $restrict ) );
2975 if ( count( $temp ) == 1 ) {
2976 // old old format should be treated as edit/move restriction
2977 $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
2978 $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
2979 } else {
2980 $restriction = trim( $temp[1] );
2981 if ( $restriction != '' ) { // some old entries are empty
2982 $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
2983 }
2984 }
2985 }
2986 }
2987
2988 if ( count( $rows ) ) {
2989 # Current system - load second to make them override.
2990 $now = wfTimestampNow();
2991
2992 # Cycle through all the restrictions.
2993 foreach ( $rows as $row ) {
2994
2995 // Don't take care of restrictions types that aren't allowed
2996 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
2997 continue;
2998 }
2999
3000 // This code should be refactored, now that it's being used more generally,
3001 // But I don't really see any harm in leaving it in Block for now -werdna
3002 $expiry = $dbr->decodeExpiry( $row->pr_expiry );
3003
3004 // Only apply the restrictions if they haven't expired!
3005 if ( !$expiry || $expiry > $now ) {
3006 $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
3007 $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
3008
3009 $this->mCascadeRestriction |= $row->pr_cascade;
3010 }
3011 }
3012 }
3013
3014 $this->mRestrictionsLoaded = true;
3015 }
3016
3023 public function loadRestrictions( $oldFashionedRestrictions = null ) {
3024 if ( !$this->mRestrictionsLoaded ) {
3025 $dbr = wfGetDB( DB_SLAVE );
3026 if ( $this->exists() ) {
3027 $res = $dbr->select(
3028 'page_restrictions',
3029 [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
3030 [ 'pr_page' => $this->getArticleID() ],
3031 __METHOD__
3032 );
3033
3034 $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
3035 } else {
3036 $title_protection = $this->getTitleProtection();
3037
3038 if ( $title_protection ) {
3039 $now = wfTimestampNow();
3040 $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
3041
3042 if ( !$expiry || $expiry > $now ) {
3043 // Apply the restrictions
3044 $this->mRestrictionsExpiry['create'] = $expiry;
3045 $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
3046 } else { // Get rid of the old restrictions
3047 $this->mTitleProtection = false;
3048 }
3049 } else {
3050 $this->mRestrictionsExpiry['create'] = 'infinity';
3051 }
3052 $this->mRestrictionsLoaded = true;
3053 }
3054 }
3055 }
3056
3061 public function flushRestrictions() {
3062 $this->mRestrictionsLoaded = false;
3063 $this->mTitleProtection = null;
3064 }
3065
3069 static function purgeExpiredRestrictions() {
3070 if ( wfReadOnly() ) {
3071 return;
3072 }
3073
3074 DeferredUpdates::addUpdate( new AtomicSectionUpdate(
3075 wfGetDB( DB_MASTER ),
3076 __METHOD__,
3077 function ( IDatabase $dbw, $fname ) {
3078 $dbw->delete(
3079 'page_restrictions',
3080 [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3081 $fname
3082 );
3083 $dbw->delete(
3084 'protected_titles',
3085 [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
3086 $fname
3087 );
3088 }
3089 ) );
3090 }
3091
3097 public function hasSubpages() {
3098 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
3099 # Duh
3100 return false;
3101 }
3102
3103 # We dynamically add a member variable for the purpose of this method
3104 # alone to cache the result. There's no point in having it hanging
3105 # around uninitialized in every Title object; therefore we only add it
3106 # if needed and don't declare it statically.
3107 if ( $this->mHasSubpages === null ) {
3108 $this->mHasSubpages = false;
3109 $subpages = $this->getSubpages( 1 );
3110 if ( $subpages instanceof TitleArray ) {
3111 $this->mHasSubpages = (bool)$subpages->count();
3112 }
3113 }
3114
3115 return $this->mHasSubpages;
3116 }
3117
3125 public function getSubpages( $limit = -1 ) {
3126 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3127 return [];
3128 }
3129
3130 $dbr = wfGetDB( DB_SLAVE );
3131 $conds['page_namespace'] = $this->getNamespace();
3132 $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
3133 $options = [];
3134 if ( $limit > -1 ) {
3135 $options['LIMIT'] = $limit;
3136 }
3137 $this->mSubpages = TitleArray::newFromResult(
3138 $dbr->select( 'page',
3139 [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ],
3140 $conds,
3141 __METHOD__,
3142 $options
3143 )
3144 );
3145 return $this->mSubpages;
3146 }
3147
3153 public function isDeleted() {
3154 if ( $this->getNamespace() < 0 ) {
3155 $n = 0;
3156 } else {
3157 $dbr = wfGetDB( DB_SLAVE );
3158
3159 $n = $dbr->selectField( 'archive', 'COUNT(*)',
3160 [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3161 __METHOD__
3162 );
3163 if ( $this->getNamespace() == NS_FILE ) {
3164 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
3165 [ 'fa_name' => $this->getDBkey() ],
3166 __METHOD__
3167 );
3168 }
3169 }
3170 return (int)$n;
3171 }
3172
3178 public function isDeletedQuick() {
3179 if ( $this->getNamespace() < 0 ) {
3180 return false;
3181 }
3182 $dbr = wfGetDB( DB_SLAVE );
3183 $deleted = (bool)$dbr->selectField( 'archive', '1',
3184 [ 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ],
3185 __METHOD__
3186 );
3187 if ( !$deleted && $this->getNamespace() == NS_FILE ) {
3188 $deleted = (bool)$dbr->selectField( 'filearchive', '1',
3189 [ 'fa_name' => $this->getDBkey() ],
3190 __METHOD__
3191 );
3192 }
3193 return $deleted;
3194 }
3195
3204 public function getArticleID( $flags = 0 ) {
3205 if ( $this->getNamespace() < 0 ) {
3206 $this->mArticleID = 0;
3207 return $this->mArticleID;
3208 }
3209 $linkCache = LinkCache::singleton();
3210 if ( $flags & self::GAID_FOR_UPDATE ) {
3211 $oldUpdate = $linkCache->forUpdate( true );
3212 $linkCache->clearLink( $this );
3213 $this->mArticleID = $linkCache->addLinkObj( $this );
3214 $linkCache->forUpdate( $oldUpdate );
3215 } else {
3216 if ( -1 == $this->mArticleID ) {
3217 $this->mArticleID = $linkCache->addLinkObj( $this );
3218 }
3219 }
3220 return $this->mArticleID;
3221 }
3222
3230 public function isRedirect( $flags = 0 ) {
3231 if ( !is_null( $this->mRedirect ) ) {
3232 return $this->mRedirect;
3233 }
3234 if ( !$this->getArticleID( $flags ) ) {
3235 $this->mRedirect = false;
3236 return $this->mRedirect;
3237 }
3238
3239 $linkCache = LinkCache::singleton();
3240 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3241 $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
3242 if ( $cached === null ) {
3243 # Trust LinkCache's state over our own
3244 # LinkCache is telling us that the page doesn't exist, despite there being cached
3245 # data relating to an existing page in $this->mArticleID. Updaters should clear
3246 # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
3247 # set, then LinkCache will definitely be up to date here, since getArticleID() forces
3248 # LinkCache to refresh its data from the master.
3249 $this->mRedirect = false;
3250 return $this->mRedirect;
3251 }
3252
3253 $this->mRedirect = (bool)$cached;
3254
3255 return $this->mRedirect;
3256 }
3257
3265 public function getLength( $flags = 0 ) {
3266 if ( $this->mLength != -1 ) {
3267 return $this->mLength;
3268 }
3269 if ( !$this->getArticleID( $flags ) ) {
3270 $this->mLength = 0;
3271 return $this->mLength;
3272 }
3273 $linkCache = LinkCache::singleton();
3274 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3275 $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
3276 if ( $cached === null ) {
3277 # Trust LinkCache's state over our own, as for isRedirect()
3278 $this->mLength = 0;
3279 return $this->mLength;
3280 }
3281
3282 $this->mLength = intval( $cached );
3283
3284 return $this->mLength;
3285 }
3286
3293 public function getLatestRevID( $flags = 0 ) {
3294 if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
3295 return intval( $this->mLatestID );
3296 }
3297 if ( !$this->getArticleID( $flags ) ) {
3298 $this->mLatestID = 0;
3299 return $this->mLatestID;
3300 }
3301 $linkCache = LinkCache::singleton();
3302 $linkCache->addLinkObj( $this ); # in case we already had an article ID
3303 $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
3304 if ( $cached === null ) {
3305 # Trust LinkCache's state over our own, as for isRedirect()
3306 $this->mLatestID = 0;
3307 return $this->mLatestID;
3308 }
3309
3310 $this->mLatestID = intval( $cached );
3311
3312 return $this->mLatestID;
3313 }
3314
3325 public function resetArticleID( $newid ) {
3326 $linkCache = LinkCache::singleton();
3327 $linkCache->clearLink( $this );
3328
3329 if ( $newid === false ) {
3330 $this->mArticleID = -1;
3331 } else {
3332 $this->mArticleID = intval( $newid );
3333 }
3334 $this->mRestrictionsLoaded = false;
3335 $this->mRestrictions = [];
3336 $this->mOldRestrictions = false;
3337 $this->mRedirect = null;
3338 $this->mLength = -1;
3339 $this->mLatestID = false;
3340 $this->mContentModel = false;
3341 $this->mEstimateRevisions = null;
3342 $this->mPageLanguage = false;
3343 $this->mDbPageLanguage = false;
3344 $this->mIsBigDeletion = null;
3345 }
3346
3347 public static function clearCaches() {
3348 $linkCache = LinkCache::singleton();
3349 $linkCache->clear();
3350
3352 $titleCache->clear();
3353 }
3354
3362 public static function capitalize( $text, $ns = NS_MAIN ) {
3364
3365 if ( MWNamespace::isCapitalized( $ns ) ) {
3366 return $wgContLang->ucfirst( $text );
3367 } else {
3368 return $text;
3369 }
3370 }
3371
3384 private function secureAndSplit() {
3385 # Initialisation
3386 $this->mInterwiki = '';
3387 $this->mFragment = '';
3388 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
3389
3390 $dbkey = $this->mDbkeyform;
3391
3392 // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
3393 // the parsing code with Title, while avoiding massive refactoring.
3394 // @todo: get rid of secureAndSplit, refactor parsing code.
3395 $titleParser = self::getMediaWikiTitleCodec();
3396 // MalformedTitleException can be thrown here
3397 $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
3398
3399 # Fill fields
3400 $this->setFragment( '#' . $parts['fragment'] );
3401 $this->mInterwiki = $parts['interwiki'];
3402 $this->mLocalInterwiki = $parts['local_interwiki'];
3403 $this->mNamespace = $parts['namespace'];
3404 $this->mUserCaseDBKey = $parts['user_case_dbkey'];
3405
3406 $this->mDbkeyform = $parts['dbkey'];
3407 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
3408 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
3409
3410 # We already know that some pages won't be in the database!
3411 if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
3412 $this->mArticleID = 0;
3413 }
3414
3415 return true;
3416 }
3417
3430 public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3431 if ( count( $options ) > 0 ) {
3432 $db = wfGetDB( DB_MASTER );
3433 } else {
3434 $db = wfGetDB( DB_SLAVE );
3435 }
3436
3437 $res = $db->select(
3438 [ 'page', $table ],
3439 self::getSelectFields(),
3440 [
3441 "{$prefix}_from=page_id",
3442 "{$prefix}_namespace" => $this->getNamespace(),
3443 "{$prefix}_title" => $this->getDBkey() ],
3444 __METHOD__,
3445 $options
3446 );
3447
3448 $retVal = [];
3449 if ( $res->numRows() ) {
3450 $linkCache = LinkCache::singleton();
3451 foreach ( $res as $row ) {
3452 $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
3453 if ( $titleObj ) {
3454 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
3455 $retVal[] = $titleObj;
3456 }
3457 }
3458 }
3459 return $retVal;
3460 }
3461
3472 public function getTemplateLinksTo( $options = [] ) {
3473 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
3474 }
3475
3488 public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) {
3489 $id = $this->getArticleID();
3490
3491 # If the page doesn't exist; there can't be any link from this page
3492 if ( !$id ) {
3493 return [];
3494 }
3495
3496 $db = wfGetDB( DB_SLAVE );
3497
3498 $blNamespace = "{$prefix}_namespace";
3499 $blTitle = "{$prefix}_title";
3500
3501 $res = $db->select(
3502 [ $table, 'page' ],
3503 array_merge(
3504 [ $blNamespace, $blTitle ],
3506 ),
3507 [ "{$prefix}_from" => $id ],
3508 __METHOD__,
3509 $options,
3510 [ 'page' => [
3511 'LEFT JOIN',
3512 [ "page_namespace=$blNamespace", "page_title=$blTitle" ]
3513 ] ]
3514 );
3515
3516 $retVal = [];
3517 $linkCache = LinkCache::singleton();
3518 foreach ( $res as $row ) {
3519 if ( $row->page_id ) {
3520 $titleObj = Title::newFromRow( $row );
3521 } else {
3522 $titleObj = Title::makeTitle( $row->$blNamespace, $row->$blTitle );
3523 $linkCache->addBadLinkObj( $titleObj );
3524 }
3525 $retVal[] = $titleObj;
3526 }
3527
3528 return $retVal;
3529 }
3530
3541 public function getTemplateLinksFrom( $options = [] ) {
3542 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
3543 }
3544
3553 public function getBrokenLinksFrom() {
3554 if ( $this->getArticleID() == 0 ) {
3555 # All links from article ID 0 are false positives
3556 return [];
3557 }
3558
3559 $dbr = wfGetDB( DB_SLAVE );
3560 $res = $dbr->select(
3561 [ 'page', 'pagelinks' ],
3562 [ 'pl_namespace', 'pl_title' ],
3563 [
3564 'pl_from' => $this->getArticleID(),
3565 'page_namespace IS NULL'
3566 ],
3567 __METHOD__, [],
3568 [
3569 'page' => [
3570 'LEFT JOIN',
3571 [ 'pl_namespace=page_namespace', 'pl_title=page_title' ]
3572 ]
3573 ]
3574 );
3575
3576 $retVal = [];
3577 foreach ( $res as $row ) {
3578 $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
3579 }
3580 return $retVal;
3581 }
3582
3589 public function getCdnUrls() {
3590 $urls = [
3591 $this->getInternalURL(),
3592 $this->getInternalURL( 'action=history' )
3593 ];
3594
3595 $pageLang = $this->getPageLanguage();
3596 if ( $pageLang->hasVariants() ) {
3597 $variants = $pageLang->getVariants();
3598 foreach ( $variants as $vCode ) {
3599 $urls[] = $this->getInternalURL( $vCode );
3600 }
3601 }
3602
3603 // If we are looking at a css/js user subpage, purge the action=raw.
3604 if ( $this->isJsSubpage() ) {
3605 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' );
3606 } elseif ( $this->isCssSubpage() ) {
3607 $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' );
3608 }
3609
3610 Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] );
3611 return $urls;
3612 }
3613
3617 public function getSquidURLs() {
3618 return $this->getCdnUrls();
3619 }
3620
3624 public function purgeSquid() {
3625 DeferredUpdates::addUpdate(
3626 new CdnCacheUpdate( $this->getCdnUrls() ),
3627 DeferredUpdates::PRESEND
3628 );
3629 }
3630
3638 public function moveNoAuth( &$nt ) {
3639 wfDeprecated( __METHOD__, '1.25' );
3640 return $this->moveTo( $nt, false );
3641 }
3642
3653 public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
3655
3656 if ( !( $nt instanceof Title ) ) {
3657 // Normally we'd add this to $errors, but we'll get
3658 // lots of syntax errors if $nt is not an object
3659 return [ [ 'badtitletext' ] ];
3660 }
3661
3662 $mp = new MovePage( $this, $nt );
3663 $errors = $mp->isValidMove()->getErrorsArray();
3664 if ( $auth ) {
3665 $errors = wfMergeErrorArrays(
3666 $errors,
3667 $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
3668 );
3669 }
3670
3671 return $errors ?: true;
3672 }
3673
3680 protected function validateFileMoveOperation( $nt ) {
3682
3683 $errors = [];
3684
3685 $destFile = wfLocalFile( $nt );
3686 $destFile->load( File::READ_LATEST );
3687 if ( !$wgUser->isAllowed( 'reupload-shared' )
3688 && !$destFile->exists() && wfFindFile( $nt )
3689 ) {
3690 $errors[] = [ 'file-exists-sharedrepo' ];
3691 }
3692
3693 return $errors;
3694 }
3695
3708 public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
3710 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
3711 if ( is_array( $err ) ) {
3712 // Auto-block user's IP if the account was "hard" blocked
3713 $wgUser->spreadAnyEditBlock();
3714 return $err;
3715 }
3716 // Check suppressredirect permission
3717 if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
3718 $createRedirect = true;
3719 }
3720
3721 $mp = new MovePage( $this, $nt );
3722 $status = $mp->move( $wgUser, $reason, $createRedirect );
3723 if ( $status->isOK() ) {
3724 return true;
3725 } else {
3726 return $status->getErrorsArray();
3727 }
3728 }
3729
3742 public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
3744 // Check permissions
3745 if ( !$this->userCan( 'move-subpages' ) ) {
3746 return [ 'cant-move-subpages' ];
3747 }
3748 // Do the source and target namespaces support subpages?
3749 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
3750 return [ 'namespace-nosubpages',
3752 }
3753 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
3754 return [ 'namespace-nosubpages',
3755 MWNamespace::getCanonicalName( $nt->getNamespace() ) ];
3756 }
3757
3758 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
3759 $retval = [];
3760 $count = 0;
3761 foreach ( $subpages as $oldSubpage ) {
3762 $count++;
3763 if ( $count > $wgMaximumMovedPages ) {
3764 $retval[$oldSubpage->getPrefixedText()] =
3765 [ 'movepage-max-pages',
3767 break;
3768 }
3769
3770 // We don't know whether this function was called before
3771 // or after moving the root page, so check both
3772 // $this and $nt
3773 if ( $oldSubpage->getArticleID() == $this->getArticleID()
3774 || $oldSubpage->getArticleID() == $nt->getArticleID()
3775 ) {
3776 // When moving a page to a subpage of itself,
3777 // don't move it twice
3778 continue;
3779 }
3780 $newPageName = preg_replace(
3781 '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
3782 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
3783 $oldSubpage->getDBkey() );
3784 if ( $oldSubpage->isTalkPage() ) {
3785 $newNs = $nt->getTalkPage()->getNamespace();
3786 } else {
3787 $newNs = $nt->getSubjectPage()->getNamespace();
3788 }
3789 # Bug 14385: we need makeTitleSafe because the new page names may
3790 # be longer than 255 characters.
3791 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
3792
3793 $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
3794 if ( $success === true ) {
3795 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
3796 } else {
3797 $retval[$oldSubpage->getPrefixedText()] = $success;
3798 }
3799 }
3800 return $retval;
3801 }
3802
3809 public function isSingleRevRedirect() {
3811
3812 $dbw = wfGetDB( DB_MASTER );
3813
3814 # Is it a redirect?
3815 $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ];
3816 if ( $wgContentHandlerUseDB ) {
3817 $fields[] = 'page_content_model';
3818 }
3819
3820 $row = $dbw->selectRow( 'page',
3821 $fields,
3822 $this->pageCond(),
3823 __METHOD__,
3824 [ 'FOR UPDATE' ]
3825 );
3826 # Cache some fields we may want
3827 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
3828 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
3829 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
3830 $this->mContentModel = $row && isset( $row->page_content_model )
3831 ? strval( $row->page_content_model )
3832 : false;
3833
3834 if ( !$this->mRedirect ) {
3835 return false;
3836 }
3837 # Does the article have a history?
3838 $row = $dbw->selectField( [ 'page', 'revision' ],
3839 'rev_id',
3840 [ 'page_namespace' => $this->getNamespace(),
3841 'page_title' => $this->getDBkey(),
3842 'page_id=rev_page',
3843 'page_latest != rev_id'
3844 ],
3845 __METHOD__,
3846 [ 'FOR UPDATE' ]
3847 );
3848 # Return true if there was no history
3849 return ( $row === false );
3850 }
3851
3860 public function isValidMoveTarget( $nt ) {
3861 # Is it an existing file?
3862 if ( $nt->getNamespace() == NS_FILE ) {
3863 $file = wfLocalFile( $nt );
3864 $file->load( File::READ_LATEST );
3865 if ( $file->exists() ) {
3866 wfDebug( __METHOD__ . ": file exists\n" );
3867 return false;
3868 }
3869 }
3870 # Is it a redirect with no history?
3871 if ( !$nt->isSingleRevRedirect() ) {
3872 wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
3873 return false;
3874 }
3875 # Get the article text
3877 if ( !is_object( $rev ) ) {
3878 return false;
3879 }
3880 $content = $rev->getContent();
3881 # Does the redirect point to the source?
3882 # Or is it a broken self-redirect, usually caused by namespace collisions?
3883 $redirTitle = $content ? $content->getRedirectTarget() : null;
3884
3885 if ( $redirTitle ) {
3886 if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
3887 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
3888 wfDebug( __METHOD__ . ": redirect points to other page\n" );
3889 return false;
3890 } else {
3891 return true;
3892 }
3893 } else {
3894 # Fail safe (not a redirect after all. strange.)
3895 wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
3896 " is a redirect, but it doesn't contain a valid redirect.\n" );
3897 return false;
3898 }
3899 }
3900
3908 public function getParentCategories() {
3910
3911 $data = [];
3912
3913 $titleKey = $this->getArticleID();
3914
3915 if ( $titleKey === 0 ) {
3916 return $data;
3917 }
3918
3919 $dbr = wfGetDB( DB_SLAVE );
3920
3921 $res = $dbr->select(
3922 'categorylinks',
3923 'cl_to',
3924 [ 'cl_from' => $titleKey ],
3925 __METHOD__
3926 );
3927
3928 if ( $res->numRows() > 0 ) {
3929 foreach ( $res as $row ) {
3930 // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
3931 $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
3932 }
3933 }
3934 return $data;
3935 }
3936
3943 public function getParentCategoryTree( $children = [] ) {
3944 $stack = [];
3945 $parents = $this->getParentCategories();
3946
3947 if ( $parents ) {
3948 foreach ( $parents as $parent => $current ) {
3949 if ( array_key_exists( $parent, $children ) ) {
3950 # Circular reference
3951 $stack[$parent] = [];
3952 } else {
3953 $nt = Title::newFromText( $parent );
3954 if ( $nt ) {
3955 $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] );
3956 }
3957 }
3958 }
3959 }
3960
3961 return $stack;
3962 }
3963
3970 public function pageCond() {
3971 if ( $this->mArticleID > 0 ) {
3972 // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
3973 return [ 'page_id' => $this->mArticleID ];
3974 } else {
3975 return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ];
3976 }
3977 }
3978
3986 public function getPreviousRevisionID( $revId, $flags = 0 ) {
3988 $revId = $db->selectField( 'revision', 'rev_id',
3989 [
3990 'rev_page' => $this->getArticleID( $flags ),
3991 'rev_id < ' . intval( $revId )
3992 ],
3993 __METHOD__,
3994 [ 'ORDER BY' => 'rev_id DESC' ]
3995 );
3996
3997 if ( $revId === false ) {
3998 return false;
3999 } else {
4000 return intval( $revId );
4001 }
4002 }
4003
4011 public function getNextRevisionID( $revId, $flags = 0 ) {
4013 $revId = $db->selectField( 'revision', 'rev_id',
4014 [
4015 'rev_page' => $this->getArticleID( $flags ),
4016 'rev_id > ' . intval( $revId )
4017 ],
4018 __METHOD__,
4019 [ 'ORDER BY' => 'rev_id' ]
4020 );
4021
4022 if ( $revId === false ) {
4023 return false;
4024 } else {
4025 return intval( $revId );
4026 }
4027 }
4028
4035 public function getFirstRevision( $flags = 0 ) {
4036 $pageId = $this->getArticleID( $flags );
4037 if ( $pageId ) {
4039 $row = $db->selectRow( 'revision', Revision::selectFields(),
4040 [ 'rev_page' => $pageId ],
4041 __METHOD__,
4042 [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ]
4043 );
4044 if ( $row ) {
4045 return new Revision( $row );
4046 }
4047 }
4048 return null;
4049 }
4050
4057 public function getEarliestRevTime( $flags = 0 ) {
4058 $rev = $this->getFirstRevision( $flags );
4059 return $rev ? $rev->getTimestamp() : null;
4060 }
4061
4067 public function isNewPage() {
4068 $dbr = wfGetDB( DB_SLAVE );
4069 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
4070 }
4071
4077 public function isBigDeletion() {
4079
4080 if ( !$wgDeleteRevisionsLimit ) {
4081 return false;
4082 }
4083
4084 if ( $this->mIsBigDeletion === null ) {
4085 $dbr = wfGetDB( DB_SLAVE );
4086
4087 $revCount = $dbr->selectRowCount(
4088 'revision',
4089 '1',
4090 [ 'rev_page' => $this->getArticleID() ],
4091 __METHOD__,
4092 [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ]
4093 );
4094
4095 $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
4096 }
4097
4098 return $this->mIsBigDeletion;
4099 }
4100
4106 public function estimateRevisionCount() {
4107 if ( !$this->exists() ) {
4108 return 0;
4109 }
4110
4111 if ( $this->mEstimateRevisions === null ) {
4112 $dbr = wfGetDB( DB_SLAVE );
4113 $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
4114 [ 'rev_page' => $this->getArticleID() ], __METHOD__ );
4115 }
4116
4118 }
4119
4129 public function countRevisionsBetween( $old, $new, $max = null ) {
4130 if ( !( $old instanceof Revision ) ) {
4131 $old = Revision::newFromTitle( $this, (int)$old );
4132 }
4133 if ( !( $new instanceof Revision ) ) {
4134 $new = Revision::newFromTitle( $this, (int)$new );
4135 }
4136 if ( !$old || !$new ) {
4137 return 0; // nothing to compare
4138 }
4139 $dbr = wfGetDB( DB_SLAVE );
4140 $conds = [
4141 'rev_page' => $this->getArticleID(),
4142 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4143 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4144 ];
4145 if ( $max !== null ) {
4146 return $dbr->selectRowCount( 'revision', '1',
4147 $conds,
4148 __METHOD__,
4149 [ 'LIMIT' => $max + 1 ] // extra to detect truncation
4150 );
4151 } else {
4152 return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
4153 }
4154 }
4155
4172 public function getAuthorsBetween( $old, $new, $limit, $options = [] ) {
4173 if ( !( $old instanceof Revision ) ) {
4174 $old = Revision::newFromTitle( $this, (int)$old );
4175 }
4176 if ( !( $new instanceof Revision ) ) {
4177 $new = Revision::newFromTitle( $this, (int)$new );
4178 }
4179 // XXX: what if Revision objects are passed in, but they don't refer to this title?
4180 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
4181 // in the sanity check below?
4182 if ( !$old || !$new ) {
4183 return null; // nothing to compare
4184 }
4185 $authors = [];
4186 $old_cmp = '>';
4187 $new_cmp = '<';
4189 if ( in_array( 'include_old', $options ) ) {
4190 $old_cmp = '>=';
4191 }
4192 if ( in_array( 'include_new', $options ) ) {
4193 $new_cmp = '<=';
4194 }
4195 if ( in_array( 'include_both', $options ) ) {
4196 $old_cmp = '>=';
4197 $new_cmp = '<=';
4198 }
4199 // No DB query needed if $old and $new are the same or successive revisions:
4200 if ( $old->getId() === $new->getId() ) {
4201 return ( $old_cmp === '>' && $new_cmp === '<' ) ?
4202 [] :
4203 [ $old->getUserText( Revision::RAW ) ];
4204 } elseif ( $old->getId() === $new->getParentId() ) {
4205 if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
4206 $authors[] = $old->getUserText( Revision::RAW );
4207 if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
4208 $authors[] = $new->getUserText( Revision::RAW );
4209 }
4210 } elseif ( $old_cmp === '>=' ) {
4211 $authors[] = $old->getUserText( Revision::RAW );
4212 } elseif ( $new_cmp === '<=' ) {
4213 $authors[] = $new->getUserText( Revision::RAW );
4214 }
4215 return $authors;
4216 }
4217 $dbr = wfGetDB( DB_SLAVE );
4218 $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
4219 [
4220 'rev_page' => $this->getArticleID(),
4221 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
4222 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
4223 ], __METHOD__,
4224 [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
4225 );
4226 foreach ( $res as $row ) {
4227 $authors[] = $row->rev_user_text;
4228 }
4229 return $authors;
4230 }
4231
4246 public function countAuthorsBetween( $old, $new, $limit, $options = [] ) {
4247 $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
4248 return $authors ? count( $authors ) : 0;
4249 }
4250
4257 public function equals( Title $title ) {
4258 // Note: === is necessary for proper matching of number-like titles.
4259 return $this->getInterwiki() === $title->getInterwiki()
4260 && $this->getNamespace() == $title->getNamespace()
4261 && $this->getDBkey() === $title->getDBkey();
4262 }
4263
4270 public function isSubpageOf( Title $title ) {
4271 return $this->getInterwiki() === $title->getInterwiki()
4272 && $this->getNamespace() == $title->getNamespace()
4273 && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
4274 }
4275
4287 public function exists( $flags = 0 ) {
4288 $exists = $this->getArticleID( $flags ) != 0;
4289 Hooks::run( 'TitleExists', [ $this, &$exists ] );
4290 return $exists;
4291 }
4292
4309 public function isAlwaysKnown() {
4310 $isKnown = null;
4311
4322 Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] );
4323
4324 if ( !is_null( $isKnown ) ) {
4325 return $isKnown;
4326 }
4327
4328 if ( $this->isExternal() ) {
4329 return true; // any interwiki link might be viewable, for all we know
4330 }
4331
4332 switch ( $this->mNamespace ) {
4333 case NS_MEDIA:
4334 case NS_FILE:
4335 // file exists, possibly in a foreign repo
4336 return (bool)wfFindFile( $this );
4337 case NS_SPECIAL:
4338 // valid special page
4339 return SpecialPageFactory::exists( $this->getDBkey() );
4340 case NS_MAIN:
4341 // selflink, possibly with fragment
4342 return $this->mDbkeyform == '';
4343 case NS_MEDIAWIKI:
4344 // known system message
4345 return $this->hasSourceText() !== false;
4346 default:
4347 return false;
4348 }
4349 }
4350
4362 public function isKnown() {
4363 return $this->isAlwaysKnown() || $this->exists();
4364 }
4365
4371 public function hasSourceText() {
4372 if ( $this->exists() ) {
4373 return true;
4374 }
4375
4376 if ( $this->mNamespace == NS_MEDIAWIKI ) {
4377 // If the page doesn't exist but is a known system message, default
4378 // message content will be displayed, same for language subpages-
4379 // Use always content language to avoid loading hundreds of languages
4380 // to get the link color.
4382 list( $name, ) = MessageCache::singleton()->figureMessage(
4383 $wgContLang->lcfirst( $this->getText() )
4384 );
4385 $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
4386 return $message->exists();
4387 }
4388
4389 return false;
4390 }
4391
4397 public function getDefaultMessageText() {
4399
4400 if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
4401 return false;
4402 }
4403
4404 list( $name, $lang ) = MessageCache::singleton()->figureMessage(
4405 $wgContLang->lcfirst( $this->getText() )
4406 );
4407 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
4408
4409 if ( $message->exists() ) {
4410 return $message->plain();
4411 } else {
4412 return false;
4413 }
4414 }
4415
4422 public function invalidateCache( $purgeTime = null ) {
4423 if ( wfReadOnly() ) {
4424 return false;
4425 }
4426
4427 if ( $this->mArticleID === 0 ) {
4428 return true; // avoid gap locking if we know it's not there
4429 }
4430
4431 $method = __METHOD__;
4432 $dbw = wfGetDB( DB_MASTER );
4433 $conds = $this->pageCond();
4434 $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method, $purgeTime ) {
4435 $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() );
4436
4437 $dbw->update(
4438 'page',
4439 [ 'page_touched' => $dbTimestamp ],
4440 $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ],
4441 $method
4442 );
4443 } );
4444
4445 return true;
4446 }
4447
4453 public function touchLinks() {
4454 DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
4455 if ( $this->getNamespace() == NS_CATEGORY ) {
4456 DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
4457 }
4458 }
4459
4466 public function getTouched( $db = null ) {
4467 if ( $db === null ) {
4468 $db = wfGetDB( DB_SLAVE );
4469 }
4470 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
4471 return $touched;
4472 }
4473
4480 public function getNotificationTimestamp( $user = null ) {
4482
4483 // Assume current user if none given
4484 if ( !$user ) {
4485 $user = $wgUser;
4486 }
4487 // Check cache first
4488 $uid = $user->getId();
4489 if ( !$uid ) {
4490 return false;
4491 }
4492 // avoid isset here, as it'll return false for null entries
4493 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
4494 return $this->mNotificationTimestamp[$uid];
4495 }
4496 // Don't cache too much!
4497 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
4498 $this->mNotificationTimestamp = [];
4499 }
4500
4501 $watchedItem = WatchedItemStore::getDefaultInstance()->getWatchedItem( $user, $this );
4502 if ( $watchedItem ) {
4503 $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp();
4504 } else {
4505 $this->mNotificationTimestamp[$uid] = false;
4506 }
4507
4508 return $this->mNotificationTimestamp[$uid];
4509 }
4510
4517 public function getNamespaceKey( $prepend = 'nstab-' ) {
4519 // Gets the subject namespace if this title
4520 $namespace = MWNamespace::getSubject( $this->getNamespace() );
4521 // Checks if canonical namespace name exists for namespace
4522 if ( MWNamespace::exists( $this->getNamespace() ) ) {
4523 // Uses canonical namespace name
4524 $namespaceKey = MWNamespace::getCanonicalName( $namespace );
4525 } else {
4526 // Uses text of namespace
4527 $namespaceKey = $this->getSubjectNsText();
4528 }
4529 // Makes namespace key lowercase
4530 $namespaceKey = $wgContLang->lc( $namespaceKey );
4531 // Uses main
4532 if ( $namespaceKey == '' ) {
4533 $namespaceKey = 'main';
4534 }
4535 // Changes file to image for backwards compatibility
4536 if ( $namespaceKey == 'file' ) {
4537 $namespaceKey = 'image';
4538 }
4539 return $prepend . $namespaceKey;
4540 }
4541
4548 public function getRedirectsHere( $ns = null ) {
4549 $redirs = [];
4550
4551 $dbr = wfGetDB( DB_SLAVE );
4552 $where = [
4553 'rd_namespace' => $this->getNamespace(),
4554 'rd_title' => $this->getDBkey(),
4555 'rd_from = page_id'
4556 ];
4557 if ( $this->isExternal() ) {
4558 $where['rd_interwiki'] = $this->getInterwiki();
4559 } else {
4560 $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
4561 }
4562 if ( !is_null( $ns ) ) {
4563 $where['page_namespace'] = $ns;
4564 }
4565
4566 $res = $dbr->select(
4567 [ 'redirect', 'page' ],
4568 [ 'page_namespace', 'page_title' ],
4569 $where,
4570 __METHOD__
4571 );
4572
4573 foreach ( $res as $row ) {
4574 $redirs[] = self::newFromRow( $row );
4575 }
4576 return $redirs;
4577 }
4578
4584 public function isValidRedirectTarget() {
4586
4587 if ( $this->isSpecialPage() ) {
4588 // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
4589 if ( $this->isSpecial( 'Userlogout' ) ) {
4590 return false;
4591 }
4592
4593 foreach ( $wgInvalidRedirectTargets as $target ) {
4594 if ( $this->isSpecial( $target ) ) {
4595 return false;
4596 }
4597 }
4598 }
4599
4600 return true;
4601 }
4602
4608 public function getBacklinkCache() {
4609 return BacklinkCache::get( $this );
4610 }
4611
4617 public function canUseNoindex() {
4619
4620 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
4623
4624 return !in_array( $this->mNamespace, $bannedNamespaces );
4625
4626 }
4627
4638 public function getCategorySortkey( $prefix = '' ) {
4639 $unprefixed = $this->getText();
4640
4641 // Anything that uses this hook should only depend
4642 // on the Title object passed in, and should probably
4643 // tell the users to run updateCollations.php --force
4644 // in order to re-sort existing category relations.
4645 Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] );
4646 if ( $prefix !== '' ) {
4647 # Separate with a line feed, so the unprefixed part is only used as
4648 # a tiebreaker when two pages have the exact same prefix.
4649 # In UCA, tab is the only character that can sort above LF
4650 # so we strip both of them from the original prefix.
4651 $prefix = strtr( $prefix, "\n\t", ' ' );
4652 return "$prefix\n$unprefixed";
4653 }
4654 return $unprefixed;
4655 }
4656
4664 private function getDbPageLanguageCode() {
4666
4667 // check, if the page language could be saved in the database, and if so and
4668 // the value is not requested already, lookup the page language using LinkCache
4669 if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) {
4670 $linkCache = LinkCache::singleton();
4671 $linkCache->addLinkObj( $this );
4672 $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' );
4673 }
4674
4676 }
4677
4686 public function getPageLanguage() {
4688 if ( $this->isSpecialPage() ) {
4689 // special pages are in the user language
4690 return $wgLang;
4691 }
4692
4693 // Checking if DB language is set
4694 $dbPageLanguage = $this->getDbPageLanguageCode();
4695 if ( $dbPageLanguage ) {
4696 return wfGetLangObj( $dbPageLanguage );
4697 }
4698
4699 if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
4700 // Note that this may depend on user settings, so the cache should
4701 // be only per-request.
4702 // NOTE: ContentHandler::getPageLanguage() may need to load the
4703 // content to determine the page language!
4704 // Checking $wgLanguageCode hasn't changed for the benefit of unit
4705 // tests.
4706 $contentHandler = ContentHandler::getForTitle( $this );
4707 $langObj = $contentHandler->getPageLanguage( $this );
4708 $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ];
4709 } else {
4710 $langObj = wfGetLangObj( $this->mPageLanguage[0] );
4711 }
4712
4713 return $langObj;
4714 }
4715
4724 public function getPageViewLanguage() {
4726
4727 if ( $this->isSpecialPage() ) {
4728 // If the user chooses a variant, the content is actually
4729 // in a language whose code is the variant code.
4730 $variant = $wgLang->getPreferredVariant();
4731 if ( $wgLang->getCode() !== $variant ) {
4732 return Language::factory( $variant );
4733 }
4734
4735 return $wgLang;
4736 }
4737
4738 // Checking if DB language is set
4739 $dbPageLanguage = $this->getDbPageLanguageCode();
4740 if ( $dbPageLanguage ) {
4741 $pageLang = wfGetLangObj( $dbPageLanguage );
4742 $variant = $pageLang->getPreferredVariant();
4743 if ( $pageLang->getCode() !== $variant ) {
4744 $pageLang = Language::factory( $variant );
4745 }
4746
4747 return $pageLang;
4748 }
4749
4750 // @note Can't be cached persistently, depends on user settings.
4751 // @note ContentHandler::getPageViewLanguage() may need to load the
4752 // content to determine the page language!
4753 $contentHandler = ContentHandler::getForTitle( $this );
4754 $pageLang = $contentHandler->getPageViewLanguage( $this );
4755 return $pageLang;
4756 }
4757
4768 public function getEditNotices( $oldid = 0 ) {
4769 $notices = [];
4770
4771 // Optional notice for the entire namespace
4772 $editnotice_ns = 'editnotice-' . $this->getNamespace();
4773 $msg = wfMessage( $editnotice_ns );
4774 if ( $msg->exists() ) {
4775 $html = $msg->parseAsBlock();
4776 // Edit notices may have complex logic, but output nothing (T91715)
4777 if ( trim( $html ) !== '' ) {
4778 $notices[$editnotice_ns] = Html::rawElement(
4779 'div',
4780 [ 'class' => [
4781 'mw-editnotice',
4782 'mw-editnotice-namespace',
4783 Sanitizer::escapeClass( "mw-$editnotice_ns" )
4784 ] ],
4785 $html
4786 );
4787 }
4788 }
4789
4790 if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
4791 // Optional notice for page itself and any parent page
4792 $parts = explode( '/', $this->getDBkey() );
4793 $editnotice_base = $editnotice_ns;
4794 while ( count( $parts ) > 0 ) {
4795 $editnotice_base .= '-' . array_shift( $parts );
4796 $msg = wfMessage( $editnotice_base );
4797 if ( $msg->exists() ) {
4798 $html = $msg->parseAsBlock();
4799 if ( trim( $html ) !== '' ) {
4800 $notices[$editnotice_base] = Html::rawElement(
4801 'div',
4802 [ 'class' => [
4803 'mw-editnotice',
4804 'mw-editnotice-base',
4805 Sanitizer::escapeClass( "mw-$editnotice_base" )
4806 ] ],
4807 $html
4808 );
4809 }
4810 }
4811 }
4812 } else {
4813 // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
4814 $editnoticeText = $editnotice_ns . '-' . strtr( $this->getDBkey(), '/', '-' );
4815 $msg = wfMessage( $editnoticeText );
4816 if ( $msg->exists() ) {
4817 $html = $msg->parseAsBlock();
4818 if ( trim( $html ) !== '' ) {
4819 $notices[$editnoticeText] = Html::rawElement(
4820 'div',
4821 [ 'class' => [
4822 'mw-editnotice',
4823 'mw-editnotice-page',
4824 Sanitizer::escapeClass( "mw-$editnoticeText" )
4825 ] ],
4826 $html
4827 );
4828 }
4829 }
4830 }
4831
4832 Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] );
4833 return $notices;
4834 }
4835
4839 public function __sleep() {
4840 return [
4841 'mNamespace',
4842 'mDbkeyform',
4843 'mFragment',
4844 'mInterwiki',
4845 'mLocalInterwiki',
4846 'mUserCaseDBKey',
4847 'mDefaultNamespace',
4848 ];
4849 }
4850
4851 public function __wakeup() {
4852 $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0;
4853 $this->mUrlform = wfUrlencode( $this->mDbkeyform );
4854 $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' );
4855 }
4856
4857}
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade names
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$wgRestrictionLevels
Rights which can be required for each protection level (via action=protect)
$wgExemptFromUserRobotsControl
An array of namespace keys in which the INDEX/__NOINDEX__ magic words will not function,...
$wgLegalTitleChars
Allowed title characters – regex character class Don't change this unless you know what you're doing.
bool $wgPageLanguageUseDB
Enable page language feature Allows setting page language in database.
$wgSemiprotectedRestrictionLevels
Restriction levels that should be considered "semiprotected".
$wgLanguageCode
Site language code.
$wgMaximumMovedPages
Maximum number of pages to move at once when moving subpages with a page.
$wgScript
The URL path to index.php.
$wgInternalServer
Internal server name as known to CDN, if different.
$wgContentNamespaces
Array of namespaces which can be deemed to contain valid "content", as far as the site statistics are...
$wgInvalidRedirectTargets
Array of invalid page redirect targets.
$wgNamespaceProtection
Set the minimum permissions required to edit pages in each namespace.
$wgRestrictionTypes
Set of available actions that can be restricted via action=protect You probably shouldn't change this...
$wgWhitelistRead
Pages anonymous user may see, set as an array of pages titles.
$wgWhitelistReadRegexp
Pages anonymous user may see, set as an array of regular expressions.
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
$wgLocalInterwikis
Array for multiple $wgLocalInterwiki values, in case there are several interwiki prefixes that point ...
$wgBlockDisablesLogin
If true, blocked users will not be allowed to login.
$wgEmailConfirmToEdit
Should editors be required to have a validated e-mail address before being allowed to edit?
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
$wgServer
URL of the server.
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility.
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfMergeErrorArrays()
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
wfLocalFile( $title)
Get an object referring to a locally registered file.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfFindFile( $title, $options=[])
Find a file.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$wgUser
Definition Setup.php:794
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition Setup.php:35
if(is_null($wgLocalTZoffset)) if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:657
Deferrable Update for closure/callback updates via IDatabase::doAtomicSection()
static get(Title $title)
Create a new BacklinkCache or reuse any existing one.
get( $key, $flags=0, $oldFlags=null)
Get an item with the given key.
Handles purging appropriate CDN URLs given a title (or titles)
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
static singleton()
Class to invalidate the HTML cache of all the pages linking to a given title.
Simple store for keeping values in an associative array for the current process.
set( $key, $value, $exptime=0, $flags=0)
Set an item.
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition Html.php:210
static fetch( $prefix)
Fetch an Interwiki object.
Definition Interwiki.php:85
static singleton()
Get an instance of this class.
Definition LinkCache.php:61
MediaWiki exception.
static getSubject( $index)
Get the subject namespace index for a given namespace Special namespaces (NS_MEDIA,...
static exists( $index)
Returns whether the specified namespace exists.
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
static isCapitalized( $index)
Is the namespace first-letter capitalized?
static canTalk( $index)
Can this namespace ever have a talk namespace?
static isWatchable( $index)
Can pages in a namespace be watched?
static hasSubpages( $index)
Does the namespace allow subpages?
static isTalk( $index)
Is the given namespace a talk namespace?
static getTalk( $index)
Get the talk namespace index for a given namespace.
static equals( $ns1, $ns2)
Returns whether the specified namespaces are the same namespace.
static subjectEquals( $ns1, $ns2)
Returns whether the specified namespaces share the same subject.
static isContent( $index)
Does this namespace contain content, for the purposes of calculating statistics, etc?
static isMovable( $index)
Can pages in the given namespace be moved?
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
A codec for MediaWiki page titles.
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
static singleton()
Get the signleton instance of this class.
Handles the backend logic of moving a page from one title to another.
Definition MovePage.php:28
static getMain()
Static methods.
static selectFields()
Return the list of revision fields that should be selected to create a new revision.
Definition Revision.php:429
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:117
const RAW
Definition Revision.php:85
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
static decodeCharReferencesAndNormalize( $text)
Decode any character references, numeric or named entities, in the next and normalize the resulting s...
static escapeId( $id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it.
static getLocalNameFor( $name, $subpage=false)
Get the local name for a specified canonical name.
static exists( $name)
Check if a given name exist as a special page or as a special page alias.
static resolveAlias( $alias)
Given a special page name with a possible subpage, return an array where the first element is the spe...
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name.
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:34
string $mInterwiki
Interwiki prefix.
Definition Title.php:74
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition Title.php:417
inNamespaces()
Returns true if the title is inside one of the specified namespaces.
Definition Title.php:1105
getSubpages( $limit=-1)
Get all subpages of this page.
Definition Title.php:3125
static getTitleInvalidRegex()
Returns a simple regex that will match on characters and sequences invalid in titles.
Definition Title.php:620
moveTo(&$nt, $auth=true, $reason='', $createRedirect=true)
Move a title to a new location.
Definition Title.php:3708
isWatchable()
Can this title be added to a user's watchlist?
Definition Title.php:1036
getNamespace()
Get the namespace index, i.e.
Definition Title.php:934
estimateRevisionCount()
Get the approximate revision count of this page.
Definition Title.php:4106
__wakeup()
Text form (spaces not underscores) of the main part.
Definition Title.php:4851
static newFromDBkey( $key)
Create a new Title from a prefixed DB key.
Definition Title.php:221
isProtected( $action='')
Does the title correspond to a protected article?
Definition Title.php:2694
bool $mPageLanguage
The (string) language code of the page's language and content code.
Definition Title.php:147
array $mCascadeSources
Where are the cascading restrictions coming from on this page?
Definition Title.php:116
isSingleRevRedirect()
Checks if this page is just a one-rev redirect.
Definition Title.php:3809
wasLocalInterwiki()
Was this a local interwiki link?
Definition Title.php:830
checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short)
Check CSS/JS sub-page permissions.
Definition Title.php:2149
getInternalURL( $query='', $query2=false)
Get the URL form for an internal link.
Definition Title.php:1855
purgeSquid()
Purge all applicable CDN URLs.
Definition Title.php:3624
getFullURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a real URL referring to this title, with interwiki link and fragment.
Definition Title.php:1666
static & makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:524
getRestrictions( $action)
Accessor/initialisation for mRestrictions.
Definition Title.php:2874
isKnown()
Does this title refer to a page that can (or might) be meaningfully viewed? In particular,...
Definition Title.php:4362
int $mEstimateRevisions
Estimated number of revisions; null of not loaded.
Definition Title.php:95
getBacklinkCache()
Get a backlink cache object.
Definition Title.php:4608
static getTitleFormatter()
B/C kludge: provide a TitleParser for use by Title.
Definition Title.php:204
inNamespace( $ns)
Returns true if the title is inside the specified namespace.
Definition Title.php:1094
equals(Title $title)
Compare with another title.
Definition Title.php:4257
isDeletedQuick()
Is there a version of this page in the deletion archive?
Definition Title.php:3178
static capitalize( $text, $ns=NS_MAIN)
Capitalize a text string for a title if it belongs to a namespace that capitalizes.
Definition Title.php:3362
checkQuickPermissions( $action, $user, $errors, $rigor, $short)
Permissions checks that fail most often, and which are easiest to test.
Definition Title.php:1982
getTalkPage()
Get a Title object associated with the talk page of this article.
Definition Title.php:1299
secureAndSplit()
Secure and split - main initialisation function for this object.
Definition Title.php:3384
getAllRestrictions()
Accessor/initialisation for mRestrictions.
Definition Title.php:2890
hasContentModel( $id)
Convenience method for checking a title's content model name.
Definition Title.php:964
getSkinFromCssJsSubpage()
Trim down a .css or .js subpage title to get the corresponding skin name.
Definition Title.php:1255
static clearCaches()
Text form (spaces not underscores) of the main part.
Definition Title.php:3347
createFragmentTarget( $fragment)
Creates a new Title for a different fragment of the same page.
Definition Title.php:1402
getDefaultNamespace()
Get the default namespace index, for when there is no namespace.
Definition Title.php:1342
isConversionTable()
Is this a conversion table for the LanguageConverter?
Definition Title.php:1195
getFragment()
Get the Title fragment (i.e.
Definition Title.php:1353
isCascadeProtected()
Cascading protection: Return true if cascading restrictions apply to this page, false if not.
Definition Title.php:2744
static getFilteredRestrictionTypes( $exists=true)
Get a filtered list of all restriction types supported by this wiki.
Definition Title.php:2559
getPrefixedURL()
Get a URL-encoded title (not an actual URL) including interwiki.
Definition Title.php:1609
TitleValue $mTitleValue
A corresponding TitleValue object.
Definition Title.php:154
checkUserBlock( $action, $user, $errors, $rigor, $short)
Check that the user isn't blocked from editing.
Definition Title.php:2343
isWikitextPage()
Does that page contain wikitext, or it is JS, CSS or whatever?
Definition Title.php:1207
validateFileMoveOperation( $nt)
Check if the requested move target is a valid file move target.
Definition Title.php:3680
getTalkNsText()
Get the namespace text of the talk page.
Definition Title.php:1008
areRestrictionsCascading()
Returns cascading restrictions for the current article.
Definition Title.php:2916
hasFragment()
Check if a Title fragment is set.
Definition Title.php:1363
static nameOf( $id)
Get the prefixed DB key associated with an ID.
Definition Title.php:584
isSpecial( $name)
Returns true if this title resolves to the named special page.
Definition Title.php:1055
getRedirectsHere( $ns=null)
Get all extant redirects to this Title.
Definition Title.php:4548
getLength( $flags=0)
What is the length of this page? Uses link cache, adding it if necessary.
Definition Title.php:3265
array $mNotificationTimestamp
Associative array of user ID -> timestamp/false.
Definition Title.php:141
isValidMoveOperation(&$nt, $auth=true, $reason='')
Check whether a given move operation would be valid.
Definition Title.php:3653
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition Title.php:1473
areRestrictionsLoaded()
Accessor for mRestrictionsLoaded.
Definition Title.php:2861
canUseNoindex()
Whether the magic words INDEX and NOINDEX function for this page.
Definition Title.php:4617
exists( $flags=0)
Check if page exists.
Definition Title.php:4287
static newFromURL( $url)
THIS IS NOT THE FUNCTION YOU WANT.
Definition Title.php:354
static newFromTextThrow( $text, $defaultNamespace=NS_MAIN)
Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,...
Definition Title.php:307
isLocal()
Determine whether the object refers to a page within this project (either this wiki or a wiki with a ...
Definition Title.php:795
int $mLength
The page length, 0 for special pages.
Definition Title.php:135
loadFromRow( $row)
Load Title object fields from a DB row.
Definition Title.php:477
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition Title.php:4686
bool $mLocalInterwiki
Was this Title created from a string with a local interwiki prefix?
Definition Title.php:77
getUserCaseDBKey()
Get the DB key with the initial letter case as specified by the user.
Definition Title.php:920
isMovable()
Would anybody with sufficient privileges be able to move this page? Some pages just aren't movable.
Definition Title.php:1154
moveNoAuth(&$nt)
Move this page without authentication.
Definition Title.php:3638
const CACHE_MAX
Title::newFromText maintains a cache to avoid expensive re-normalization of commonly used titles.
Definition Title.php:43
getRestrictionExpiry( $action)
Get the expiry time for the restriction against a given action.
Definition Title.php:2904
getUserPermissionsErrors( $action, $user, $rigor='secure', $ignoreErrors=[])
Can $user perform $action on this page?
Definition Title.php:1951
getSubjectPage()
Get a title object associated with the subject page of this talk page.
Definition Title.php:1309
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:3472
fixSpecialName()
If the Title refers to a special page alias which is not the local default, resolve the alias,...
Definition Title.php:1071
getRestrictionTypes()
Returns restriction types for the current Title.
Definition Title.php:2577
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition Title.php:606
__toString()
Return a string representation of this title.
Definition Title.php:1463
hasSubjectNamespace( $ns)
Returns true if the title has the same subject namespace as the namespace specified.
Definition Title.php:1133
isSemiProtected( $action='edit')
Is this page "semi-protected" - the only protection levels are listed in $wgSemiprotectedRestrictionL...
Definition Title.php:2666
isCssJsSubpage()
Is this a .css or .js subpage of a user page?
Definition Title.php:1244
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1437
areCascadeProtectionSourcesLoaded( $getPages=true)
Determines whether cascading protection sources have already been loaded from the database.
Definition Title.php:2758
getPreviousRevisionID( $revId, $flags=0)
Get the revision ID of the previous revision.
Definition Title.php:3986
getNsText()
Get the namespace text.
Definition Title.php:973
canExist()
Is this in a namespace that allows actual pages?
Definition Title.php:1027
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition Title.php:3069
getDefaultMessageText()
Get the default message text or false if the message doesn't exist.
Definition Title.php:4397
getDbPageLanguageCode()
Returns the page language code saved in the database, if $wgPageLanguageUseDB is set to true in Local...
Definition Title.php:4664
countRevisionsBetween( $old, $new, $max=null)
Get the number of revisions between the given revision.
Definition Title.php:4129
checkPermissionHooks( $action, $user, $errors, $rigor, $short)
Check various permission hooks.
Definition Title.php:2083
getNotificationTimestamp( $user=null)
Get the timestamp when this page was updated since the user last saw it.
Definition Title.php:4480
isTrans()
Determine whether the object refers to a page within this project and is transcludable.
Definition Title.php:840
resetArticleID( $newid)
This clears some fields in this object, and clears any associated keys in the "bad links" section of ...
Definition Title.php:3325
isNewPage()
Check if this is a new page.
Definition Title.php:4067
touchLinks()
Update page_touched timestamps and send CDN purge messages for pages linking to this title.
Definition Title.php:4453
isExternal()
Is this Title interwiki?
Definition Title.php:810
bool $mRestrictionsLoaded
Boolean for initialisation on demand.
Definition Title.php:119
isMainPage()
Is this the mainpage?
Definition Title.php:1175
getFragmentForURL()
Get the fragment in URL form, including the "#" character if there is one.
Definition Title.php:1371
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:151
getAuthorsBetween( $old, $new, $limit, $options=[])
Get the authors between the given revisions or revision IDs.
Definition Title.php:4172
isSpecialPage()
Returns true if this is a special page.
Definition Title.php:1045
isNamespaceProtected(User $user)
Determines if $user is unable to edit this page because it has been protected by $wgNamespaceProtecti...
Definition Title.php:2726
static newFromLinkTarget(LinkTarget $linkTarget)
Create a new Title from a LinkTarget.
Definition Title.php:251
getSubpageUrlForm()
Get a URL-encoded form of the subpage text.
Definition Title.php:1598
isTalkPage()
Is this a talk page of some sort?
Definition Title.php:1290
getRootTitle()
Get the root page name title, i.e.
Definition Title.php:1513
bool int $mLatestID
ID of most recent revision.
Definition Title.php:86
getBrokenLinksFrom()
Get an array of Title objects referring to non-existent articles linked from this page.
Definition Title.php:3553
getDBkey()
Get the main part with underscores.
Definition Title.php:911
missingPermissionError( $action, $short)
Get a description array when the user doesn't have the right to perform $action (i....
Definition Title.php:2463
prefix( $name)
Prefix some arbitrary text with the namespace or interwiki prefix of this object.
Definition Title.php:1419
getEarliestRevTime( $flags=0)
Get the oldest revision timestamp of this page.
Definition Title.php:4057
checkActionPermissions( $action, $user, $errors, $rigor, $short)
Check action permissions not already checked in checkQuickPermissions.
Definition Title.php:2266
string $mFragment
Title fragment (i.e.
Definition Title.php:80
loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions=null)
Loads a string into mRestrictions array.
Definition Title.php:2931
getRootText()
Get the root page name text without a namespace, i.e.
Definition Title.php:1493
getFullUrlForRedirect( $query='', $proto=PROTO_CURRENT)
Get a url appropriate for making redirects based on an untrusted url arg.
Definition Title.php:1701
static newFromTitleValue(TitleValue $titleValue)
Create a new Title from a TitleValue.
Definition Title.php:240
string $mPrefixedText
Text form including namespace/interwiki, initialised on demand.
Definition Title.php:122
bool string $mContentModel
ID of the page's content model, i.e.
Definition Title.php:92
getLatestRevID( $flags=0)
What is the page_latest field for this page?
Definition Title.php:3293
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition Title.php:634
isValidRedirectTarget()
Check if this Title is a valid redirect target.
Definition Title.php:4584
static HashBagOStuff $titleCache
Definition Title.php:36
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:3488
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition Title.php:738
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:3430
bool $mHasCascadingRestrictions
Are cascading restrictions in effect on this page?
Definition Title.php:113
getPartialURL()
Get the URL-encoded form of the main part.
Definition Title.php:902
getBaseText()
Get the base page name without a namespace, i.e.
Definition Title.php:1528
isContentPage()
Is this Title in a namespace which contains content? In other words, is this a content page,...
Definition Title.php:1144
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:893
getTouched( $db=null)
Get the last touched timestamp.
Definition Title.php:4466
getTitleValue()
Get a TitleValue object representing this Title.
Definition Title.php:870
pageCond()
Get an associative array for selecting this title from the "page" table.
Definition Title.php:3970
bool $mCascadeRestriction
Cascade restrictions on this page to included templates and images?
Definition Title.php:104
static compare( $a, $b)
Callback for usort() to do title sorts by (namespace, title)
Definition Title.php:780
string $mUrlform
URL-encoded form of the main part.
Definition Title.php:62
isJsSubpage()
Is this a .js subpage of a user page?
Definition Title.php:1280
getFirstRevision( $flags=0)
Get the first revision of the page.
Definition Title.php:4035
static getMediaWikiTitleCodec()
B/C kludge: provide a TitleParser for use by Title.
Definition Title.php:168
string $mTextform
Text form (spaces not underscores) of the main part.
Definition Title.php:59
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:1326
static newFromIDs( $ids)
Make an array of titles from an array of IDs.
Definition Title.php:439
getUserPermissionsErrorsInternal( $action, $user, $rigor='secure', $short=false)
Can $user perform $action on this page? This is an internal function, with multiple levels of checks ...
Definition Title.php:2498
quickUserCan( $action, $user=null)
Can $user perform $action on this page? This skips potentially expensive cascading permission checks ...
Definition Title.php:1914
static getSelectFields()
Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
Definition Title.php:391
__construct()
Text form (spaces not underscores) of the main part.
Definition Title.php:210
isSubpageOf(Title $title)
Check if this title is a subpage of another title.
Definition Title.php:4270
getBaseTitle()
Get the base page name title, i.e.
Definition Title.php:1553
static newMainPage()
Create a new Title for the Main Page.
Definition Title.php:569
getParentCategoryTree( $children=[])
Get a tree of parent categories.
Definition Title.php:3943
checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short)
Check permissions on special pages & namespaces.
Definition Title.php:2120
bool $mHasSubpages
Whether a page has any subpages.
Definition Title.php:144
isCssSubpage()
Is this a .css subpage of a user page?
Definition Title.php:1270
getNextRevisionID( $revId, $flags=0)
Get the revision ID of the next revision.
Definition Title.php:4011
array $mRestrictionsExpiry
When do the restrictions on this page expire?
Definition Title.php:110
loadRestrictionsFromRows( $rows, $oldFashionedRestrictions=null)
Compiles list of active page restrictions from both page table (pre 1.10) and page_restrictions table...
Definition Title.php:2950
static fixUrlQueryArgs( $query, $query2=false)
Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args get{Canonical,...
Definition Title.php:1628
isValidMoveTarget( $nt)
Checks if $this can be moved to a given Title.
Definition Title.php:3860
loadRestrictions( $oldFashionedRestrictions=null)
Load restrictions from the page_restrictions table.
Definition Title.php:3023
getSquidURLs()
Definition Title.php:3617
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition Title.php:3230
checkPageRestrictions( $action, $user, $errors, $rigor, $short)
Check against page_restrictions table requirements on this page.
Definition Title.php:2185
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition Title.php:277
invalidateCache( $purgeTime=null)
Updates page_touched for this page; called from LinksUpdate.php.
Definition Title.php:4422
$mCascadingRestrictions
Caching the results of getCascadeProtectionSources.
Definition Title.php:107
static escapeFragmentForURL( $fragment)
Escape a text fragment, say from a link, for a URL.
Definition Title.php:764
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:3204
getSubjectNsText()
Get the namespace text of the subject (rather than talk) page.
Definition Title.php:998
bool $mIsBigDeletion
Would deleting this page be a big deletion?
Definition Title.php:157
int $mNamespace
Namespace index, i.e.
Definition Title.php:71
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition Title.php:548
null $mRedirect
Is the article at this title a redirect?
Definition Title.php:138
countAuthorsBetween( $old, $new, $limit, $options=[])
Get the number of authors between the given revisions or revision IDs.
Definition Title.php:4246
getCanonicalURL( $query='', $query2=false)
Get the URL for a canonical link, for use in things like IRC and e-mail notifications.
Definition Title.php:1877
checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short)
Check restrictions on cascading pages.
Definition Title.php:2219
isDeleted()
Is there a version of this page in the deletion archive?
Definition Title.php:3153
getPageViewLanguage()
Get the language in which the content of this page is written when viewed by user.
Definition Title.php:4724
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition Title.php:49
checkReadPermissions( $action, $user, $errors, $rigor, $short)
Check that the user is allowed to read this page.
Definition Title.php:2388
getLinkURL( $query='', $query2=false, $proto=PROTO_RELATIVE)
Get a URL that's the simplest URL that will be valid to link, locally, to the current Title.
Definition Title.php:1832
userCan( $action, $user=null, $rigor='secure')
Can $user perform $action on this page?
Definition Title.php:1927
array $mRestrictions
Array of groups allowed to edit this article.
Definition Title.php:98
int $mDefaultNamespace
Namespace index when there is no namespace.
Definition Title.php:132
deleteTitleProtection()
Remove any title protection due to page existing.
Definition Title.php:2648
getSubpage( $text)
Get the title for a subpage of the current page.
Definition Title.php:1589
getTitleProtection()
Is this title subject to title protection? Title protection is the one applied against creation of su...
Definition Title.php:2604
getEditURL()
Get the edit URL for this Title.
Definition Title.php:1891
getParentCategories()
Get categories to which this Title belongs and return an array of categories' names.
Definition Title.php:3908
int $mArticleID
Article ID, fetched from the link cache on demand.
Definition Title.php:83
static getTitleCache()
Definition Title.php:377
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:3541
getTransWikiID()
Returns the DB name of the distant wiki which owns the object.
Definition Title.php:853
isSubpage()
Is this a subpage?
Definition Title.php:1184
setFragment( $fragment)
Set the fragment for this title.
Definition Title.php:1391
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition Title.php:1735
getContentModel( $flags=0)
Get the page's content model id, see the CONTENT_MODEL_XXX constants.
Definition Title.php:944
isCssOrJsPage()
Could this page contain custom CSS or JavaScript for the global UI.
Definition Title.php:1225
isBigDeletion()
Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit.
Definition Title.php:4077
getCdnUrls()
Get a list of URLs to purge from the CDN cache when this page changes.
Definition Title.php:3589
string $mUserCaseDBKey
Database key with the initial letter in the case specified by the user.
Definition Title.php:68
getInterwiki()
Get the interwiki prefix.
Definition Title.php:821
getEditNotices( $oldid=0)
Get a list of rendered edit notices for this page.
Definition Title.php:4768
__sleep()
Definition Title.php:4839
getCascadeProtectionSources( $getPages=true)
Cascading protection: Get the source of any cascading restrictions on this page.
Definition Title.php:2775
mixed $mTitleProtection
Cached value for getTitleProtection (create protection)
Definition Title.php:125
getSubpageText()
Get the lowest-level subpage name, i.e.
Definition Title.php:1568
string $mDbkeyform
Main part with underscores.
Definition Title.php:65
moveSubpages( $nt, $auth=true, $reason='', $createRedirect=true)
Move this page's subpages to be subpages of $nt.
Definition Title.php:3742
hasSourceText()
Does this page have source text?
Definition Title.php:4371
flushRestrictions()
Flush the protection cache in this object and force reload from the database.
Definition Title.php:3061
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1449
hasSubpages()
Does this have subpages? (Warning, usually requires an extra DB query.)
Definition Title.php:3097
string bool $mOldRestrictions
Text form (spaces not underscores) of the main part.
Definition Title.php:101
canTalk()
Could this title have a corresponding talk page?
Definition Title.php:1018
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition Title.php:2052
isAlwaysKnown()
Should links to this title be shown as potentially viewable (i.e.
Definition Title.php:4309
getNamespaceKey( $prepend='nstab-')
Generate strings used for xml 'id' names in monobook tabs.
Definition Title.php:4517
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Definition Title.php:4638
static newFromRow( $row)
Make a Title object from a DB row.
Definition Title.php:465
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:47
static selectFields()
Return the list of revision fields that should be selected to create a new page.
Definition WikiPage.php:266
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an article
Definition design.txt:25
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition design.txt:57
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition design.txt:18
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition design.txt:56
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
const PROTO_CANONICAL
Definition Defines.php:266
const NS_USER
Definition Defines.php:72
const CONTENT_MODEL_CSS
Definition Defines.php:280
const DB_MASTER
Definition Defines.php:48
const NS_FILE
Definition Defines.php:76
const PROTO_CURRENT
Definition Defines.php:265
const NS_MAIN
Definition Defines.php:70
const NS_MEDIAWIKI
Definition Defines.php:78
const NS_SPECIAL
Definition Defines.php:59
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:278
const PROTO_HTTP
Definition Defines.php:262
const NS_MEDIA
Definition Defines.php:58
const PROTO_RELATIVE
Definition Defines.php:264
const NS_CATEGORY
Definition Defines.php:84
const CONTENT_MODEL_JAVASCRIPT
Definition Defines.php:279
const DB_SLAVE
Definition Defines.php:47
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context $revId
Definition hooks.txt:1041
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1007
the array() calling protocol came about after MediaWiki 1.4rc1.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition hooks.txt:2379
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition hooks.txt:986
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':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:1799
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account incomplete not yet checked for validity & $retval
Definition hooks.txt:268
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:183
namespace and then decline to actually register it & $namespaces
Definition hooks.txt:915
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1042
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition hooks.txt:1040
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:1810
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2413
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:944
null for the local wiki Added in
Definition hooks.txt:1421
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2555
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1081
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:846
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:1818
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:314
processing should stop and the error should be shown to the user * false
Definition hooks.txt:189
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:1458
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:1597
$wgActionPaths
Definition img_auth.php:46
$wgArticlePath
Definition img_auth.php:45
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Basic database interface for live and lazy-loaded DB handles.
Definition IDatabase.php:35
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
addQuotes( $s)
Adds quotes and backslashes.
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
getInterwiki()
The interwiki component of this LinkTarget.
getFragment()
Get the link fragment (i.e.
getNamespace()
Get the namespace index.
getText()
Returns the link in text form, without namespace prefix or fragment.
linkcache txt The LinkCache class maintains a list of article titles and the information about whether or not the article exists in the database This is used to mark up links when displaying a page If the same link appears more than once on any page then it only has to be looked up once In most cases link lookups are done in batches with the LinkBatch class or the equivalent in so the link cache is mostly useful for short snippets of parsed and for links in the navigation areas of the skin The link cache was formerly used to track links used in a document for the purposes of updating the link tables This application is now deprecated To create a you can use the following $titles
Definition linkcache.txt:17
if(!isset( $args[0])) $lang