Passed
Branch master (631cc3)
by Stephan
33:03
created

Element::buildFormattedTermAsLink()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 11
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 23
rs 9.0856
ccs 0
cts 12
cp 0
crap 6
1
<?php
2
3
/**
4
 * File holding the Lingo\Element class.
5
 *
6
 * This file is part of the MediaWiki extension Lingo.
7
 *
8
 * @copyright 2011 - 2018, Stephan Gambke
9
 * @license   GNU General Public License, version 2 (or any later version)
10
 *
11
 * The Lingo extension is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by the Free
13
 * Software Foundation; either version 2 of the License, or (at your option) any
14
 * later version.
15
 *
16
 * The Lingo extension is distributed in the hope that it will be useful, but
17
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19
 * details.
20
 *
21
 * You should have received a copy of the GNU General Public License along
22
 * with this program. If not, see <http://www.gnu.org/licenses/>.
23
 *
24
 * @author Stephan Gambke
25
 *
26
 * @file
27
 * @ingroup Lingo
28
 */
29
30
namespace Lingo;
31
32
use DOMDocument;
33
use DOMElement;
34
use DOMNode;
35
use DOMText;
36
use Title;
0 ignored issues
show
Bug introduced by
The type Title was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
37
38
/**
39
 * This class represents a term-definition pair.
40
 * One term may be related to several definitions.
41
 *
42
 * @ingroup Lingo
43
 */
44
class Element {
45
	const ELEMENT_TERM = 0;
46
	const ELEMENT_DEFINITION = 1;
47
	const ELEMENT_SOURCE = 2;
48
	const ELEMENT_LINK = 3;
49
	const ELEMENT_STYLE = 4;
50
51
	const ELEMENT_FIELDCOUNT = 5;  // number of fields stored for each element; (last field's index) + 1
52
53
	const LINK_TEMPLATE_ID = 'LingoLink';
54
55
	private $formattedTerm = null;
56
	private $formattedDefinitions = null;
57
58
	private $mDefinitions = [];
59
	private $mTerm = null;
60
61
	private $hasBeenDisplayed = false;
62
63
	/**
64
	 * Lingo\Element constructor.
65
	 *
66
	 * @param $term
67
	 * @param $definition
68
	 */
69 1
	public function __construct( &$term, &$definition = null ) {
70
71 1
		$this->mTerm = $term;
72
73 1
		if ( $definition ) {
74 1
			$this->addDefinition( $definition );
75
		}
76 1
	}
77
78
	/**
79
	 * @param array $definition
80
	 */
81
	public function addDefinition( &$definition ) {
82
		$this->mDefinitions[] = array_pad( $definition, self::ELEMENT_FIELDCOUNT, null );
83
	}
84
85
	/**
86
	 * @param DOMDocument $doc
87
	 *
88
	 * @return DOMNode|DOMText
89
	 */
90
	public function getFormattedTerm( DOMDocument &$doc ) {
91
92
		global $wgexLingoDisplayOnce;
93
94
		if ( $wgexLingoDisplayOnce && $this->hasBeenDisplayed ) {
95
			return $doc->createTextNode( $this->mTerm );
96
		}
97
98
		$this->hasBeenDisplayed = true;
99
100
		$this->buildFormattedTerm( $doc );
101
102
		return $this->formattedTerm->cloneNode( true );
103
	}
104
105
	/**
106
	 * @param DOMDocument $doc
107
	 */
108
	private function buildFormattedTerm( DOMDocument &$doc ) {
109
110
		// only create if not yet created
111
		if ( $this->formattedTerm === null || $this->formattedTerm->ownerDocument !== $doc ) {
112
113
			if ( $this->isSimpleLink() ) {
114
				$this->formattedTerm = $this->buildFormattedTermAsLink( $doc );
115
			} else {
116
				$this->formattedTerm = $this->buildFormattedTermAsTooltip( $doc );
117
			}
118
		}
119
	}
120
121
	/**
122
	 * @return bool
123
	 */
124
	private function isSimpleLink() {
125
		return count( $this->mDefinitions ) === 1 &&
126
			!is_string( $this->mDefinitions[ 0 ][ self::ELEMENT_DEFINITION ] ) &&
127
			is_string( $this->mDefinitions[ 0 ][ self::ELEMENT_LINK ] );
128
	}
129
130
	/**
131
	 * @param DOMDocument $doc
132
	 * @return DOMElement
133
	 */
134
	protected function buildFormattedTermAsLink( DOMDocument &$doc ) {
135
136
		// create Title object for target page
137
		$target = Title::newFromText( $this->mDefinitions[ 0 ][ self::ELEMENT_LINK ] );
138
139
		if ( !$target instanceof Title ) {
140
			$errorMessage = wfMessage( 'lingo-invalidlinktarget', $this->mTerm, $this->mDefinitions[ 0 ][ self::ELEMENT_LINK ] )->text();
0 ignored issues
show
Bug introduced by
The function wfMessage was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

140
			$errorMessage = /** @scrutinizer ignore-call */ wfMessage( 'lingo-invalidlinktarget', $this->mTerm, $this->mDefinitions[ 0 ][ self::ELEMENT_LINK ] )->text();
Loading history...
141
			$errorDefinition = [ self::ELEMENT_DEFINITION => $errorMessage, self::ELEMENT_STYLE => 'invalid-link-target' ];
142
			$this->addDefinition( $errorDefinition );
143
			return $this->buildFormattedTermAsTooltip( $doc );
144
		}
145
146
		// create link element
147
		$link = $doc->createElement( 'a', $this->mDefinitions[ 0 ][ self::ELEMENT_TERM ] );
148
149
		// set the link target
150
		$link->setAttribute( 'href', $target->getLinkURL() );
151
152
153
		$link = $this->addClassAttributeToLink( $target, $link );
154
		$link = $this->addTitleAttributeToLink( $target, $link );
155
156
		return $link;
157
	}
158
159
	/**
160
	 * @param DOMDocument $doc
161
	 *
162
	 * @return DOMElement
163
	 */
164
	protected function buildFormattedTermAsTooltip( DOMDocument &$doc ) {
165
166
		// Wrap term and definition in <span> tags
167
		$span = $doc->createElement( 'span', htmlentities( $this->mTerm, ENT_COMPAT, 'UTF-8' ) );
168
		$span->setAttribute( 'class', 'mw-lingo-term ' . $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] );
169
		$span->setAttribute( 'data-lingo-term-id', $this->getId() );
170
171
		return $span;
172
	}
173
174
	/**
175
	 * @param Title $target
176
	 * @param DOMElement $link
177
	 *
178
	 * @return DOMElement
179
	 */
180
	protected function &addClassAttributeToLink( $target, &$link ) {
181
182
		// TODO: should this be more elaborate? See Linker::linkAttribs
183
		// Cleanest would probably be to use Linker::link and parse it
184
		// back into a DOMElement, but we are in a somewhat time-critical
185
		// part here.
186
187
		// set style
188
		$classes = [];
189
190
		if ( $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] !== null ) {
191
			$classes[] = $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ];
192
		}
193
194
		if ( !$target->isKnown() ) {
195
			$classes[] = 'new';
196
		}
197
198
		if ( $target->isExternal() ) {
199
			$classes[] = 'extiw';
200
		}
201
202
		if ( count( $classes ) > 0 ) {
203
			$link->setAttribute( 'class', join( ' ', $classes ) );
204
		}
205
206
		return $link;
207
	}
208
209
	/**
210
	 * @param Title $target
211
	 * @param DOMElement $link
212
	 *
213
	 * @return DOMElement
214
	 */
215
	protected function &addTitleAttributeToLink( $target, &$link ) {
216
217
		if ( $target->getPrefixedText() === '' ) {
218
			// A link like [[#Foo]].  This used to mean an empty title
219
			// attribute, but that's silly.  Just don't output a title.
220
		} elseif ( $target->isKnown() ) {
221
			$link->setAttribute( 'title', $target->getPrefixedText() );
222
		} else {
223
			$link->setAttribute( 'title', wfMessage( 'red-link-title', $target->getPrefixedText() )->text() );
0 ignored issues
show
Bug introduced by
The function wfMessage was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

223
			$link->setAttribute( 'title', /** @scrutinizer ignore-call */ wfMessage( 'red-link-title', $target->getPrefixedText() )->text() );
Loading history...
224
		}
225
226
		return $link;
227
	}
228
229
	/**
230
	 * @return string[]
231
	 */
232
	public function getFormattedDefinitions() {
233
234
		if ( $this->formattedDefinitions === null ) {
235
			$this->buildFormattedDefinitions();
236
		}
237
238
		return $this->formattedDefinitions;
239
	}
240
241
	/**
242
	 */
243
	protected function buildFormattedDefinitions() {
244
245
		// Wrap definition in a <div> tag
246
		$divDefinitions = [];
247
		$divDefinitions[] = '<div class="mw-lingo-tooltip ' . $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] . '" id="' . $this->getId() . '" >';
248
249
		foreach ( $this->mDefinitions as $definition ) {
250
251
			$divDefinitions[] = '<div class="mw-lingo-definition">';
252
253
			$divDefinitions[] = '<div class="mw-lingo-definition-text ' . $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] . "\">\n";
254
			$divDefinitions[] = $definition[ self::ELEMENT_DEFINITION ];
255
			$divDefinitions[] = "\n" . '</div>';
256
257
			if ( $definition[ self::ELEMENT_LINK ] ) {
258
259
				if ( wfParseUrl( $definition[ self::ELEMENT_LINK ] ) !== false ) {
0 ignored issues
show
Bug introduced by
The function wfParseUrl was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

259
				if ( /** @scrutinizer ignore-call */ wfParseUrl( $definition[ self::ELEMENT_LINK ] ) !== false ) {
Loading history...
260
					$url = $definition[ self::ELEMENT_LINK ];
261
				} else {
262
					$url = Title::newFromText( $definition[ self::ELEMENT_LINK ] )->getFullURL();
263
				}
264
265
				if ( $url !== null ) {
266
					$divDefinitions[] = '<div class="mw-lingo-definition-link">[' . $url . ' <nowiki/>]</div>';
267
				}
268
			}
269
270
			$divDefinitions[] = '</div>';
271
		}
272
273
		$divDefinitions[] = "\n" . '</div>';
274
275
		$this->formattedDefinitions = join( $divDefinitions );
0 ignored issues
show
Bug introduced by
$divDefinitions of type array<mixed,mixed|string>|string[] is incompatible with the type string expected by parameter $glue of join(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

275
		$this->formattedDefinitions = join( /** @scrutinizer ignore-type */ $divDefinitions );
Loading history...
Bug introduced by
The call to join() has too few arguments starting with pieces. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

275
		$this->formattedDefinitions = /** @scrutinizer ignore-call */ join( $divDefinitions );

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
276
	}
277
278
	/**
279
	 * @return string
280
	 */
281
	public function getId() {
282
		return md5( $this->mTerm );
283
	}
284
285
	/**
286
	 * @return mixed
287
	 */
288
	public function getCurrentKey() {
289
		return key( $this->mDefinitions );
290
	}
291
292
	/**
293
	 * @param $key
294
	 *
295
	 * @return mixed
296
	 */
297
	public function getTerm( $key ) {
298
		return $this->mDefinitions[ $key ][ self::ELEMENT_TERM ];
299
	}
300
301
	/**
302
	 * @param $key
303
	 *
304
	 * @return mixed
305
	 */
306
	public function getSource( &$key ) {
307
		return $this->mDefinitions[ $key ][ self::ELEMENT_SOURCE ];
308
	}
309
310
	/**
311
	 * @param $key
312
	 *
313
	 * @return mixed
314
	 */
315
	public function getDefinition( &$key ) {
316
		return $this->mDefinitions[ $key ][ self::ELEMENT_DEFINITION ];
317
	}
318
319
	/**
320
	 * @param $key
321
	 *
322
	 * @return mixed
323
	 */
324
	public function getLink( &$key ) {
325
		return $this->mDefinitions[ $key ][ self::ELEMENT_LINK ];
326
	}
327
328
	/**
329
	 * @param $key
330
	 *
331
	 * @return mixed
332
	 */
333
	public function getStyle( &$key ) {
334
		return $this->mDefinitions[ $key ][ self::ELEMENT_STYLE ];
335
	}
336
337
	public function next() {
338
		next( $this->mDefinitions );
339
	}
340
341
}
342