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