MediaWiki master
Article.php
Go to the documentation of this file.
1<?php
28use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
53use Wikimedia\IPUtils;
55use Wikimedia\NonSerializable\NonSerializableTrait;
57
67class Article implements Page {
68 use ProtectedHookAccessorTrait;
69 use NonSerializableTrait;
70
76 protected $mContext;
77
79 protected $mPage;
80
85 public $mOldId;
86
88 public $mRedirectedFrom = null;
89
91 public $mRedirectUrl = false;
92
97 private $fetchResult = null;
98
104 public $mParserOutput = null;
105
111 protected $viewIsRenderAction = false;
112
116 protected $linkRenderer;
117
121 private $revisionStore;
122
126 private $userNameUtils;
127
131 private $userOptionsLookup;
132
134 private $commentFormatter;
135
137 private $wikiPageFactory;
138
140 private $jobQueueGroup;
141
143 private $archivedRevisionLookup;
144
146
148 protected $blockStore;
149
156 private $mRevisionRecord = null;
157
162 public function __construct( Title $title, $oldId = null ) {
163 $this->mOldId = $oldId;
164 $this->mPage = $this->newPage( $title );
165
166 $services = MediaWikiServices::getInstance();
167 $this->linkRenderer = $services->getLinkRenderer();
168 $this->revisionStore = $services->getRevisionStore();
169 $this->userNameUtils = $services->getUserNameUtils();
170 $this->userOptionsLookup = $services->getUserOptionsLookup();
171 $this->commentFormatter = $services->getCommentFormatter();
172 $this->wikiPageFactory = $services->getWikiPageFactory();
173 $this->jobQueueGroup = $services->getJobQueueGroup();
174 $this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
175 $this->dbProvider = $services->getConnectionProvider();
176 $this->blockStore = $services->getDatabaseBlockStore();
177 }
178
183 protected function newPage( Title $title ) {
184 return new WikiPage( $title );
185 }
186
192 public static function newFromID( $id ) {
193 $t = Title::newFromID( $id );
194 return $t === null ? null : new static( $t );
195 }
196
204 public static function newFromTitle( $title, IContextSource $context ): self {
205 if ( $title->getNamespace() === NS_MEDIA ) {
206 // XXX: This should not be here, but where should it go?
207 $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
208 }
209
210 $page = null;
211 ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
212 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
213 ->onArticleFromTitle( $title, $page, $context );
214 if ( !$page ) {
215 switch ( $title->getNamespace() ) {
216 case NS_FILE:
217 $page = new ImagePage( $title );
218 break;
219 case NS_CATEGORY:
220 $page = new CategoryPage( $title );
221 break;
222 default:
223 $page = new Article( $title );
224 }
225 }
226 $page->setContext( $context );
227
228 return $page;
229 }
230
238 public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
239 $article = self::newFromTitle( $page->getTitle(), $context );
240 $article->mPage = $page; // override to keep process cached vars
241 return $article;
242 }
243
249 public function getRedirectedFrom() {
250 return $this->mRedirectedFrom;
251 }
252
258 public function setRedirectedFrom( Title $from ) {
259 $this->mRedirectedFrom = $from;
260 }
261
267 public function getTitle() {
268 return $this->mPage->getTitle();
269 }
270
277 public function getPage() {
278 return $this->mPage;
279 }
280
281 public function clear() {
282 $this->mRedirectedFrom = null; # Title object if set
283 $this->mRedirectUrl = false;
284 $this->mRevisionRecord = null;
285 $this->fetchResult = null;
286
287 // TODO hard-deprecate direct access to public fields
288
289 $this->mPage->clear();
290 }
291
299 public function getOldID() {
300 if ( $this->mOldId === null ) {
301 $this->mOldId = $this->getOldIDFromRequest();
302 }
303
304 return $this->mOldId;
305 }
306
312 public function getOldIDFromRequest() {
313 $this->mRedirectUrl = false;
314
315 $request = $this->getContext()->getRequest();
316 $oldid = $request->getIntOrNull( 'oldid' );
317
318 if ( $oldid === null ) {
319 return 0;
320 }
321
322 if ( $oldid !== 0 ) {
323 # Load the given revision and check whether the page is another one.
324 # In that case, update this instance to reflect the change.
325 if ( $oldid === $this->mPage->getLatest() ) {
326 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
327 } else {
328 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
329 if ( $this->mRevisionRecord !== null ) {
330 $revPageId = $this->mRevisionRecord->getPageId();
331 // Revision title doesn't match the page title given?
332 if ( $this->mPage->getId() !== $revPageId ) {
333 $this->mPage = $this->wikiPageFactory->newFromID( $revPageId );
334 }
335 }
336 }
337 }
338
339 $oldRev = $this->mRevisionRecord;
340 if ( $request->getRawVal( 'direction' ) === 'next' ) {
341 $nextid = 0;
342 if ( $oldRev ) {
343 $nextRev = $this->revisionStore->getNextRevision( $oldRev );
344 if ( $nextRev ) {
345 $nextid = $nextRev->getId();
346 }
347 }
348 if ( $nextid ) {
349 $oldid = $nextid;
350 $this->mRevisionRecord = null;
351 } else {
352 $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
353 }
354 } elseif ( $request->getRawVal( 'direction' ) === 'prev' ) {
355 $previd = 0;
356 if ( $oldRev ) {
357 $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
358 if ( $prevRev ) {
359 $previd = $prevRev->getId();
360 }
361 }
362 if ( $previd ) {
363 $oldid = $previd;
364 $this->mRevisionRecord = null;
365 }
366 }
367
368 return $oldid;
369 }
370
380 public function fetchRevisionRecord() {
381 if ( $this->fetchResult ) {
382 return $this->mRevisionRecord;
383 }
384
385 $oldid = $this->getOldID();
386
387 // $this->mRevisionRecord might already be fetched by getOldIDFromRequest()
388 if ( !$this->mRevisionRecord ) {
389 if ( !$oldid ) {
390 $this->mRevisionRecord = $this->mPage->getRevisionRecord();
391
392 if ( !$this->mRevisionRecord ) {
393 wfDebug( __METHOD__ . " failed to find page data for title " .
394 $this->getTitle()->getPrefixedText() );
395
396 // Output for this case is done by showMissingArticle().
397 $this->fetchResult = Status::newFatal( 'noarticletext' );
398 return null;
399 }
400 } else {
401 $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
402
403 if ( !$this->mRevisionRecord ) {
404 wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid" );
405
406 $this->fetchResult = Status::newFatal( $this->getMissingRevisionMsg( $oldid ) );
407 return null;
408 }
409 }
410 }
411
412 if ( !$this->mRevisionRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getAuthority() ) ) {
413 wfDebug( __METHOD__ . " failed to retrieve content of revision " . $this->mRevisionRecord->getId() );
414
415 // Output for this case is done by showDeletedRevisionHeader().
416 // title used in wikilinks, should not contain whitespaces
417 $this->fetchResult = new Status;
418 $title = $this->getTitle()->getPrefixedDBkey();
419
420 if ( $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
421 $this->fetchResult->fatal( 'rev-suppressed-text' );
422 } else {
423 $this->fetchResult->fatal( 'rev-deleted-text-permission', $title );
424 }
425
426 return null;
427 }
428
429 $this->fetchResult = Status::newGood( $this->mRevisionRecord );
430 return $this->mRevisionRecord;
431 }
432
438 public function isCurrent() {
439 # If no oldid, this is the current version.
440 if ( $this->getOldID() == 0 ) {
441 return true;
442 }
443
444 return $this->mPage->exists() &&
445 $this->mRevisionRecord &&
446 $this->mRevisionRecord->isCurrent();
447 }
448
457 public function getRevIdFetched() {
458 if ( $this->fetchResult && $this->fetchResult->isOK() ) {
460 $rev = $this->fetchResult->getValue();
461 return $rev->getId();
462 } else {
463 return $this->mPage->getLatest();
464 }
465 }
466
471 public function view() {
472 $context = $this->getContext();
473 $useFileCache = $context->getConfig()->get( MainConfigNames::UseFileCache );
474
475 # Get variables from query string
476 # As side effect this will load the revision and update the title
477 # in a revision ID is passed in the request, so this should remain
478 # the first call of this method even if $oldid is used way below.
479 $oldid = $this->getOldID();
480
481 $authority = $context->getAuthority();
482 # Another check in case getOldID() is altering the title
483 $permissionStatus = PermissionStatus::newEmpty();
484 if ( !$authority
485 ->authorizeRead( 'read', $this->getTitle(), $permissionStatus )
486 ) {
487 wfDebug( __METHOD__ . ": denied on secondary read check" );
488 throw new PermissionsError( 'read', $permissionStatus );
489 }
490
491 $outputPage = $context->getOutput();
492 # getOldID() may as well want us to redirect somewhere else
493 if ( $this->mRedirectUrl ) {
494 $outputPage->redirect( $this->mRedirectUrl );
495 wfDebug( __METHOD__ . ": redirecting due to oldid" );
496
497 return;
498 }
499
500 # If we got diff in the query, we want to see a diff page instead of the article.
501 if ( $context->getRequest()->getCheck( 'diff' ) ) {
502 wfDebug( __METHOD__ . ": showing diff page" );
503 $this->showDiffPage();
504
505 return;
506 }
507
508 # Set page title (may be overridden from ParserOutput if title conversion is enabled or DISPLAYTITLE is used)
509 $outputPage->setPageTitle( Parser::formatPageTitle(
510 str_replace( '_', ' ', $this->getTitle()->getNsText() ),
511 ':',
512 $this->getTitle()->getText()
513 ) );
514
515 $outputPage->setArticleFlag( true );
516 # Allow frames by default
517 $outputPage->setPreventClickjacking( false );
518
519 $parserOptions = $this->getParserOptions();
520
521 $poOptions = [];
522 # Allow extensions to vary parser options used for article rendering
523 ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
524 ->onArticleParserOptions( $this, $parserOptions );
525 # Render printable version, use printable version cache
526 if ( $outputPage->isPrintable() ) {
527 $parserOptions->setIsPrintable( true );
528 $poOptions['enableSectionEditLinks'] = false;
529 $outputPage->prependHTML(
530 Html::warningBox(
531 $outputPage->msg( 'printableversion-deprecated-warning' )->escaped()
532 )
533 );
534 } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
535 !$authority->probablyCan( 'edit', $this->getTitle() )
536 ) {
537 $poOptions['enableSectionEditLinks'] = false;
538 }
539
540 # Try client and file cache
541 if ( $oldid === 0 && $this->mPage->checkTouched() ) {
542 # Try to stream the output from file cache
543 if ( $useFileCache && $this->tryFileCache() ) {
544 wfDebug( __METHOD__ . ": done file cache" );
545 # tell wgOut that output is taken care of
546 $outputPage->disable();
547 $this->mPage->doViewUpdates( $authority, $oldid );
548
549 return;
550 }
551 }
552
553 $this->showRedirectedFromHeader();
554 $this->showNamespaceHeader();
555
556 if ( $this->viewIsRenderAction ) {
557 $poOptions += [ 'absoluteURLs' => true ];
558 }
559 $poOptions += [ 'includeDebugInfo' => true ];
560
561 try {
562 $continue =
563 $this->generateContentOutput( $authority, $parserOptions, $oldid, $outputPage, $poOptions );
564 } catch ( BadRevisionException $e ) {
565 $continue = false;
566 $this->showViewError( wfMessage( 'badrevision' )->text() );
567 }
568
569 if ( !$continue ) {
570 return;
571 }
572
573 # For the main page, overwrite the <title> element with the con-
574 # tents of 'pagetitle-view-mainpage' instead of the default (if
575 # that's not empty).
576 # This message always exists because it is in the i18n files
577 if ( $this->getTitle()->isMainPage() ) {
578 $msg = $context->msg( 'pagetitle-view-mainpage' )->inContentLanguage();
579 if ( !$msg->isDisabled() ) {
580 $outputPage->setHTMLTitle( $msg->text() );
581 }
582 }
583
584 # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
585 # This could use getTouched(), but that could be scary for major template edits.
586 $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), ExpirationAwareness::TTL_DAY );
587
588 $this->showViewFooter();
589 $this->mPage->doViewUpdates( $authority, $oldid, $this->fetchRevisionRecord() );
590
591 # Load the postEdit module if the user just saved this revision
592 # See also EditPage::setPostEditCookie
593 $request = $context->getRequest();
594 $cookieKey = EditPage::POST_EDIT_COOKIE_KEY_PREFIX . $this->getRevIdFetched();
595 $postEdit = $request->getCookie( $cookieKey );
596 if ( $postEdit ) {
597 # Clear the cookie. This also prevents caching of the response.
598 $request->response()->clearCookie( $cookieKey );
599 $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
600 $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
601 if ( $this->getContext()->getConfig()->get( 'EnableEditRecovery' )
602 && $this->userOptionsLookup->getOption( $this->getContext()->getUser(), 'editrecovery' )
603 ) {
604 $outputPage->addModules( 'mediawiki.editRecovery.postEdit' );
605 }
606 }
607 }
608
621 private function generateContentOutput(
622 Authority $performer,
623 ParserOptions $parserOptions,
624 int $oldid,
625 OutputPage $outputPage,
626 array $textOptions
627 ): bool {
628 # Should the parser cache be used?
629 $useParserCache = true;
630 $pOutput = null;
631 $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
632
633 // NOTE: $outputDone and $useParserCache may be changed by the hook
634 $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
635 if ( $outputDone ) {
636 if ( $outputDone instanceof ParserOutput ) {
637 $pOutput = $outputDone;
638 }
639
640 if ( $pOutput ) {
641 $this->doOutputMetaData( $pOutput, $outputPage );
642 }
643 return true;
644 }
645
646 // Early abort if the page doesn't exist
647 if ( !$this->mPage->exists() ) {
648 wfDebug( __METHOD__ . ": showing missing article" );
649 $this->showMissingArticle();
650 $this->mPage->doViewUpdates( $performer );
651 return false; // skip all further output to OutputPage
652 }
653
654 // Try the latest parser cache
655 // NOTE: try latest-revision cache first to avoid loading revision.
656 if ( $useParserCache && !$oldid ) {
657 $pOutput = $parserOutputAccess->getCachedParserOutput(
658 $this->getPage(),
659 $parserOptions,
660 null,
661 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked
662 );
663
664 if ( $pOutput ) {
665 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
666 $this->doOutputMetaData( $pOutput, $outputPage );
667 return true;
668 }
669 }
670
671 $rev = $this->fetchRevisionRecord();
672 if ( !$this->fetchResult->isOK() ) {
673 $this->showViewError( $this->fetchResult->getWikiText(
674 false, false, $this->getContext()->getLanguage()
675 ) );
676 return true;
677 }
678
679 # Are we looking at an old revision
680 if ( $oldid ) {
681 $this->setOldSubtitle( $oldid );
682
683 if ( !$this->showDeletedRevisionHeader() ) {
684 wfDebug( __METHOD__ . ": cannot view deleted revision" );
685 return false; // skip all further output to OutputPage
686 }
687
688 // Try the old revision parser cache
689 // NOTE: Repeating cache check for old revision to avoid fetching $rev
690 // before it's absolutely necessary.
691 if ( $useParserCache ) {
692 $pOutput = $parserOutputAccess->getCachedParserOutput(
693 $this->getPage(),
694 $parserOptions,
695 $rev,
696 ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked in fetchRevisionRecord
697 );
698
699 if ( $pOutput ) {
700 $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
701 $this->doOutputMetaData( $pOutput, $outputPage );
702 return true;
703 }
704 }
705 }
706
707 # Ensure that UI elements requiring revision ID have
708 # the correct version information. (This may be overwritten after creation of ParserOutput)
709 $outputPage->setRevisionId( $this->getRevIdFetched() );
710 $outputPage->setRevisionIsCurrent( $rev->isCurrent() );
711 # Preload timestamp to avoid a DB hit
712 $outputPage->setRevisionTimestamp( $rev->getTimestamp() );
713
714 # Pages containing custom CSS or JavaScript get special treatment
715 if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
716 $dir = $this->getContext()->getLanguage()->getDir();
717 $lang = $this->getContext()->getLanguage()->getHtmlCode();
718
719 $outputPage->wrapWikiMsg(
720 "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
721 'clearyourcache'
722 );
723 $outputPage->addModuleStyles( 'mediawiki.action.styles' );
724 } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
725 $rev,
726 $this->getTitle(),
727 $oldid,
728 $outputPage )
729 ) {
730 // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
731 // Allow extensions do their own custom view for certain pages
732 $this->doOutputMetaData( $pOutput, $outputPage );
733 return true;
734 }
735
736 # Run the parse, protected by a pool counter
737 wfDebug( __METHOD__ . ": doing uncached parse" );
738
739 $opt = 0;
740
741 // we already checked the cache in case 2, don't check again.
742 $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
743
744 // we already checked in fetchRevisionRecord()
745 $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
746
747 // Attempt to trigger WikiPage::triggerOpportunisticLinksUpdate
748 // Ideally this should not be the responsibility of the ParserCache to control this.
749 // See https://phabricator.wikimedia.org/T329842#8816557 for more context.
750 $opt |= ParserOutputAccess::OPT_LINKS_UPDATE;
751
752 if ( !$rev->getId() || !$useParserCache ) {
753 // fake revision or uncacheable options
754 $opt |= ParserOutputAccess::OPT_NO_CACHE;
755 }
756
757 $renderStatus = $parserOutputAccess->getParserOutput(
758 $this->getPage(),
759 $parserOptions,
760 $rev,
761 $opt
762 );
763
764 // T327164: If parsoid cache warming is enabled, we want to ensure that the page
765 // the user is currently looking at has a cached parsoid rendering, in case they
766 // open visual editor. The cache entry would typically be missing if it has expired
767 // from the cache or it was invalidated by RefreshLinksJob. When "traditional"
768 // parser output has been invalidated by RefreshLinksJob, we will render it on
769 // the fly when a user requests the page, and thereby populate the cache again,
770 // per the code above.
771 // The code below is intended to do the same for parsoid output, but asynchronously
772 // in a job, so the user does not have to wait.
773 // Note that we get here if the traditional parser output was missing from the cache.
774 // We do not check if the parsoid output is present in the cache, because that check
775 // takes time. The assumption is that if we have traditional parser output
776 // cached, we probably also have parsoid output cached.
777 // So we leave it to ParsoidCachePrewarmJob to determine whether or not parsing is
778 // needed.
779 if ( $oldid === 0 || $oldid === $this->getPage()->getLatest() ) {
780 $parsoidCacheWarmingEnabled = $this->getContext()->getConfig()
781 ->get( MainConfigNames::ParsoidCacheConfig )['WarmParsoidParserCache'];
782
783 if ( $parsoidCacheWarmingEnabled ) {
784 $parsoidJobSpec = ParsoidCachePrewarmJob::newSpec(
785 $rev->getId(),
786 $this->getPage()->toPageRecord(),
787 [ 'causeAction' => 'view' ]
788 );
789 $this->jobQueueGroup->lazyPush( $parsoidJobSpec );
790 }
791 }
792
793 $this->doOutputFromRenderStatus(
794 $rev,
795 $renderStatus,
796 $outputPage,
797 $textOptions
798 );
799
800 if ( !$renderStatus->isOK() ) {
801 return true;
802 }
803
804 $pOutput = $renderStatus->getValue();
805 $this->doOutputMetaData( $pOutput, $outputPage );
806 return true;
807 }
808
813 private function doOutputMetaData( ?ParserOutput $pOutput, OutputPage $outputPage ) {
814 # Adjust title for main page & pages with displaytitle
815 if ( $pOutput ) {
816 $this->adjustDisplayTitle( $pOutput );
817
818 // It would be nice to automatically set this during the first call
819 // to OutputPage::addParserOutputMetadata, but we can't because doing
820 // so would break non-pageview actions where OutputPage::getContLangForJS
821 // has different requirements.
822 $pageLang = $pOutput->getLanguage();
823 if ( $pageLang ) {
824 $outputPage->setContentLangForJS( $pageLang );
825 }
826 }
827
828 # Check for any __NOINDEX__ tags on the page using $pOutput
829 $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
830 $outputPage->setIndexPolicy( $policy['index'] );
831 $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
832
833 $this->mParserOutput = $pOutput;
834 }
835
841 private function doOutputFromParserCache(
842 ParserOutput $pOutput,
843 OutputPage $outputPage,
844 array $textOptions
845 ) {
846 # Ensure that UI elements requiring revision ID have
847 # the correct version information.
848 $oldid = $pOutput->getCacheRevisionId() ?? $this->getRevIdFetched();
849 $outputPage->setRevisionId( $oldid );
850 $outputPage->setRevisionIsCurrent( $oldid === $this->mPage->getLatest() );
851 $outputPage->addParserOutput( $pOutput, $textOptions );
852 # Preload timestamp to avoid a DB hit
853 $cachedTimestamp = $pOutput->getRevisionTimestamp();
854 if ( $cachedTimestamp !== null ) {
855 $outputPage->setRevisionTimestamp( $cachedTimestamp );
856 $this->mPage->setTimestamp( $cachedTimestamp );
857 }
858 }
859
866 private function doOutputFromRenderStatus(
867 RevisionRecord $rev,
868 Status $renderStatus,
869 OutputPage $outputPage,
870 array $textOptions
871 ) {
872 $context = $this->getContext();
873 if ( !$renderStatus->isOK() ) {
874 $this->showViewError( $renderStatus->getWikiText(
875 false, 'view-pool-error', $context->getLanguage()
876 ) );
877 return;
878 }
879
880 $pOutput = $renderStatus->getValue();
881
882 // Cache stale ParserOutput object with a short expiry
883 if ( $renderStatus->hasMessage( 'view-pool-dirty-output' ) ) {
884 $outputPage->lowerCdnMaxage( $context->getConfig()->get( MainConfigNames::CdnMaxageStale ) );
885 $outputPage->setLastModified( $pOutput->getCacheTime() );
886 $staleReason = $renderStatus->hasMessage( 'view-pool-contention' )
887 ? $context->msg( 'view-pool-contention' )->escaped()
888 : $context->msg( 'view-pool-timeout' )->escaped();
889 $outputPage->addHTML( "<!-- parser cache is expired, " .
890 "sending anyway due to $staleReason-->\n" );
891
892 // Ensure OutputPage knowns the id from the dirty cache, but keep the current flag (T341013)
893 $cachedId = $pOutput->getCacheRevisionId();
894 if ( $cachedId !== null ) {
895 $outputPage->setRevisionId( $cachedId );
896 $outputPage->setRevisionTimestamp( $pOutput->getTimestamp() );
897 }
898 }
899
900 $outputPage->addParserOutput( $pOutput, $textOptions );
901
902 if ( $this->getRevisionRedirectTarget( $rev ) ) {
903 $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
904 $context->msg( 'redirectpagesub' )->parse() . "</span>" );
905 }
906 }
907
912 private function getRevisionRedirectTarget( RevisionRecord $revision ) {
913 // TODO: find a *good* place for the code that determines the redirect target for
914 // a given revision!
915 // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
916 $content = $revision->getContent( SlotRecord::MAIN );
917 return $content ? $content->getRedirectTarget() : null;
918 }
919
924 public function adjustDisplayTitle( ParserOutput $pOutput ) {
925 $out = $this->getContext()->getOutput();
926
927 # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
928 $titleText = $pOutput->getTitleText();
929 if ( strval( $titleText ) !== '' ) {
930 $out->setPageTitle( $titleText );
931 $out->setDisplayTitle( $titleText );
932 }
933 }
934
939 protected function showDiffPage() {
940 $context = $this->getContext();
941 $outputPage = $context->getOutput();
942 $outputPage->addBodyClasses( 'mw-article-diff' );
943 $request = $context->getRequest();
944 $diff = $request->getVal( 'diff' );
945 $rcid = $request->getInt( 'rcid' );
946 $purge = $request->getRawVal( 'action' ) === 'purge';
947 $unhide = $request->getInt( 'unhide' ) === 1;
948 $oldid = $this->getOldID();
949
950 $rev = $this->fetchRevisionRecord();
951
952 if ( !$rev ) {
953 // T213621: $rev maybe null due to either lack of permission to view the
954 // revision or actually not existing. So let's try loading it from the id
955 $rev = $this->revisionStore->getRevisionById( $oldid );
956 if ( $rev ) {
957 // Revision exists but $user lacks permission to diff it.
958 // Do nothing here.
959 // The $rev will later be used to create standard diff elements however.
960 } else {
961 $outputPage->setPageTitleMsg( $context->msg( 'errorpagetitle' ) );
962 $msg = $context->msg( 'difference-missing-revision' )
963 ->params( $oldid )
964 ->numParams( 1 )
965 ->parseAsBlock();
966 $outputPage->addHTML( $msg );
967 return;
968 }
969 }
970
971 $services = MediaWikiServices::getInstance();
972
973 $contentHandler = $services
974 ->getContentHandlerFactory()
975 ->getContentHandler(
976 $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
977 );
978 $de = $contentHandler->createDifferenceEngine(
979 $context,
980 $oldid,
981 $diff,
982 $rcid,
983 $purge,
984 $unhide
985 );
986
987 $diffType = $request->getVal( 'diff-type' );
988
989 if ( $diffType === null ) {
990 $diffType = $this->userOptionsLookup
991 ->getOption( $context->getUser(), 'diff-type' );
992 } else {
993 $de->setExtraQueryParams( [ 'diff-type' => $diffType ] );
994 }
995
996 $de->setSlotDiffOptions( [
997 'diff-type' => $diffType,
998 'expand-url' => $this->viewIsRenderAction,
999 'inline-toggle' => true,
1000 ] );
1001 $de->showDiffPage( $this->isDiffOnlyView() );
1002
1003 // Run view updates for the newer revision being diffed (and shown
1004 // below the diff if not diffOnly).
1005 [ , $new ] = $de->mapDiffPrevNext( $oldid, $diff );
1006 // New can be false, convert it to 0 - this conveniently means the latest revision
1007 $this->mPage->doViewUpdates( $context->getAuthority(), (int)$new );
1008
1009 // Add link to help page; see T321569
1010 $context->getOutput()->addHelpLink( 'Help:Diff' );
1011 }
1012
1013 protected function isDiffOnlyView() {
1014 return $this->getContext()->getRequest()->getBool(
1015 'diffonly',
1016 $this->userOptionsLookup->getBoolOption( $this->getContext()->getUser(), 'diffonly' )
1017 );
1018 }
1019
1027 public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
1028 $context = $this->getContext();
1029 $mainConfig = $context->getConfig();
1030 $articleRobotPolicies = $mainConfig->get( MainConfigNames::ArticleRobotPolicies );
1031 $namespaceRobotPolicies = $mainConfig->get( MainConfigNames::NamespaceRobotPolicies );
1032 $defaultRobotPolicy = $mainConfig->get( MainConfigNames::DefaultRobotPolicy );
1033 $title = $this->getTitle();
1034 $ns = $title->getNamespace();
1035
1036 # Don't index user and user talk pages for blocked users (T13443)
1037 if ( $ns === NS_USER || $ns === NS_USER_TALK ) {
1038 $specificTarget = null;
1039 $vagueTarget = null;
1040 $titleText = $title->getText();
1041 if ( IPUtils::isValid( $titleText ) ) {
1042 $vagueTarget = $titleText;
1043 } else {
1044 $specificTarget = $title->getRootText();
1045 }
1046 if ( $this->blockStore->newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
1047 return [
1048 'index' => 'noindex',
1049 'follow' => 'nofollow'
1050 ];
1051 }
1052 }
1053
1054 if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
1055 # Non-articles (special pages etc), and old revisions
1056 return [
1057 'index' => 'noindex',
1058 'follow' => 'nofollow'
1059 ];
1060 } elseif ( $context->getOutput()->isPrintable() ) {
1061 # Discourage indexing of printable versions, but encourage following
1062 return [
1063 'index' => 'noindex',
1064 'follow' => 'follow'
1065 ];
1066 } elseif ( $context->getRequest()->getInt( 'curid' ) ) {
1067 # For ?curid=x urls, disallow indexing
1068 return [
1069 'index' => 'noindex',
1070 'follow' => 'follow'
1071 ];
1072 }
1073
1074 # Otherwise, construct the policy based on the various config variables.
1075 $policy = self::formatRobotPolicy( $defaultRobotPolicy );
1076
1077 if ( isset( $namespaceRobotPolicies[$ns] ) ) {
1078 # Honour customised robot policies for this namespace
1079 $policy = array_merge(
1080 $policy,
1081 self::formatRobotPolicy( $namespaceRobotPolicies[$ns] )
1082 );
1083 }
1084 if ( $title->canUseNoindex() && $pOutput && $pOutput->getIndexPolicy() ) {
1085 # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1086 # a final check that we have really got the parser output.
1087 $policy = array_merge(
1088 $policy,
1089 [ 'index' => $pOutput->getIndexPolicy() ]
1090 );
1091 }
1092
1093 if ( isset( $articleRobotPolicies[$title->getPrefixedText()] ) ) {
1094 # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1095 $policy = array_merge(
1096 $policy,
1097 self::formatRobotPolicy( $articleRobotPolicies[$title->getPrefixedText()] )
1098 );
1099 }
1100
1101 return $policy;
1102 }
1103
1111 public static function formatRobotPolicy( $policy ) {
1112 if ( is_array( $policy ) ) {
1113 return $policy;
1114 } elseif ( !$policy ) {
1115 return [];
1116 }
1117
1118 $arr = [];
1119 foreach ( explode( ',', $policy ) as $var ) {
1120 $var = trim( $var );
1121 if ( $var === 'index' || $var === 'noindex' ) {
1122 $arr['index'] = $var;
1123 } elseif ( $var === 'follow' || $var === 'nofollow' ) {
1124 $arr['follow'] = $var;
1125 }
1126 }
1127
1128 return $arr;
1129 }
1130
1138 public function showRedirectedFromHeader() {
1139 $context = $this->getContext();
1140 $redirectSources = $context->getConfig()->get( MainConfigNames::RedirectSources );
1141 $outputPage = $context->getOutput();
1142 $request = $context->getRequest();
1143 $rdfrom = $request->getVal( 'rdfrom' );
1144
1145 // Construct a URL for the current page view, but with the target title
1146 $query = $request->getValues();
1147 unset( $query['rdfrom'] );
1148 unset( $query['title'] );
1149 if ( $this->getTitle()->isRedirect() ) {
1150 // Prevent double redirects
1151 $query['redirect'] = 'no';
1152 }
1153 $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1154
1155 if ( isset( $this->mRedirectedFrom ) ) {
1156 // This is an internally redirected page view.
1157 // We'll need a backlink to the source page for navigation.
1158 if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1159 $redir = $this->linkRenderer->makeKnownLink(
1160 $this->mRedirectedFrom,
1161 null,
1162 [],
1163 [ 'redirect' => 'no' ]
1164 );
1165
1166 $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1167 $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1168 . "</span>" );
1169
1170 // Add the script to update the displayed URL and
1171 // set the fragment if one was specified in the redirect
1172 $outputPage->addJsConfigVars( [
1173 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1174 ] );
1175 $outputPage->addModules( 'mediawiki.action.view.redirect' );
1176
1177 // Add a <link rel="canonical"> tag
1178 $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1179
1180 // Tell the output object that the user arrived at this article through a redirect
1181 $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1182
1183 return true;
1184 }
1185 } elseif ( $rdfrom ) {
1186 // This is an externally redirected view, from some other wiki.
1187 // If it was reported from a trusted site, supply a backlink.
1188 if ( $redirectSources && preg_match( $redirectSources, $rdfrom ) ) {
1189 $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1190 $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1191 $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1192 . "</span>" );
1193
1194 // Add the script to update the displayed URL
1195 $outputPage->addJsConfigVars( [
1196 'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1197 ] );
1198 $outputPage->addModules( 'mediawiki.action.view.redirect' );
1199
1200 return true;
1201 }
1202 }
1203
1204 return false;
1205 }
1206
1211 public function showNamespaceHeader() {
1212 if ( $this->getTitle()->isTalkPage() && !$this->getContext()->msg( 'talkpageheader' )->isDisabled() ) {
1213 $this->getContext()->getOutput()->wrapWikiMsg(
1214 "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1215 [ 'talkpageheader' ]
1216 );
1217 }
1218 }
1219
1223 public function showViewFooter() {
1224 # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1225 if ( $this->getTitle()->getNamespace() === NS_USER_TALK
1226 && IPUtils::isValid( $this->getTitle()->getText() )
1227 ) {
1228 $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1229 }
1230
1231 // Show a footer allowing the user to patrol the shown revision or page if possible
1232 $patrolFooterShown = $this->showPatrolFooter();
1233
1234 $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1235 }
1236
1247 public function showPatrolFooter() {
1248 $context = $this->getContext();
1249 $mainConfig = $context->getConfig();
1250 $useNPPatrol = $mainConfig->get( MainConfigNames::UseNPPatrol );
1251 $useRCPatrol = $mainConfig->get( MainConfigNames::UseRCPatrol );
1252 $useFilePatrol = $mainConfig->get( MainConfigNames::UseFilePatrol );
1253 // Allow hooks to decide whether to not output this at all
1254 if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1255 return false;
1256 }
1257
1258 $outputPage = $context->getOutput();
1259 $user = $context->getUser();
1260 $title = $this->getTitle();
1261 $rc = false;
1262
1263 if ( !$context->getAuthority()->probablyCan( 'patrol', $title )
1264 || !( $useRCPatrol || $useNPPatrol
1265 || ( $useFilePatrol && $title->inNamespace( NS_FILE ) ) )
1266 ) {
1267 // Patrolling is disabled or the user isn't allowed to
1268 return false;
1269 }
1270
1271 if ( $this->mRevisionRecord
1272 && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1273 ) {
1274 // The current revision is already older than what could be in the RC table
1275 // 6h tolerance because the RC might not be cleaned out regularly
1276 return false;
1277 }
1278
1279 // Check for cached results
1280 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1281 $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1282 if ( $cache->get( $key ) ) {
1283 return false;
1284 }
1285
1286 $dbr = $this->dbProvider->getReplicaDatabase();
1287 $oldestRevisionRow = $dbr->newSelectQueryBuilder()
1288 ->select( [ 'rev_id', 'rev_timestamp' ] )
1289 ->from( 'revision' )
1290 ->where( [ 'rev_page' => $title->getArticleID() ] )
1291 ->orderBy( [ 'rev_timestamp', 'rev_id' ] )
1292 ->caller( __METHOD__ )->fetchRow();
1293 $oldestRevisionTimestamp = $oldestRevisionRow ? $oldestRevisionRow->rev_timestamp : false;
1294
1295 // New page patrol: Get the timestamp of the oldest revision which
1296 // the revision table holds for the given page. Then we look
1297 // whether it's within the RC lifespan and if it is, we try
1298 // to get the recentchanges row belonging to that entry.
1299 $recentPageCreation = false;
1300 if ( $oldestRevisionTimestamp
1301 && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1302 ) {
1303 // 6h tolerance because the RC might not be cleaned out regularly
1304 $recentPageCreation = true;
1305 $rc = RecentChange::newFromConds(
1306 [
1307 'rc_this_oldid' => intval( $oldestRevisionRow->rev_id ),
1308 // Avoid selecting a categorization entry
1309 'rc_type' => RC_NEW,
1310 ],
1311 __METHOD__
1312 );
1313 if ( $rc ) {
1314 // Use generic patrol message for new pages
1315 $markPatrolledMsg = $context->msg( 'markaspatrolledtext' );
1316 }
1317 }
1318
1319 // File patrol: Get the timestamp of the latest upload for this page,
1320 // check whether it is within the RC lifespan and if it is, we try
1321 // to get the recentchanges row belonging to that entry
1322 // (with rc_type = RC_LOG, rc_log_type = upload).
1323 $recentFileUpload = false;
1324 if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $useFilePatrol
1325 && $title->getNamespace() === NS_FILE ) {
1326 // Retrieve timestamp from the current file (lastest upload)
1327 $newestUploadTimestamp = $dbr->newSelectQueryBuilder()
1328 ->select( 'img_timestamp' )
1329 ->from( 'image' )
1330 ->where( [ 'img_name' => $title->getDBkey() ] )
1331 ->caller( __METHOD__ )->fetchField();
1332 if ( $newestUploadTimestamp
1333 && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1334 ) {
1335 // 6h tolerance because the RC might not be cleaned out regularly
1336 $recentFileUpload = true;
1337 $rc = RecentChange::newFromConds(
1338 [
1339 'rc_type' => RC_LOG,
1340 'rc_log_type' => 'upload',
1341 'rc_timestamp' => $newestUploadTimestamp,
1342 'rc_namespace' => NS_FILE,
1343 'rc_cur_id' => $title->getArticleID()
1344 ],
1345 __METHOD__
1346 );
1347 if ( $rc ) {
1348 // Use patrol message specific to files
1349 $markPatrolledMsg = $context->msg( 'markaspatrolledtext-file' );
1350 }
1351 }
1352 }
1353
1354 if ( !$recentPageCreation && !$recentFileUpload ) {
1355 // Page creation and latest upload (for files) is too old to be in RC
1356
1357 // We definitely can't patrol so cache the information
1358 // When a new file version is uploaded, the cache is cleared
1359 $cache->set( $key, '1' );
1360
1361 return false;
1362 }
1363
1364 if ( !$rc ) {
1365 // Don't cache: This can be hit if the page gets accessed very fast after
1366 // its creation / latest upload or in case we have high replica DB lag. In case
1367 // the revision is too old, we will already return above.
1368 return false;
1369 }
1370
1371 if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1372 // Patrolled RC entry around
1373
1374 // Cache the information we gathered above in case we can't patrol
1375 // Don't cache in case we can patrol as this could change
1376 $cache->set( $key, '1' );
1377
1378 return false;
1379 }
1380
1381 if ( $rc->getPerformerIdentity()->equals( $user ) ) {
1382 // Don't show a patrol link for own creations/uploads. If the user could
1383 // patrol them, they already would be patrolled
1384 return false;
1385 }
1386
1387 $outputPage->setPreventClickjacking( true );
1388 if ( $context->getAuthority()->isAllowed( 'writeapi' ) ) {
1389 $outputPage->addModules( 'mediawiki.misc-authed-curate' );
1390 }
1391
1392 $link = $this->linkRenderer->makeKnownLink(
1393 $title,
1394 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $markPatrolledMsg is always set when $rc is set
1395 $markPatrolledMsg->text(),
1396 [],
1397 [
1398 'action' => 'markpatrolled',
1399 'rcid' => $rc->getAttribute( 'rc_id' ),
1400 ]
1401 );
1402
1403 $outputPage->addModuleStyles( 'mediawiki.action.styles' );
1404 $outputPage->addHTML(
1405 "<div class='patrollink' data-mw='interface'>" .
1406 $context->msg( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1407 '</div>'
1408 );
1409
1410 return true;
1411 }
1412
1419 public static function purgePatrolFooterCache( $articleID ) {
1420 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1421 $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1422 }
1423
1428 public function showMissingArticle() {
1429 $context = $this->getContext();
1430 $send404Code = $context->getConfig()->get( MainConfigNames::Send404Code );
1431
1432 $outputPage = $context->getOutput();
1433 // Whether the page is a root user page of an existing user (but not a subpage)
1434 $validUserPage = false;
1435
1436 $title = $this->getTitle();
1437
1438 $services = MediaWikiServices::getInstance();
1439
1440 $contextUser = $context->getUser();
1441
1442 # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1443 if ( $title->getNamespace() === NS_USER
1444 || $title->getNamespace() === NS_USER_TALK
1445 ) {
1446 $rootPart = $title->getRootText();
1447 $user = User::newFromName( $rootPart, false /* allow IP users */ );
1448 $ip = $this->userNameUtils->isIP( $rootPart );
1449 $block = $this->blockStore->newFromTarget( $user, $user );
1450
1451 if ( $user && $user->isRegistered() && $user->isHidden() &&
1452 !$context->getAuthority()->isAllowed( 'hideuser' )
1453 ) {
1454 // T120883 if the user is hidden and the viewer cannot see hidden
1455 // users, pretend like it does not exist at all.
1456 $user = false;
1457 }
1458
1459 if ( !( $user && $user->isRegistered() ) && !$ip ) {
1460 // User does not exist
1461 $outputPage->addHTML( Html::warningBox(
1462 $context->msg( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) )->parse(),
1463 'mw-userpage-userdoesnotexist'
1464 ) );
1465
1466 // Show renameuser log extract
1467 LogEventsList::showLogExtract(
1468 $outputPage,
1469 'renameuser',
1470 Title::makeTitleSafe( NS_USER, $rootPart ),
1471 '',
1472 [
1473 'lim' => 10,
1474 'showIfEmpty' => false,
1475 'msgKey' => [ 'renameuser-renamed-notice', $title->getBaseText() ]
1476 ]
1477 );
1478 } elseif (
1479 $block !== null &&
1480 $block->getType() != DatabaseBlock::TYPE_AUTO &&
1481 (
1482 $block->isSitewide() ||
1483 $services->getPermissionManager()->isBlockedFrom( $user, $title, true )
1484 )
1485 ) {
1486 // Show log extract if the user is sitewide blocked or is partially
1487 // blocked and not allowed to edit their user page or user talk page
1488 LogEventsList::showLogExtract(
1489 $outputPage,
1490 'block',
1491 $services->getNamespaceInfo()->getCanonicalName( NS_USER ) . ':' .
1492 $block->getTargetName(),
1493 '',
1494 [
1495 'lim' => 1,
1496 'showIfEmpty' => false,
1497 'msgKey' => [
1498 'blocked-notice-logextract',
1499 $user->getName() # Support GENDER in notice
1500 ]
1501 ]
1502 );
1503 $validUserPage = !$title->isSubpage();
1504 } else {
1505 $validUserPage = !$title->isSubpage();
1506 }
1507 }
1508
1509 $this->getHookRunner()->onShowMissingArticle( $this );
1510
1511 # Show delete and move logs if there were any such events.
1512 # The logging query can DOS the site when bots/crawlers cause 404 floods,
1513 # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1514 $dbCache = MediaWikiServices::getInstance()->getMainObjectStash();
1515 $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1516 $isRegistered = $contextUser->isRegistered();
1517 $sessionExists = $context->getRequest()->getSession()->isPersistent();
1518
1519 if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1520 $logTypes = [ 'delete', 'move', 'protect' ];
1521
1522 $dbr = $this->dbProvider->getReplicaDatabase();
1523
1524 $conds = [ $dbr->expr( 'log_action', '!=', 'revision' ) ];
1525 // Give extensions a chance to hide their (unrelated) log entries
1526 $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1527 LogEventsList::showLogExtract(
1528 $outputPage,
1529 $logTypes,
1530 $title,
1531 '',
1532 [
1533 'lim' => 10,
1534 'conds' => $conds,
1535 'showIfEmpty' => false,
1536 'msgKey' => [ $isRegistered || $sessionExists
1537 ? 'moveddeleted-notice'
1538 : 'moveddeleted-notice-recent'
1539 ]
1540 ]
1541 );
1542 }
1543
1544 if ( !$this->mPage->hasViewableContent() && $send404Code && !$validUserPage ) {
1545 // If there's no backing content, send a 404 Not Found
1546 // for better machine handling of broken links.
1547 $context->getRequest()->response()->statusHeader( 404 );
1548 }
1549
1550 // Also apply the robot policy for nonexisting pages (even if a 404 was used)
1551 $policy = $this->getRobotPolicy( 'view' );
1552 $outputPage->setIndexPolicy( $policy['index'] );
1553 $outputPage->setFollowPolicy( $policy['follow'] );
1554
1555 $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1556
1557 if ( !$hookResult ) {
1558 return;
1559 }
1560
1561 # Show error message
1562 $oldid = $this->getOldID();
1563 if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1564 $text = $this->getTitle()->getDefaultMessageText() ?? '';
1565 $outputPage->addWikiTextAsContent( $text );
1566 } else {
1567 if ( $oldid ) {
1568 $text = $this->getMissingRevisionMsg( $oldid )->plain();
1569 } elseif ( $context->getAuthority()->probablyCan( 'edit', $title ) ) {
1570 $message = $isRegistered ? 'noarticletext' : 'noarticletextanon';
1571 $text = $context->msg( $message )->plain();
1572 } else {
1573 $text = $context->msg( 'noarticletext-nopermission' )->plain();
1574 }
1575
1576 $dir = $context->getLanguage()->getDir();
1577 $lang = $context->getLanguage()->getHtmlCode();
1578 $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1579 'class' => "noarticletext mw-content-$dir",
1580 'dir' => $dir,
1581 'lang' => $lang,
1582 ] ) . "\n$text\n</div>" );
1583 }
1584 }
1585
1590 private function showViewError( string $errortext ) {
1591 $outputPage = $this->getContext()->getOutput();
1592 $outputPage->setPageTitleMsg( $this->getContext()->msg( 'errorpagetitle' ) );
1593 $outputPage->disableClientCache();
1594 $outputPage->setRobotPolicy( 'noindex,nofollow' );
1595 $outputPage->clearHTML();
1596 $outputPage->addHTML( Html::errorBox( $outputPage->parseAsContent( $errortext ) ) );
1597 }
1598
1605 public function showDeletedRevisionHeader() {
1606 if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1607 // Not deleted
1608 return true;
1609 }
1610 $outputPage = $this->getContext()->getOutput();
1611 // Used in wikilinks, should not contain whitespaces
1612 $titleText = $this->getTitle()->getPrefixedDBkey();
1613 // If the user is not allowed to see it...
1614 if ( !$this->mRevisionRecord->userCan(
1615 RevisionRecord::DELETED_TEXT,
1616 $this->getContext()->getAuthority()
1617 ) ) {
1618 $outputPage->addHTML(
1619 Html::warningBox(
1620 $outputPage->msg( 'rev-deleted-text-permission', $titleText )->parse(),
1621 'plainlinks'
1622 )
1623 );
1624
1625 return false;
1626 // If the user needs to confirm that they want to see it...
1627 } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) !== 1 ) {
1628 # Give explanation and add a link to view the revision...
1629 $oldid = intval( $this->getOldID() );
1630 $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1631 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1632 'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1633 $outputPage->addHTML(
1634 Html::warningBox(
1635 $outputPage->msg( $msg, $link )->parse(),
1636 'plainlinks'
1637 )
1638 );
1639
1640 return false;
1641 // We are allowed to see...
1642 } else {
1643 $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1644 ? [ 'rev-suppressed-text-view', $titleText ]
1645 : [ 'rev-deleted-text-view', $titleText ];
1646 $outputPage->addHTML(
1647 Html::warningBox(
1648 $outputPage->msg( $msg[0], $msg[1] )->parse(),
1649 'plainlinks'
1650 )
1651 );
1652
1653 return true;
1654 }
1655 }
1656
1665 public function setOldSubtitle( $oldid = 0 ) {
1666 if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1667 return;
1668 }
1669
1670 $context = $this->getContext();
1671 $unhide = $context->getRequest()->getInt( 'unhide' ) === 1;
1672
1673 # Cascade unhide param in links for easy deletion browsing
1674 $extraParams = [];
1675 if ( $unhide ) {
1676 $extraParams['unhide'] = 1;
1677 }
1678
1679 if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1680 $revisionRecord = $this->mRevisionRecord;
1681 } else {
1682 $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1683 }
1684 if ( !$revisionRecord ) {
1685 throw new LogicException( 'There should be a revision record at this point.' );
1686 }
1687
1688 $timestamp = $revisionRecord->getTimestamp();
1689
1690 $current = ( $oldid == $this->mPage->getLatest() );
1691 $language = $context->getLanguage();
1692 $user = $context->getUser();
1693
1694 $td = $language->userTimeAndDate( $timestamp, $user );
1695 $tddate = $language->userDate( $timestamp, $user );
1696 $tdtime = $language->userTime( $timestamp, $user );
1697
1698 # Show user links if allowed to see them. If hidden, then show them only if requested...
1699 $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1700
1701 $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1702 ? 'revision-info-current'
1703 : 'revision-info';
1704
1705 $outputPage = $context->getOutput();
1706 $outputPage->addModuleStyles( [
1707 'mediawiki.action.styles',
1708 'mediawiki.interface.helpers.styles'
1709 ] );
1710
1711 $revisionUser = $revisionRecord->getUser();
1712 $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1713 $context->msg( $infomsg, $td )
1714 ->rawParams( $userlinks )
1715 ->params(
1716 $revisionRecord->getId(),
1717 $tddate,
1718 $tdtime,
1719 $revisionUser ? $revisionUser->getName() : ''
1720 )
1721 ->rawParams( $this->commentFormatter->formatRevision(
1722 $revisionRecord,
1723 $user,
1724 true,
1725 !$unhide
1726 ) )
1727 ->parse() .
1728 "</div>";
1729
1730 $lnk = $current
1731 ? $context->msg( 'currentrevisionlink' )->escaped()
1732 : $this->linkRenderer->makeKnownLink(
1733 $this->getTitle(),
1734 $context->msg( 'currentrevisionlink' )->text(),
1735 [],
1736 $extraParams
1737 );
1738 $curdiff = $current
1739 ? $context->msg( 'diff' )->escaped()
1740 : $this->linkRenderer->makeKnownLink(
1741 $this->getTitle(),
1742 $context->msg( 'diff' )->text(),
1743 [],
1744 [
1745 'diff' => 'cur',
1746 'oldid' => $oldid
1747 ] + $extraParams
1748 );
1749 $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1750 $prevlink = $prevExist
1751 ? $this->linkRenderer->makeKnownLink(
1752 $this->getTitle(),
1753 $context->msg( 'previousrevision' )->text(),
1754 [],
1755 [
1756 'direction' => 'prev',
1757 'oldid' => $oldid
1758 ] + $extraParams
1759 )
1760 : $context->msg( 'previousrevision' )->escaped();
1761 $prevdiff = $prevExist
1762 ? $this->linkRenderer->makeKnownLink(
1763 $this->getTitle(),
1764 $context->msg( 'diff' )->text(),
1765 [],
1766 [
1767 'diff' => 'prev',
1768 'oldid' => $oldid
1769 ] + $extraParams
1770 )
1771 : $context->msg( 'diff' )->escaped();
1772 $nextlink = $current
1773 ? $context->msg( 'nextrevision' )->escaped()
1774 : $this->linkRenderer->makeKnownLink(
1775 $this->getTitle(),
1776 $context->msg( 'nextrevision' )->text(),
1777 [],
1778 [
1779 'direction' => 'next',
1780 'oldid' => $oldid
1781 ] + $extraParams
1782 );
1783 $nextdiff = $current
1784 ? $context->msg( 'diff' )->escaped()
1785 : $this->linkRenderer->makeKnownLink(
1786 $this->getTitle(),
1787 $context->msg( 'diff' )->text(),
1788 [],
1789 [
1790 'diff' => 'next',
1791 'oldid' => $oldid
1792 ] + $extraParams
1793 );
1794
1795 $cdel = Linker::getRevDeleteLink(
1796 $context->getAuthority(),
1797 $revisionRecord,
1798 $this->getTitle()
1799 );
1800 if ( $cdel !== '' ) {
1801 $cdel .= ' ';
1802 }
1803
1804 // the outer div is need for styling the revision info and nav in MobileFrontend
1805 $outputPage->addSubtitle(
1806 Html::warningBox(
1807 $revisionInfo .
1808 "<div id=\"mw-revision-nav\">" . $cdel .
1809 $context->msg( 'revision-nav' )->rawParams(
1810 $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1811 )->escaped() . "</div>",
1812 'mw-revision'
1813 )
1814 );
1815 }
1816
1830 public static function getRedirectHeaderHtml( Language $lang, Title $target, $forceKnown = false ) {
1831 wfDeprecated( __METHOD__, '1.41' );
1832 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1833 return $linkRenderer->makeRedirectHeader( $lang, $target, $forceKnown );
1834 }
1835
1844 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1845 $out = $this->getContext()->getOutput();
1846 $msg = $out->msg( 'namespace-' . $this->getTitle()->getNamespace() . '-helppage' );
1847
1848 if ( !$msg->isDisabled() ) {
1849 $title = Title::newFromText( $msg->plain() );
1850 if ( $title instanceof Title ) {
1851 $out->addHelpLink( $title->getLocalURL(), true );
1852 }
1853 } else {
1854 $out->addHelpLink( $to, $overrideBaseUrl );
1855 }
1856 }
1857
1861 public function render() {
1862 $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1863 $this->getContext()->getOutput()->setArticleBodyOnly( true );
1864 // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1865 $this->viewIsRenderAction = true;
1866 $this->view();
1867 }
1868
1872 public function protect() {
1873 $form = new ProtectionForm( $this );
1874 $form->execute();
1875 }
1876
1880 public function unprotect() {
1881 $this->protect();
1882 }
1883
1884 /* Caching functions */
1885
1893 protected function tryFileCache() {
1894 static $called = false;
1895
1896 if ( $called ) {
1897 wfDebug( "Article::tryFileCache(): called twice!?" );
1898 return false;
1899 }
1900
1901 $called = true;
1902 if ( $this->isFileCacheable() ) {
1903 $cache = new HTMLFileCache( $this->getTitle(), 'view' );
1904 if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1905 wfDebug( "Article::tryFileCache(): about to load file" );
1906 $cache->loadFromFileCache( $this->getContext() );
1907 return true;
1908 } else {
1909 wfDebug( "Article::tryFileCache(): starting buffer" );
1910 ob_start( [ &$cache, 'saveToFileCache' ] );
1911 }
1912 } else {
1913 wfDebug( "Article::tryFileCache(): not cacheable" );
1914 }
1915
1916 return false;
1917 }
1918
1924 public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
1925 $cacheable = false;
1926
1927 if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
1928 $cacheable = $this->mPage->getId()
1929 && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1930 // Extension may have reason to disable file caching on some pages.
1931 if ( $cacheable ) {
1932 $cacheable = $this->getHookRunner()->onIsFileCacheable( $this ) ?? false;
1933 }
1934 }
1935
1936 return $cacheable;
1937 }
1938
1950 public function getParserOutput( $oldid = null, UserIdentity $user = null ) {
1951 if ( $user === null ) {
1952 $parserOptions = $this->getParserOptions();
1953 } else {
1954 $parserOptions = $this->mPage->makeParserOptions( $user );
1955 $parserOptions->setRenderReason( 'page-view' );
1956 }
1957
1958 return $this->mPage->getParserOutput( $parserOptions, $oldid );
1959 }
1960
1965 public function getParserOptions() {
1966 $parserOptions = $this->mPage->makeParserOptions( $this->getContext() );
1967 $parserOptions->setRenderReason( 'page-view' );
1968 return $parserOptions;
1969 }
1970
1977 public function setContext( $context ) {
1978 $this->mContext = $context;
1979 }
1980
1987 public function getContext(): IContextSource {
1988 if ( $this->mContext instanceof IContextSource ) {
1989 return $this->mContext;
1990 } else {
1991 wfDebug( __METHOD__ . " called and \$mContext is null. " .
1992 "Return RequestContext::getMain()" );
1993 return RequestContext::getMain();
1994 }
1995 }
1996
2002 public function getActionOverrides() {
2003 return $this->mPage->getActionOverrides();
2004 }
2005
2006 private function getMissingRevisionMsg( int $oldid ): Message {
2007 // T251066: Try loading the revision from the archive table.
2008 // Show link to view it if it exists and the user has permission to view it.
2009 // (Ignore the given title, if any; look it up from the revision instead.)
2010 $context = $this->getContext();
2011 $revRecord = $this->archivedRevisionLookup->getArchivedRevisionRecord( null, $oldid );
2012 if (
2013 $revRecord &&
2014 $revRecord->userCan(
2015 RevisionRecord::DELETED_TEXT,
2016 $context->getAuthority()
2017 ) &&
2018 $context->getAuthority()->isAllowedAny( 'deletedtext', 'undelete' )
2019 ) {
2020 return $context->msg(
2021 'missing-revision-permission',
2022 $oldid,
2023 $revRecord->getTimestamp(),
2024 Title::newFromPageIdentity( $revRecord->getPage() )->getPrefixedDBkey()
2025 );
2026 }
2027 return $context->msg( 'missing-revision', $oldid );
2028 }
2029}
getRequest()
const NS_USER
Definition Defines.php:66
const NS_FILE
Definition Defines.php:70
const RC_NEW
Definition Defines.php:117
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.
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
getContext()
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:67
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition Article.php:238
getContext()
Gets the context this Article is executed in.
Definition Article.php:1987
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition Article.php:312
getRedirectedFrom()
Get the page this view was redirected from.
Definition Article.php:249
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition Article.php:88
bool $viewIsRenderAction
Whether render() was called.
Definition Article.php:111
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition Article.php:471
__construct(Title $title, $oldId=null)
Definition Article.php:162
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition Article.php:1027
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition Article.php:1419
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition Article.php:104
getOldID()
Definition Article.php:299
LinkRenderer $linkRenderer
Definition Article.php:116
getTitle()
Get the title object of the article.
Definition Article.php:267
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition Article.php:2002
isDiffOnlyView()
Definition Article.php:1013
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition Article.php:924
DatabaseBlockStore $blockStore
Definition Article.php:148
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition Article.php:1605
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition Article.php:1965
IContextSource null $mContext
The context this Article is executed in.
Definition Article.php:76
static getRedirectHeaderHtml(Language $lang, Title $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition Article.php:1830
getParserOutput( $oldid=null, UserIdentity $user=null)
Lightweight method to get the parser output for a page, checking the parser cache and so on.
Definition Article.php:1950
protect()
action=protect handler
Definition Article.php:1872
string false $mRedirectUrl
URL to redirect to or false if none.
Definition Article.php:91
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
Definition Article.php:438
showMissingArticle()
Show the error text for a missing article.
Definition Article.php:1428
IConnectionProvider $dbProvider
Definition Article.php:145
unprotect()
action=unprotect handler (alias)
Definition Article.php:1880
newPage(Title $title)
Definition Article.php:183
getPage()
Get the WikiPage object of this instance.
Definition Article.php:277
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition Article.php:1844
static newFromID( $id)
Constructor from a page id.
Definition Article.php:192
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition Article.php:85
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition Article.php:1111
fetchRevisionRecord()
Fetches the revision to work on.
Definition Article.php:380
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition Article.php:1247
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition Article.php:1665
showViewFooter()
Show the footer section of an ordinary page view.
Definition Article.php:1223
WikiPage $mPage
The WikiPage object of this instance.
Definition Article.php:79
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
Definition Article.php:258
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition Article.php:1924
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition Article.php:1893
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition Article.php:457
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition Article.php:1211
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition Article.php:204
showDiffPage()
Show a diff page according to current request variables.
Definition Article.php:939
render()
Handle action=render.
Definition Article.php:1861
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition Article.php:1138
setContext( $context)
Sets the context this Article is executed in.
Definition Article.php:1977
getCacheRevisionId()
Special handling for category description pages.
Page view caching in the file system.
Rendering of file description pages.
Definition ImagePage.php:37
Handle enqueueing of background jobs.
Base class for language-specific code.
Definition Language.php:63
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
This is the main service interface for converting single-line comments from various DB comment fields...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Group all the pieces relevant to the context of a request into one instance.
The HTML user interface for page editing.
Definition EditPage.php:145
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
Class that generates HTML for internal links.
makeRedirectHeader(Language $lang, Title $target, bool $forceKnown=false)
Return the HTML for the top of a redirect page.
Some internal bits split of from Skin.php.
Definition Linker.php:65
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
This is one of the Core classes and should be read at least once by any new developers.
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
disableClientCache()
Force the page to send nocache headers.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
setPreventClickjacking(bool $enable)
Set the prevent-clickjacking flag.
disable()
Disable output completely, i.e.
setRedirectedFrom(PageReference $t)
Set $mRedirectedFrom, the page which redirected us to the current page.
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
addBodyClasses( $classes)
Add a class to the <body> element.
lowerCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header to $maxage if that is lower t...
setContentLangForJS(Bcp47Code $lang)
setLastModified( $timestamp)
Override the last modified timestamp.
setRevisionIsCurrent(bool $isCurrent)
Set whether the revision displayed (as set in ::setRevisionId()) is the latest revision of the page.
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
clearHTML()
Clear the body HTML.
setPageTitle( $name)
"Page title" means the contents of <h1>.
setPageTitleMsg(Message $msg)
"Page title" means the contents of <h1>.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
prependHTML( $text)
Prepend $text to the body HTML.
setRobotPolicy( $policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
setCanonicalUrl( $url)
Set the URL to be used for the <link rel=canonical>>.
addWikiTextAsContent( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
addHTML( $text)
Append $text to the body HTML.
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
addSubtitle( $str)
Add $str to the subtitle.
addModuleStyles( $modules)
Load the styles of one or more style-only ResourceLoader modules on this page.
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
isPrintable()
Return whether the page is "printable".
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
addWikiTextAsInterface( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the user interface language to HTML and add it to the buffer.
Service for getting rendered output of a given page.
Handles the page protection UI and backend.
Service for creating WikiPage objects.
Rendered output of a wiki page, as parsed from wikitext.
getLanguage()
Get the primary language code of the output.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:151
A StatusValue for permission errors.
Exception raised when the text of a revision is permanently missing or corrupt.
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.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
getWikiText( $shortContext=false, $longContext=false, $lang=null)
Get the error list as a wikitext formatted list.
Definition Status.php:211
Represents a title within MediaWiki.
Definition Title.php:78
Provides access to user options.
UserNameUtils service.
internal since 1.36
Definition User.php:93
Set options of the Parser.
setIsPrintable( $x)
Parsing the printable version of the page?
setRenderReason(string $renderReason)
Sets reason for rendering the content.
static newSpec(int $revisionId, PageRecord $page, array $params=[])
Show an error when a user tries to do something they do not have the necessary permissions for.
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.
Base representation for an editable wiki page.
Definition WikiPage.php:79
getTitle()
Get the title object of the article.
Definition WikiPage.php:260
Interface for objects which can provide a MediaWiki context on request.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:37
Interface for objects representing user identity.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition Page.php:30
Generic interface providing Time-To-Live constants for expirable object storage.
Provide primary and replica IDatabase connections.