ItemDiffView::getChangedLine()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
3
namespace Wikibase\Repo\Diff;
4
5
use Diff\DiffOp\Diff\Diff;
6
use Diff\DiffOp\DiffOp;
7
use Diff\DiffOp\DiffOpAdd;
8
use Diff\DiffOp\DiffOpChange;
9
use Diff\DiffOp\DiffOpRemove;
10
use Html;
11
use InvalidArgumentException;
12
use LanguageCode;
13
use MessageLocalizer;
14
use MWException;
15
use Site;
16
use SiteLookup;
17
use Wikibase\DataModel\Entity\ItemId;
18
use Wikibase\DataModel\Services\EntityId\EntityIdFormatter;
19
20
/**
21
 * Class for generating views of DiffOp objects.
22
 *
23
 * @license GPL-2.0-or-later
24
 */
25
class ItemDiffView implements DiffView {
26
27
	/**
28
	 * @var string[]
29
	 */
30
	private $path;
31
32
	/**
33
	 * @var Diff
34
	 */
35
	private $diff;
36
37
	/**
38
	 * @var SiteLookup
39
	 */
40
	private $siteLookup;
41
42
	/**
43
	 * @var EntityIdFormatter
44
	 */
45
	private $entityIdFormatter;
46
47
	/**
48
	 * @var MessageLocalizer
49
	 */
50
	private $messageLocalizer;
51
52
	/**
53
	 * @var string
54
	 */
55
	private $siteLinkPath;
56
57
	/**
58
	 * @param string[] $path
59
	 * @param Diff $diff
60
	 * @param SiteLookup $siteLookup
61
	 * @param EntityIdFormatter $entityIdFormatter that must return only HTML! otherwise injections might be possible
62
	 * @param MessageLocalizer $messageLocalizer
63
	 */
64
	public function __construct(
65
		array $path,
66
		Diff $diff,
67
		SiteLookup $siteLookup,
68
		EntityIdFormatter $entityIdFormatter,
69
		MessageLocalizer $messageLocalizer
70
	) {
71
		$this->path = $path;
72
		$this->diff = $diff;
73
		$this->siteLookup = $siteLookup;
74
		$this->entityIdFormatter = $entityIdFormatter;
75
		$this->messageLocalizer = $messageLocalizer;
76
77
		$this->siteLinkPath = $this->messageLocalizer->msg( 'wikibase-diffview-link' )->text();
78
	}
79
80
	/**
81
	 * Builds and returns the HTML to represent the Diff.
82
	 *
83
	 * @return string
84
	 */
85
	public function getHtml() {
86
		return $this->generateOpHtml( $this->path, $this->diff );
87
	}
88
89
	/**
90
	 * Does the actual work.
91
	 *
92
	 * @param string[] $path
93
	 * @param DiffOp $op
94
	 *
95
	 * @return string
96
	 * @throws MWException
97
	 */
98
	protected function generateOpHtml( array $path, DiffOp $op ) {
99
		if ( $op->isAtomic() ) {
100
			$localizedPath = $path;
101
102
			$translatedLinkSubPath = $this->messageLocalizer->msg(
103
				'wikibase-diffview-link-' . $path[2]
104
			);
105
106
			if ( !$translatedLinkSubPath->isDisabled() ) {
107
				$localizedPath[2] = $translatedLinkSubPath->text();
108
			}
109
110
			$html = $this->generateDiffHeaderHtml( implode( ' / ', $localizedPath ) );
111
112
			//TODO: no path, but localized section title
113
114
			//FIXME: complex objects as values?
115
			if ( $op->getType() === 'add' ) {
116
				/** @var DiffOpAdd $op */
117
				'@phan-var DiffOpAdd $op';
118
				$html .= $this->generateChangeOpHtml( null, $op->getNewValue(), $path );
119
			} elseif ( $op->getType() === 'remove' ) {
120
				/** @var DiffOpRemove $op */
121
				'@phan-var DiffOpRemove $op';
122
				$html .= $this->generateChangeOpHtml( $op->getOldValue(), null, $path );
123
			} elseif ( $op->getType() === 'change' ) {
124
				/** @var DiffOpChange $op */
125
				'@phan-var DiffOpChange $op';
126
				$html .= $this->generateChangeOpHtml( $op->getOldValue(), $op->getNewValue(), $path );
127
			} else {
128
				throw new MWException( 'Invalid diffOp type' );
129
			}
130
		} else {
131
			$html = '';
132
			// @phan-suppress-next-line PhanTypeNoPropertiesForeach
133
			foreach ( $op as $key => $subOp ) {
0 ignored issues
show
Bug introduced by
The expression $op of type object<Diff\DiffOp\DiffOp> is not traversable.
Loading history...
134
				$html .= $this->generateOpHtml(
135
					array_merge( $path, [ $key ] ),
136
					$subOp
137
				);
138
			}
139
		}
140
141
		return $html;
142
	}
143
144
	/**
145
	 * Generates HTML for an change diffOp
146
	 *
147
	 * @param string|null $oldValue
148
	 * @param string|null $newValue
149
	 * @param string[] $path
150
	 *
151
	 * @return string
152
	 */
153
	protected function generateChangeOpHtml( $oldValue, $newValue, array $path ) {
154
		//TODO: use WordLevelDiff!
155
		$html = Html::openElement( 'tr' );
156
		if ( $oldValue !== null ) {
157
			$html .= Html::rawElement( 'td', [ 'class' => 'diff-marker' ], '-' );
158
			$html .= Html::rawElement( 'td', [ 'class' => 'diff-deletedline' ],
159
				Html::rawElement( 'div', [], $this->getDeletedLine( $oldValue, $path ) ) );
160
		}
161
		if ( $newValue !== null ) {
162
			if ( $oldValue === null ) {
163
				$html .= Html::rawElement( 'td', [ 'colspan' => '2' ], '&nbsp;' );
164
			}
165
			$html .= Html::rawElement( 'td', [ 'class' => 'diff-marker' ], '+' );
166
			$html .= Html::rawElement( 'td', [ 'class' => 'diff-addedline' ],
167
				Html::rawElement( 'div', [], $this->getAddedLine( $newValue, $path ) ) );
168
		}
169
		$html .= Html::closeElement( 'tr' );
170
171
		return $html;
172
	}
173
174
	/**
175
	 * @param string $value
176
	 * @param string[] $path
177
	 *
178
	 * @return string
179
	 */
180
	private function getDeletedLine( $value, array $path ) {
181
		return $this->getChangedLine( 'del', $value, $path );
182
	}
183
184
	/**
185
	 * @param string $value
186
	 * @param string[] $path
187
	 *
188
	 * @return string
189
	 */
190
	private function getAddedLine( $value, array $path ) {
191
		return $this->getChangedLine( 'ins', $value, $path );
192
	}
193
194
	/**
195
	 * @param string $tag
196
	 * @param string $value
197
	 * @param string[] $path
198
	 *
199
	 * @return string
200
	 */
201
	private function getChangedLine( $tag, $value, array $path ) {
202
		if ( $path[2] === 'badges' ) {
203
			$content = $this->getBadgeLinkElement( $value );
204
		} else {
205
			$content = $this->getSiteLinkElement( $path[1], $value );
206
		}
207
208
		return Html::rawElement( $tag, [ 'class' => 'diffchange diffchange-inline' ], $content );
209
	}
210
211
	/**
212
	 * @param string $siteId
213
	 * @param string $pageName
214
	 *
215
	 * @return string
216
	 */
217
	private function getSiteLinkElement( $siteId, $pageName ) {
218
		$site = $this->siteLookup->getSite( $siteId );
219
220
		$tagName = 'span';
221
		$attrs = [
222
			'dir' => 'auto',
223
		];
224
225
		if ( $site instanceof Site ) {
0 ignored issues
show
Bug introduced by
The class Site does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
226
			// Otherwise it may have been deleted from the sites table
227
			$tagName = 'a';
228
			$attrs['href'] = $site->getPageUrl( $pageName );
229
			$attrs['hreflang'] = LanguageCode::bcp47( $site->getLanguageCode() );
230
		}
231
232
		return Html::element( $tagName, $attrs, $pageName );
233
	}
234
235
	/**
236
	 * @param string $idString
237
	 *
238
	 * @return string HTML
239
	 */
240
	private function getBadgeLinkElement( $idString ) {
241
		try {
242
			$itemId = new ItemId( $idString );
243
		} catch ( InvalidArgumentException $ex ) {
244
			return htmlspecialchars( $idString );
245
		}
246
247
		return $this->entityIdFormatter->formatEntityId( $itemId );
248
	}
249
250
	/**
251
	 * Generates HTML for the header of the diff operation
252
	 *
253
	 * @param string $name
254
	 *
255
	 * @return string
256
	 */
257
	protected function generateDiffHeaderHtml( $name ) {
258
		$html = Html::openElement( 'tr' );
259
		$html .= Html::element( 'td', [ 'colspan' => '2', 'class' => 'diff-lineno' ], $name );
260
		$html .= Html::element( 'td', [ 'colspan' => '2', 'class' => 'diff-lineno' ], $name );
261
		$html .= Html::closeElement( 'tr' );
262
263
		return $html;
264
	}
265
266
}
267