MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
25 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
33 use Wikimedia\IPUtils;
34 use Wikimedia\NonSerializable\NonSerializableTrait;
36 
47 class Article implements Page {
48  use ProtectedHookAccessorTrait;
49  use NonSerializableTrait;
50 
56  protected $mContext;
57 
59  protected $mPage;
60 
65  public $mOldId;
66 
68  public $mRedirectedFrom = null;
69 
71  public $mRedirectUrl = false;
72 
77  private $fetchResult = null;
78 
84  public $mParserOutput = null;
85 
91  protected $viewIsRenderAction = false;
92 
96  protected $linkRenderer;
97 
101  private $permManager;
102 
106  private $revisionStore;
107 
117  private $mRevisionRecord = null;
118 
123  public function __construct( Title $title, $oldId = null ) {
124  $this->mOldId = $oldId;
125  $this->mPage = $this->newPage( $title );
126 
127  $services = MediaWikiServices::getInstance();
128  $this->linkRenderer = $services->getLinkRenderer();
129  $this->permManager = $services->getPermissionManager();
130  $this->revisionStore = $services->getRevisionStore();
131  }
132 
137  protected function newPage( Title $title ) {
138  return new WikiPage( $title );
139  }
140 
146  public static function newFromID( $id ) {
147  $t = Title::newFromID( $id );
148  return $t == null ? null : new static( $t );
149  }
150 
158  public static function newFromTitle( $title, IContextSource $context ) {
159  if ( $title->getNamespace() === NS_MEDIA ) {
160  // XXX: This should not be here, but where should it go?
161  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
162  }
163 
164  $page = null;
165  Hooks::runner()->onArticleFromTitle( $title, $page, $context );
166  if ( !$page ) {
167  switch ( $title->getNamespace() ) {
168  case NS_FILE:
169  $page = new ImagePage( $title );
170  break;
171  case NS_CATEGORY:
172  $page = new CategoryPage( $title );
173  break;
174  default:
175  $page = new Article( $title );
176  }
177  }
178  $page->setContext( $context );
179 
180  return $page;
181  }
182 
190  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
191  $article = self::newFromTitle( $page->getTitle(), $context );
192  $article->mPage = $page; // override to keep process cached vars
193  return $article;
194  }
195 
201  public function getRedirectedFrom() {
202  return $this->mRedirectedFrom;
203  }
204 
210  public function setRedirectedFrom( Title $from ) {
211  $this->mRedirectedFrom = $from;
212  }
213 
219  public function getTitle() {
220  return $this->mPage->getTitle();
221  }
222 
229  public function getPage() {
230  return $this->mPage;
231  }
232 
233  public function clear() {
234  $this->mRedirectedFrom = null; # Title object if set
235  $this->mRedirectUrl = false;
236  $this->mRevisionRecord = null;
237  $this->fetchResult = null;
238 
239  // TODO hard-deprecate direct access to public fields
240 
241  $this->mPage->clear();
242  }
243 
261  protected function getContentObject() {
262  wfDeprecated( __METHOD__, '1.32' );
263  $content = null;
264  if ( $this->mPage->getId() === 0 ) {
265  $content = $this->getSubstituteContent();
266  } else {
267  $revision = $this->fetchRevisionRecord();
268  if ( $revision ) {
269  $content = $revision->getContent(
270  SlotRecord::MAIN,
271  RevisionRecord::FOR_THIS_USER,
272  $this->getContext()->getUser()
273  );
274  }
275  }
276  return $content;
277  }
278 
284  private function getSubstituteContent() {
285  # If this is a MediaWiki:x message, then load the messages
286  # and return the message value for x.
287  if ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) {
288  $text = $this->getTitle()->getDefaultMessageText();
289  if ( $text === false ) {
290  $text = '';
291  }
292 
293  $content = ContentHandler::makeContent( $text, $this->getTitle() );
294  } else {
295  $message = $this->getContext()->getUser()->isRegistered() ? 'noarticletext' : 'noarticletextanon';
296  $content = new MessageContent( $message, null );
297  }
298 
299  return $content;
300  }
301 
309  public function getOldID() {
310  if ( $this->mOldId === null ) {
311  $this->mOldId = $this->getOldIDFromRequest();
312  }
313 
314  return $this->mOldId;
315  }
316 
322  public function getOldIDFromRequest() {
323  $this->mRedirectUrl = false;
324 
325  $request = $this->getContext()->getRequest();
326  $oldid = $request->getIntOrNull( 'oldid' );
327 
328  if ( $oldid === null ) {
329  return 0;
330  }
331 
332  if ( $oldid !== 0 ) {
333  # Load the given revision and check whether the page is another one.
334  # In that case, update this instance to reflect the change.
335  if ( $oldid === $this->mPage->getLatest() ) {
336  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
337  } else {
338  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
339  if ( $this->mRevisionRecord !== null ) {
340  $revPageId = $this->mRevisionRecord->getPageId();
341  // Revision title doesn't match the page title given?
342  if ( $this->mPage->getId() != $revPageId ) {
343  $function = get_class( $this->mPage ) . '::newFromID';
344  $this->mPage = $function( $revPageId );
345  }
346  }
347  }
348  }
349 
350  $oldRev = $this->mRevisionRecord;
351  if ( $request->getVal( 'direction' ) == 'next' ) {
352  $nextid = 0;
353  if ( $oldRev ) {
354  $nextRev = $this->revisionStore->getNextRevision( $oldRev );
355  if ( $nextRev ) {
356  $nextid = $nextRev->getId();
357  }
358  }
359  if ( $nextid ) {
360  $oldid = $nextid;
361  $this->mRevisionRecord = null;
362  } else {
363  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
364  }
365  } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
366  $previd = 0;
367  if ( $oldRev ) {
368  $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
369  if ( $prevRev ) {
370  $previd = $prevRev->getId();
371  }
372  }
373  if ( $previd ) {
374  $oldid = $previd;
375  $this->mRevisionRecord = null;
376  }
377  }
378 
379  return $oldid;
380  }
381 
391  public function fetchRevisionRecord() {
392  if ( $this->fetchResult ) {
393  return $this->mRevisionRecord;
394  }
395 
396  $oldid = $this->getOldID();
397 
398  // $this->mRevisionRecord might already be fetched by getOldIDFromRequest()
399  if ( !$this->mRevisionRecord ) {
400  if ( !$oldid ) {
401  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
402 
403  if ( !$this->mRevisionRecord ) {
404  wfDebug( __METHOD__ . " failed to find page data for title " .
405  $this->getTitle()->getPrefixedText() );
406 
407  // Just for sanity, output for this case is done by showMissingArticle().
408  $this->fetchResult = Status::newFatal( 'noarticletext' );
409  return null;
410  }
411  } else {
412  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
413 
414  if ( !$this->mRevisionRecord ) {
415  wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid" );
416 
417  $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
418  return null;
419  }
420  }
421  }
422 
423  if ( !RevisionRecord::userCanBitfield(
424  $this->mRevisionRecord->getVisibility(),
425  RevisionRecord::DELETED_TEXT,
426  $this->getContext()->getUser()
427  ) ) {
428  wfDebug( __METHOD__ . " failed to retrieve content of revision " .
429  $this->mRevisionRecord->getId() );
430 
431  // Just for sanity, output for this case is done by showDeletedRevisionHeader().
432  // title used in wikilinks, should not contain whitespaces
433  $this->fetchResult = Status::newFatal(
434  'rev-deleted-text-permission', $this->getTitle()->getPrefixedDBkey() );
435  return null;
436  }
437 
438  $this->fetchResult = Status::newGood( $this->mRevisionRecord );
439  return $this->mRevisionRecord;
440  }
441 
447  public function isCurrent() {
448  # If no oldid, this is the current version.
449  if ( $this->getOldID() == 0 ) {
450  return true;
451  }
452 
453  return $this->mPage->exists() &&
454  $this->mRevisionRecord &&
455  $this->mRevisionRecord->isCurrent();
456  }
457 
467  public function getRevisionFetched() {
468  wfDeprecated( __METHOD__, '1.35' );
469  $revRecord = $this->fetchRevisionRecord();
470 
471  return $revRecord ? new Revision( $revRecord ) : null;
472  }
473 
482  public function getRevIdFetched() {
483  if ( $this->fetchResult && $this->fetchResult->isOK() ) {
484  return $this->fetchResult->value->getId();
485  } else {
486  return $this->mPage->getLatest();
487  }
488  }
489 
494  public function view() {
495  global $wgUseFileCache;
496 
497  # Get variables from query string
498  # As side effect this will load the revision and update the title
499  # in a revision ID is passed in the request, so this should remain
500  # the first call of this method even if $oldid is used way below.
501  $oldid = $this->getOldID();
502 
503  $user = $this->getContext()->getUser();
504  # Another whitelist check in case getOldID() is altering the title
505  $permErrors = $this->permManager->getPermissionErrors(
506  'read',
507  $user,
508  $this->getTitle()
509  );
510  if ( count( $permErrors ) ) {
511  wfDebug( __METHOD__ . ": denied on secondary read check" );
512  throw new PermissionsError( 'read', $permErrors );
513  }
514 
515  $outputPage = $this->getContext()->getOutput();
516  # getOldID() may as well want us to redirect somewhere else
517  if ( $this->mRedirectUrl ) {
518  $outputPage->redirect( $this->mRedirectUrl );
519  wfDebug( __METHOD__ . ": redirecting due to oldid" );
520 
521  return;
522  }
523 
524  # If we got diff in the query, we want to see a diff page instead of the article.
525  if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
526  wfDebug( __METHOD__ . ": showing diff page" );
527  $this->showDiffPage();
528 
529  return;
530  }
531 
532  # Set page title (may be overridden by DISPLAYTITLE)
533  $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
534 
535  $outputPage->setArticleFlag( true );
536  # Allow frames by default
537  $outputPage->allowClickjacking();
538 
539  $parserOptions = $this->getParserOptions();
540  $poOptions = [];
541  # Allow extensions to vary parser options used for article rendering
542  Hooks::runner()->onArticleParserOptions( $this, $parserOptions );
543  # Render printable version, use printable version cache
544  if ( $outputPage->isPrintable() ) {
545  $parserOptions->setIsPrintable( true );
546  $poOptions['enableSectionEditLinks'] = false;
547  $outputPage->prependHTML(
549  $outputPage->msg( 'printableversion-deprecated-warning' )->escaped()
550  )
551  );
552  } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
553  !$this->permManager->quickUserCan( 'edit', $user, $this->getTitle() )
554  ) {
555  $poOptions['enableSectionEditLinks'] = false;
556  }
557 
558  # Try client and file cache
559  if ( $oldid === 0 && $this->mPage->checkTouched() ) {
560  # Try to stream the output from file cache
561  if ( $wgUseFileCache && $this->tryFileCache() ) {
562  wfDebug( __METHOD__ . ": done file cache" );
563  # tell wgOut that output is taken care of
564  $outputPage->disable();
565  $this->mPage->doViewUpdates( $user, $oldid );
566 
567  return;
568  }
569  }
570 
571  $this->showRedirectedFromHeader();
572  $this->showNamespaceHeader();
573 
574  $continue =
575  $this->generateContentOutput( $user, $parserOptions, $oldid, $outputPage, $poOptions );
576 
577  if ( !$continue ) {
578  return;
579  }
580 
581  # For the main page, overwrite the <title> element with the con-
582  # tents of 'pagetitle-view-mainpage' instead of the default (if
583  # that's not empty).
584  # This message always exists because it is in the i18n files
585  if ( $this->getTitle()->isMainPage() ) {
586  $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
587  if ( !$msg->isDisabled() ) {
588  $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
589  }
590  }
591 
592  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
593  # This could use getTouched(), but that could be scary for major template edits.
594  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
595 
596  $this->showViewFooter();
597  $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this
598 
599  # Load the postEdit module if the user just saved this revision
600  # See also EditPage::setPostEditCookie
601  $request = $this->getContext()->getRequest();
603  $postEdit = $request->getCookie( $cookieKey );
604  if ( $postEdit ) {
605  # Clear the cookie. This also prevents caching of the response.
606  $request->response()->clearCookie( $cookieKey );
607  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
608  $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
609  }
610  }
611 
624  private function generateContentOutput(
625  User $user,
626  ParserOptions $parserOptions,
627  int $oldid,
628  OutputPage $outputPage,
629  array $textOptions
630  ): bool {
631  # Should the parser cache be used?
632  $useParserCache = true;
633  $pOutput = null;
634  $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
635 
636  // NOTE: $outputDone and $useParserCache may be changed by the hook
637  $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
638  if ( $outputDone ) {
639  if ( $outputDone instanceof ParserOutput ) {
640  $pOutput = $outputDone;
641  }
642 
643  if ( $pOutput ) {
644  $this->doOutputMetaData( $pOutput, $outputPage );
645  }
646  return true;
647  }
648 
649  // Early abort if the page doesn't exist
650  if ( !$this->mPage->exists() ) {
651  wfDebug( __METHOD__ . ": showing missing article" );
652  $this->showMissingArticle();
653  $this->mPage->doViewUpdates( $user );
654  return false; // skip all further output to OutputPage
655  }
656 
657  // Try the latest parser cache
658  // NOTE: try latest-revision cache first to avoid loading revision.
659  if ( $useParserCache && !$oldid ) {
660  $pOutput = $parserOutputAccess->getCachedParserOutput(
661  $this->getPage(),
662  $parserOptions,
663  null,
664  ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked
665  );
666 
667  if ( $pOutput ) {
668  $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
669  $this->doOutputMetaData( $pOutput, $outputPage );
670  return true;
671  }
672  }
673 
674  $rev = $this->fetchRevisionRecord();
675  if ( !$this->fetchResult->isOK() ) {
676  $this->showViewError( $this->fetchResult->getWikiText(
677  false, false, $this->getContext()->getLanguage()
678  ) );
679  return true;
680  }
681 
682  # Are we looking at an old revision
683  if ( $oldid ) {
684  $this->setOldSubtitle( $oldid );
685 
686  if ( !$this->showDeletedRevisionHeader() ) {
687  wfDebug( __METHOD__ . ": cannot view deleted revision" );
688  return false; // skip all further output to OutputPage
689  }
690 
691  // Try the old revision parser cache
692  // NOTE: Repeating cache check for old revision to avoid fetching $rev
693  // before it's absolutely necessary.
694  if ( $useParserCache ) {
695  $pOutput = $parserOutputAccess->getCachedParserOutput(
696  $this->getPage(),
697  $parserOptions,
698  $rev,
699  ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked in fetchRevisionRevord
700  );
701 
702  if ( $pOutput ) {
703  $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
704  $this->doOutputMetaData( $pOutput, $outputPage );
705  return true;
706  }
707  }
708  }
709 
710  # Ensure that UI elements requiring revision ID have
711  # the correct version information.
712  $outputPage->setRevisionId( $this->getRevIdFetched() );
713  # Preload timestamp to avoid a DB hit
714  $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
715 
716  # Pages containing custom CSS or JavaScript get special treatment
717  if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
718  $dir = $this->getContext()->getLanguage()->getDir();
719  $lang = $this->getContext()->getLanguage()->getHtmlCode();
720 
721  $outputPage->wrapWikiMsg(
722  "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
723  'clearyourcache'
724  );
725  } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
726  $rev,
727  $this->getTitle(),
728  $oldid,
729  $outputPage )
730  ) {
731  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
732  // Allow extensions do their own custom view for certain pages
733  $this->doOutputMetaData( $pOutput, $outputPage );
734  return true;
735  }
736 
737  # Run the parse, protected by a pool counter
738  wfDebug( __METHOD__ . ": doing uncached parse" );
739 
740  if ( !$rev ) {
741  // No revision, abort! Shouldn't happen.
742  return false;
743  }
744 
745  $opt = 0;
746 
747  // we already checked the cache in case 2, don't check again.
748  $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
749 
750  // we already checked in fetchRevisionRecord()
751  $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
752 
753  if ( !$rev->getId() || !$useParserCache ) {
754  // fake revision or uncacheable options
755  $opt |= ParserOutputAccess::OPT_NO_CACHE;
756  }
757 
758  $renderStatus = $parserOutputAccess->getParserOutput(
759  $this->getPage(),
760  $parserOptions,
761  $rev,
762  $opt
763  );
764 
766  $rev,
767  $renderStatus,
768  $outputPage,
769  $textOptions
770  );
771 
772  if ( !$renderStatus->isOK() ) {
773  return true;
774  }
775 
776  $pOutput = $renderStatus->getValue();
777  $this->doOutputMetaData( $pOutput, $outputPage );
778  return true;
779  }
780 
785  private function doOutputMetaData( ?ParserOutput $pOutput, OutputPage $outputPage ) {
786  # Adjust title for main page & pages with displaytitle
787  if ( $pOutput ) {
788  $this->adjustDisplayTitle( $pOutput );
789  }
790 
791  # Check for any __NOINDEX__ tags on the page using $pOutput
792  $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
793  $outputPage->setIndexPolicy( $policy['index'] );
794  $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
795 
796  $this->mParserOutput = $pOutput;
797  }
798 
804  private function doOutputFromParserCache(
805  ParserOutput $pOutput,
806  OutputPage $outputPage,
807  array $textOptions
808  ) {
809  $outputPage->addParserOutput( $pOutput, $textOptions );
810  # Ensure that UI elements requiring revision ID have
811  # the correct version information.
812  $outputPage->setRevisionId( $pOutput->getCacheRevisionId() ?? $this->mPage->getLatest() );
813  # Preload timestamp to avoid a DB hit
814  $cachedTimestamp = $pOutput->getTimestamp();
815  if ( $cachedTimestamp !== null ) {
816  $outputPage->setRevisionTimestamp( $cachedTimestamp );
817  $this->mPage->setTimestamp( $cachedTimestamp );
818  }
819  }
820 
827  private function doOutputFromRenderStatus(
828  ?RevisionRecord $rev,
829  Status $renderStatus,
830  OutputPage $outputPage,
831  array $textOptions
832  ) {
833  global $wgCdnMaxageStale;
834  $ok = $renderStatus->isOK();
835 
836  $pOutput = $ok ? $renderStatus->getValue() : null;
837 
838  // Cache stale ParserOutput object with a short expiry
839  if ( $ok && $renderStatus->hasMessage( 'view-pool-dirty-output' ) ) {
840  $outputPage->setCdnMaxage( $wgCdnMaxageStale );
841  $outputPage->setLastModified( $pOutput->getCacheTime() );
842  $staleReason = $renderStatus->hasMessage( 'view-pool-contention' )
843  ? $this->getContext()->msg( 'view-pool-contention' )
844  : $this->getContext()->msg( 'view-pool-timeout' );
845  $outputPage->addHTML( "<!-- parser cache is expired, " .
846  "sending anyway due to $staleReason-->\n" );
847  }
848 
849  if ( !$renderStatus->isOK() ) {
850  $this->showViewError( $renderStatus->getWikiText(
851  false, 'view-pool-error', $this->getContext()->getLanguage()
852  ) );
853  return;
854  }
855 
856  if ( $pOutput ) {
857  $outputPage->addParserOutput( $pOutput, $textOptions );
858  }
859 
860  if ( $this->getRevisionRedirectTarget( $rev ) ) {
861  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
862  $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
863  }
864  }
865 
870  private function getRevisionRedirectTarget( RevisionRecord $revision ) {
871  // TODO: find a *good* place for the code that determines the redirect target for
872  // a given revision!
873  // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
874  $content = $revision->getContent( SlotRecord::MAIN );
875  return $content ? $content->getRedirectTarget() : null;
876  }
877 
882  public function adjustDisplayTitle( ParserOutput $pOutput ) {
883  $out = $this->getContext()->getOutput();
884 
885  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
886  $titleText = $pOutput->getTitleText();
887  if ( strval( $titleText ) !== '' ) {
888  $out->setPageTitle( $titleText );
889  $out->setDisplayTitle( $titleText );
890  }
891  }
892 
897  protected function showDiffPage() {
898  $request = $this->getContext()->getRequest();
899  $user = $this->getContext()->getUser();
900  $diff = $request->getVal( 'diff' );
901  $rcid = $request->getVal( 'rcid' );
902  $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
903  $purge = $request->getVal( 'action' ) == 'purge';
904  $unhide = $request->getInt( 'unhide' ) == 1;
905  $oldid = $this->getOldID();
906 
907  $rev = $this->fetchRevisionRecord();
908 
909  if ( !$rev ) {
910  // T213621: $rev maybe null due to either lack of permission to view the
911  // revision or actually not existing. So let's try loading it from the id
912  $rev = $this->revisionStore->getRevisionById( $oldid );
913  if ( $rev ) {
914  // Revision exists but $user lacks permission to diff it.
915  // Do nothing here.
916  // The $rev will later be used to create standard diff elements however.
917  } else {
918  $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
919  $msg = $this->getContext()->msg( 'difference-missing-revision' )
920  ->params( $oldid )
921  ->numParams( 1 )
922  ->parseAsBlock();
923  $this->getContext()->getOutput()->addHTML( $msg );
924  return;
925  }
926  }
927 
928  $contentHandler = MediaWikiServices::getInstance()
929  ->getContentHandlerFactory()
930  ->getContentHandler(
931  $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
932  );
933  $de = $contentHandler->createDifferenceEngine(
934  $this->getContext(),
935  $oldid,
936  $diff,
937  $rcid,
938  $purge,
939  $unhide
940  );
941  $de->setSlotDiffOptions( [
942  'diff-type' => $request->getVal( 'diff-type' )
943  ] );
944  $de->showDiffPage( $diffOnly );
945 
946  // Run view updates for the newer revision being diffed (and shown
947  // below the diff if not $diffOnly).
948  list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
949  // New can be false, convert it to 0 - this conveniently means the latest revision
950  $this->mPage->doViewUpdates( $user, (int)$new );
951  }
952 
960  public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
962 
963  $ns = $this->getTitle()->getNamespace();
964 
965  # Don't index user and user talk pages for blocked users (T13443)
966  if ( ( $ns === NS_USER || $ns === NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
967  $specificTarget = null;
968  $vagueTarget = null;
969  $titleText = $this->getTitle()->getText();
970  if ( IPUtils::isValid( $titleText ) ) {
971  $vagueTarget = $titleText;
972  } else {
973  $specificTarget = $titleText;
974  }
975  if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
976  return [
977  'index' => 'noindex',
978  'follow' => 'nofollow'
979  ];
980  }
981  }
982 
983  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
984  # Non-articles (special pages etc), and old revisions
985  return [
986  'index' => 'noindex',
987  'follow' => 'nofollow'
988  ];
989  } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
990  # Discourage indexing of printable versions, but encourage following
991  return [
992  'index' => 'noindex',
993  'follow' => 'follow'
994  ];
995  } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
996  # For ?curid=x urls, disallow indexing
997  return [
998  'index' => 'noindex',
999  'follow' => 'follow'
1000  ];
1001  }
1002 
1003  # Otherwise, construct the policy based on the various config variables.
1005 
1006  if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
1007  # Honour customised robot policies for this namespace
1008  $policy = array_merge(
1009  $policy,
1010  self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
1011  );
1012  }
1013  if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
1014  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1015  # a final sanity check that we have really got the parser output.
1016  $policy = array_merge(
1017  $policy,
1018  [ 'index' => $pOutput->getIndexPolicy() ]
1019  );
1020  }
1021 
1022  if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
1023  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1024  $policy = array_merge(
1025  $policy,
1026  self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
1027  );
1028  }
1029 
1030  return $policy;
1031  }
1032 
1040  public static function formatRobotPolicy( $policy ) {
1041  if ( is_array( $policy ) ) {
1042  return $policy;
1043  } elseif ( !$policy ) {
1044  return [];
1045  }
1046 
1047  $policy = explode( ',', $policy );
1048  $policy = array_map( 'trim', $policy );
1049 
1050  $arr = [];
1051  foreach ( $policy as $var ) {
1052  if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
1053  $arr['index'] = $var;
1054  } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
1055  $arr['follow'] = $var;
1056  }
1057  }
1058 
1059  return $arr;
1060  }
1061 
1069  public function showRedirectedFromHeader() {
1070  global $wgRedirectSources;
1071 
1072  $context = $this->getContext();
1073  $outputPage = $context->getOutput();
1074  $request = $context->getRequest();
1075  $rdfrom = $request->getVal( 'rdfrom' );
1076 
1077  // Construct a URL for the current page view, but with the target title
1078  $query = $request->getValues();
1079  unset( $query['rdfrom'] );
1080  unset( $query['title'] );
1081  if ( $this->getTitle()->isRedirect() ) {
1082  // Prevent double redirects
1083  $query['redirect'] = 'no';
1084  }
1085  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1086 
1087  if ( isset( $this->mRedirectedFrom ) ) {
1088  // This is an internally redirected page view.
1089  // We'll need a backlink to the source page for navigation.
1090  if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1091  $redir = $this->linkRenderer->makeKnownLink(
1092  $this->mRedirectedFrom,
1093  null,
1094  [],
1095  [ 'redirect' => 'no' ]
1096  );
1097 
1098  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1099  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1100  . "</span>" );
1101 
1102  // Add the script to update the displayed URL and
1103  // set the fragment if one was specified in the redirect
1104  $outputPage->addJsConfigVars( [
1105  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1106  ] );
1107  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1108 
1109  // Add a <link rel="canonical"> tag
1110  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1111 
1112  // Tell the output object that the user arrived at this article through a redirect
1113  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1114 
1115  return true;
1116  }
1117  } elseif ( $rdfrom ) {
1118  // This is an externally redirected view, from some other wiki.
1119  // If it was reported from a trusted site, supply a backlink.
1120  if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
1121  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1122  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1123  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1124  . "</span>" );
1125 
1126  // Add the script to update the displayed URL
1127  $outputPage->addJsConfigVars( [
1128  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1129  ] );
1130  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1131 
1132  return true;
1133  }
1134  }
1135 
1136  return false;
1137  }
1138 
1143  public function showNamespaceHeader() {
1144  if ( $this->getTitle()->isTalkPage() && !wfMessage( 'talkpageheader' )->isDisabled() ) {
1145  $this->getContext()->getOutput()->wrapWikiMsg(
1146  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1147  [ 'talkpageheader' ]
1148  );
1149  }
1150  }
1151 
1155  public function showViewFooter() {
1156  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1157  if ( $this->getTitle()->getNamespace() === NS_USER_TALK
1158  && IPUtils::isValid( $this->getTitle()->getText() )
1159  ) {
1160  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1161  }
1162 
1163  // Show a footer allowing the user to patrol the shown revision or page if possible
1164  $patrolFooterShown = $this->showPatrolFooter();
1165 
1166  $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1167  }
1168 
1179  public function showPatrolFooter() {
1181 
1182  // Allow hooks to decide whether to not output this at all
1183  if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1184  return false;
1185  }
1186 
1187  $outputPage = $this->getContext()->getOutput();
1188  $user = $this->getContext()->getUser();
1189  $title = $this->getTitle();
1190  $rc = false;
1191 
1192  if ( !$this->permManager->quickUserCan( 'patrol', $user, $title )
1194  || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1195  ) {
1196  // Patrolling is disabled or the user isn't allowed to
1197  return false;
1198  }
1199 
1200  if ( $this->mRevisionRecord
1201  && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1202  ) {
1203  // The current revision is already older than what could be in the RC table
1204  // 6h tolerance because the RC might not be cleaned out regularly
1205  return false;
1206  }
1207 
1208  // Check for cached results
1209  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1210  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1211  if ( $cache->get( $key ) ) {
1212  return false;
1213  }
1214 
1215  $dbr = wfGetDB( DB_REPLICA );
1216  $oldestRevisionTimestamp = $dbr->selectField(
1217  'revision',
1218  'MIN( rev_timestamp )',
1219  [ 'rev_page' => $title->getArticleID() ],
1220  __METHOD__
1221  );
1222 
1223  // New page patrol: Get the timestamp of the oldest revison which
1224  // the revision table holds for the given page. Then we look
1225  // whether it's within the RC lifespan and if it is, we try
1226  // to get the recentchanges row belonging to that entry
1227  // (with rc_new = 1).
1228  $recentPageCreation = false;
1229  if ( $oldestRevisionTimestamp
1230  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1231  ) {
1232  // 6h tolerance because the RC might not be cleaned out regularly
1233  $recentPageCreation = true;
1235  [
1236  'rc_new' => 1,
1237  'rc_timestamp' => $oldestRevisionTimestamp,
1238  'rc_namespace' => $title->getNamespace(),
1239  'rc_cur_id' => $title->getArticleID()
1240  ],
1241  __METHOD__
1242  );
1243  if ( $rc ) {
1244  // Use generic patrol message for new pages
1245  $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1246  }
1247  }
1248 
1249  // File patrol: Get the timestamp of the latest upload for this page,
1250  // check whether it is within the RC lifespan and if it is, we try
1251  // to get the recentchanges row belonging to that entry
1252  // (with rc_type = RC_LOG, rc_log_type = upload).
1253  $recentFileUpload = false;
1254  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1255  && $title->getNamespace() === NS_FILE ) {
1256  // Retrieve timestamp of most recent upload
1257  $newestUploadTimestamp = $dbr->selectField(
1258  'image',
1259  'MAX( img_timestamp )',
1260  [ 'img_name' => $title->getDBkey() ],
1261  __METHOD__
1262  );
1263  if ( $newestUploadTimestamp
1264  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1265  ) {
1266  // 6h tolerance because the RC might not be cleaned out regularly
1267  $recentFileUpload = true;
1269  [
1270  'rc_type' => RC_LOG,
1271  'rc_log_type' => 'upload',
1272  'rc_timestamp' => $newestUploadTimestamp,
1273  'rc_namespace' => NS_FILE,
1274  'rc_cur_id' => $title->getArticleID()
1275  ],
1276  __METHOD__
1277  );
1278  if ( $rc ) {
1279  // Use patrol message specific to files
1280  $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1281  }
1282  }
1283  }
1284 
1285  if ( !$recentPageCreation && !$recentFileUpload ) {
1286  // Page creation and latest upload (for files) is too old to be in RC
1287 
1288  // We definitely can't patrol so cache the information
1289  // When a new file version is uploaded, the cache is cleared
1290  $cache->set( $key, '1' );
1291 
1292  return false;
1293  }
1294 
1295  if ( !$rc ) {
1296  // Don't cache: This can be hit if the page gets accessed very fast after
1297  // its creation / latest upload or in case we have high replica DB lag. In case
1298  // the revision is too old, we will already return above.
1299  return false;
1300  }
1301 
1302  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1303  // Patrolled RC entry around
1304 
1305  // Cache the information we gathered above in case we can't patrol
1306  // Don't cache in case we can patrol as this could change
1307  $cache->set( $key, '1' );
1308 
1309  return false;
1310  }
1311 
1312  if ( $rc->getPerformer()->equals( $user ) ) {
1313  // Don't show a patrol link for own creations/uploads. If the user could
1314  // patrol them, they already would be patrolled
1315  return false;
1316  }
1317 
1318  $outputPage->preventClickjacking();
1319  if ( $this->permManager->userHasRight( $user, 'writeapi' ) ) {
1320  $outputPage->addModules( 'mediawiki.misc-authed-curate' );
1321  }
1322 
1323  $link = $this->linkRenderer->makeKnownLink(
1324  $title,
1325  $markPatrolledMsg->text(),
1326  [],
1327  [
1328  'action' => 'markpatrolled',
1329  'rcid' => $rc->getAttribute( 'rc_id' ),
1330  ]
1331  );
1332 
1333  $outputPage->addHTML(
1334  "<div class='patrollink' data-mw='interface'>" .
1335  wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1336  '</div>'
1337  );
1338 
1339  return true;
1340  }
1341 
1348  public static function purgePatrolFooterCache( $articleID ) {
1349  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1350  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1351  }
1352 
1357  public function showMissingArticle() {
1358  global $wgSend404Code;
1359 
1360  $outputPage = $this->getContext()->getOutput();
1361  // Whether the page is a root user page of an existing user (but not a subpage)
1362  $validUserPage = false;
1363 
1364  $title = $this->getTitle();
1365 
1366  $services = MediaWikiServices::getInstance();
1367 
1368  $contextUser = $this->getContext()->getUser();
1369 
1370  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1371  if ( $title->getNamespace() === NS_USER
1372  || $title->getNamespace() === NS_USER_TALK
1373  ) {
1374  $rootPart = explode( '/', $title->getText() )[0];
1375  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1376  $ip = User::isIP( $rootPart );
1377  $block = DatabaseBlock::newFromTarget( $user, $user );
1378 
1379  if ( $user && $user->isRegistered() && $user->isHidden() &&
1380  !$services->getPermissionManager()
1381  ->userHasRight( $contextUser, 'hideuser' )
1382  ) {
1383  // T120883 if the user is hidden and the viewer cannot see hidden
1384  // users, pretend like it does not exist at all.
1385  $user = false;
1386  }
1387  if ( !( $user && $user->isRegistered() ) && !$ip ) { # User does not exist
1388  $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1389  [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1390  } elseif (
1391  $block !== null &&
1392  $block->getType() != DatabaseBlock::TYPE_AUTO &&
1393  ( $block->isSitewide() || $user->isBlockedFrom( $title ) )
1394  ) {
1395  // Show log extract if the user is sitewide blocked or is partially
1396  // blocked and not allowed to edit their user page or user talk page
1398  $outputPage,
1399  'block',
1400  $services->getNamespaceInfo()->getCanonicalName( NS_USER ) . ':' .
1401  $block->getTarget(),
1402  '',
1403  [
1404  'lim' => 1,
1405  'showIfEmpty' => false,
1406  'msgKey' => [
1407  'blocked-notice-logextract',
1408  $user->getName() # Support GENDER in notice
1409  ]
1410  ]
1411  );
1412  $validUserPage = !$title->isSubpage();
1413  } else {
1414  $validUserPage = !$title->isSubpage();
1415  }
1416  }
1417 
1418  $this->getHookRunner()->onShowMissingArticle( $this );
1419 
1420  # Show delete and move logs if there were any such events.
1421  # The logging query can DOS the site when bots/crawlers cause 404 floods,
1422  # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1423  $dbCache = ObjectCache::getInstance( 'db-replicated' );
1424  $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1425  $isRegistered = $contextUser->isRegistered();
1426  $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
1427 
1428  if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1429  $logTypes = [ 'delete', 'move', 'protect' ];
1430 
1431  $dbr = wfGetDB( DB_REPLICA );
1432 
1433  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1434  // Give extensions a chance to hide their (unrelated) log entries
1435  $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1437  $outputPage,
1438  $logTypes,
1439  $title,
1440  '',
1441  [
1442  'lim' => 10,
1443  'conds' => $conds,
1444  'showIfEmpty' => false,
1445  'msgKey' => [ $isRegistered || $sessionExists
1446  ? 'moveddeleted-notice'
1447  : 'moveddeleted-notice-recent'
1448  ]
1449  ]
1450  );
1451  }
1452 
1453  if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1454  // If there's no backing content, send a 404 Not Found
1455  // for better machine handling of broken links.
1456  $this->getContext()->getRequest()->response()->statusHeader( 404 );
1457  }
1458 
1459  // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1460  $policy = $this->getRobotPolicy( 'view' );
1461  $outputPage->setIndexPolicy( $policy['index'] );
1462  $outputPage->setFollowPolicy( $policy['follow'] );
1463 
1464  $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1465 
1466  if ( !$hookResult ) {
1467  return;
1468  }
1469 
1470  # Show error message
1471  $oldid = $this->getOldID();
1472  $pm = $this->permManager;
1473  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1474  $text = $this->getTitle()->getDefaultMessageText() ?? '';
1475  $outputPage->addWikiTextAsContent( $text );
1476  } else {
1477  if ( $oldid ) {
1478  // T251066: Try loading the revision from the archive table.
1479  // Show link to view it if it exists and the user has permission to view it.
1480  $pa = new PageArchive( $title, $this->getContext()->getConfig() );
1481  $revRecord = $pa->getArchivedRevisionRecord( $oldid );
1482  if ( $revRecord && $revRecord->audienceCan(
1483  RevisionRecord::DELETED_TEXT,
1484  RevisionRecord::FOR_THIS_USER,
1485  $contextUser
1486  ) ) {
1487  $text = wfMessage(
1488  'missing-revision-permission', $oldid,
1489  $revRecord->getTimestamp(),
1490  $title->getPrefixedDBkey()
1491  )->plain();
1492  } else {
1493  $text = wfMessage( 'missing-revision', $oldid )->plain();
1494  }
1495 
1496  } elseif ( $pm->quickUserCan( 'create', $contextUser, $title ) &&
1497  $pm->quickUserCan( 'edit', $contextUser, $title )
1498  ) {
1499  $message = $isRegistered ? 'noarticletext' : 'noarticletextanon';
1500  $text = wfMessage( $message )->plain();
1501  } else {
1502  $text = wfMessage( 'noarticletext-nopermission' )->plain();
1503  }
1504 
1505  $dir = $this->getContext()->getLanguage()->getDir();
1506  $lang = $this->getContext()->getLanguage()->getHtmlCode();
1507  $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1508  'class' => "noarticletext mw-content-$dir",
1509  'dir' => $dir,
1510  'lang' => $lang,
1511  ] ) . "\n$text\n</div>" );
1512  }
1513  }
1514 
1519  private function showViewError( string $errortext ) {
1520  $outputPage = $this->getContext()->getOutput();
1521  $outputPage->setPageTitle( $this->getContext()->msg( 'errorpagetitle' ) );
1522  $outputPage->enableClientCache( false );
1523  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1524  $outputPage->clearHTML();
1525  $outputPage->wrapWikiTextAsInterface( 'errorbox', $errortext );
1526  }
1527 
1534  public function showDeletedRevisionHeader() {
1535  if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1536  // Not deleted
1537  return true;
1538  }
1539  $outputPage = $this->getContext()->getOutput();
1540  $user = $this->getContext()->getUser();
1541  // Used in wikilinks, should not contain whitespaces
1542  $titleText = $this->getTitle()->getPrefixedDBkey();
1543  // If the user is not allowed to see it...
1544  if ( !RevisionRecord::userCanBitfield(
1545  $this->mRevisionRecord->getVisibility(),
1546  RevisionRecord::DELETED_TEXT,
1547  $user
1548  ) ) {
1549  $outputPage->addHtml(
1551  $outputPage->msg( 'rev-deleted-text-permission', $titleText )->parse(),
1552  'plainlinks'
1553  )
1554  );
1555 
1556  return false;
1557  // If the user needs to confirm that they want to see it...
1558  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1559  # Give explanation and add a link to view the revision...
1560  $oldid = intval( $this->getOldID() );
1561  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1562  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1563  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1564  $outputPage->addHtml(
1566  $outputPage->msg( $msg, $link )->parse(),
1567  'plainlinks'
1568  )
1569  );
1570 
1571  return false;
1572  // We are allowed to see...
1573  } else {
1574  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1575  ? [ 'rev-suppressed-text-view', $titleText ]
1576  : [ 'rev-deleted-text-view', $titleText ];
1577  $outputPage->addHtml(
1579  $outputPage->msg( $msg[0], $msg[1] )->parse(),
1580  'plainlinks'
1581  )
1582  );
1583 
1584  return true;
1585  }
1586  }
1587 
1596  public function setOldSubtitle( $oldid = 0 ) {
1597  if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1598  return;
1599  }
1600 
1601  $context = $this->getContext();
1602  $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1603 
1604  # Cascade unhide param in links for easy deletion browsing
1605  $extraParams = [];
1606  if ( $unhide ) {
1607  $extraParams['unhide'] = 1;
1608  }
1609 
1610  if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1611  $revisionRecord = $this->mRevisionRecord;
1612  } else {
1613  $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1614  }
1615 
1616  $timestamp = $revisionRecord->getTimestamp();
1617 
1618  $current = ( $oldid == $this->mPage->getLatest() );
1619  $language = $context->getLanguage();
1620  $user = $context->getUser();
1621 
1622  $td = $language->userTimeAndDate( $timestamp, $user );
1623  $tddate = $language->userDate( $timestamp, $user );
1624  $tdtime = $language->userTime( $timestamp, $user );
1625 
1626  # Show user links if allowed to see them. If hidden, then show them only if requested...
1627  $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1628 
1629  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1630  ? 'revision-info-current'
1631  : 'revision-info';
1632 
1633  $outputPage = $context->getOutput();
1634  $revisionUser = $revisionRecord->getUser();
1635  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1636  $context->msg( $infomsg, $td )
1637  ->rawParams( $userlinks )
1638  ->params(
1639  $revisionRecord->getId(),
1640  $tddate,
1641  $tdtime,
1642  $revisionUser ? $revisionUser->getName() : ''
1643  )
1644  ->rawParams( Linker::revComment(
1645  $revisionRecord,
1646  true,
1647  true
1648  ) )
1649  ->parse() .
1650  "</div>";
1651 
1652  $lnk = $current
1653  ? $context->msg( 'currentrevisionlink' )->escaped()
1654  : $this->linkRenderer->makeKnownLink(
1655  $this->getTitle(),
1656  $context->msg( 'currentrevisionlink' )->text(),
1657  [],
1658  $extraParams
1659  );
1660  $curdiff = $current
1661  ? $context->msg( 'diff' )->escaped()
1662  : $this->linkRenderer->makeKnownLink(
1663  $this->getTitle(),
1664  $context->msg( 'diff' )->text(),
1665  [],
1666  [
1667  'diff' => 'cur',
1668  'oldid' => $oldid
1669  ] + $extraParams
1670  );
1671  $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1672  $prevlink = $prevExist
1673  ? $this->linkRenderer->makeKnownLink(
1674  $this->getTitle(),
1675  $context->msg( 'previousrevision' )->text(),
1676  [],
1677  [
1678  'direction' => 'prev',
1679  'oldid' => $oldid
1680  ] + $extraParams
1681  )
1682  : $context->msg( 'previousrevision' )->escaped();
1683  $prevdiff = $prevExist
1684  ? $this->linkRenderer->makeKnownLink(
1685  $this->getTitle(),
1686  $context->msg( 'diff' )->text(),
1687  [],
1688  [
1689  'diff' => 'prev',
1690  'oldid' => $oldid
1691  ] + $extraParams
1692  )
1693  : $context->msg( 'diff' )->escaped();
1694  $nextlink = $current
1695  ? $context->msg( 'nextrevision' )->escaped()
1696  : $this->linkRenderer->makeKnownLink(
1697  $this->getTitle(),
1698  $context->msg( 'nextrevision' )->text(),
1699  [],
1700  [
1701  'direction' => 'next',
1702  'oldid' => $oldid
1703  ] + $extraParams
1704  );
1705  $nextdiff = $current
1706  ? $context->msg( 'diff' )->escaped()
1707  : $this->linkRenderer->makeKnownLink(
1708  $this->getTitle(),
1709  $context->msg( 'diff' )->text(),
1710  [],
1711  [
1712  'diff' => 'next',
1713  'oldid' => $oldid
1714  ] + $extraParams
1715  );
1716 
1717  $cdel = Linker::getRevDeleteLink(
1718  $user,
1719  $revisionRecord,
1720  $this->getTitle()
1721  );
1722  if ( $cdel !== '' ) {
1723  $cdel .= ' ';
1724  }
1725 
1726  // the outer div is need for styling the revision info and nav in MobileFrontend
1727  $outputPage->addSubtitle( "<div class=\"mw-revision warningbox\">" . $revisionInfo .
1728  "<div id=\"mw-revision-nav\">" . $cdel .
1729  $context->msg( 'revision-nav' )->rawParams(
1730  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1731  )->escaped() . "</div></div>" );
1732  }
1733 
1747  public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1748  $lang = $this->getTitle()->getPageLanguage();
1749  $out = $this->getContext()->getOutput();
1750  if ( $appendSubtitle ) {
1751  $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1752  }
1753  $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1754  return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1755  }
1756 
1769  public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1770  if ( !is_array( $target ) ) {
1771  $target = [ $target ];
1772  }
1773 
1774  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1775 
1776  $html = '<ul class="redirectText">';
1778  foreach ( $target as $title ) {
1779  if ( $forceKnown ) {
1780  $link = $linkRenderer->makeKnownLink(
1781  $title,
1782  $title->getFullText(),
1783  [],
1784  // Make sure wiki page redirects are not followed
1785  $title->isRedirect() ? [ 'redirect' => 'no' ] : []
1786  );
1787  } else {
1788  $link = $linkRenderer->makeLink(
1789  $title,
1790  $title->getFullText(),
1791  [],
1792  // Make sure wiki page redirects are not followed
1793  $title->isRedirect() ? [ 'redirect' => 'no' ] : []
1794  );
1795  }
1796  $html .= '<li>' . $link . '</li>';
1797  }
1798  $html .= '</ul>';
1799 
1800  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1801 
1802  return '<div class="redirectMsg">' .
1803  '<p>' . $redirectToText . '</p>' .
1804  $html .
1805  '</div>';
1806  }
1807 
1816  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1817  $out = $this->getContext()->getOutput();
1818  $msg = $out->msg( 'namespace-' . $this->getTitle()->getNamespace() . '-helppage' );
1819 
1820  if ( !$msg->isDisabled() ) {
1821  $title = Title::newFromText( $msg->plain() );
1822  if ( $title instanceof Title ) {
1823  $out->addHelpLink( $title->getLocalURL(), true );
1824  }
1825  } else {
1826  $out->addHelpLink( $to, $overrideBaseUrl );
1827  }
1828  }
1829 
1833  public function render() {
1834  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1835  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1836  // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1837  $this->viewIsRenderAction = true;
1838  $this->view();
1839  }
1840 
1844  public function protect() {
1845  $form = new ProtectionForm( $this );
1846  $form->execute();
1847  }
1848 
1852  public function unprotect() {
1853  $this->protect();
1854  }
1855 
1859  public function delete() {
1860  # This code desperately needs to be totally rewritten
1861 
1862  $title = $this->getTitle();
1863  $context = $this->getContext();
1864  $user = $context->getUser();
1865  $request = $context->getRequest();
1866 
1867  # Check permissions
1868  $permissionErrors = $this->permManager->getPermissionErrors( 'delete', $user, $title );
1869  if ( count( $permissionErrors ) ) {
1870  throw new PermissionsError( 'delete', $permissionErrors );
1871  }
1872 
1873  # Read-only check...
1874  if ( wfReadOnly() ) {
1875  throw new ReadOnlyError;
1876  }
1877 
1878  # Better double-check that it hasn't been deleted yet!
1879  $this->mPage->loadPageData(
1880  $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1881  );
1882  if ( !$this->mPage->exists() ) {
1883  $deleteLogPage = new LogPage( 'delete' );
1884  $outputPage = $context->getOutput();
1885  $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1886  $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1887  [ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1888  );
1889  $outputPage->addHTML(
1890  Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1891  );
1893  $outputPage,
1894  'delete',
1895  $title
1896  );
1897 
1898  return;
1899  }
1900 
1901  $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1902  $deleteReason = $request->getText( 'wpReason' );
1903 
1904  if ( $deleteReasonList == 'other' ) {
1905  $reason = $deleteReason;
1906  } elseif ( $deleteReason != '' ) {
1907  // Entry from drop down menu + additional comment
1908  $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1909  $reason = $deleteReasonList . $colonseparator . $deleteReason;
1910  } else {
1911  $reason = $deleteReasonList;
1912  }
1913 
1914  if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1915  [ 'delete', $this->getTitle()->getPrefixedText() ] )
1916  ) {
1917  # Flag to hide all contents of the archived revisions
1918 
1919  $suppress = $request->getCheck( 'wpSuppress' ) &&
1920  $this->permManager->userHasRight( $user, 'suppressrevision' );
1921 
1922  $this->doDelete( $reason, $suppress );
1923 
1924  WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1925 
1926  return;
1927  }
1928 
1929  // Generate deletion reason
1930  $hasHistory = false;
1931  if ( !$reason ) {
1932  try {
1933  $reason = $this->getPage()
1934  ->getAutoDeleteReason( $hasHistory );
1935  } catch ( Exception $e ) {
1936  # if a page is horribly broken, we still want to be able to
1937  # delete it. So be lenient about errors here.
1938  wfDebug( "Error while building auto delete summary: $e" );
1939  $reason = '';
1940  }
1941  }
1942 
1943  // If the page has a history, insert a warning
1944  if ( $hasHistory ) {
1945  $title = $this->getTitle();
1946 
1947  // The following can use the real revision count as this is only being shown for users
1948  // that can delete this page.
1949  // This, as a side-effect, also makes sure that the following query isn't being run for
1950  // pages with a larger history, unless the user has the 'bigdelete' right
1951  // (and is about to delete this page).
1952  $dbr = wfGetDB( DB_REPLICA );
1953  $revisions = $edits = (int)$dbr->selectField(
1954  'revision',
1955  'COUNT(rev_page)',
1956  [ 'rev_page' => $title->getArticleID() ],
1957  __METHOD__
1958  );
1959 
1960  // @todo i18n issue/patchwork message
1961  $context->getOutput()->addHTML(
1962  '<strong class="mw-delete-warning-revisions">' .
1963  $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1964  $context->msg( 'word-separator' )->escaped() . $this->linkRenderer->makeKnownLink(
1965  $title,
1966  $context->msg( 'history' )->text(),
1967  [],
1968  [ 'action' => 'history' ] ) .
1969  '</strong>'
1970  );
1971 
1972  if ( $title->isBigDeletion() ) {
1973  global $wgDeleteRevisionsLimit;
1974  $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1975  [
1976  'delete-warning-toobig',
1977  $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1978  ]
1979  );
1980  }
1981  }
1982 
1983  $this->confirmDelete( $reason );
1984  }
1985 
1991  public function confirmDelete( $reason ) {
1992  wfDebug( "Article::confirmDelete" );
1993 
1994  $title = $this->getTitle();
1995  $ctx = $this->getContext();
1996  $outputPage = $ctx->getOutput();
1997  $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1998  $outputPage->addBacklinkSubtitle( $title );
1999  $outputPage->setRobotPolicy( 'noindex,nofollow' );
2000  $outputPage->addModules( 'mediawiki.action.delete' );
2001 
2002  $backlinkCache = $title->getBacklinkCache();
2003  if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
2004  $outputPage->addHtml(
2006  $outputPage->msg( 'deleting-backlinks-warning' )->parse(),
2007  'plainlinks'
2008  )
2009  );
2010  }
2011 
2012  $subpageQueryLimit = 51;
2013  $subpages = $title->getSubpages( $subpageQueryLimit );
2014  $subpageCount = count( $subpages );
2015  if ( $subpageCount > 0 ) {
2016  $outputPage->addHtml(
2018  $outputPage->msg( 'deleting-subpages-warning', Message::numParam( $subpageCount ) )->parse(),
2019  'plainlinks'
2020  )
2021  );
2022  }
2023  $outputPage->addWikiMsg( 'confirmdeletetext' );
2024 
2025  $this->getHookRunner()->onArticleConfirmDelete( $this, $outputPage, $reason );
2026 
2027  $user = $this->getContext()->getUser();
2028  $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
2029 
2030  $outputPage->enableOOUI();
2031 
2032  $fields = [];
2033 
2034  $suppressAllowed = $this->permManager->userHasRight( $user, 'suppressrevision' );
2035  $dropDownReason = $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text();
2036  // Add additional specific reasons for suppress
2037  if ( $suppressAllowed ) {
2038  $dropDownReason .= "\n" . $ctx->msg( 'deletereason-dropdown-suppress' )
2039  ->inContentLanguage()->text();
2040  }
2041 
2042  $options = Xml::listDropDownOptions(
2043  $dropDownReason,
2044  [ 'other' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text() ]
2045  );
2046  $options = Xml::listDropDownOptionsOoui( $options );
2047 
2048  $fields[] = new OOUI\FieldLayout(
2049  new OOUI\DropdownInputWidget( [
2050  'name' => 'wpDeleteReasonList',
2051  'inputId' => 'wpDeleteReasonList',
2052  'tabIndex' => 1,
2053  'infusable' => true,
2054  'value' => '',
2055  'options' => $options
2056  ] ),
2057  [
2058  'label' => $ctx->msg( 'deletecomment' )->text(),
2059  'align' => 'top',
2060  ]
2061  );
2062 
2063  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
2064  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
2065  // Unicode codepoints.
2066  $fields[] = new OOUI\FieldLayout(
2067  new OOUI\TextInputWidget( [
2068  'name' => 'wpReason',
2069  'inputId' => 'wpReason',
2070  'tabIndex' => 2,
2072  'infusable' => true,
2073  'value' => $reason,
2074  'autofocus' => true,
2075  ] ),
2076  [
2077  'label' => $ctx->msg( 'deleteotherreason' )->text(),
2078  'align' => 'top',
2079  ]
2080  );
2081 
2082  if ( $user->isRegistered() ) {
2083  $fields[] = new OOUI\FieldLayout(
2084  new OOUI\CheckboxInputWidget( [
2085  'name' => 'wpWatch',
2086  'inputId' => 'wpWatch',
2087  'tabIndex' => 3,
2088  'selected' => $checkWatch,
2089  ] ),
2090  [
2091  'label' => $ctx->msg( 'watchthis' )->text(),
2092  'align' => 'inline',
2093  'infusable' => true,
2094  ]
2095  );
2096  }
2097  if ( $suppressAllowed ) {
2098  $fields[] = new OOUI\FieldLayout(
2099  new OOUI\CheckboxInputWidget( [
2100  'name' => 'wpSuppress',
2101  'inputId' => 'wpSuppress',
2102  'tabIndex' => 4,
2103  ] ),
2104  [
2105  'label' => $ctx->msg( 'revdelete-suppress' )->text(),
2106  'align' => 'inline',
2107  'infusable' => true,
2108  ]
2109  );
2110  }
2111 
2112  $fields[] = new OOUI\FieldLayout(
2113  new OOUI\ButtonInputWidget( [
2114  'name' => 'wpConfirmB',
2115  'inputId' => 'wpConfirmB',
2116  'tabIndex' => 5,
2117  'value' => $ctx->msg( 'deletepage' )->text(),
2118  'label' => $ctx->msg( 'deletepage' )->text(),
2119  'flags' => [ 'primary', 'destructive' ],
2120  'type' => 'submit',
2121  ] ),
2122  [
2123  'align' => 'top',
2124  ]
2125  );
2126 
2127  $fieldset = new OOUI\FieldsetLayout( [
2128  'label' => $ctx->msg( 'delete-legend' )->text(),
2129  'id' => 'mw-delete-table',
2130  'items' => $fields,
2131  ] );
2132 
2133  $form = new OOUI\FormLayout( [
2134  'method' => 'post',
2135  'action' => $title->getLocalURL( 'action=delete' ),
2136  'id' => 'deleteconfirm',
2137  ] );
2138  $form->appendContent(
2139  $fieldset,
2140  new OOUI\HtmlSnippet(
2141  Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
2142  )
2143  );
2144 
2145  $outputPage->addHTML(
2146  new OOUI\PanelLayout( [
2147  'classes' => [ 'deletepage-wrapper' ],
2148  'expanded' => false,
2149  'padded' => true,
2150  'framed' => true,
2151  'content' => $form,
2152  ] )
2153  );
2154 
2155  if ( $this->permManager->userHasRight( $user, 'editinterface' ) ) {
2156  $link = '';
2157  if ( $suppressAllowed ) {
2158  $link .= $this->linkRenderer->makeKnownLink(
2159  $ctx->msg( 'deletereason-dropdown-suppress' )->inContentLanguage()->getTitle(),
2160  $ctx->msg( 'delete-edit-reasonlist-suppress' )->text(),
2161  [],
2162  [ 'action' => 'edit' ]
2163  );
2164  $link .= $ctx->msg( 'pipe-separator' )->escaped();
2165  }
2166  $link .= $this->linkRenderer->makeKnownLink(
2167  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
2168  $ctx->msg( 'delete-edit-reasonlist' )->text(),
2169  [],
2170  [ 'action' => 'edit' ]
2171  );
2172  $outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
2173  }
2174 
2175  $deleteLogPage = new LogPage( 'delete' );
2176  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2177  LogEventsList::showLogExtract( $outputPage, 'delete', $title );
2178  }
2179 
2188  public function doDelete( $reason, $suppress = false, $immediate = false ) {
2189  $error = '';
2190  $context = $this->getContext();
2191  $outputPage = $context->getOutput();
2192  $user = $context->getUser();
2193  $status = $this->mPage->doDeleteArticleReal(
2194  $reason, $user, $suppress, null, $error,
2195  null, [], 'delete', $immediate
2196  );
2197 
2198  if ( $status->isOK() ) {
2199  $deleted = $this->getTitle()->getPrefixedText();
2200 
2201  $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
2202  $outputPage->setRobotPolicy( 'noindex,nofollow' );
2203 
2204  if ( $status->isGood() ) {
2205  $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
2206  $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
2207  $this->getHookRunner()->onArticleDeleteAfterSuccess( $this->getTitle(), $outputPage );
2208  } else {
2209  $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
2210  }
2211 
2212  $outputPage->returnToMain( false );
2213  } else {
2214  $outputPage->setPageTitle(
2215  wfMessage( 'cannotdelete-title',
2216  $this->getTitle()->getPrefixedText() )
2217  );
2218 
2219  if ( $error == '' ) {
2220  $outputPage->wrapWikiTextAsInterface(
2221  'error mw-error-cannotdelete',
2222  $status->getWikiText( false, false, $context->getLanguage() )
2223  );
2224  $deleteLogPage = new LogPage( 'delete' );
2225  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2226 
2228  $outputPage,
2229  'delete',
2230  $this->getTitle()
2231  );
2232  } else {
2233  $outputPage->addHTML( $error );
2234  }
2235  }
2236  }
2237 
2238  /* Caching functions */
2239 
2247  protected function tryFileCache() {
2248  static $called = false;
2249 
2250  if ( $called ) {
2251  wfDebug( "Article::tryFileCache(): called twice!?" );
2252  return false;
2253  }
2254 
2255  $called = true;
2256  if ( $this->isFileCacheable() ) {
2257  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
2258  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
2259  wfDebug( "Article::tryFileCache(): about to load file" );
2260  $cache->loadFromFileCache( $this->getContext() );
2261  return true;
2262  } else {
2263  wfDebug( "Article::tryFileCache(): starting buffer" );
2264  ob_start( [ &$cache, 'saveToFileCache' ] );
2265  }
2266  } else {
2267  wfDebug( "Article::tryFileCache(): not cacheable" );
2268  }
2269 
2270  return false;
2271  }
2272 
2278  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
2279  $cacheable = false;
2280 
2281  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
2282  $cacheable = $this->mPage->getId()
2283  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
2284  // Extension may have reason to disable file caching on some pages.
2285  if ( $cacheable ) {
2286  $cacheable = $this->getHookRunner()->onIsFileCacheable( $this );
2287  }
2288  }
2289 
2290  return $cacheable;
2291  }
2292 
2306  public function getParserOutput( $oldid = null, User $user = null ) {
2307  if ( $user === null ) {
2308  $parserOptions = $this->getParserOptions();
2309  } else {
2310  $parserOptions = $this->mPage->makeParserOptions( $user );
2311  }
2312 
2313  return $this->mPage->getParserOutput( $parserOptions, $oldid );
2314  }
2315 
2320  public function getParserOptions() {
2321  return $this->mPage->makeParserOptions( $this->getContext() );
2322  }
2323 
2330  public function setContext( $context ) {
2331  $this->mContext = $context;
2332  }
2333 
2340  public function getContext() {
2341  if ( $this->mContext instanceof IContextSource ) {
2342  return $this->mContext;
2343  } else {
2344  wfDebug( __METHOD__ . " called and \$mContext is null. " .
2345  "Return RequestContext::getMain(); for sanity" );
2346  return RequestContext::getMain();
2347  }
2348  }
2349 
2359  public function __get( $fname ) {
2360  wfDeprecatedMsg( "Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
2361  '1.35' );
2362 
2363  if ( $fname === 'mRevision' ) {
2364  $record = $this->fetchRevisionRecord(); // Ensure that it is loaded
2365  return $record ? new Revision( $record ) : null;
2366  }
2367 
2368  if ( property_exists( $this->mPage, $fname ) ) {
2369  return $this->mPage->$fname;
2370  }
2371  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2372  }
2373 
2383  public function __set( $fname, $fvalue ) {
2384  wfDeprecatedMsg( "Setting Article::\$$fname is deprecated since MediaWiki 1.35",
2385  '1.35' );
2386 
2387  if ( $fname === 'mRevision' ) {
2388  $this->mRevisionRecord = $fvalue ?
2389  $fvalue->getRevisionRecord() :
2390  null;
2391  return;
2392  }
2393 
2394  if ( property_exists( $this->mPage, $fname ) ) {
2395  $this->mPage->$fname = $fvalue;
2396  // Note: extensions may want to toss on new fields
2397  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2398  $this->mPage->$fname = $fvalue;
2399  } else {
2400  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2401  }
2402  }
2403 
2415  public function checkFlags( $flags ) {
2416  wfDeprecated( __METHOD__, '1.35' );
2417  return $this->mPage->checkFlags( $flags );
2418  }
2419 
2425  public function checkTouched() {
2426  wfDeprecated( __METHOD__, '1.35' );
2427  return $this->mPage->checkTouched();
2428  }
2429 
2435  public function clearPreparedEdit() {
2436  wfDeprecated( __METHOD__, '1.35' );
2437  $this->mPage->clearPreparedEdit();
2438  }
2439 
2448  public function doDeleteUpdates(
2449  $id,
2450  Content $content = null,
2451  $revision = null,
2452  User $user = null
2453  ) {
2454  wfDeprecated( __METHOD__, '1.35' );
2455  $this->mPage->doDeleteUpdates( $id, $content, $revision, $user );
2456  }
2457 
2467  public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2468  wfDeprecated( __METHOD__, '1.35' );
2469  $this->mPage->doEditUpdates( $revision, $user, $options );
2470  }
2471 
2479  public function doPurge() {
2480  wfDeprecated( __METHOD__, '1.35' );
2481  return $this->mPage->doPurge();
2482  }
2483 
2490  public function doViewUpdates( User $user, $oldid = 0 ) {
2491  wfDeprecated( __METHOD__, '1.35' );
2492  $this->mPage->doViewUpdates( $user, $oldid );
2493  }
2494 
2500  public function exists() {
2501  wfDeprecated( __METHOD__, '1.35' );
2502  return $this->mPage->exists();
2503  }
2504 
2510  public function followRedirect() {
2511  wfDeprecated( __METHOD__, '1.35' );
2512  return $this->mPage->followRedirect();
2513  }
2514 
2520  public function getActionOverrides() {
2521  return $this->mPage->getActionOverrides();
2522  }
2523 
2530  public function getAutoDeleteReason( &$hasHistory ) {
2531  wfDeprecated( __METHOD__, '1.35' );
2532  return $this->mPage->getAutoDeleteReason( $hasHistory );
2533  }
2534 
2540  public function getCategories() {
2541  wfDeprecated( __METHOD__, '1.35' );
2542  return $this->mPage->getCategories();
2543  }
2544 
2550  public function getContentHandler() {
2551  wfDeprecated( __METHOD__, '1.35' );
2552  return $this->mPage->getContentHandler();
2553  }
2554 
2560  public function getContentModel() {
2561  wfDeprecated( __METHOD__, '1.35' );
2562  return $this->mPage->getContentModel();
2563  }
2564 
2570  public function getContributors() {
2571  wfDeprecated( __METHOD__, '1.35' );
2572  return $this->mPage->getContributors();
2573  }
2574 
2581  public function getDeletionUpdates( Content $content = null ) {
2582  wfDeprecated( __METHOD__, '1.35' );
2583  return $this->mPage->getDeletionUpdates( $content );
2584  }
2585 
2591  public function getHiddenCategories() {
2592  wfDeprecated( __METHOD__, '1.35' );
2593  return $this->mPage->getHiddenCategories();
2594  }
2595 
2601  public function getId() {
2602  wfDeprecated( __METHOD__, '1.35' );
2603  return $this->mPage->getId();
2604  }
2605 
2611  public function getLatest() {
2612  wfDeprecated( __METHOD__, '1.35' );
2613  return $this->mPage->getLatest();
2614  }
2615 
2621  public function getLinksTimestamp() {
2622  wfDeprecated( __METHOD__, '1.35' );
2623  return $this->mPage->getLinksTimestamp();
2624  }
2625 
2631  public function getMinorEdit() {
2632  wfDeprecated( __METHOD__, '1.35' );
2633  return $this->mPage->getMinorEdit();
2634  }
2635 
2640  public function getOldestRevision() {
2641  wfDeprecated( __METHOD__, '1.35' );
2642  return $this->mPage->getOldestRevision();
2643  }
2644 
2650  public function getRedirectTarget() {
2651  wfDeprecated( __METHOD__, '1.35' );
2652  return $this->mPage->getRedirectTarget();
2653  }
2654 
2661  public function getRedirectURL( $rt ) {
2662  wfDeprecated( __METHOD__, '1.35' );
2663  return $this->mPage->getRedirectURL( $rt );
2664  }
2665 
2672  public function getRevision() {
2673  wfDeprecated( __METHOD__, '1.35' );
2674  return $this->mPage->getRevision();
2675  }
2676 
2682  public function getTimestamp() {
2683  wfDeprecated( __METHOD__, '1.35' );
2684  return $this->mPage->getTimestamp();
2685  }
2686 
2692  public function getTouched() {
2693  wfDeprecated( __METHOD__, '1.35' );
2694  return $this->mPage->getTouched();
2695  }
2696 
2705  public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2706  wfDeprecated( __METHOD__, '1.35' );
2707  return $this->mPage->getUndoContent( $undo, $undoafter );
2708  }
2709 
2715  public function hasViewableContent() {
2716  wfDeprecated( __METHOD__, '1.35' );
2717  return $this->mPage->hasViewableContent();
2718  }
2719 
2727  public function insertOn( $dbw, $pageId = null ) {
2728  wfDeprecated( __METHOD__, '1.35' );
2729  return $this->mPage->insertOn( $dbw, $pageId );
2730  }
2731 
2737  public function insertRedirect() {
2738  wfDeprecated( __METHOD__, '1.35' );
2739  return $this->mPage->insertRedirect();
2740  }
2741 
2749  public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2750  wfDeprecated( __METHOD__, '1.35' );
2751  return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2752  }
2753 
2760  public function isCountable( $editInfo = false ) {
2761  wfDeprecated( __METHOD__, '1.35' );
2762  return $this->mPage->isCountable( $editInfo );
2763  }
2764 
2770  public function isRedirect() {
2771  wfDeprecated( __METHOD__, '1.35' );
2772  return $this->mPage->isRedirect();
2773  }
2774 
2781  public function loadFromRow( $data, $from ) {
2782  wfDeprecated( __METHOD__, '1.35' );
2783  $this->mPage->loadFromRow( $data, $from );
2784  }
2785 
2791  public function loadPageData( $from = 'fromdb' ) {
2792  wfDeprecated( __METHOD__, '1.35' );
2793  $this->mPage->loadPageData( $from );
2794  }
2795 
2801  public function lockAndGetLatest() {
2802  wfDeprecated( __METHOD__, '1.35' );
2803  return $this->mPage->lockAndGetLatest();
2804  }
2805 
2812  public function makeParserOptions( $context ) {
2813  wfDeprecated( __METHOD__, '1.35' );
2814  return $this->mPage->makeParserOptions( $context );
2815  }
2816 
2825  public function pageDataFromId( $dbr, $id, $options = [] ) {
2826  wfDeprecated( __METHOD__, '1.35' );
2827  return $this->mPage->pageDataFromId( $dbr, $id, $options );
2828  }
2829 
2838  public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2839  wfDeprecated( __METHOD__, '1.35' );
2840  return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2841  }
2842 
2856  public function prepareContentForEdit(
2857  Content $content, $revision = null, User $user = null,
2858  $serialFormat = null, $useCache = true
2859  ) {
2860  wfDeprecated( __METHOD__, '1.35' );
2861  return $this->mPage->prepareContentForEdit(
2862  $content, $revision, $user,
2863  $serialFormat, $useCache
2864  );
2865  }
2866 
2874  public function protectDescription( array $limit, array $expiry ) {
2875  wfDeprecated( __METHOD__, '1.35' );
2876  return $this->mPage->protectDescription( $limit, $expiry );
2877  }
2878 
2886  public function protectDescriptionLog( array $limit, array $expiry ) {
2887  wfDeprecated( __METHOD__, '1.35' );
2888  return $this->mPage->protectDescriptionLog( $limit, $expiry );
2889  }
2890 
2900  public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2901  $sectionTitle = '', $baseRevId = null
2902  ) {
2903  wfDeprecated( __METHOD__, '1.35' );
2904  return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2905  $sectionTitle, $baseRevId
2906  );
2907  }
2908 
2920  public function replaceSectionContent(
2921  $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2922  ) {
2923  wfDeprecated( __METHOD__, '1.35' );
2924  return $this->mPage->replaceSectionContent(
2925  $sectionId, $sectionContent, $sectionTitle, $edittime
2926  );
2927  }
2928 
2934  public function setTimestamp( $ts ) {
2935  wfDeprecated( __METHOD__, '1.35' );
2936  $this->mPage->setTimestamp( $ts );
2937  }
2938 
2946  public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2947  wfDeprecated( __METHOD__, '1.35' );
2948  return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2949  }
2950 
2956  public function supportsSections() {
2957  wfDeprecated( __METHOD__, '1.35' );
2958  return $this->mPage->supportsSections();
2959  }
2960 
2966  public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2967  wfDeprecated( __METHOD__, '1.35' );
2968  $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2969  }
2970 
2978  public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
2979  wfDeprecated( __METHOD__, '1.35' );
2980  $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2981  }
2982 
2991  public function updateIfNewerOn( $dbw, $revision ) {
2992  wfDeprecated( __METHOD__, '1.35' );
2993  return $this->mPage->updateIfNewerOn( $dbw, $revision );
2994  }
2995 
3004  public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
3005  wfDeprecated( __METHOD__, '1.35' );
3006  return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect );
3007  }
3008 
3018  public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
3019  $lastRevIsRedirect = null
3020  ) {
3021  wfDeprecated( __METHOD__, '1.35' );
3022  return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
3023  $lastRevIsRedirect
3024  );
3025  }
3026 
3036  public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
3037  $reason, User $user
3038  ) {
3039  wfDeprecated( __METHOD__, '1.35' );
3040  return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
3041  }
3042 
3051  public function updateRestrictions( $limit = [], $reason = '',
3052  &$cascade = 0, $expiry = []
3053  ) {
3054  wfDeprecated( __METHOD__, '1.35' );
3055  return $this->mPage->doUpdateRestrictions(
3056  $limit,
3057  $expiry,
3058  $cascade,
3059  $reason,
3060  $this->getContext()->getUser()
3061  );
3062  }
3063 
3074  public function doRollback(
3075  $fromP,
3076  $summary,
3077  $token,
3078  $bot,
3079  &$resultDetails,
3080  User $user = null
3081  ) {
3082  wfDeprecated( __METHOD__, '1.35' );
3083  if ( !$user ) {
3084  $user = $this->getContext()->getUser();
3085  }
3086 
3087  return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
3088  }
3089 
3100  public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
3101  wfDeprecated( __METHOD__, '1.35' );
3102  if ( !$guser ) {
3103  $guser = $this->getContext()->getUser();
3104  }
3105 
3106  return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
3107  }
3108 
3115  public function generateReason( &$hasHistory ) {
3116  wfDeprecated( __METHOD__, '1.35' );
3117  return $this->getPage()->getAutoDeleteReason( $hasHistory );
3118  }
3119 
3120 }
ReadOnlyError
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Definition: ReadOnlyError.php:29
$wgSend404Code
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code,...
Definition: DefaultSettings.php:3662
Article\showMissingArticle
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1357
Article\checkFlags
checkFlags( $flags)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2415
$wgCdnMaxageStale
$wgCdnMaxageStale
Cache timeout when delivering a stale ParserCache response due to PoolCounter contention.
Definition: DefaultSettings.php:2939
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:44
HTMLFileCache\useFileCache
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
Definition: HTMLFileCache.php:93
Article\$mContext
IContextSource null $mContext
The context this Article is executed in.
Definition: Article.php:56
Message\numParam
static numParam( $num)
Definition: Message.php:1038
Page
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:29
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:361
Article\isRedirect
isRedirect()
Definition: Article.php:2770
Article\getCategories
getCategories()
Definition: Article.php:2540
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
Article\doOutputMetaData
doOutputMetaData(?ParserOutput $pOutput, OutputPage $outputPage)
Definition: Article.php:785
OutputPage\addSubtitle
addSubtitle( $str)
Add $str to the subtitle.
Definition: OutputPage.php:1074
ParserOutput
Definition: ParserOutput.php:31
Article\formatRobotPolicy
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1040
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:71
Article\$viewIsRenderAction
bool $viewIsRenderAction
Whether render() was called.
Definition: Article.php:91
Article\getRedirectTarget
getRedirectTarget()
Definition: Article.php:2650
Article\view
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition: Article.php:494
Xml\listDropDownOptionsOoui
static listDropDownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
Definition: Xml.php:590
Article\getContentModel
getContentModel()
Definition: Article.php:2560
PageArchive
Used to show archived pages and eventually restore them.
Definition: PageArchive.php:32
Article\getLinksTimestamp
getLinksTimestamp()
Definition: Article.php:2621
Article\getOldIDFromRequest
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition: Article.php:322
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:166
Article\showPatrolFooter
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:1179
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:221
Article\tryFileCache
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition: Article.php:2247
Article\getContentHandler
getContentHandler()
Definition: Article.php:2550
Article\clearPreparedEdit
clearPreparedEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2435
Article\$revisionStore
RevisionStore $revisionStore
Definition: Article.php:106
HTMLFileCache
Page view caching in the file system.
Definition: HTMLFileCache.php:33
Page\ParserOutputAccess
Service for getting rendered output of a given page.
Definition: ParserOutputAccess.php:49
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:80
Article\lockAndGetLatest
lockAndGetLatest()
Definition: Article.php:2801
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:41
getUser
getUser()
Article\supportsSections
supportsSections()
Definition: Article.php:2956
Article\getOldestRevision
getOldestRevision()
Definition: Article.php:2640
Article\getDeletionUpdates
getDeletionUpdates(Content $content=null)
Definition: Article.php:2581
Article\checkTouched
checkTouched()
Definition: Article.php:2425
Article\getRevision
getRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2672
CategoryPage
Special handling for category description pages, showing pages, subcategories and file that belong to...
Definition: CategoryPage.php:30
Revision\RevisionRecord\getTimestamp
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
Definition: RevisionRecord.php:434
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:55
Linker\revComment
static revComment( $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition: Linker.php:1604
Linker\getRevDeleteLink
static getRevDeleteLink(User $user, $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2201
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1134
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:548
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1230
Article\showDiffPage
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:897
$wgArticleRobotPolicies
$wgArticleRobotPolicies
Robot policies per article.
Definition: DefaultSettings.php:8494
ImagePage
Class for viewing MediaWiki file description pages.
Definition: ImagePage.php:33
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:31
Article\confirmDelete
confirmDelete( $reason)
Output deletion confirmation dialog.
Definition: Article.php:1991
Article\updateCategoryCounts
updateCategoryCounts(array $added, array $deleted, $id=0)
Definition: Article.php:2978
Html\warningBox
static warningBox( $html, $className='')
Return a warning box.
Definition: Html.php:729
Article\doOutputFromRenderStatus
doOutputFromRenderStatus(?RevisionRecord $rev, Status $renderStatus, OutputPage $outputPage, array $textOptions)
Definition: Article.php:827
Article\adjustDisplayTitle
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition: Article.php:882
Article\showNamespaceHeader
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:1143
Article\shouldCheckParserCache
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Definition: Article.php:2946
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:7344
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:110
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:7360
CacheTime\getCacheRevisionId
getCacheRevisionId()
Definition: CacheTime.php:101
Article\$linkRenderer
LinkRenderer $linkRenderer
Definition: Article.php:96
ProtectionForm
Handles the page protection UI and backend.
Definition: ProtectionForm.php:33
OutputPage\addHTML
addHTML( $text)
Append $text to the body HTML.
Definition: OutputPage.php:1617
Article\doDelete
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition: Article.php:2188
Article\protectDescriptionLog
protectDescriptionLog(array $limit, array $expiry)
Definition: Article.php:2886
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
Article\getRevisionRedirectTarget
getRevisionRedirectTarget(RevisionRecord $revision)
Definition: Article.php:870
OutputPage\setLastModified
setLastModified( $timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:857
Article\$mRedirectedFrom
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition: Article.php:68
$dbr
$dbr
Definition: testCompression.php:54
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
Article\getSubstituteContent
getSubstituteContent()
Returns Content object to use when the page does not exist.
Definition: Article.php:284
Revision
Definition: Revision.php:40
StatusValue\getValue
getValue()
Definition: StatusValue.php:138
Article\unprotect
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1852
RC_LOG
const RC_LOG
Definition: Defines.php:127
Article\insertRedirect
insertRedirect()
Definition: Article.php:2737
Status\getWikiText
getWikiText( $shortContext=false, $longContext=false, $lang=null)
Get the error list as a wikitext formatted list.
Definition: Status.php:189
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:50
Article\getRedirectedFrom
getRedirectedFrom()
Get the page this view was redirected from.
Definition: Article.php:201
Article\clear
clear()
Definition: Article.php:233
Article\updateRedirectOn
updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect=null)
Definition: Article.php:3004
Article\newFromWikiPage
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:190
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1065
Article\getTouched
getTouched()
Definition: Article.php:2692
Article\getTitle
getTitle()
Get the title object of the article.
Definition: Article.php:219
Article\updateRevisionOn
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Definition: Article.php:3018
Article\render
render()
Handle action=render.
Definition: Article.php:1833
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1033
Article\$permManager
PermissionManager $permManager
Definition: Article.php:101
Article\$mParserOutput
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition: Article.php:84
Article\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1816
Article\exists
exists()
Definition: Article.php:2500
ParserOutput\getTimestamp
getTimestamp()
Definition: ParserOutput.php:681
Article\setRedirectedFrom
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
Definition: Article.php:210
$wgUseFileCache
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites.
Definition: DefaultSettings.php:2795
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2466
Article\__construct
__construct(Title $title, $oldId=null)
Definition: Article.php:123
StatusValue\isOK
isOK()
Returns whether the operation completed.
Definition: StatusValue.php:131
Article\showDeletedRevisionHeader
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1534
Article\isFileCacheable
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition: Article.php:2278
Article\doRollback
doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user=null)
Definition: Article.php:3074
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:285
OutputPage\setRevisionId
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
Definition: OutputPage.php:1682
LogPage
Class to simplify the use of log pages.
Definition: LogPage.php:37
Article\setOldSubtitle
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition: Article.php:1596
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
ObjectCache\getInstance
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:78
Article\getPage
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:229
Article\getContext
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2340
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:958
Article\pageDataFromId
pageDataFromId( $dbr, $id, $options=[])
Definition: Article.php:2825
$title
$title
Definition: testCompression.php:38
Article\getRobotPolicy
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:960
Article\insertOn
insertOn( $dbw, $pageId=null)
Definition: Article.php:2727
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:845
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:624
Article\getRedirectURL
getRedirectURL( $rt)
Definition: Article.php:2661
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:618
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
Article\newFromID
static newFromID( $id)
Constructor from a page id.
Definition: Article.php:146
Article\newPage
newPage(Title $title)
Definition: Article.php:137
Article\getUndoContent
getUndoContent(Revision $undo, Revision $undoafter=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2705
Article\prepareContentForEdit
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2856
$wgDeleteRevisionsLimit
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
Definition: DefaultSettings.php:5968
Linker\revUserTools
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1141
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:7371
Article\getLatest
getLatest()
Definition: Article.php:2611
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:914
OutputPage
This is one of the Core classes and should be read at least once by any new developers.
Definition: OutputPage.php:47
Article\isCurrent
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
Definition: Article.php:447
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:142
Article\getAutoDeleteReason
getAutoDeleteReason(&$hasHistory)
Definition: Article.php:2530
Article\isCountable
isCountable( $editInfo=false)
Definition: Article.php:2760
Revision\RevisionRecord\getId
getId()
Get revision ID.
Definition: RevisionRecord.php:271
OutputPage\wrapWikiMsg
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
Definition: OutputPage.php:4100
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:805
ParserOutput\getTitleText
getTitleText()
Definition: ParserOutput.php:594
Article\doPurge
doPurge()
Definition: Article.php:2479
OutputPage\setIndexPolicy
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
Definition: OutputPage.php:897
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:51
$content
$content
Definition: router.php:76
OutputPage\setRevisionTimestamp
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
Definition: OutputPage.php:1713
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:51
Article\hasViewableContent
hasViewableContent()
Definition: Article.php:2715
Article\$mOldId
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition: Article.php:65
Article\getContributors
getContributors()
Definition: Article.php:2570
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
HTMLFileCache\MODE_NORMAL
const MODE_NORMAL
Definition: HTMLFileCache.php:34
Article\loadFromRow
loadFromRow( $data, $from)
Definition: Article.php:2781
Article\protect
protect()
action=protect handler
Definition: Article.php:1844
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
Article\getTimestamp
getTimestamp()
Definition: Article.php:2682
Article\doOutputFromParserCache
doOutputFromParserCache(ParserOutput $pOutput, OutputPage $outputPage, array $textOptions)
Definition: Article.php:804
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1504
Article\getMinorEdit
getMinorEdit()
Definition: Article.php:2631
Article\commitRollback
commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser=null)
Definition: Article.php:3100
$wgNamespaceRobotPolicies
$wgNamespaceRobotPolicies
Robot policies per namespaces.
Definition: DefaultSettings.php:8466
Article\getParserOutput
getParserOutput( $oldid=null, User $user=null)
#-
Definition: Article.php:2306
Article\getContentObject
getContentObject()
Returns a Content object representing the pages effective display content, not necessarily the revisi...
Definition: Article.php:261
NS_USER
const NS_USER
Definition: Defines.php:65
Article\followRedirect
followRedirect()
Definition: Article.php:2510
Article\getHiddenCategories
getHiddenCategories()
Definition: Article.php:2591
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:454
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user, string $expiry=null)
Watch or unwatch a page.
Definition: WatchAction.php:241
Article\setTimestamp
setTimestamp( $ts)
Definition: Article.php:2934
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
Article\__get
__get( $fname)
Definition: Article.php:2359
OutputPage\setFollowPolicy
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
Definition: OutputPage.php:920
Content
Base interface for content objects.
Definition: Content.php:35
CommentStore\COMMENT_CHARACTER_LIMIT
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
Definition: CommentStore.php:48
Article\replaceSectionAtRev
replaceSectionAtRev( $sectionId, Content $sectionContent, $sectionTitle='', $baseRevId=null)
Definition: Article.php:2900
Article\fetchRevisionRecord
fetchRevisionRecord()
Fetches the revision to work on.
Definition: Article.php:391
Title
Represents a title within MediaWiki.
Definition: Title.php:46
OutputPage\setCdnMaxage
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
Definition: OutputPage.php:2128
$cache
$cache
Definition: mcc.php:33
Article\replaceSectionContent
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2920
MediaWiki\Linker\LinkRenderer\makeKnownLink
makeKnownLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:225
Article\getRevIdFetched
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition: Article.php:482
MessageContent
Wrapper allowing us to handle a system message as a Content object.
Definition: MessageContent.php:36
Article\makeParserOptions
makeParserOptions( $context)
Definition: Article.php:2812
Article\showRedirectedFromHeader
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:1069
RecentChange\isInRCLifespan
static isInRCLifespan( $timestamp, $tolerance=0)
Check whether the given timestamp is new enough to have a RC row with a given tolerance as the recent...
Definition: RecentChange.php:1163
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:77
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:66
Revision\RevisionRecord\getContent
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
Definition: RevisionRecord.php:159
Article\getId
getId()
Definition: Article.php:2601
Article\getActionOverrides
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2520
StatusValue\hasMessage
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
Definition: StatusValue.php:258
Article\$mRedirectUrl
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:71
Article\doUpdateRestrictions
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user)
Definition: Article.php:3036
Article\updateRestrictions
updateRestrictions( $limit=[], $reason='', &$cascade=0, $expiry=[])
Definition: Article.php:3051
Article\protectDescription
protectDescription(array $limit, array $expiry)
Definition: Article.php:2874
Article\getParserOptions
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition: Article.php:2320
MediaWiki\Edit\PreparedEdit
Represents information returned by WikiPage::prepareContentForEdit()
Definition: PreparedEdit.php:35
$wgRedirectSources
$wgRedirectSources
If local interwikis are set up which allow redirects, set this regexp to restrict URLs which will be ...
Definition: DefaultSettings.php:4421
Article\updateIfNewerOn
updateIfNewerOn( $dbw, $revision)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2991
$t
$t
Definition: testCompression.php:74
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:47
Article\doDeleteUpdates
doDeleteUpdates( $id, Content $content=null, $revision=null, User $user=null)
Definition: Article.php:2448
Article\pageDataFromTitle
pageDataFromTitle( $dbr, $title, $options=[])
Definition: Article.php:2838
Article\generateContentOutput
generateContentOutput(User $user, ParserOptions $parserOptions, int $oldid, OutputPage $outputPage, array $textOptions)
Determines the desired ParserOutput and passes it to $outputPage.
Definition: Article.php:624
NS_FILE
const NS_FILE
Definition: Defines.php:69
Article\triggerOpportunisticLinksUpdate
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Definition: Article.php:2966
Article\getOldID
getOldID()
Definition: Article.php:309
EditPage\POST_EDIT_COOKIE_KEY_PREFIX
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:97
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
Article\setContext
setContext( $context)
Sets the context this Article is executed in.
Definition: Article.php:2330
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:505
Article\insertRedirectEntry
insertRedirectEntry(Title $rt, $oldLatest=null)
Definition: Article.php:2749
OutputPage\addParserOutput
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
Definition: OutputPage.php:2017
MediaWiki\Linker\LinkRenderer\makeLink
makeLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:160
$wgDefaultRobotPolicy
$wgDefaultRobotPolicy
Default robot policy.
Definition: DefaultSettings.php:8450
Article\getRevisionFetched
getRevisionFetched()
Get the fetched Revision object depending on request parameters or null on failure.
Definition: Article.php:467
Article\viewRedirect
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1747
Article\loadPageData
loadPageData( $from='fromdb')
Definition: Article.php:2791
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:42
Article\doEditUpdates
doEditUpdates(Revision $revision, User $user, array $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2467
Article\showViewError
showViewError(string $errortext)
Show error text for errors generated in Article::view().
Definition: Article.php:1519
Article\$mRevisionRecord
RevisionRecord null $mRevisionRecord
Revision to be shown.
Definition: Article.php:117
Article\__set
__set( $fname, $fvalue)
Definition: Article.php:2383
Article\showViewFooter
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:1155
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
Article\$mPage
WikiPage $mPage
The WikiPage object of this instance.
Definition: Article.php:59
Article\getRedirectHeaderHtml
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1769
Xml\listDropDownOptions
static listDropDownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
Definition: Xml.php:545
Article\newFromTitle
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:158
Article\generateReason
generateReason(&$hasHistory)
Definition: Article.php:3115
Article\purgePatrolFooterCache
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition: Article.php:1348
Article\doViewUpdates
doViewUpdates(User $user, $oldid=0)
Definition: Article.php:2490
Article\$fetchResult
Status null $fetchResult
represents the outcome of fetchRevisionRecord().
Definition: Article.php:77