MediaWiki REL1_39
Article.php
Go to the documentation of this file.
1<?php
22use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
36use Wikimedia\IPUtils;
37use Wikimedia\NonSerializable\NonSerializableTrait;
38
48class Article implements Page {
49 use ProtectedHookAccessorTrait;
50 use NonSerializableTrait;
51
57 protected $mContext;
58
60 protected $mPage;
61
66 public $mOldId;
67
69 public $mRedirectedFrom = null;
70
72 public $mRedirectUrl = false;
73
78 private $fetchResult = null;
79
85 public $mParserOutput = null;
86
92 protected $viewIsRenderAction = false;
93
97 protected $linkRenderer;
98
102 private $revisionStore;
103
107 private $watchlistManager;
108
112 private $userNameUtils;
113
117 private $userOptionsLookup;
118
125 private $mRevisionRecord = null;
126
131 public function __construct( Title $title, $oldId = null ) {
132 $this->mOldId = $oldId;
133 $this->mPage = $this->newPage( $title );
134
135 $services = MediaWikiServices::getInstance();
136 $this->linkRenderer = $services->getLinkRenderer();
137 $this->revisionStore = $services->getRevisionStore();
138 $this->watchlistManager = $services->getWatchlistManager();
139 $this->userNameUtils = $services->getUserNameUtils();
140 $this->userOptionsLookup = $services->getUserOptionsLookup();
141 }
142
147 protected function newPage( Title $title ) {
148 return new WikiPage( $title );
149 }
150
156 public static function newFromID( $id ) {
157 $t = Title::newFromID( $id );
158 return $t == null ? null : new static( $t );
159 }
160
168 public static function newFromTitle( $title, IContextSource $context ) {
169 if ( $title->getNamespace() === NS_MEDIA ) {
170 // XXX: This should not be here, but where should it go?
171 $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
172 }
173
174 $page = null;
175 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
176 Hooks::runner()->onArticleFromTitle( $title, $page, $context );
177 if ( !$page ) {
178 switch ( $title->getNamespace() ) {
179 case NS_FILE:
180 $page = new ImagePage( $title );
181 break;
182 case NS_CATEGORY:
183 $page = new CategoryPage( $title );
184 break;
185 default:
186 $page = new Article( $title );
187 }
188 }
189 $page->setContext( $context );
190
191 return $page;
192 }
193
201 public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
202 $article = self::newFromTitle( $page->getTitle(), $context );
203 $article->mPage = $page; // override to keep process cached vars
204 return $article;
205 }
206
212 public function getRedirectedFrom() {
213 return $this->mRedirectedFrom;
214 }
215
221 public function setRedirectedFrom( Title $from ) {
222 $this->mRedirectedFrom = $from;
223 }
224
230 public function getTitle() {
231 return $this->mPage->getTitle();
232 }
233
240 public function getPage() {
241 return $this->mPage;
242 }
243
244 public function clear() {
245 $this->mRedirectedFrom = null; # Title object if set
246 $this->mRedirectUrl = false;
247 $this->mRevisionRecord = null;
248 $this->fetchResult = null;
249
250 // TODO hard-deprecate direct access to public fields
251
252 $this->mPage->clear();
253 }
254
262 public function getOldID() {
263 if ( $this->mOldId === null ) {
264 $this->mOldId = $this->getOldIDFromRequest();
265 }
266
267 return $this->mOldId;
268 }
269
275 public function getOldIDFromRequest() {
276 $this->mRedirectUrl = false;
277
278 $request = $this->getContext()->getRequest();
279 $oldid = $request->getIntOrNull( 'oldid' );
280
281 if ( $oldid === null ) {
282 return 0;
283 }
284
285 if ( $oldid !== 0 ) {
286 # Load the given revision and check whether the page is another one.
287 # In that case, update this instance to reflect the change.
288 if ( $oldid === $this->mPage->getLatest() ) {
289 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
290 } else {
291 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
292 if ( $this->mRevisionRecord !== null ) {
293 $revPageId = $this->mRevisionRecord->getPageId();
294 // Revision title doesn't match the page title given?
295 if ( $this->mPage->getId() != $revPageId ) {
296 $function = get_class( $this->mPage ) . '::newFromID';
297 $this->mPage = $function( $revPageId );
298 }
299 }
300 }
301 }
302
303 $oldRev = $this->mRevisionRecord;
304 if ( $request->getRawVal( 'direction' ) === 'next' ) {
305 $nextid = 0;
306 if ( $oldRev ) {
307 $nextRev = $this->revisionStore->getNextRevision( $oldRev );
308 if ( $nextRev ) {
309 $nextid = $nextRev->getId();
310 }
311 }
312 if ( $nextid ) {
313 $oldid = $nextid;
314 $this->mRevisionRecord = null;
315 } else {
316 $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
317 }
318 } elseif ( $request->getRawVal( 'direction' ) === 'prev' ) {
319 $previd = 0;
320 if ( $oldRev ) {
321 $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
322 if ( $prevRev ) {
323 $previd = $prevRev->getId();
324 }
325 }
326 if ( $previd ) {
327 $oldid = $previd;
328 $this->mRevisionRecord = null;
329 }
330 }
331
332 return $oldid;
333 }
334
344 public function fetchRevisionRecord() {
345 if ( $this->fetchResult ) {
346 return $this->mRevisionRecord;
347 }
348
349 $oldid = $this->getOldID();
350
351 // $this->mRevisionRecord might already be fetched by getOldIDFromRequest()
352 if ( !$this->mRevisionRecord ) {
353 if ( !$oldid ) {
354 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
355
356 if ( !$this->mRevisionRecord ) {
357 wfDebug( __METHOD__ . " failed to find page data for title " .
358 $this->getTitle()->getPrefixedText() );
359
360 // Output for this case is done by showMissingArticle().
361 $this->fetchResult = Status::newFatal( 'noarticletext' );
362 return null;
363 }
364 } else {
365 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
366
367 if ( !$this->mRevisionRecord ) {
368 wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid" );
369
370 $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
371 return null;
372 }
373 }
374 }
375
376 if ( !$this->mRevisionRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getAuthority() ) ) {
377 wfDebug( __METHOD__ . " failed to retrieve content of revision " . $this->mRevisionRecord->getId() );
378
379 // Output for this case is done by showDeletedRevisionHeader().
380 // title used in wikilinks, should not contain whitespaces
381 $this->fetchResult = new Status;
382 $title = $this->getTitle()->getPrefixedDBkey();
383
384 if ( $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
385 $this->fetchResult->fatal( 'rev-suppressed-text' );
386 } else {
387 $this->fetchResult->fatal( 'rev-deleted-text-permission', $title );
388 }
389
390 return null;
391 }
392
393 $this->fetchResult = Status::newGood( $this->mRevisionRecord );
394 return $this->mRevisionRecord;
395 }
396
402 public function isCurrent() {
403 # If no oldid, this is the current version.
404 if ( $this->getOldID() == 0 ) {
405 return true;
406 }
407
408 return $this->mPage->exists() &&
409 $this->mRevisionRecord &&
410 $this->mRevisionRecord->isCurrent();
411 }
412
421 public function getRevIdFetched() {
422 if ( $this->fetchResult && $this->fetchResult->isOK() ) {
423 return $this->fetchResult->value->getId();
424 } else {
425 return $this->mPage->getLatest();
426 }
427 }
428
433 public function view() {
434 $context = $this->getContext();
435 $useFileCache = $context->getConfig()->get( MainConfigNames::UseFileCache );
436
437 # Get variables from query string
438 # As side effect this will load the revision and update the title
439 # in a revision ID is passed in the request, so this should remain
440 # the first call of this method even if $oldid is used way below.
441 $oldid = $this->getOldID();
442
443 $authority = $context->getAuthority();
444 # Another check in case getOldID() is altering the title
445 $permissionStatus = PermissionStatus::newEmpty();
446 if ( !$authority
447 ->authorizeRead( 'read', $this->getTitle(), $permissionStatus )
448 ) {
449 wfDebug( __METHOD__ . ": denied on secondary read check" );
450 throw new PermissionsError( 'read', $permissionStatus );
451 }
452
453 $outputPage = $context->getOutput();
454 # getOldID() may as well want us to redirect somewhere else
455 if ( $this->mRedirectUrl ) {
456 $outputPage->redirect( $this->mRedirectUrl );
457 wfDebug( __METHOD__ . ": redirecting due to oldid" );
458
459 return;
460 }
461
462 # If we got diff in the query, we want to see a diff page instead of the article.
463 if ( $context->getRequest()->getCheck( 'diff' ) ) {
464 wfDebug( __METHOD__ . ": showing diff page" );
465 $this->showDiffPage();
466
467 return;
468 }
469
470 # Set page title (may be overridden from ParserOutput if title conversion is enabled or DISPLAYTITLE is used)
472 str_replace( '_', ' ', $this->getTitle()->getNsText() ),
473 ':',
474 $this->getTitle()->getText()
475 ) );
476
477 $outputPage->setArticleFlag( true );
478 # Allow frames by default
479 $outputPage->setPreventClickjacking( false );
480
481 $skin = $context->getSkin();
482 $skinOptions = $skin->getOptions();
483
484 $parserOptions = $this->getParserOptions();
485 $poOptions = [
486 'skin' => $skin,
487 'injectTOC' => $skinOptions['toc'],
488 ];
489 # Allow extensions to vary parser options used for article rendering
490 Hooks::runner()->onArticleParserOptions( $this, $parserOptions );
491 # Render printable version, use printable version cache
492 if ( $outputPage->isPrintable() ) {
493 $parserOptions->setIsPrintable( true );
494 $poOptions['enableSectionEditLinks'] = false;
495 $outputPage->prependHTML(
496 Html::warningBox(
497 $outputPage->msg( 'printableversion-deprecated-warning' )->escaped()
498 )
499 );
500 } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
501 !$authority->probablyCan( 'edit', $this->getTitle() )
502 ) {
503 $poOptions['enableSectionEditLinks'] = false;
504 }
505
506 # Try client and file cache
507 if ( $oldid === 0 && $this->mPage->checkTouched() ) {
508 # Try to stream the output from file cache
509 if ( $useFileCache && $this->tryFileCache() ) {
510 wfDebug( __METHOD__ . ": done file cache" );
511 # tell wgOut that output is taken care of
512 $outputPage->disable();
513 $this->mPage->doViewUpdates( $authority, $oldid );
514
515 return;
516 }
517 }
518
520 $this->showNamespaceHeader();
521
522 if ( $this->viewIsRenderAction ) {
523 $poOptions += [ 'absoluteURLs' => true ];
524 }
525 $poOptions += [ 'includeDebugInfo' => true ];
526
527 $continue =
528 $this->generateContentOutput( $authority, $parserOptions, $oldid, $outputPage, $poOptions );
529
530 if ( !$continue ) {
531 return;
532 }
533
534 # For the main page, overwrite the <title> element with the con-
535 # tents of 'pagetitle-view-mainpage' instead of the default (if
536 # that's not empty).
537 # This message always exists because it is in the i18n files
538 if ( $this->getTitle()->isMainPage() ) {
539 $msg = $context->msg( 'pagetitle-view-mainpage' )->inContentLanguage();
540 if ( !$msg->isDisabled() ) {
541 $outputPage->setHTMLTitle( $msg->text() );
542 }
543 }
544
545 # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
546 # This could use getTouched(), but that could be scary for major template edits.
547 $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
548
549 $this->showViewFooter();
550 $this->mPage->doViewUpdates( $authority, $oldid, $this->fetchRevisionRecord() );
551
552 # Load the postEdit module if the user just saved this revision
553 # See also EditPage::setPostEditCookie
554 $request = $context->getRequest();
556 $postEdit = $request->getCookie( $cookieKey );
557 if ( $postEdit ) {
558 # Clear the cookie. This also prevents caching of the response.
559 $request->response()->clearCookie( $cookieKey );
560 $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
561 $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
562 }
563 }
564
577 private function generateContentOutput(
578 Authority $performer,
579 ParserOptions $parserOptions,
580 int $oldid,
581 OutputPage $outputPage,
582 array $textOptions
583 ): bool {
584 # Should the parser cache be used?
585 $useParserCache = true;
586 $pOutput = null;
587 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
588
589 // NOTE: $outputDone and $useParserCache may be changed by the hook
590 $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
591 if ( $outputDone ) {
592 if ( $outputDone instanceof ParserOutput ) {
593 $pOutput = $outputDone;
594 }
595
596 if ( $pOutput ) {
597 $this->doOutputMetaData( $pOutput, $outputPage );
598 }
599 return true;
600 }
601
602 // Early abort if the page doesn't exist
603 if ( !$this->mPage->exists() ) {
604 wfDebug( __METHOD__ . ": showing missing article" );
605 $this->showMissingArticle();
606 $this->mPage->doViewUpdates( $performer );
607 return false; // skip all further output to OutputPage
608 }
609
610 // Try the latest parser cache
611 // NOTE: try latest-revision cache first to avoid loading revision.
612 if ( $useParserCache && !$oldid ) {
613 $pOutput = $parserOutputAccess->getCachedParserOutput(
614 $this->getPage(),
615 $parserOptions,
616 null,
617 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked
618 );
619
620 if ( $pOutput ) {
621 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
622 $this->doOutputMetaData( $pOutput, $outputPage );
623 return true;
624 }
625 }
626
627 $rev = $this->fetchRevisionRecord();
628 if ( !$this->fetchResult->isOK() ) {
629 $this->showViewError( $this->fetchResult->getWikiText(
630 false, false, $this->getContext()->getLanguage()
631 ) );
632 return true;
633 }
634
635 # Are we looking at an old revision
636 if ( $oldid ) {
637 $this->setOldSubtitle( $oldid );
638
639 if ( !$this->showDeletedRevisionHeader() ) {
640 wfDebug( __METHOD__ . ": cannot view deleted revision" );
641 return false; // skip all further output to OutputPage
642 }
643
644 // Try the old revision parser cache
645 // NOTE: Repeating cache check for old revision to avoid fetching $rev
646 // before it's absolutely necessary.
647 if ( $useParserCache ) {
648 $pOutput = $parserOutputAccess->getCachedParserOutput(
649 $this->getPage(),
650 $parserOptions,
651 $rev,
652 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked in fetchRevisionRecord
653 );
654
655 if ( $pOutput ) {
656 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
657 $this->doOutputMetaData( $pOutput, $outputPage );
658 return true;
659 }
660 }
661 }
662
663 # Ensure that UI elements requiring revision ID have
664 # the correct version information.
665 $outputPage->setRevisionId( $this->getRevIdFetched() );
666 $outputPage->setRevisionIsCurrent( $rev->isCurrent() );
667 # Preload timestamp to avoid a DB hit
668 $outputPage->setRevisionTimestamp( $rev->getTimestamp() );
669
670 # Pages containing custom CSS or JavaScript get special treatment
671 if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
672 $dir = $this->getContext()->getLanguage()->getDir();
673 $lang = $this->getContext()->getLanguage()->getHtmlCode();
674
675 $outputPage->wrapWikiMsg(
676 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
677 'clearyourcache'
678 );
679 $outputPage->addModuleStyles( 'mediawiki.action.styles' );
680 } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
681 $rev,
682 $this->getTitle(),
683 $oldid,
684 $outputPage )
685 ) {
686 // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
687 // Allow extensions do their own custom view for certain pages
688 $this->doOutputMetaData( $pOutput, $outputPage );
689 return true;
690 }
691
692 # Run the parse, protected by a pool counter
693 wfDebug( __METHOD__ . ": doing uncached parse" );
694
695 if ( !$rev ) {
696 // No revision, abort! Shouldn't happen.
697 return false;
698 }
699
700 $opt = 0;
701
702 // we already checked the cache in case 2, don't check again.
703 $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
704
705 // we already checked in fetchRevisionRecord()
706 $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
707
708 if ( !$rev->getId() || !$useParserCache ) {
709 // fake revision or uncacheable options
710 $opt |= ParserOutputAccess::OPT_NO_CACHE;
711 }
712
713 $renderStatus = $parserOutputAccess->getParserOutput(
714 $this->getPage(),
715 $parserOptions,
716 $rev,
717 $opt
718 );
719
720 $this->doOutputFromRenderStatus(
721 $rev,
722 $renderStatus,
723 $outputPage,
724 $textOptions
725 );
726
727 if ( !$renderStatus->isOK() ) {
728 return true;
729 }
730
731 $pOutput = $renderStatus->getValue();
732 $this->doOutputMetaData( $pOutput, $outputPage );
733 return true;
734 }
735
740 private function doOutputMetaData( ?ParserOutput $pOutput, OutputPage $outputPage ) {
741 # Adjust title for main page & pages with displaytitle
742 if ( $pOutput ) {
743 $this->adjustDisplayTitle( $pOutput );
744 }
745
746 # Check for any __NOINDEX__ tags on the page using $pOutput
747 $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
748 $outputPage->setIndexPolicy( $policy['index'] );
749 $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
750
751 $this->mParserOutput = $pOutput;
752 }
753
759 private function doOutputFromParserCache(
760 ParserOutput $pOutput,
761 OutputPage $outputPage,
762 array $textOptions
763 ) {
764 # Ensure that UI elements requiring revision ID have
765 # the correct version information.
766 $oldid = $pOutput->getCacheRevisionId() ?? $this->getRevIdFetched();
767 $outputPage->setRevisionId( $oldid );
768 $outputPage->setRevisionIsCurrent( $oldid === $this->mPage->getLatest() );
769 # Ensure that the skin has the necessary ToC information
770 # (and do this before OutputPage::addParserOutput() calls the
771 # OutputPageParserOutput hook)
772 $outputPage->setSections( $pOutput->getSections() );
773 $outputPage->addParserOutput( $pOutput, $textOptions );
774 # Preload timestamp to avoid a DB hit
775 $cachedTimestamp = $pOutput->getTimestamp();
776 if ( $cachedTimestamp !== null ) {
777 $outputPage->setRevisionTimestamp( $cachedTimestamp );
778 $this->mPage->setTimestamp( $cachedTimestamp );
779 }
780 }
781
788 private function doOutputFromRenderStatus(
789 ?RevisionRecord $rev,
790 Status $renderStatus,
791 OutputPage $outputPage,
792 array $textOptions
793 ) {
794 $context = $this->getContext();
795 $cdnMaxageStale = $context->getConfig()->get( MainConfigNames::CdnMaxageStale );
796 $ok = $renderStatus->isOK();
797
798 $pOutput = $ok ? $renderStatus->getValue() : null;
799
800 // Cache stale ParserOutput object with a short expiry
801 if ( $ok && $renderStatus->hasMessage( 'view-pool-dirty-output' ) ) {
802 $outputPage->setCdnMaxage( $cdnMaxageStale );
803 $outputPage->setLastModified( $pOutput->getCacheTime() );
804 $staleReason = $renderStatus->hasMessage( 'view-pool-contention' )
805 ? $context->msg( 'view-pool-contention' )
806 : $context->msg( 'view-pool-timeout' );
807 $outputPage->addHTML( "<!-- parser cache is expired, " .
808 "sending anyway due to $staleReason-->\n" );
809 }
810
811 if ( !$renderStatus->isOK() ) {
812 $this->showViewError( $renderStatus->getWikiText(
813 false, 'view-pool-error', $context->getLanguage()
814 ) );
815 return;
816 }
817
818 if ( $pOutput ) {
819 $outputPage->addParserOutput( $pOutput, $textOptions );
820 $outputPage->setSections( $pOutput->getSections() );
821 }
822
823 if ( $this->getRevisionRedirectTarget( $rev ) ) {
824 $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
825 $context->msg( 'redirectpagesub' )->parse() . "</span>" );
826 }
827 }
828
833 private function getRevisionRedirectTarget( RevisionRecord $revision ) {
834 // TODO: find a *good* place for the code that determines the redirect target for
835 // a given revision!
836 // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
837 $content = $revision->getContent( SlotRecord::MAIN );
838 return $content ? $content->getRedirectTarget() : null;
839 }
840
845 public function adjustDisplayTitle( ParserOutput $pOutput ) {
846 $out = $this->getContext()->getOutput();
847
848 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
849 $titleText = $pOutput->getTitleText();
850 if ( strval( $titleText ) !== '' ) {
851 $out->setPageTitle( $titleText );
852 $out->setDisplayTitle( $titleText );
853 }
854 }
855
860 protected function showDiffPage() {
861 $context = $this->getContext();
862 $request = $context->getRequest();
863 $user = $context->getUser();
864 $diff = $request->getVal( 'diff' );
865 $rcid = $request->getInt( 'rcid' );
866 $purge = $request->getRawVal( 'action' ) === 'purge';
867 $unhide = $request->getInt( 'unhide' ) == 1;
868 $oldid = $this->getOldID();
869
870 $rev = $this->fetchRevisionRecord();
871
872 if ( !$rev ) {
873 // T213621: $rev maybe null due to either lack of permission to view the
874 // revision or actually not existing. So let's try loading it from the id
875 $rev = $this->revisionStore->getRevisionById( $oldid );
876 if ( $rev ) {
877 // Revision exists but $user lacks permission to diff it.
878 // Do nothing here.
879 // The $rev will later be used to create standard diff elements however.
880 } else {
881 $context->getOutput()->setPageTitle( $context->msg( 'errorpagetitle' ) );
882 $msg = $context->msg( 'difference-missing-revision' )
883 ->params( $oldid )
884 ->numParams( 1 )
885 ->parseAsBlock();
886 $context->getOutput()->addHTML( $msg );
887 return;
888 }
889 }
890
891 $contentHandler = MediaWikiServices::getInstance()
892 ->getContentHandlerFactory()
893 ->getContentHandler(
894 $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
895 );
896 $de = $contentHandler->createDifferenceEngine(
897 $context,
898 $oldid,
899 $diff,
900 $rcid,
901 $purge,
902 $unhide
903 );
904 $de->setSlotDiffOptions( [
905 'diff-type' => $request->getVal( 'diff-type' ),
906 'expand-url' => $this->viewIsRenderAction
907 ] );
908 $de->showDiffPage( $this->isDiffOnlyView() );
909
910 // Run view updates for the newer revision being diffed (and shown
911 // below the diff if not diffOnly).
912 list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
913 // New can be false, convert it to 0 - this conveniently means the latest revision
914 $this->mPage->doViewUpdates( $context->getAuthority(), (int)$new );
915 }
916
917 protected function isDiffOnlyView() {
918 return $this->getContext()->getRequest()->getBool(
919 'diffonly',
920 $this->userOptionsLookup->getBoolOption( $this->getContext()->getUser(), 'diffonly' )
921 );
922 }
923
931 public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
932 $context = $this->getContext();
933 $mainConfig = $context->getConfig();
934 $articleRobotPolicies = $mainConfig->get( MainConfigNames::ArticleRobotPolicies );
935 $namespaceRobotPolicies = $mainConfig->get( MainConfigNames::NamespaceRobotPolicies );
936 $defaultRobotPolicy = $mainConfig->get( MainConfigNames::DefaultRobotPolicy );
937 $title = $this->getTitle();
938 $ns = $title->getNamespace();
939
940 # Don't index user and user talk pages for blocked users (T13443)
941 if ( ( $ns === NS_USER || $ns === NS_USER_TALK ) && !$title->isSubpage() ) {
942 $specificTarget = null;
943 $vagueTarget = null;
944 $titleText = $title->getText();
945 if ( IPUtils::isValid( $titleText ) ) {
946 $vagueTarget = $titleText;
947 } else {
948 $specificTarget = $titleText;
949 }
950 if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
951 return [
952 'index' => 'noindex',
953 'follow' => 'nofollow'
954 ];
955 }
956 }
957
958 if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
959 # Non-articles (special pages etc), and old revisions
960 return [
961 'index' => 'noindex',
962 'follow' => 'nofollow'
963 ];
964 } elseif ( $context->getOutput()->isPrintable() ) {
965 # Discourage indexing of printable versions, but encourage following
966 return [
967 'index' => 'noindex',
968 'follow' => 'follow'
969 ];
970 } elseif ( $context->getRequest()->getInt( 'curid' ) ) {
971 # For ?curid=x urls, disallow indexing
972 return [
973 'index' => 'noindex',
974 'follow' => 'follow'
975 ];
976 }
977
978 # Otherwise, construct the policy based on the various config variables.
979 $policy = self::formatRobotPolicy( $defaultRobotPolicy );
980
981 if ( isset( $namespaceRobotPolicies[$ns] ) ) {
982 # Honour customised robot policies for this namespace
983 $policy = array_merge(
984 $policy,
985 self::formatRobotPolicy( $namespaceRobotPolicies[$ns] )
986 );
987 }
988 if ( $title->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
989 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
990 # a final check that we have really got the parser output.
991 $policy = array_merge(
992 $policy,
993 [ 'index' => $pOutput->getIndexPolicy() ]
994 );
995 }
996
997 if ( isset( $articleRobotPolicies[$title->getPrefixedText()] ) ) {
998 # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
999 $policy = array_merge(
1000 $policy,
1001 self::formatRobotPolicy( $articleRobotPolicies[$title->getPrefixedText()] )
1002 );
1003 }
1004
1005 return $policy;
1006 }
1007
1015 public static function formatRobotPolicy( $policy ) {
1016 if ( is_array( $policy ) ) {
1017 return $policy;
1018 } elseif ( !$policy ) {
1019 return [];
1020 }
1021
1022 $arr = [];
1023 foreach ( explode( ',', $policy ) as $var ) {
1024 $var = trim( $var );
1025 if ( $var === 'index' || $var === 'noindex' ) {
1026 $arr['index'] = $var;
1027 } elseif ( $var === 'follow' || $var === 'nofollow' ) {
1028 $arr['follow'] = $var;
1029 }
1030 }
1031
1032 return $arr;
1033 }
1034
1042 public function showRedirectedFromHeader() {
1043 $context = $this->getContext();
1044 $redirectSources = $context->getConfig()->get( MainConfigNames::RedirectSources );
1045 $outputPage = $context->getOutput();
1046 $request = $context->getRequest();
1047 $rdfrom = $request->getVal( 'rdfrom' );
1048
1049 // Construct a URL for the current page view, but with the target title
1050 $query = $request->getValues();
1051 unset( $query['rdfrom'] );
1052 unset( $query['title'] );
1053 if ( $this->getTitle()->isRedirect() ) {
1054 // Prevent double redirects
1055 $query['redirect'] = 'no';
1056 }
1057 $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1058
1059 if ( isset( $this->mRedirectedFrom ) ) {
1060 // This is an internally redirected page view.
1061 // We'll need a backlink to the source page for navigation.
1062 if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1063 $redir = $this->linkRenderer->makeKnownLink(
1064 $this->mRedirectedFrom,
1065 null,
1066 [],
1067 [ 'redirect' => 'no' ]
1068 );
1069
1070 $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1071 $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1072 . "</span>" );
1073
1074 // Add the script to update the displayed URL and
1075 // set the fragment if one was specified in the redirect
1076 $outputPage->addJsConfigVars( [
1077 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1078 ] );
1079 $outputPage->addModules( 'mediawiki.action.view.redirect' );
1080
1081 // Add a <link rel="canonical"> tag
1082 $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1083
1084 // Tell the output object that the user arrived at this article through a redirect
1085 $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1086
1087 return true;
1088 }
1089 } elseif ( $rdfrom ) {
1090 // This is an externally redirected view, from some other wiki.
1091 // If it was reported from a trusted site, supply a backlink.
1092 if ( $redirectSources && preg_match( $redirectSources, $rdfrom ) ) {
1093 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1094 $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1095 $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1096 . "</span>" );
1097
1098 // Add the script to update the displayed URL
1099 $outputPage->addJsConfigVars( [
1100 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1101 ] );
1102 $outputPage->addModules( 'mediawiki.action.view.redirect' );
1103
1104 return true;
1105 }
1106 }
1107
1108 return false;
1109 }
1110
1115 public function showNamespaceHeader() {
1116 if ( $this->getTitle()->isTalkPage() && !$this->getContext()->msg( 'talkpageheader' )->isDisabled() ) {
1117 $this->getContext()->getOutput()->wrapWikiMsg(
1118 "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1119 [ 'talkpageheader' ]
1120 );
1121 }
1122 }
1123
1127 public function showViewFooter() {
1128 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1129 if ( $this->getTitle()->getNamespace() === NS_USER_TALK
1130 && IPUtils::isValid( $this->getTitle()->getText() )
1131 ) {
1132 $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1133 }
1134
1135 // Show a footer allowing the user to patrol the shown revision or page if possible
1136 $patrolFooterShown = $this->showPatrolFooter();
1137
1138 $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1139 }
1140
1151 public function showPatrolFooter() {
1152 $context = $this->getContext();
1153 $mainConfig = $context->getConfig();
1154 $useNPPatrol = $mainConfig->get( MainConfigNames::UseNPPatrol );
1155 $useRCPatrol = $mainConfig->get( MainConfigNames::UseRCPatrol );
1156 $useFilePatrol = $mainConfig->get( MainConfigNames::UseFilePatrol );
1157 // Allow hooks to decide whether to not output this at all
1158 if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1159 return false;
1160 }
1161
1162 $outputPage = $context->getOutput();
1163 $user = $context->getUser();
1164 $title = $this->getTitle();
1165 $rc = false;
1166
1167 if ( !$context->getAuthority()->probablyCan( 'patrol', $title )
1168 || !( $useRCPatrol || $useNPPatrol
1169 || ( $useFilePatrol && $title->inNamespace( NS_FILE ) ) )
1170 ) {
1171 // Patrolling is disabled or the user isn't allowed to
1172 return false;
1173 }
1174
1175 if ( $this->mRevisionRecord
1176 && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1177 ) {
1178 // The current revision is already older than what could be in the RC table
1179 // 6h tolerance because the RC might not be cleaned out regularly
1180 return false;
1181 }
1182
1183 // Check for cached results
1184 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1185 $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1186 if ( $cache->get( $key ) ) {
1187 return false;
1188 }
1189
1190 $dbr = wfGetDB( DB_REPLICA );
1191 $oldestRevisionTimestamp = $dbr->selectField(
1192 'revision',
1193 'MIN( rev_timestamp )',
1194 [ 'rev_page' => $title->getArticleID() ],
1195 __METHOD__
1196 );
1197
1198 // New page patrol: Get the timestamp of the oldest revision which
1199 // the revision table holds for the given page. Then we look
1200 // whether it's within the RC lifespan and if it is, we try
1201 // to get the recentchanges row belonging to that entry
1202 // (with rc_new = 1).
1203 $recentPageCreation = false;
1204 if ( $oldestRevisionTimestamp
1205 && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1206 ) {
1207 // 6h tolerance because the RC might not be cleaned out regularly
1208 $recentPageCreation = true;
1209 $rc = RecentChange::newFromConds(
1210 [
1211 'rc_new' => 1,
1212 'rc_timestamp' => $oldestRevisionTimestamp,
1213 'rc_namespace' => $title->getNamespace(),
1214 'rc_cur_id' => $title->getArticleID()
1215 ],
1216 __METHOD__
1217 );
1218 if ( $rc ) {
1219 // Use generic patrol message for new pages
1220 $markPatrolledMsg = $context->msg( 'markaspatrolledtext' );
1221 }
1222 }
1223
1224 // File patrol: Get the timestamp of the latest upload for this page,
1225 // check whether it is within the RC lifespan and if it is, we try
1226 // to get the recentchanges row belonging to that entry
1227 // (with rc_type = RC_LOG, rc_log_type = upload).
1228 $recentFileUpload = false;
1229 if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $useFilePatrol
1230 && $title->getNamespace() === NS_FILE ) {
1231 // Retrieve timestamp of most recent upload
1232 $newestUploadTimestamp = $dbr->selectField(
1233 'image',
1234 'MAX( img_timestamp )',
1235 [ 'img_name' => $title->getDBkey() ],
1236 __METHOD__
1237 );
1238 if ( $newestUploadTimestamp
1239 && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1240 ) {
1241 // 6h tolerance because the RC might not be cleaned out regularly
1242 $recentFileUpload = true;
1243 $rc = RecentChange::newFromConds(
1244 [
1245 'rc_type' => RC_LOG,
1246 'rc_log_type' => 'upload',
1247 'rc_timestamp' => $newestUploadTimestamp,
1248 'rc_namespace' => NS_FILE,
1249 'rc_cur_id' => $title->getArticleID()
1250 ],
1251 __METHOD__
1252 );
1253 if ( $rc ) {
1254 // Use patrol message specific to files
1255 $markPatrolledMsg = $context->msg( 'markaspatrolledtext-file' );
1256 }
1257 }
1258 }
1259
1260 if ( !$recentPageCreation && !$recentFileUpload ) {
1261 // Page creation and latest upload (for files) is too old to be in RC
1262
1263 // We definitely can't patrol so cache the information
1264 // When a new file version is uploaded, the cache is cleared
1265 $cache->set( $key, '1' );
1266
1267 return false;
1268 }
1269
1270 if ( !$rc ) {
1271 // Don't cache: This can be hit if the page gets accessed very fast after
1272 // its creation / latest upload or in case we have high replica DB lag. In case
1273 // the revision is too old, we will already return above.
1274 return false;
1275 }
1276
1277 if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1278 // Patrolled RC entry around
1279
1280 // Cache the information we gathered above in case we can't patrol
1281 // Don't cache in case we can patrol as this could change
1282 $cache->set( $key, '1' );
1283
1284 return false;
1285 }
1286
1287 if ( $rc->getPerformerIdentity()->equals( $user ) ) {
1288 // Don't show a patrol link for own creations/uploads. If the user could
1289 // patrol them, they already would be patrolled
1290 return false;
1291 }
1292
1293 $outputPage->setPreventClickjacking( true );
1294 if ( $context->getAuthority()->isAllowed( 'writeapi' ) ) {
1295 $outputPage->addModules( 'mediawiki.misc-authed-curate' );
1296 }
1297
1298 $link = $this->linkRenderer->makeKnownLink(
1299 $title,
1300 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $markPatrolledMsg is always set when $rc is set
1301 $markPatrolledMsg->text(),
1302 [],
1303 [
1304 'action' => 'markpatrolled',
1305 'rcid' => $rc->getAttribute( 'rc_id' ),
1306 ]
1307 );
1308
1309 $outputPage->addModuleStyles( 'mediawiki.action.styles' );
1310 $outputPage->addHTML(
1311 "<div class='patrollink' data-mw='interface'>" .
1312 $context->msg( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1313 '</div>'
1314 );
1315
1316 return true;
1317 }
1318
1325 public static function purgePatrolFooterCache( $articleID ) {
1326 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1327 $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1328 }
1329
1334 public function showMissingArticle() {
1335 $context = $this->getContext();
1336 $send404Code = $context->getConfig()->get( MainConfigNames::Send404Code );
1337
1338 $outputPage = $context->getOutput();
1339 // Whether the page is a root user page of an existing user (but not a subpage)
1340 $validUserPage = false;
1341
1342 $title = $this->getTitle();
1343
1344 $services = MediaWikiServices::getInstance();
1345
1346 $contextUser = $context->getUser();
1347
1348 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1349 if ( $title->getNamespace() === NS_USER
1350 || $title->getNamespace() === NS_USER_TALK
1351 ) {
1352 $rootPart = explode( '/', $title->getText() )[0];
1353 $user = User::newFromName( $rootPart, false /* allow IP users */ );
1354 $ip = $this->userNameUtils->isIP( $rootPart );
1355 $block = DatabaseBlock::newFromTarget( $user, $user );
1356
1357 if ( $user && $user->isRegistered() && $user->isHidden() &&
1358 !$context->getAuthority()->isAllowed( 'hideuser' )
1359 ) {
1360 // T120883 if the user is hidden and the viewer cannot see hidden
1361 // users, pretend like it does not exist at all.
1362 $user = false;
1363 }
1364
1365 if ( !( $user && $user->isRegistered() ) && !$ip ) { # User does not exist
1366 $outputPage->addHtml( Html::warningBox(
1367 $context->msg( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) )->parse(),
1368 'mw-userpage-userdoesnotexist'
1369 ) );
1370 } elseif (
1371 $block !== null &&
1372 $block->getType() != DatabaseBlock::TYPE_AUTO &&
1373 (
1374 $block->isSitewide() ||
1375 $user->isBlockedFrom( $title, true )
1376 )
1377 ) {
1378 // Show log extract if the user is sitewide blocked or is partially
1379 // blocked and not allowed to edit their user page or user talk page
1380 LogEventsList::showLogExtract(
1381 $outputPage,
1382 'block',
1383 $services->getNamespaceInfo()->getCanonicalName( NS_USER ) . ':' .
1384 $block->getTargetName(),
1385 '',
1386 [
1387 'lim' => 1,
1388 'showIfEmpty' => false,
1389 'msgKey' => [
1390 'blocked-notice-logextract',
1391 $user->getName() # Support GENDER in notice
1392 ]
1393 ]
1394 );
1395 $validUserPage = !$title->isSubpage();
1396 } else {
1397 $validUserPage = !$title->isSubpage();
1398 }
1399 }
1400
1401 $this->getHookRunner()->onShowMissingArticle( $this );
1402
1403 # Show delete and move logs if there were any such events.
1404 # The logging query can DOS the site when bots/crawlers cause 404 floods,
1405 # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1406 $dbCache = MediaWikiServices::getInstance()->getMainObjectStash();
1407 $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1408 $isRegistered = $contextUser->isRegistered();
1409 $sessionExists = $context->getRequest()->getSession()->isPersistent();
1410
1411 if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1412 $logTypes = [ 'delete', 'move', 'protect' ];
1413
1414 $dbr = wfGetDB( DB_REPLICA );
1415
1416 $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1417 // Give extensions a chance to hide their (unrelated) log entries
1418 $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1419 LogEventsList::showLogExtract(
1420 $outputPage,
1421 $logTypes,
1422 $title,
1423 '',
1424 [
1425 'lim' => 10,
1426 'conds' => $conds,
1427 'showIfEmpty' => false,
1428 'msgKey' => [ $isRegistered || $sessionExists
1429 ? 'moveddeleted-notice'
1430 : 'moveddeleted-notice-recent'
1431 ]
1432 ]
1433 );
1434 }
1435
1436 if ( !$this->mPage->hasViewableContent() && $send404Code && !$validUserPage ) {
1437 // If there's no backing content, send a 404 Not Found
1438 // for better machine handling of broken links.
1439 $context->getRequest()->response()->statusHeader( 404 );
1440 }
1441
1442 // Also apply the robot policy for nonexisting pages (even if a 404 was used)
1443 $policy = $this->getRobotPolicy( 'view' );
1444 $outputPage->setIndexPolicy( $policy['index'] );
1445 $outputPage->setFollowPolicy( $policy['follow'] );
1446
1447 $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1448
1449 if ( !$hookResult ) {
1450 return;
1451 }
1452
1453 # Show error message
1454 $oldid = $this->getOldID();
1455 if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1456 $text = $this->getTitle()->getDefaultMessageText() ?? '';
1457 $outputPage->addWikiTextAsContent( $text );
1458 } else {
1459 if ( $oldid ) {
1460 // T251066: Try loading the revision from the archive table.
1461 // Show link to view it if it exists and the user has permission to view it.
1462 $pa = new PageArchive( $title );
1463 $revRecord = $pa->getArchivedRevisionRecord( $oldid );
1464 if ( $revRecord && $revRecord->userCan(
1465 RevisionRecord::DELETED_TEXT,
1466 $context->getAuthority()
1467 ) && $context->getAuthority()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
1468 $text = $context->msg(
1469 'missing-revision-permission', $oldid,
1470 $revRecord->getTimestamp(),
1471 $title->getPrefixedDBkey()
1472 )->plain();
1473 } else {
1474 $text = $context->msg( 'missing-revision', $oldid )->plain();
1475 }
1476
1477 } elseif ( $context->getAuthority()->probablyCan( 'edit', $title ) ) {
1478 $message = $isRegistered ? 'noarticletext' : 'noarticletextanon';
1479 $text = $context->msg( $message )->plain();
1480 } else {
1481 $text = $context->msg( 'noarticletext-nopermission' )->plain();
1482 }
1483
1484 $dir = $context->getLanguage()->getDir();
1485 $lang = $context->getLanguage()->getHtmlCode();
1486 $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1487 'class' => "noarticletext mw-content-$dir",
1488 'dir' => $dir,
1489 'lang' => $lang,
1490 ] ) . "\n$text\n</div>" );
1491 }
1492 }
1493
1498 private function showViewError( string $errortext ) {
1499 $outputPage = $this->getContext()->getOutput();
1500 $outputPage->setPageTitle( $this->getContext()->msg( 'errorpagetitle' ) );
1501 $outputPage->disableClientCache();
1502 $outputPage->setRobotPolicy( 'noindex,nofollow' );
1503 $outputPage->clearHTML();
1504 $outputPage->addHTML( Html::errorBox( $outputPage->parseAsContent( $errortext ) ) );
1505 }
1506
1513 public function showDeletedRevisionHeader() {
1514 if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1515 // Not deleted
1516 return true;
1517 }
1518 $outputPage = $this->getContext()->getOutput();
1519 // Used in wikilinks, should not contain whitespaces
1520 $titleText = $this->getTitle()->getPrefixedDBkey();
1521 // If the user is not allowed to see it...
1522 if ( !$this->mRevisionRecord->userCan(
1523 RevisionRecord::DELETED_TEXT,
1524 $this->getContext()->getAuthority()
1525 ) ) {
1526 $outputPage->addHtml(
1527 Html::warningBox(
1528 $outputPage->msg( 'rev-deleted-text-permission', $titleText )->parse(),
1529 'plainlinks'
1530 )
1531 );
1532
1533 return false;
1534 // If the user needs to confirm that they want to see it...
1535 } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1536 # Give explanation and add a link to view the revision...
1537 $oldid = intval( $this->getOldID() );
1538 $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1539 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1540 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1541 $outputPage->addHtml(
1542 Html::warningBox(
1543 $outputPage->msg( $msg, $link )->parse(),
1544 'plainlinks'
1545 )
1546 );
1547
1548 return false;
1549 // We are allowed to see...
1550 } else {
1551 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1552 ? [ 'rev-suppressed-text-view', $titleText ]
1553 : [ 'rev-deleted-text-view', $titleText ];
1554 $outputPage->addHtml(
1555 Html::warningBox(
1556 $outputPage->msg( $msg[0], $msg[1] )->parse(),
1557 'plainlinks'
1558 )
1559 );
1560
1561 return true;
1562 }
1563 }
1564
1573 public function setOldSubtitle( $oldid = 0 ) {
1574 if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1575 return;
1576 }
1577
1578 $context = $this->getContext();
1579 $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1580
1581 # Cascade unhide param in links for easy deletion browsing
1582 $extraParams = [];
1583 if ( $unhide ) {
1584 $extraParams['unhide'] = 1;
1585 }
1586
1587 if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1588 $revisionRecord = $this->mRevisionRecord;
1589 } else {
1590 $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1591 }
1592
1593 $timestamp = $revisionRecord->getTimestamp();
1594
1595 $current = ( $oldid == $this->mPage->getLatest() );
1596 $language = $context->getLanguage();
1597 $user = $context->getUser();
1598
1599 $td = $language->userTimeAndDate( $timestamp, $user );
1600 $tddate = $language->userDate( $timestamp, $user );
1601 $tdtime = $language->userTime( $timestamp, $user );
1602
1603 # Show user links if allowed to see them. If hidden, then show them only if requested...
1604 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable revisionRecord known to exists
1605 $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1606
1607 $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1608 ? 'revision-info-current'
1609 : 'revision-info';
1610
1611 $outputPage = $context->getOutput();
1612 $outputPage->addModuleStyles( [
1613 'mediawiki.action.styles',
1614 'mediawiki.interface.helpers.styles'
1615 ] );
1616
1617 $revisionUser = $revisionRecord->getUser();
1618 $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1619 $context->msg( $infomsg, $td )
1620 ->rawParams( $userlinks )
1621 ->params(
1622 $revisionRecord->getId(),
1623 $tddate,
1624 $tdtime,
1625 $revisionUser ? $revisionUser->getName() : ''
1626 )
1627 ->rawParams( Linker::revComment(
1628 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable revisionRecord known to exists
1629 $revisionRecord,
1630 true,
1631 true
1632 ) )
1633 ->parse() .
1634 "</div>";
1635
1636 $lnk = $current
1637 ? $context->msg( 'currentrevisionlink' )->escaped()
1638 : $this->linkRenderer->makeKnownLink(
1639 $this->getTitle(),
1640 $context->msg( 'currentrevisionlink' )->text(),
1641 [],
1642 $extraParams
1643 );
1644 $curdiff = $current
1645 ? $context->msg( 'diff' )->escaped()
1646 : $this->linkRenderer->makeKnownLink(
1647 $this->getTitle(),
1648 $context->msg( 'diff' )->text(),
1649 [],
1650 [
1651 'diff' => 'cur',
1652 'oldid' => $oldid
1653 ] + $extraParams
1654 );
1655 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable revisionRecord known to exists
1656 $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1657 $prevlink = $prevExist
1658 ? $this->linkRenderer->makeKnownLink(
1659 $this->getTitle(),
1660 $context->msg( 'previousrevision' )->text(),
1661 [],
1662 [
1663 'direction' => 'prev',
1664 'oldid' => $oldid
1665 ] + $extraParams
1666 )
1667 : $context->msg( 'previousrevision' )->escaped();
1668 $prevdiff = $prevExist
1669 ? $this->linkRenderer->makeKnownLink(
1670 $this->getTitle(),
1671 $context->msg( 'diff' )->text(),
1672 [],
1673 [
1674 'diff' => 'prev',
1675 'oldid' => $oldid
1676 ] + $extraParams
1677 )
1678 : $context->msg( 'diff' )->escaped();
1679 $nextlink = $current
1680 ? $context->msg( 'nextrevision' )->escaped()
1681 : $this->linkRenderer->makeKnownLink(
1682 $this->getTitle(),
1683 $context->msg( 'nextrevision' )->text(),
1684 [],
1685 [
1686 'direction' => 'next',
1687 'oldid' => $oldid
1688 ] + $extraParams
1689 );
1690 $nextdiff = $current
1691 ? $context->msg( 'diff' )->escaped()
1692 : $this->linkRenderer->makeKnownLink(
1693 $this->getTitle(),
1694 $context->msg( 'diff' )->text(),
1695 [],
1696 [
1697 'diff' => 'next',
1698 'oldid' => $oldid
1699 ] + $extraParams
1700 );
1701
1703 $context->getAuthority(),
1704 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable revisionRecord known to exists
1705 $revisionRecord,
1706 $this->getTitle()
1707 );
1708 if ( $cdel !== '' ) {
1709 $cdel .= ' ';
1710 }
1711
1712 // the outer div is need for styling the revision info and nav in MobileFrontend
1713 $outputPage->addSubtitle(
1714 Html::warningBox(
1715 $revisionInfo .
1716 "<div id=\"mw-revision-nav\">" . $cdel .
1717 $context->msg( 'revision-nav' )->rawParams(
1718 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1719 )->escaped() . "</div>",
1720 'mw-revision'
1721 )
1722 );
1723 }
1724
1738 public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1739 wfDeprecated( __METHOD__, '1.30' );
1740 $lang = $this->getTitle()->getPageLanguage();
1741 $out = $this->getContext()->getOutput();
1742 if ( $appendSubtitle ) {
1743 $out->addSubtitle( $this->getContext()->msg( 'redirectpagesub' ) );
1744 }
1745 $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1746 return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1747 }
1748
1761 public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1762 if ( is_array( $target ) ) {
1763 // Up until 1.39, $target was allowed to be an array.
1764 wfDeprecatedMsg( 'The $target parameter can no longer be an array', '1.39' );
1765 $target = reset( $target ); // There really can only be one element (T296430)
1766 }
1767
1768 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1769
1770 $html = '<ul class="redirectText">';
1771 if ( $forceKnown ) {
1772 $link = $linkRenderer->makeKnownLink(
1773 $target,
1774 $target->getFullText(),
1775 [],
1776 // Make sure wiki page redirects are not followed
1777 $target->isRedirect() ? [ 'redirect' => 'no' ] : []
1778 );
1779 } else {
1780 $link = $linkRenderer->makeLink(
1781 $target,
1782 $target->getFullText(),
1783 [],
1784 // Make sure wiki page redirects are not followed
1785 $target->isRedirect() ? [ 'redirect' => 'no' ] : []
1786 );
1787 }
1788 $html .= '<li>' . $link . '</li>';
1789 $html .= '</ul>';
1790
1791 $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1792
1793 return '<div class="redirectMsg">' .
1794 '<p>' . $redirectToText . '</p>' .
1795 $html .
1796 '</div>';
1797 }
1798
1807 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1808 $out = $this->getContext()->getOutput();
1809 $msg = $out->msg( 'namespace-' . $this->getTitle()->getNamespace() . '-helppage' );
1810
1811 if ( !$msg->isDisabled() ) {
1812 $title = Title::newFromText( $msg->plain() );
1813 if ( $title instanceof Title ) {
1814 $out->addHelpLink( $title->getLocalURL(), true );
1815 }
1816 } else {
1817 $out->addHelpLink( $to, $overrideBaseUrl );
1818 }
1819 }
1820
1824 public function render() {
1825 $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1826 $this->getContext()->getOutput()->setArticleBodyOnly( true );
1827 // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1828 $this->viewIsRenderAction = true;
1829 $this->view();
1830 }
1831
1835 public function protect() {
1836 $form = new ProtectionForm( $this );
1837 $form->execute();
1838 }
1839
1843 public function unprotect() {
1844 $this->protect();
1845 }
1846
1860 public function doDelete( $reason, $suppress = false, $immediate = false ) {
1861 wfDeprecated( __METHOD__, '1.37' );
1862 $error = '';
1863 $context = $this->getContext();
1864 $outputPage = $context->getOutput();
1865 $user = $context->getUser();
1866 $status = $this->mPage->doDeleteArticleReal(
1867 $reason, $user, $suppress, null, $error,
1868 null, [], 'delete', $immediate
1869 );
1870
1871 if ( $status->isOK() ) {
1872 $deleted = $this->getTitle()->getPrefixedText();
1873
1874 $outputPage->setPageTitle( $context->msg( 'actioncomplete' ) );
1875 $outputPage->setRobotPolicy( 'noindex,nofollow' );
1876
1877 if ( $status->isGood() ) {
1878 $loglink = '[[Special:Log/delete|' . $context->msg( 'deletionlog' )->text() . ']]';
1879 $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
1880 $this->getHookRunner()->onArticleDeleteAfterSuccess( $this->getTitle(), $outputPage );
1881 } else {
1882 $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
1883 }
1884
1885 $outputPage->returnToMain( false );
1886 } else {
1887 $outputPage->setPageTitle(
1888 $context->msg( 'cannotdelete-title',
1889 $this->getTitle()->getPrefixedText() )
1890 );
1891
1892 if ( $error == '' ) {
1893 $outputPage->wrapWikiTextAsInterface(
1894 'error mw-error-cannotdelete',
1895 $status->getWikiText( false, false, $context->getLanguage() )
1896 );
1897 $deleteLogPage = new LogPage( 'delete' );
1898 $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1899
1900 LogEventsList::showLogExtract(
1901 $outputPage,
1902 'delete',
1903 $this->getTitle()
1904 );
1905 } else {
1906 $outputPage->addHTML( $error );
1907 }
1908 }
1909 }
1910
1911 /* Caching functions */
1912
1920 protected function tryFileCache() {
1921 static $called = false;
1922
1923 if ( $called ) {
1924 wfDebug( "Article::tryFileCache(): called twice!?" );
1925 return false;
1926 }
1927
1928 $called = true;
1929 if ( $this->isFileCacheable() ) {
1930 $cache = new HTMLFileCache( $this->getTitle(), 'view' );
1931 if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1932 wfDebug( "Article::tryFileCache(): about to load file" );
1933 $cache->loadFromFileCache( $this->getContext() );
1934 return true;
1935 } else {
1936 wfDebug( "Article::tryFileCache(): starting buffer" );
1937 ob_start( [ &$cache, 'saveToFileCache' ] );
1938 }
1939 } else {
1940 wfDebug( "Article::tryFileCache(): not cacheable" );
1941 }
1942
1943 return false;
1944 }
1945
1951 public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
1952 $cacheable = false;
1953
1954 if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
1955 $cacheable = $this->mPage->getId()
1956 && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1957 // Extension may have reason to disable file caching on some pages.
1958 if ( $cacheable ) {
1959 $cacheable = $this->getHookRunner()->onIsFileCacheable( $this ) ?? false;
1960 }
1961 }
1962
1963 return $cacheable;
1964 }
1965
1979 public function getParserOutput( $oldid = null, UserIdentity $user = null ) {
1980 if ( $user === null ) {
1981 $parserOptions = $this->getParserOptions();
1982 } else {
1983 $parserOptions = $this->mPage->makeParserOptions( $user );
1984 }
1985
1986 return $this->mPage->getParserOutput( $parserOptions, $oldid );
1987 }
1988
1993 public function getParserOptions() {
1994 return $this->mPage->makeParserOptions( $this->getContext() );
1995 }
1996
2003 public function setContext( $context ) {
2004 $this->mContext = $context;
2005 }
2006
2013 public function getContext() {
2014 if ( $this->mContext instanceof IContextSource ) {
2015 return $this->mContext;
2016 } else {
2017 wfDebug( __METHOD__ . " called and \$mContext is null. " .
2018 "Return RequestContext::getMain()" );
2019 return RequestContext::getMain();
2020 }
2021 }
2022
2032 public function __get( $fname ) {
2033 wfDeprecatedMsg( "Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
2034 '1.35' );
2035
2036 if ( property_exists( $this->mPage, $fname ) ) {
2037 return $this->mPage->$fname;
2038 }
2039 trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2040 }
2041
2051 public function __set( $fname, $fvalue ) {
2052 wfDeprecatedMsg( "Setting Article::\$$fname is deprecated since MediaWiki 1.35",
2053 '1.35' );
2054
2055 if ( property_exists( $this->mPage, $fname ) ) {
2056 $this->mPage->$fname = $fvalue;
2057 // Note: extensions may want to toss on new fields
2058 } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2059 $this->mPage->$fname = $fvalue;
2060 } else {
2061 trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2062 }
2063 }
2064
2070 public function getActionOverrides() {
2071 return $this->mPage->getActionOverrides();
2072 }
2073
2079 public function getTimestamp() {
2080 wfDeprecated( __METHOD__, '1.35' );
2081 return $this->mPage->getTimestamp();
2082 }
2083}
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:118
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()
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:48
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition Article.php:201
getContext()
Gets the context this Article is executed in.
Definition Article.php:2013
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition Article.php:275
getRedirectedFrom()
Get the page this view was redirected from.
Definition Article.php:212
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition Article.php:69
bool $viewIsRenderAction
Whether render() was called.
Definition Article.php:92
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition Article.php:433
__construct(Title $title, $oldId=null)
Definition Article.php:131
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition Article.php:931
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition Article.php:1325
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition Article.php:1860
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition Article.php:85
getOldID()
Definition Article.php:262
LinkRenderer $linkRenderer
Definition Article.php:97
getTitle()
Get the title object of the article.
Definition Article.php:230
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2070
isDiffOnlyView()
Definition Article.php:917
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition Article.php:845
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition Article.php:1513
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition Article.php:1993
IContextSource null $mContext
The context this Article is executed in.
Definition Article.php:57
getParserOutput( $oldid=null, UserIdentity $user=null)
#-
Definition Article.php:1979
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition Article.php:1761
protect()
action=protect handler
Definition Article.php:1835
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
Definition Article.php:402
showMissingArticle()
Show the error text for a missing article.
Definition Article.php:1334
__set( $fname, $fvalue)
Definition Article.php:2051
unprotect()
action=unprotect handler (alias)
Definition Article.php:1843
newPage(Title $title)
Definition Article.php:147
getPage()
Get the WikiPage object of this instance.
Definition Article.php:240
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition Article.php:1807
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition Article.php:72
getTimestamp()
Definition Article.php:2079
static newFromID( $id)
Constructor from a page id.
Definition Article.php:156
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition Article.php:66
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition Article.php:1015
fetchRevisionRecord()
Fetches the revision to work on.
Definition Article.php:344
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition Article.php:1738
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition Article.php:1151
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition Article.php:1573
showViewFooter()
Show the footer section of an ordinary page view.
Definition Article.php:1127
WikiPage $mPage
The WikiPage object of this instance.
Definition Article.php:60
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
Definition Article.php:221
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition Article.php:1951
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition Article.php:1920
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition Article.php:421
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition Article.php:1115
__get( $fname)
Definition Article.php:2032
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition Article.php:168
showDiffPage()
Show a diff page according to current request variables.
Definition Article.php:860
render()
Handle action=render.
Definition Article.php:1824
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition Article.php:1042
setContext( $context)
Sets the context this Article is executed in.
Definition Article.php:2003
getCacheRevisionId()
Definition CacheTime.php:98
Special handling for category description pages.
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:121
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.
Rendering of file description pages.
Definition ImagePage.php:31
Base class for language-specific code.
Definition Language.php:53
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:1607
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:2153
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:1371
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:1061
Class to simplify the use of log pages.
Definition LogPage.php:39
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Class that generates HTML anchor link elements for pages.
makeKnownLink( $target, $text=null, array $extraAttribs=[], array $query=[])
makeLink( $target, $text=null, array $extraAttribs=[], array $query=[])
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Service for getting rendered output of a given page.
A StatusValue for permission errors.
Page revision base class.
isCurrent()
Checks whether the revision record is a stored current revision.
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
getSlot( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns meta-data for the given slot.
getContent( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns the Content of the given slot of this revision.
getId( $wikiId=self::LOCAL)
Get revision ID.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
UserNameUtils service.
Provides access to user options.
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.
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.
setSections(array $sections)
Adds sections to OutputPage from ParserOutput.
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
setRevisionIsCurrent(bool $isCurrent)
Set whether the revision displayed (as set in ::setRevisionId()) is the latest revision of the page.
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.
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
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,...
disableClientCache()
Force the page to send nocache headers.
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.
setPreventClickjacking(bool $enable)
Set the prevent-clickjacking flag.
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?
static formatPageTitle( $nsText, $nsSeparator, $mainText)
Add HTML tags marking the parts of a page title, to be displayed in the first heading of the page.
Definition Parser.php:6518
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:190
Represents a title within MediaWiki.
Definition Title.php:49
internal since 1.36
Definition User.php:70
static newFromName( $name, $validate='valid')
Definition User.php:598
Base representation for an editable wiki page.
Definition WikiPage.php:62
getTitle()
Get the title object of the article.
Definition WikiPage.php:303
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:26
$content
Definition router.php:76
return true
Definition router.php:92
if(!isset( $args[0])) $lang