Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.85% |
86 / 87 |
|
91.67% |
11 / 12 |
CRAP | |
0.00% |
0 / 1 |
EntitySchemaSlotDiffRenderer | |
98.85% |
86 / 87 |
|
91.67% |
11 / 12 |
24 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getDiff | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
localizeDiff | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
renderSchemaDiffRows | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
5 | |||
renderDiffOp | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
7 | |||
renderTextDiff | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
diffRow | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
diffContext | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
diffBlankLine | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
diffMarker | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
diffAddedLine | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
diffRemovedLine | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace EntitySchema\MediaWiki\Content; |
6 | |
7 | use Diff\DiffOp\AtomicDiffOp; |
8 | use Diff\DiffOp\Diff\Diff; |
9 | use Diff\DiffOp\DiffOp; |
10 | use Diff\DiffOp\DiffOpAdd; |
11 | use Diff\DiffOp\DiffOpChange; |
12 | use Diff\DiffOp\DiffOpRemove; |
13 | use EntitySchema\Services\Converter\EntitySchemaConverter; |
14 | use EntitySchema\Services\Diff\EntitySchemaDiffer; |
15 | use MediaWiki\Content\Content; |
16 | use MediaWiki\Context\IContextSource; |
17 | use MediaWiki\Html\Html; |
18 | use MessageLocalizer; |
19 | use SlotDiffRenderer; |
20 | use TextSlotDiffRenderer; |
21 | |
22 | /** |
23 | * @license GPL-2.0-or-later |
24 | */ |
25 | class EntitySchemaSlotDiffRenderer extends SlotDiffRenderer { |
26 | |
27 | private EntitySchemaConverter $schemaConverter; |
28 | |
29 | private EntitySchemaDiffer $schemaDiffer; |
30 | |
31 | private TextSlotDiffRenderer $textSlotDiffRenderer; |
32 | |
33 | private MessageLocalizer $msgLocalizer; |
34 | |
35 | public function __construct( |
36 | IContextSource $context, |
37 | TextSlotDiffRenderer $textSlotDiffRenderer |
38 | ) { |
39 | $this->schemaDiffer = new EntitySchemaDiffer(); |
40 | $this->schemaConverter = new EntitySchemaConverter(); |
41 | $this->textSlotDiffRenderer = $textSlotDiffRenderer; |
42 | $this->msgLocalizer = $context; |
43 | } |
44 | |
45 | /** |
46 | * @param EntitySchemaContent|null $oldContent |
47 | * @param EntitySchemaContent|null $newContent |
48 | * |
49 | * @return string |
50 | * @suppress PhanParamSignatureMismatch LSP violation |
51 | */ |
52 | public function getDiff( ?Content $oldContent = null, ?Content $newContent = null ): string { |
53 | $this->normalizeContents( $oldContent, $newContent, EntitySchemaContent::class ); |
54 | |
55 | $diff = $this->schemaDiffer->diffSchemas( |
56 | $this->schemaConverter->getFullArraySchemaData( $oldContent->getText() ), |
57 | $this->schemaConverter->getFullArraySchemaData( $newContent->getText() ) |
58 | ); |
59 | |
60 | return $this->renderSchemaDiffRows( $diff ); |
61 | } |
62 | |
63 | /** @inheritDoc */ |
64 | public function localizeDiff( string $diff, array $options = [] ) { |
65 | return $this->textSlotDiffRenderer->localizeDiff( $diff, $options ); |
66 | } |
67 | |
68 | public function renderSchemaDiffRows( Diff $diff ): string { |
69 | // split $diff into labels/descriptions/aliases (renderDiffOp()) |
70 | // and schema (renderTextDiff()) |
71 | $nameBadgeDiffOps = []; |
72 | if ( isset( $diff['labels'] ) ) { |
73 | $nameBadgeDiffOps[ |
74 | $this->msgLocalizer->msg( 'entityschema-diff-label' )->text() |
75 | ] = $diff['labels']; |
76 | } |
77 | if ( isset( $diff['descriptions'] ) ) { |
78 | $nameBadgeDiffOps[ |
79 | $this->msgLocalizer->msg( 'entityschema-diff-description' )->text() |
80 | ] = $diff['descriptions']; |
81 | } |
82 | if ( isset( $diff['aliases'] ) ) { |
83 | $nameBadgeDiffOps[ |
84 | $this->msgLocalizer->msg( 'entityschema-diff-aliases' )->text() |
85 | ] = $diff['aliases']; |
86 | } |
87 | $nameBadgeDiff = $this->renderDiffOp( [], new Diff( $nameBadgeDiffOps, true ) ); |
88 | |
89 | if ( isset( $diff['schemaText'] ) ) { |
90 | $schemaDiff = $this->renderTextDiff( |
91 | $this->msgLocalizer->msg( 'entityschema-diff-schema' )->text(), |
92 | $diff['schemaText'] |
93 | ); |
94 | } else { |
95 | $schemaDiff = ''; |
96 | } |
97 | |
98 | return $nameBadgeDiff . $schemaDiff; |
99 | } |
100 | |
101 | private function renderDiffOp( array $keys, DiffOp $diffOp ): string { |
102 | if ( $diffOp instanceof Diff ) { |
103 | $output = ''; |
104 | foreach ( $diffOp->getOperations() as $key => $op ) { |
105 | $moreKeys = $keys; |
106 | $moreKeys[] = $key; |
107 | $output .= $this->renderDiffOp( $moreKeys, $op ); |
108 | } |
109 | return $output; |
110 | } |
111 | |
112 | if ( $diffOp instanceof DiffOpRemove || $diffOp instanceof DiffOpChange ) { |
113 | $leftContext = $this->diffContext( implode( ' / ', $keys ) ); |
114 | $leftTds = $this->diffRemovedLine( $diffOp->getOldValue() ); |
115 | } else { |
116 | $leftContext = $this->diffContext( '' ); |
117 | $leftTds = $this->diffBlankLine(); |
118 | } |
119 | |
120 | if ( $diffOp instanceof DiffOpAdd || $diffOp instanceof DiffOpChange ) { |
121 | $rightContext = $this->diffContext( implode( ' / ', $keys ) ); |
122 | $rightTds = $this->diffAddedLine( $diffOp->getNewValue() ); |
123 | } else { |
124 | $rightContext = $this->diffContext( '' ); |
125 | $rightTds = $this->diffBlankLine(); |
126 | } |
127 | |
128 | $context = $this->diffRow( $leftContext . $rightContext ); |
129 | $changes = $this->diffRow( $leftTds . $rightTds ); |
130 | |
131 | return $context . $changes; |
132 | } |
133 | |
134 | /** |
135 | * @suppress PhanUndeclaredMethod |
136 | */ |
137 | private function renderTextDiff( string $key, AtomicDiffOp $diffOp ): string { |
138 | if ( $diffOp instanceof DiffOpAdd || $diffOp instanceof DiffOpRemove ) { |
139 | return $this->renderDiffOp( [ $key ], $diffOp ); |
140 | } |
141 | |
142 | /** @var DiffOpChange $diffOp */ |
143 | // Line 1 → schema / Line 1 |
144 | return preg_replace( |
145 | '/<td[^>]* class="diff-lineno"[^>]*>/', |
146 | '$0' . htmlspecialchars( $key ) . ' / ', |
147 | $this->textSlotDiffRenderer->getTextDiff( |
148 | trim( $diffOp->getOldValue() ), |
149 | trim( $diffOp->getNewValue() ) |
150 | ) |
151 | ); |
152 | } |
153 | |
154 | private function diffRow( string $content ): string { |
155 | return Html::rawElement( |
156 | 'tr', |
157 | [], |
158 | $content |
159 | ); |
160 | } |
161 | |
162 | private function diffContext( string $context ): string { |
163 | return Html::element( |
164 | 'td', |
165 | [ 'colspan' => '2', 'class' => 'diff-lineno' ], |
166 | $context |
167 | ); |
168 | } |
169 | |
170 | private function diffBlankLine(): string { |
171 | return Html::element( 'td', [ 'colspan' => '2' ] ); |
172 | } |
173 | |
174 | private function diffMarker( string $marker ): string { |
175 | return Html::element( |
176 | 'td', |
177 | [ 'class' => 'diff-marker', 'data-marker' => $marker ] |
178 | ); |
179 | } |
180 | |
181 | private function diffAddedLine( string $line ): string { |
182 | return $this->diffMarker( '+' ) . Html::element( |
183 | 'td', |
184 | [ 'class' => 'diff-addedline' ], |
185 | $line |
186 | ); |
187 | } |
188 | |
189 | private function diffRemovedLine( string $line ): string { |
190 | return $this->diffMarker( '−' ) . Html::element( |
191 | 'td', |
192 | [ 'class' => 'diff-deletedline' ], |
193 | $line |
194 | ); |
195 | } |
196 | |
197 | } |