MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
23 
35 class Article implements Page {
37  protected $mContext;
38 
40  protected $mPage;
41 
44 
49  public $mContent;
50 
56 
58  public $mContentLoaded = false;
59 
61  public $mOldId;
62 
64  public $mRedirectedFrom = null;
65 
67  public $mRedirectUrl = false;
68 
70  public $mRevIdFetched = 0;
71 
73  public $mRevision = null;
74 
77 
84 
90  public function __construct( Title $title, $oldId = null ) {
91  $this->mOldId = $oldId;
92  $this->mPage = $this->newPage( $title );
93  }
94 
99  protected function newPage( Title $title ) {
100  return new WikiPage( $title );
101  }
102 
108  public static function newFromID( $id ) {
109  $t = Title::newFromID( $id );
110  return $t == null ? null : new static( $t );
111  }
112 
120  public static function newFromTitle( $title, IContextSource $context ) {
121  if ( NS_MEDIA == $title->getNamespace() ) {
122  // FIXME: where should this go?
123  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
124  }
125 
126  $page = null;
127  Hooks::run( 'ArticleFromTitle', [ &$title, &$page, $context ] );
128  if ( !$page ) {
129  switch ( $title->getNamespace() ) {
130  case NS_FILE:
131  $page = new ImagePage( $title );
132  break;
133  case NS_CATEGORY:
134  $page = new CategoryPage( $title );
135  break;
136  default:
137  $page = new Article( $title );
138  }
139  }
140  $page->setContext( $context );
141 
142  return $page;
143  }
144 
152  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
153  $article = self::newFromTitle( $page->getTitle(), $context );
154  $article->mPage = $page; // override to keep process cached vars
155  return $article;
156  }
157 
163  public function getRedirectedFrom() {
164  return $this->mRedirectedFrom;
165  }
166 
172  public function setRedirectedFrom( Title $from ) {
173  $this->mRedirectedFrom = $from;
174  }
175 
181  public function getTitle() {
182  return $this->mPage->getTitle();
183  }
184 
191  public function getPage() {
192  return $this->mPage;
193  }
194 
198  public function clear() {
199  $this->mContentLoaded = false;
200 
201  $this->mRedirectedFrom = null; # Title object if set
202  $this->mRevIdFetched = 0;
203  $this->mRedirectUrl = false;
204 
205  $this->mPage->clear();
206  }
207 
223  protected function getContentObject() {
224  if ( $this->mPage->getId() === 0 ) {
225  # If this is a MediaWiki:x message, then load the messages
226  # and return the message value for x.
227  if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
228  $text = $this->getTitle()->getDefaultMessageText();
229  if ( $text === false ) {
230  $text = '';
231  }
232 
233  $content = ContentHandler::makeContent( $text, $this->getTitle() );
234  } else {
235  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
236  $content = new MessageContent( $message, null, 'parsemag' );
237  }
238  } else {
239  $this->fetchContentObject();
240  $content = $this->mContentObject;
241  }
242 
243  return $content;
244  }
245 
249  public function getOldID() {
250  if ( is_null( $this->mOldId ) ) {
251  $this->mOldId = $this->getOldIDFromRequest();
252  }
253 
254  return $this->mOldId;
255  }
256 
262  public function getOldIDFromRequest() {
263  $this->mRedirectUrl = false;
264 
265  $request = $this->getContext()->getRequest();
266  $oldid = $request->getIntOrNull( 'oldid' );
267 
268  if ( $oldid === null ) {
269  return 0;
270  }
271 
272  if ( $oldid !== 0 ) {
273  # Load the given revision and check whether the page is another one.
274  # In that case, update this instance to reflect the change.
275  if ( $oldid === $this->mPage->getLatest() ) {
276  $this->mRevision = $this->mPage->getRevision();
277  } else {
278  $this->mRevision = Revision::newFromId( $oldid );
279  if ( $this->mRevision !== null ) {
280  // Revision title doesn't match the page title given?
281  if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
282  $function = [ get_class( $this->mPage ), 'newFromID' ];
283  $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
284  }
285  }
286  }
287  }
288 
289  if ( $request->getVal( 'direction' ) == 'next' ) {
290  $nextid = $this->getTitle()->getNextRevisionID( $oldid );
291  if ( $nextid ) {
292  $oldid = $nextid;
293  $this->mRevision = null;
294  } else {
295  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
296  }
297  } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
298  $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
299  if ( $previd ) {
300  $oldid = $previd;
301  $this->mRevision = null;
302  }
303  }
304 
305  return $oldid;
306  }
307 
320  protected function fetchContentObject() {
321  if ( $this->mContentLoaded ) {
322  return $this->mContentObject;
323  }
324 
325  $this->mContentLoaded = true;
326  $this->mContent = null;
327 
328  $oldid = $this->getOldID();
329 
330  # Pre-fill content with error message so that if something
331  # fails we'll have something telling us what we intended.
332  // XXX: this isn't page content but a UI message. horrible.
333  $this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
334 
335  if ( $oldid ) {
336  # $this->mRevision might already be fetched by getOldIDFromRequest()
337  if ( !$this->mRevision ) {
338  $this->mRevision = Revision::newFromId( $oldid );
339  if ( !$this->mRevision ) {
340  wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
341  return false;
342  }
343  }
344  } else {
345  $oldid = $this->mPage->getLatest();
346  if ( !$oldid ) {
347  wfDebug( __METHOD__ . " failed to find page data for title " .
348  $this->getTitle()->getPrefixedText() . "\n" );
349  return false;
350  }
351 
352  # Update error message with correct oldid
353  $this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
354 
355  $this->mRevision = $this->mPage->getRevision();
356 
357  if ( !$this->mRevision ) {
358  wfDebug( __METHOD__ . " failed to retrieve current page, rev_id $oldid\n" );
359  return false;
360  }
361  }
362 
363  // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
364  // We should instead work with the Revision object when we need it...
365  // Loads if user is allowed
366  $content = $this->mRevision->getContent(
368  $this->getContext()->getUser()
369  );
370 
371  if ( !$content ) {
372  wfDebug( __METHOD__ . " failed to retrieve content of revision " .
373  $this->mRevision->getId() . "\n" );
374  return false;
375  }
376 
377  $this->mContentObject = $content;
378  $this->mRevIdFetched = $this->mRevision->getId();
379 
380  // Avoid PHP 7.1 warning of passing $this by reference
381  $articlePage = $this;
382 
383  Hooks::run(
384  'ArticleAfterFetchContentObject',
385  [ &$articlePage, &$this->mContentObject ]
386  );
387 
388  return $this->mContentObject;
389  }
390 
396  public function isCurrent() {
397  # If no oldid, this is the current version.
398  if ( $this->getOldID() == 0 ) {
399  return true;
400  }
401 
402  return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
403  }
404 
412  public function getRevisionFetched() {
413  $this->fetchContentObject();
414 
415  return $this->mRevision;
416  }
417 
423  public function getRevIdFetched() {
424  if ( $this->mRevIdFetched ) {
425  return $this->mRevIdFetched;
426  } else {
427  return $this->mPage->getLatest();
428  }
429  }
430 
435  public function view() {
436  global $wgUseFileCache, $wgDebugToolbar;
437 
438  # Get variables from query string
439  # As side effect this will load the revision and update the title
440  # in a revision ID is passed in the request, so this should remain
441  # the first call of this method even if $oldid is used way below.
442  $oldid = $this->getOldID();
443 
444  $user = $this->getContext()->getUser();
445  # Another whitelist check in case getOldID() is altering the title
446  $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
447  if ( count( $permErrors ) ) {
448  wfDebug( __METHOD__ . ": denied on secondary read check\n" );
449  throw new PermissionsError( 'read', $permErrors );
450  }
451 
452  $outputPage = $this->getContext()->getOutput();
453  # getOldID() may as well want us to redirect somewhere else
454  if ( $this->mRedirectUrl ) {
455  $outputPage->redirect( $this->mRedirectUrl );
456  wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
457 
458  return;
459  }
460 
461  # If we got diff in the query, we want to see a diff page instead of the article.
462  if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
463  wfDebug( __METHOD__ . ": showing diff page\n" );
464  $this->showDiffPage();
465 
466  return;
467  }
468 
469  # Set page title (may be overridden by DISPLAYTITLE)
470  $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
471 
472  $outputPage->setArticleFlag( true );
473  # Allow frames by default
474  $outputPage->allowClickjacking();
475 
476  $parserCache = MediaWikiServices::getInstance()->getParserCache();
477 
478  $parserOptions = $this->getParserOptions();
479  $poOptions = [];
480  # Render printable version, use printable version cache
481  if ( $outputPage->isPrintable() ) {
482  $parserOptions->setIsPrintable( true );
483  $parserOptions->setEditSection( false );
484  $poOptions['enableSectionEditLinks'] = false;
485  } elseif ( $this->disableSectionEditForRender
486  || !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user )
487  ) {
488  $parserOptions->setEditSection( false );
489  $poOptions['enableSectionEditLinks'] = false;
490  }
491 
492  # Try client and file cache
493  if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
494  # Try to stream the output from file cache
495  if ( $wgUseFileCache && $this->tryFileCache() ) {
496  wfDebug( __METHOD__ . ": done file cache\n" );
497  # tell wgOut that output is taken care of
498  $outputPage->disable();
499  $this->mPage->doViewUpdates( $user, $oldid );
500 
501  return;
502  }
503  }
504 
505  # Should the parser cache be used?
506  $useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
507  wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
508  if ( $user->getStubThreshold() ) {
509  MediaWikiServices::getInstance()->getStatsdDataFactory()->increment( 'pcache_miss_stub' );
510  }
511 
512  $this->showRedirectedFromHeader();
513  $this->showNamespaceHeader();
514 
515  # Iterate through the possible ways of constructing the output text.
516  # Keep going until $outputDone is set, or we run out of things to do.
517  $pass = 0;
518  $outputDone = false;
519  $this->mParserOutput = false;
520 
521  while ( !$outputDone && ++$pass ) {
522  switch ( $pass ) {
523  case 1:
524  // Avoid PHP 7.1 warning of passing $this by reference
525  $articlePage = $this;
526  Hooks::run( 'ArticleViewHeader', [ &$articlePage, &$outputDone, &$useParserCache ] );
527  break;
528  case 2:
529  # Early abort if the page doesn't exist
530  if ( !$this->mPage->exists() ) {
531  wfDebug( __METHOD__ . ": showing missing article\n" );
532  $this->showMissingArticle();
533  $this->mPage->doViewUpdates( $user );
534  return;
535  }
536 
537  # Try the parser cache
538  if ( $useParserCache ) {
539  $this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
540 
541  if ( $this->mParserOutput !== false ) {
542  if ( $oldid ) {
543  wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
544  $this->setOldSubtitle( $oldid );
545  } else {
546  wfDebug( __METHOD__ . ": showing parser cache contents\n" );
547  }
548  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
549  # Ensure that UI elements requiring revision ID have
550  # the correct version information.
551  $outputPage->setRevisionId( $this->mPage->getLatest() );
552  # Preload timestamp to avoid a DB hit
553  $cachedTimestamp = $this->mParserOutput->getTimestamp();
554  if ( $cachedTimestamp !== null ) {
555  $outputPage->setRevisionTimestamp( $cachedTimestamp );
556  $this->mPage->setTimestamp( $cachedTimestamp );
557  }
558  $outputDone = true;
559  }
560  }
561  break;
562  case 3:
563  # This will set $this->mRevision if needed
564  $this->fetchContentObject();
565 
566  # Are we looking at an old revision
567  if ( $oldid && $this->mRevision ) {
568  $this->setOldSubtitle( $oldid );
569 
570  if ( !$this->showDeletedRevisionHeader() ) {
571  wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
572  return;
573  }
574  }
575 
576  # Ensure that UI elements requiring revision ID have
577  # the correct version information.
578  $outputPage->setRevisionId( $this->getRevIdFetched() );
579  # Preload timestamp to avoid a DB hit
580  $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
581 
582  if ( !Hooks::run( 'ArticleContentViewCustom',
583  [ $this->fetchContentObject(), $this->getTitle(), $outputPage ] )
584  ) {
585  # Allow extensions do their own custom view for certain pages
586  $outputDone = true;
587  }
588  break;
589  case 4:
590  # Run the parse, protected by a pool counter
591  wfDebug( __METHOD__ . ": doing uncached parse\n" );
592 
593  $content = $this->getContentObject();
594  $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
595  $this->getRevIdFetched(), $useParserCache, $content );
596 
597  if ( !$poolArticleView->execute() ) {
598  $error = $poolArticleView->getError();
599  if ( $error ) {
600  $outputPage->clearHTML(); // for release() errors
601  $outputPage->enableClientCache( false );
602  $outputPage->setRobotPolicy( 'noindex,nofollow' );
603 
604  $errortext = $error->getWikiText( false, 'view-pool-error' );
605  $outputPage->addWikiText( Html::errorBox( $errortext ) );
606  }
607  # Connection or timeout error
608  return;
609  }
610 
611  $this->mParserOutput = $poolArticleView->getParserOutput();
612  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
613  if ( $content->getRedirectTarget() ) {
614  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
615  $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
616  }
617 
618  # Don't cache a dirty ParserOutput object
619  if ( $poolArticleView->getIsDirty() ) {
620  $outputPage->setCdnMaxage( 0 );
621  $outputPage->addHTML( "<!-- parser cache is expired, " .
622  "sending anyway due to pool overload-->\n" );
623  }
624 
625  $outputDone = true;
626  break;
627  # Should be unreachable, but just in case...
628  default:
629  break 2;
630  }
631  }
632 
633  # Get the ParserOutput actually *displayed* here.
634  # Note that $this->mParserOutput is the *current*/oldid version output.
635  $pOutput = ( $outputDone instanceof ParserOutput )
636  ? $outputDone // object fetched by hook
637  : $this->mParserOutput;
638 
639  # Adjust title for main page & pages with displaytitle
640  if ( $pOutput ) {
641  $this->adjustDisplayTitle( $pOutput );
642  }
643 
644  # For the main page, overwrite the <title> element with the con-
645  # tents of 'pagetitle-view-mainpage' instead of the default (if
646  # that's not empty).
647  # This message always exists because it is in the i18n files
648  if ( $this->getTitle()->isMainPage() ) {
649  $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
650  if ( !$msg->isDisabled() ) {
651  $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
652  }
653  }
654 
655  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
656  # This could use getTouched(), but that could be scary for major template edits.
657  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
658 
659  # Check for any __NOINDEX__ tags on the page using $pOutput
660  $policy = $this->getRobotPolicy( 'view', $pOutput );
661  $outputPage->setIndexPolicy( $policy['index'] );
662  $outputPage->setFollowPolicy( $policy['follow'] );
663 
664  $this->showViewFooter();
665  $this->mPage->doViewUpdates( $user, $oldid );
666 
667  # Load the postEdit module if the user just saved this revision
668  # See also EditPage::setPostEditCookie
669  $request = $this->getContext()->getRequest();
671  $postEdit = $request->getCookie( $cookieKey );
672  if ( $postEdit ) {
673  # Clear the cookie. This also prevents caching of the response.
674  $request->response()->clearCookie( $cookieKey );
675  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
676  $outputPage->addModules( 'mediawiki.action.view.postEdit' );
677  }
678  }
679 
684  public function adjustDisplayTitle( ParserOutput $pOutput ) {
685  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
686  $titleText = $pOutput->getTitleText();
687  if ( strval( $titleText ) !== '' ) {
688  $this->getContext()->getOutput()->setPageTitle( $titleText );
689  }
690  }
691 
696  protected function showDiffPage() {
697  $request = $this->getContext()->getRequest();
698  $user = $this->getContext()->getUser();
699  $diff = $request->getVal( 'diff' );
700  $rcid = $request->getVal( 'rcid' );
701  $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
702  $purge = $request->getVal( 'action' ) == 'purge';
703  $unhide = $request->getInt( 'unhide' ) == 1;
704  $oldid = $this->getOldID();
705 
706  $rev = $this->getRevisionFetched();
707 
708  if ( !$rev ) {
709  $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
710  $msg = $this->getContext()->msg( 'difference-missing-revision' )
711  ->params( $oldid )
712  ->numParams( 1 )
713  ->parseAsBlock();
714  $this->getContext()->getOutput()->addHTML( $msg );
715  return;
716  }
717 
718  $contentHandler = $rev->getContentHandler();
719  $de = $contentHandler->createDifferenceEngine(
720  $this->getContext(),
721  $oldid,
722  $diff,
723  $rcid,
724  $purge,
725  $unhide
726  );
727 
728  // DifferenceEngine directly fetched the revision:
729  $this->mRevIdFetched = $de->mNewid;
730  $de->showDiffPage( $diffOnly );
731 
732  // Run view updates for the newer revision being diffed (and shown
733  // below the diff if not $diffOnly).
734  list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
735  // New can be false, convert it to 0 - this conveniently means the latest revision
736  $this->mPage->doViewUpdates( $user, (int)$new );
737  }
738 
746  public function getRobotPolicy( $action, $pOutput = null ) {
747  global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
748 
749  $ns = $this->getTitle()->getNamespace();
750 
751  # Don't index user and user talk pages for blocked users (T13443)
752  if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
753  $specificTarget = null;
754  $vagueTarget = null;
755  $titleText = $this->getTitle()->getText();
756  if ( IP::isValid( $titleText ) ) {
757  $vagueTarget = $titleText;
758  } else {
759  $specificTarget = $titleText;
760  }
761  if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
762  return [
763  'index' => 'noindex',
764  'follow' => 'nofollow'
765  ];
766  }
767  }
768 
769  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
770  # Non-articles (special pages etc), and old revisions
771  return [
772  'index' => 'noindex',
773  'follow' => 'nofollow'
774  ];
775  } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
776  # Discourage indexing of printable versions, but encourage following
777  return [
778  'index' => 'noindex',
779  'follow' => 'follow'
780  ];
781  } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
782  # For ?curid=x urls, disallow indexing
783  return [
784  'index' => 'noindex',
785  'follow' => 'follow'
786  ];
787  }
788 
789  # Otherwise, construct the policy based on the various config variables.
790  $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
791 
792  if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
793  # Honour customised robot policies for this namespace
794  $policy = array_merge(
795  $policy,
796  self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
797  );
798  }
799  if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
800  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
801  # a final sanity check that we have really got the parser output.
802  $policy = array_merge(
803  $policy,
804  [ 'index' => $pOutput->getIndexPolicy() ]
805  );
806  }
807 
808  if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
809  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
810  $policy = array_merge(
811  $policy,
812  self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
813  );
814  }
815 
816  return $policy;
817  }
818 
826  public static function formatRobotPolicy( $policy ) {
827  if ( is_array( $policy ) ) {
828  return $policy;
829  } elseif ( !$policy ) {
830  return [];
831  }
832 
833  $policy = explode( ',', $policy );
834  $policy = array_map( 'trim', $policy );
835 
836  $arr = [];
837  foreach ( $policy as $var ) {
838  if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
839  $arr['index'] = $var;
840  } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
841  $arr['follow'] = $var;
842  }
843  }
844 
845  return $arr;
846  }
847 
855  public function showRedirectedFromHeader() {
857 
858  $context = $this->getContext();
859  $outputPage = $context->getOutput();
860  $request = $context->getRequest();
861  $rdfrom = $request->getVal( 'rdfrom' );
862 
863  // Construct a URL for the current page view, but with the target title
864  $query = $request->getValues();
865  unset( $query['rdfrom'] );
866  unset( $query['title'] );
867  if ( $this->getTitle()->isRedirect() ) {
868  // Prevent double redirects
869  $query['redirect'] = 'no';
870  }
871  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
872 
873  if ( isset( $this->mRedirectedFrom ) ) {
874  // Avoid PHP 7.1 warning of passing $this by reference
875  $articlePage = $this;
876 
877  // This is an internally redirected page view.
878  // We'll need a backlink to the source page for navigation.
879  if ( Hooks::run( 'ArticleViewRedirect', [ &$articlePage ] ) ) {
880  $redir = Linker::linkKnown(
881  $this->mRedirectedFrom,
882  null,
883  [],
884  [ 'redirect' => 'no' ]
885  );
886 
887  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
888  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
889  . "</span>" );
890 
891  // Add the script to update the displayed URL and
892  // set the fragment if one was specified in the redirect
893  $outputPage->addJsConfigVars( [
894  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
895  ] );
896  $outputPage->addModules( 'mediawiki.action.view.redirect' );
897 
898  // Add a <link rel="canonical"> tag
899  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
900 
901  // Tell the output object that the user arrived at this article through a redirect
902  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
903 
904  return true;
905  }
906  } elseif ( $rdfrom ) {
907  // This is an externally redirected view, from some other wiki.
908  // If it was reported from a trusted site, supply a backlink.
909  if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
910  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
911  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
912  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
913  . "</span>" );
914 
915  // Add the script to update the displayed URL
916  $outputPage->addJsConfigVars( [
917  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
918  ] );
919  $outputPage->addModules( 'mediawiki.action.view.redirect' );
920 
921  return true;
922  }
923  }
924 
925  return false;
926  }
927 
932  public function showNamespaceHeader() {
933  if ( $this->getTitle()->isTalkPage() ) {
934  if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
935  $this->getContext()->getOutput()->wrapWikiMsg(
936  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
937  [ 'talkpageheader' ]
938  );
939  }
940  }
941  }
942 
946  public function showViewFooter() {
947  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
948  if ( $this->getTitle()->getNamespace() == NS_USER_TALK
949  && IP::isValid( $this->getTitle()->getText() )
950  ) {
951  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
952  }
953 
954  // Show a footer allowing the user to patrol the shown revision or page if possible
955  $patrolFooterShown = $this->showPatrolFooter();
956 
957  Hooks::run( 'ArticleViewFooter', [ $this, $patrolFooterShown ] );
958  }
959 
969  public function showPatrolFooter() {
970  global $wgUseNPPatrol, $wgUseRCPatrol, $wgUseFilePatrol, $wgEnableAPI, $wgEnableWriteAPI;
971 
972  $outputPage = $this->getContext()->getOutput();
973  $user = $this->getContext()->getUser();
974  $title = $this->getTitle();
975  $rc = false;
976 
977  if ( !$title->quickUserCan( 'patrol', $user )
978  || !( $wgUseRCPatrol || $wgUseNPPatrol
979  || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
980  ) {
981  // Patrolling is disabled or the user isn't allowed to
982  return false;
983  }
984 
985  if ( $this->mRevision
986  && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
987  ) {
988  // The current revision is already older than what could be in the RC table
989  // 6h tolerance because the RC might not be cleaned out regularly
990  return false;
991  }
992 
993  // Check for cached results
994  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
995  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
996  if ( $cache->get( $key ) ) {
997  return false;
998  }
999 
1000  $dbr = wfGetDB( DB_REPLICA );
1001  $oldestRevisionTimestamp = $dbr->selectField(
1002  'revision',
1003  'MIN( rev_timestamp )',
1004  [ 'rev_page' => $title->getArticleID() ],
1005  __METHOD__
1006  );
1007 
1008  // New page patrol: Get the timestamp of the oldest revison which
1009  // the revision table holds for the given page. Then we look
1010  // whether it's within the RC lifespan and if it is, we try
1011  // to get the recentchanges row belonging to that entry
1012  // (with rc_new = 1).
1013  $recentPageCreation = false;
1014  if ( $oldestRevisionTimestamp
1015  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1016  ) {
1017  // 6h tolerance because the RC might not be cleaned out regularly
1018  $recentPageCreation = true;
1020  [
1021  'rc_new' => 1,
1022  'rc_timestamp' => $oldestRevisionTimestamp,
1023  'rc_namespace' => $title->getNamespace(),
1024  'rc_cur_id' => $title->getArticleID()
1025  ],
1026  __METHOD__
1027  );
1028  if ( $rc ) {
1029  // Use generic patrol message for new pages
1030  $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1031  }
1032  }
1033 
1034  // File patrol: Get the timestamp of the latest upload for this page,
1035  // check whether it is within the RC lifespan and if it is, we try
1036  // to get the recentchanges row belonging to that entry
1037  // (with rc_type = RC_LOG, rc_log_type = upload).
1038  $recentFileUpload = false;
1039  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1040  && $title->getNamespace() === NS_FILE ) {
1041  // Retrieve timestamp of most recent upload
1042  $newestUploadTimestamp = $dbr->selectField(
1043  'image',
1044  'MAX( img_timestamp )',
1045  [ 'img_name' => $title->getDBkey() ],
1046  __METHOD__
1047  );
1048  if ( $newestUploadTimestamp
1049  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1050  ) {
1051  // 6h tolerance because the RC might not be cleaned out regularly
1052  $recentFileUpload = true;
1054  [
1055  'rc_type' => RC_LOG,
1056  'rc_log_type' => 'upload',
1057  'rc_timestamp' => $newestUploadTimestamp,
1058  'rc_namespace' => NS_FILE,
1059  'rc_cur_id' => $title->getArticleID()
1060  ],
1061  __METHOD__,
1062  [ 'USE INDEX' => 'rc_timestamp' ]
1063  );
1064  if ( $rc ) {
1065  // Use patrol message specific to files
1066  $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1067  }
1068  }
1069  }
1070 
1071  if ( !$recentPageCreation && !$recentFileUpload ) {
1072  // Page creation and latest upload (for files) is too old to be in RC
1073 
1074  // We definitely can't patrol so cache the information
1075  // When a new file version is uploaded, the cache is cleared
1076  $cache->set( $key, '1' );
1077 
1078  return false;
1079  }
1080 
1081  if ( !$rc ) {
1082  // Don't cache: This can be hit if the page gets accessed very fast after
1083  // its creation / latest upload or in case we have high replica DB lag. In case
1084  // the revision is too old, we will already return above.
1085  return false;
1086  }
1087 
1088  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1089  // Patrolled RC entry around
1090 
1091  // Cache the information we gathered above in case we can't patrol
1092  // Don't cache in case we can patrol as this could change
1093  $cache->set( $key, '1' );
1094 
1095  return false;
1096  }
1097 
1098  if ( $rc->getPerformer()->equals( $user ) ) {
1099  // Don't show a patrol link for own creations/uploads. If the user could
1100  // patrol them, they already would be patrolled
1101  return false;
1102  }
1103 
1104  $outputPage->preventClickjacking();
1105  if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) {
1106  $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
1107  }
1108 
1110  $title,
1111  $markPatrolledMsg->escaped(),
1112  [],
1113  [
1114  'action' => 'markpatrolled',
1115  'rcid' => $rc->getAttribute( 'rc_id' ),
1116  ]
1117  );
1118 
1119  $outputPage->addHTML(
1120  "<div class='patrollink' data-mw='interface'>" .
1121  wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1122  '</div>'
1123  );
1124 
1125  return true;
1126  }
1127 
1134  public static function purgePatrolFooterCache( $articleID ) {
1135  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1136  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1137  }
1138 
1143  public function showMissingArticle() {
1145 
1146  $outputPage = $this->getContext()->getOutput();
1147  // Whether the page is a root user page of an existing user (but not a subpage)
1148  $validUserPage = false;
1149 
1150  $title = $this->getTitle();
1151 
1152  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1153  if ( $title->getNamespace() == NS_USER
1154  || $title->getNamespace() == NS_USER_TALK
1155  ) {
1156  $rootPart = explode( '/', $title->getText() )[0];
1157  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1158  $ip = User::isIP( $rootPart );
1159  $block = Block::newFromTarget( $user, $user );
1160 
1161  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1162  $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1163  [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1164  } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
1165  # Show log extract if the user is currently blocked
1167  $outputPage,
1168  'block',
1169  MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
1170  '',
1171  [
1172  'lim' => 1,
1173  'showIfEmpty' => false,
1174  'msgKey' => [
1175  'blocked-notice-logextract',
1176  $user->getName() # Support GENDER in notice
1177  ]
1178  ]
1179  );
1180  $validUserPage = !$title->isSubpage();
1181  } else {
1182  $validUserPage = !$title->isSubpage();
1183  }
1184  }
1185 
1186  Hooks::run( 'ShowMissingArticle', [ $this ] );
1187 
1188  # Show delete and move logs if there were any such events.
1189  # The logging query can DOS the site when bots/crawlers cause 404 floods,
1190  # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1191  $cache = MediaWikiServices::getInstance()->getMainObjectStash();
1192  $key = $cache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1193  $loggedIn = $this->getContext()->getUser()->isLoggedIn();
1194  $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
1195  if ( $loggedIn || $cache->get( $key ) || $sessionExists ) {
1196  $logTypes = [ 'delete', 'move', 'protect' ];
1197 
1198  $dbr = wfGetDB( DB_REPLICA );
1199 
1200  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1201  // Give extensions a chance to hide their (unrelated) log entries
1202  Hooks::run( 'Article::MissingArticleConditions', [ &$conds, $logTypes ] );
1204  $outputPage,
1205  $logTypes,
1206  $title,
1207  '',
1208  [
1209  'lim' => 10,
1210  'conds' => $conds,
1211  'showIfEmpty' => false,
1212  'msgKey' => [ $loggedIn || $sessionExists
1213  ? 'moveddeleted-notice'
1214  : 'moveddeleted-notice-recent'
1215  ]
1216  ]
1217  );
1218  }
1219 
1220  if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1221  // If there's no backing content, send a 404 Not Found
1222  // for better machine handling of broken links.
1223  $this->getContext()->getRequest()->response()->statusHeader( 404 );
1224  }
1225 
1226  // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1227  $policy = $this->getRobotPolicy( 'view' );
1228  $outputPage->setIndexPolicy( $policy['index'] );
1229  $outputPage->setFollowPolicy( $policy['follow'] );
1230 
1231  $hookResult = Hooks::run( 'BeforeDisplayNoArticleText', [ $this ] );
1232 
1233  if ( !$hookResult ) {
1234  return;
1235  }
1236 
1237  # Show error message
1238  $oldid = $this->getOldID();
1239  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1240  $outputPage->addParserOutput( $this->getContentObject()->getParserOutput( $title ) );
1241  } else {
1242  if ( $oldid ) {
1243  $text = wfMessage( 'missing-revision', $oldid )->plain();
1244  } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
1245  && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
1246  ) {
1247  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1248  $text = wfMessage( $message )->plain();
1249  } else {
1250  $text = wfMessage( 'noarticletext-nopermission' )->plain();
1251  }
1252 
1253  $dir = $this->getContext()->getLanguage()->getDir();
1254  $lang = $this->getContext()->getLanguage()->getCode();
1255  $outputPage->addWikiText( Xml::openElement( 'div', [
1256  'class' => "noarticletext mw-content-$dir",
1257  'dir' => $dir,
1258  'lang' => $lang,
1259  ] ) . "\n$text\n</div>" );
1260  }
1261  }
1262 
1269  public function showDeletedRevisionHeader() {
1270  if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
1271  // Not deleted
1272  return true;
1273  }
1274 
1275  $outputPage = $this->getContext()->getOutput();
1276  $user = $this->getContext()->getUser();
1277  // If the user is not allowed to see it...
1278  if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
1279  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1280  'rev-deleted-text-permission' );
1281 
1282  return false;
1283  // If the user needs to confirm that they want to see it...
1284  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1285  # Give explanation and add a link to view the revision...
1286  $oldid = intval( $this->getOldID() );
1287  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1288  $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1289  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1290  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1291  [ $msg, $link ] );
1292 
1293  return false;
1294  // We are allowed to see...
1295  } else {
1296  $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1297  'rev-suppressed-text-view' : 'rev-deleted-text-view';
1298  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1299 
1300  return true;
1301  }
1302  }
1303 
1312  public function setOldSubtitle( $oldid = 0 ) {
1313  // Avoid PHP 7.1 warning of passing $this by reference
1314  $articlePage = $this;
1315 
1316  if ( !Hooks::run( 'DisplayOldSubtitle', [ &$articlePage, &$oldid ] ) ) {
1317  return;
1318  }
1319 
1320  $context = $this->getContext();
1321  $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1322 
1323  # Cascade unhide param in links for easy deletion browsing
1324  $extraParams = [];
1325  if ( $unhide ) {
1326  $extraParams['unhide'] = 1;
1327  }
1328 
1329  if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1330  $revision = $this->mRevision;
1331  } else {
1332  $revision = Revision::newFromId( $oldid );
1333  }
1334 
1335  $timestamp = $revision->getTimestamp();
1336 
1337  $current = ( $oldid == $this->mPage->getLatest() );
1338  $language = $context->getLanguage();
1339  $user = $context->getUser();
1340 
1341  $td = $language->userTimeAndDate( $timestamp, $user );
1342  $tddate = $language->userDate( $timestamp, $user );
1343  $tdtime = $language->userTime( $timestamp, $user );
1344 
1345  # Show user links if allowed to see them. If hidden, then show them only if requested...
1346  $userlinks = Linker::revUserTools( $revision, !$unhide );
1347 
1348  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1349  ? 'revision-info-current'
1350  : 'revision-info';
1351 
1352  $outputPage = $context->getOutput();
1353  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1354  $context->msg( $infomsg, $td )
1355  ->rawParams( $userlinks )
1356  ->params( $revision->getId(), $tddate, $tdtime, $revision->getUserText() )
1357  ->rawParams( Linker::revComment( $revision, true, true ) )
1358  ->parse() .
1359  "</div>";
1360 
1361  $lnk = $current
1362  ? $context->msg( 'currentrevisionlink' )->escaped()
1364  $this->getTitle(),
1365  $context->msg( 'currentrevisionlink' )->escaped(),
1366  [],
1367  $extraParams
1368  );
1369  $curdiff = $current
1370  ? $context->msg( 'diff' )->escaped()
1372  $this->getTitle(),
1373  $context->msg( 'diff' )->escaped(),
1374  [],
1375  [
1376  'diff' => 'cur',
1377  'oldid' => $oldid
1378  ] + $extraParams
1379  );
1380  $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
1381  $prevlink = $prev
1383  $this->getTitle(),
1384  $context->msg( 'previousrevision' )->escaped(),
1385  [],
1386  [
1387  'direction' => 'prev',
1388  'oldid' => $oldid
1389  ] + $extraParams
1390  )
1391  : $context->msg( 'previousrevision' )->escaped();
1392  $prevdiff = $prev
1394  $this->getTitle(),
1395  $context->msg( 'diff' )->escaped(),
1396  [],
1397  [
1398  'diff' => 'prev',
1399  'oldid' => $oldid
1400  ] + $extraParams
1401  )
1402  : $context->msg( 'diff' )->escaped();
1403  $nextlink = $current
1404  ? $context->msg( 'nextrevision' )->escaped()
1406  $this->getTitle(),
1407  $context->msg( 'nextrevision' )->escaped(),
1408  [],
1409  [
1410  'direction' => 'next',
1411  'oldid' => $oldid
1412  ] + $extraParams
1413  );
1414  $nextdiff = $current
1415  ? $context->msg( 'diff' )->escaped()
1417  $this->getTitle(),
1418  $context->msg( 'diff' )->escaped(),
1419  [],
1420  [
1421  'diff' => 'next',
1422  'oldid' => $oldid
1423  ] + $extraParams
1424  );
1425 
1426  $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
1427  if ( $cdel !== '' ) {
1428  $cdel .= ' ';
1429  }
1430 
1431  // the outer div is need for styling the revision info and nav in MobileFrontend
1432  $outputPage->addSubtitle( "<div class=\"mw-revision\">" . $revisionInfo .
1433  "<div id=\"mw-revision-nav\">" . $cdel .
1434  $context->msg( 'revision-nav' )->rawParams(
1435  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1436  )->escaped() . "</div></div>" );
1437  }
1438 
1452  public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1453  $lang = $this->getTitle()->getPageLanguage();
1454  $out = $this->getContext()->getOutput();
1455  if ( $appendSubtitle ) {
1456  $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1457  }
1458  $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1459  return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1460  }
1461 
1474  public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1475  if ( !is_array( $target ) ) {
1476  $target = [ $target ];
1477  }
1478 
1479  $html = '<ul class="redirectText">';
1481  foreach ( $target as $title ) {
1482  $html .= '<li>' . Linker::link(
1483  $title,
1484  htmlspecialchars( $title->getFullText() ),
1485  [],
1486  // Make sure wiki page redirects are not followed
1487  $title->isRedirect() ? [ 'redirect' => 'no' ] : [],
1488  ( $forceKnown ? [ 'known', 'noclasses' ] : [] )
1489  ) . '</li>';
1490  }
1491  $html .= '</ul>';
1492 
1493  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1494 
1495  return '<div class="redirectMsg">' .
1496  '<p>' . $redirectToText . '</p>' .
1497  $html .
1498  '</div>';
1499  }
1500 
1509  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1510  $msg = wfMessage(
1511  'namespace-' . $this->getTitle()->getNamespace() . '-helppage'
1512  );
1513 
1514  $out = $this->getContext()->getOutput();
1515  if ( !$msg->isDisabled() ) {
1516  $helpUrl = Skin::makeUrl( $msg->plain() );
1517  $out->addHelpLink( $helpUrl, true );
1518  } else {
1519  $out->addHelpLink( $to, $overrideBaseUrl );
1520  }
1521  }
1522 
1526  public function render() {
1527  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1528  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1529  $this->getContext()->getOutput()->enableSectionEditLinks( false );
1530  $this->disableSectionEditForRender = true;
1531  $this->view();
1532  }
1533 
1537  public function protect() {
1538  $form = new ProtectionForm( $this );
1539  $form->execute();
1540  }
1541 
1545  public function unprotect() {
1546  $this->protect();
1547  }
1548 
1552  public function delete() {
1553  # This code desperately needs to be totally rewritten
1554 
1555  $title = $this->getTitle();
1556  $context = $this->getContext();
1557  $user = $context->getUser();
1558  $request = $context->getRequest();
1559 
1560  # Check permissions
1561  $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
1562  if ( count( $permissionErrors ) ) {
1563  throw new PermissionsError( 'delete', $permissionErrors );
1564  }
1565 
1566  # Read-only check...
1567  if ( wfReadOnly() ) {
1568  throw new ReadOnlyError;
1569  }
1570 
1571  # Better double-check that it hasn't been deleted yet!
1572  $this->mPage->loadPageData(
1573  $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1574  );
1575  if ( !$this->mPage->exists() ) {
1576  $deleteLogPage = new LogPage( 'delete' );
1577  $outputPage = $context->getOutput();
1578  $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1579  $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1580  [ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1581  );
1582  $outputPage->addHTML(
1583  Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1584  );
1586  $outputPage,
1587  'delete',
1588  $title
1589  );
1590 
1591  return;
1592  }
1593 
1594  $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1595  $deleteReason = $request->getText( 'wpReason' );
1596 
1597  if ( $deleteReasonList == 'other' ) {
1598  $reason = $deleteReason;
1599  } elseif ( $deleteReason != '' ) {
1600  // Entry from drop down menu + additional comment
1601  $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1602  $reason = $deleteReasonList . $colonseparator . $deleteReason;
1603  } else {
1604  $reason = $deleteReasonList;
1605  }
1606 
1607  if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1608  [ 'delete', $this->getTitle()->getPrefixedText() ] )
1609  ) {
1610  # Flag to hide all contents of the archived revisions
1611  $suppress = $request->getCheck( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
1612 
1613  $this->doDelete( $reason, $suppress );
1614 
1615  WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1616 
1617  return;
1618  }
1619 
1620  // Generate deletion reason
1621  $hasHistory = false;
1622  if ( !$reason ) {
1623  try {
1624  $reason = $this->generateReason( $hasHistory );
1625  } catch ( Exception $e ) {
1626  # if a page is horribly broken, we still want to be able to
1627  # delete it. So be lenient about errors here.
1628  wfDebug( "Error while building auto delete summary: $e" );
1629  $reason = '';
1630  }
1631  }
1632 
1633  // If the page has a history, insert a warning
1634  if ( $hasHistory ) {
1635  $title = $this->getTitle();
1636 
1637  // The following can use the real revision count as this is only being shown for users
1638  // that can delete this page.
1639  // This, as a side-effect, also makes sure that the following query isn't being run for
1640  // pages with a larger history, unless the user has the 'bigdelete' right
1641  // (and is about to delete this page).
1642  $dbr = wfGetDB( DB_REPLICA );
1643  $revisions = $edits = (int)$dbr->selectField(
1644  'revision',
1645  'COUNT(rev_page)',
1646  [ 'rev_page' => $title->getArticleID() ],
1647  __METHOD__
1648  );
1649 
1650  // @todo FIXME: i18n issue/patchwork message
1651  $context->getOutput()->addHTML(
1652  '<strong class="mw-delete-warning-revisions">' .
1653  $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1654  $context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
1655  $context->msg( 'history' )->escaped(),
1656  [],
1657  [ 'action' => 'history' ] ) .
1658  '</strong>'
1659  );
1660 
1661  if ( $title->isBigDeletion() ) {
1662  global $wgDeleteRevisionsLimit;
1663  $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1664  [
1665  'delete-warning-toobig',
1666  $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1667  ]
1668  );
1669  }
1670  }
1671 
1672  $this->confirmDelete( $reason );
1673  }
1674 
1680  public function confirmDelete( $reason ) {
1681  wfDebug( "Article::confirmDelete\n" );
1682 
1683  $title = $this->getTitle();
1684  $ctx = $this->getContext();
1685  $outputPage = $ctx->getOutput();
1686  $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1687  $outputPage->addBacklinkSubtitle( $title );
1688  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1689 
1690  $backlinkCache = $title->getBacklinkCache();
1691  if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
1692  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1693  'deleting-backlinks-warning' );
1694  }
1695 
1696  $subpageQueryLimit = 51;
1697  $subpages = $title->getSubpages( $subpageQueryLimit );
1698  $subpageCount = count( $subpages );
1699  if ( $subpageCount > 0 ) {
1700  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1701  [ 'deleting-subpages-warning', Message::numParam( $subpageCount ) ] );
1702  }
1703  $outputPage->addWikiMsg( 'confirmdeletetext' );
1704 
1705  Hooks::run( 'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
1706 
1707  $user = $this->getContext()->getUser();
1708  $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
1709 
1710  $outputPage->enableOOUI();
1711 
1713  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text(),
1714  [ 'other' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text() ]
1715  );
1716  $options = Xml::listDropDownOptionsOoui( $options );
1717 
1718  $fields[] = new OOUI\FieldLayout(
1719  new OOUI\DropdownInputWidget( [
1720  'name' => 'wpDeleteReasonList',
1721  'inputId' => 'wpDeleteReasonList',
1722  'tabIndex' => 1,
1723  'infusable' => true,
1724  'value' => '',
1725  'options' => $options
1726  ] ),
1727  [
1728  'label' => $ctx->msg( 'deletecomment' )->text(),
1729  'align' => 'top',
1730  ]
1731  );
1732 
1733  $fields[] = new OOUI\FieldLayout(
1734  new OOUI\TextInputWidget( [
1735  'name' => 'wpReason',
1736  'inputId' => 'wpReason',
1737  'tabIndex' => 2,
1738  'maxLength' => 255,
1739  'infusable' => true,
1740  'value' => $reason,
1741  'autofocus' => true,
1742  ] ),
1743  [
1744  'label' => $ctx->msg( 'deleteotherreason' )->text(),
1745  'align' => 'top',
1746  ]
1747  );
1748 
1749  if ( $user->isLoggedIn() ) {
1750  $fields[] = new OOUI\FieldLayout(
1751  new OOUI\CheckboxInputWidget( [
1752  'name' => 'wpWatch',
1753  'inputId' => 'wpWatch',
1754  'tabIndex' => 3,
1755  'selected' => $checkWatch,
1756  ] ),
1757  [
1758  'label' => $ctx->msg( 'watchthis' )->text(),
1759  'align' => 'inline',
1760  'infusable' => true,
1761  ]
1762  );
1763  }
1764 
1765  if ( $user->isAllowed( 'suppressrevision' ) ) {
1766  $fields[] = new OOUI\FieldLayout(
1767  new OOUI\CheckboxInputWidget( [
1768  'name' => 'wpSuppress',
1769  'inputId' => 'wpSuppress',
1770  'tabIndex' => 4,
1771  ] ),
1772  [
1773  'label' => $ctx->msg( 'revdelete-suppress' )->text(),
1774  'align' => 'inline',
1775  'infusable' => true,
1776  ]
1777  );
1778  }
1779 
1780  $fields[] = new OOUI\FieldLayout(
1781  new OOUI\ButtonInputWidget( [
1782  'name' => 'wpConfirmB',
1783  'inputId' => 'wpConfirmB',
1784  'tabIndex' => 5,
1785  'value' => $ctx->msg( 'deletepage' )->text(),
1786  'label' => $ctx->msg( 'deletepage' )->text(),
1787  'flags' => [ 'primary', 'destructive' ],
1788  'type' => 'submit',
1789  ] ),
1790  [
1791  'align' => 'top',
1792  ]
1793  );
1794 
1795  $fieldset = new OOUI\FieldsetLayout( [
1796  'label' => $ctx->msg( 'delete-legend' )->text(),
1797  'id' => 'mw-delete-table',
1798  'items' => $fields,
1799  ] );
1800 
1801  $form = new OOUI\FormLayout( [
1802  'method' => 'post',
1803  'action' => $title->getLocalURL( 'action=delete' ),
1804  'id' => 'deleteconfirm',
1805  ] );
1806  $form->appendContent(
1807  $fieldset,
1808  new OOUI\HtmlSnippet(
1809  Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
1810  )
1811  );
1812 
1813  $outputPage->addHTML(
1814  new OOUI\PanelLayout( [
1815  'classes' => [ 'deletepage-wrapper' ],
1816  'expanded' => false,
1817  'padded' => true,
1818  'framed' => true,
1819  'content' => $form,
1820  ] )
1821  );
1822 
1823  if ( $user->isAllowed( 'editinterface' ) ) {
1825  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
1826  wfMessage( 'delete-edit-reasonlist' )->escaped(),
1827  [],
1828  [ 'action' => 'edit' ]
1829  );
1830  $outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
1831  }
1832 
1833  $deleteLogPage = new LogPage( 'delete' );
1834  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1835  LogEventsList::showLogExtract( $outputPage, 'delete', $title );
1836  }
1837 
1843  public function doDelete( $reason, $suppress = false ) {
1844  $error = '';
1845  $context = $this->getContext();
1846  $outputPage = $context->getOutput();
1847  $user = $context->getUser();
1848  $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error, $user );
1849 
1850  if ( $status->isGood() ) {
1851  $deleted = $this->getTitle()->getPrefixedText();
1852 
1853  $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
1854  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1855 
1856  $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
1857 
1858  $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
1859 
1860  Hooks::run( 'ArticleDeleteAfterSuccess', [ $this->getTitle(), $outputPage ] );
1861 
1862  $outputPage->returnToMain( false );
1863  } else {
1864  $outputPage->setPageTitle(
1865  wfMessage( 'cannotdelete-title',
1866  $this->getTitle()->getPrefixedText() )
1867  );
1868 
1869  if ( $error == '' ) {
1870  $outputPage->addWikiText(
1871  "<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>"
1872  );
1873  $deleteLogPage = new LogPage( 'delete' );
1874  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1875 
1877  $outputPage,
1878  'delete',
1879  $this->getTitle()
1880  );
1881  } else {
1882  $outputPage->addHTML( $error );
1883  }
1884  }
1885  }
1886 
1887  /* Caching functions */
1888 
1896  protected function tryFileCache() {
1897  static $called = false;
1898 
1899  if ( $called ) {
1900  wfDebug( "Article::tryFileCache(): called twice!?\n" );
1901  return false;
1902  }
1903 
1904  $called = true;
1905  if ( $this->isFileCacheable() ) {
1906  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
1907  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1908  wfDebug( "Article::tryFileCache(): about to load file\n" );
1909  $cache->loadFromFileCache( $this->getContext() );
1910  return true;
1911  } else {
1912  wfDebug( "Article::tryFileCache(): starting buffer\n" );
1913  ob_start( [ &$cache, 'saveToFileCache' ] );
1914  }
1915  } else {
1916  wfDebug( "Article::tryFileCache(): not cacheable\n" );
1917  }
1918 
1919  return false;
1920  }
1921 
1927  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
1928  $cacheable = false;
1929 
1930  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
1931  $cacheable = $this->mPage->getId()
1932  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1933  // Extension may have reason to disable file caching on some pages.
1934  if ( $cacheable ) {
1935  // Avoid PHP 7.1 warning of passing $this by reference
1936  $articlePage = $this;
1937  $cacheable = Hooks::run( 'IsFileCacheable', [ &$articlePage ] );
1938  }
1939  }
1940 
1941  return $cacheable;
1942  }
1943 
1957  public function getParserOutput( $oldid = null, User $user = null ) {
1958  // XXX: bypasses mParserOptions and thus setParserOptions()
1959 
1960  if ( $user === null ) {
1961  $parserOptions = $this->getParserOptions();
1962  } else {
1963  $parserOptions = $this->mPage->makeParserOptions( $user );
1964  }
1965 
1966  return $this->mPage->getParserOutput( $parserOptions, $oldid );
1967  }
1968 
1976  if ( $this->mParserOptions ) {
1977  throw new MWException( "can't change parser options after they have already been set" );
1978  }
1979 
1980  // clone, so if $options is modified later, it doesn't confuse the parser cache.
1981  $this->mParserOptions = clone $options;
1982  }
1983 
1988  public function getParserOptions() {
1989  if ( !$this->mParserOptions ) {
1990  $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
1991  }
1992  // Clone to allow modifications of the return value without affecting cache
1993  return clone $this->mParserOptions;
1994  }
1995 
2002  public function setContext( $context ) {
2003  $this->mContext = $context;
2004  }
2005 
2012  public function getContext() {
2013  if ( $this->mContext instanceof IContextSource ) {
2014  return $this->mContext;
2015  } else {
2016  wfDebug( __METHOD__ . " called and \$mContext is null. " .
2017  "Return RequestContext::getMain(); for sanity\n" );
2018  return RequestContext::getMain();
2019  }
2020  }
2021 
2029  public function __get( $fname ) {
2030  if ( property_exists( $this->mPage, $fname ) ) {
2031  # wfWarn( "Access to raw $fname field " . __CLASS__ );
2032  return $this->mPage->$fname;
2033  }
2034  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2035  }
2036 
2044  public function __set( $fname, $fvalue ) {
2045  if ( property_exists( $this->mPage, $fname ) ) {
2046  # wfWarn( "Access to raw $fname field of " . __CLASS__ );
2047  $this->mPage->$fname = $fvalue;
2048  // Note: extensions may want to toss on new fields
2049  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2050  $this->mPage->$fname = $fvalue;
2051  } else {
2052  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2053  }
2054  }
2055 
2060  public function checkFlags( $flags ) {
2061  return $this->mPage->checkFlags( $flags );
2062  }
2063 
2068  public function checkTouched() {
2069  return $this->mPage->checkTouched();
2070  }
2071 
2076  public function clearPreparedEdit() {
2077  $this->mPage->clearPreparedEdit();
2078  }
2079 
2084  public function doDeleteArticleReal(
2085  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
2086  $tags = []
2087  ) {
2088  return $this->mPage->doDeleteArticleReal(
2089  $reason, $suppress, $u1, $u2, $error, $user, $tags
2090  );
2091  }
2092 
2097  public function doDeleteUpdates( $id, Content $content = null ) {
2098  return $this->mPage->doDeleteUpdates( $id, $content );
2099  }
2100 
2106  public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
2107  User $user = null, $serialFormat = null
2108  ) {
2109  wfDeprecated( __METHOD__, '1.29' );
2110  return $this->mPage->doEditContent( $content, $summary, $flags, $baseRevId,
2111  $user, $serialFormat
2112  );
2113  }
2114 
2119  public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2120  return $this->mPage->doEditUpdates( $revision, $user, $options );
2121  }
2122 
2129  public function doPurge() {
2130  return $this->mPage->doPurge();
2131  }
2132 
2137  public function doViewUpdates( User $user, $oldid = 0 ) {
2138  $this->mPage->doViewUpdates( $user, $oldid );
2139  }
2140 
2145  public function exists() {
2146  return $this->mPage->exists();
2147  }
2148 
2153  public function followRedirect() {
2154  return $this->mPage->followRedirect();
2155  }
2156 
2161  public function getActionOverrides() {
2162  return $this->mPage->getActionOverrides();
2163  }
2164 
2169  public function getAutoDeleteReason( &$hasHistory ) {
2170  return $this->mPage->getAutoDeleteReason( $hasHistory );
2171  }
2172 
2177  public function getCategories() {
2178  return $this->mPage->getCategories();
2179  }
2180 
2185  public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2186  return $this->mPage->getComment( $audience, $user );
2187  }
2188 
2193  public function getContentHandler() {
2194  return $this->mPage->getContentHandler();
2195  }
2196 
2201  public function getContentModel() {
2202  return $this->mPage->getContentModel();
2203  }
2204 
2209  public function getContributors() {
2210  return $this->mPage->getContributors();
2211  }
2212 
2217  public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2218  return $this->mPage->getCreator( $audience, $user );
2219  }
2220 
2225  public function getDeletionUpdates( Content $content = null ) {
2226  return $this->mPage->getDeletionUpdates( $content );
2227  }
2228 
2233  public function getHiddenCategories() {
2234  return $this->mPage->getHiddenCategories();
2235  }
2236 
2241  public function getId() {
2242  return $this->mPage->getId();
2243  }
2244 
2249  public function getLatest() {
2250  return $this->mPage->getLatest();
2251  }
2252 
2257  public function getLinksTimestamp() {
2258  return $this->mPage->getLinksTimestamp();
2259  }
2260 
2265  public function getMinorEdit() {
2266  return $this->mPage->getMinorEdit();
2267  }
2268 
2273  public function getOldestRevision() {
2274  return $this->mPage->getOldestRevision();
2275  }
2276 
2281  public function getRedirectTarget() {
2282  return $this->mPage->getRedirectTarget();
2283  }
2284 
2289  public function getRedirectURL( $rt ) {
2290  return $this->mPage->getRedirectURL( $rt );
2291  }
2292 
2297  public function getRevision() {
2298  return $this->mPage->getRevision();
2299  }
2300 
2305  public function getTimestamp() {
2306  return $this->mPage->getTimestamp();
2307  }
2308 
2313  public function getTouched() {
2314  return $this->mPage->getTouched();
2315  }
2316 
2321  public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2322  return $this->mPage->getUndoContent( $undo, $undoafter );
2323  }
2324 
2329  public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2330  return $this->mPage->getUser( $audience, $user );
2331  }
2332 
2337  public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2338  return $this->mPage->getUserText( $audience, $user );
2339  }
2340 
2345  public function hasViewableContent() {
2346  return $this->mPage->hasViewableContent();
2347  }
2348 
2353  public function insertOn( $dbw, $pageId = null ) {
2354  return $this->mPage->insertOn( $dbw, $pageId );
2355  }
2356 
2361  public function insertProtectNullRevision( $revCommentMsg, array $limit,
2362  array $expiry, $cascade, $reason, $user = null
2363  ) {
2364  return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
2365  $expiry, $cascade, $reason, $user
2366  );
2367  }
2368 
2373  public function insertRedirect() {
2374  return $this->mPage->insertRedirect();
2375  }
2376 
2381  public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2382  return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2383  }
2384 
2389  public function isCountable( $editInfo = false ) {
2390  return $this->mPage->isCountable( $editInfo );
2391  }
2392 
2397  public function isRedirect() {
2398  return $this->mPage->isRedirect();
2399  }
2400 
2405  public function loadFromRow( $data, $from ) {
2406  return $this->mPage->loadFromRow( $data, $from );
2407  }
2408 
2413  public function loadPageData( $from = 'fromdb' ) {
2414  $this->mPage->loadPageData( $from );
2415  }
2416 
2421  public function lockAndGetLatest() {
2422  return $this->mPage->lockAndGetLatest();
2423  }
2424 
2429  public function makeParserOptions( $context ) {
2430  return $this->mPage->makeParserOptions( $context );
2431  }
2432 
2437  public function pageDataFromId( $dbr, $id, $options = [] ) {
2438  return $this->mPage->pageDataFromId( $dbr, $id, $options );
2439  }
2440 
2445  public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2446  return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2447  }
2448 
2453  public function prepareContentForEdit(
2454  Content $content, $revision = null, User $user = null,
2455  $serialFormat = null, $useCache = true
2456  ) {
2457  return $this->mPage->prepareContentForEdit(
2458  $content, $revision, $user,
2459  $serialFormat, $useCache
2460  );
2461  }
2462 
2467  public function protectDescription( array $limit, array $expiry ) {
2468  return $this->mPage->protectDescription( $limit, $expiry );
2469  }
2470 
2475  public function protectDescriptionLog( array $limit, array $expiry ) {
2476  return $this->mPage->protectDescriptionLog( $limit, $expiry );
2477  }
2478 
2483  public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2484  $sectionTitle = '', $baseRevId = null
2485  ) {
2486  return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2487  $sectionTitle, $baseRevId
2488  );
2489  }
2490 
2495  public function replaceSectionContent(
2496  $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2497  ) {
2498  return $this->mPage->replaceSectionContent(
2499  $sectionId, $sectionContent, $sectionTitle, $edittime
2500  );
2501  }
2502 
2507  public function setTimestamp( $ts ) {
2508  return $this->mPage->setTimestamp( $ts );
2509  }
2510 
2515  public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2516  return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2517  }
2518 
2523  public function supportsSections() {
2524  return $this->mPage->supportsSections();
2525  }
2526 
2531  public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2532  return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2533  }
2534 
2539  public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
2540  return $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2541  }
2542 
2547  public function updateIfNewerOn( $dbw, $revision ) {
2548  return $this->mPage->updateIfNewerOn( $dbw, $revision );
2549  }
2550 
2555  public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
2556  return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null );
2557  }
2558 
2563  public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
2564  $lastRevIsRedirect = null
2565  ) {
2566  return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
2567  $lastRevIsRedirect
2568  );
2569  }
2570 
2579  public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
2580  $reason, User $user
2581  ) {
2582  return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2583  }
2584 
2592  public function updateRestrictions( $limit = [], $reason = '',
2593  &$cascade = 0, $expiry = []
2594  ) {
2595  return $this->mPage->doUpdateRestrictions(
2596  $limit,
2597  $expiry,
2598  $cascade,
2599  $reason,
2600  $this->getContext()->getUser()
2601  );
2602  }
2603 
2612  public function doDeleteArticle(
2613  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = ''
2614  ) {
2615  return $this->mPage->doDeleteArticle( $reason, $suppress, $u1, $u2, $error );
2616  }
2617 
2627  public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
2628  $user = is_null( $user ) ? $this->getContext()->getUser() : $user;
2629  return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2630  }
2631 
2640  public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
2641  $guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser;
2642  return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2643  }
2644 
2649  public function generateReason( &$hasHistory ) {
2650  $title = $this->mPage->getTitle();
2652  return $handler->getAutoDeleteReason( $title, $hasHistory );
2653  }
2654 
2655  // ******
2656 }
pageDataFromId($dbr, $id, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2437
__set($fname, $fvalue)
Use PHP's magic __set handler to handle setting of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2044
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:551
getRedirectTarget()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2281
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function array $article
Definition: hooks.txt:77
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:1134
const FOR_THIS_USER
Definition: Revision.php:55
viewRedirect($target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1452
static newFromID($id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:416
lockAndGetLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2421
getUndoContent(Revision $undo, Revision $undoafter=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2321
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:1973
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
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...
static revComment(Revision $rev, $local=false, $isPublic=false)
Wrap and format the given revision's comment block, if the current user is allowed to view it...
Definition: Linker.php:1480
bool $disableSectionEditForRender
Whether render() was called.
Definition: Article.php:83
WikiPage $mPage
The WikiPage object of this instance.
Definition: Article.php:40
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:784
getRobotPolicy($action, $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:746
the array() calling protocol came about after MediaWiki 1.4rc1.
getRedirectedFrom()
Get the page this view was redirected from.
Definition: Article.php:163
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:1584
updateRestrictions($limit=[], $reason= '', &$cascade=0, $expiry=[])
Definition: Article.php:2592
doEditContent(Content $content, $summary, $flags=0, $baseRevId=false, User $user=null, $serialFormat=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2106
getLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2249
setParserOptions(ParserOptions $options)
Override the ParserOptions used to render the primary article wikitext.
Definition: Article.php:1975
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2563
doEditUpdates(Revision $revision, User $user, array $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2119
static getRevDeleteLink(User $user, Revision $rev, Title $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2051
static element($element, $attribs=null, $contents= '', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
exists()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2145
supportsSections()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2523
static makeUrl($name, $urlaction= '')
Definition: Skin.php:1162
doRollback($fromP, $summary, $token, $bot, &$resultDetails, User $user=null)
Definition: Article.php:2627
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
doDeleteArticle($reason, $suppress=false, $u1=null, $u2=null, &$error= '')
Definition: Article.php:2612
protect()
action=protect handler
Definition: Article.php:1537
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2149
IContextSource $mContext
The context this Article is executed in.
Definition: Article.php:37
isCountable($editInfo=false)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2389
Wrapper allowing us to handle a system message as a Content object.
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:191
getParserOutput($oldid=null, User $user=null)
#@-
Definition: Article.php:1957
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:2539
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:67
loadPageData($from= 'fromdb')
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2413
if(!isset($args[0])) $lang
ParserOptions $mParserOptions
ParserOptions object for $wgUser articles.
Definition: Article.php:43
Content $mContentObject
Content of the revision we are working on.
Definition: Article.php:55
Special handling for category description pages, showing pages, subcategories and file that belong to...
isFileCacheable($mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition: Article.php:1927
doDeleteUpdates($id, Content $content=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2097
static newFromConds($conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition: Article.php:684
static hidden($name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:771
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2515
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user)
Definition: Article.php:2579
Class for viewing MediaWiki article and history.
Definition: Article.php:35
null for the local wiki Added in
Definition: hooks.txt:1584
Page view caching in the file system.
followRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2153
confirmDelete($reason)
Output deletion confirmation dialog.
Definition: Article.php:1680
Class for viewing MediaWiki file description pages.
Definition: ImagePage.php:30
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2531
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:262
__get($fname)
Use PHP's magic __get handler to handle accessing of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2029
getDeletionUpdates(Content $content=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2225
updateIfNewerOn($dbw, $revision)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2547
clearPreparedEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2076
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
static errorBox($html, $heading= '')
Return an error box.
Definition: Html.php:710
getContributors()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2209
commitRollback($fromP, $summary, $bot, &$resultDetails, User $guser=null)
Definition: Article.php:2640
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1143
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
static showLogExtract(&$out, $types=[], $page= '', $user= '', $param=[])
Show log extract.
protectDescription(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2467
Class to simplify the use of log pages.
Definition: LogPage.php:31
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2997
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:52
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2012
static listDropDownOptionsOoui($options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc...
Definition: Xml.php:582
isRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2397
__construct(Title $title, $oldId=null)
Constructor and clear the article.
Definition: Article.php:90
setTimestamp($ts)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2507
protectDescriptionLog(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2475
makeParserOptions($context)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2429
wfEscapeWikiText($text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
wfReadOnly()
Check whether the wiki is in read-only mode.
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:2453
const FOR_PUBLIC
Definition: Revision.php:54
static getCanonicalName($index)
Returns the canonical (English) name for a given index.
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites...
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 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 unsetoffset-wrap String Wrap the message in html(usually something like"&lt
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:946
static newFromTarget($specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing Block object if possible.
Definition: Block.php:1151
getRevisionFetched()
Get the fetched Revision object depending on request parameters or null on failure.
Definition: Article.php:412
insertProtectNullRevision($revCommentMsg, array $limit, array $expiry, $cascade, $reason, $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2361
const NS_MEDIA
Definition: Defines.php:53
static isValid($ip)
Validate an IP address.
Definition: IP.php:111
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:855
ParserOutput $mParserOutput
Definition: Article.php:76
getRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2297
generateReason(&$hasHistory)
Definition: Article.php:2649
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1474
insertRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2373
int null $mOldId
The oldid of the article that is to be shown, 0 for the current revision.
Definition: Article.php:61
static openElement($element, $attribs=null)
This opens an XML element.
Definition: Xml.php:109
hasViewableContent()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2345
getComment($audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2185
getTitle()
Get the title object of the article.
Definition: Article.php:181
loadFromRow($data, $from)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2405
$cache
Definition: mcc.php:33
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2161
doViewUpdates(User $user, $oldid=0)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2137
getTitle()
Get the title object of the article.
Definition: WikiPage.php:237
const NS_CATEGORY
Definition: Defines.php:79
doPurge()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2129
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:1973
doDeleteArticleReal($reason, $suppress=false, $u1=null, $u2=null, &$error= '', User $user=null, $tags=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2084
static newFromTitle($title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:120
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
render()
Handle action=render.
Definition: Article.php:1526
static isIP($name)
Does the string match an anonymous IP address?
Definition: User.php:831
const DELETED_RESTRICTED
Definition: Revision.php:49
getCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2177
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:935
static linkKnown($target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:164
replaceSectionAtRev($sectionId, Content $sectionContent, $sectionTitle= '', $baseRevId=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2483
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
checkFlags($flags)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2060
const NS_FILE
Definition: Defines.php:71
static makeContent($text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
$wgRedirectSources
If local interwikis are set up which allow redirects, set this regexp to restrict URLs which will be ...
const TYPE_AUTO
Definition: Block.php:86
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:1753
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
getContentHandler()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2193
const NS_MEDIAWIKI
Definition: Defines.php:73
static link($target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:107
Title $mRedirectedFrom
Title from which we were redirected here.
Definition: Article.php:64
const DELETED_TEXT
Definition: Revision.php:46
getCreator($audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2217
addHelpLink($to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1509
newPage(Title $title)
Definition: Article.php:99
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:99
getOldestRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2273
string $mContent
Text of the revision we are working on.
Definition: Article.php:49
static makeExternalLink($url, $text, $escape=true, $linktype= '', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:843
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:1584
clear()
Clear the object.
Definition: Article.php:198
bool $mContentLoaded
Is the content ($mContent) already loaded?
Definition: Article.php:58
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
getAutoDeleteReason(&$hasHistory)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2169
checkTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2068
updateRevisionOn($dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2563
static newFromID($id)
Constructor from a page id.
Definition: Article.php:108
getId()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2241
getTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2305
pageDataFromTitle($dbr, $title, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2445
getMinorEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2265
view()
This is the default action of the index.php entry point: just view the page of the given title...
Definition: Article.php:435
static numParam($num)
Definition: Message.php:1028
getContentObject()
Returns a Content object representing the pages effective display content, not necessarily the revisi...
Definition: Article.php:223
getOldID()
Definition: Article.php:249
doDelete($reason, $suppress=false)
Perform a deletion and output success or failure messages.
Definition: Article.php:1843
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:932
Show an error when a user tries to do something they do not have the necessary permissions for...
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:112
updateRedirectOn($dbw, $redirectTitle, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2555
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition: Article.php:1896
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:2589
insertRedirectEntry(Title $rt, $oldLatest=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2381
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. '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 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object '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). '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:1248
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:201
int $mRevIdFetched
Revision ID of revision we are working on.
Definition: Article.php:70
fetchContentObject()
Get text content object Does NOT follow redirects.
Definition: Article.php:320
const DB_REPLICA
Definition: defines.php:25
setOldSubtitle($oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition: Article.php:1312
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists)...
Definition: Article.php:396
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1545
Handles the page protection UI and backend.
getTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2313
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:784
getUser($audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2329
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:152
static doWatchOrUnwatch($watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:91
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code, mangling or hiding MediaWiki's output.
replaceSectionContent($sectionId, Content $sectionContent, $sectionTitle= '', $edittime=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2495
setContext($context)
Sets the context this Article is executed in.
Definition: Article.php:2002
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition: Article.php:423
const NS_USER_TALK
Definition: Defines.php:68
static revUserTools($rev, $isPublic=false)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1070
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:696
getRedirectURL($rt)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2289
Definition: Block.php:27
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:2589
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1269
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:534
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki...
Definition: Article.php:172
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:244
getContentModel()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2201
getLinksTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2257
insertOn($dbw, $pageId=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2353
static listDropDownOptions($list, $params=[])
Build options for a drop-down box from a textual list.
Definition: Xml.php:540
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition: Article.php:1988
const RC_LOG
Definition: Defines.php:145
Revision $mRevision
Revision we are working on.
Definition: Article.php:73
getUserText($audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2337
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:969
static formatRobotPolicy($policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:826
getHiddenCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2233