wiki.techinc.nl/includes/diff/TextSlotDiffRenderer.php
Umherirrender 1b29f07440 Use namespaced classes
Changes to the use statements done automatically via script
Addition of missing use statement done manually

Change-Id: I73fb416573f5af600e529d224b5beb5d2e3d27d3
2024-10-21 20:41:20 +02:00

367 lines
10 KiB
PHP

<?php
/**
* Renders a slot diff by doing a text diff on the native representation.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup DifferenceEngine
*/
use MediaWiki\Content\Content;
use MediaWiki\Content\TextContent;
use MediaWiki\Context\IContextSource;
use MediaWiki\Context\RequestContext;
use MediaWiki\Diff\TextDiffer\ManifoldTextDiffer;
use MediaWiki\Diff\TextDiffer\TextDiffer;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\HookContainer\HookRunner;
use MediaWiki\Html\Html;
use MediaWiki\Language\Language;
use MediaWiki\MediaWikiServices;
use MediaWiki\PoolCounter\PoolCounterWorkViaCallback;
use MediaWiki\Status\Status;
use MediaWiki\Title\Title;
use OOUI\ToggleSwitchWidget;
use Wikimedia\Stats\IBufferingStatsdDataFactory;
use Wikimedia\Stats\StatsFactory;
/**
* Renders a slot diff by doing a text diff on the native representation.
*
* If you want to use this without content objects (to call getTextDiff() on some
* non-content-related texts), obtain an instance with
* ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
* ->getSlotDiffRenderer( RequestContext::getMain() )
*
* @ingroup DifferenceEngine
*/
class TextSlotDiffRenderer extends SlotDiffRenderer {
/** Use the PHP diff implementation (DiffEngine). */
public const ENGINE_PHP = 'php';
/** Use the wikidiff2 PHP module. */
public const ENGINE_WIKIDIFF2 = 'wikidiff2';
/** Use the wikidiff2 PHP module. */
public const ENGINE_WIKIDIFF2_INLINE = 'wikidiff2inline';
/** Use an external executable. */
public const ENGINE_EXTERNAL = 'external';
public const INLINE_LEGEND_KEY = '10_mw-diff-inline-legend';
public const INLINE_SWITCHER_KEY = '60_mw-diff-inline-switch';
/** @var StatsFactory|null */
private $statsFactory;
/** @var HookRunner|null */
private $hookRunner;
/** @var string|null */
private $format;
/** @var string */
private $contentModel;
/** @var TextDiffer|null */
private $textDiffer;
/** @var bool */
private $inlineToggleEnabled = false;
/** @inheritDoc */
public function getExtraCacheKeys() {
return $this->textDiffer->getCacheKeys( [ $this->format ] );
}
/**
* Convenience helper to use getTextDiff without an instance.
* @param string $oldText
* @param string $newText
* @param array $options
* @return string
*/
public static function diff( $oldText, $newText, $options = [] ) {
/** @var TextSlotDiffRenderer $slotDiffRenderer */
$slotDiffRenderer = MediaWikiServices::getInstance()
->getContentHandlerFactory()
->getContentHandler( CONTENT_MODEL_TEXT )
->getSlotDiffRenderer( RequestContext::getMain(), $options );
'@phan-var TextSlotDiffRenderer $slotDiffRenderer';
return $slotDiffRenderer->getTextDiff( $oldText, $newText );
}
/**
* This has no effect since MW 1.43.
*
* @internal Use ContentHandler::createTextSlotDiffRenderer instead
* @param IBufferingStatsdDataFactory $statsdDataFactory
*/
public function setStatsdDataFactory( IBufferingStatsdDataFactory $statsdDataFactory ) {
wfDeprecated( __METHOD__, '1.43' );
}
/**
* @internal Use ContentHandler::createTextSlotDiffRenderer instead
* @param StatsFactory $statsFactory
*/
public function setStatsFactory( StatsFactory $statsFactory ) {
$this->statsFactory = $statsFactory;
}
/**
* This has no effect since MW 1.41. The language is now injected via setTextDiffer().
*
* @param Language $language
* @deprecated since 1.41
*/
public function setLanguage( Language $language ) {
wfDeprecated( __METHOD__, '1.41' );
}
/**
* @internal Use ContentHandler::createTextSlotDiffRenderer instead
* @since 1.41
* @param HookContainer $hookContainer
*/
public function setHookContainer( HookContainer $hookContainer ): void {
$this->hookRunner = new HookRunner( $hookContainer );
}
/**
* @param string $contentModel
* @since 1.41
*/
public function setContentModel( string $contentModel ) {
$this->contentModel = $contentModel;
}
/**
* Set which diff engine to use.
*
* @param string $type One of the ENGINE_* constants.
* @param null $executable Must be null since 1.41. Previously a path to execute.
*/
public function setEngine( $type, $executable = null ) {
if ( $executable !== null ) {
throw new \InvalidArgumentException(
'The $executable parameter is no longer supported and must be null'
);
}
switch ( $type ) {
case self::ENGINE_PHP:
$engine = 'php';
$format = 'table';
break;
case self::ENGINE_WIKIDIFF2:
$engine = 'wikidiff2';
$format = 'table';
break;
case self::ENGINE_EXTERNAL:
$engine = 'external';
$format = 'external';
break;
case self::ENGINE_WIKIDIFF2_INLINE:
$engine = 'wikidiff2';
$format = 'inline';
break;
default:
throw new \InvalidArgumentException( '$type ' .
'must be one of the TextSlotDiffRenderer::ENGINE_* constants' );
}
if ( $this->textDiffer instanceof ManifoldTextDiffer ) {
$this->textDiffer->setEngine( $engine );
}
$this->setFormat( $format );
}
/**
* Set the TextDiffer format
*
* @since 1.41
* @param string $format
*/
public function setFormat( $format ) {
$this->format = $format;
}
/**
* @param TextDiffer $textDiffer
*/
public function setTextDiffer( TextDiffer $textDiffer ) {
$this->textDiffer = $textDiffer;
}
/**
* Get the current TextDiffer, or throw an exception if setTextDiffer() has
* not been called.
*
* @return TextDiffer
*/
private function getTextDiffer(): TextDiffer {
return $this->textDiffer;
}
/**
* Set a flag indicating whether the inline toggle switch is shown.
*
* @since 1.41
* @param bool $enabled
*/
public function setInlineToggleEnabled( $enabled = true ) {
$this->inlineToggleEnabled = $enabled;
}
/**
* Get the content model ID that this renderer acts on
*
* @since 1.41
* @return string
*/
public function getContentModel(): string {
return $this->contentModel;
}
/** @inheritDoc */
public function getDiff( ?Content $oldContent = null, ?Content $newContent = null ) {
$this->normalizeContents( $oldContent, $newContent, TextContent::class );
$oldText = $oldContent->serialize();
$newText = $newContent->serialize();
return $this->getTextDiff( $oldText, $newText );
}
public function localizeDiff( $diff, $options = [] ) {
return $this->textDiffer->localize( $this->format, $diff, $options );
}
/**
* @inheritDoc
*/
public function getTablePrefix( IContextSource $context, Title $newTitle ): array {
$parts = $this->getTextDiffer()->getTablePrefixes( $this->format );
$showDiffToggleSwitch = $this->inlineToggleEnabled && $this->getTextDiffer()->hasFormat( 'inline' );
// If we support the inline type, add a toggle switch
if ( $showDiffToggleSwitch ) {
$values = $context->getRequest()->getValues();
$isInlineDiffType = $this->format === 'inline';
$values[ 'diff-type' ] = $isInlineDiffType ? 'table' : 'inline';
unset( $values[ 'title' ] );
$parts[self::INLINE_SWITCHER_KEY] = Html::rawElement( 'div',
[ 'class' => 'mw-diffPage-inlineToggle-container' ],
new OOUI\FieldLayout(
new ToggleSwitchWidget( [
'id' => 'mw-diffPage-inline-toggle-switch',
'href' => $newTitle->getLocalURL( $values ),
'value' => $isInlineDiffType,
'title' => $context->msg( 'diff-inline-switch-desc' )->plain()
] ),
[
'id' => 'mw-diffPage-inline-toggle-switch-layout',
'label' => $context->msg( 'diff-inline-format-label' )->plain(),
'infusable' => true,
'title' => $context->msg( 'diff-inline-switch-desc' )->plain()
]
),
);
}
// Add an empty placeholder for the legend is added when it's not in
// use and other items have been added.
$parts += [ self::INLINE_LEGEND_KEY => null, self::INLINE_SWITCHER_KEY => null ];
// Allow extensions to add other parts to this area (or modify the legend).
$this->hookRunner->onTextSlotDiffRendererTablePrefix( $this, $context, $parts );
if ( count( $parts ) > 1 && $parts[self::INLINE_LEGEND_KEY] === null ) {
$parts[self::INLINE_LEGEND_KEY] = Html::element( 'div' );
}
return $parts;
}
/**
* Diff the text representations of two content objects (or just two pieces of text in general).
* @param string $oldText
* @param string $newText
* @return string HTML. One or more <tr> tags, or an empty string if the inputs are identical.
*/
public function getTextDiff( string $oldText, string $newText ) {
$diff = function () use ( $oldText, $newText ) {
$time = microtime( true );
$result = $this->getTextDiffInternal( $oldText, $newText );
$time = intval( ( microtime( true ) - $time ) * 1000 );
if ( $this->statsFactory ) {
$this->statsFactory->getTiming( 'diff_text_seconds' )
->copyToStatsdAt( 'diff_time' )
->observe( $time );
}
return $result;
};
/**
* @param Status $status
* @throws FatalError
* @return never
*/
$error = static function ( $status ) {
throw new FatalError( $status->getWikiText() );
};
// Use PoolCounter if the diff looks like it can be expensive
if ( strlen( $oldText ) + strlen( $newText ) > 20000 ) {
$work = new PoolCounterWorkViaCallback( 'diff',
md5( $oldText ) . md5( $newText ),
[ 'doWork' => $diff, 'error' => $error ]
);
return $work->execute();
}
return $diff();
}
/**
* Diff the text representations of two content objects (or just two pieces of text in general).
* This does the actual diffing, getTextDiff() wraps it with logging and resource limiting.
* @param string $oldText
* @param string $newText
* @return string
* @throws Exception
*/
protected function getTextDiffInternal( $oldText, $newText ) {
$oldText = str_replace( "\r\n", "\n", $oldText );
$newText = str_replace( "\r\n", "\n", $newText );
if ( $oldText === $newText ) {
return '';
}
$textDiffer = $this->getTextDiffer();
$diffText = $textDiffer->render( $oldText, $newText, $this->format );
return $textDiffer->addRowWrapper( $this->format, $diffText );
}
}