MediaWiki REL1_33
Article.php
Go to the documentation of this file.
1<?php
26
37class Article implements Page {
42 protected $mContext;
43
45 protected $mPage;
46
52
62
69 public $mContentLoaded = false;
70
76 public $mOldId;
77
79 public $mRedirectedFrom = null;
80
82 public $mRedirectUrl = false;
83
89 public $mRevIdFetched = 0;
90
99 private $fetchResult = null;
100
108 public $mRevision = null;
109
115 public $mParserOutput = null;
116
122 protected $viewIsRenderAction = false;
123
129 public function __construct( Title $title, $oldId = null ) {
130 $this->mOldId = $oldId;
131 $this->mPage = $this->newPage( $title );
132 }
133
138 protected function newPage( Title $title ) {
139 return new WikiPage( $title );
140 }
141
147 public static function newFromID( $id ) {
148 $t = Title::newFromID( $id );
149 return $t == null ? null : new static( $t );
150 }
151
159 public static function newFromTitle( $title, IContextSource $context ) {
160 if ( NS_MEDIA == $title->getNamespace() ) {
161 // XXX: This should not be here, but where should it go?
162 $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
163 }
164
165 $page = null;
166 Hooks::run( 'ArticleFromTitle', [ &$title, &$page, $context ] );
167 if ( !$page ) {
168 switch ( $title->getNamespace() ) {
169 case NS_FILE:
170 $page = new ImagePage( $title );
171 break;
172 case NS_CATEGORY:
173 $page = new CategoryPage( $title );
174 break;
175 default:
176 $page = new Article( $title );
177 }
178 }
179 $page->setContext( $context );
180
181 return $page;
182 }
183
191 public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
193 $article->mPage = $page; // override to keep process cached vars
194 return $article;
195 }
196
202 public function getRedirectedFrom() {
204 }
205
211 public function setRedirectedFrom( Title $from ) {
212 $this->mRedirectedFrom = $from;
213 }
214
220 public function getTitle() {
221 return $this->mPage->getTitle();
222 }
223
230 public function getPage() {
231 return $this->mPage;
232 }
233
237 public function clear() {
238 $this->mContentLoaded = false;
239
240 $this->mRedirectedFrom = null; # Title object if set
241 $this->mRevIdFetched = 0;
242 $this->mRedirectUrl = false;
243 $this->mRevision = null;
244 $this->mContentObject = null;
245 $this->fetchResult = null;
246
247 // TODO hard-deprecate direct access to public fields
248
249 $this->mPage->clear();
250 }
251
269 protected function getContentObject() {
270 if ( $this->mPage->getId() === 0 ) {
272 } else {
273 $this->fetchContentObject();
275 }
276
277 return $content;
278 }
279
285 private function getSubstituteContent() {
286 # If this is a MediaWiki:x message, then load the messages
287 # and return the message value for x.
288 if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
289 $text = $this->getTitle()->getDefaultMessageText();
290 if ( $text === false ) {
291 $text = '';
292 }
293
294 $content = ContentHandler::makeContent( $text, $this->getTitle() );
295 } else {
296 $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
297 $content = new MessageContent( $message, null );
298 }
299
300 return $content;
301 }
302
314
315 return $content->getParserOutput( $this->getTitle(), 0, $options );
316 }
317
325 public function getOldID() {
326 if ( is_null( $this->mOldId ) ) {
327 $this->mOldId = $this->getOldIDFromRequest();
328 }
329
330 return $this->mOldId;
331 }
332
338 public function getOldIDFromRequest() {
339 $this->mRedirectUrl = false;
340
341 $request = $this->getContext()->getRequest();
342 $oldid = $request->getIntOrNull( 'oldid' );
343
344 if ( $oldid === null ) {
345 return 0;
346 }
347
348 if ( $oldid !== 0 ) {
349 # Load the given revision and check whether the page is another one.
350 # In that case, update this instance to reflect the change.
351 if ( $oldid === $this->mPage->getLatest() ) {
352 $this->mRevision = $this->mPage->getRevision();
353 } else {
354 $this->mRevision = Revision::newFromId( $oldid );
355 if ( $this->mRevision !== null ) {
356 // Revision title doesn't match the page title given?
357 if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
358 $function = get_class( $this->mPage ) . '::newFromID';
359 $this->mPage = $function( $this->mRevision->getPage() );
360 }
361 }
362 }
363 }
364
365 if ( $request->getVal( 'direction' ) == 'next' ) {
366 $nextid = $this->getTitle()->getNextRevisionID( $oldid );
367 if ( $nextid ) {
368 $oldid = $nextid;
369 $this->mRevision = null;
370 } else {
371 $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
372 }
373 } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
374 $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
375 if ( $previd ) {
376 $oldid = $previd;
377 $this->mRevision = null;
378 }
379 }
380
381 $this->mRevIdFetched = $this->mRevision ? $this->mRevision->getId() : 0;
382
383 return $oldid;
384 }
385
399 protected function fetchContentObject() {
400 if ( !$this->mContentLoaded ) {
401 $this->fetchRevisionRecord();
402 }
403
405 }
406
416 protected function fetchRevisionRecord() {
417 if ( $this->fetchResult ) {
418 return $this->mRevision ? $this->mRevision->getRevisionRecord() : null;
419 }
420
421 $this->mContentLoaded = true;
422 $this->mContentObject = null;
423
424 $oldid = $this->getOldID();
425
426 // $this->mRevision might already be fetched by getOldIDFromRequest()
427 if ( !$this->mRevision ) {
428 if ( !$oldid ) {
429 $this->mRevision = $this->mPage->getRevision();
430
431 if ( !$this->mRevision ) {
432 wfDebug( __METHOD__ . " failed to find page data for title " .
433 $this->getTitle()->getPrefixedText() . "\n" );
434
435 // Just for sanity, output for this case is done by showMissingArticle().
436 $this->fetchResult = Status::newFatal( 'noarticletext' );
438 return null;
439 }
440 } else {
441 $this->mRevision = Revision::newFromId( $oldid );
442
443 if ( !$this->mRevision ) {
444 wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid\n" );
445
446 $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
448 return null;
449 }
450 }
451 }
452
453 $this->mRevIdFetched = $this->mRevision->getId();
454 $this->fetchResult = Status::newGood( $this->mRevision );
455
456 if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $this->getContext()->getUser() ) ) {
457 wfDebug( __METHOD__ . " failed to retrieve content of revision " .
458 $this->mRevision->getId() . "\n" );
459
460 // Just for sanity, output for this case is done by showDeletedRevisionHeader().
461 $this->fetchResult = Status::newFatal( 'rev-deleted-text-permission' );
463 return null;
464 }
465
466 if ( Hooks::isRegistered( 'ArticleAfterFetchContentObject' ) ) {
467 $contentObject = $this->mRevision->getContent(
469 $this->getContext()->getUser()
470 );
471
472 $hookContentObject = $contentObject;
473
474 // Avoid PHP 7.1 warning of passing $this by reference
475 $articlePage = $this;
476
477 Hooks::run(
478 'ArticleAfterFetchContentObject',
479 [ &$articlePage, &$hookContentObject ],
480 '1.32'
481 );
482
483 if ( $hookContentObject !== $contentObject ) {
484 // A hook handler is trying to override the content
485 $this->applyContentOverride( $hookContentObject );
486 }
487 }
488
489 // For B/C only
490 $this->mContentObject = $this->mRevision->getContent(
492 $this->getContext()->getUser()
493 );
494
495 return $this->mRevision->getRevisionRecord();
496 }
497
504 private function makeFetchErrorContent() {
505 if ( !$this->fetchResult || $this->fetchResult->isOK() ) {
506 return null;
507 }
508
509 return new MessageContent( $this->fetchResult->getMessage() );
510 }
511
524 private function applyContentOverride( Content $override ) {
525 // Construct a fake revision
526 $rev = new MutableRevisionRecord( $this->getTitle() );
527 $rev->setContent( SlotRecord::MAIN, $override );
528
529 $this->mRevision = new Revision( $rev );
530
531 // For B/C only
532 $this->mContentObject = $override;
533 }
534
540 public function isCurrent() {
541 # If no oldid, this is the current version.
542 if ( $this->getOldID() == 0 ) {
543 return true;
544 }
545
546 return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
547 }
548
558 public function getRevisionFetched() {
559 $this->fetchRevisionRecord();
560
561 if ( $this->fetchResult->isOK() ) {
562 return $this->mRevision;
563 }
564 }
565
574 public function getRevIdFetched() {
575 if ( $this->fetchResult && $this->fetchResult->isOK() ) {
576 return $this->fetchResult->value->getId();
577 } else {
578 return $this->mPage->getLatest();
579 }
580 }
581
586 public function view() {
588
589 # Get variables from query string
590 # As side effect this will load the revision and update the title
591 # in a revision ID is passed in the request, so this should remain
592 # the first call of this method even if $oldid is used way below.
593 $oldid = $this->getOldID();
594
595 $user = $this->getContext()->getUser();
596 # Another whitelist check in case getOldID() is altering the title
597 $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
598 if ( count( $permErrors ) ) {
599 wfDebug( __METHOD__ . ": denied on secondary read check\n" );
600 throw new PermissionsError( 'read', $permErrors );
601 }
602
603 $outputPage = $this->getContext()->getOutput();
604 # getOldID() may as well want us to redirect somewhere else
605 if ( $this->mRedirectUrl ) {
606 $outputPage->redirect( $this->mRedirectUrl );
607 wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
608
609 return;
610 }
611
612 # If we got diff in the query, we want to see a diff page instead of the article.
613 if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
614 wfDebug( __METHOD__ . ": showing diff page\n" );
615 $this->showDiffPage();
616
617 return;
618 }
619
620 # Set page title (may be overridden by DISPLAYTITLE)
621 $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
622
623 $outputPage->setArticleFlag( true );
624 # Allow frames by default
625 $outputPage->allowClickjacking();
626
627 $parserCache = MediaWikiServices::getInstance()->getParserCache();
628
629 $parserOptions = $this->getParserOptions();
630 $poOptions = [];
631 # Render printable version, use printable version cache
632 if ( $outputPage->isPrintable() ) {
633 $parserOptions->setIsPrintable( true );
634 $poOptions['enableSectionEditLinks'] = false;
635 } elseif ( $this->viewIsRenderAction
636 || !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user )
637 ) {
638 $poOptions['enableSectionEditLinks'] = false;
639 }
640
641 # Try client and file cache
642 if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
643 # Try to stream the output from file cache
644 if ( $wgUseFileCache && $this->tryFileCache() ) {
645 wfDebug( __METHOD__ . ": done file cache\n" );
646 # tell wgOut that output is taken care of
647 $outputPage->disable();
648 $this->mPage->doViewUpdates( $user, $oldid );
649
650 return;
651 }
652 }
653
654 # Should the parser cache be used?
655 $useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
656 wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
657 if ( $user->getStubThreshold() ) {
658 MediaWikiServices::getInstance()->getStatsdDataFactory()->increment( 'pcache_miss_stub' );
659 }
660
662 $this->showNamespaceHeader();
663
664 # Iterate through the possible ways of constructing the output text.
665 # Keep going until $outputDone is set, or we run out of things to do.
666 $pass = 0;
667 $outputDone = false;
668 $this->mParserOutput = false;
669
670 while ( !$outputDone && ++$pass ) {
671 switch ( $pass ) {
672 case 1:
673 // Avoid PHP 7.1 warning of passing $this by reference
674 $articlePage = $this;
675 Hooks::run( 'ArticleViewHeader', [ &$articlePage, &$outputDone, &$useParserCache ] );
676 break;
677 case 2:
678 # Early abort if the page doesn't exist
679 if ( !$this->mPage->exists() ) {
680 wfDebug( __METHOD__ . ": showing missing article\n" );
681 $this->showMissingArticle();
682 $this->mPage->doViewUpdates( $user );
683 return;
684 }
685
686 # Try the parser cache
687 if ( $useParserCache ) {
688 $this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
689
690 if ( $this->mParserOutput !== false ) {
691 if ( $oldid ) {
692 wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
693 $this->setOldSubtitle( $oldid );
694 } else {
695 wfDebug( __METHOD__ . ": showing parser cache contents\n" );
696 }
697 $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
698 # Ensure that UI elements requiring revision ID have
699 # the correct version information.
700 $outputPage->setRevisionId( $this->mPage->getLatest() );
701 # Preload timestamp to avoid a DB hit
702 $cachedTimestamp = $this->mParserOutput->getTimestamp();
703 if ( $cachedTimestamp !== null ) {
704 $outputPage->setRevisionTimestamp( $cachedTimestamp );
705 $this->mPage->setTimestamp( $cachedTimestamp );
706 }
707 $outputDone = true;
708 }
709 }
710 break;
711 case 3:
712 # Are we looking at an old revision
713 $rev = $this->fetchRevisionRecord();
714 if ( $oldid && $this->fetchResult->isOK() ) {
715 $this->setOldSubtitle( $oldid );
716
717 if ( !$this->showDeletedRevisionHeader() ) {
718 wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
719 return;
720 }
721 }
722
723 # Ensure that UI elements requiring revision ID have
724 # the correct version information.
725 $outputPage->setRevisionId( $this->getRevIdFetched() );
726 # Preload timestamp to avoid a DB hit
727 $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
728
729 # Pages containing custom CSS or JavaScript get special treatment
730 if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
731 $dir = $this->getContext()->getLanguage()->getDir();
732 $lang = $this->getContext()->getLanguage()->getHtmlCode();
733
734 $outputPage->wrapWikiMsg(
735 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
736 'clearyourcache'
737 );
738 } elseif ( !Hooks::run( 'ArticleRevisionViewCustom', [
739 $rev,
740 $this->getTitle(),
741 $oldid,
742 $outputPage,
743 ] )
744 ) {
745 // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
746 // Allow extensions do their own custom view for certain pages
747 $outputDone = true;
748 } elseif ( !Hooks::run( 'ArticleContentViewCustom',
749 [ $this->fetchContentObject(), $this->getTitle(), $outputPage ], '1.32' )
750 ) {
751 // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
752 // Allow extensions do their own custom view for certain pages
753 $outputDone = true;
754 }
755 break;
756 case 4:
757 # Run the parse, protected by a pool counter
758 wfDebug( __METHOD__ . ": doing uncached parse\n" );
759
760 $rev = $this->fetchRevisionRecord();
761 $error = null;
762
763 if ( $rev ) {
764 $poolArticleView = new PoolWorkArticleView(
765 $this->getPage(),
766 $parserOptions,
767 $this->getRevIdFetched(),
768 $useParserCache,
769 $rev,
770 // permission checking was done earlier via showDeletedRevisionHeader()
771 RevisionRecord::RAW
772 );
773 $ok = $poolArticleView->execute();
774 $error = $poolArticleView->getError();
775 $this->mParserOutput = $poolArticleView->getParserOutput() ?: null;
776
777 # Don't cache a dirty ParserOutput object
778 if ( $poolArticleView->getIsDirty() ) {
779 $outputPage->setCdnMaxage( 0 );
780 $outputPage->addHTML( "<!-- parser cache is expired, " .
781 "sending anyway due to pool overload-->\n" );
782 }
783 } else {
784 $ok = false;
785 }
786
787 if ( !$ok ) {
788 if ( $error ) {
789 $outputPage->clearHTML(); // for release() errors
790 $outputPage->enableClientCache( false );
791 $outputPage->setRobotPolicy( 'noindex,nofollow' );
792
793 $errortext = $error->getWikiText( false, 'view-pool-error' );
794 $outputPage->wrapWikiTextAsInterface( 'errorbox', $errortext );
795 }
796 # Connection or timeout error
797 return;
798 }
799
800 if ( $this->mParserOutput ) {
801 $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
802 }
803
804 if ( $rev && $this->getRevisionRedirectTarget( $rev ) ) {
805 $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
806 $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
807 }
808
809 $outputDone = true;
810 break;
811 # Should be unreachable, but just in case...
812 default:
813 break 2;
814 }
815 }
816
817 // Get the ParserOutput actually *displayed* here.
818 // Note that $this->mParserOutput is the *current*/oldid version output.
819 // Note that the ArticleViewHeader hook is allowed to set $outputDone to a
820 // ParserOutput instance.
821 $pOutput = ( $outputDone instanceof ParserOutput )
822 ? $outputDone // object fetched by hook
823 : ( $this->mParserOutput ?: null ); // ParserOutput or null, avoid false
824
825 # Adjust title for main page & pages with displaytitle
826 if ( $pOutput ) {
827 $this->adjustDisplayTitle( $pOutput );
828 }
829
830 # For the main page, overwrite the <title> element with the con-
831 # tents of 'pagetitle-view-mainpage' instead of the default (if
832 # that's not empty).
833 # This message always exists because it is in the i18n files
834 if ( $this->getTitle()->isMainPage() ) {
835 $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
836 if ( !$msg->isDisabled() ) {
837 $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
838 }
839 }
840
841 # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
842 # This could use getTouched(), but that could be scary for major template edits.
843 $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
844
845 # Check for any __NOINDEX__ tags on the page using $pOutput
846 $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
847 $outputPage->setIndexPolicy( $policy['index'] );
848 $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
849
850 $this->showViewFooter();
851 $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this
852
853 # Load the postEdit module if the user just saved this revision
854 # See also EditPage::setPostEditCookie
855 $request = $this->getContext()->getRequest();
857 $postEdit = $request->getCookie( $cookieKey );
858 if ( $postEdit ) {
859 # Clear the cookie. This also prevents caching of the response.
860 $request->response()->clearCookie( $cookieKey );
861 $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
862 $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
863 }
864 }
865
870 private function getRevisionRedirectTarget( RevisionRecord $revision ) {
871 // TODO: find a *good* place for the code that determines the redirect target for
872 // a given revision!
873 // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
874 $content = $revision->getContent( SlotRecord::MAIN );
875 return $content ? $content->getRedirectTarget() : null;
876 }
877
882 public function adjustDisplayTitle( ParserOutput $pOutput ) {
883 $out = $this->getContext()->getOutput();
884
885 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
886 $titleText = $pOutput->getTitleText();
887 if ( strval( $titleText ) !== '' ) {
888 $out->setPageTitle( $titleText );
889 $out->setDisplayTitle( $titleText );
890 }
891 }
892
897 protected function showDiffPage() {
898 $request = $this->getContext()->getRequest();
899 $user = $this->getContext()->getUser();
900 $diff = $request->getVal( 'diff' );
901 $rcid = $request->getVal( 'rcid' );
902 $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
903 $purge = $request->getVal( 'action' ) == 'purge';
904 $unhide = $request->getInt( 'unhide' ) == 1;
905 $oldid = $this->getOldID();
906
907 $rev = $this->getRevisionFetched();
908
909 if ( !$rev ) {
910 $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
911 $msg = $this->getContext()->msg( 'difference-missing-revision' )
912 ->params( $oldid )
913 ->numParams( 1 )
914 ->parseAsBlock();
915 $this->getContext()->getOutput()->addHTML( $msg );
916 return;
917 }
918
919 $contentHandler = $rev->getContentHandler();
920 $de = $contentHandler->createDifferenceEngine(
921 $this->getContext(),
922 $oldid,
923 $diff,
924 $rcid,
925 $purge,
926 $unhide
927 );
928
929 // DifferenceEngine directly fetched the revision:
930 $this->mRevIdFetched = $de->getNewid();
931 $de->showDiffPage( $diffOnly );
932
933 // Run view updates for the newer revision being diffed (and shown
934 // below the diff if not $diffOnly).
935 list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
936 // New can be false, convert it to 0 - this conveniently means the latest revision
937 $this->mPage->doViewUpdates( $user, (int)$new );
938 }
939
947 public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
949
950 $ns = $this->getTitle()->getNamespace();
951
952 # Don't index user and user talk pages for blocked users (T13443)
953 if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
954 $specificTarget = null;
955 $vagueTarget = null;
956 $titleText = $this->getTitle()->getText();
957 if ( IP::isValid( $titleText ) ) {
958 $vagueTarget = $titleText;
959 } else {
960 $specificTarget = $titleText;
961 }
962 if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
963 return [
964 'index' => 'noindex',
965 'follow' => 'nofollow'
966 ];
967 }
968 }
969
970 if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
971 # Non-articles (special pages etc), and old revisions
972 return [
973 'index' => 'noindex',
974 'follow' => 'nofollow'
975 ];
976 } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
977 # Discourage indexing of printable versions, but encourage following
978 return [
979 'index' => 'noindex',
980 'follow' => 'follow'
981 ];
982 } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
983 # For ?curid=x urls, disallow indexing
984 return [
985 'index' => 'noindex',
986 'follow' => 'follow'
987 ];
988 }
989
990 # Otherwise, construct the policy based on the various config variables.
992
993 if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
994 # Honour customised robot policies for this namespace
995 $policy = array_merge(
996 $policy,
997 self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
998 );
999 }
1000 if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
1001 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1002 # a final sanity check that we have really got the parser output.
1003 $policy = array_merge(
1004 $policy,
1005 [ 'index' => $pOutput->getIndexPolicy() ]
1006 );
1007 }
1008
1009 if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
1010 # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1011 $policy = array_merge(
1012 $policy,
1013 self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
1014 );
1015 }
1016
1017 return $policy;
1018 }
1019
1027 public static function formatRobotPolicy( $policy ) {
1028 if ( is_array( $policy ) ) {
1029 return $policy;
1030 } elseif ( !$policy ) {
1031 return [];
1032 }
1033
1034 $policy = explode( ',', $policy );
1035 $policy = array_map( 'trim', $policy );
1036
1037 $arr = [];
1038 foreach ( $policy as $var ) {
1039 if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
1040 $arr['index'] = $var;
1041 } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
1042 $arr['follow'] = $var;
1043 }
1044 }
1045
1046 return $arr;
1047 }
1048
1056 public function showRedirectedFromHeader() {
1057 global $wgRedirectSources;
1058
1059 $context = $this->getContext();
1060 $outputPage = $context->getOutput();
1061 $request = $context->getRequest();
1062 $rdfrom = $request->getVal( 'rdfrom' );
1063
1064 // Construct a URL for the current page view, but with the target title
1065 $query = $request->getValues();
1066 unset( $query['rdfrom'] );
1067 unset( $query['title'] );
1068 if ( $this->getTitle()->isRedirect() ) {
1069 // Prevent double redirects
1070 $query['redirect'] = 'no';
1071 }
1072 $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1073
1074 if ( isset( $this->mRedirectedFrom ) ) {
1075 // Avoid PHP 7.1 warning of passing $this by reference
1076 $articlePage = $this;
1077
1078 // This is an internally redirected page view.
1079 // We'll need a backlink to the source page for navigation.
1080 if ( Hooks::run( 'ArticleViewRedirect', [ &$articlePage ] ) ) {
1081 $redir = Linker::linkKnown(
1082 $this->mRedirectedFrom,
1083 null,
1084 [],
1085 [ 'redirect' => 'no' ]
1086 );
1087
1088 $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1089 $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1090 . "</span>" );
1091
1092 // Add the script to update the displayed URL and
1093 // set the fragment if one was specified in the redirect
1094 $outputPage->addJsConfigVars( [
1095 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1096 ] );
1097 $outputPage->addModules( 'mediawiki.action.view.redirect' );
1098
1099 // Add a <link rel="canonical"> tag
1100 $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1101
1102 // Tell the output object that the user arrived at this article through a redirect
1103 $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1104
1105 return true;
1106 }
1107 } elseif ( $rdfrom ) {
1108 // This is an externally redirected view, from some other wiki.
1109 // If it was reported from a trusted site, supply a backlink.
1110 if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
1111 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1112 $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1113 $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1114 . "</span>" );
1115
1116 // Add the script to update the displayed URL
1117 $outputPage->addJsConfigVars( [
1118 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1119 ] );
1120 $outputPage->addModules( 'mediawiki.action.view.redirect' );
1121
1122 return true;
1123 }
1124 }
1125
1126 return false;
1127 }
1128
1133 public function showNamespaceHeader() {
1134 if ( $this->getTitle()->isTalkPage() && !wfMessage( 'talkpageheader' )->isDisabled() ) {
1135 $this->getContext()->getOutput()->wrapWikiMsg(
1136 "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1137 [ 'talkpageheader' ]
1138 );
1139 }
1140 }
1141
1145 public function showViewFooter() {
1146 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1147 if ( $this->getTitle()->getNamespace() == NS_USER_TALK
1148 && IP::isValid( $this->getTitle()->getText() )
1149 ) {
1150 $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1151 }
1152
1153 // Show a footer allowing the user to patrol the shown revision or page if possible
1154 $patrolFooterShown = $this->showPatrolFooter();
1155
1156 Hooks::run( 'ArticleViewFooter', [ $this, $patrolFooterShown ] );
1157 }
1158
1168 public function showPatrolFooter() {
1170
1171 // Allow hooks to decide whether to not output this at all
1172 if ( !Hooks::run( 'ArticleShowPatrolFooter', [ $this ] ) ) {
1173 return false;
1174 }
1175
1176 $outputPage = $this->getContext()->getOutput();
1177 $user = $this->getContext()->getUser();
1178 $title = $this->getTitle();
1179 $rc = false;
1180
1181 if ( !$title->quickUserCan( 'patrol', $user )
1183 || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1184 ) {
1185 // Patrolling is disabled or the user isn't allowed to
1186 return false;
1187 }
1188
1189 if ( $this->mRevision
1190 && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
1191 ) {
1192 // The current revision is already older than what could be in the RC table
1193 // 6h tolerance because the RC might not be cleaned out regularly
1194 return false;
1195 }
1196
1197 // Check for cached results
1198 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1199 $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1200 if ( $cache->get( $key ) ) {
1201 return false;
1202 }
1203
1204 $dbr = wfGetDB( DB_REPLICA );
1205 $oldestRevisionTimestamp = $dbr->selectField(
1206 'revision',
1207 'MIN( rev_timestamp )',
1208 [ 'rev_page' => $title->getArticleID() ],
1209 __METHOD__
1210 );
1211
1212 // New page patrol: Get the timestamp of the oldest revison which
1213 // the revision table holds for the given page. Then we look
1214 // whether it's within the RC lifespan and if it is, we try
1215 // to get the recentchanges row belonging to that entry
1216 // (with rc_new = 1).
1217 $recentPageCreation = false;
1218 if ( $oldestRevisionTimestamp
1219 && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1220 ) {
1221 // 6h tolerance because the RC might not be cleaned out regularly
1222 $recentPageCreation = true;
1223 $rc = RecentChange::newFromConds(
1224 [
1225 'rc_new' => 1,
1226 'rc_timestamp' => $oldestRevisionTimestamp,
1227 'rc_namespace' => $title->getNamespace(),
1228 'rc_cur_id' => $title->getArticleID()
1229 ],
1230 __METHOD__
1231 );
1232 if ( $rc ) {
1233 // Use generic patrol message for new pages
1234 $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1235 }
1236 }
1237
1238 // File patrol: Get the timestamp of the latest upload for this page,
1239 // check whether it is within the RC lifespan and if it is, we try
1240 // to get the recentchanges row belonging to that entry
1241 // (with rc_type = RC_LOG, rc_log_type = upload).
1242 $recentFileUpload = false;
1243 if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1244 && $title->getNamespace() === NS_FILE ) {
1245 // Retrieve timestamp of most recent upload
1246 $newestUploadTimestamp = $dbr->selectField(
1247 'image',
1248 'MAX( img_timestamp )',
1249 [ 'img_name' => $title->getDBkey() ],
1250 __METHOD__
1251 );
1252 if ( $newestUploadTimestamp
1253 && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1254 ) {
1255 // 6h tolerance because the RC might not be cleaned out regularly
1256 $recentFileUpload = true;
1257 $rc = RecentChange::newFromConds(
1258 [
1259 'rc_type' => RC_LOG,
1260 'rc_log_type' => 'upload',
1261 'rc_timestamp' => $newestUploadTimestamp,
1262 'rc_namespace' => NS_FILE,
1263 'rc_cur_id' => $title->getArticleID()
1264 ],
1265 __METHOD__
1266 );
1267 if ( $rc ) {
1268 // Use patrol message specific to files
1269 $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1270 }
1271 }
1272 }
1273
1274 if ( !$recentPageCreation && !$recentFileUpload ) {
1275 // Page creation and latest upload (for files) is too old to be in RC
1276
1277 // We definitely can't patrol so cache the information
1278 // When a new file version is uploaded, the cache is cleared
1279 $cache->set( $key, '1' );
1280
1281 return false;
1282 }
1283
1284 if ( !$rc ) {
1285 // Don't cache: This can be hit if the page gets accessed very fast after
1286 // its creation / latest upload or in case we have high replica DB lag. In case
1287 // the revision is too old, we will already return above.
1288 return false;
1289 }
1290
1291 if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1292 // Patrolled RC entry around
1293
1294 // Cache the information we gathered above in case we can't patrol
1295 // Don't cache in case we can patrol as this could change
1296 $cache->set( $key, '1' );
1297
1298 return false;
1299 }
1300
1301 if ( $rc->getPerformer()->equals( $user ) ) {
1302 // Don't show a patrol link for own creations/uploads. If the user could
1303 // patrol them, they already would be patrolled
1304 return false;
1305 }
1306
1307 $outputPage->preventClickjacking();
1308 if ( $user->isAllowed( 'writeapi' ) ) {
1309 $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
1310 }
1311
1313 $title,
1314 $markPatrolledMsg->escaped(),
1315 [],
1316 [
1317 'action' => 'markpatrolled',
1318 'rcid' => $rc->getAttribute( 'rc_id' ),
1319 ]
1320 );
1321
1322 $outputPage->addHTML(
1323 "<div class='patrollink' data-mw='interface'>" .
1324 wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1325 '</div>'
1326 );
1327
1328 return true;
1329 }
1330
1337 public static function purgePatrolFooterCache( $articleID ) {
1338 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1339 $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1340 }
1341
1346 public function showMissingArticle() {
1347 global $wgSend404Code;
1348
1349 $outputPage = $this->getContext()->getOutput();
1350 // Whether the page is a root user page of an existing user (but not a subpage)
1351 $validUserPage = false;
1352
1353 $title = $this->getTitle();
1354
1355 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1356 if ( $title->getNamespace() == NS_USER
1357 || $title->getNamespace() == NS_USER_TALK
1358 ) {
1359 $rootPart = explode( '/', $title->getText() )[0];
1360 $user = User::newFromName( $rootPart, false /* allow IP users */ );
1361 $ip = User::isIP( $rootPart );
1362 $block = Block::newFromTarget( $user, $user );
1363
1364 if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1365 $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1366 [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1367 } elseif (
1368 !is_null( $block ) &&
1369 $block->getType() != Block::TYPE_AUTO &&
1370 ( $block->isSitewide() || $user->isBlockedFrom( $title ) )
1371 ) {
1372 // Show log extract if the user is sitewide blocked or is partially
1373 // blocked and not allowed to edit their user page or user talk page
1375 $outputPage,
1376 'block',
1377 MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
1378 '',
1379 [
1380 'lim' => 1,
1381 'showIfEmpty' => false,
1382 'msgKey' => [
1383 'blocked-notice-logextract',
1384 $user->getName() # Support GENDER in notice
1385 ]
1386 ]
1387 );
1388 $validUserPage = !$title->isSubpage();
1389 } else {
1390 $validUserPage = !$title->isSubpage();
1391 }
1392 }
1393
1394 Hooks::run( 'ShowMissingArticle', [ $this ] );
1395
1396 # Show delete and move logs if there were any such events.
1397 # The logging query can DOS the site when bots/crawlers cause 404 floods,
1398 # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1399 $cache = MediaWikiServices::getInstance()->getMainObjectStash();
1400 $key = $cache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1401 $loggedIn = $this->getContext()->getUser()->isLoggedIn();
1402 $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
1403 if ( $loggedIn || $cache->get( $key ) || $sessionExists ) {
1404 $logTypes = [ 'delete', 'move', 'protect' ];
1405
1406 $dbr = wfGetDB( DB_REPLICA );
1407
1408 $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1409 // Give extensions a chance to hide their (unrelated) log entries
1410 Hooks::run( 'Article::MissingArticleConditions', [ &$conds, $logTypes ] );
1412 $outputPage,
1413 $logTypes,
1414 $title,
1415 '',
1416 [
1417 'lim' => 10,
1418 'conds' => $conds,
1419 'showIfEmpty' => false,
1420 'msgKey' => [ $loggedIn || $sessionExists
1421 ? 'moveddeleted-notice'
1422 : 'moveddeleted-notice-recent'
1423 ]
1424 ]
1425 );
1426 }
1427
1428 if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1429 // If there's no backing content, send a 404 Not Found
1430 // for better machine handling of broken links.
1431 $this->getContext()->getRequest()->response()->statusHeader( 404 );
1432 }
1433
1434 // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1435 $policy = $this->getRobotPolicy( 'view' );
1436 $outputPage->setIndexPolicy( $policy['index'] );
1437 $outputPage->setFollowPolicy( $policy['follow'] );
1438
1439 $hookResult = Hooks::run( 'BeforeDisplayNoArticleText', [ $this ] );
1440
1441 if ( !$hookResult ) {
1442 return;
1443 }
1444
1445 # Show error message
1446 $oldid = $this->getOldID();
1447 if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1448 // use fake Content object for system message
1449 $parserOptions = ParserOptions::newCanonical( 'canonical' );
1450 $outputPage->addParserOutput( $this->getEmptyPageParserOutput( $parserOptions ) );
1451 } else {
1452 if ( $oldid ) {
1453 $text = wfMessage( 'missing-revision', $oldid )->plain();
1454 } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
1455 && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
1456 ) {
1457 $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1458 $text = wfMessage( $message )->plain();
1459 } else {
1460 $text = wfMessage( 'noarticletext-nopermission' )->plain();
1461 }
1462
1463 $dir = $this->getContext()->getLanguage()->getDir();
1464 $lang = $this->getContext()->getLanguage()->getHtmlCode();
1465 $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1466 'class' => "noarticletext mw-content-$dir",
1467 'dir' => $dir,
1468 'lang' => $lang,
1469 ] ) . "\n$text\n</div>" );
1470 }
1471 }
1472
1479 public function showDeletedRevisionHeader() {
1480 if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
1481 // Not deleted
1482 return true;
1483 }
1484
1485 $outputPage = $this->getContext()->getOutput();
1486 $user = $this->getContext()->getUser();
1487 // If the user is not allowed to see it...
1488 if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
1489 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1490 'rev-deleted-text-permission' );
1491
1492 return false;
1493 // If the user needs to confirm that they want to see it...
1494 } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1495 # Give explanation and add a link to view the revision...
1496 $oldid = intval( $this->getOldID() );
1497 $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1498 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1499 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1500 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1501 [ $msg, $link ] );
1502
1503 return false;
1504 // We are allowed to see...
1505 } else {
1506 $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1507 'rev-suppressed-text-view' : 'rev-deleted-text-view';
1508 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1509
1510 return true;
1511 }
1512 }
1513
1522 public function setOldSubtitle( $oldid = 0 ) {
1523 // Avoid PHP 7.1 warning of passing $this by reference
1524 $articlePage = $this;
1525
1526 if ( !Hooks::run( 'DisplayOldSubtitle', [ &$articlePage, &$oldid ] ) ) {
1527 return;
1528 }
1529
1530 $context = $this->getContext();
1531 $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1532
1533 # Cascade unhide param in links for easy deletion browsing
1534 $extraParams = [];
1535 if ( $unhide ) {
1536 $extraParams['unhide'] = 1;
1537 }
1538
1539 if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1540 $revision = $this->mRevision;
1541 } else {
1542 $revision = Revision::newFromId( $oldid );
1543 }
1544
1545 $timestamp = $revision->getTimestamp();
1546
1547 $current = ( $oldid == $this->mPage->getLatest() );
1548 $language = $context->getLanguage();
1549 $user = $context->getUser();
1550
1551 $td = $language->userTimeAndDate( $timestamp, $user );
1552 $tddate = $language->userDate( $timestamp, $user );
1553 $tdtime = $language->userTime( $timestamp, $user );
1554
1555 # Show user links if allowed to see them. If hidden, then show them only if requested...
1556 $userlinks = Linker::revUserTools( $revision, !$unhide );
1557
1558 $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1559 ? 'revision-info-current'
1560 : 'revision-info';
1561
1562 $outputPage = $context->getOutput();
1563 $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1564 $context->msg( $infomsg, $td )
1565 ->rawParams( $userlinks )
1566 ->params( $revision->getId(), $tddate, $tdtime, $revision->getUserText() )
1567 ->rawParams( Linker::revComment( $revision, true, true ) )
1568 ->parse() .
1569 "</div>";
1570
1571 $lnk = $current
1572 ? $context->msg( 'currentrevisionlink' )->escaped()
1574 $this->getTitle(),
1575 $context->msg( 'currentrevisionlink' )->escaped(),
1576 [],
1577 $extraParams
1578 );
1579 $curdiff = $current
1580 ? $context->msg( 'diff' )->escaped()
1582 $this->getTitle(),
1583 $context->msg( 'diff' )->escaped(),
1584 [],
1585 [
1586 'diff' => 'cur',
1587 'oldid' => $oldid
1588 ] + $extraParams
1589 );
1590 $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
1591 $prevlink = $prev
1593 $this->getTitle(),
1594 $context->msg( 'previousrevision' )->escaped(),
1595 [],
1596 [
1597 'direction' => 'prev',
1598 'oldid' => $oldid
1599 ] + $extraParams
1600 )
1601 : $context->msg( 'previousrevision' )->escaped();
1602 $prevdiff = $prev
1604 $this->getTitle(),
1605 $context->msg( 'diff' )->escaped(),
1606 [],
1607 [
1608 'diff' => 'prev',
1609 'oldid' => $oldid
1610 ] + $extraParams
1611 )
1612 : $context->msg( 'diff' )->escaped();
1613 $nextlink = $current
1614 ? $context->msg( 'nextrevision' )->escaped()
1616 $this->getTitle(),
1617 $context->msg( 'nextrevision' )->escaped(),
1618 [],
1619 [
1620 'direction' => 'next',
1621 'oldid' => $oldid
1622 ] + $extraParams
1623 );
1624 $nextdiff = $current
1625 ? $context->msg( 'diff' )->escaped()
1627 $this->getTitle(),
1628 $context->msg( 'diff' )->escaped(),
1629 [],
1630 [
1631 'diff' => 'next',
1632 'oldid' => $oldid
1633 ] + $extraParams
1634 );
1635
1636 $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
1637 if ( $cdel !== '' ) {
1638 $cdel .= ' ';
1639 }
1640
1641 // the outer div is need for styling the revision info and nav in MobileFrontend
1642 $outputPage->addSubtitle( "<div class=\"mw-revision\">" . $revisionInfo .
1643 "<div id=\"mw-revision-nav\">" . $cdel .
1644 $context->msg( 'revision-nav' )->rawParams(
1645 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1646 )->escaped() . "</div></div>" );
1647 }
1648
1662 public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1663 $lang = $this->getTitle()->getPageLanguage();
1664 $out = $this->getContext()->getOutput();
1665 if ( $appendSubtitle ) {
1666 $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1667 }
1668 $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1669 return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1670 }
1671
1684 public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1685 if ( !is_array( $target ) ) {
1686 $target = [ $target ];
1687 }
1688
1689 $html = '<ul class="redirectText">';
1691 foreach ( $target as $title ) {
1692 $html .= '<li>' . Linker::link(
1693 $title,
1694 htmlspecialchars( $title->getFullText() ),
1695 [],
1696 // Make sure wiki page redirects are not followed
1697 $title->isRedirect() ? [ 'redirect' => 'no' ] : [],
1698 ( $forceKnown ? [ 'known', 'noclasses' ] : [] )
1699 ) . '</li>';
1700 }
1701 $html .= '</ul>';
1702
1703 $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1704
1705 return '<div class="redirectMsg">' .
1706 '<p>' . $redirectToText . '</p>' .
1707 $html .
1708 '</div>';
1709 }
1710
1719 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1720 $msg = wfMessage(
1721 'namespace-' . $this->getTitle()->getNamespace() . '-helppage'
1722 );
1723
1724 $out = $this->getContext()->getOutput();
1725 if ( !$msg->isDisabled() ) {
1726 $helpUrl = Skin::makeUrl( $msg->plain() );
1727 $out->addHelpLink( $helpUrl, true );
1728 } else {
1729 $out->addHelpLink( $to, $overrideBaseUrl );
1730 }
1731 }
1732
1736 public function render() {
1737 $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1738 $this->getContext()->getOutput()->setArticleBodyOnly( true );
1739 // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1740 $this->viewIsRenderAction = true;
1741 $this->view();
1742 }
1743
1747 public function protect() {
1748 $form = new ProtectionForm( $this );
1749 $form->execute();
1750 }
1751
1755 public function unprotect() {
1756 $this->protect();
1757 }
1758
1762 public function delete() {
1763 # This code desperately needs to be totally rewritten
1764
1765 $title = $this->getTitle();
1766 $context = $this->getContext();
1767 $user = $context->getUser();
1768 $request = $context->getRequest();
1769
1770 # Check permissions
1771 $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
1772 if ( count( $permissionErrors ) ) {
1773 throw new PermissionsError( 'delete', $permissionErrors );
1774 }
1775
1776 # Read-only check...
1777 if ( wfReadOnly() ) {
1778 throw new ReadOnlyError;
1779 }
1780
1781 # Better double-check that it hasn't been deleted yet!
1782 $this->mPage->loadPageData(
1783 $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1784 );
1785 if ( !$this->mPage->exists() ) {
1786 $deleteLogPage = new LogPage( 'delete' );
1787 $outputPage = $context->getOutput();
1788 $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1789 $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1790 [ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1791 );
1792 $outputPage->addHTML(
1793 Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1794 );
1796 $outputPage,
1797 'delete',
1798 $title
1799 );
1800
1801 return;
1802 }
1803
1804 $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1805 $deleteReason = $request->getText( 'wpReason' );
1806
1807 if ( $deleteReasonList == 'other' ) {
1808 $reason = $deleteReason;
1809 } elseif ( $deleteReason != '' ) {
1810 // Entry from drop down menu + additional comment
1811 $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1812 $reason = $deleteReasonList . $colonseparator . $deleteReason;
1813 } else {
1814 $reason = $deleteReasonList;
1815 }
1816
1817 if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1818 [ 'delete', $this->getTitle()->getPrefixedText() ] )
1819 ) {
1820 # Flag to hide all contents of the archived revisions
1821 $suppress = $request->getCheck( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
1822
1823 $this->doDelete( $reason, $suppress );
1824
1825 WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1826
1827 return;
1828 }
1829
1830 // Generate deletion reason
1831 $hasHistory = false;
1832 if ( !$reason ) {
1833 try {
1834 $reason = $this->generateReason( $hasHistory );
1835 } catch ( Exception $e ) {
1836 # if a page is horribly broken, we still want to be able to
1837 # delete it. So be lenient about errors here.
1838 wfDebug( "Error while building auto delete summary: $e" );
1839 $reason = '';
1840 }
1841 }
1842
1843 // If the page has a history, insert a warning
1844 if ( $hasHistory ) {
1845 $title = $this->getTitle();
1846
1847 // The following can use the real revision count as this is only being shown for users
1848 // that can delete this page.
1849 // This, as a side-effect, also makes sure that the following query isn't being run for
1850 // pages with a larger history, unless the user has the 'bigdelete' right
1851 // (and is about to delete this page).
1852 $dbr = wfGetDB( DB_REPLICA );
1853 $revisions = $edits = (int)$dbr->selectField(
1854 'revision',
1855 'COUNT(rev_page)',
1856 [ 'rev_page' => $title->getArticleID() ],
1857 __METHOD__
1858 );
1859
1860 // @todo i18n issue/patchwork message
1861 $context->getOutput()->addHTML(
1862 '<strong class="mw-delete-warning-revisions">' .
1863 $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1864 $context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
1865 $context->msg( 'history' )->escaped(),
1866 [],
1867 [ 'action' => 'history' ] ) .
1868 '</strong>'
1869 );
1870
1871 if ( $title->isBigDeletion() ) {
1873 $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1874 [
1875 'delete-warning-toobig',
1876 $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1877 ]
1878 );
1879 }
1880 }
1881
1882 $this->confirmDelete( $reason );
1883 }
1884
1890 public function confirmDelete( $reason ) {
1891 wfDebug( "Article::confirmDelete\n" );
1892
1893 $title = $this->getTitle();
1894 $ctx = $this->getContext();
1895 $outputPage = $ctx->getOutput();
1896 $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1897 $outputPage->addBacklinkSubtitle( $title );
1898 $outputPage->setRobotPolicy( 'noindex,nofollow' );
1899 $outputPage->addModules( 'mediawiki.action.delete' );
1900
1901 $backlinkCache = $title->getBacklinkCache();
1902 if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
1903 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1904 'deleting-backlinks-warning' );
1905 }
1906
1907 $subpageQueryLimit = 51;
1908 $subpages = $title->getSubpages( $subpageQueryLimit );
1909 $subpageCount = count( $subpages );
1910 if ( $subpageCount > 0 ) {
1911 $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1912 [ 'deleting-subpages-warning', Message::numParam( $subpageCount ) ] );
1913 }
1914 $outputPage->addWikiMsg( 'confirmdeletetext' );
1915
1916 Hooks::run( 'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
1917
1918 $user = $this->getContext()->getUser();
1919 $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
1920
1921 $outputPage->enableOOUI();
1922
1923 $options = Xml::listDropDownOptions(
1924 $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text(),
1925 [ 'other' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text() ]
1926 );
1927 $options = Xml::listDropDownOptionsOoui( $options );
1928
1929 $fields[] = new OOUI\FieldLayout(
1930 new OOUI\DropdownInputWidget( [
1931 'name' => 'wpDeleteReasonList',
1932 'inputId' => 'wpDeleteReasonList',
1933 'tabIndex' => 1,
1934 'infusable' => true,
1935 'value' => '',
1936 'options' => $options
1937 ] ),
1938 [
1939 'label' => $ctx->msg( 'deletecomment' )->text(),
1940 'align' => 'top',
1941 ]
1942 );
1943
1944 // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
1945 // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
1946 // Unicode codepoints.
1947 $fields[] = new OOUI\FieldLayout(
1948 new OOUI\TextInputWidget( [
1949 'name' => 'wpReason',
1950 'inputId' => 'wpReason',
1951 'tabIndex' => 2,
1952 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
1953 'infusable' => true,
1954 'value' => $reason,
1955 'autofocus' => true,
1956 ] ),
1957 [
1958 'label' => $ctx->msg( 'deleteotherreason' )->text(),
1959 'align' => 'top',
1960 ]
1961 );
1962
1963 if ( $user->isLoggedIn() ) {
1964 $fields[] = new OOUI\FieldLayout(
1965 new OOUI\CheckboxInputWidget( [
1966 'name' => 'wpWatch',
1967 'inputId' => 'wpWatch',
1968 'tabIndex' => 3,
1969 'selected' => $checkWatch,
1970 ] ),
1971 [
1972 'label' => $ctx->msg( 'watchthis' )->text(),
1973 'align' => 'inline',
1974 'infusable' => true,
1975 ]
1976 );
1977 }
1978
1979 if ( $user->isAllowed( 'suppressrevision' ) ) {
1980 $fields[] = new OOUI\FieldLayout(
1981 new OOUI\CheckboxInputWidget( [
1982 'name' => 'wpSuppress',
1983 'inputId' => 'wpSuppress',
1984 'tabIndex' => 4,
1985 ] ),
1986 [
1987 'label' => $ctx->msg( 'revdelete-suppress' )->text(),
1988 'align' => 'inline',
1989 'infusable' => true,
1990 ]
1991 );
1992 }
1993
1994 $fields[] = new OOUI\FieldLayout(
1995 new OOUI\ButtonInputWidget( [
1996 'name' => 'wpConfirmB',
1997 'inputId' => 'wpConfirmB',
1998 'tabIndex' => 5,
1999 'value' => $ctx->msg( 'deletepage' )->text(),
2000 'label' => $ctx->msg( 'deletepage' )->text(),
2001 'flags' => [ 'primary', 'destructive' ],
2002 'type' => 'submit',
2003 ] ),
2004 [
2005 'align' => 'top',
2006 ]
2007 );
2008
2009 $fieldset = new OOUI\FieldsetLayout( [
2010 'label' => $ctx->msg( 'delete-legend' )->text(),
2011 'id' => 'mw-delete-table',
2012 'items' => $fields,
2013 ] );
2014
2015 $form = new OOUI\FormLayout( [
2016 'method' => 'post',
2017 'action' => $title->getLocalURL( 'action=delete' ),
2018 'id' => 'deleteconfirm',
2019 ] );
2020 $form->appendContent(
2021 $fieldset,
2022 new OOUI\HtmlSnippet(
2023 Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
2024 )
2025 );
2026
2027 $outputPage->addHTML(
2028 new OOUI\PanelLayout( [
2029 'classes' => [ 'deletepage-wrapper' ],
2030 'expanded' => false,
2031 'padded' => true,
2032 'framed' => true,
2033 'content' => $form,
2034 ] )
2035 );
2036
2037 if ( $user->isAllowed( 'editinterface' ) ) {
2039 $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
2040 wfMessage( 'delete-edit-reasonlist' )->escaped(),
2041 [],
2042 [ 'action' => 'edit' ]
2043 );
2044 $outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
2045 }
2046
2047 $deleteLogPage = new LogPage( 'delete' );
2048 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2049 LogEventsList::showLogExtract( $outputPage, 'delete', $title );
2050 }
2051
2060 public function doDelete( $reason, $suppress = false, $immediate = false ) {
2061 $error = '';
2062 $context = $this->getContext();
2063 $outputPage = $context->getOutput();
2064 $user = $context->getUser();
2065 $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error, $user,
2066 [], 'delete', $immediate );
2067
2068 if ( $status->isOK() ) {
2069 $deleted = $this->getTitle()->getPrefixedText();
2070
2071 $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
2072 $outputPage->setRobotPolicy( 'noindex,nofollow' );
2073
2074 if ( $status->isGood() ) {
2075 $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
2076 $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
2077 Hooks::run( 'ArticleDeleteAfterSuccess', [ $this->getTitle(), $outputPage ] );
2078 } else {
2079 $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
2080 }
2081
2082 $outputPage->returnToMain( false );
2083 } else {
2084 $outputPage->setPageTitle(
2085 wfMessage( 'cannotdelete-title',
2086 $this->getTitle()->getPrefixedText() )
2087 );
2088
2089 if ( $error == '' ) {
2090 $outputPage->wrapWikiTextAsInterface(
2091 'error mw-error-cannotdelete',
2092 $status->getWikiText()
2093 );
2094 $deleteLogPage = new LogPage( 'delete' );
2095 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2096
2098 $outputPage,
2099 'delete',
2100 $this->getTitle()
2101 );
2102 } else {
2103 $outputPage->addHTML( $error );
2104 }
2105 }
2106 }
2107
2108 /* Caching functions */
2109
2117 protected function tryFileCache() {
2118 static $called = false;
2119
2120 if ( $called ) {
2121 wfDebug( "Article::tryFileCache(): called twice!?\n" );
2122 return false;
2123 }
2124
2125 $called = true;
2126 if ( $this->isFileCacheable() ) {
2127 $cache = new HTMLFileCache( $this->getTitle(), 'view' );
2128 if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
2129 wfDebug( "Article::tryFileCache(): about to load file\n" );
2130 $cache->loadFromFileCache( $this->getContext() );
2131 return true;
2132 } else {
2133 wfDebug( "Article::tryFileCache(): starting buffer\n" );
2134 ob_start( [ &$cache, 'saveToFileCache' ] );
2135 }
2136 } else {
2137 wfDebug( "Article::tryFileCache(): not cacheable\n" );
2138 }
2139
2140 return false;
2141 }
2142
2148 public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
2149 $cacheable = false;
2150
2151 if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
2152 $cacheable = $this->mPage->getId()
2153 && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
2154 // Extension may have reason to disable file caching on some pages.
2155 if ( $cacheable ) {
2156 // Avoid PHP 7.1 warning of passing $this by reference
2157 $articlePage = $this;
2158 $cacheable = Hooks::run( 'IsFileCacheable', [ &$articlePage ] );
2159 }
2160 }
2161
2162 return $cacheable;
2163 }
2164
2178 public function getParserOutput( $oldid = null, User $user = null ) {
2179 // XXX: bypasses mParserOptions and thus setParserOptions()
2180
2181 if ( $user === null ) {
2182 $parserOptions = $this->getParserOptions();
2183 } else {
2184 $parserOptions = $this->mPage->makeParserOptions( $user );
2185 }
2186
2187 return $this->mPage->getParserOutput( $parserOptions, $oldid );
2188 }
2189
2197 if ( $this->mParserOptions ) {
2198 throw new MWException( "can't change parser options after they have already been set" );
2199 }
2200
2201 // clone, so if $options is modified later, it doesn't confuse the parser cache.
2202 $this->mParserOptions = clone $options;
2203 }
2204
2209 public function getParserOptions() {
2210 if ( !$this->mParserOptions ) {
2211 $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
2212 }
2213 // Clone to allow modifications of the return value without affecting cache
2214 return clone $this->mParserOptions;
2215 }
2216
2223 public function setContext( $context ) {
2224 $this->mContext = $context;
2225 }
2226
2233 public function getContext() {
2234 if ( $this->mContext instanceof IContextSource ) {
2235 return $this->mContext;
2236 } else {
2237 wfDebug( __METHOD__ . " called and \$mContext is null. " .
2238 "Return RequestContext::getMain(); for sanity\n" );
2239 return RequestContext::getMain();
2240 }
2241 }
2242
2250 public function __get( $fname ) {
2251 if ( property_exists( $this->mPage, $fname ) ) {
2252 # wfWarn( "Access to raw $fname field " . __CLASS__ );
2253 return $this->mPage->$fname;
2254 }
2255 trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2256 }
2257
2265 public function __set( $fname, $fvalue ) {
2266 if ( property_exists( $this->mPage, $fname ) ) {
2267 # wfWarn( "Access to raw $fname field of " . __CLASS__ );
2268 $this->mPage->$fname = $fvalue;
2269 // Note: extensions may want to toss on new fields
2270 } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2271 $this->mPage->$fname = $fvalue;
2272 } else {
2273 trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2274 }
2275 }
2276
2281 public function checkFlags( $flags ) {
2282 return $this->mPage->checkFlags( $flags );
2283 }
2284
2289 public function checkTouched() {
2290 return $this->mPage->checkTouched();
2291 }
2292
2297 public function clearPreparedEdit() {
2298 $this->mPage->clearPreparedEdit();
2299 }
2300
2305 public function doDeleteArticleReal(
2306 $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
2307 $tags = [], $immediate = false
2308 ) {
2309 return $this->mPage->doDeleteArticleReal(
2310 $reason, $suppress, $u1, $u2, $error, $user, $tags, 'delete', $immediate
2311 );
2312 }
2313
2318 public function doDeleteUpdates(
2319 $id,
2320 Content $content = null,
2321 $revision = null,
2322 User $user = null
2323 ) {
2324 $this->mPage->doDeleteUpdates( $id, $content, $revision, $user );
2325 }
2326
2332 public function doEditContent( Content $content, $summary, $flags = 0, $originalRevId = false,
2333 User $user = null, $serialFormat = null
2334 ) {
2335 wfDeprecated( __METHOD__, '1.29' );
2336 return $this->mPage->doEditContent( $content, $summary, $flags, $originalRevId,
2337 $user, $serialFormat
2338 );
2339 }
2340
2345 public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2346 return $this->mPage->doEditUpdates( $revision, $user, $options );
2347 }
2348
2355 public function doPurge() {
2356 return $this->mPage->doPurge();
2357 }
2358
2363 public function doViewUpdates( User $user, $oldid = 0 ) {
2364 $this->mPage->doViewUpdates( $user, $oldid );
2365 }
2366
2371 public function exists() {
2372 return $this->mPage->exists();
2373 }
2374
2379 public function followRedirect() {
2380 return $this->mPage->followRedirect();
2381 }
2382
2387 public function getActionOverrides() {
2388 return $this->mPage->getActionOverrides();
2389 }
2390
2395 public function getAutoDeleteReason( &$hasHistory ) {
2396 return $this->mPage->getAutoDeleteReason( $hasHistory );
2397 }
2398
2403 public function getCategories() {
2404 return $this->mPage->getCategories();
2405 }
2406
2411 public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2412 return $this->mPage->getComment( $audience, $user );
2413 }
2414
2419 public function getContentHandler() {
2420 return $this->mPage->getContentHandler();
2421 }
2422
2427 public function getContentModel() {
2428 return $this->mPage->getContentModel();
2429 }
2430
2435 public function getContributors() {
2436 return $this->mPage->getContributors();
2437 }
2438
2443 public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2444 return $this->mPage->getCreator( $audience, $user );
2445 }
2446
2451 public function getDeletionUpdates( Content $content = null ) {
2452 return $this->mPage->getDeletionUpdates( $content );
2453 }
2454
2459 public function getHiddenCategories() {
2460 return $this->mPage->getHiddenCategories();
2461 }
2462
2467 public function getId() {
2468 return $this->mPage->getId();
2469 }
2470
2475 public function getLatest() {
2476 return $this->mPage->getLatest();
2477 }
2478
2483 public function getLinksTimestamp() {
2484 return $this->mPage->getLinksTimestamp();
2485 }
2486
2491 public function getMinorEdit() {
2492 return $this->mPage->getMinorEdit();
2493 }
2494
2499 public function getOldestRevision() {
2500 return $this->mPage->getOldestRevision();
2501 }
2502
2507 public function getRedirectTarget() {
2508 return $this->mPage->getRedirectTarget();
2509 }
2510
2515 public function getRedirectURL( $rt ) {
2516 return $this->mPage->getRedirectURL( $rt );
2517 }
2518
2523 public function getRevision() {
2524 return $this->mPage->getRevision();
2525 }
2526
2531 public function getTimestamp() {
2532 return $this->mPage->getTimestamp();
2533 }
2534
2539 public function getTouched() {
2540 return $this->mPage->getTouched();
2541 }
2542
2547 public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2548 return $this->mPage->getUndoContent( $undo, $undoafter );
2549 }
2550
2555 public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2556 return $this->mPage->getUser( $audience, $user );
2557 }
2558
2563 public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2564 return $this->mPage->getUserText( $audience, $user );
2565 }
2566
2571 public function hasViewableContent() {
2572 return $this->mPage->hasViewableContent();
2573 }
2574
2579 public function insertOn( $dbw, $pageId = null ) {
2580 return $this->mPage->insertOn( $dbw, $pageId );
2581 }
2582
2587 public function insertProtectNullRevision( $revCommentMsg, array $limit,
2588 array $expiry, $cascade, $reason, $user = null
2589 ) {
2590 return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
2591 $expiry, $cascade, $reason, $user
2592 );
2593 }
2594
2599 public function insertRedirect() {
2600 return $this->mPage->insertRedirect();
2601 }
2602
2607 public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2608 return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2609 }
2610
2615 public function isCountable( $editInfo = false ) {
2616 return $this->mPage->isCountable( $editInfo );
2617 }
2618
2623 public function isRedirect() {
2624 return $this->mPage->isRedirect();
2625 }
2626
2631 public function loadFromRow( $data, $from ) {
2632 return $this->mPage->loadFromRow( $data, $from );
2633 }
2634
2639 public function loadPageData( $from = 'fromdb' ) {
2640 $this->mPage->loadPageData( $from );
2641 }
2642
2647 public function lockAndGetLatest() {
2648 return $this->mPage->lockAndGetLatest();
2649 }
2650
2655 public function makeParserOptions( $context ) {
2656 return $this->mPage->makeParserOptions( $context );
2657 }
2658
2663 public function pageDataFromId( $dbr, $id, $options = [] ) {
2664 return $this->mPage->pageDataFromId( $dbr, $id, $options );
2665 }
2666
2671 public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2672 return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2673 }
2674
2679 public function prepareContentForEdit(
2680 Content $content, $revision = null, User $user = null,
2681 $serialFormat = null, $useCache = true
2682 ) {
2683 return $this->mPage->prepareContentForEdit(
2684 $content, $revision, $user,
2685 $serialFormat, $useCache
2686 );
2687 }
2688
2693 public function protectDescription( array $limit, array $expiry ) {
2694 return $this->mPage->protectDescription( $limit, $expiry );
2695 }
2696
2701 public function protectDescriptionLog( array $limit, array $expiry ) {
2702 return $this->mPage->protectDescriptionLog( $limit, $expiry );
2703 }
2704
2709 public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2710 $sectionTitle = '', $baseRevId = null
2711 ) {
2712 return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2713 $sectionTitle, $baseRevId
2714 );
2715 }
2716
2721 public function replaceSectionContent(
2722 $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2723 ) {
2724 return $this->mPage->replaceSectionContent(
2725 $sectionId, $sectionContent, $sectionTitle, $edittime
2726 );
2727 }
2728
2733 public function setTimestamp( $ts ) {
2734 $this->mPage->setTimestamp( $ts );
2735 }
2736
2741 public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2742 return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2743 }
2744
2749 public function supportsSections() {
2750 return $this->mPage->supportsSections();
2751 }
2752
2757 public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2758 return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2759 }
2760
2765 public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
2766 return $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2767 }
2768
2773 public function updateIfNewerOn( $dbw, $revision ) {
2774 return $this->mPage->updateIfNewerOn( $dbw, $revision );
2775 }
2776
2781 public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
2782 return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect );
2783 }
2784
2789 public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
2790 $lastRevIsRedirect = null
2791 ) {
2792 return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
2793 $lastRevIsRedirect
2794 );
2795 }
2796
2805 public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
2806 $reason, User $user
2807 ) {
2808 return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2809 }
2810
2818 public function updateRestrictions( $limit = [], $reason = '',
2819 &$cascade = 0, $expiry = []
2820 ) {
2821 return $this->mPage->doUpdateRestrictions(
2822 $limit,
2823 $expiry,
2824 $cascade,
2825 $reason,
2826 $this->getContext()->getUser()
2827 );
2828 }
2829
2841 public function doDeleteArticle(
2842 $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', $immediate = false
2843 ) {
2844 return $this->mPage->doDeleteArticle( $reason, $suppress, $u1, $u2, $error,
2845 null, $immediate );
2846 }
2847
2857 public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
2858 if ( !$user ) {
2859 $user = $this->getContext()->getUser();
2860 }
2861
2862 return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2863 }
2864
2873 public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
2874 if ( !$guser ) {
2875 $guser = $this->getContext()->getUser();
2876 }
2877
2878 return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2879 }
2880
2885 public function generateReason( &$hasHistory ) {
2886 $title = $this->mPage->getTitle();
2887 $handler = ContentHandler::getForTitle( $title );
2888 return $handler->getAutoDeleteReason( $title, $hasHistory );
2889 }
2890
2891 // ******
2892}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$wgArticleRobotPolicies
Robot policies per article.
$wgDefaultRobotPolicy
Default robot policy.
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code,...
$wgRedirectSources
If local interwikis are set up which allow redirects, set this regexp to restrict URLs which will be ...
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
$wgNamespaceRobotPolicies
Robot policies per namespaces.
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
$wgDebugToolbar
Display the new debugging toolbar.
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition Setup.php:123
Class for viewing MediaWiki article and history.
Definition Article.php:37
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2741
getRevisionFetched()
Get the fetched Revision object depending on request parameters or null on failure.
Definition Article.php:558
doDeleteUpdates( $id, Content $content=null, $revision=null, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2318
updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2781
generateReason(&$hasHistory)
Definition Article.php:2885
getSubstituteContent()
Returns Content object to use when the page does not exist.
Definition Article.php:285
checkFlags( $flags)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2281
checkTouched()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2289
getHiddenCategories()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2459
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition Article.php:191
doViewUpdates(User $user, $oldid=0)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2363
getContext()
Gets the context this Article is executed in.
Definition Article.php:2233
getCategories()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2403
commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser=null)
Definition Article.php:2873
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition Article.php:338
getParserOutput( $oldid=null, User $user=null)
#-
Definition Article.php:2178
insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason, $user=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2587
doEditContent(Content $content, $summary, $flags=0, $originalRevId=false, User $user=null, $serialFormat=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2332
doPurge()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2355
getRedirectedFrom()
Get the page this view was redirected from.
Definition Article.php:202
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition Article.php:79
getUserText( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2563
insertOn( $dbw, $pageId=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2579
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2789
bool $viewIsRenderAction
Whether render() was called.
Definition Article.php:122
fetchContentObject()
Get text content object Does NOT follow redirects.
Definition Article.php:399
supportsSections()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2749
pageDataFromTitle( $dbr, $title, $options=[])
Call to WikiPage function for backwards compatibility.
Definition Article.php:2671
getContentHandler()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2419
updateCategoryCounts(array $added, array $deleted, $id=0)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2765
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition Article.php:586
loadPageData( $from='fromdb')
Call to WikiPage function for backwards compatibility.
Definition Article.php:2639
doDeleteArticleReal( $reason, $suppress=false, $u1=null, $u2=null, &$error='', User $user=null, $tags=[], $immediate=false)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2305
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user)
Definition Article.php:2805
protectDescriptionLog(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2701
WikiPage null $mPage
The WikiPage object of this instance.
Definition Article.php:45
clearPreparedEdit()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2297
__construct(Title $title, $oldId=null)
Constructor and clear the article.
Definition Article.php:129
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition Article.php:947
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition Article.php:1337
followRedirect()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2379
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2679
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition Article.php:2060
replaceSectionAtRev( $sectionId, Content $sectionContent, $sectionTitle='', $baseRevId=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2709
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition Article.php:115
getAutoDeleteReason(&$hasHistory)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2395
getLinksTimestamp()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2483
getOldID()
Definition Article.php:325
protectDescription(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2693
getOldestRevision()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2499
setTimestamp( $ts)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2733
getTitle()
Get the title object of the article.
Definition Article.php:220
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2387
doEditUpdates(Revision $revision, User $user, array $options=[])
Call to WikiPage function for backwards compatibility.
Definition Article.php:2345
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition Article.php:882
updateIfNewerOn( $dbw, $revision)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2773
getLatest()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2475
pageDataFromId( $dbr, $id, $options=[])
Call to WikiPage function for backwards compatibility.
Definition Article.php:2663
insertRedirect()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2599
getContributors()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2435
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition Article.php:1479
isCountable( $editInfo=false)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2615
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition Article.php:2209
getCreator( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2443
clear()
Clear the object.
Definition Article.php:237
getComment( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2411
IContextSource null $mContext
The context this Article is executed in.
Definition Article.php:42
getContentModel()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2427
Revision null $mRevision
Revision to be shown.
Definition Article.php:108
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition Article.php:1684
protect()
action=protect handler
Definition Article.php:1747
lockAndGetLatest()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2647
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
Definition Article.php:540
updateRestrictions( $limit=[], $reason='', &$cascade=0, $expiry=[])
Definition Article.php:2818
showMissingArticle()
Show the error text for a missing article.
Definition Article.php:1346
getUndoContent(Revision $undo, Revision $undoafter=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2547
__set( $fname, $fvalue)
Use PHP's magic __set handler to handle setting of raw WikiPage fields for backwards compatibility.
Definition Article.php:2265
getId()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2467
unprotect()
action=unprotect handler (alias)
Definition Article.php:1755
int $mRevIdFetched
Revision ID of revision that was loaded.
Definition Article.php:89
applyContentOverride(Content $override)
Applies a content override by constructing a fake Revision object and assigning it to mRevision.
Definition Article.php:524
isRedirect()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2623
ParserOptions null $mParserOptions
ParserOptions object for $wgUser articles.
Definition Article.php:51
newPage(Title $title)
Definition Article.php:138
makeFetchErrorContent()
Returns a Content object representing any error in $this->fetchContent, or null if there is no such e...
Definition Article.php:504
confirmDelete( $reason)
Output deletion confirmation dialog.
Definition Article.php:1890
getPage()
Get the WikiPage object of this instance.
Definition Article.php:230
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition Article.php:1719
getTouched()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2539
getRevision()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2523
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition Article.php:82
getTimestamp()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2531
static newFromID( $id)
Constructor from a page id.
Definition Article.php:147
getRedirectTarget()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2507
getUser( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2555
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition Article.php:76
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition Article.php:1027
fetchRevisionRecord()
Fetches the revision to work on.
Definition Article.php:416
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition Article.php:1662
insertRedirectEntry(Title $rt, $oldLatest=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2607
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2757
makeParserOptions( $context)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2655
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition Article.php:1168
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition Article.php:1522
showViewFooter()
Show the footer section of an ordinary page view.
Definition Article.php:1145
Status null $fetchResult
represents the outcome of fetchRevisionRecord().
Definition Article.php:99
loadFromRow( $data, $from)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2631
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
Definition Article.php:211
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition Article.php:2148
doDeleteArticle( $reason, $suppress=false, $u1=null, $u2=null, &$error='', $immediate=false)
Definition Article.php:2841
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition Article.php:2117
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition Article.php:574
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2721
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition Article.php:1133
__get( $fname)
Use PHP's magic __get handler to handle accessing of raw WikiPage fields for backwards compatibility.
Definition Article.php:2250
getRevisionRedirectTarget(RevisionRecord $revision)
Definition Article.php:870
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition Article.php:159
hasViewableContent()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2571
getContentObject()
Returns a Content object representing the pages effective display content, not necessarily the revisi...
Definition Article.php:269
showDiffPage()
Show a diff page according to current request variables.
Definition Article.php:897
getMinorEdit()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2491
setParserOptions(ParserOptions $options)
Override the ParserOptions used to render the primary article wikitext.
Definition Article.php:2196
getEmptyPageParserOutput(ParserOptions $options)
Returns ParserOutput to use when a page does not exist.
Definition Article.php:312
render()
Handle action=render.
Definition Article.php:1736
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition Article.php:1056
exists()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2371
setContext( $context)
Sets the context this Article is executed in.
Definition Article.php:2223
getRedirectURL( $rt)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2515
bool $mContentLoaded
Is the target revision loaded? Set by fetchRevisionRecord().
Definition Article.php:69
getDeletionUpdates(Content $content=null)
Call to WikiPage function for backwards compatibility.
Definition Article.php:2451
doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user=null)
Definition Article.php:2857
Content null $mContentObject
Content of the main slot of $this->mRevision.
Definition Article.php:61
const TYPE_AUTO
Definition Block.php:99
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing Block object if possible.
Definition Block.php:1403
Special handling for category description pages, showing pages, subcategories and file that belong to...
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition EditPage.php:201
Page view caching in the file system.
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
Class for viewing MediaWiki file description pages.
Definition ImagePage.php:31
Internationalisation code.
Definition Language.php:36
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:84
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:146
static revComment(Revision $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition Linker.php:1510
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1086
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:842
static getRevDeleteLink(User $user, Revision $rev, Title $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition Linker.php:2049
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class to simplify the use of log pages.
Definition LogPage.php:33
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Mutable RevisionRecord implementation, for building new revision entries programmatically.
Page revision base class.
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
Value object representing a content slot associated with a page revision.
Wrapper allowing us to handle a system message as a Content object.
Set options of the Parser.
Show an error when a user tries to do something they do not have the necessary permissions for.
Handles the page protection UI and backend.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
const DELETED_TEXT
Definition Revision.php:46
const DELETED_RESTRICTED
Definition Revision.php:49
const FOR_PUBLIC
Definition Revision.php:54
const FOR_THIS_USER
Definition Revision.php:55
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition Revision.php:118
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:40
Represents a title within MediaWiki.
Definition Title.php:40
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:585
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:967
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Class representing a MediaWiki article and history.
Definition WikiPage.php:45
getTitle()
Get the title object of the article.
Definition WikiPage.php:294
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
const NS_USER
Definition Defines.php:75
const NS_FILE
Definition Defines.php:79
const NS_MEDIAWIKI
Definition Defines.php:81
const RC_LOG
Definition Defines.php:153
const NS_MEDIA
Definition Defines.php:61
const NS_USER_TALK
Definition Defines.php:76
const NS_CATEGORY
Definition Defines.php:87
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 as strings Extensions should add to this list prev or next refreshes the diff cache $unhide
Definition hooks.txt:1629
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2843
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:855
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition hooks.txt:894
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition hooks.txt:1266
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:1999
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition hooks.txt:2848
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:955
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition hooks.txt:783
null for the local wiki Added in
Definition hooks.txt:1588
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition hooks.txt:2011
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3069
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used the name of the old file & $article
Definition hooks.txt:1580
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1617
return true to allow those checks to and false if checking is done & $user
Definition hooks.txt:1510
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1779
returning false will NOT prevent logging $e
Definition hooks.txt:2175
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
Base interface for content objects.
Definition Content.php:34
Interface for objects which can provide a MediaWiki context on request.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition Page.php:24
$cache
Definition mcc.php:33
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$content
const DB_REPLICA
Definition defines.php:25
if(!isset( $args[0])) $lang