MediaWiki  1.32.0
DifferenceEngine.php
Go to the documentation of this file.
1 <?php
26 
50 
52 
59  const DIFF_VERSION = '1.12';
60 
67  protected $mOldid;
68 
75  protected $mNewid;
76 
88  protected $mOldRev;
89 
99  protected $mNewRev;
100 
106  protected $mOldPage;
107 
113  protected $mNewPage;
114 
119  private $mOldTags;
120 
125  private $mNewTags;
126 
132  private $mOldContent;
133 
139  private $mNewContent;
140 
142  protected $mDiffLang;
143 
145  private $mRevisionsIdsLoaded = false;
146 
148  protected $mRevisionsLoaded = false;
149 
151  protected $mTextLoaded = 0;
152 
161  protected $isContentOverridden = false;
162 
164  protected $mCacheHit = false;
165 
171  public $enableDebugComment = false;
172 
176  protected $mReducedLineNumbers = false;
177 
179  protected $mMarkPatrolledLink = null;
180 
182  protected $unhide = false;
183 
185  protected $mRefreshCache = false;
186 
188  protected $slotDiffRenderers = null;
189 
196  protected $isSlotDiffRenderer = false;
197 
208  public function __construct( $context = null, $old = 0, $new = 0, $rcid = 0,
209  $refreshCache = false, $unhide = false
210  ) {
211  $this->deprecatePublicProperty( 'mOldid', '1.32', __CLASS__ );
212  $this->deprecatePublicProperty( 'mNewid', '1.32', __CLASS__ );
213  $this->deprecatePublicProperty( 'mOldRev', '1.32', __CLASS__ );
214  $this->deprecatePublicProperty( 'mNewRev', '1.32', __CLASS__ );
215  $this->deprecatePublicProperty( 'mOldPage', '1.32', __CLASS__ );
216  $this->deprecatePublicProperty( 'mNewPage', '1.32', __CLASS__ );
217  $this->deprecatePublicProperty( 'mOldContent', '1.32', __CLASS__ );
218  $this->deprecatePublicProperty( 'mNewContent', '1.32', __CLASS__ );
219  $this->deprecatePublicProperty( 'mRevisionsLoaded', '1.32', __CLASS__ );
220  $this->deprecatePublicProperty( 'mTextLoaded', '1.32', __CLASS__ );
221  $this->deprecatePublicProperty( 'mCacheHit', '1.32', __CLASS__ );
222 
223  if ( $context instanceof IContextSource ) {
224  $this->setContext( $context );
225  }
226 
227  wfDebug( "DifferenceEngine old '$old' new '$new' rcid '$rcid'\n" );
228 
229  $this->mOldid = $old;
230  $this->mNewid = $new;
231  $this->mRefreshCache = $refreshCache;
232  $this->unhide = $unhide;
233  }
234 
239  protected function getSlotDiffRenderers() {
240  if ( $this->isSlotDiffRenderer ) {
241  throw new LogicException( __METHOD__ . ' called in slot diff renderer mode' );
242  }
243 
244  if ( $this->slotDiffRenderers === null ) {
245  if ( !$this->loadRevisionData() ) {
246  return [];
247  }
248 
249  $slotContents = $this->getSlotContents();
250  $this->slotDiffRenderers = array_map( function ( $contents ) {
252  $content = $contents['new'] ?: $contents['old'];
253  return $content->getContentHandler()->getSlotDiffRenderer( $this->getContext() );
254  }, $slotContents );
255  }
257  }
258 
265  public function markAsSlotDiffRenderer() {
266  $this->isSlotDiffRenderer = true;
267  }
268 
274  protected function getSlotContents() {
275  if ( $this->isContentOverridden ) {
276  return [
277  SlotRecord::MAIN => [
278  'old' => $this->mOldContent,
279  'new' => $this->mNewContent,
280  ]
281  ];
282  } elseif ( !$this->loadRevisionData() ) {
283  return [];
284  }
285 
286  $newSlots = $this->mNewRev->getRevisionRecord()->getSlots()->getSlots();
287  if ( $this->mOldRev ) {
288  $oldSlots = $this->mOldRev->getRevisionRecord()->getSlots()->getSlots();
289  } else {
290  $oldSlots = [];
291  }
292  // The order here will determine the visual order of the diff. The current logic is
293  // slots of the new revision first in natural order, then deleted ones. This is ad hoc
294  // and should not be relied on - in the future we may want the ordering to depend
295  // on the page type.
296  $roles = array_merge( array_keys( $newSlots ), array_keys( $oldSlots ) );
297 
298  $slots = [];
299  foreach ( $roles as $role ) {
300  $slots[$role] = [
301  'old' => isset( $oldSlots[$role] ) ? $oldSlots[$role]->getContent() : null,
302  'new' => isset( $newSlots[$role] ) ? $newSlots[$role]->getContent() : null,
303  ];
304  }
305  // move main slot to front
306  if ( isset( $slots[SlotRecord::MAIN] ) ) {
307  $slots = [ SlotRecord::MAIN => $slots[SlotRecord::MAIN] ] + $slots;
308  }
309  return $slots;
310  }
311 
312  public function getTitle() {
313  // T202454 avoid errors when there is no title
314  return parent::getTitle() ?: Title::makeTitle( NS_SPECIAL, 'BadTitle/DifferenceEngine' );
315  }
316 
323  public function setReducedLineNumbers( $value = true ) {
324  $this->mReducedLineNumbers = $value;
325  }
326 
332  public function getDiffLang() {
333  if ( $this->mDiffLang === null ) {
334  # Default language in which the diff text is written.
335  $this->mDiffLang = $this->getTitle()->getPageLanguage();
336  }
337 
338  return $this->mDiffLang;
339  }
340 
344  public function wasCacheHit() {
345  return $this->mCacheHit;
346  }
347 
355  public function getOldid() {
356  $this->loadRevisionIds();
357 
358  return $this->mOldid;
359  }
360 
367  public function getNewid() {
368  $this->loadRevisionIds();
369 
370  return $this->mNewid;
371  }
372 
379  public function getOldRevision() {
380  return $this->mOldRev ? $this->mOldRev->getRevisionRecord() : null;
381  }
382 
388  public function getNewRevision() {
389  return $this->mNewRev ? $this->mNewRev->getRevisionRecord() : null;
390  }
391 
400  public function deletedLink( $id ) {
401  if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
402  $dbr = wfGetDB( DB_REPLICA );
403  $arQuery = Revision::getArchiveQueryInfo();
404  $row = $dbr->selectRow(
405  $arQuery['tables'],
406  array_merge( $arQuery['fields'], [ 'ar_namespace', 'ar_title' ] ),
407  [ 'ar_rev_id' => $id ],
408  __METHOD__,
409  [],
410  $arQuery['joins']
411  );
412  if ( $row ) {
414  $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
415 
416  return SpecialPage::getTitleFor( 'Undelete' )->getFullURL( [
417  'target' => $title->getPrefixedText(),
418  'timestamp' => $rev->getTimestamp()
419  ] );
420  }
421  }
422 
423  return false;
424  }
425 
433  public function deletedIdMarker( $id ) {
434  $link = $this->deletedLink( $id );
435  if ( $link ) {
436  return "[$link $id]";
437  } else {
438  return (string)$id;
439  }
440  }
441 
442  private function showMissingRevision() {
443  $out = $this->getOutput();
444 
445  $missing = [];
446  if ( $this->mOldRev === null ||
447  ( $this->mOldRev && $this->mOldContent === null )
448  ) {
449  $missing[] = $this->deletedIdMarker( $this->mOldid );
450  }
451  if ( $this->mNewRev === null ||
452  ( $this->mNewRev && $this->mNewContent === null )
453  ) {
454  $missing[] = $this->deletedIdMarker( $this->mNewid );
455  }
456 
457  $out->setPageTitle( $this->msg( 'errorpagetitle' ) );
458  $msg = $this->msg( 'difference-missing-revision' )
459  ->params( $this->getLanguage()->listToText( $missing ) )
460  ->numParams( count( $missing ) )
461  ->parseAsBlock();
462  $out->addHTML( $msg );
463  }
464 
465  public function showDiffPage( $diffOnly = false ) {
466  # Allow frames except in certain special cases
467  $out = $this->getOutput();
468  $out->allowClickjacking();
469  $out->setRobotPolicy( 'noindex,nofollow' );
470 
471  // Allow extensions to add any extra output here
472  Hooks::run( 'DifferenceEngineShowDiffPage', [ $out ] );
473 
474  if ( !$this->loadRevisionData() ) {
475  if ( Hooks::run( 'DifferenceEngineShowDiffPageMaybeShowMissingRevision', [ $this ] ) ) {
476  $this->showMissingRevision();
477  }
478  return;
479  }
480 
481  $user = $this->getUser();
482  $permErrors = [];
483  if ( $this->mNewPage ) {
484  $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user );
485  }
486  if ( $this->mOldPage ) {
487  $permErrors = wfMergeErrorArrays( $permErrors,
488  $this->mOldPage->getUserPermissionsErrors( 'read', $user ) );
489  }
490  if ( count( $permErrors ) ) {
491  throw new PermissionsError( 'read', $permErrors );
492  }
493 
494  $rollback = '';
495 
496  $query = [];
497  # Carry over 'diffonly' param via navigation links
498  if ( $diffOnly != $user->getBoolOption( 'diffonly' ) ) {
499  $query['diffonly'] = $diffOnly;
500  }
501  # Cascade unhide param in links for easy deletion browsing
502  if ( $this->unhide ) {
503  $query['unhide'] = 1;
504  }
505 
506  # Check if one of the revisions is deleted/suppressed
507  $deleted = $suppressed = false;
508  $allowed = $this->mNewRev->userCan( Revision::DELETED_TEXT, $user );
509 
510  $revisionTools = [];
511 
512  # mOldRev is false if the difference engine is called with a "vague" query for
513  # a diff between a version V and its previous version V' AND the version V
514  # is the first version of that article. In that case, V' does not exist.
515  if ( $this->mOldRev === false ) {
516  if ( $this->mNewPage ) {
517  $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
518  }
519  $samePage = true;
520  $oldHeader = '';
521  // Allow extensions to change the $oldHeader variable
522  Hooks::run( 'DifferenceEngineOldHeaderNoOldRev', [ &$oldHeader ] );
523  } else {
524  Hooks::run( 'DiffViewHeader', [ $this, $this->mOldRev, $this->mNewRev ] );
525 
526  if ( !$this->mOldPage || !$this->mNewPage ) {
527  // XXX say something to the user?
528  $samePage = false;
529  } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
530  $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
531  $samePage = true;
532  } else {
533  $out->setPageTitle( $this->msg( 'difference-title-multipage',
534  $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
535  $out->addSubtitle( $this->msg( 'difference-multipage' ) );
536  $samePage = false;
537  }
538 
539  if ( $samePage && $this->mNewPage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
540  if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) {
541  $rollbackLink = Linker::generateRollback( $this->mNewRev, $this->getContext() );
542  if ( $rollbackLink ) {
543  $out->preventClickjacking();
544  $rollback = "\u{00A0}\u{00A0}\u{00A0}" . $rollbackLink;
545  }
546  }
547 
548  if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) &&
549  !$this->mNewRev->isDeleted( Revision::DELETED_TEXT )
550  ) {
551  $undoLink = Html::element( 'a', [
552  'href' => $this->mNewPage->getLocalURL( [
553  'action' => 'edit',
554  'undoafter' => $this->mOldid,
555  'undo' => $this->mNewid
556  ] ),
557  'title' => Linker::titleAttrib( 'undo' ),
558  ],
559  $this->msg( 'editundo' )->text()
560  );
561  $revisionTools['mw-diff-undo'] = $undoLink;
562  }
563  }
564 
565  # Make "previous revision link"
566  if ( $samePage && $this->mOldPage && $this->mOldRev->getPrevious() ) {
567  $prevlink = Linker::linkKnown(
568  $this->mOldPage,
569  $this->msg( 'previousdiff' )->escaped(),
570  [ 'id' => 'differences-prevlink' ],
571  [ 'diff' => 'prev', 'oldid' => $this->mOldid ] + $query
572  );
573  } else {
574  $prevlink = "\u{00A0}";
575  }
576 
577  if ( $this->mOldRev->isMinor() ) {
578  $oldminor = ChangesList::flag( 'minor' );
579  } else {
580  $oldminor = '';
581  }
582 
583  $ldel = $this->revisionDeleteLink( $this->mOldRev );
584  $oldRevisionHeader = $this->getRevisionHeader( $this->mOldRev, 'complete' );
585  $oldChangeTags = ChangeTags::formatSummaryRow( $this->mOldTags, 'diff', $this->getContext() );
586 
587  $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader . '</strong></div>' .
588  '<div id="mw-diff-otitle2">' .
589  Linker::revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
590  '<div id="mw-diff-otitle3">' . $oldminor .
591  Linker::revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
592  '<div id="mw-diff-otitle5">' . $oldChangeTags[0] . '</div>' .
593  '<div id="mw-diff-otitle4">' . $prevlink . '</div>';
594 
595  // Allow extensions to change the $oldHeader variable
596  Hooks::run( 'DifferenceEngineOldHeader', [ $this, &$oldHeader, $prevlink, $oldminor,
597  $diffOnly, $ldel, $this->unhide ] );
598 
599  if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
600  $deleted = true; // old revisions text is hidden
601  if ( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
602  $suppressed = true; // also suppressed
603  }
604  }
605 
606  # Check if this user can see the revisions
607  if ( !$this->mOldRev->userCan( Revision::DELETED_TEXT, $user ) ) {
608  $allowed = false;
609  }
610  }
611 
612  $out->addJsConfigVars( [
613  'wgDiffOldId' => $this->mOldid,
614  'wgDiffNewId' => $this->mNewid,
615  ] );
616 
617  # Make "next revision link"
618  # Skip next link on the top revision
619  if ( $samePage && $this->mNewPage && !$this->mNewRev->isCurrent() ) {
620  $nextlink = Linker::linkKnown(
621  $this->mNewPage,
622  $this->msg( 'nextdiff' )->escaped(),
623  [ 'id' => 'differences-nextlink' ],
624  [ 'diff' => 'next', 'oldid' => $this->mNewid ] + $query
625  );
626  } else {
627  $nextlink = "\u{00A0}";
628  }
629 
630  if ( $this->mNewRev->isMinor() ) {
631  $newminor = ChangesList::flag( 'minor' );
632  } else {
633  $newminor = '';
634  }
635 
636  # Handle RevisionDelete links...
637  $rdel = $this->revisionDeleteLink( $this->mNewRev );
638 
639  # Allow extensions to define their own revision tools
640  Hooks::run( 'DiffRevisionTools',
641  [ $this->mNewRev, &$revisionTools, $this->mOldRev, $user ] );
642  $formattedRevisionTools = [];
643  // Put each one in parentheses (poor man's button)
644  foreach ( $revisionTools as $key => $tool ) {
645  $toolClass = is_string( $key ) ? $key : 'mw-diff-tool';
646  $element = Html::rawElement(
647  'span',
648  [ 'class' => $toolClass ],
649  $this->msg( 'parentheses' )->rawParams( $tool )->escaped()
650  );
651  $formattedRevisionTools[] = $element;
652  }
653  $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) .
654  ' ' . implode( ' ', $formattedRevisionTools );
655  $newChangeTags = ChangeTags::formatSummaryRow( $this->mNewTags, 'diff', $this->getContext() );
656 
657  $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' .
658  '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $this->mNewRev, !$this->unhide ) .
659  " $rollback</div>" .
660  '<div id="mw-diff-ntitle3">' . $newminor .
661  Linker::revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
662  '<div id="mw-diff-ntitle5">' . $newChangeTags[0] . '</div>' .
663  '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() . '</div>';
664 
665  // Allow extensions to change the $newHeader variable
666  Hooks::run( 'DifferenceEngineNewHeader', [ $this, &$newHeader, $formattedRevisionTools,
667  $nextlink, $rollback, $newminor, $diffOnly, $rdel, $this->unhide ] );
668 
669  if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
670  $deleted = true; // new revisions text is hidden
671  if ( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
672  $suppressed = true; // also suppressed
673  }
674  }
675 
676  # If the diff cannot be shown due to a deleted revision, then output
677  # the diff header and links to unhide (if available)...
678  if ( $deleted && ( !$this->unhide || !$allowed ) ) {
679  $this->showDiffStyle();
680  $multi = $this->getMultiNotice();
681  $out->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) );
682  if ( !$allowed ) {
683  $msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff';
684  # Give explanation for why revision is not visible
685  $out->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
686  [ $msg ] );
687  } else {
688  # Give explanation and add a link to view the diff...
689  $query = $this->getRequest()->appendQueryValue( 'unhide', '1' );
690  $link = $this->getTitle()->getFullURL( $query );
691  $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
692  $out->wrapWikiMsg(
693  "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
694  [ $msg, $link ]
695  );
696  }
697  # Otherwise, output a regular diff...
698  } else {
699  # Add deletion notice if the user is viewing deleted content
700  $notice = '';
701  if ( $deleted ) {
702  $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view';
703  $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" .
704  $this->msg( $msg )->parse() .
705  "</div>\n";
706  }
707  $this->showDiff( $oldHeader, $newHeader, $notice );
708  if ( !$diffOnly ) {
709  $this->renderNewRevision();
710  }
711  }
712  }
713 
723  public function markPatrolledLink() {
724  if ( $this->mMarkPatrolledLink === null ) {
725  $linkInfo = $this->getMarkPatrolledLinkInfo();
726  // If false, there is no patrol link needed/allowed
727  if ( !$linkInfo || !$this->mNewPage ) {
728  $this->mMarkPatrolledLink = '';
729  } else {
730  $this->mMarkPatrolledLink = ' <span class="patrollink" data-mw="interface">[' .
732  $this->mNewPage,
733  $this->msg( 'markaspatrolleddiff' )->escaped(),
734  [],
735  [
736  'action' => 'markpatrolled',
737  'rcid' => $linkInfo['rcid'],
738  ]
739  ) . ']</span>';
740  // Allow extensions to change the markpatrolled link
741  Hooks::run( 'DifferenceEngineMarkPatrolledLink', [ $this,
742  &$this->mMarkPatrolledLink, $linkInfo['rcid'] ] );
743  }
744  }
746  }
747 
755  protected function getMarkPatrolledLinkInfo() {
756  global $wgUseRCPatrol;
757 
758  $user = $this->getUser();
759 
760  // Prepare a change patrol link, if applicable
761  if (
762  // Is patrolling enabled and the user allowed to?
763  $wgUseRCPatrol && $this->mNewPage && $this->mNewPage->quickUserCan( 'patrol', $user ) &&
764  // Only do this if the revision isn't more than 6 hours older
765  // than the Max RC age (6h because the RC might not be cleaned out regularly)
766  RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 )
767  ) {
768  // Look for an unpatrolled change corresponding to this diff
769  $db = wfGetDB( DB_REPLICA );
770  $change = RecentChange::newFromConds(
771  [
772  'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
773  'rc_this_oldid' => $this->mNewid,
774  'rc_patrolled' => RecentChange::PRC_UNPATROLLED
775  ],
776  __METHOD__
777  );
778 
779  if ( $change && !$change->getPerformer()->equals( $user ) ) {
780  $rcid = $change->getAttribute( 'rc_id' );
781  } else {
782  // None found or the page has been created by the current user.
783  // If the user could patrol this it already would be patrolled
784  $rcid = 0;
785  }
786 
787  // Allow extensions to possibly change the rcid here
788  // For example the rcid might be set to zero due to the user
789  // being the same as the performer of the change but an extension
790  // might still want to show it under certain conditions
791  Hooks::run( 'DifferenceEngineMarkPatrolledRCID', [ &$rcid, $this, $change, $user ] );
792 
793  // Build the link
794  if ( $rcid ) {
795  $this->getOutput()->preventClickjacking();
796  if ( $user->isAllowed( 'writeapi' ) ) {
797  $this->getOutput()->addModules( 'mediawiki.page.patrol.ajax' );
798  }
799 
800  return [
801  'rcid' => $rcid,
802  ];
803  }
804  }
805 
806  // No mark as patrolled link applicable
807  return false;
808  }
809 
815  protected function revisionDeleteLink( $rev ) {
816  $link = Linker::getRevDeleteLink( $this->getUser(), $rev, $rev->getTitle() );
817  if ( $link !== '' ) {
818  $link = "\u{00A0}\u{00A0}\u{00A0}" . $link . ' ';
819  }
820 
821  return $link;
822  }
823 
829  public function renderNewRevision() {
830  if ( $this->isContentOverridden ) {
831  // The code below only works with a Revision object. We could construct a fake revision
832  // (here or in setContent), but since this does not seem needed at the moment,
833  // we'll just fail for now.
834  throw new LogicException(
835  __METHOD__
836  . ' is not supported after calling setContent(). Use setRevisions() instead.'
837  );
838  }
839 
840  $out = $this->getOutput();
841  $revHeader = $this->getRevisionHeader( $this->mNewRev );
842  # Add "current version as of X" title
843  $out->addHTML( "<hr class='diff-hr' id='mw-oldid' />
844  <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
845  # Page content may be handled by a hooked call instead...
846  if ( Hooks::run( 'ArticleContentOnDiff', [ $this, $out ] ) ) {
847  $this->loadNewText();
848  if ( !$this->mNewPage ) {
849  // New revision is unsaved; bail out.
850  // TODO in theory rendering the new revision is a meaningful thing to do
851  // even if it's unsaved, but a lot of untangling is required to do it safely.
852  return;
853  }
854 
855  $out->setRevisionId( $this->mNewid );
856  $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
857  $out->setArticleFlag( true );
858 
859  if ( !Hooks::run( 'ArticleRevisionViewCustom',
860  [ $this->mNewRev->getRevisionRecord(), $this->mNewPage, $out ] )
861  ) {
862  // Handled by extension
863  // NOTE: sync with hooks called in Article::view()
864  } elseif ( !Hooks::run( 'ArticleContentViewCustom',
865  [ $this->mNewContent, $this->mNewPage, $out ], '1.32' )
866  ) {
867  // Handled by extension
868  // NOTE: sync with hooks called in Article::view()
869  } else {
870  // Normal page
871  if ( $this->getTitle()->equals( $this->mNewPage ) ) {
872  // If the Title stored in the context is the same as the one
873  // of the new revision, we can use its associated WikiPage
874  // object.
875  $wikiPage = $this->getWikiPage();
876  } else {
877  // Otherwise we need to create our own WikiPage object
878  $wikiPage = WikiPage::factory( $this->mNewPage );
879  }
880 
881  $parserOutput = $this->getParserOutput( $wikiPage, $this->mNewRev );
882 
883  # WikiPage::getParserOutput() should not return false, but just in case
884  if ( $parserOutput ) {
885  // Allow extensions to change parser output here
886  if ( Hooks::run( 'DifferenceEngineRenderRevisionAddParserOutput',
887  [ $this, $out, $parserOutput, $wikiPage ] )
888  ) {
889  $out->addParserOutput( $parserOutput, [
890  'enableSectionEditLinks' => $this->mNewRev->isCurrent()
891  && $this->mNewRev->getTitle()->quickUserCan( 'edit', $this->getUser() ),
892  ] );
893  }
894  }
895  }
896  }
897 
898  // Allow extensions to optionally not show the final patrolled link
899  if ( Hooks::run( 'DifferenceEngineRenderRevisionShowFinalPatrolLink' ) ) {
900  # Add redundant patrol link on bottom...
901  $out->addHTML( $this->markPatrolledLink() );
902  }
903  }
904 
911  protected function getParserOutput( WikiPage $page, Revision $rev ) {
912  if ( !$rev->getId() ) {
913  // WikiPage::getParserOutput wants a revision ID. Passing 0 will incorrectly show
914  // the current revision, so fail instead. If need be, WikiPage::getParserOutput
915  // could be made to accept a Revision or RevisionRecord instead of the id.
916  return false;
917  }
918 
919  $parserOptions = $page->makeParserOptions( $this->getContext() );
920  $parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() );
921 
922  return $parserOutput;
923  }
924 
935  public function showDiff( $otitle, $ntitle, $notice = '' ) {
936  // Allow extensions to affect the output here
937  Hooks::run( 'DifferenceEngineShowDiff', [ $this ] );
938 
939  $diff = $this->getDiff( $otitle, $ntitle, $notice );
940  if ( $diff === false ) {
941  $this->showMissingRevision();
942 
943  return false;
944  } else {
945  $this->showDiffStyle();
946  $this->getOutput()->addHTML( $diff );
947 
948  return true;
949  }
950  }
951 
955  public function showDiffStyle() {
956  if ( !$this->isSlotDiffRenderer ) {
957  $this->getOutput()->addModuleStyles( 'mediawiki.diff.styles' );
958  foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
959  $slotDiffRenderer->addModules( $this->getOutput() );
960  }
961  }
962  }
963 
973  public function getDiff( $otitle, $ntitle, $notice = '' ) {
974  $body = $this->getDiffBody();
975  if ( $body === false ) {
976  return false;
977  }
978 
979  $multi = $this->getMultiNotice();
980  // Display a message when the diff is empty
981  if ( $body === '' ) {
982  $notice .= '<div class="mw-diff-empty">' .
983  $this->msg( 'diff-empty' )->parse() .
984  "</div>\n";
985  }
986 
987  return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
988  }
989 
995  public function getDiffBody() {
996  $this->mCacheHit = true;
997  // Check if the diff should be hidden from this user
998  if ( !$this->isContentOverridden ) {
999  if ( !$this->loadRevisionData() ) {
1000  return false;
1001  } elseif ( $this->mOldRev &&
1002  !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
1003  ) {
1004  return false;
1005  } elseif ( $this->mNewRev &&
1006  !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
1007  ) {
1008  return false;
1009  }
1010  // Short-circuit
1011  if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev &&
1012  $this->mOldRev->getId() && $this->mOldRev->getId() == $this->mNewRev->getId() )
1013  ) {
1014  if ( Hooks::run( 'DifferenceEngineShowEmptyOldContent', [ $this ] ) ) {
1015  return '';
1016  }
1017  }
1018  }
1019 
1020  // Cacheable?
1021  $key = false;
1023  if ( $this->mOldid && $this->mNewid ) {
1024  // Check if subclass is still using the old way
1025  // for backwards-compatibility
1026  $key = $this->getDiffBodyCacheKey();
1027  if ( $key === null ) {
1028  $key = $cache->makeKey( ...$this->getDiffBodyCacheKeyParams() );
1029  }
1030 
1031  // Try cache
1032  if ( !$this->mRefreshCache ) {
1033  $difftext = $cache->get( $key );
1034  if ( $difftext ) {
1035  wfIncrStats( 'diff_cache.hit' );
1036  $difftext = $this->localiseDiff( $difftext );
1037  $difftext .= "\n<!-- diff cache key $key -->\n";
1038 
1039  return $difftext;
1040  }
1041  } // don't try to load but save the result
1042  }
1043  $this->mCacheHit = false;
1044 
1045  // Loadtext is permission safe, this just clears out the diff
1046  if ( !$this->loadText() ) {
1047  return false;
1048  }
1049 
1050  $difftext = '';
1051  // We've checked for revdelete at the beginning of this method; it's OK to ignore
1052  // read permissions here.
1053  $slotContents = $this->getSlotContents();
1054  foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
1055  $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role]['old'],
1056  $slotContents[$role]['new'] );
1057  if ( $slotDiff && $role !== SlotRecord::MAIN ) {
1058  // TODO use human-readable role name at least
1059  $slotTitle = $role;
1060  $difftext .= $this->getSlotHeader( $slotTitle );
1061  }
1062  $difftext .= $slotDiff;
1063  }
1064 
1065  // Avoid PHP 7.1 warning from passing $this by reference
1066  $diffEngine = $this;
1067 
1068  // Save to cache for 7 days
1069  if ( !Hooks::run( 'AbortDiffCache', [ &$diffEngine ] ) ) {
1070  wfIncrStats( 'diff_cache.uncacheable' );
1071  } elseif ( $key !== false && $difftext !== false ) {
1072  wfIncrStats( 'diff_cache.miss' );
1073  $cache->set( $key, $difftext, 7 * 86400 );
1074  } else {
1075  wfIncrStats( 'diff_cache.uncacheable' );
1076  }
1077  // localise line numbers and title attribute text
1078  if ( $difftext !== false ) {
1079  $difftext = $this->localiseDiff( $difftext );
1080  }
1081 
1082  return $difftext;
1083  }
1084 
1091  public function getDiffBodyForRole( $role ) {
1092  $diffRenderers = $this->getSlotDiffRenderers();
1093  if ( !isset( $diffRenderers[$role] ) ) {
1094  return false;
1095  }
1096 
1097  $slotContents = $this->getSlotContents();
1098  $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role]['old'],
1099  $slotContents[$role]['new'] );
1100  if ( !$slotDiff ) {
1101  return false;
1102  }
1103 
1104  if ( $role !== SlotRecord::MAIN ) {
1105  // TODO use human-readable role name at least
1106  $slotTitle = $role;
1107  $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
1108  }
1109 
1110  return $this->localiseDiff( $slotDiff );
1111  }
1112 
1120  protected function getSlotHeader( $headerText ) {
1121  // The old revision is missing on oldid=<first>&diff=prev; only 2 columns in that case.
1122  $columnCount = $this->mOldRev ? 4 : 2;
1123  $userLang = $this->getLanguage()->getHtmlCode();
1124  return Html::rawElement( 'tr', [ 'class' => 'mw-diff-slot-header', 'lang' => $userLang ],
1125  Html::element( 'th', [ 'colspan' => $columnCount ], $headerText ) );
1126  }
1127 
1137  protected function getDiffBodyCacheKey() {
1138  return null;
1139  }
1140 
1154  protected function getDiffBodyCacheKeyParams() {
1155  if ( !$this->mOldid || !$this->mNewid ) {
1156  throw new MWException( 'mOldid and mNewid must be set to get diff cache key.' );
1157  }
1158 
1159  $engine = $this->getEngine();
1160  $params = [
1161  'diff',
1162  $engine,
1164  "old-{$this->mOldid}",
1165  "rev-{$this->mNewid}"
1166  ];
1167 
1168  if ( $engine === 'wikidiff2' ) {
1169  $params[] = phpversion( 'wikidiff2' );
1170  $params[] = $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' );
1171  }
1172 
1173  if ( !$this->isSlotDiffRenderer ) {
1174  foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
1175  $params = array_merge( $params, $slotDiffRenderer->getExtraCacheKeys() );
1176  }
1177  }
1178 
1179  return $params;
1180  }
1181 
1189  public function getExtraCacheKeys() {
1190  // This method is called when the DifferenceEngine is used for a slot diff. We only care
1191  // about special things, not the revision IDs, which are added to the cache key by the
1192  // page-level DifferenceEngine, and which might not have a valid value for this object.
1193  $this->mOldid = 123456789;
1194  $this->mNewid = 987654321;
1195 
1196  // This will repeat a bunch of unnecessary key fields for each slot. Not nice but harmless.
1197  $cacheString = $this->getDiffBodyCacheKey();
1198  if ( $cacheString ) {
1199  return [ $cacheString ];
1200  }
1201 
1203 
1204  // Try to get rid of the standard keys to keep the cache key human-readable:
1205  // call the getDiffBodyCacheKeyParams implementation of the base class, and if
1206  // the child class includes the same keys, drop them.
1207  // Uses an obscure PHP feature where static calls to non-static methods are allowed
1208  // as long as we are already in a non-static method of the same class, and the call context
1209  // ($this) will be inherited.
1210  // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed
1211  $standardParams = DifferenceEngine::getDiffBodyCacheKeyParams();
1212  if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
1213  $params = array_slice( $params, count( $standardParams ) );
1214  }
1215 
1216  return $params;
1217  }
1218 
1232  public function generateContentDiffBody( Content $old, Content $new ) {
1233  $slotDiffRenderer = $new->getContentHandler()->getSlotDiffRenderer( $this->getContext() );
1234  if (
1235  $slotDiffRenderer instanceof DifferenceEngineSlotDiffRenderer
1236  && $this->isSlotDiffRenderer
1237  ) {
1238  // Oops, we are just about to enter an infinite loop (the slot-level DifferenceEngine
1239  // called a DifferenceEngineSlotDiffRenderer that wraps the same DifferenceEngine class).
1240  // This will happen when a content model has no custom slot diff renderer, it does have
1241  // a custom difference engine, but that does not override this method.
1242  throw new Exception( get_class( $this ) . ': could not maintain backwards compatibility. '
1243  . 'Please use a SlotDiffRenderer.' );
1244  }
1245  return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
1246  }
1247 
1260  public function generateTextDiffBody( $otext, $ntext ) {
1261  $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
1262  ->getSlotDiffRenderer( $this->getContext() );
1263  if ( !( $slotDiffRenderer instanceof TextSlotDiffRenderer ) ) {
1264  // Someone used the GetSlotDiffRenderer hook to replace the renderer.
1265  // This is too unlikely to happen to bother handling properly.
1266  throw new Exception( 'The slot diff renderer for text content should be a '
1267  . 'TextSlotDiffRenderer subclass' );
1268  }
1269  return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1270  }
1271 
1278  public static function getEngine() {
1279  global $wgExternalDiffEngine;
1280  // We use the global here instead of Config because we write to the value,
1281  // and Config is not mutable.
1282  if ( $wgExternalDiffEngine == 'wikidiff' || $wgExternalDiffEngine == 'wikidiff3' ) {
1283  wfDeprecated( "\$wgExternalDiffEngine = '{$wgExternalDiffEngine}'", '1.27' );
1284  $wgExternalDiffEngine = false;
1285  } elseif ( $wgExternalDiffEngine == 'wikidiff2' ) {
1286  wfDeprecated( "\$wgExternalDiffEngine = '{$wgExternalDiffEngine}'", '1.32' );
1287  $wgExternalDiffEngine = false;
1288  } elseif ( !is_string( $wgExternalDiffEngine ) && $wgExternalDiffEngine !== false ) {
1289  // And prevent people from shooting themselves in the foot...
1290  wfWarn( '$wgExternalDiffEngine is set to a non-string value, forcing it to false' );
1291  $wgExternalDiffEngine = false;
1292  }
1293 
1294  if ( is_string( $wgExternalDiffEngine ) && is_executable( $wgExternalDiffEngine ) ) {
1295  return $wgExternalDiffEngine;
1296  } elseif ( $wgExternalDiffEngine === false && function_exists( 'wikidiff2_do_diff' ) ) {
1297  return 'wikidiff2';
1298  } else {
1299  // Native PHP
1300  return false;
1301  }
1302  }
1303 
1316  protected function textDiff( $otext, $ntext ) {
1317  $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
1318  ->getSlotDiffRenderer( $this->getContext() );
1319  if ( !( $slotDiffRenderer instanceof TextSlotDiffRenderer ) ) {
1320  // Someone used the GetSlotDiffRenderer hook to replace the renderer.
1321  // This is too unlikely to happen to bother handling properly.
1322  throw new Exception( 'The slot diff renderer for text content should be a '
1323  . 'TextSlotDiffRenderer subclass' );
1324  }
1325  return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
1326  }
1327 
1336  protected function debug( $generator = "internal" ) {
1337  global $wgShowHostnames;
1338  if ( !$this->enableDebugComment ) {
1339  return '';
1340  }
1341  $data = [ $generator ];
1342  if ( $wgShowHostnames ) {
1343  $data[] = wfHostname();
1344  }
1345  $data[] = wfTimestamp( TS_DB );
1346 
1347  return "<!-- diff generator: " .
1348  implode( " ", array_map( "htmlspecialchars", $data ) ) .
1349  " -->\n";
1350  }
1351 
1352  private function getDebugString() {
1354  if ( $engine === 'wikidiff2' ) {
1355  return $this->debug( 'wikidiff2' );
1356  } elseif ( $engine === false ) {
1357  return $this->debug( 'native PHP' );
1358  } else {
1359  return $this->debug( "external $engine" );
1360  }
1361  }
1362 
1369  private function localiseDiff( $text ) {
1370  $text = $this->localiseLineNumbers( $text );
1371  if ( $this->getEngine() === 'wikidiff2' &&
1372  version_compare( phpversion( 'wikidiff2' ), '1.5.1', '>=' )
1373  ) {
1374  $text = $this->addLocalisedTitleTooltips( $text );
1375  }
1376  return $text;
1377  }
1378 
1386  public function localiseLineNumbers( $text ) {
1387  return preg_replace_callback(
1388  '/<!--LINE (\d+)-->/',
1389  [ $this, 'localiseLineNumbersCb' ],
1390  $text
1391  );
1392  }
1393 
1394  public function localiseLineNumbersCb( $matches ) {
1395  if ( $matches[1] === '1' && $this->mReducedLineNumbers ) {
1396  return '';
1397  }
1398 
1399  return $this->msg( 'lineno' )->numParams( $matches[1] )->escaped();
1400  }
1401 
1408  private function addLocalisedTitleTooltips( $text ) {
1409  return preg_replace_callback(
1410  '/class="mw-diff-movedpara-(left|right)"/',
1411  [ $this, 'addLocalisedTitleTooltipsCb' ],
1412  $text
1413  );
1414  }
1415 
1421  $key = $matches[1] === 'right' ?
1422  'diff-paragraph-moved-toold' :
1423  'diff-paragraph-moved-tonew';
1424  return $matches[0] . ' title="' . $this->msg( $key )->escaped() . '"';
1425  }
1426 
1432  public function getMultiNotice() {
1433  // The notice only make sense if we are diffing two saved revisions of the same page.
1434  if (
1435  !$this->mOldRev || !$this->mNewRev
1436  || !$this->mOldPage || !$this->mNewPage
1437  || !$this->mOldPage->equals( $this->mNewPage )
1438  ) {
1439  return '';
1440  }
1441 
1442  if ( $this->mOldRev->getTimestamp() > $this->mNewRev->getTimestamp() ) {
1443  $oldRev = $this->mNewRev; // flip
1444  $newRev = $this->mOldRev; // flip
1445  } else { // normal case
1446  $oldRev = $this->mOldRev;
1448  }
1449 
1450  // Sanity: don't show the notice if too many rows must be scanned
1451  // @todo show some special message for that case
1452  $nEdits = $this->mNewPage->countRevisionsBetween( $oldRev, $newRev, 1000 );
1453  if ( $nEdits > 0 && $nEdits <= 1000 ) {
1454  $limit = 100; // use diff-multi-manyusers if too many users
1455  $users = $this->mNewPage->getAuthorsBetween( $oldRev, $newRev, $limit );
1456  $numUsers = count( $users );
1457 
1458  if ( $numUsers == 1 && $users[0] == $newRev->getUserText( Revision::RAW ) ) {
1459  $numUsers = 0; // special case to say "by the same user" instead of "by one other user"
1460  }
1461 
1462  return self::intermediateEditsMsg( $nEdits, $numUsers, $limit );
1463  }
1464 
1465  return ''; // nothing
1466  }
1467 
1477  public static function intermediateEditsMsg( $numEdits, $numUsers, $limit ) {
1478  if ( $numUsers === 0 ) {
1479  $msg = 'diff-multi-sameuser';
1480  } elseif ( $numUsers > $limit ) {
1481  $msg = 'diff-multi-manyusers';
1482  $numUsers = $limit;
1483  } else {
1484  $msg = 'diff-multi-otherusers';
1485  }
1486 
1487  return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
1488  }
1489 
1499  public function getRevisionHeader( Revision $rev, $complete = '' ) {
1500  $lang = $this->getLanguage();
1501  $user = $this->getUser();
1502  $revtimestamp = $rev->getTimestamp();
1503  $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
1504  $dateofrev = $lang->userDate( $revtimestamp, $user );
1505  $timeofrev = $lang->userTime( $revtimestamp, $user );
1506 
1507  $header = $this->msg(
1508  $rev->isCurrent() ? 'currentrev-asof' : 'revisionasof',
1509  $timestamp,
1510  $dateofrev,
1511  $timeofrev
1512  )->escaped();
1513 
1514  if ( $complete !== 'complete' ) {
1515  return $header;
1516  }
1517 
1518  $title = $rev->getTitle();
1519 
1521  [ 'oldid' => $rev->getId() ] );
1522 
1523  if ( $rev->userCan( Revision::DELETED_TEXT, $user ) ) {
1524  $editQuery = [ 'action' => 'edit' ];
1525  if ( !$rev->isCurrent() ) {
1526  $editQuery['oldid'] = $rev->getId();
1527  }
1528 
1529  $key = $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold';
1530  $msg = $this->msg( $key )->escaped();
1531  $editLink = $this->msg( 'parentheses' )->rawParams(
1532  Linker::linkKnown( $title, $msg, [], $editQuery ) )->escaped();
1533  $header .= ' ' . Html::rawElement(
1534  'span',
1535  [ 'class' => 'mw-diff-edit' ],
1536  $editLink
1537  );
1538  if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
1540  'span',
1541  [ 'class' => 'history-deleted' ],
1542  $header
1543  );
1544  }
1545  } else {
1546  $header = Html::rawElement( 'span', [ 'class' => 'history-deleted' ], $header );
1547  }
1548 
1549  return $header;
1550  }
1551 
1564  public function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
1565  // shared.css sets diff in interface language/dir, but the actual content
1566  // is often in a different language, mostly the page content language/dir
1567  $header = Html::openElement( 'table', [
1568  'class' => [ 'diff', 'diff-contentalign-' . $this->getDiffLang()->alignStart() ],
1569  'data-mw' => 'interface',
1570  ] );
1571  $userLang = htmlspecialchars( $this->getLanguage()->getHtmlCode() );
1572 
1573  if ( !$diff && !$otitle ) {
1574  $header .= "
1575  <tr class=\"diff-title\" lang=\"{$userLang}\">
1576  <td class=\"diff-ntitle\">{$ntitle}</td>
1577  </tr>";
1578  $multiColspan = 1;
1579  } else {
1580  if ( $diff ) { // Safari/Chrome show broken output if cols not used
1581  $header .= "
1582  <col class=\"diff-marker\" />
1583  <col class=\"diff-content\" />
1584  <col class=\"diff-marker\" />
1585  <col class=\"diff-content\" />";
1586  $colspan = 2;
1587  $multiColspan = 4;
1588  } else {
1589  $colspan = 1;
1590  $multiColspan = 2;
1591  }
1592  if ( $otitle || $ntitle ) {
1593  $header .= "
1594  <tr class=\"diff-title\" lang=\"{$userLang}\">
1595  <td colspan=\"$colspan\" class=\"diff-otitle\">{$otitle}</td>
1596  <td colspan=\"$colspan\" class=\"diff-ntitle\">{$ntitle}</td>
1597  </tr>";
1598  }
1599  }
1600 
1601  if ( $multi != '' ) {
1602  $header .= "<tr><td colspan=\"{$multiColspan}\" " .
1603  "class=\"diff-multi\" lang=\"{$userLang}\">{$multi}</td></tr>";
1604  }
1605  if ( $notice != '' ) {
1606  $header .= "<tr><td colspan=\"{$multiColspan}\" " .
1607  "class=\"diff-notice\" lang=\"{$userLang}\">{$notice}</td></tr>";
1608  }
1609 
1610  return $header . $diff . "</table>";
1611  }
1612 
1620  public function setContent( Content $oldContent, Content $newContent ) {
1621  $this->mOldContent = $oldContent;
1622  $this->mNewContent = $newContent;
1623 
1624  $this->mTextLoaded = 2;
1625  $this->mRevisionsLoaded = true;
1626  $this->isContentOverridden = true;
1627  $this->slotDiffRenderers = null;
1628  }
1629 
1635  public function setRevisions(
1636  RevisionRecord $oldRevision = null, RevisionRecord $newRevision
1637  ) {
1638  if ( $oldRevision ) {
1639  $this->mOldRev = new Revision( $oldRevision );
1640  $this->mOldid = $oldRevision->getId();
1641  $this->mOldPage = Title::newFromLinkTarget( $oldRevision->getPageAsLinkTarget() );
1642  // This method is meant for edit diffs and such so there is no reason to provide a
1643  // revision that's not readable to the user, but check it just in case.
1644  $this->mOldContent = $oldRevision ? $oldRevision->getContent( SlotRecord::MAIN,
1645  RevisionRecord::FOR_THIS_USER, $this->getUser() ) : null;
1646  } else {
1647  $this->mOldPage = null;
1648  $this->mOldRev = $this->mOldid = false;
1649  }
1650  $this->mNewRev = new Revision( $newRevision );
1651  $this->mNewid = $newRevision->getId();
1652  $this->mNewPage = Title::newFromLinkTarget( $newRevision->getPageAsLinkTarget() );
1653  $this->mNewContent = $newRevision->getContent( SlotRecord::MAIN,
1654  RevisionRecord::FOR_THIS_USER, $this->getUser() );
1655 
1656  $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded = true;
1657  $this->mTextLoaded = !!$oldRevision + 1;
1658  $this->isContentOverridden = false;
1659  $this->slotDiffRenderers = null;
1660  }
1661 
1668  public function setTextLanguage( $lang ) {
1669  if ( !$lang instanceof Language ) {
1670  wfDeprecated( __METHOD__ . ' with other type than Language for $lang', '1.32' );
1671  }
1672  $this->mDiffLang = wfGetLangObj( $lang );
1673  }
1674 
1686  public function mapDiffPrevNext( $old, $new ) {
1687  if ( $new === 'prev' ) {
1688  // Show diff between revision $old and the previous one. Get previous one from DB.
1689  $newid = intval( $old );
1690  $oldid = $this->getTitle()->getPreviousRevisionID( $newid );
1691  } elseif ( $new === 'next' ) {
1692  // Show diff between revision $old and the next one. Get next one from DB.
1693  $oldid = intval( $old );
1694  $newid = $this->getTitle()->getNextRevisionID( $oldid );
1695  } else {
1696  $oldid = intval( $old );
1697  $newid = intval( $new );
1698  }
1699 
1700  return [ $oldid, $newid ];
1701  }
1702 
1706  private function loadRevisionIds() {
1707  if ( $this->mRevisionsIdsLoaded ) {
1708  return;
1709  }
1710 
1711  $this->mRevisionsIdsLoaded = true;
1712 
1713  $old = $this->mOldid;
1714  $new = $this->mNewid;
1715 
1716  list( $this->mOldid, $this->mNewid ) = self::mapDiffPrevNext( $old, $new );
1717  if ( $new === 'next' && $this->mNewid === false ) {
1718  # if no result, NewId points to the newest old revision. The only newer
1719  # revision is cur, which is "0".
1720  $this->mNewid = 0;
1721  }
1722 
1723  Hooks::run(
1724  'NewDifferenceEngine',
1725  [ $this->getTitle(), &$this->mOldid, &$this->mNewid, $old, $new ]
1726  );
1727  }
1728 
1742  public function loadRevisionData() {
1743  if ( $this->mRevisionsLoaded ) {
1744  return $this->isContentOverridden || $this->mNewRev && !is_null( $this->mOldRev );
1745  }
1746 
1747  // Whether it succeeds or fails, we don't want to try again
1748  $this->mRevisionsLoaded = true;
1749 
1750  $this->loadRevisionIds();
1751 
1752  // Load the new revision object
1753  if ( $this->mNewid ) {
1754  $this->mNewRev = Revision::newFromId( $this->mNewid );
1755  } else {
1756  $this->mNewRev = Revision::newFromTitle(
1757  $this->getTitle(),
1758  false,
1759  Revision::READ_NORMAL
1760  );
1761  }
1762 
1763  if ( !$this->mNewRev instanceof Revision ) {
1764  return false;
1765  }
1766 
1767  // Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
1768  $this->mNewid = $this->mNewRev->getId();
1769  if ( $this->mNewid ) {
1770  $this->mNewPage = $this->mNewRev->getTitle();
1771  } else {
1772  $this->mNewPage = null;
1773  }
1774 
1775  // Load the old revision object
1776  $this->mOldRev = false;
1777  if ( $this->mOldid ) {
1778  $this->mOldRev = Revision::newFromId( $this->mOldid );
1779  } elseif ( $this->mOldid === 0 ) {
1780  $rev = $this->mNewRev->getPrevious();
1781  if ( $rev ) {
1782  $this->mOldid = $rev->getId();
1783  $this->mOldRev = $rev;
1784  } else {
1785  // No previous revision; mark to show as first-version only.
1786  $this->mOldid = false;
1787  $this->mOldRev = false;
1788  }
1789  } /* elseif ( $this->mOldid === false ) leave mOldRev false; */
1790 
1791  if ( is_null( $this->mOldRev ) ) {
1792  return false;
1793  }
1794 
1795  if ( $this->mOldRev && $this->mOldRev->getId() ) {
1796  $this->mOldPage = $this->mOldRev->getTitle();
1797  } else {
1798  $this->mOldPage = null;
1799  }
1800 
1801  // Load tags information for both revisions
1802  $dbr = wfGetDB( DB_REPLICA );
1803  if ( $this->mOldid !== false ) {
1804  $this->mOldTags = $dbr->selectField(
1805  'tag_summary',
1806  'ts_tags',
1807  [ 'ts_rev_id' => $this->mOldid ],
1808  __METHOD__
1809  );
1810  } else {
1811  $this->mOldTags = false;
1812  }
1813  $this->mNewTags = $dbr->selectField(
1814  'tag_summary',
1815  'ts_tags',
1816  [ 'ts_rev_id' => $this->mNewid ],
1817  __METHOD__
1818  );
1819 
1820  return true;
1821  }
1822 
1831  public function loadText() {
1832  if ( $this->mTextLoaded == 2 ) {
1833  return $this->loadRevisionData() && ( $this->mOldRev === false || $this->mOldContent )
1834  && $this->mNewContent;
1835  }
1836 
1837  // Whether it succeeds or fails, we don't want to try again
1838  $this->mTextLoaded = 2;
1839 
1840  if ( !$this->loadRevisionData() ) {
1841  return false;
1842  }
1843 
1844  if ( $this->mOldRev ) {
1845  $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
1846  if ( $this->mOldContent === null ) {
1847  return false;
1848  }
1849  }
1850 
1851  $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
1852  Hooks::run( 'DifferenceEngineLoadTextAfterNewContentIsLoaded', [ $this ] );
1853  if ( $this->mNewContent === null ) {
1854  return false;
1855  }
1856 
1857  return true;
1858  }
1859 
1865  public function loadNewText() {
1866  if ( $this->mTextLoaded >= 1 ) {
1867  return $this->loadRevisionData();
1868  }
1869 
1870  $this->mTextLoaded = 1;
1871 
1872  if ( !$this->loadRevisionData() ) {
1873  return false;
1874  }
1875 
1876  $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
1877 
1878  Hooks::run( 'DifferenceEngineAfterLoadNewText', [ $this ] );
1879 
1880  return true;
1881  }
1882 
1883 }
Content\getContentHandler
getContentHandler()
Convenience method that returns the ContentHandler singleton for handling the content model that this...
Revision\newFromArchiveRow
static newFromArchiveRow( $row, $overrides=[])
Make a fake revision object from an archive table row.
Definition: Revision.php:167
DifferenceEngine\$mRevisionsIdsLoaded
bool $mRevisionsIdsLoaded
Have the revisions IDs been loaded.
Definition: DifferenceEngine.php:145
DifferenceEngine\revisionDeleteLink
revisionDeleteLink( $rev)
Definition: DifferenceEngine.php:815
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:33
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:63
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:297
DifferenceEngine\getSlotContents
getSlotContents()
Get the old and new content objects for all slots.
Definition: DifferenceEngine.php:274
$user
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 account $user
Definition: hooks.txt:244
wfMergeErrorArrays
wfMergeErrorArrays(... $args)
Merge arrays in the style of getUserPermissionsErrors, with duplicate removal e.g.
Definition: GlobalFunctions.php:203
Revision\DELETED_RESTRICTED
const DELETED_RESTRICTED
Definition: Revision.php:50
DifferenceEngine\markPatrolledLink
markPatrolledLink()
Build a link to mark a change as patrolled.
Definition: DifferenceEngine.php:723
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:40
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:45
DifferenceEngine\getEngine
static getEngine()
Process $wgExternalDiffEngine and get a sane, usable engine.
Definition: DifferenceEngine.php:1278
DifferenceEngine\$mTextLoaded
int $mTextLoaded
How many text blobs have been loaded, 0, 1 or 2?
Definition: DifferenceEngine.php:151
DifferenceEngine\addLocalisedTitleTooltipsCb
addLocalisedTitleTooltipsCb(array $matches)
Definition: DifferenceEngine.php:1420
DifferenceEngine\getDiffBodyCacheKeyParams
getDiffBodyCacheKeyParams()
Get the cache key parameters.
Definition: DifferenceEngine.php:1154
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:114
DifferenceEngine\$unhide
bool $unhide
Show rev_deleted content if allowed.
Definition: DifferenceEngine.php:182
DifferenceEngine\setTextLanguage
setTextLanguage( $lang)
Set the language in which the diff text is written.
Definition: DifferenceEngine.php:1668
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:204
DifferenceEngine\setReducedLineNumbers
setReducedLineNumbers( $value=true)
Set reduced line numbers mode.
Definition: DifferenceEngine.php:323
$newminor
also included in $newHeader if any $newminor
Definition: hooks.txt:1308
captcha-old.count
count
Definition: captcha-old.py:249
ContextSource\msg
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:168
DifferenceEngine\setContent
setContent(Content $oldContent, Content $newContent)
Use specified text instead of loading from the database.
Definition: DifferenceEngine.php:1620
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1954
DifferenceEngine\getNewid
getNewid()
Get the ID of new revision (right pane) of the diff.
Definition: DifferenceEngine.php:367
DifferenceEngine\getOldRevision
getOldRevision()
Get the left side of the diff.
Definition: DifferenceEngine.php:379
DifferenceEngine\getOldid
getOldid()
Get the ID of old revision (left pane) of the diff.
Definition: DifferenceEngine.php:355
$wgShowHostnames
$wgShowHostnames
Expose backend server host names through the API and various HTML comments.
Definition: DefaultSettings.php:6338
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:44
DifferenceEngine\$mRevisionsLoaded
bool $mRevisionsLoaded
Have the revisions been loaded.
Definition: DifferenceEngine.php:148
DifferenceEngine\deletedIdMarker
deletedIdMarker( $id)
Build a wikitext link toward a deleted revision, if viewable.
Definition: DifferenceEngine.php:433
$params
$params
Definition: styleTest.css.php:44
WikiPage\makeParserOptions
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
Definition: WikiPage.php:1917
wfHostname
wfHostname()
Fetch server name for use in error reporting etc.
Definition: GlobalFunctions.php:1392
Linker\linkKnown
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:141
DifferenceEngine\$isSlotDiffRenderer
bool $isSlotDiffRenderer
Temporary hack for B/C while slot diff related methods of DifferenceEngine are being deprecated.
Definition: DifferenceEngine.php:196
Revision\getArchiveQueryInfo
static getArchiveQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new archived revision objec...
Definition: Revision.php:535
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:82
DifferenceEngine\addLocalisedTitleTooltips
addLocalisedTitleTooltips( $text)
Add title attributes for tooltips on moved paragraph indicators.
Definition: DifferenceEngine.php:1408
Revision\isCurrent
isCurrent()
Definition: Revision.php:1010
ContextSource\getRequest
getRequest()
Definition: ContextSource.php:71
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:28
DifferenceEngine\showDiffStyle
showDiffStyle()
Add style sheets for diff display.
Definition: DifferenceEngine.php:955
DifferenceEngine\loadNewText
loadNewText()
Load the text of the new revision, not the old one.
Definition: DifferenceEngine.php:1865
ContextSource\getUser
getUser()
Definition: ContextSource.php:120
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6933
DifferenceEngine\showDiff
showDiff( $otitle, $ntitle, $notice='')
Get the diff text, send it to the OutputPage object Returns false if the diff could not be generated,...
Definition: DifferenceEngine.php:935
DifferenceEngine\getDiffBodyCacheKey
getDiffBodyCacheKey()
Returns the cache key for diff body text or content.
Definition: DifferenceEngine.php:1137
DifferenceEngine\localiseDiff
localiseDiff( $text)
Localise diff output.
Definition: DifferenceEngine.php:1369
DifferenceEngine\$mNewid
int string false null $mNewid
Revision ID for the new revision.
Definition: DifferenceEngine.php:75
php
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
DifferenceEngine\generateTextDiffBody
generateTextDiffBody( $otext, $ntext)
Generate a diff, no caching.
Definition: DifferenceEngine.php:1260
$dbr
$dbr
Definition: testCompression.php:50
ContextSource\getLanguage
getLanguage()
Definition: ContextSource.php:128
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:56
Revision
Definition: Revision.php:41
DifferenceEngine\$mReducedLineNumbers
bool $mReducedLineNumbers
If true, line X is not displayed when X is 1, for example to increase readability and conserve space ...
Definition: DifferenceEngine.php:176
Revision\newFromTitle
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition: Revision.php:133
$query
null for the 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:1627
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:53
DifferenceEngine\loadRevisionData
loadRevisionData()
Load revision metadata for the specified revisions.
Definition: DifferenceEngine.php:1742
DifferenceEngine\localiseLineNumbersCb
localiseLineNumbersCb( $matches)
Definition: DifferenceEngine.php:1394
MWException
MediaWiki exception.
Definition: MWException.php:26
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
$newRev
$newRev
Definition: pageupdater.txt:66
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:127
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1118
DifferenceEngine\getDiffBodyForRole
getDiffBodyForRole( $role)
Get the diff table body for one slot, without header.
Definition: DifferenceEngine.php:1091
DifferenceEngine\$slotDiffRenderers
SlotDiffRenderer[] $slotDiffRenderers
DifferenceEngine classes for the slots, keyed by role name.
Definition: DifferenceEngine.php:188
Linker\generateRollback
static generateRollback( $rev, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition: Linker.php:1704
DifferenceEngine\wasCacheHit
wasCacheHit()
Definition: DifferenceEngine.php:344
DifferenceEngine\$mNewTags
string[] null $mNewTags
Change tags of $mNewRev or null if it does not exist / is not saved.
Definition: DifferenceEngine.php:125
wfIncrStats
wfIncrStats( $key, $count=1)
Increment a statistics counter.
Definition: GlobalFunctions.php:1227
DifferenceEngine\getDiffLang
getDiffLang()
Get the language of the difference engine, defaults to page content language.
Definition: DifferenceEngine.php:332
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget)
Create a new Title from a LinkTarget.
Definition: Title.php:251
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2693
Linker\revUserTools
static revUserTools( $rev, $isPublic=false)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1053
WikiPage\getParserOutput
getParserOutput(ParserOptions $parserOptions, $oldid=null, $forceParse=false)
Get a ParserOutput for the given ParserOptions and revision ID.
Definition: WikiPage.php:1197
ContextSource\getOutput
getOutput()
Definition: ContextSource.php:112
$matches
$matches
Definition: NoLocalSettings.php:24
DifferenceEngine\getSlotHeader
getSlotHeader( $headerText)
Get a slot header for inclusion in a diff body (as a table row).
Definition: DifferenceEngine.php:1120
ContextSource
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
Definition: ContextSource.php:29
DifferenceEngine\$mOldRev
Revision null false $mOldRev
Old revision (left pane).
Definition: DifferenceEngine.php:88
ContextSource\getWikiPage
getWikiPage()
Get the WikiPage object.
Definition: ContextSource.php:104
DifferenceEngine\$mNewContent
Content null $mNewContent
Definition: DifferenceEngine.php:139
DifferenceEngine\deletedLink
deletedLink( $id)
Look up a special:Undelete link to the given deleted revision id, as a workaround for being unable to...
Definition: DifferenceEngine.php:400
wfGetLangObj
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
Definition: GlobalFunctions.php:1281
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
ChangesList\flag
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
Definition: ChangesList.php:255
DifferenceEngine\getDiffBody
getDiffBody()
Get the diff table body, without header.
Definition: DifferenceEngine.php:995
DifferenceEngine\getRevisionHeader
getRevisionHeader(Revision $rev, $complete='')
Get a header for a specified revision.
Definition: DifferenceEngine.php:1499
DifferenceEngine\getSlotDiffRenderers
getSlotDiffRenderers()
Definition: DifferenceEngine.php:239
$generator
$generator
Definition: generateLocalAutoload.php:13
$engine
the value to return A Title object or null for latest all implement SearchIndexField $engine
Definition: hooks.txt:2938
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:545
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
DifferenceEngine\loadText
loadText()
Load the text of the revisions, as well as revision data.
Definition: DifferenceEngine.php:1831
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:988
ContextSource\setContext
setContext(IContextSource $context)
Definition: ContextSource.php:55
list
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
DifferenceEngine\$mCacheHit
bool $mCacheHit
Was the diff fetched from cache?
Definition: DifferenceEngine.php:164
deprecatePublicProperty
deprecatePublicProperty( $property, $version, $class=null, $component=null)
Mark a property as deprecated.
Definition: DeprecationHelper.php:68
$rollback
also included in $newHeader $rollback
Definition: hooks.txt:1308
$wgExternalDiffEngine
$wgExternalDiffEngine
Name of the external diff engine to use.
Definition: DefaultSettings.php:8495
DifferenceEngine\markAsSlotDiffRenderer
markAsSlotDiffRenderer()
Mark this DifferenceEngine as a slot renderer (as opposed to a page renderer).
Definition: DifferenceEngine.php:265
DifferenceEngine\$enableDebugComment
$enableDebugComment
Set this to true to add debug info to the HTML output.
Definition: DifferenceEngine.php:171
Revision\RevisionRecord\getId
getId()
Get revision ID.
Definition: RevisionRecord.php:273
DifferenceEngine\__construct
__construct( $context=null, $old=0, $new=0, $rcid=0, $refreshCache=false, $unhide=false)
#-
Definition: DifferenceEngine.php:208
DifferenceEngine\getDebugString
getDebugString()
Definition: DifferenceEngine.php:1352
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:573
Linker\revComment
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:1466
$value
$value
Definition: styleTest.css.php:49
DifferenceEngine\$mOldTags
string[] null $mOldTags
Change tags of $mOldRev or null if it does not exist / is not saved.
Definition: DifferenceEngine.php:119
$header
$header
Definition: updateCredits.php:35
Linker\getRevDeleteLink
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
$suppressed
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging a wrapping ErrorException $suppressed
Definition: hooks.txt:2213
$refreshCache
null for the 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 $refreshCache
Definition: hooks.txt:1627
Revision\RAW
const RAW
Definition: Revision.php:57
DifferenceEngine\getTitle
getTitle()
Definition: DifferenceEngine.php:312
DifferenceEngine\showDiffPage
showDiffPage( $diffOnly=false)
Definition: DifferenceEngine.php:465
DifferenceEngine\$mOldid
int false null $mOldid
Revision ID for the old revision.
Definition: DifferenceEngine.php:67
Linker\titleAttrib
static titleAttrib( $name, $options=null, array $msgParams=[])
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
Definition: Linker.php:1967
DifferenceEngine\$isContentOverridden
bool $isContentOverridden
Was the content overridden via setContent()? If the content was overridden, most internal state (e....
Definition: DifferenceEngine.php:161
DifferenceEngine\DIFF_VERSION
const DIFF_VERSION
Constant to indicate diff cache compatibility.
Definition: DifferenceEngine.php:59
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
DifferenceEngine\intermediateEditsMsg
static intermediateEditsMsg( $numEdits, $numUsers, $limit)
Get a notice about how many intermediate edits and users there are.
Definition: DifferenceEngine.php:1477
DifferenceEngine\showMissingRevision
showMissingRevision()
Definition: DifferenceEngine.php:442
Content
Base interface for content objects.
Definition: Content.php:34
DifferenceEngine\loadRevisionIds
loadRevisionIds()
Load revision IDs.
Definition: DifferenceEngine.php:1706
DifferenceEngine\getParserOutput
getParserOutput(WikiPage $page, Revision $rev)
Definition: DifferenceEngine.php:911
DifferenceEngine\getNewRevision
getNewRevision()
Get the right side of the diff.
Definition: DifferenceEngine.php:388
Revision\RevisionRecord\getPageAsLinkTarget
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
Definition: RevisionRecord.php:345
Title
Represents a title within MediaWiki.
Definition: Title.php:39
SlotDiffRenderer
Renders a diff for a single slot (that is, a diff between two content objects).
Definition: SlotDiffRenderer.php:39
$cache
$cache
Definition: mcc.php:33
DifferenceEngine\$mDiffLang
Language $mDiffLang
Definition: DifferenceEngine.php:142
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:378
DifferenceEngine\setRevisions
setRevisions(RevisionRecord $oldRevision=null, RevisionRecord $newRevision)
Use specified text instead of loading from the database.
Definition: DifferenceEngine.php:1635
DifferenceEngine\getMarkPatrolledLinkInfo
getMarkPatrolledLinkInfo()
Returns an array of meta data needed to build a "mark as patrolled" link and adds the mediawiki....
Definition: DifferenceEngine.php:755
DifferenceEngine\$mOldPage
Title null $mOldPage
Title of $mOldRev or null if the old revision does not exist or does not belong to a page.
Definition: DifferenceEngine.php:106
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:77
$rev
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:1808
as
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
Html\openElement
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:252
DifferenceEngine\getExtraCacheKeys
getExtraCacheKeys()
Implements DifferenceEngineSlotDiffRenderer::getExtraCacheKeys().
Definition: DifferenceEngine.php:1189
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
DifferenceEngine
DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
Definition: DifferenceEngine.php:49
DifferenceEngineSlotDiffRenderer
B/C adapter for turning a DifferenceEngine into a SlotDiffRenderer.
Definition: DifferenceEngineSlotDiffRenderer.php:31
RecentChange\isInRCLifespan
static isInRCLifespan( $timestamp, $tolerance=0)
Check whether the given timestamp is new enough to have a RC row with a given tolerance as the recent...
Definition: RecentChange.php:1172
Revision\RevisionRecord\getContent
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
Definition: RevisionRecord.php:167
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3090
$oldminor
passed in as a query string parameter to the various URLs constructed here(i.e. $nextlink) $rdel also included in $oldHeader $oldminor
Definition: hooks.txt:1312
DifferenceEngine\$mMarkPatrolledLink
string $mMarkPatrolledLink
Link to action=markpatrolled.
Definition: DifferenceEngine.php:179
$content
$content
Definition: pageupdater.txt:72
DifferenceEngine\localiseLineNumbers
localiseLineNumbers( $text)
Replace line numbers with the text in the user's language.
Definition: DifferenceEngine.php:1386
DifferenceEngine\$mOldContent
Content null $mOldContent
Definition: DifferenceEngine.php:132
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1132
DifferenceEngine\debug
debug( $generator="internal")
Generate a debug comment indicating diff generating time, server node, and generator backend.
Definition: DifferenceEngine.php:1336
DifferenceEngine\renderNewRevision
renderNewRevision()
Show the new revision of the page.
Definition: DifferenceEngine.php:829
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
CONTENT_MODEL_TEXT
const CONTENT_MODEL_TEXT
Definition: Defines.php:238
DifferenceEngine\getDiff
getDiff( $otitle, $ntitle, $notice='')
Get complete diff table, including header.
Definition: DifferenceEngine.php:973
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
DifferenceEngine\$mNewRev
Revision null $mNewRev
New revision (right pane).
Definition: DifferenceEngine.php:99
DeprecationHelper
trait DeprecationHelper
Use this trait in classes which have properties for which public access is deprecated.
Definition: DeprecationHelper.php:45
DifferenceEngine\generateContentDiffBody
generateContentDiffBody(Content $old, Content $new)
Generate a diff, no caching.
Definition: DifferenceEngine.php:1232
DifferenceEngine\mapDiffPrevNext
mapDiffPrevNext( $old, $new)
Maps a revision pair definition as accepted by DifferenceEngine constructor to a pair of actual integ...
Definition: DifferenceEngine.php:1686
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
DifferenceEngine\addHeader
addHeader( $diff, $otitle, $ntitle, $multi='', $notice='')
Add the header to a diff body.
Definition: DifferenceEngine.php:1564
ChangeTags\formatSummaryRow
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:93
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:47
Language
Internationalisation code.
Definition: Language.php:35
DifferenceEngine\textDiff
textDiff( $otext, $ntext)
Generates diff, to be wrapped internally in a logging/instrumentation.
Definition: DifferenceEngine.php:1316
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:39
DifferenceEngine\$mNewPage
Title null $mNewPage
Title of $mNewRev or null if the new revision does not exist or does not belong to a page.
Definition: DifferenceEngine.php:113
TextSlotDiffRenderer
Renders a slot diff by doing a text diff on the native representation.
Definition: TextSlotDiffRenderer.php:37
DifferenceEngine\$mRefreshCache
bool $mRefreshCache
Refresh the diff cache.
Definition: DifferenceEngine.php:185
$out
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:813
DifferenceEngine\getMultiNotice
getMultiNotice()
If there are revisions between the ones being compared, return a note saying so.
Definition: DifferenceEngine.php:1432