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 Content; |
8 | use Diff\DiffOp\AtomicDiffOp; |
9 | use Diff\DiffOp\Diff\Diff; |
10 | use Diff\DiffOp\DiffOp; |
11 | use Diff\DiffOp\DiffOpAdd; |
12 | use Diff\DiffOp\DiffOpChange; |
13 | use Diff\DiffOp\DiffOpRemove; |
14 | use EntitySchema\Services\Converter\EntitySchemaConverter; |
15 | use EntitySchema\Services\Diff\EntitySchemaDiffer; |
16 | use 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 | public function localizeDiff( string $diff, array $options = [] ) { |
64 | return $this->textSlotDiffRenderer->localizeDiff( $diff, $options ); |
65 | } |
66 | |
67 | public function renderSchemaDiffRows( Diff $diff ): string { |
68 | // split $diff into labels/descriptions/aliases (renderDiffOp()) |
69 | // and schema (renderTextDiff()) |
70 | $nameBadgeDiffOps = []; |
71 | if ( isset( $diff['labels'] ) ) { |
72 | $nameBadgeDiffOps[ |
73 | $this->msgLocalizer->msg( 'entityschema-diff-label' )->text() |
74 | ] = $diff['labels']; |
75 | } |
76 | if ( isset( $diff['descriptions'] ) ) { |
77 | $nameBadgeDiffOps[ |
78 | $this->msgLocalizer->msg( 'entityschema-diff-description' )->text() |
79 | ] = $diff['descriptions']; |
80 | } |
81 | if ( isset( $diff['aliases'] ) ) { |
82 | $nameBadgeDiffOps[ |
83 | $this->msgLocalizer->msg( 'entityschema-diff-aliases' )->text() |
84 | ] = $diff['aliases']; |
85 | } |
86 | $nameBadgeDiff = $this->renderDiffOp( [], new Diff( $nameBadgeDiffOps, true ) ); |
87 | |
88 | if ( isset( $diff['schemaText'] ) ) { |
89 | $schemaDiff = $this->renderTextDiff( |
90 | $this->msgLocalizer->msg( 'entityschema-diff-schema' )->text(), |
91 | $diff['schemaText'] |
92 | ); |
93 | } else { |
94 | $schemaDiff = ''; |
95 | } |
96 | |
97 | return $nameBadgeDiff . $schemaDiff; |
98 | } |
99 | |
100 | private function renderDiffOp( array $keys, DiffOp $diffOp ): string { |
101 | if ( $diffOp instanceof Diff ) { |
102 | $output = ''; |
103 | foreach ( $diffOp->getOperations() as $key => $op ) { |
104 | $moreKeys = $keys; |
105 | $moreKeys[] = $key; |
106 | $output .= $this->renderDiffOp( $moreKeys, $op ); |
107 | } |
108 | return $output; |
109 | } |
110 | |
111 | if ( $diffOp instanceof DiffOpRemove || $diffOp instanceof DiffOpChange ) { |
112 | $leftContext = $this->diffContext( implode( ' / ', $keys ) ); |
113 | $leftTds = $this->diffRemovedLine( $diffOp->getOldValue() ); |
114 | } else { |
115 | $leftContext = $this->diffContext( '' ); |
116 | $leftTds = $this->diffBlankLine(); |
117 | } |
118 | |
119 | if ( $diffOp instanceof DiffOpAdd || $diffOp instanceof DiffOpChange ) { |
120 | $rightContext = $this->diffContext( implode( ' / ', $keys ) ); |
121 | $rightTds = $this->diffAddedLine( $diffOp->getNewValue() ); |
122 | } else { |
123 | $rightContext = $this->diffContext( '' ); |
124 | $rightTds = $this->diffBlankLine(); |
125 | } |
126 | |
127 | $context = $this->diffRow( $leftContext . $rightContext ); |
128 | $changes = $this->diffRow( $leftTds . $rightTds ); |
129 | |
130 | return $context . $changes; |
131 | } |
132 | |
133 | /** |
134 | * @suppress PhanUndeclaredMethod |
135 | */ |
136 | private function renderTextDiff( string $key, AtomicDiffOp $diffOp ): string { |
137 | if ( $diffOp instanceof DiffOpAdd || $diffOp instanceof DiffOpRemove ) { |
138 | return $this->renderDiffOp( [ $key ], $diffOp ); |
139 | } |
140 | |
141 | /** @var DiffOpChange $diffOp */ |
142 | // Line 1 → schema / Line 1 |
143 | return preg_replace( |
144 | '/<td[^>]* class="diff-lineno"[^>]*>/', |
145 | '$0' . htmlspecialchars( $key ) . ' / ', |
146 | $this->textSlotDiffRenderer->getTextDiff( |
147 | trim( $diffOp->getOldValue() ), |
148 | trim( $diffOp->getNewValue() ) |
149 | ) |
150 | ); |
151 | } |
152 | |
153 | private function diffRow( string $content ): string { |
154 | return Html::rawElement( |
155 | 'tr', |
156 | [], |
157 | $content |
158 | ); |
159 | } |
160 | |
161 | private function diffContext( string $context ): string { |
162 | return Html::element( |
163 | 'td', |
164 | [ 'colspan' => '2', 'class' => 'diff-lineno' ], |
165 | $context |
166 | ); |
167 | } |
168 | |
169 | private function diffBlankLine(): string { |
170 | return Html::element( 'td', [ 'colspan' => '2' ] ); |
171 | } |
172 | |
173 | private function diffMarker( string $marker ): string { |
174 | return Html::element( |
175 | 'td', |
176 | [ 'class' => 'diff-marker', 'data-marker' => $marker ] |
177 | ); |
178 | } |
179 | |
180 | private function diffAddedLine( string $line ): string { |
181 | return $this->diffMarker( '+' ) . Html::element( |
182 | 'td', |
183 | [ 'class' => 'diff-addedline' ], |
184 | $line |
185 | ); |
186 | } |
187 | |
188 | private function diffRemovedLine( string $line ): string { |
189 | return $this->diffMarker( '−' ) . Html::element( |
190 | 'td', |
191 | [ 'class' => 'diff-deletedline' ], |
192 | $line |
193 | ); |
194 | } |
195 | |
196 | } |