Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
90.91% covered (success)
90.91%
10 / 11
CRAP
93.98% covered (success)
93.98%
78 / 83
EntitySchema\MediaWiki\Content\EntitySchemaSlotDiffRenderer
0.00% covered (danger)
0.00%
0 / 1
90.91% covered (success)
90.91%
10 / 11
26.15
93.98% covered (success)
93.98%
78 / 83
 __construct
0.00% covered (danger)
0.00%
0 / 1
5.16
58.33% covered (warning)
58.33%
7 / 12
 getDiff
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 renderSchemaDiffRows
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
17 / 17
 renderDiffOp
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
20 / 20
 renderTextDiff
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
8 / 8
 diffRow
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 diffContext
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 diffBlankLine
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 diffMarker
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 diffAddedLine
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 diffRemovedLine
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
<?php
namespace EntitySchema\MediaWiki\Content;
use Content;
use ContentHandler;
use Diff\DiffOp\AtomicDiffOp;
use Diff\DiffOp\Diff\Diff;
use Diff\DiffOp\DiffOp;
use Diff\DiffOp\DiffOpAdd;
use Diff\DiffOp\DiffOpChange;
use Diff\DiffOp\DiffOpRemove;
use Html;
use IContextSource;
use MessageLocalizer;
use RequestContext;
use SlotDiffRenderer;
use TextSlotDiffRenderer;
use UnexpectedValueException;
use EntitySchema\Services\Diff\SchemaDiffer;
use EntitySchema\Services\SchemaConverter\SchemaConverter;
/**
 * @license GPL-2.0-or-later
 */
class EntitySchemaSlotDiffRenderer extends SlotDiffRenderer {
    /** @var SchemaConverter */
    private $schemaConverter;
    /** @var SchemaDiffer */
    private $schemaDiffer;
    /** @var TextSlotDiffRenderer */
    private $textSlotDiffRenderer;
    /** @var MessageLocalizer */
    private $msgLocalizer;
    public function __construct(
        IContextSource $context = null,
        TextSlotDiffRenderer $textSlotDiffRenderer = null
    ) {
        if ( $context === null ) {
            $context = RequestContext::getMain();
        }
        if ( $textSlotDiffRenderer === null ) {
            $textSlotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
                ->getSlotDiffRenderer( $context );
            if ( !is_a( $textSlotDiffRenderer, TextSlotDiffRenderer::class ) ) {
                throw new UnexpectedValueException( 'Expected a TextSlotDiffRenderer' );
            }
        }
        $this->schemaDiffer = new SchemaDiffer();
        $this->schemaConverter = new SchemaConverter();
        $this->textSlotDiffRenderer = $textSlotDiffRenderer;
        $this->msgLocalizer = $context;
    }
    /**
     * @phan-suppress PhanParamSignatureMismatch
     *
     * @param EntitySchemaContent|null $oldContent
     * @param EntitySchemaContent|null $newContent
     *
     * @return string
     */
    public function getDiff( Content $oldContent = null, Content $newContent = null ) {
        $this->normalizeContents( $oldContent, $newContent, EntitySchemaContent::class );
        $diff = $this->schemaDiffer->diffSchemas(
            $this->schemaConverter->getFullArraySchemaData( $oldContent->getText() ),
            $this->schemaConverter->getFullArraySchemaData( $newContent->getText() )
        );
        return $this->renderSchemaDiffRows( $diff );
    }
    public function renderSchemaDiffRows( Diff $diff ) {
        // split $diff into labels/descriptions/aliases (renderDiffOp())
        // and schema (renderTextDiff())
        $nameBadgeDiffOps = [];
        if ( isset( $diff['labels'] ) ) {
            $nameBadgeDiffOps[
                $this->msgLocalizer->msg( 'entityschema-diff-label' )->text()
                ] = $diff['labels'];
        }
        if ( isset( $diff['descriptions'] ) ) {
            $nameBadgeDiffOps[
                $this->msgLocalizer->msg( 'entityschema-diff-description' )->text()
            ] = $diff['descriptions'];
        }
        if ( isset( $diff['aliases'] ) ) {
            $nameBadgeDiffOps[
                $this->msgLocalizer->msg( 'entityschema-diff-aliases' )->text()
            ] = $diff['aliases'];
        }
        $nameBadgeDiff = $this->renderDiffOp( [], new Diff( $nameBadgeDiffOps, true ) );
        if ( isset( $diff['schemaText'] ) ) {
            $schemaDiff = $this->renderTextDiff(
                $this->msgLocalizer->msg( 'entityschema-diff-schema' )->text(),
                $diff['schemaText']
            );
        } else {
            $schemaDiff = '';
        }
        return $nameBadgeDiff . $schemaDiff;
    }
    private function renderDiffOp( array $keys, DiffOp $diffOp ) {
        if ( $diffOp instanceof Diff ) {
            $output = '';
            foreach ( $diffOp->getOperations() as $key => $op ) {
                $moreKeys = $keys;
                $moreKeys[] = $key;
                $output .= $this->renderDiffOp( $moreKeys, $op );
            }
            return $output;
        }
        if ( $diffOp instanceof DiffOpRemove || $diffOp instanceof DiffOpChange ) {
            $leftContext = $this->diffContext( implode( ' / ', $keys ) );
            $leftTds = $this->diffRemovedLine( $diffOp->getOldValue() );
        } else {
            $leftContext = $this->diffContext( '' );
            $leftTds = $this->diffBlankLine();
        }
        if ( $diffOp instanceof DiffOpAdd || $diffOp instanceof DiffOpChange ) {
            $rightContext = $this->diffContext( implode( ' / ', $keys ) );
            $rightTds = $this->diffAddedLine( $diffOp->getNewValue() );
        } else {
            $rightContext = $this->diffContext( '' );
            $rightTds = $this->diffBlankLine();
        }
        $context = $this->diffRow( $leftContext . $rightContext );
        $changes = $this->diffRow( $leftTds . $rightTds );
        return $context . $changes;
    }
    /**
     * @suppress PhanUndeclaredMethod
     */
    private function renderTextDiff( $key, AtomicDiffOp $diffOp ) {
        if ( $diffOp instanceof DiffOpAdd || $diffOp instanceof DiffOpRemove ) {
            return $this->renderDiffOp( [ $key ], $diffOp );
        }
        /** @var DiffOpChange $diffOp */
        // Line 1 → schema / Line 1
        return preg_replace(
            '/<td[^>]* class="diff-lineno"[^>]*>/',
            '$0' . htmlspecialchars( $key ) . ' / ',
            $this->textSlotDiffRenderer->getTextDiff(
                trim( $diffOp->getOldValue() ),
                trim( $diffOp->getNewValue() )
            )
        );
    }
    private function diffRow( $content ) {
        return Html::rawElement(
            'tr',
            [],
            $content
        );
    }
    private function diffContext( $context ) {
        return Html::element(
            'td',
            [ 'colspan' => '2', 'class' => 'diff-lineno' ],
            $context
        );
    }
    private function diffBlankLine() {
        return Html::element( 'td', [ 'colspan' => '2' ] );
    }
    private function diffMarker( $marker ) {
        return Html::element(
            'td',
            [ 'class' => 'diff-marker' ],
            $marker
        );
    }
    private function diffAddedLine( $line ) {
        return $this->diffMarker( '+' ) . Html::element(
            'td',
            [ 'class' => 'diff-addedline' ],
            $line
        );
    }
    private function diffRemovedLine( $line ) {
        return $this->diffMarker( '−' ) . Html::element(
            'td',
            [ 'class' => 'diff-deletedline' ],
            $line
        );
    }
}