MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
27 
38 class Article implements Page {
43  protected $mContext;
44 
46  protected $mPage;
47 
53 
63 
70  public $mContentLoaded = false;
71 
77  public $mOldId;
78 
81 
83  public $mRedirectUrl = false;
84 
90  public $mRevIdFetched = 0;
91 
100  private $fetchResult = null;
101 
109  public $mRevision = null;
110 
117 
123  protected $viewIsRenderAction = false;
124 
130  public function __construct( Title $title, $oldId = null ) {
131  $this->mOldId = $oldId;
132  $this->mPage = $this->newPage( $title );
133  }
134 
139  protected function newPage( Title $title ) {
140  return new WikiPage( $title );
141  }
142 
148  public static function newFromID( $id ) {
149  $t = Title::newFromID( $id );
150  return $t == null ? null : new static( $t );
151  }
152 
160  public static function newFromTitle( $title, IContextSource $context ) {
161  if ( NS_MEDIA == $title->getNamespace() ) {
162  // XXX: This should not be here, but where should it go?
163  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
164  }
165 
166  $page = null;
167  Hooks::run( 'ArticleFromTitle', [ &$title, &$page, $context ] );
168  if ( !$page ) {
169  switch ( $title->getNamespace() ) {
170  case NS_FILE:
171  $page = new ImagePage( $title );
172  break;
173  case NS_CATEGORY:
174  $page = new CategoryPage( $title );
175  break;
176  default:
177  $page = new Article( $title );
178  }
179  }
180  $page->setContext( $context );
181 
182  return $page;
183  }
184 
192  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
193  $article = self::newFromTitle( $page->getTitle(), $context );
194  $article->mPage = $page; // override to keep process cached vars
195  return $article;
196  }
197 
203  public function getRedirectedFrom() {
204  return $this->mRedirectedFrom;
205  }
206 
212  public function setRedirectedFrom( Title $from ) {
213  $this->mRedirectedFrom = $from;
214  }
215 
221  public function getTitle() {
222  return $this->mPage->getTitle();
223  }
224 
231  public function getPage() {
232  return $this->mPage;
233  }
234 
238  public function clear() {
239  $this->mContentLoaded = false;
240 
241  $this->mRedirectedFrom = null; # Title object if set
242  $this->mRevIdFetched = 0;
243  $this->mRedirectUrl = false;
244  $this->mRevision = null;
245  $this->mContentObject = null;
246  $this->fetchResult = null;
247 
248  // TODO hard-deprecate direct access to public fields
249 
250  $this->mPage->clear();
251  }
252 
270  protected function getContentObject() {
271  if ( $this->mPage->getId() === 0 ) {
272  $content = $this->getSubstituteContent();
273  } else {
274  $this->fetchContentObject();
276  }
277 
278  return $content;
279  }
280 
286  private function getSubstituteContent() {
287  # If this is a MediaWiki:x message, then load the messages
288  # and return the message value for x.
289  if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
290  $text = $this->getTitle()->getDefaultMessageText();
291  if ( $text === false ) {
292  $text = '';
293  }
294 
295  $content = ContentHandler::makeContent( $text, $this->getTitle() );
296  } else {
297  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
298  $content = new MessageContent( $message, null );
299  }
300 
301  return $content;
302  }
303 
314  $content = $this->getSubstituteContent();
315 
316  return $content->getParserOutput( $this->getTitle(), 0, $options );
317  }
318 
326  public function getOldID() {
327  if ( is_null( $this->mOldId ) ) {
328  $this->mOldId = $this->getOldIDFromRequest();
329  }
330 
331  return $this->mOldId;
332  }
333 
339  public function getOldIDFromRequest() {
340  $this->mRedirectUrl = false;
341 
342  $request = $this->getContext()->getRequest();
343  $oldid = $request->getIntOrNull( 'oldid' );
344 
345  if ( $oldid === null ) {
346  return 0;
347  }
348 
349  if ( $oldid !== 0 ) {
350  # Load the given revision and check whether the page is another one.
351  # In that case, update this instance to reflect the change.
352  if ( $oldid === $this->mPage->getLatest() ) {
353  $this->mRevision = $this->mPage->getRevision();
354  } else {
355  $this->mRevision = Revision::newFromId( $oldid );
356  if ( $this->mRevision !== null ) {
357  // Revision title doesn't match the page title given?
358  if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
359  $function = get_class( $this->mPage ) . '::newFromID';
360  $this->mPage = $function( $this->mRevision->getPage() );
361  }
362  }
363  }
364  }
365 
366  if ( $request->getVal( 'direction' ) == 'next' ) {
367  $nextid = $this->getTitle()->getNextRevisionID( $oldid );
368  if ( $nextid ) {
369  $oldid = $nextid;
370  $this->mRevision = null;
371  } else {
372  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
373  }
374  } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
375  $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
376  if ( $previd ) {
377  $oldid = $previd;
378  $this->mRevision = null;
379  }
380  }
381 
382  $this->mRevIdFetched = $this->mRevision ? $this->mRevision->getId() : 0;
383 
384  return $oldid;
385  }
386 
400  protected function fetchContentObject() {
401  if ( !$this->mContentLoaded ) {
402  $this->fetchRevisionRecord();
403  }
404 
405  return $this->mContentObject;
406  }
407 
417  protected function fetchRevisionRecord() {
418  if ( $this->fetchResult ) {
419  return $this->mRevision ? $this->mRevision->getRevisionRecord() : null;
420  }
421 
422  $this->mContentLoaded = true;
423  $this->mContentObject = null;
424 
425  $oldid = $this->getOldID();
426 
427  // $this->mRevision might already be fetched by getOldIDFromRequest()
428  if ( !$this->mRevision ) {
429  if ( !$oldid ) {
430  $this->mRevision = $this->mPage->getRevision();
431 
432  if ( !$this->mRevision ) {
433  wfDebug( __METHOD__ . " failed to find page data for title " .
434  $this->getTitle()->getPrefixedText() . "\n" );
435 
436  // Just for sanity, output for this case is done by showMissingArticle().
437  $this->fetchResult = Status::newFatal( 'noarticletext' );
438  $this->applyContentOverride( $this->makeFetchErrorContent() );
439  return null;
440  }
441  } else {
442  $this->mRevision = Revision::newFromId( $oldid );
443 
444  if ( !$this->mRevision ) {
445  wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid\n" );
446 
447  $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
448  $this->applyContentOverride( $this->makeFetchErrorContent() );
449  return null;
450  }
451  }
452  }
453 
454  $this->mRevIdFetched = $this->mRevision->getId();
455  $this->fetchResult = Status::newGood( $this->mRevision );
456 
457  if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $this->getContext()->getUser() ) ) {
458  wfDebug( __METHOD__ . " failed to retrieve content of revision " .
459  $this->mRevision->getId() . "\n" );
460 
461  // Just for sanity, output for this case is done by showDeletedRevisionHeader().
462  $this->fetchResult = Status::newFatal( 'rev-deleted-text-permission' );
463  $this->applyContentOverride( $this->makeFetchErrorContent() );
464  return null;
465  }
466 
467  if ( Hooks::isRegistered( 'ArticleAfterFetchContentObject' ) ) {
468  $contentObject = $this->mRevision->getContent(
470  $this->getContext()->getUser()
471  );
472 
473  $hookContentObject = $contentObject;
474 
475  // Avoid PHP 7.1 warning of passing $this by reference
476  $articlePage = $this;
477 
478  Hooks::run(
479  'ArticleAfterFetchContentObject',
480  [ &$articlePage, &$hookContentObject ],
481  '1.32'
482  );
483 
484  if ( $hookContentObject !== $contentObject ) {
485  // A hook handler is trying to override the content
486  $this->applyContentOverride( $hookContentObject );
487  }
488  }
489 
490  // For B/C only
491  $this->mContentObject = $this->mRevision->getContent(
493  $this->getContext()->getUser()
494  );
495 
496  return $this->mRevision->getRevisionRecord();
497  }
498 
505  private function makeFetchErrorContent() {
506  if ( !$this->fetchResult || $this->fetchResult->isOK() ) {
507  return null;
508  }
509 
510  return new MessageContent( $this->fetchResult->getMessage() );
511  }
512 
525  private function applyContentOverride( Content $override ) {
526  // Construct a fake revision
527  $rev = new MutableRevisionRecord( $this->getTitle() );
528  $rev->setContent( SlotRecord::MAIN, $override );
529 
530  $this->mRevision = new Revision( $rev );
531 
532  // For B/C only
533  $this->mContentObject = $override;
534  }
535 
541  public function isCurrent() {
542  # If no oldid, this is the current version.
543  if ( $this->getOldID() == 0 ) {
544  return true;
545  }
546 
547  return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
548  }
549 
559  public function getRevisionFetched() {
560  $this->fetchRevisionRecord();
561 
562  if ( $this->fetchResult->isOK() ) {
563  return $this->mRevision;
564  }
565  }
566 
575  public function getRevIdFetched() {
576  if ( $this->fetchResult && $this->fetchResult->isOK() ) {
577  return $this->fetchResult->value->getId();
578  } else {
579  return $this->mPage->getLatest();
580  }
581  }
582 
587  public function view() {
589 
590  # Get variables from query string
591  # As side effect this will load the revision and update the title
592  # in a revision ID is passed in the request, so this should remain
593  # the first call of this method even if $oldid is used way below.
594  $oldid = $this->getOldID();
595 
596  $user = $this->getContext()->getUser();
597  # Another whitelist check in case getOldID() is altering the title
598  $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
599  if ( count( $permErrors ) ) {
600  wfDebug( __METHOD__ . ": denied on secondary read check\n" );
601  throw new PermissionsError( 'read', $permErrors );
602  }
603 
604  $outputPage = $this->getContext()->getOutput();
605  # getOldID() may as well want us to redirect somewhere else
606  if ( $this->mRedirectUrl ) {
607  $outputPage->redirect( $this->mRedirectUrl );
608  wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
609 
610  return;
611  }
612 
613  # If we got diff in the query, we want to see a diff page instead of the article.
614  if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
615  wfDebug( __METHOD__ . ": showing diff page\n" );
616  $this->showDiffPage();
617 
618  return;
619  }
620 
621  # Set page title (may be overridden by DISPLAYTITLE)
622  $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
623 
624  $outputPage->setArticleFlag( true );
625  # Allow frames by default
626  $outputPage->allowClickjacking();
627 
628  $parserCache = MediaWikiServices::getInstance()->getParserCache();
629 
630  $parserOptions = $this->getParserOptions();
631  $poOptions = [];
632  # Render printable version, use printable version cache
633  if ( $outputPage->isPrintable() ) {
634  $parserOptions->setIsPrintable( true );
635  $poOptions['enableSectionEditLinks'] = false;
636  } elseif ( $this->viewIsRenderAction
637  || !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user )
638  ) {
639  $poOptions['enableSectionEditLinks'] = false;
640  }
641 
642  # Try client and file cache
643  if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
644  # Try to stream the output from file cache
645  if ( $wgUseFileCache && $this->tryFileCache() ) {
646  wfDebug( __METHOD__ . ": done file cache\n" );
647  # tell wgOut that output is taken care of
648  $outputPage->disable();
649  $this->mPage->doViewUpdates( $user, $oldid );
650 
651  return;
652  }
653  }
654 
655  # Should the parser cache be used?
656  $useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
657  wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
658  if ( $user->getStubThreshold() ) {
659  MediaWikiServices::getInstance()->getStatsdDataFactory()->increment( 'pcache_miss_stub' );
660  }
661 
662  $this->showRedirectedFromHeader();
663  $this->showNamespaceHeader();
664 
665  # Iterate through the possible ways of constructing the output text.
666  # Keep going until $outputDone is set, or we run out of things to do.
667  $pass = 0;
668  $outputDone = false;
669  $this->mParserOutput = false;
670 
671  while ( !$outputDone && ++$pass ) {
672  switch ( $pass ) {
673  case 1:
674  // Avoid PHP 7.1 warning of passing $this by reference
675  $articlePage = $this;
676  Hooks::run( 'ArticleViewHeader', [ &$articlePage, &$outputDone, &$useParserCache ] );
677  break;
678  case 2:
679  # Early abort if the page doesn't exist
680  if ( !$this->mPage->exists() ) {
681  wfDebug( __METHOD__ . ": showing missing article\n" );
682  $this->showMissingArticle();
683  $this->mPage->doViewUpdates( $user );
684  return;
685  }
686 
687  # Try the parser cache
688  if ( $useParserCache ) {
689  $this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
690 
691  if ( $this->mParserOutput !== false ) {
692  if ( $oldid ) {
693  wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
694  $this->setOldSubtitle( $oldid );
695  } else {
696  wfDebug( __METHOD__ . ": showing parser cache contents\n" );
697  }
698  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
699  # Ensure that UI elements requiring revision ID have
700  # the correct version information.
701  $outputPage->setRevisionId( $this->mPage->getLatest() );
702  # Preload timestamp to avoid a DB hit
703  $cachedTimestamp = $this->mParserOutput->getTimestamp();
704  if ( $cachedTimestamp !== null ) {
705  $outputPage->setRevisionTimestamp( $cachedTimestamp );
706  $this->mPage->setTimestamp( $cachedTimestamp );
707  }
708  $outputDone = true;
709  }
710  }
711  break;
712  case 3:
713  # Are we looking at an old revision
714  $rev = $this->fetchRevisionRecord();
715  if ( $oldid && $this->fetchResult->isOK() ) {
716  $this->setOldSubtitle( $oldid );
717 
718  if ( !$this->showDeletedRevisionHeader() ) {
719  wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
720  return;
721  }
722  }
723 
724  # Ensure that UI elements requiring revision ID have
725  # the correct version information.
726  $outputPage->setRevisionId( $this->getRevIdFetched() );
727  # Preload timestamp to avoid a DB hit
728  $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
729 
730  # Pages containing custom CSS or JavaScript get special treatment
731  if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
732  $dir = $this->getContext()->getLanguage()->getDir();
733  $lang = $this->getContext()->getLanguage()->getHtmlCode();
734 
735  $outputPage->wrapWikiMsg(
736  "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
737  'clearyourcache'
738  );
739  } elseif ( !Hooks::run( 'ArticleRevisionViewCustom', [
740  $rev,
741  $this->getTitle(),
742  $oldid,
743  $outputPage,
744  ] )
745  ) {
746  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
747  // Allow extensions do their own custom view for certain pages
748  $outputDone = true;
749  } elseif ( !Hooks::run( 'ArticleContentViewCustom',
750  [ $this->fetchContentObject(), $this->getTitle(), $outputPage ], '1.32' )
751  ) {
752  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
753  // Allow extensions do their own custom view for certain pages
754  $outputDone = true;
755  }
756  break;
757  case 4:
758  # Run the parse, protected by a pool counter
759  wfDebug( __METHOD__ . ": doing uncached parse\n" );
760 
761  $rev = $this->fetchRevisionRecord();
762  $error = null;
763 
764  if ( $rev ) {
765  $poolArticleView = new PoolWorkArticleView(
766  $this->getPage(),
767  $parserOptions,
768  $this->getRevIdFetched(),
769  $useParserCache,
770  $rev,
771  // permission checking was done earlier via showDeletedRevisionHeader()
772  RevisionRecord::RAW
773  );
774  $ok = $poolArticleView->execute();
775  $error = $poolArticleView->getError();
776  $this->mParserOutput = $poolArticleView->getParserOutput() ?: null;
777 
778  # Don't cache a dirty ParserOutput object
779  if ( $poolArticleView->getIsDirty() ) {
780  $outputPage->setCdnMaxage( 0 );
781  $outputPage->addHTML( "<!-- parser cache is expired, " .
782  "sending anyway due to pool overload-->\n" );
783  }
784  } else {
785  $ok = false;
786  }
787 
788  if ( !$ok ) {
789  if ( $error ) {
790  $outputPage->clearHTML(); // for release() errors
791  $outputPage->enableClientCache( false );
792  $outputPage->setRobotPolicy( 'noindex,nofollow' );
793 
794  $errortext = $error->getWikiText( false, 'view-pool-error' );
795  $outputPage->wrapWikiTextAsInterface( 'errorbox', $errortext );
796  }
797  # Connection or timeout error
798  return;
799  }
800 
801  if ( $this->mParserOutput ) {
802  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
803  }
804 
805  if ( $rev && $this->getRevisionRedirectTarget( $rev ) ) {
806  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
807  $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
808  }
809 
810  $outputDone = true;
811  break;
812  # Should be unreachable, but just in case...
813  default:
814  break 2;
815  }
816  }
817 
818  // Get the ParserOutput actually *displayed* here.
819  // Note that $this->mParserOutput is the *current*/oldid version output.
820  // Note that the ArticleViewHeader hook is allowed to set $outputDone to a
821  // ParserOutput instance.
822  $pOutput = ( $outputDone instanceof ParserOutput )
823  ? $outputDone // object fetched by hook
824  : ( $this->mParserOutput ?: null ); // ParserOutput or null, avoid false
825 
826  # Adjust title for main page & pages with displaytitle
827  if ( $pOutput ) {
828  $this->adjustDisplayTitle( $pOutput );
829  }
830 
831  # For the main page, overwrite the <title> element with the con-
832  # tents of 'pagetitle-view-mainpage' instead of the default (if
833  # that's not empty).
834  # This message always exists because it is in the i18n files
835  if ( $this->getTitle()->isMainPage() ) {
836  $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
837  if ( !$msg->isDisabled() ) {
838  $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
839  }
840  }
841 
842  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
843  # This could use getTouched(), but that could be scary for major template edits.
844  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
845 
846  # Check for any __NOINDEX__ tags on the page using $pOutput
847  $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
848  $outputPage->setIndexPolicy( $policy['index'] );
849  $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
850 
851  $this->showViewFooter();
852  $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this
853 
854  # Load the postEdit module if the user just saved this revision
855  # See also EditPage::setPostEditCookie
856  $request = $this->getContext()->getRequest();
858  $postEdit = $request->getCookie( $cookieKey );
859  if ( $postEdit ) {
860  # Clear the cookie. This also prevents caching of the response.
861  $request->response()->clearCookie( $cookieKey );
862  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
863  $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
864  }
865  }
866 
871  private function getRevisionRedirectTarget( RevisionRecord $revision ) {
872  // TODO: find a *good* place for the code that determines the redirect target for
873  // a given revision!
874  // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
875  $content = $revision->getContent( SlotRecord::MAIN );
876  return $content ? $content->getRedirectTarget() : null;
877  }
878 
883  public function adjustDisplayTitle( ParserOutput $pOutput ) {
884  $out = $this->getContext()->getOutput();
885 
886  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
887  $titleText = $pOutput->getTitleText();
888  if ( strval( $titleText ) !== '' ) {
889  $out->setPageTitle( $titleText );
890  $out->setDisplayTitle( $titleText );
891  }
892  }
893 
898  protected function showDiffPage() {
899  $request = $this->getContext()->getRequest();
900  $user = $this->getContext()->getUser();
901  $diff = $request->getVal( 'diff' );
902  $rcid = $request->getVal( 'rcid' );
903  $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
904  $purge = $request->getVal( 'action' ) == 'purge';
905  $unhide = $request->getInt( 'unhide' ) == 1;
906  $oldid = $this->getOldID();
907 
908  $rev = $this->getRevisionFetched();
909 
910  if ( !$rev ) {
911  $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
912  $msg = $this->getContext()->msg( 'difference-missing-revision' )
913  ->params( $oldid )
914  ->numParams( 1 )
915  ->parseAsBlock();
916  $this->getContext()->getOutput()->addHTML( $msg );
917  return;
918  }
919 
920  $contentHandler = $rev->getContentHandler();
921  $de = $contentHandler->createDifferenceEngine(
922  $this->getContext(),
923  $oldid,
924  $diff,
925  $rcid,
926  $purge,
927  $unhide
928  );
929 
930  // DifferenceEngine directly fetched the revision:
931  $this->mRevIdFetched = $de->getNewid();
932  $de->showDiffPage( $diffOnly );
933 
934  // Run view updates for the newer revision being diffed (and shown
935  // below the diff if not $diffOnly).
936  list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
937  // New can be false, convert it to 0 - this conveniently means the latest revision
938  $this->mPage->doViewUpdates( $user, (int)$new );
939  }
940 
948  public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
950 
951  $ns = $this->getTitle()->getNamespace();
952 
953  # Don't index user and user talk pages for blocked users (T13443)
954  if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
955  $specificTarget = null;
956  $vagueTarget = null;
957  $titleText = $this->getTitle()->getText();
958  if ( IP::isValid( $titleText ) ) {
959  $vagueTarget = $titleText;
960  } else {
961  $specificTarget = $titleText;
962  }
963  if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
964  return [
965  'index' => 'noindex',
966  'follow' => 'nofollow'
967  ];
968  }
969  }
970 
971  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
972  # Non-articles (special pages etc), and old revisions
973  return [
974  'index' => 'noindex',
975  'follow' => 'nofollow'
976  ];
977  } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
978  # Discourage indexing of printable versions, but encourage following
979  return [
980  'index' => 'noindex',
981  'follow' => 'follow'
982  ];
983  } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
984  # For ?curid=x urls, disallow indexing
985  return [
986  'index' => 'noindex',
987  'follow' => 'follow'
988  ];
989  }
990 
991  # Otherwise, construct the policy based on the various config variables.
992  $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
993 
994  if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
995  # Honour customised robot policies for this namespace
996  $policy = array_merge(
997  $policy,
998  self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
999  );
1000  }
1001  if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
1002  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1003  # a final sanity check that we have really got the parser output.
1004  $policy = array_merge(
1005  $policy,
1006  [ 'index' => $pOutput->getIndexPolicy() ]
1007  );
1008  }
1009 
1010  if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
1011  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1012  $policy = array_merge(
1013  $policy,
1014  self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
1015  );
1016  }
1017 
1018  return $policy;
1019  }
1020 
1028  public static function formatRobotPolicy( $policy ) {
1029  if ( is_array( $policy ) ) {
1030  return $policy;
1031  } elseif ( !$policy ) {
1032  return [];
1033  }
1034 
1035  $policy = explode( ',', $policy );
1036  $policy = array_map( 'trim', $policy );
1037 
1038  $arr = [];
1039  foreach ( $policy as $var ) {
1040  if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
1041  $arr['index'] = $var;
1042  } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
1043  $arr['follow'] = $var;
1044  }
1045  }
1046 
1047  return $arr;
1048  }
1049 
1057  public function showRedirectedFromHeader() {
1058  global $wgRedirectSources;
1059 
1060  $context = $this->getContext();
1061  $outputPage = $context->getOutput();
1062  $request = $context->getRequest();
1063  $rdfrom = $request->getVal( 'rdfrom' );
1064 
1065  // Construct a URL for the current page view, but with the target title
1066  $query = $request->getValues();
1067  unset( $query['rdfrom'] );
1068  unset( $query['title'] );
1069  if ( $this->getTitle()->isRedirect() ) {
1070  // Prevent double redirects
1071  $query['redirect'] = 'no';
1072  }
1073  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1074 
1075  if ( isset( $this->mRedirectedFrom ) ) {
1076  // Avoid PHP 7.1 warning of passing $this by reference
1077  $articlePage = $this;
1078 
1079  // This is an internally redirected page view.
1080  // We'll need a backlink to the source page for navigation.
1081  if ( Hooks::run( 'ArticleViewRedirect', [ &$articlePage ] ) ) {
1082  $redir = Linker::linkKnown(
1083  $this->mRedirectedFrom,
1084  null,
1085  [],
1086  [ 'redirect' => 'no' ]
1087  );
1088 
1089  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1090  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1091  . "</span>" );
1092 
1093  // Add the script to update the displayed URL and
1094  // set the fragment if one was specified in the redirect
1095  $outputPage->addJsConfigVars( [
1096  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1097  ] );
1098  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1099 
1100  // Add a <link rel="canonical"> tag
1101  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1102 
1103  // Tell the output object that the user arrived at this article through a redirect
1104  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1105 
1106  return true;
1107  }
1108  } elseif ( $rdfrom ) {
1109  // This is an externally redirected view, from some other wiki.
1110  // If it was reported from a trusted site, supply a backlink.
1111  if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
1112  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1113  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1114  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1115  . "</span>" );
1116 
1117  // Add the script to update the displayed URL
1118  $outputPage->addJsConfigVars( [
1119  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1120  ] );
1121  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1122 
1123  return true;
1124  }
1125  }
1126 
1127  return false;
1128  }
1129 
1134  public function showNamespaceHeader() {
1135  if ( $this->getTitle()->isTalkPage() && !wfMessage( 'talkpageheader' )->isDisabled() ) {
1136  $this->getContext()->getOutput()->wrapWikiMsg(
1137  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1138  [ 'talkpageheader' ]
1139  );
1140  }
1141  }
1142 
1146  public function showViewFooter() {
1147  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1148  if ( $this->getTitle()->getNamespace() == NS_USER_TALK
1149  && IP::isValid( $this->getTitle()->getText() )
1150  ) {
1151  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1152  }
1153 
1154  // Show a footer allowing the user to patrol the shown revision or page if possible
1155  $patrolFooterShown = $this->showPatrolFooter();
1156 
1157  Hooks::run( 'ArticleViewFooter', [ $this, $patrolFooterShown ] );
1158  }
1159 
1169  public function showPatrolFooter() {
1171 
1172  // Allow hooks to decide whether to not output this at all
1173  if ( !Hooks::run( 'ArticleShowPatrolFooter', [ $this ] ) ) {
1174  return false;
1175  }
1176 
1177  $outputPage = $this->getContext()->getOutput();
1178  $user = $this->getContext()->getUser();
1179  $title = $this->getTitle();
1180  $rc = false;
1181 
1182  if ( !$title->quickUserCan( 'patrol', $user )
1183  || !( $wgUseRCPatrol || $wgUseNPPatrol
1184  || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1185  ) {
1186  // Patrolling is disabled or the user isn't allowed to
1187  return false;
1188  }
1189 
1190  if ( $this->mRevision
1191  && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
1192  ) {
1193  // The current revision is already older than what could be in the RC table
1194  // 6h tolerance because the RC might not be cleaned out regularly
1195  return false;
1196  }
1197 
1198  // Check for cached results
1199  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1200  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1201  if ( $cache->get( $key ) ) {
1202  return false;
1203  }
1204 
1205  $dbr = wfGetDB( DB_REPLICA );
1206  $oldestRevisionTimestamp = $dbr->selectField(
1207  'revision',
1208  'MIN( rev_timestamp )',
1209  [ 'rev_page' => $title->getArticleID() ],
1210  __METHOD__
1211  );
1212 
1213  // New page patrol: Get the timestamp of the oldest revison which
1214  // the revision table holds for the given page. Then we look
1215  // whether it's within the RC lifespan and if it is, we try
1216  // to get the recentchanges row belonging to that entry
1217  // (with rc_new = 1).
1218  $recentPageCreation = false;
1219  if ( $oldestRevisionTimestamp
1220  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1221  ) {
1222  // 6h tolerance because the RC might not be cleaned out regularly
1223  $recentPageCreation = true;
1225  [
1226  'rc_new' => 1,
1227  'rc_timestamp' => $oldestRevisionTimestamp,
1228  'rc_namespace' => $title->getNamespace(),
1229  'rc_cur_id' => $title->getArticleID()
1230  ],
1231  __METHOD__
1232  );
1233  if ( $rc ) {
1234  // Use generic patrol message for new pages
1235  $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1236  }
1237  }
1238 
1239  // File patrol: Get the timestamp of the latest upload for this page,
1240  // check whether it is within the RC lifespan and if it is, we try
1241  // to get the recentchanges row belonging to that entry
1242  // (with rc_type = RC_LOG, rc_log_type = upload).
1243  $recentFileUpload = false;
1244  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1245  && $title->getNamespace() === NS_FILE ) {
1246  // Retrieve timestamp of most recent upload
1247  $newestUploadTimestamp = $dbr->selectField(
1248  'image',
1249  'MAX( img_timestamp )',
1250  [ 'img_name' => $title->getDBkey() ],
1251  __METHOD__
1252  );
1253  if ( $newestUploadTimestamp
1254  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1255  ) {
1256  // 6h tolerance because the RC might not be cleaned out regularly
1257  $recentFileUpload = true;
1259  [
1260  'rc_type' => RC_LOG,
1261  'rc_log_type' => 'upload',
1262  'rc_timestamp' => $newestUploadTimestamp,
1263  'rc_namespace' => NS_FILE,
1264  'rc_cur_id' => $title->getArticleID()
1265  ],
1266  __METHOD__
1267  );
1268  if ( $rc ) {
1269  // Use patrol message specific to files
1270  $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1271  }
1272  }
1273  }
1274 
1275  if ( !$recentPageCreation && !$recentFileUpload ) {
1276  // Page creation and latest upload (for files) is too old to be in RC
1277 
1278  // We definitely can't patrol so cache the information
1279  // When a new file version is uploaded, the cache is cleared
1280  $cache->set( $key, '1' );
1281 
1282  return false;
1283  }
1284 
1285  if ( !$rc ) {
1286  // Don't cache: This can be hit if the page gets accessed very fast after
1287  // its creation / latest upload or in case we have high replica DB lag. In case
1288  // the revision is too old, we will already return above.
1289  return false;
1290  }
1291 
1292  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1293  // Patrolled RC entry around
1294 
1295  // Cache the information we gathered above in case we can't patrol
1296  // Don't cache in case we can patrol as this could change
1297  $cache->set( $key, '1' );
1298 
1299  return false;
1300  }
1301 
1302  if ( $rc->getPerformer()->equals( $user ) ) {
1303  // Don't show a patrol link for own creations/uploads. If the user could
1304  // patrol them, they already would be patrolled
1305  return false;
1306  }
1307 
1308  $outputPage->preventClickjacking();
1309  if ( $user->isAllowed( 'writeapi' ) ) {
1310  $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
1311  }
1312 
1314  $title,
1315  $markPatrolledMsg->escaped(),
1316  [],
1317  [
1318  'action' => 'markpatrolled',
1319  'rcid' => $rc->getAttribute( 'rc_id' ),
1320  ]
1321  );
1322 
1323  $outputPage->addHTML(
1324  "<div class='patrollink' data-mw='interface'>" .
1325  wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1326  '</div>'
1327  );
1328 
1329  return true;
1330  }
1331 
1338  public static function purgePatrolFooterCache( $articleID ) {
1339  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1340  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1341  }
1342 
1347  public function showMissingArticle() {
1348  global $wgSend404Code;
1349 
1350  $outputPage = $this->getContext()->getOutput();
1351  // Whether the page is a root user page of an existing user (but not a subpage)
1352  $validUserPage = false;
1353 
1354  $title = $this->getTitle();
1355 
1356  $services = MediaWikiServices::getInstance();
1357 
1358  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1359  if ( $title->getNamespace() == NS_USER
1360  || $title->getNamespace() == NS_USER_TALK
1361  ) {
1362  $rootPart = explode( '/', $title->getText() )[0];
1363  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1364  $ip = User::isIP( $rootPart );
1365  $block = DatabaseBlock::newFromTarget( $user, $user );
1366 
1367  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1368  $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1369  [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1370  } elseif (
1371  !is_null( $block ) &&
1372  $block->getType() != DatabaseBlock::TYPE_AUTO &&
1373  ( $block->isSitewide() || $user->isBlockedFrom( $title ) )
1374  ) {
1375  // Show log extract if the user is sitewide blocked or is partially
1376  // blocked and not allowed to edit their user page or user talk page
1378  $outputPage,
1379  'block',
1380  $services->getNamespaceInfo()->getCanonicalName( NS_USER ) . ':' .
1381  $block->getTarget(),
1382  '',
1383  [
1384  'lim' => 1,
1385  'showIfEmpty' => false,
1386  'msgKey' => [
1387  'blocked-notice-logextract',
1388  $user->getName() # Support GENDER in notice
1389  ]
1390  ]
1391  );
1392  $validUserPage = !$title->isSubpage();
1393  } else {
1394  $validUserPage = !$title->isSubpage();
1395  }
1396  }
1397 
1398  Hooks::run( 'ShowMissingArticle', [ $this ] );
1399 
1400  # Show delete and move logs if there were any such events.
1401  # The logging query can DOS the site when bots/crawlers cause 404 floods,
1402  # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1403  $cache = $services->getMainObjectStash();
1404  $key = $cache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1405  $loggedIn = $this->getContext()->getUser()->isLoggedIn();
1406  $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
1407  if ( $loggedIn || $cache->get( $key ) || $sessionExists ) {
1408  $logTypes = [ 'delete', 'move', 'protect' ];
1409 
1410  $dbr = wfGetDB( DB_REPLICA );
1411 
1412  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1413  // Give extensions a chance to hide their (unrelated) log entries
1414  Hooks::run( 'Article::MissingArticleConditions', [ &$conds, $logTypes ] );
1416  $outputPage,
1417  $logTypes,
1418  $title,
1419  '',
1420  [
1421  'lim' => 10,
1422  'conds' => $conds,
1423  'showIfEmpty' => false,
1424  'msgKey' => [ $loggedIn || $sessionExists
1425  ? 'moveddeleted-notice'
1426  : 'moveddeleted-notice-recent'
1427  ]
1428  ]
1429  );
1430  }
1431 
1432  if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1433  // If there's no backing content, send a 404 Not Found
1434  // for better machine handling of broken links.
1435  $this->getContext()->getRequest()->response()->statusHeader( 404 );
1436  }
1437 
1438  // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1439  $policy = $this->getRobotPolicy( 'view' );
1440  $outputPage->setIndexPolicy( $policy['index'] );
1441  $outputPage->setFollowPolicy( $policy['follow'] );
1442 
1443  $hookResult = Hooks::run( 'BeforeDisplayNoArticleText', [ $this ] );
1444 
1445  if ( !$hookResult ) {
1446  return;
1447  }
1448 
1449  # Show error message
1450  $oldid = $this->getOldID();
1451  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1452  // use fake Content object for system message
1453  $parserOptions = ParserOptions::newCanonical( 'canonical' );
1454  $outputPage->addParserOutput( $this->getEmptyPageParserOutput( $parserOptions ) );
1455  } else {
1456  if ( $oldid ) {
1457  $text = wfMessage( 'missing-revision', $oldid )->plain();
1458  } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
1459  && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
1460  ) {
1461  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1462  $text = wfMessage( $message )->plain();
1463  } else {
1464  $text = wfMessage( 'noarticletext-nopermission' )->plain();
1465  }
1466 
1467  $dir = $this->getContext()->getLanguage()->getDir();
1468  $lang = $this->getContext()->getLanguage()->getHtmlCode();
1469  $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1470  'class' => "noarticletext mw-content-$dir",
1471  'dir' => $dir,
1472  'lang' => $lang,
1473  ] ) . "\n$text\n</div>" );
1474  }
1475  }
1476 
1483  public function showDeletedRevisionHeader() {
1484  if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
1485  // Not deleted
1486  return true;
1487  }
1488 
1489  $outputPage = $this->getContext()->getOutput();
1490  $user = $this->getContext()->getUser();
1491  // If the user is not allowed to see it...
1492  if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
1493  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1494  'rev-deleted-text-permission' );
1495 
1496  return false;
1497  // If the user needs to confirm that they want to see it...
1498  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1499  # Give explanation and add a link to view the revision...
1500  $oldid = intval( $this->getOldID() );
1501  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1502  $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1503  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1504  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1505  [ $msg, $link ] );
1506 
1507  return false;
1508  // We are allowed to see...
1509  } else {
1510  $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1511  'rev-suppressed-text-view' : 'rev-deleted-text-view';
1512  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1513 
1514  return true;
1515  }
1516  }
1517 
1526  public function setOldSubtitle( $oldid = 0 ) {
1527  // Avoid PHP 7.1 warning of passing $this by reference
1528  $articlePage = $this;
1529 
1530  if ( !Hooks::run( 'DisplayOldSubtitle', [ &$articlePage, &$oldid ] ) ) {
1531  return;
1532  }
1533 
1534  $context = $this->getContext();
1535  $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1536 
1537  # Cascade unhide param in links for easy deletion browsing
1538  $extraParams = [];
1539  if ( $unhide ) {
1540  $extraParams['unhide'] = 1;
1541  }
1542 
1543  if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1544  $revision = $this->mRevision;
1545  } else {
1546  $revision = Revision::newFromId( $oldid );
1547  }
1548 
1549  $timestamp = $revision->getTimestamp();
1550 
1551  $current = ( $oldid == $this->mPage->getLatest() );
1552  $language = $context->getLanguage();
1553  $user = $context->getUser();
1554 
1555  $td = $language->userTimeAndDate( $timestamp, $user );
1556  $tddate = $language->userDate( $timestamp, $user );
1557  $tdtime = $language->userTime( $timestamp, $user );
1558 
1559  # Show user links if allowed to see them. If hidden, then show them only if requested...
1560  $userlinks = Linker::revUserTools( $revision, !$unhide );
1561 
1562  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1563  ? 'revision-info-current'
1564  : 'revision-info';
1565 
1566  $outputPage = $context->getOutput();
1567  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1568  $context->msg( $infomsg, $td )
1569  ->rawParams( $userlinks )
1570  ->params( $revision->getId(), $tddate, $tdtime, $revision->getUserText() )
1571  ->rawParams( Linker::revComment( $revision, true, true ) )
1572  ->parse() .
1573  "</div>";
1574 
1575  $lnk = $current
1576  ? $context->msg( 'currentrevisionlink' )->escaped()
1578  $this->getTitle(),
1579  $context->msg( 'currentrevisionlink' )->escaped(),
1580  [],
1581  $extraParams
1582  );
1583  $curdiff = $current
1584  ? $context->msg( 'diff' )->escaped()
1586  $this->getTitle(),
1587  $context->msg( 'diff' )->escaped(),
1588  [],
1589  [
1590  'diff' => 'cur',
1591  'oldid' => $oldid
1592  ] + $extraParams
1593  );
1594  $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
1595  $prevlink = $prev
1597  $this->getTitle(),
1598  $context->msg( 'previousrevision' )->escaped(),
1599  [],
1600  [
1601  'direction' => 'prev',
1602  'oldid' => $oldid
1603  ] + $extraParams
1604  )
1605  : $context->msg( 'previousrevision' )->escaped();
1606  $prevdiff = $prev
1608  $this->getTitle(),
1609  $context->msg( 'diff' )->escaped(),
1610  [],
1611  [
1612  'diff' => 'prev',
1613  'oldid' => $oldid
1614  ] + $extraParams
1615  )
1616  : $context->msg( 'diff' )->escaped();
1617  $nextlink = $current
1618  ? $context->msg( 'nextrevision' )->escaped()
1620  $this->getTitle(),
1621  $context->msg( 'nextrevision' )->escaped(),
1622  [],
1623  [
1624  'direction' => 'next',
1625  'oldid' => $oldid
1626  ] + $extraParams
1627  );
1628  $nextdiff = $current
1629  ? $context->msg( 'diff' )->escaped()
1631  $this->getTitle(),
1632  $context->msg( 'diff' )->escaped(),
1633  [],
1634  [
1635  'diff' => 'next',
1636  'oldid' => $oldid
1637  ] + $extraParams
1638  );
1639 
1640  $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
1641  if ( $cdel !== '' ) {
1642  $cdel .= ' ';
1643  }
1644 
1645  // the outer div is need for styling the revision info and nav in MobileFrontend
1646  $outputPage->addSubtitle( "<div class=\"mw-revision\">" . $revisionInfo .
1647  "<div id=\"mw-revision-nav\">" . $cdel .
1648  $context->msg( 'revision-nav' )->rawParams(
1649  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1650  )->escaped() . "</div></div>" );
1651  }
1652 
1666  public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1667  $lang = $this->getTitle()->getPageLanguage();
1668  $out = $this->getContext()->getOutput();
1669  if ( $appendSubtitle ) {
1670  $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1671  }
1672  $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1673  return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1674  }
1675 
1688  public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1689  if ( !is_array( $target ) ) {
1690  $target = [ $target ];
1691  }
1692 
1693  $html = '<ul class="redirectText">';
1695  foreach ( $target as $title ) {
1696  $html .= '<li>' . Linker::link(
1697  $title,
1698  htmlspecialchars( $title->getFullText() ),
1699  [],
1700  // Make sure wiki page redirects are not followed
1701  $title->isRedirect() ? [ 'redirect' => 'no' ] : [],
1702  ( $forceKnown ? [ 'known', 'noclasses' ] : [] )
1703  ) . '</li>';
1704  }
1705  $html .= '</ul>';
1706 
1707  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1708 
1709  return '<div class="redirectMsg">' .
1710  '<p>' . $redirectToText . '</p>' .
1711  $html .
1712  '</div>';
1713  }
1714 
1723  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1724  $msg = wfMessage(
1725  'namespace-' . $this->getTitle()->getNamespace() . '-helppage'
1726  );
1727 
1728  $out = $this->getContext()->getOutput();
1729  if ( !$msg->isDisabled() ) {
1730  $helpUrl = Skin::makeUrl( $msg->plain() );
1731  $out->addHelpLink( $helpUrl, true );
1732  } else {
1733  $out->addHelpLink( $to, $overrideBaseUrl );
1734  }
1735  }
1736 
1740  public function render() {
1741  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1742  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1743  // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1744  $this->viewIsRenderAction = true;
1745  $this->view();
1746  }
1747 
1751  public function protect() {
1752  $form = new ProtectionForm( $this );
1753  $form->execute();
1754  }
1755 
1759  public function unprotect() {
1760  $this->protect();
1761  }
1762 
1766  public function delete() {
1767  # This code desperately needs to be totally rewritten
1768 
1769  $title = $this->getTitle();
1770  $context = $this->getContext();
1771  $user = $context->getUser();
1772  $request = $context->getRequest();
1773 
1774  # Check permissions
1775  $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
1776  if ( count( $permissionErrors ) ) {
1777  throw new PermissionsError( 'delete', $permissionErrors );
1778  }
1779 
1780  # Read-only check...
1781  if ( wfReadOnly() ) {
1782  throw new ReadOnlyError;
1783  }
1784 
1785  # Better double-check that it hasn't been deleted yet!
1786  $this->mPage->loadPageData(
1787  $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1788  );
1789  if ( !$this->mPage->exists() ) {
1790  $deleteLogPage = new LogPage( 'delete' );
1791  $outputPage = $context->getOutput();
1792  $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1793  $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1794  [ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1795  );
1796  $outputPage->addHTML(
1797  Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1798  );
1800  $outputPage,
1801  'delete',
1802  $title
1803  );
1804 
1805  return;
1806  }
1807 
1808  $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1809  $deleteReason = $request->getText( 'wpReason' );
1810 
1811  if ( $deleteReasonList == 'other' ) {
1812  $reason = $deleteReason;
1813  } elseif ( $deleteReason != '' ) {
1814  // Entry from drop down menu + additional comment
1815  $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1816  $reason = $deleteReasonList . $colonseparator . $deleteReason;
1817  } else {
1818  $reason = $deleteReasonList;
1819  }
1820 
1821  if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1822  [ 'delete', $this->getTitle()->getPrefixedText() ] )
1823  ) {
1824  # Flag to hide all contents of the archived revisions
1825  $suppress = $request->getCheck( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
1826 
1827  $this->doDelete( $reason, $suppress );
1828 
1829  WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1830 
1831  return;
1832  }
1833 
1834  // Generate deletion reason
1835  $hasHistory = false;
1836  if ( !$reason ) {
1837  try {
1838  $reason = $this->generateReason( $hasHistory );
1839  } catch ( Exception $e ) {
1840  # if a page is horribly broken, we still want to be able to
1841  # delete it. So be lenient about errors here.
1842  wfDebug( "Error while building auto delete summary: $e" );
1843  $reason = '';
1844  }
1845  }
1846 
1847  // If the page has a history, insert a warning
1848  if ( $hasHistory ) {
1849  $title = $this->getTitle();
1850 
1851  // The following can use the real revision count as this is only being shown for users
1852  // that can delete this page.
1853  // This, as a side-effect, also makes sure that the following query isn't being run for
1854  // pages with a larger history, unless the user has the 'bigdelete' right
1855  // (and is about to delete this page).
1856  $dbr = wfGetDB( DB_REPLICA );
1857  $revisions = $edits = (int)$dbr->selectField(
1858  'revision',
1859  'COUNT(rev_page)',
1860  [ 'rev_page' => $title->getArticleID() ],
1861  __METHOD__
1862  );
1863 
1864  // @todo i18n issue/patchwork message
1865  $context->getOutput()->addHTML(
1866  '<strong class="mw-delete-warning-revisions">' .
1867  $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1868  $context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
1869  $context->msg( 'history' )->escaped(),
1870  [],
1871  [ 'action' => 'history' ] ) .
1872  '</strong>'
1873  );
1874 
1875  if ( $title->isBigDeletion() ) {
1876  global $wgDeleteRevisionsLimit;
1877  $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1878  [
1879  'delete-warning-toobig',
1880  $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1881  ]
1882  );
1883  }
1884  }
1885 
1886  $this->confirmDelete( $reason );
1887  }
1888 
1894  public function confirmDelete( $reason ) {
1895  wfDebug( "Article::confirmDelete\n" );
1896 
1897  $title = $this->getTitle();
1898  $ctx = $this->getContext();
1899  $outputPage = $ctx->getOutput();
1900  $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1901  $outputPage->addBacklinkSubtitle( $title );
1902  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1903  $outputPage->addModules( 'mediawiki.action.delete' );
1904 
1905  $backlinkCache = $title->getBacklinkCache();
1906  if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
1907  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1908  'deleting-backlinks-warning' );
1909  }
1910 
1911  $subpageQueryLimit = 51;
1912  $subpages = $title->getSubpages( $subpageQueryLimit );
1913  $subpageCount = count( $subpages );
1914  if ( $subpageCount > 0 ) {
1915  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1916  [ 'deleting-subpages-warning', Message::numParam( $subpageCount ) ] );
1917  }
1918  $outputPage->addWikiMsg( 'confirmdeletetext' );
1919 
1920  Hooks::run( 'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
1921 
1922  $user = $this->getContext()->getUser();
1923  $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
1924 
1925  $outputPage->enableOOUI();
1926 
1928  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text(),
1929  [ 'other' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text() ]
1930  );
1931  $options = Xml::listDropDownOptionsOoui( $options );
1932 
1933  $fields[] = new OOUI\FieldLayout(
1934  new OOUI\DropdownInputWidget( [
1935  'name' => 'wpDeleteReasonList',
1936  'inputId' => 'wpDeleteReasonList',
1937  'tabIndex' => 1,
1938  'infusable' => true,
1939  'value' => '',
1940  'options' => $options
1941  ] ),
1942  [
1943  'label' => $ctx->msg( 'deletecomment' )->text(),
1944  'align' => 'top',
1945  ]
1946  );
1947 
1948  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
1949  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
1950  // Unicode codepoints.
1951  $fields[] = new OOUI\FieldLayout(
1952  new OOUI\TextInputWidget( [
1953  'name' => 'wpReason',
1954  'inputId' => 'wpReason',
1955  'tabIndex' => 2,
1957  'infusable' => true,
1958  'value' => $reason,
1959  'autofocus' => true,
1960  ] ),
1961  [
1962  'label' => $ctx->msg( 'deleteotherreason' )->text(),
1963  'align' => 'top',
1964  ]
1965  );
1966 
1967  if ( $user->isLoggedIn() ) {
1968  $fields[] = new OOUI\FieldLayout(
1969  new OOUI\CheckboxInputWidget( [
1970  'name' => 'wpWatch',
1971  'inputId' => 'wpWatch',
1972  'tabIndex' => 3,
1973  'selected' => $checkWatch,
1974  ] ),
1975  [
1976  'label' => $ctx->msg( 'watchthis' )->text(),
1977  'align' => 'inline',
1978  'infusable' => true,
1979  ]
1980  );
1981  }
1982 
1983  if ( $user->isAllowed( 'suppressrevision' ) ) {
1984  $fields[] = new OOUI\FieldLayout(
1985  new OOUI\CheckboxInputWidget( [
1986  'name' => 'wpSuppress',
1987  'inputId' => 'wpSuppress',
1988  'tabIndex' => 4,
1989  ] ),
1990  [
1991  'label' => $ctx->msg( 'revdelete-suppress' )->text(),
1992  'align' => 'inline',
1993  'infusable' => true,
1994  ]
1995  );
1996  }
1997 
1998  $fields[] = new OOUI\FieldLayout(
1999  new OOUI\ButtonInputWidget( [
2000  'name' => 'wpConfirmB',
2001  'inputId' => 'wpConfirmB',
2002  'tabIndex' => 5,
2003  'value' => $ctx->msg( 'deletepage' )->text(),
2004  'label' => $ctx->msg( 'deletepage' )->text(),
2005  'flags' => [ 'primary', 'destructive' ],
2006  'type' => 'submit',
2007  ] ),
2008  [
2009  'align' => 'top',
2010  ]
2011  );
2012 
2013  $fieldset = new OOUI\FieldsetLayout( [
2014  'label' => $ctx->msg( 'delete-legend' )->text(),
2015  'id' => 'mw-delete-table',
2016  'items' => $fields,
2017  ] );
2018 
2019  $form = new OOUI\FormLayout( [
2020  'method' => 'post',
2021  'action' => $title->getLocalURL( 'action=delete' ),
2022  'id' => 'deleteconfirm',
2023  ] );
2024  $form->appendContent(
2025  $fieldset,
2026  new OOUI\HtmlSnippet(
2027  Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
2028  )
2029  );
2030 
2031  $outputPage->addHTML(
2032  new OOUI\PanelLayout( [
2033  'classes' => [ 'deletepage-wrapper' ],
2034  'expanded' => false,
2035  'padded' => true,
2036  'framed' => true,
2037  'content' => $form,
2038  ] )
2039  );
2040 
2041  if ( $user->isAllowed( 'editinterface' ) ) {
2043  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
2044  wfMessage( 'delete-edit-reasonlist' )->escaped(),
2045  [],
2046  [ 'action' => 'edit' ]
2047  );
2048  $outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
2049  }
2050 
2051  $deleteLogPage = new LogPage( 'delete' );
2052  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2053  LogEventsList::showLogExtract( $outputPage, 'delete', $title );
2054  }
2055 
2064  public function doDelete( $reason, $suppress = false, $immediate = false ) {
2065  $error = '';
2066  $context = $this->getContext();
2067  $outputPage = $context->getOutput();
2068  $user = $context->getUser();
2069  $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error, $user,
2070  [], 'delete', $immediate );
2071 
2072  if ( $status->isOK() ) {
2073  $deleted = $this->getTitle()->getPrefixedText();
2074 
2075  $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
2076  $outputPage->setRobotPolicy( 'noindex,nofollow' );
2077 
2078  if ( $status->isGood() ) {
2079  $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
2080  $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
2081  Hooks::run( 'ArticleDeleteAfterSuccess', [ $this->getTitle(), $outputPage ] );
2082  } else {
2083  $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
2084  }
2085 
2086  $outputPage->returnToMain( false );
2087  } else {
2088  $outputPage->setPageTitle(
2089  wfMessage( 'cannotdelete-title',
2090  $this->getTitle()->getPrefixedText() )
2091  );
2092 
2093  if ( $error == '' ) {
2094  $outputPage->wrapWikiTextAsInterface(
2095  'error mw-error-cannotdelete',
2096  $status->getWikiText()
2097  );
2098  $deleteLogPage = new LogPage( 'delete' );
2099  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2100 
2102  $outputPage,
2103  'delete',
2104  $this->getTitle()
2105  );
2106  } else {
2107  $outputPage->addHTML( $error );
2108  }
2109  }
2110  }
2111 
2112  /* Caching functions */
2113 
2121  protected function tryFileCache() {
2122  static $called = false;
2123 
2124  if ( $called ) {
2125  wfDebug( "Article::tryFileCache(): called twice!?\n" );
2126  return false;
2127  }
2128 
2129  $called = true;
2130  if ( $this->isFileCacheable() ) {
2131  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
2132  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
2133  wfDebug( "Article::tryFileCache(): about to load file\n" );
2134  $cache->loadFromFileCache( $this->getContext() );
2135  return true;
2136  } else {
2137  wfDebug( "Article::tryFileCache(): starting buffer\n" );
2138  ob_start( [ &$cache, 'saveToFileCache' ] );
2139  }
2140  } else {
2141  wfDebug( "Article::tryFileCache(): not cacheable\n" );
2142  }
2143 
2144  return false;
2145  }
2146 
2152  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
2153  $cacheable = false;
2154 
2155  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
2156  $cacheable = $this->mPage->getId()
2157  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
2158  // Extension may have reason to disable file caching on some pages.
2159  if ( $cacheable ) {
2160  // Avoid PHP 7.1 warning of passing $this by reference
2161  $articlePage = $this;
2162  $cacheable = Hooks::run( 'IsFileCacheable', [ &$articlePage ] );
2163  }
2164  }
2165 
2166  return $cacheable;
2167  }
2168 
2182  public function getParserOutput( $oldid = null, User $user = null ) {
2183  // XXX: bypasses mParserOptions and thus setParserOptions()
2184 
2185  if ( $user === null ) {
2186  $parserOptions = $this->getParserOptions();
2187  } else {
2188  $parserOptions = $this->mPage->makeParserOptions( $user );
2189  }
2190 
2191  return $this->mPage->getParserOutput( $parserOptions, $oldid );
2192  }
2193 
2201  if ( $this->mParserOptions ) {
2202  throw new MWException( "can't change parser options after they have already been set" );
2203  }
2204 
2205  // clone, so if $options is modified later, it doesn't confuse the parser cache.
2206  $this->mParserOptions = clone $options;
2207  }
2208 
2213  public function getParserOptions() {
2214  if ( !$this->mParserOptions ) {
2215  $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
2216  }
2217  // Clone to allow modifications of the return value without affecting cache
2218  return clone $this->mParserOptions;
2219  }
2220 
2227  public function setContext( $context ) {
2228  $this->mContext = $context;
2229  }
2230 
2237  public function getContext() {
2238  if ( $this->mContext instanceof IContextSource ) {
2239  return $this->mContext;
2240  } else {
2241  wfDebug( __METHOD__ . " called and \$mContext is null. " .
2242  "Return RequestContext::getMain(); for sanity\n" );
2243  return RequestContext::getMain();
2244  }
2245  }
2246 
2254  public function __get( $fname ) {
2255  if ( property_exists( $this->mPage, $fname ) ) {
2256  # wfWarn( "Access to raw $fname field " . __CLASS__ );
2257  return $this->mPage->$fname;
2258  }
2259  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2260  }
2261 
2269  public function __set( $fname, $fvalue ) {
2270  if ( property_exists( $this->mPage, $fname ) ) {
2271  # wfWarn( "Access to raw $fname field of " . __CLASS__ );
2272  $this->mPage->$fname = $fvalue;
2273  // Note: extensions may want to toss on new fields
2274  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2275  $this->mPage->$fname = $fvalue;
2276  } else {
2277  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2278  }
2279  }
2280 
2285  public function checkFlags( $flags ) {
2286  return $this->mPage->checkFlags( $flags );
2287  }
2288 
2293  public function checkTouched() {
2294  return $this->mPage->checkTouched();
2295  }
2296 
2301  public function clearPreparedEdit() {
2302  $this->mPage->clearPreparedEdit();
2303  }
2304 
2309  public function doDeleteArticleReal(
2310  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
2311  $tags = [], $immediate = false
2312  ) {
2313  return $this->mPage->doDeleteArticleReal(
2314  $reason, $suppress, $u1, $u2, $error, $user, $tags, 'delete', $immediate
2315  );
2316  }
2317 
2322  public function doDeleteUpdates(
2323  $id,
2324  Content $content = null,
2325  $revision = null,
2326  User $user = null
2327  ) {
2328  $this->mPage->doDeleteUpdates( $id, $content, $revision, $user );
2329  }
2330 
2336  public function doEditContent( Content $content, $summary, $flags = 0, $originalRevId = false,
2337  User $user = null, $serialFormat = null
2338  ) {
2339  wfDeprecated( __METHOD__, '1.29' );
2340  return $this->mPage->doEditContent( $content, $summary, $flags, $originalRevId,
2341  $user, $serialFormat
2342  );
2343  }
2344 
2349  public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2350  return $this->mPage->doEditUpdates( $revision, $user, $options );
2351  }
2352 
2359  public function doPurge() {
2360  return $this->mPage->doPurge();
2361  }
2362 
2367  public function doViewUpdates( User $user, $oldid = 0 ) {
2368  $this->mPage->doViewUpdates( $user, $oldid );
2369  }
2370 
2375  public function exists() {
2376  return $this->mPage->exists();
2377  }
2378 
2383  public function followRedirect() {
2384  return $this->mPage->followRedirect();
2385  }
2386 
2391  public function getActionOverrides() {
2392  return $this->mPage->getActionOverrides();
2393  }
2394 
2399  public function getAutoDeleteReason( &$hasHistory ) {
2400  return $this->mPage->getAutoDeleteReason( $hasHistory );
2401  }
2402 
2407  public function getCategories() {
2408  return $this->mPage->getCategories();
2409  }
2410 
2415  public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2416  return $this->mPage->getComment( $audience, $user );
2417  }
2418 
2423  public function getContentHandler() {
2424  return $this->mPage->getContentHandler();
2425  }
2426 
2431  public function getContentModel() {
2432  return $this->mPage->getContentModel();
2433  }
2434 
2439  public function getContributors() {
2440  return $this->mPage->getContributors();
2441  }
2442 
2447  public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2448  return $this->mPage->getCreator( $audience, $user );
2449  }
2450 
2455  public function getDeletionUpdates( Content $content = null ) {
2456  return $this->mPage->getDeletionUpdates( $content );
2457  }
2458 
2463  public function getHiddenCategories() {
2464  return $this->mPage->getHiddenCategories();
2465  }
2466 
2471  public function getId() {
2472  return $this->mPage->getId();
2473  }
2474 
2479  public function getLatest() {
2480  return $this->mPage->getLatest();
2481  }
2482 
2487  public function getLinksTimestamp() {
2488  return $this->mPage->getLinksTimestamp();
2489  }
2490 
2495  public function getMinorEdit() {
2496  return $this->mPage->getMinorEdit();
2497  }
2498 
2503  public function getOldestRevision() {
2504  return $this->mPage->getOldestRevision();
2505  }
2506 
2511  public function getRedirectTarget() {
2512  return $this->mPage->getRedirectTarget();
2513  }
2514 
2519  public function getRedirectURL( $rt ) {
2520  return $this->mPage->getRedirectURL( $rt );
2521  }
2522 
2527  public function getRevision() {
2528  return $this->mPage->getRevision();
2529  }
2530 
2535  public function getTimestamp() {
2536  return $this->mPage->getTimestamp();
2537  }
2538 
2543  public function getTouched() {
2544  return $this->mPage->getTouched();
2545  }
2546 
2551  public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2552  return $this->mPage->getUndoContent( $undo, $undoafter );
2553  }
2554 
2559  public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2560  return $this->mPage->getUser( $audience, $user );
2561  }
2562 
2567  public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2568  return $this->mPage->getUserText( $audience, $user );
2569  }
2570 
2575  public function hasViewableContent() {
2576  return $this->mPage->hasViewableContent();
2577  }
2578 
2583  public function insertOn( $dbw, $pageId = null ) {
2584  return $this->mPage->insertOn( $dbw, $pageId );
2585  }
2586 
2591  public function insertProtectNullRevision( $revCommentMsg, array $limit,
2592  array $expiry, $cascade, $reason, $user = null
2593  ) {
2594  return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
2595  $expiry, $cascade, $reason, $user
2596  );
2597  }
2598 
2603  public function insertRedirect() {
2604  return $this->mPage->insertRedirect();
2605  }
2606 
2611  public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2612  return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2613  }
2614 
2619  public function isCountable( $editInfo = false ) {
2620  return $this->mPage->isCountable( $editInfo );
2621  }
2622 
2627  public function isRedirect() {
2628  return $this->mPage->isRedirect();
2629  }
2630 
2635  public function loadFromRow( $data, $from ) {
2636  return $this->mPage->loadFromRow( $data, $from );
2637  }
2638 
2643  public function loadPageData( $from = 'fromdb' ) {
2644  $this->mPage->loadPageData( $from );
2645  }
2646 
2651  public function lockAndGetLatest() {
2652  return $this->mPage->lockAndGetLatest();
2653  }
2654 
2659  public function makeParserOptions( $context ) {
2660  return $this->mPage->makeParserOptions( $context );
2661  }
2662 
2667  public function pageDataFromId( $dbr, $id, $options = [] ) {
2668  return $this->mPage->pageDataFromId( $dbr, $id, $options );
2669  }
2670 
2675  public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2676  return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2677  }
2678 
2683  public function prepareContentForEdit(
2684  Content $content, $revision = null, User $user = null,
2685  $serialFormat = null, $useCache = true
2686  ) {
2687  return $this->mPage->prepareContentForEdit(
2688  $content, $revision, $user,
2689  $serialFormat, $useCache
2690  );
2691  }
2692 
2697  public function protectDescription( array $limit, array $expiry ) {
2698  return $this->mPage->protectDescription( $limit, $expiry );
2699  }
2700 
2705  public function protectDescriptionLog( array $limit, array $expiry ) {
2706  return $this->mPage->protectDescriptionLog( $limit, $expiry );
2707  }
2708 
2713  public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2714  $sectionTitle = '', $baseRevId = null
2715  ) {
2716  return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2717  $sectionTitle, $baseRevId
2718  );
2719  }
2720 
2725  public function replaceSectionContent(
2726  $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2727  ) {
2728  return $this->mPage->replaceSectionContent(
2729  $sectionId, $sectionContent, $sectionTitle, $edittime
2730  );
2731  }
2732 
2737  public function setTimestamp( $ts ) {
2738  $this->mPage->setTimestamp( $ts );
2739  }
2740 
2745  public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2746  return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2747  }
2748 
2753  public function supportsSections() {
2754  return $this->mPage->supportsSections();
2755  }
2756 
2761  public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2762  return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2763  }
2764 
2769  public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
2770  return $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2771  }
2772 
2777  public function updateIfNewerOn( $dbw, $revision ) {
2778  return $this->mPage->updateIfNewerOn( $dbw, $revision );
2779  }
2780 
2785  public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
2786  return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect );
2787  }
2788 
2793  public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
2794  $lastRevIsRedirect = null
2795  ) {
2796  return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
2797  $lastRevIsRedirect
2798  );
2799  }
2800 
2809  public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
2810  $reason, User $user
2811  ) {
2812  return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2813  }
2814 
2822  public function updateRestrictions( $limit = [], $reason = '',
2823  &$cascade = 0, $expiry = []
2824  ) {
2825  return $this->mPage->doUpdateRestrictions(
2826  $limit,
2827  $expiry,
2828  $cascade,
2829  $reason,
2830  $this->getContext()->getUser()
2831  );
2832  }
2833 
2845  public function doDeleteArticle(
2846  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', $immediate = false
2847  ) {
2848  return $this->mPage->doDeleteArticle( $reason, $suppress, $u1, $u2, $error,
2849  null, $immediate );
2850  }
2851 
2861  public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
2862  if ( !$user ) {
2863  $user = $this->getContext()->getUser();
2864  }
2865 
2866  return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2867  }
2868 
2877  public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
2878  if ( !$guser ) {
2879  $guser = $this->getContext()->getUser();
2880  }
2881 
2882  return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2883  }
2884 
2889  public function generateReason( &$hasHistory ) {
2890  $title = $this->mPage->getTitle();
2892  return $handler->getAutoDeleteReason( $title, $hasHistory );
2893  }
2894 
2895  // ******
2896 }
getRedirectTarget()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2511
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
const FOR_THIS_USER
Definition: Revision.php:55
lockAndGetLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2651
getUndoContent(Revision $undo, Revision $undoafter=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2551
getUserText( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2567
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1982
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
getRedirectedFrom()
Get the page this view was redirected from.
Definition: Article.php:203
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1585
getLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2479
setParserOptions(ParserOptions $options)
Override the ParserOptions used to render the primary article wikitext.
Definition: Article.php:2200
doEditUpdates(Revision $revision, User $user, array $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2349
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:470
checkFlags( $flags)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2285
isCountable( $editInfo=false)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2619
exists()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2375
supportsSections()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2753
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:985
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
protect()
action=protect handler
Definition: Article.php:1751
Status null $fetchResult
represents the outcome of fetchRevisionRecord().
Definition: Article.php:100
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2159
$wgNamespaceRobotPolicies
Robot policies per namespaces.
Wrapper allowing us to handle a system message as a Content object.
$wgDebugToolbar
Display the new debugging toolbar.
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:231
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
updateCategoryCounts(array $added, array $deleted, $id=0)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2769
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:83
if(!isset( $args[0])) $lang
ParserOptions null $mParserOptions
ParserOptions object for $wgUser articles.
Definition: Article.php:52
Special handling for category description pages, showing pages, subcategories and file that belong to...
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition: Article.php:883
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2745
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user)
Definition: Article.php:2809
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getParserOutput( $oldid=null, User $user=null)
#-
Definition: Article.php:2182
makeFetchErrorContent()
Returns a Content object representing any error in $this->fetchContent, or null if there is no such e...
Definition: Article.php:505
Class for viewing MediaWiki article and history.
Definition: Article.php:38
null for the local wiki Added in
Definition: hooks.txt:1585
Page view caching in the file system.
getRedirectURL( $rt)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2519
followRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2383
__get( $fname)
Use PHP&#39;s magic __get handler to handle accessing of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2254
Class for viewing MediaWiki file description pages.
Definition: ImagePage.php:31
doDeleteArticle( $reason, $suppress=false, $u1=null, $u2=null, &$error='', $immediate=false)
Definition: Article.php:2845
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2761
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition: Article.php:339
getDeletionUpdates(Content $content=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2455
setContext( $context)
Sets the context this Article is executed in.
Definition: Article.php:2227
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
fetchRevisionRecord()
Fetches the revision to work on.
Definition: Article.php:417
clearPreparedEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2301
getRevisionRedirectTarget(RevisionRecord $revision)
Definition: Article.php:871
static numParam( $num)
Definition: Message.php:1049
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2725
__set( $fname, $fvalue)
Use PHP&#39;s magic __set handler to handle setting of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2269
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
getContributors()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2439
updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2785
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1347
loadFromRow( $data, $from)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2635
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2793
protectDescription(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2697
Mutable RevisionRecord implementation, for building new revision entries programmatically.
Class to simplify the use of log pages.
Definition: LogPage.php:33
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition: Article.php:1526
static newCanonical( $context=null, $userLang=null)
Creates a "canonical" ParserOptions object.
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3050
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:780
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:780
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2237
Revision null $mRevision
Revision to be shown.
Definition: Article.php:109
pageDataFromTitle( $dbr, $title, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2675
bool $viewIsRenderAction
Whether render() was called.
Definition: Article.php:123
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1263
isRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2627
__construct(Title $title, $oldId=null)
Constructor and clear the article.
Definition: Article.php:130
protectDescriptionLog(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2705
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:24
wfReadOnly()
Check whether the wiki is in read-only mode.
$wgDefaultRobotPolicy
Default robot policy.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
static getMain()
Get the RequestContext object associated with the main request.
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2683
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
const FOR_PUBLIC
Definition: Revision.php:54
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites...
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition: hooks.txt:2217
pageDataFromId( $dbr, $id, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2667
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:1146
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:89
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
getRevisionFetched()
Get the fetched Revision object depending on request parameters or null on failure.
Definition: Article.php:559
doEditContent(Content $content, $summary, $flags=0, $originalRevId=false, User $user=null, $serialFormat=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2336
const NS_MEDIA
Definition: Defines.php:48
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:1057
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
makeParserOptions( $context)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2659
getRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2527
generateReason(&$hasHistory)
Definition: Article.php:2889
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
static listDropDownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
Definition: Xml.php:539
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1688
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example, it is done during re-uploads when file patrol is used.
Definition: Article.php:1338
insertRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2603
static isValid( $ip)
Validate an IP address.
Definition: IP.php:111
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition: Article.php:77
hasViewableContent()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2575
IContextSource null $mContext
The context this Article is executed in.
Definition: Article.php:43
getTitle()
Get the title object of the article.
Definition: Article.php:221
$cache
Definition: mcc.php:33
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2391
doViewUpdates(User $user, $oldid=0)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2367
getTitle()
Get the title object of the article.
Definition: WikiPage.php:294
const NS_CATEGORY
Definition: Defines.php:74
doPurge()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2359
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1982
doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user=null)
Definition: Article.php:2861
updateRestrictions( $limit=[], $reason='', &$cascade=0, $expiry=[])
Definition: Article.php:2822
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:850
render()
Handle action=render.
Definition: Article.php:1740
insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason, $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2591
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1723
const DELETED_RESTRICTED
Definition: Revision.php:49
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
getCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2407
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
confirmDelete( $reason)
Output deletion confirmation dialog.
Definition: Article.php:1894
const NS_FILE
Definition: Defines.php:66
$wgRedirectSources
If local interwikis are set up which allow redirects, set this regexp to restrict URLs which will be ...
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1766
$wgArticleRobotPolicies
Robot policies per article.
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
WikiPage null $mPage
The WikiPage object of this instance.
Definition: Article.php:46
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
replaceSectionAtRev( $sectionId, Content $sectionContent, $sectionTitle='', $baseRevId=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2713
getContentHandler()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2423
const NS_MEDIAWIKI
Definition: Defines.php:68
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition: Article.php:2064
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:123
insertOn( $dbw, $pageId=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2583
getUser( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2559
static makeUrl( $name, $urlaction='')
Definition: Skin.php:1192
const DELETED_TEXT
Definition: Revision.php:46
newPage(Title $title)
Definition: Article.php:139
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition: Article.php:80
setTimestamp( $ts)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2737
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:589
getOldestRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2503
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...
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify as strings Extensions should add to this list prev or next refreshes the diff cache $unhide
Definition: hooks.txt:1585
clear()
Clear the object.
Definition: Article.php:238
bool $mContentLoaded
Is the target revision loaded? Set by fetchRevisionRecord().
Definition: Article.php:70
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
static isRegistered( $name)
Returns true if a hook has a function registered to it.
Definition: Hooks.php:80
getAutoDeleteReason(&$hasHistory)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2399
checkTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2293
static getRevDeleteLink(User $user, Revision $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2111
getId()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2471
getTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2535
Content null $mContentObject
Content of the main slot of $this->mRevision.
Definition: Article.php:62
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition: Article.php:116
doDeleteArticleReal( $reason, $suppress=false, $u1=null, $u2=null, &$error='', User $user=null, $tags=[], $immediate=false)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2309
getCreator( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2447
getMinorEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2495
view()
This is the default action of the index.php entry point: just view the page of the given title...
Definition: Article.php:587
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to &#39;known&#39;.
Definition: Linker.php:146
getEmptyPageParserOutput(ParserOptions $options)
Returns ParserOutput to use when a page does not exist.
Definition: Article.php:313
getContentObject()
Returns a Content object representing the pages effective display content, not necessarily the revisi...
Definition: Article.php:270
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:797
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition: Article.php:2152
getOldID()
Definition: Article.php:326
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:1134
Show an error when a user tries to do something they do not have the necessary permissions for...
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the &#39;bigdelete&#39; perm...
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition: Article.php:2121
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2633
getComment( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2415
insertRedirectEntry(Title $rt, $oldLatest=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2611
doDeleteUpdates( $id, Content $content=null, $revision=null, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2322
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1666
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:202
static revComment(Revision $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision&#39;s comment block, if the current user is allowed to view it...
Definition: Linker.php:1572
int $mRevIdFetched
Revision ID of revision that was loaded.
Definition: Article.php:90
getSubstituteContent()
Returns Content object to use when the page does not exist.
Definition: Article.php:286
Page revision base class.
loadPageData( $from='fromdb')
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2643
static newFromID( $id)
Constructor from a page id.
Definition: Article.php:148
fetchContentObject()
Get text content object Does NOT follow redirects.
Definition: Article.php:400
const DB_REPLICA
Definition: defines.php:25
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used the name of the old file & $article
Definition: hooks.txt:1473
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:160
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists)...
Definition: Article.php:541
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1759
Handles the page protection UI and backend.
getTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2543
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:594
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:192
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:118
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:84
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code, mangling or hiding MediaWiki&#39;s output.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
$content
Definition: pageupdater.txt:72
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition: Article.php:575
const NS_USER_TALK
Definition: Defines.php:63
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:898
applyContentOverride(Content $override)
Applies a content override by constructing a fake Revision object and assigning it to mRevision...
Definition: Article.php:525
updateIfNewerOn( $dbw, $revision)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2777
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2633
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1483
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki...
Definition: Article.php:212
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1473
getContentModel()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2431
getLinksTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2487
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser=null)
Definition: Article.php:2877
static listDropDownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc...
Definition: Xml.php:581
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition: Article.php:2213
const RC_LOG
Definition: Defines.php:140
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:948
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:1169
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
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:1124
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1028
getHiddenCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2463