MediaWiki REL1_37
Article.php
Go to the documentation of this file.
1<?php
24use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
36use Wikimedia\IPUtils;
37use Wikimedia\NonSerializable\NonSerializableTrait;
38
49class Article implements Page {
50 use ProtectedHookAccessorTrait;
51 use NonSerializableTrait;
52
58 protected $mContext;
59
61 protected $mPage;
62
67 public $mOldId;
68
70 public $mRedirectedFrom = null;
71
73 public $mRedirectUrl = false;
74
79 private $fetchResult = null;
80
86 public $mParserOutput = null;
87
93 protected $viewIsRenderAction = false;
94
98 protected $linkRenderer;
99
104
109
114
121 private $mRevisionRecord = null;
122
127 public function __construct( Title $title, $oldId = null ) {
128 $this->mOldId = $oldId;
129 $this->mPage = $this->newPage( $title );
130
131 $services = MediaWikiServices::getInstance();
132 $this->linkRenderer = $services->getLinkRenderer();
133 $this->revisionStore = $services->getRevisionStore();
134 $this->watchlistManager = $services->getWatchlistManager();
135 $this->userNameUtils = $services->getUserNameUtils();
136 }
137
142 protected function newPage( Title $title ) {
143 return new WikiPage( $title );
144 }
145
151 public static function newFromID( $id ) {
152 $t = Title::newFromID( $id );
153 return $t == null ? null : new static( $t );
154 }
155
163 public static function newFromTitle( $title, IContextSource $context ) {
164 if ( $title->getNamespace() === NS_MEDIA ) {
165 // XXX: This should not be here, but where should it go?
166 $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
167 }
168
169 $page = null;
170 Hooks::runner()->onArticleFromTitle( $title, $page, $context );
171 if ( !$page ) {
172 switch ( $title->getNamespace() ) {
173 case NS_FILE:
174 $page = new ImagePage( $title );
175 break;
176 case NS_CATEGORY:
177 $page = new CategoryPage( $title );
178 break;
179 default:
180 $page = new Article( $title );
181 }
182 }
183 $page->setContext( $context );
184
185 return $page;
186 }
187
195 public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
196 $article = self::newFromTitle( $page->getTitle(), $context );
197 $article->mPage = $page; // override to keep process cached vars
198 return $article;
199 }
200
206 public function getRedirectedFrom() {
207 return $this->mRedirectedFrom;
208 }
209
215 public function setRedirectedFrom( Title $from ) {
216 $this->mRedirectedFrom = $from;
217 }
218
224 public function getTitle() {
225 return $this->mPage->getTitle();
226 }
227
234 public function getPage() {
235 return $this->mPage;
236 }
237
238 public function clear() {
239 $this->mRedirectedFrom = null; # Title object if set
240 $this->mRedirectUrl = false;
241 $this->mRevisionRecord = null;
242 $this->fetchResult = null;
243
244 // TODO hard-deprecate direct access to public fields
245
246 $this->mPage->clear();
247 }
248
256 public function getOldID() {
257 if ( $this->mOldId === null ) {
258 $this->mOldId = $this->getOldIDFromRequest();
259 }
260
261 return $this->mOldId;
262 }
263
269 public function getOldIDFromRequest() {
270 $this->mRedirectUrl = false;
271
272 $request = $this->getContext()->getRequest();
273 $oldid = $request->getIntOrNull( 'oldid' );
274
275 if ( $oldid === null ) {
276 return 0;
277 }
278
279 if ( $oldid !== 0 ) {
280 # Load the given revision and check whether the page is another one.
281 # In that case, update this instance to reflect the change.
282 if ( $oldid === $this->mPage->getLatest() ) {
283 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
284 } else {
285 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
286 if ( $this->mRevisionRecord !== null ) {
287 $revPageId = $this->mRevisionRecord->getPageId();
288 // Revision title doesn't match the page title given?
289 if ( $this->mPage->getId() != $revPageId ) {
290 $function = get_class( $this->mPage ) . '::newFromID';
291 $this->mPage = $function( $revPageId );
292 }
293 }
294 }
295 }
296
297 $oldRev = $this->mRevisionRecord;
298 if ( $request->getRawVal( 'direction' ) === 'next' ) {
299 $nextid = 0;
300 if ( $oldRev ) {
301 $nextRev = $this->revisionStore->getNextRevision( $oldRev );
302 if ( $nextRev ) {
303 $nextid = $nextRev->getId();
304 }
305 }
306 if ( $nextid ) {
307 $oldid = $nextid;
308 $this->mRevisionRecord = null;
309 } else {
310 $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
311 }
312 } elseif ( $request->getRawVal( 'direction' ) === 'prev' ) {
313 $previd = 0;
314 if ( $oldRev ) {
315 $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
316 if ( $prevRev ) {
317 $previd = $prevRev->getId();
318 }
319 }
320 if ( $previd ) {
321 $oldid = $previd;
322 $this->mRevisionRecord = null;
323 }
324 }
325
326 return $oldid;
327 }
328
338 public function fetchRevisionRecord() {
339 if ( $this->fetchResult ) {
340 return $this->mRevisionRecord;
341 }
342
343 $oldid = $this->getOldID();
344
345 // $this->mRevisionRecord might already be fetched by getOldIDFromRequest()
346 if ( !$this->mRevisionRecord ) {
347 if ( !$oldid ) {
348 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
349
350 if ( !$this->mRevisionRecord ) {
351 wfDebug( __METHOD__ . " failed to find page data for title " .
352 $this->getTitle()->getPrefixedText() );
353
354 // Just for sanity, output for this case is done by showMissingArticle().
355 $this->fetchResult = Status::newFatal( 'noarticletext' );
356 return null;
357 }
358 } else {
359 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
360
361 if ( !$this->mRevisionRecord ) {
362 wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid" );
363
364 $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
365 return null;
366 }
367 }
368 }
369
370 if ( !$this->mRevisionRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getAuthority() ) ) {
371 wfDebug( __METHOD__ . " failed to retrieve content of revision " . $this->mRevisionRecord->getId() );
372
373 // Just for sanity, output for this case is done by showDeletedRevisionHeader().
374 // title used in wikilinks, should not contain whitespaces
375 $this->fetchResult = new Status;
376 $title = $this->getTitle()->getPrefixedDBkey();
377
378 if ( $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
379 $this->fetchResult->fatal( 'rev-suppressed-text' );
380 } else {
381 $this->fetchResult->fatal( 'rev-deleted-text-permission', $title );
382 }
383
384 return null;
385 }
386
387 $this->fetchResult = Status::newGood( $this->mRevisionRecord );
388 return $this->mRevisionRecord;
389 }
390
396 public function isCurrent() {
397 # If no oldid, this is the current version.
398 if ( $this->getOldID() == 0 ) {
399 return true;
400 }
401
402 return $this->mPage->exists() &&
403 $this->mRevisionRecord &&
404 $this->mRevisionRecord->isCurrent();
405 }
406
415 public function getRevIdFetched() {
416 if ( $this->fetchResult && $this->fetchResult->isOK() ) {
417 return $this->fetchResult->value->getId();
418 } else {
419 return $this->mPage->getLatest();
420 }
421 }
422
427 public function view() {
428 global $wgUseFileCache;
429
430 # Get variables from query string
431 # As side effect this will load the revision and update the title
432 # in a revision ID is passed in the request, so this should remain
433 # the first call of this method even if $oldid is used way below.
434 $oldid = $this->getOldID();
435
436 $user = $this->getContext()->getUser();
437 # Another check in case getOldID() is altering the title
438 $permissionStatus = PermissionStatus::newEmpty();
439 if ( !$this->getContext()->getAuthority()
440 ->authorizeRead( 'read', $this->getTitle(), $permissionStatus )
441 ) {
442 wfDebug( __METHOD__ . ": denied on secondary read check" );
443 throw new PermissionsError( 'read', $permissionStatus );
444 }
445
446 $outputPage = $this->getContext()->getOutput();
447 # getOldID() may as well want us to redirect somewhere else
448 if ( $this->mRedirectUrl ) {
449 $outputPage->redirect( $this->mRedirectUrl );
450 wfDebug( __METHOD__ . ": redirecting due to oldid" );
451
452 return;
453 }
454
455 # If we got diff in the query, we want to see a diff page instead of the article.
456 if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
457 wfDebug( __METHOD__ . ": showing diff page" );
458 $this->showDiffPage();
459
460 return;
461 }
462
463 # Set page title (may be overridden by DISPLAYTITLE)
464 $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
465
466 $outputPage->setArticleFlag( true );
467 # Allow frames by default
468 $outputPage->allowClickjacking();
469
470 $parserOptions = $this->getParserOptions();
471 $poOptions = [];
472 # Allow extensions to vary parser options used for article rendering
473 Hooks::runner()->onArticleParserOptions( $this, $parserOptions );
474 # Render printable version, use printable version cache
475 if ( $outputPage->isPrintable() ) {
476 $parserOptions->setIsPrintable( true );
477 $poOptions['enableSectionEditLinks'] = false;
478 $outputPage->prependHTML(
479 Html::warningBox(
480 $outputPage->msg( 'printableversion-deprecated-warning' )->escaped()
481 )
482 );
483 } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
484 !$this->getContext()->getAuthority()->probablyCan( 'edit', $this->getTitle() )
485 ) {
486 $poOptions['enableSectionEditLinks'] = false;
487 }
488
489 # Try client and file cache
490 if ( $oldid === 0 && $this->mPage->checkTouched() ) {
491 # Try to stream the output from file cache
492 if ( $wgUseFileCache && $this->tryFileCache() ) {
493 wfDebug( __METHOD__ . ": done file cache" );
494 # tell wgOut that output is taken care of
495 $outputPage->disable();
496 $this->mPage->doViewUpdates( $user, $oldid );
497
498 return;
499 }
500 }
501
503 $this->showNamespaceHeader();
504
505 $continue =
506 $this->generateContentOutput( $user, $parserOptions, $oldid, $outputPage, $poOptions );
507
508 if ( !$continue ) {
509 return;
510 }
511
512 # For the main page, overwrite the <title> element with the con-
513 # tents of 'pagetitle-view-mainpage' instead of the default (if
514 # that's not empty).
515 # This message always exists because it is in the i18n files
516 if ( $this->getTitle()->isMainPage() ) {
517 $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
518 if ( !$msg->isDisabled() ) {
519 $outputPage->setHTMLTitle( $msg->page( $this->getTitle() )->text() );
520 }
521 }
522
523 # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
524 # This could use getTouched(), but that could be scary for major template edits.
525 $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
526
527 $this->showViewFooter();
528 $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this
529
530 # Load the postEdit module if the user just saved this revision
531 # See also EditPage::setPostEditCookie
532 $request = $this->getContext()->getRequest();
534 $postEdit = $request->getCookie( $cookieKey );
535 if ( $postEdit ) {
536 # Clear the cookie. This also prevents caching of the response.
537 $request->response()->clearCookie( $cookieKey );
538 $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
539 $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
540 }
541 }
542
555 private function generateContentOutput(
556 Authority $performer,
557 ParserOptions $parserOptions,
558 int $oldid,
559 OutputPage $outputPage,
560 array $textOptions
561 ): bool {
562 # Should the parser cache be used?
563 $useParserCache = true;
564 $pOutput = null;
565 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
566
567 // NOTE: $outputDone and $useParserCache may be changed by the hook
568 $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
569 if ( $outputDone ) {
570 if ( $outputDone instanceof ParserOutput ) {
571 $pOutput = $outputDone;
572 }
573
574 if ( $pOutput ) {
575 $this->doOutputMetaData( $pOutput, $outputPage );
576 }
577 return true;
578 }
579
580 // Early abort if the page doesn't exist
581 if ( !$this->mPage->exists() ) {
582 wfDebug( __METHOD__ . ": showing missing article" );
583 $this->showMissingArticle();
584 $this->mPage->doViewUpdates( $performer );
585 return false; // skip all further output to OutputPage
586 }
587
588 // Try the latest parser cache
589 // NOTE: try latest-revision cache first to avoid loading revision.
590 if ( $useParserCache && !$oldid ) {
591 $pOutput = $parserOutputAccess->getCachedParserOutput(
592 $this->getPage(),
593 $parserOptions,
594 null,
595 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked
596 );
597
598 if ( $pOutput ) {
599 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
600 $this->doOutputMetaData( $pOutput, $outputPage );
601 return true;
602 }
603 }
604
605 $rev = $this->fetchRevisionRecord();
606 if ( !$this->fetchResult->isOK() ) {
607 $this->showViewError( $this->fetchResult->getWikiText(
608 false, false, $this->getContext()->getLanguage()
609 ) );
610 return true;
611 }
612
613 # Are we looking at an old revision
614 if ( $oldid ) {
615 $this->setOldSubtitle( $oldid );
616
617 if ( !$this->showDeletedRevisionHeader() ) {
618 wfDebug( __METHOD__ . ": cannot view deleted revision" );
619 return false; // skip all further output to OutputPage
620 }
621
622 // Try the old revision parser cache
623 // NOTE: Repeating cache check for old revision to avoid fetching $rev
624 // before it's absolutely necessary.
625 if ( $useParserCache ) {
626 $pOutput = $parserOutputAccess->getCachedParserOutput(
627 $this->getPage(),
628 $parserOptions,
629 $rev,
630 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked in fetchRevisionRevord
631 );
632
633 if ( $pOutput ) {
634 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
635 $this->doOutputMetaData( $pOutput, $outputPage );
636 return true;
637 }
638 }
639 }
640
641 # Ensure that UI elements requiring revision ID have
642 # the correct version information.
643 $outputPage->setRevisionId( $this->getRevIdFetched() );
644 # Preload timestamp to avoid a DB hit
645 $outputPage->setRevisionTimestamp( $rev->getTimestamp() );
646
647 # Pages containing custom CSS or JavaScript get special treatment
648 if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
649 $dir = $this->getContext()->getLanguage()->getDir();
650 $lang = $this->getContext()->getLanguage()->getHtmlCode();
651
652 $outputPage->wrapWikiMsg(
653 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
654 'clearyourcache'
655 );
656 $outputPage->addModuleStyles( 'mediawiki.action.styles' );
657 } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
658 $rev,
659 $this->getTitle(),
660 $oldid,
661 $outputPage )
662 ) {
663 // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
664 // Allow extensions do their own custom view for certain pages
665 $this->doOutputMetaData( $pOutput, $outputPage );
666 return true;
667 }
668
669 # Run the parse, protected by a pool counter
670 wfDebug( __METHOD__ . ": doing uncached parse" );
671
672 if ( !$rev ) {
673 // No revision, abort! Shouldn't happen.
674 return false;
675 }
676
677 $opt = 0;
678
679 // we already checked the cache in case 2, don't check again.
680 $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
681
682 // we already checked in fetchRevisionRecord()
683 $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
684
685 if ( !$rev->getId() || !$useParserCache ) {
686 // fake revision or uncacheable options
687 $opt |= ParserOutputAccess::OPT_NO_CACHE;
688 }
689
690 $renderStatus = $parserOutputAccess->getParserOutput(
691 $this->getPage(),
692 $parserOptions,
693 $rev,
694 $opt
695 );
696
698 $rev,
699 $renderStatus,
700 $outputPage,
701 $textOptions
702 );
703
704 if ( !$renderStatus->isOK() ) {
705 return true;
706 }
707
708 $pOutput = $renderStatus->getValue();
709 $this->doOutputMetaData( $pOutput, $outputPage );
710 return true;
711 }
712
717 private function doOutputMetaData( ?ParserOutput $pOutput, OutputPage $outputPage ) {
718 # Adjust title for main page & pages with displaytitle
719 if ( $pOutput ) {
720 $this->adjustDisplayTitle( $pOutput );
721 }
722
723 # Check for any __NOINDEX__ tags on the page using $pOutput
724 $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
725 $outputPage->setIndexPolicy( $policy['index'] );
726 $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
727
728 $this->mParserOutput = $pOutput;
729 }
730
736 private function doOutputFromParserCache(
737 ParserOutput $pOutput,
738 OutputPage $outputPage,
739 array $textOptions
740 ) {
741 # Ensure that UI elements requiring revision ID have
742 # the correct version information.
743 $outputPage->setRevisionId( $pOutput->getCacheRevisionId() ?? $this->getRevIdFetched() );
744
745 $outputPage->addParserOutput( $pOutput, $textOptions );
746 # Preload timestamp to avoid a DB hit
747 $cachedTimestamp = $pOutput->getTimestamp();
748 if ( $cachedTimestamp !== null ) {
749 $outputPage->setRevisionTimestamp( $cachedTimestamp );
750 $this->mPage->setTimestamp( $cachedTimestamp );
751 }
752 }
753
760 private function doOutputFromRenderStatus(
761 ?RevisionRecord $rev,
762 Status $renderStatus,
763 OutputPage $outputPage,
764 array $textOptions
765 ) {
766 global $wgCdnMaxageStale;
767 $ok = $renderStatus->isOK();
768
769 $pOutput = $ok ? $renderStatus->getValue() : null;
770
771 // Cache stale ParserOutput object with a short expiry
772 if ( $ok && $renderStatus->hasMessage( 'view-pool-dirty-output' ) ) {
773 $outputPage->setCdnMaxage( $wgCdnMaxageStale );
774 $outputPage->setLastModified( $pOutput->getCacheTime() );
775 $staleReason = $renderStatus->hasMessage( 'view-pool-contention' )
776 ? $this->getContext()->msg( 'view-pool-contention' )
777 : $this->getContext()->msg( 'view-pool-timeout' );
778 $outputPage->addHTML( "<!-- parser cache is expired, " .
779 "sending anyway due to $staleReason-->\n" );
780 }
781
782 if ( !$renderStatus->isOK() ) {
783 $this->showViewError( $renderStatus->getWikiText(
784 false, 'view-pool-error', $this->getContext()->getLanguage()
785 ) );
786 return;
787 }
788
789 if ( $pOutput ) {
790 $outputPage->addParserOutput( $pOutput, $textOptions );
791 }
792
793 if ( $this->getRevisionRedirectTarget( $rev ) ) {
794 $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
795 $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
796 }
797 }
798
803 private function getRevisionRedirectTarget( RevisionRecord $revision ) {
804 // TODO: find a *good* place for the code that determines the redirect target for
805 // a given revision!
806 // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
807 $content = $revision->getContent( SlotRecord::MAIN );
808 return $content ? $content->getRedirectTarget() : null;
809 }
810
815 public function adjustDisplayTitle( ParserOutput $pOutput ) {
816 $out = $this->getContext()->getOutput();
817
818 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
819 $titleText = $pOutput->getTitleText();
820 if ( strval( $titleText ) !== '' ) {
821 $out->setPageTitle( $titleText );
822 $out->setDisplayTitle( $titleText );
823 }
824 }
825
830 protected function showDiffPage() {
831 $request = $this->getContext()->getRequest();
832 $user = $this->getContext()->getUser();
833 $diff = $request->getVal( 'diff' );
834 $rcid = $request->getVal( 'rcid' );
835 $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
836 $purge = $request->getRawVal( 'action' ) === 'purge';
837 $unhide = $request->getInt( 'unhide' ) == 1;
838 $oldid = $this->getOldID();
839
840 $rev = $this->fetchRevisionRecord();
841
842 if ( !$rev ) {
843 // T213621: $rev maybe null due to either lack of permission to view the
844 // revision or actually not existing. So let's try loading it from the id
845 $rev = $this->revisionStore->getRevisionById( $oldid );
846 if ( $rev ) {
847 // Revision exists but $user lacks permission to diff it.
848 // Do nothing here.
849 // The $rev will later be used to create standard diff elements however.
850 } else {
851 $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
852 $msg = $this->getContext()->msg( 'difference-missing-revision' )
853 ->params( $oldid )
854 ->numParams( 1 )
855 ->parseAsBlock();
856 $this->getContext()->getOutput()->addHTML( $msg );
857 return;
858 }
859 }
860
861 $contentHandler = MediaWikiServices::getInstance()
862 ->getContentHandlerFactory()
863 ->getContentHandler(
864 $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
865 );
866 $de = $contentHandler->createDifferenceEngine(
867 $this->getContext(),
868 $oldid,
869 $diff,
870 $rcid,
871 $purge,
872 $unhide
873 );
874 $de->setSlotDiffOptions( [
875 'diff-type' => $request->getVal( 'diff-type' )
876 ] );
877 $de->showDiffPage( $diffOnly );
878
879 // Run view updates for the newer revision being diffed (and shown
880 // below the diff if not $diffOnly).
881 list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
882 // New can be false, convert it to 0 - this conveniently means the latest revision
883 $this->mPage->doViewUpdates( $user, (int)$new );
884 }
885
893 public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
895
896 $ns = $this->getTitle()->getNamespace();
897
898 # Don't index user and user talk pages for blocked users (T13443)
899 if ( ( $ns === NS_USER || $ns === NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
900 $specificTarget = null;
901 $vagueTarget = null;
902 $titleText = $this->getTitle()->getText();
903 if ( IPUtils::isValid( $titleText ) ) {
904 $vagueTarget = $titleText;
905 } else {
906 $specificTarget = $titleText;
907 }
908 if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
909 return [
910 'index' => 'noindex',
911 'follow' => 'nofollow'
912 ];
913 }
914 }
915
916 if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
917 # Non-articles (special pages etc), and old revisions
918 return [
919 'index' => 'noindex',
920 'follow' => 'nofollow'
921 ];
922 } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
923 # Discourage indexing of printable versions, but encourage following
924 return [
925 'index' => 'noindex',
926 'follow' => 'follow'
927 ];
928 } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
929 # For ?curid=x urls, disallow indexing
930 return [
931 'index' => 'noindex',
932 'follow' => 'follow'
933 ];
934 }
935
936 # Otherwise, construct the policy based on the various config variables.
937 $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
938
939 if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
940 # Honour customised robot policies for this namespace
941 $policy = array_merge(
942 $policy,
943 self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
944 );
945 }
946 if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
947 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
948 # a final sanity check that we have really got the parser output.
949 $policy = array_merge(
950 $policy,
951 [ 'index' => $pOutput->getIndexPolicy() ]
952 );
953 }
954
955 if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
956 # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
957 $policy = array_merge(
958 $policy,
959 self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
960 );
961 }
962
963 return $policy;
964 }
965
973 public static function formatRobotPolicy( $policy ) {
974 if ( is_array( $policy ) ) {
975 return $policy;
976 } elseif ( !$policy ) {
977 return [];
978 }
979
980 $policy = explode( ',', $policy );
981 $policy = array_map( 'trim', $policy );
982
983 $arr = [];
984 foreach ( $policy as $var ) {
985 if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
986 $arr['index'] = $var;
987 } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
988 $arr['follow'] = $var;
989 }
990 }
991
992 return $arr;
993 }
994
1002 public function showRedirectedFromHeader() {
1003 global $wgRedirectSources;
1004
1005 $context = $this->getContext();
1006 $outputPage = $context->getOutput();
1007 $request = $context->getRequest();
1008 $rdfrom = $request->getVal( 'rdfrom' );
1009
1010 // Construct a URL for the current page view, but with the target title
1011 $query = $request->getValues();
1012 unset( $query['rdfrom'] );
1013 unset( $query['title'] );
1014 if ( $this->getTitle()->isRedirect() ) {
1015 // Prevent double redirects
1016 $query['redirect'] = 'no';
1017 }
1018 $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1019
1020 if ( isset( $this->mRedirectedFrom ) ) {
1021 // This is an internally redirected page view.
1022 // We'll need a backlink to the source page for navigation.
1023 if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1024 $redir = $this->linkRenderer->makeKnownLink(
1025 $this->mRedirectedFrom,
1026 null,
1027 [],
1028 [ 'redirect' => 'no' ]
1029 );
1030
1031 $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1032 $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1033 . "</span>" );
1034
1035 // Add the script to update the displayed URL and
1036 // set the fragment if one was specified in the redirect
1037 $outputPage->addJsConfigVars( [
1038 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1039 ] );
1040 $outputPage->addModules( 'mediawiki.action.view.redirect' );
1041
1042 // Add a <link rel="canonical"> tag
1043 $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1044
1045 // Tell the output object that the user arrived at this article through a redirect
1046 $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1047
1048 return true;
1049 }
1050 } elseif ( $rdfrom ) {
1051 // This is an externally redirected view, from some other wiki.
1052 // If it was reported from a trusted site, supply a backlink.
1053 if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
1054 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1055 $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1056 $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1057 . "</span>" );
1058
1059 // Add the script to update the displayed URL
1060 $outputPage->addJsConfigVars( [
1061 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1062 ] );
1063 $outputPage->addModules( 'mediawiki.action.view.redirect' );
1064
1065 return true;
1066 }
1067 }
1068
1069 return false;
1070 }
1071
1076 public function showNamespaceHeader() {
1077 if ( $this->getTitle()->isTalkPage() && !wfMessage( 'talkpageheader' )->isDisabled() ) {
1078 $this->getContext()->getOutput()->wrapWikiMsg(
1079 "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1080 [ 'talkpageheader' ]
1081 );
1082 }
1083 }
1084
1088 public function showViewFooter() {
1089 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1090 if ( $this->getTitle()->getNamespace() === NS_USER_TALK
1091 && IPUtils::isValid( $this->getTitle()->getText() )
1092 ) {
1093 $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1094 }
1095
1096 // Show a footer allowing the user to patrol the shown revision or page if possible
1097 $patrolFooterShown = $this->showPatrolFooter();
1098
1099 $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1100 }
1101
1112 public function showPatrolFooter() {
1114
1115 // Allow hooks to decide whether to not output this at all
1116 if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1117 return false;
1118 }
1119
1120 $outputPage = $this->getContext()->getOutput();
1121 $user = $this->getContext()->getUser();
1122 $title = $this->getTitle();
1123 $rc = false;
1124
1125 if ( !$this->getContext()->getAuthority()->probablyCan( 'patrol', $title )
1127 || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1128 ) {
1129 // Patrolling is disabled or the user isn't allowed to
1130 return false;
1131 }
1132
1133 if ( $this->mRevisionRecord
1134 && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1135 ) {
1136 // The current revision is already older than what could be in the RC table
1137 // 6h tolerance because the RC might not be cleaned out regularly
1138 return false;
1139 }
1140
1141 // Check for cached results
1142 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1143 $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1144 if ( $cache->get( $key ) ) {
1145 return false;
1146 }
1147
1148 $dbr = wfGetDB( DB_REPLICA );
1149 $oldestRevisionTimestamp = $dbr->selectField(
1150 'revision',
1151 'MIN( rev_timestamp )',
1152 [ 'rev_page' => $title->getArticleID() ],
1153 __METHOD__
1154 );
1155
1156 // New page patrol: Get the timestamp of the oldest revison which
1157 // the revision table holds for the given page. Then we look
1158 // whether it's within the RC lifespan and if it is, we try
1159 // to get the recentchanges row belonging to that entry
1160 // (with rc_new = 1).
1161 $recentPageCreation = false;
1162 if ( $oldestRevisionTimestamp
1163 && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1164 ) {
1165 // 6h tolerance because the RC might not be cleaned out regularly
1166 $recentPageCreation = true;
1167 $rc = RecentChange::newFromConds(
1168 [
1169 'rc_new' => 1,
1170 'rc_timestamp' => $oldestRevisionTimestamp,
1171 'rc_namespace' => $title->getNamespace(),
1172 'rc_cur_id' => $title->getArticleID()
1173 ],
1174 __METHOD__
1175 );
1176 if ( $rc ) {
1177 // Use generic patrol message for new pages
1178 $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1179 }
1180 }
1181
1182 // File patrol: Get the timestamp of the latest upload for this page,
1183 // check whether it is within the RC lifespan and if it is, we try
1184 // to get the recentchanges row belonging to that entry
1185 // (with rc_type = RC_LOG, rc_log_type = upload).
1186 $recentFileUpload = false;
1187 if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1188 && $title->getNamespace() === NS_FILE ) {
1189 // Retrieve timestamp of most recent upload
1190 $newestUploadTimestamp = $dbr->selectField(
1191 'image',
1192 'MAX( img_timestamp )',
1193 [ 'img_name' => $title->getDBkey() ],
1194 __METHOD__
1195 );
1196 if ( $newestUploadTimestamp
1197 && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1198 ) {
1199 // 6h tolerance because the RC might not be cleaned out regularly
1200 $recentFileUpload = true;
1201 $rc = RecentChange::newFromConds(
1202 [
1203 'rc_type' => RC_LOG,
1204 'rc_log_type' => 'upload',
1205 'rc_timestamp' => $newestUploadTimestamp,
1206 'rc_namespace' => NS_FILE,
1207 'rc_cur_id' => $title->getArticleID()
1208 ],
1209 __METHOD__
1210 );
1211 if ( $rc ) {
1212 // Use patrol message specific to files
1213 $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1214 }
1215 }
1216 }
1217
1218 if ( !$recentPageCreation && !$recentFileUpload ) {
1219 // Page creation and latest upload (for files) is too old to be in RC
1220
1221 // We definitely can't patrol so cache the information
1222 // When a new file version is uploaded, the cache is cleared
1223 $cache->set( $key, '1' );
1224
1225 return false;
1226 }
1227
1228 if ( !$rc ) {
1229 // Don't cache: This can be hit if the page gets accessed very fast after
1230 // its creation / latest upload or in case we have high replica DB lag. In case
1231 // the revision is too old, we will already return above.
1232 return false;
1233 }
1234
1235 if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1236 // Patrolled RC entry around
1237
1238 // Cache the information we gathered above in case we can't patrol
1239 // Don't cache in case we can patrol as this could change
1240 $cache->set( $key, '1' );
1241
1242 return false;
1243 }
1244
1245 if ( $rc->getPerformerIdentity()->equals( $user ) ) {
1246 // Don't show a patrol link for own creations/uploads. If the user could
1247 // patrol them, they already would be patrolled
1248 return false;
1249 }
1250
1251 $outputPage->preventClickjacking();
1252 if ( $this->getContext()->getAuthority()->isAllowed( 'writeapi' ) ) {
1253 $outputPage->addModules( 'mediawiki.misc-authed-curate' );
1254 }
1255
1256 $link = $this->linkRenderer->makeKnownLink(
1257 $title,
1258 $markPatrolledMsg->text(),
1259 [],
1260 [
1261 'action' => 'markpatrolled',
1262 'rcid' => $rc->getAttribute( 'rc_id' ),
1263 ]
1264 );
1265
1266 $outputPage->addModuleStyles( 'mediawiki.action.styles' );
1267 $outputPage->addHTML(
1268 "<div class='patrollink' data-mw='interface'>" .
1269 wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1270 '</div>'
1271 );
1272
1273 return true;
1274 }
1275
1282 public static function purgePatrolFooterCache( $articleID ) {
1283 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1284 $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1285 }
1286
1291 public function showMissingArticle() {
1292 global $wgSend404Code;
1293
1294 $outputPage = $this->getContext()->getOutput();
1295 // Whether the page is a root user page of an existing user (but not a subpage)
1296 $validUserPage = false;
1297
1298 $title = $this->getTitle();
1299
1300 $services = MediaWikiServices::getInstance();
1301
1302 $contextUser = $this->getContext()->getUser();
1303
1304 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1305 if ( $title->getNamespace() === NS_USER
1306 || $title->getNamespace() === NS_USER_TALK
1307 ) {
1308 $rootPart = explode( '/', $title->getText() )[0];
1309 $user = User::newFromName( $rootPart, false /* allow IP users */ );
1310 $ip = $this->userNameUtils->isIP( $rootPart );
1311 $block = DatabaseBlock::newFromTarget( $user, $user );
1312
1313 if ( $user && $user->isRegistered() && $user->isHidden() &&
1314 !$this->getContext()->getAuthority()->isAllowed( 'hideuser' )
1315 ) {
1316 // T120883 if the user is hidden and the viewer cannot see hidden
1317 // users, pretend like it does not exist at all.
1318 $user = false;
1319 }
1320
1321 if ( !( $user && $user->isRegistered() ) && !$ip ) { # User does not exist
1322 $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1323 [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1324 } elseif (
1325 $block !== null &&
1326 $block->getType() != DatabaseBlock::TYPE_AUTO &&
1327 (
1328 $block->isSitewide() ||
1329 $user->isBlockedFrom( $title, true )
1330 )
1331 ) {
1332 // Show log extract if the user is sitewide blocked or is partially
1333 // blocked and not allowed to edit their user page or user talk page
1334 LogEventsList::showLogExtract(
1335 $outputPage,
1336 'block',
1337 $services->getNamespaceInfo()->getCanonicalName( NS_USER ) . ':' .
1338 $block->getTargetName(),
1339 '',
1340 [
1341 'lim' => 1,
1342 'showIfEmpty' => false,
1343 'msgKey' => [
1344 'blocked-notice-logextract',
1345 $user->getName() # Support GENDER in notice
1346 ]
1347 ]
1348 );
1349 $validUserPage = !$title->isSubpage();
1350 } else {
1351 $validUserPage = !$title->isSubpage();
1352 }
1353 }
1354
1355 $this->getHookRunner()->onShowMissingArticle( $this );
1356
1357 # Show delete and move logs if there were any such events.
1358 # The logging query can DOS the site when bots/crawlers cause 404 floods,
1359 # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1360 $dbCache = ObjectCache::getInstance( 'db-replicated' );
1361 $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1362 $isRegistered = $contextUser->isRegistered();
1363 $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
1364
1365 if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1366 $logTypes = [ 'delete', 'move', 'protect' ];
1367
1368 $dbr = wfGetDB( DB_REPLICA );
1369
1370 $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1371 // Give extensions a chance to hide their (unrelated) log entries
1372 $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1373 LogEventsList::showLogExtract(
1374 $outputPage,
1375 $logTypes,
1376 $title,
1377 '',
1378 [
1379 'lim' => 10,
1380 'conds' => $conds,
1381 'showIfEmpty' => false,
1382 'msgKey' => [ $isRegistered || $sessionExists
1383 ? 'moveddeleted-notice'
1384 : 'moveddeleted-notice-recent'
1385 ]
1386 ]
1387 );
1388 }
1389
1390 if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1391 // If there's no backing content, send a 404 Not Found
1392 // for better machine handling of broken links.
1393 $this->getContext()->getRequest()->response()->statusHeader( 404 );
1394 }
1395
1396 // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1397 $policy = $this->getRobotPolicy( 'view' );
1398 $outputPage->setIndexPolicy( $policy['index'] );
1399 $outputPage->setFollowPolicy( $policy['follow'] );
1400
1401 $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1402
1403 if ( !$hookResult ) {
1404 return;
1405 }
1406
1407 # Show error message
1408 $oldid = $this->getOldID();
1409 if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1410 $text = $this->getTitle()->getDefaultMessageText() ?? '';
1411 $outputPage->addWikiTextAsContent( $text );
1412 } else {
1413 if ( $oldid ) {
1414 // T251066: Try loading the revision from the archive table.
1415 // Show link to view it if it exists and the user has permission to view it.
1416 $pa = new PageArchive( $title, $this->getContext()->getConfig() );
1417 $revRecord = $pa->getArchivedRevisionRecord( $oldid );
1418 if ( $revRecord && $revRecord->userCan(
1419 RevisionRecord::DELETED_TEXT,
1420 $this->getContext()->getAuthority()
1421 ) ) {
1422 $text = wfMessage(
1423 'missing-revision-permission', $oldid,
1424 $revRecord->getTimestamp(),
1425 $title->getPrefixedDBkey()
1426 )->plain();
1427 } else {
1428 $text = wfMessage( 'missing-revision', $oldid )->plain();
1429 }
1430
1431 } elseif ( $this->getContext()->getAuthority()->probablyCan( 'edit', $title )
1432 ) {
1433 $message = $isRegistered ? 'noarticletext' : 'noarticletextanon';
1434 $text = wfMessage( $message )->plain();
1435 } else {
1436 $text = wfMessage( 'noarticletext-nopermission' )->plain();
1437 }
1438
1439 $dir = $this->getContext()->getLanguage()->getDir();
1440 $lang = $this->getContext()->getLanguage()->getHtmlCode();
1441 $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1442 'class' => "noarticletext mw-content-$dir",
1443 'dir' => $dir,
1444 'lang' => $lang,
1445 ] ) . "\n$text\n</div>" );
1446 }
1447 }
1448
1453 private function showViewError( string $errortext ) {
1454 $outputPage = $this->getContext()->getOutput();
1455 $outputPage->setPageTitle( $this->getContext()->msg( 'errorpagetitle' ) );
1456 $outputPage->enableClientCache( false );
1457 $outputPage->setRobotPolicy( 'noindex,nofollow' );
1458 $outputPage->clearHTML();
1459 $outputPage->wrapWikiTextAsInterface( 'errorbox', $errortext );
1460 }
1461
1468 public function showDeletedRevisionHeader() {
1469 if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1470 // Not deleted
1471 return true;
1472 }
1473 $outputPage = $this->getContext()->getOutput();
1474 $user = $this->getContext()->getUser();
1475 // Used in wikilinks, should not contain whitespaces
1476 $titleText = $this->getTitle()->getPrefixedDBkey();
1477 // If the user is not allowed to see it...
1478 if ( !$this->mRevisionRecord->userCan(
1479 RevisionRecord::DELETED_TEXT,
1480 $this->getContext()->getAuthority()
1481 ) ) {
1482 $outputPage->addHtml(
1483 Html::warningBox(
1484 $outputPage->msg( 'rev-deleted-text-permission', $titleText )->parse(),
1485 'plainlinks'
1486 )
1487 );
1488
1489 return false;
1490 // If the user needs to confirm that they want to see it...
1491 } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1492 # Give explanation and add a link to view the revision...
1493 $oldid = intval( $this->getOldID() );
1494 $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1495 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1496 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1497 $outputPage->addHtml(
1498 Html::warningBox(
1499 $outputPage->msg( $msg, $link )->parse(),
1500 'plainlinks'
1501 )
1502 );
1503
1504 return false;
1505 // We are allowed to see...
1506 } else {
1507 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1508 ? [ 'rev-suppressed-text-view', $titleText ]
1509 : [ 'rev-deleted-text-view', $titleText ];
1510 $outputPage->addHtml(
1511 Html::warningBox(
1512 $outputPage->msg( $msg[0], $msg[1] )->parse(),
1513 'plainlinks'
1514 )
1515 );
1516
1517 return true;
1518 }
1519 }
1520
1529 public function setOldSubtitle( $oldid = 0 ) {
1530 if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1531 return;
1532 }
1533
1534 $context = $this->getContext();
1535 $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1536
1537 # Cascade unhide param in links for easy deletion browsing
1538 $extraParams = [];
1539 if ( $unhide ) {
1540 $extraParams['unhide'] = 1;
1541 }
1542
1543 if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1544 $revisionRecord = $this->mRevisionRecord;
1545 } else {
1546 $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1547 }
1548
1549 $timestamp = $revisionRecord->getTimestamp();
1550
1551 $current = ( $oldid == $this->mPage->getLatest() );
1552 $language = $context->getLanguage();
1553 $user = $context->getUser();
1554
1555 $td = $language->userTimeAndDate( $timestamp, $user );
1556 $tddate = $language->userDate( $timestamp, $user );
1557 $tdtime = $language->userTime( $timestamp, $user );
1558
1559 # Show user links if allowed to see them. If hidden, then show them only if requested...
1560 $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1561
1562 $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1563 ? 'revision-info-current'
1564 : 'revision-info';
1565
1566 $outputPage = $context->getOutput();
1567 $outputPage->addModuleStyles( [
1568 'mediawiki.action.styles',
1569 'mediawiki.interface.helpers.styles'
1570 ] );
1571
1572 $revisionUser = $revisionRecord->getUser();
1573 $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1574 $context->msg( $infomsg, $td )
1575 ->rawParams( $userlinks )
1576 ->params(
1577 $revisionRecord->getId(),
1578 $tddate,
1579 $tdtime,
1580 $revisionUser ? $revisionUser->getName() : ''
1581 )
1582 ->rawParams( Linker::revComment(
1583 $revisionRecord,
1584 true,
1585 true
1586 ) )
1587 ->parse() .
1588 "</div>";
1589
1590 $lnk = $current
1591 ? $context->msg( 'currentrevisionlink' )->escaped()
1592 : $this->linkRenderer->makeKnownLink(
1593 $this->getTitle(),
1594 $context->msg( 'currentrevisionlink' )->text(),
1595 [],
1596 $extraParams
1597 );
1598 $curdiff = $current
1599 ? $context->msg( 'diff' )->escaped()
1600 : $this->linkRenderer->makeKnownLink(
1601 $this->getTitle(),
1602 $context->msg( 'diff' )->text(),
1603 [],
1604 [
1605 'diff' => 'cur',
1606 'oldid' => $oldid
1607 ] + $extraParams
1608 );
1609 $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1610 $prevlink = $prevExist
1611 ? $this->linkRenderer->makeKnownLink(
1612 $this->getTitle(),
1613 $context->msg( 'previousrevision' )->text(),
1614 [],
1615 [
1616 'direction' => 'prev',
1617 'oldid' => $oldid
1618 ] + $extraParams
1619 )
1620 : $context->msg( 'previousrevision' )->escaped();
1621 $prevdiff = $prevExist
1622 ? $this->linkRenderer->makeKnownLink(
1623 $this->getTitle(),
1624 $context->msg( 'diff' )->text(),
1625 [],
1626 [
1627 'diff' => 'prev',
1628 'oldid' => $oldid
1629 ] + $extraParams
1630 )
1631 : $context->msg( 'diff' )->escaped();
1632 $nextlink = $current
1633 ? $context->msg( 'nextrevision' )->escaped()
1634 : $this->linkRenderer->makeKnownLink(
1635 $this->getTitle(),
1636 $context->msg( 'nextrevision' )->text(),
1637 [],
1638 [
1639 'direction' => 'next',
1640 'oldid' => $oldid
1641 ] + $extraParams
1642 );
1643 $nextdiff = $current
1644 ? $context->msg( 'diff' )->escaped()
1645 : $this->linkRenderer->makeKnownLink(
1646 $this->getTitle(),
1647 $context->msg( 'diff' )->text(),
1648 [],
1649 [
1650 'diff' => 'next',
1651 'oldid' => $oldid
1652 ] + $extraParams
1653 );
1654
1656 $user,
1657 $revisionRecord,
1658 $this->getTitle()
1659 );
1660 if ( $cdel !== '' ) {
1661 $cdel .= ' ';
1662 }
1663
1664 // the outer div is need for styling the revision info and nav in MobileFrontend
1665 $outputPage->addSubtitle( "<div class=\"mw-revision warningbox\">" . $revisionInfo .
1666 "<div id=\"mw-revision-nav\">" . $cdel .
1667 $context->msg( 'revision-nav' )->rawParams(
1668 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1669 )->escaped() . "</div></div>" );
1670 }
1671
1685 public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1686 $lang = $this->getTitle()->getPageLanguage();
1687 $out = $this->getContext()->getOutput();
1688 if ( $appendSubtitle ) {
1689 $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1690 }
1691 $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1692 return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1693 }
1694
1707 public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1708 if ( !is_array( $target ) ) {
1709 $target = [ $target ];
1710 }
1711
1712 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1713
1714 $html = '<ul class="redirectText">';
1716 foreach ( $target as $title ) {
1717 if ( $forceKnown ) {
1718 $link = $linkRenderer->makeKnownLink(
1719 $title,
1720 $title->getFullText(),
1721 [],
1722 // Make sure wiki page redirects are not followed
1723 $title->isRedirect() ? [ 'redirect' => 'no' ] : []
1724 );
1725 } else {
1726 $link = $linkRenderer->makeLink(
1727 $title,
1728 $title->getFullText(),
1729 [],
1730 // Make sure wiki page redirects are not followed
1731 $title->isRedirect() ? [ 'redirect' => 'no' ] : []
1732 );
1733 }
1734 $html .= '<li>' . $link . '</li>';
1735 }
1736 $html .= '</ul>';
1737
1738 $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1739
1740 return '<div class="redirectMsg">' .
1741 '<p>' . $redirectToText . '</p>' .
1742 $html .
1743 '</div>';
1744 }
1745
1754 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1755 $out = $this->getContext()->getOutput();
1756 $msg = $out->msg( 'namespace-' . $this->getTitle()->getNamespace() . '-helppage' );
1757
1758 if ( !$msg->isDisabled() ) {
1759 $title = Title::newFromText( $msg->plain() );
1760 if ( $title instanceof Title ) {
1761 $out->addHelpLink( $title->getLocalURL(), true );
1762 }
1763 } else {
1764 $out->addHelpLink( $to, $overrideBaseUrl );
1765 }
1766 }
1767
1771 public function render() {
1772 $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1773 $this->getContext()->getOutput()->setArticleBodyOnly( true );
1774 // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1775 $this->viewIsRenderAction = true;
1776 $this->view();
1777 }
1778
1782 public function protect() {
1783 $form = new ProtectionForm( $this );
1784 $form->execute();
1785 }
1786
1790 public function unprotect() {
1791 $this->protect();
1792 }
1793
1802 public function doDelete( $reason, $suppress = false, $immediate = false ) {
1803 $error = '';
1804 $context = $this->getContext();
1805 $outputPage = $context->getOutput();
1806 $user = $context->getUser();
1807 $status = $this->mPage->doDeleteArticleReal(
1808 $reason, $user, $suppress, null, $error,
1809 null, [], 'delete', $immediate
1810 );
1811
1812 if ( $status->isOK() ) {
1813 $deleted = $this->getTitle()->getPrefixedText();
1814
1815 $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
1816 $outputPage->setRobotPolicy( 'noindex,nofollow' );
1817
1818 if ( $status->isGood() ) {
1819 $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
1820 $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
1821 $this->getHookRunner()->onArticleDeleteAfterSuccess( $this->getTitle(), $outputPage );
1822 } else {
1823 $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
1824 }
1825
1826 $outputPage->returnToMain( false );
1827 } else {
1828 $outputPage->setPageTitle(
1829 wfMessage( 'cannotdelete-title',
1830 $this->getTitle()->getPrefixedText() )
1831 );
1832
1833 if ( $error == '' ) {
1834 $outputPage->wrapWikiTextAsInterface(
1835 'error mw-error-cannotdelete',
1836 $status->getWikiText( false, false, $context->getLanguage() )
1837 );
1838 $deleteLogPage = new LogPage( 'delete' );
1839 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1840
1841 LogEventsList::showLogExtract(
1842 $outputPage,
1843 'delete',
1844 $this->getTitle()
1845 );
1846 } else {
1847 $outputPage->addHTML( $error );
1848 }
1849 }
1850 }
1851
1852 /* Caching functions */
1853
1861 protected function tryFileCache() {
1862 static $called = false;
1863
1864 if ( $called ) {
1865 wfDebug( "Article::tryFileCache(): called twice!?" );
1866 return false;
1867 }
1868
1869 $called = true;
1870 if ( $this->isFileCacheable() ) {
1871 $cache = new HTMLFileCache( $this->getTitle(), 'view' );
1872 if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1873 wfDebug( "Article::tryFileCache(): about to load file" );
1874 $cache->loadFromFileCache( $this->getContext() );
1875 return true;
1876 } else {
1877 wfDebug( "Article::tryFileCache(): starting buffer" );
1878 ob_start( [ &$cache, 'saveToFileCache' ] );
1879 }
1880 } else {
1881 wfDebug( "Article::tryFileCache(): not cacheable" );
1882 }
1883
1884 return false;
1885 }
1886
1892 public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
1893 $cacheable = false;
1894
1895 if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
1896 $cacheable = $this->mPage->getId()
1897 && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1898 // Extension may have reason to disable file caching on some pages.
1899 if ( $cacheable ) {
1900 $cacheable = $this->getHookRunner()->onIsFileCacheable( $this );
1901 }
1902 }
1903
1904 return $cacheable;
1905 }
1906
1920 public function getParserOutput( $oldid = null, UserIdentity $user = null ) {
1921 if ( $user === null ) {
1922 $parserOptions = $this->getParserOptions();
1923 } else {
1924 $parserOptions = $this->mPage->makeParserOptions( $user );
1925 }
1926
1927 return $this->mPage->getParserOutput( $parserOptions, $oldid );
1928 }
1929
1934 public function getParserOptions() {
1935 return $this->mPage->makeParserOptions( $this->getContext() );
1936 }
1937
1944 public function setContext( $context ) {
1945 $this->mContext = $context;
1946 }
1947
1954 public function getContext() {
1955 if ( $this->mContext instanceof IContextSource ) {
1956 return $this->mContext;
1957 } else {
1958 wfDebug( __METHOD__ . " called and \$mContext is null. " .
1959 "Return RequestContext::getMain(); for sanity" );
1960 return RequestContext::getMain();
1961 }
1962 }
1963
1973 public function __get( $fname ) {
1974 wfDeprecatedMsg( "Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
1975 '1.35' );
1976
1977 if ( property_exists( $this->mPage, $fname ) ) {
1978 return $this->mPage->$fname;
1979 }
1980 trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
1981 }
1982
1992 public function __set( $fname, $fvalue ) {
1993 wfDeprecatedMsg( "Setting Article::\$$fname is deprecated since MediaWiki 1.35",
1994 '1.35' );
1995
1996 if ( property_exists( $this->mPage, $fname ) ) {
1997 $this->mPage->$fname = $fvalue;
1998 // Note: extensions may want to toss on new fields
1999 } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2000 $this->mPage->$fname = $fvalue;
2001 } else {
2002 trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2003 }
2004 }
2005
2011 public function getActionOverrides() {
2012 return $this->mPage->getActionOverrides();
2013 }
2014
2020 public function getTimestamp() {
2021 wfDeprecated( __METHOD__, '1.35' );
2022 return $this->mPage->getTimestamp();
2023 }
2024}
getAuthority()
$wgArticleRobotPolicies
Robot policies per article.
$wgCdnMaxageStale
Cache timeout when delivering a stale ParserCache response due to PoolCounter contention.
$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.
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites.
const NS_USER
Definition Defines.php:66
const NS_FILE
Definition Defines.php:70
const NS_MEDIAWIKI
Definition Defines.php:72
const RC_LOG
Definition Defines.php:117
const NS_MEDIA
Definition Defines.php:52
const NS_USER_TALK
Definition Defines.php:67
const NS_CATEGORY
Definition Defines.php:78
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
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)
Logs a warning that a deprecated feature was used.
getContext()
Class for viewing MediaWiki article and history.
Definition Article.php:49
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition Article.php:195
UserNameUtils $userNameUtils
Definition Article.php:113
getContext()
Gets the context this Article is executed in.
Definition Article.php:1954
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition Article.php:269
getRedirectedFrom()
Get the page this view was redirected from.
Definition Article.php:206
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition Article.php:70
bool $viewIsRenderAction
Whether render() was called.
Definition Article.php:93
RevisionRecord null $mRevisionRecord
Revision to be shown.
Definition Article.php:121
RevisionStore $revisionStore
Definition Article.php:103
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition Article.php:427
__construct(Title $title, $oldId=null)
Definition Article.php:127
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition Article.php:893
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition Article.php:1282
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition Article.php:1802
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition Article.php:86
getOldID()
Definition Article.php:256
LinkRenderer $linkRenderer
Definition Article.php:98
getTitle()
Get the title object of the article.
Definition Article.php:224
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2011
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition Article.php:815
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition Article.php:1468
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition Article.php:1934
IContextSource null $mContext
The context this Article is executed in.
Definition Article.php:58
getParserOutput( $oldid=null, UserIdentity $user=null)
#-
Definition Article.php:1920
showViewError(string $errortext)
Show error text for errors generated in Article::view().
Definition Article.php:1453
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition Article.php:1707
protect()
action=protect handler
Definition Article.php:1782
generateContentOutput(Authority $performer, ParserOptions $parserOptions, int $oldid, OutputPage $outputPage, array $textOptions)
Determines the desired ParserOutput and passes it to $outputPage.
Definition Article.php:555
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
Definition Article.php:396
showMissingArticle()
Show the error text for a missing article.
Definition Article.php:1291
__set( $fname, $fvalue)
Definition Article.php:1992
unprotect()
action=unprotect handler (alias)
Definition Article.php:1790
newPage(Title $title)
Definition Article.php:142
getPage()
Get the WikiPage object of this instance.
Definition Article.php:234
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition Article.php:1754
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition Article.php:73
getTimestamp()
Definition Article.php:2020
static newFromID( $id)
Constructor from a page id.
Definition Article.php:151
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition Article.php:67
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition Article.php:973
fetchRevisionRecord()
Fetches the revision to work on.
Definition Article.php:338
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition Article.php:1685
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition Article.php:1112
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition Article.php:1529
showViewFooter()
Show the footer section of an ordinary page view.
Definition Article.php:1088
Status null $fetchResult
represents the outcome of fetchRevisionRecord().
Definition Article.php:79
WikiPage $mPage
The WikiPage object of this instance.
Definition Article.php:61
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
Definition Article.php:215
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition Article.php:1892
doOutputMetaData(?ParserOutput $pOutput, OutputPage $outputPage)
Definition Article.php:717
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition Article.php:1861
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition Article.php:415
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition Article.php:1076
__get( $fname)
Definition Article.php:1973
WatchlistManager $watchlistManager
Definition Article.php:108
getRevisionRedirectTarget(RevisionRecord $revision)
Definition Article.php:803
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition Article.php:163
doOutputFromRenderStatus(?RevisionRecord $rev, Status $renderStatus, OutputPage $outputPage, array $textOptions)
Definition Article.php:760
showDiffPage()
Show a diff page according to current request variables.
Definition Article.php:830
render()
Handle action=render.
Definition Article.php:1771
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition Article.php:1002
doOutputFromParserCache(ParserOutput $pOutput, OutputPage $outputPage, array $textOptions)
Definition Article.php:736
setContext( $context)
Sets the context this Article is executed in.
Definition Article.php:1944
getCacheRevisionId()
Definition CacheTime.php:96
Special handling for category description pages, showing pages, subcategories and file that belong to...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition EditPage.php:106
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:34
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:42
static revComment(RevisionRecord $revRecord, $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:1782
static getRevDeleteLink(Authority $performer, RevisionRecord $revRecord, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition Linker.php:2360
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1319
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:1011
Class to simplify the use of log pages.
Definition LogPage.php:38
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Class that generates HTML links for pages.
makeKnownLink( $target, $text=null, array $extraAttribs=[], array $query=[])
makeLink( $target, $text=null, array $extraAttribs=[], array $query=[])
MediaWikiServices is the service locator for the application scope of MediaWiki.
Service for getting rendered output of a given page.
A StatusValue for permission errors.
Page revision base class.
getContent( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns the Content of the given slot of this revision.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
UserNameUtils service.
This is one of the Core classes and should be read at least once by any new developers.
disable()
Disable output completely, i.e.
addWikiMsg(... $args)
Add a wikitext-formatted message to the output.
allowClickjacking()
Turn off frame-breaking.
wrapWikiTextAsInterface( $wrapperClass, $text)
Convert wikitext in the user interface language to HTML and add it to the buffer with a <div class="$...
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
setRobotPolicy( $policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
setPageTitle( $name)
"Page title" means the contents of <h1>.
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
setLastModified( $timestamp)
Override the last modified timestamp.
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
preventClickjacking( $enable=true)
Set a flag which will cause an X-Frame-Options header appropriate for edit pages to be sent.
addWikiTextAsInterface( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the user interface language to HTML and add it to the buffer.
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
isPrintable()
Return whether the page is "printable".
addModuleStyles( $modules)
Load the styles of one or more style-only ResourceLoader modules on this page.
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
returnToMain( $unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request,...
addSubtitle( $str)
Add $str to the subtitle.
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
clearHTML()
Clear the body HTML.
enableClientCache( $state)
Use enableClientCache(false) to force it to send nocache headers.
addHTML( $text)
Append $text to the body HTML.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
setCanonicalUrl( $url)
Set the URL to be used for the <link rel=canonical>>.
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
setRedirectedFrom(PageReference $t)
Set $mRedirectedFrom, the page which redirected us to the current page.
prependHTML( $text)
Prepend $text to the body HTML.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
addWikiTextAsContent( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
Used to show archived pages and eventually restore them.
Set options of the Parser.
setIsPrintable( $x)
Parsing the printable version of the page?
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.
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
isOK()
Returns whether the operation completed.
fatal( $message,... $parameters)
Add an error and set OK to false, indicating that the operation as a whole was fatal.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
getWikiText( $shortContext=false, $longContext=false, $lang=null)
Get the error list as a wikitext formatted list.
Definition Status.php:189
Represents a title within MediaWiki.
Definition Title.php:48
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:69
static newFromName( $name, $validate='valid')
Definition User.php:607
Class representing a MediaWiki article and history.
Definition WikiPage.php:60
getTitle()
Get the title object of the article.
Definition WikiPage.php:311
Interface for objects which can provide a MediaWiki context on request.
This interface represents the authority associated the current execution context, such as a web reque...
Definition Authority.php:37
Interface for objects representing user identity.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition Page.php:29
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
$content
Definition router.php:76
return true
Definition router.php:92
if(!isset( $args[0])) $lang