Test Setup Failed
Push — master ( 1f58dd...e46154 )
by
unknown
31:29
created

src/Element.php (4 issues)

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 GPL-2.0-or-later
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 DOMText;
35
use Title;
0 ignored issues
show
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...
36
37
/**
38
 * This class represents a term-definition pair.
39
 * One term may be related to several definitions.
40
 *
41
 * @ingroup Lingo
42
 */
43
class Element {
44
	const ELEMENT_TERM = 0;
45
	const ELEMENT_DEFINITION = 1;
46
	const ELEMENT_SOURCE = 2;
47
	const ELEMENT_LINK = 3;
48
	const ELEMENT_STYLE = 4;
49
50
	const ELEMENT_FIELDCOUNT = 5;  // number of fields stored for each element; (last field's index) + 1
51
52
	const LINK_TEMPLATE_ID = 'LingoLink';
53
54
	private $formattedTerm = null;
55
	private $formattedDefinitions = null;
56
57
	private $mDefinitions = [];
58
	private $mTerm = null;
59
60
	private $hasBeenDisplayed = false;
61
62
	/**
63
	 * Lingo\Element constructor.
64
	 *
65
	 * @param string $term
66
	 * @param string[] $definition
67
	 */
68 12
	public function __construct( &$term, &$definition ) {
69 12
		$this->mTerm = $term;
70 12
		$this->addDefinition( $definition );
71 12
	}
72
73
	/**
74
	 * @param array $definition
75
	 */
76 11
	public function addDefinition( &$definition ) {
77 11
		$this->mDefinitions[] = $definition + array_fill( 0, self::ELEMENT_FIELDCOUNT, null );
78 11
	}
79
80
	/**
81
	 * @param DOMDocument $doc
82
	 *
83
	 * @return DOMElement|DOMText
84
	 */
85 11
	public function getFormattedTerm( DOMDocument &$doc ) {
86 11
		global $wgexLingoDisplayOnce;
87
88 11
		if ( $wgexLingoDisplayOnce && $this->hasBeenDisplayed ) {
89 2
			return $doc->createTextNode( $this->mTerm );
90
		}
91
92 11
		$this->hasBeenDisplayed = true;
93
94 11
		$this->buildFormattedTerm( $doc );
95
96 11
		return $this->formattedTerm->cloneNode( true );
97
	}
98
99
	/**
100
	 * @param DOMDocument $doc
101
	 */
102 11
	private function buildFormattedTerm( DOMDocument &$doc ) {
103
		// only create if not yet created
104 11
		if ( $this->formattedTerm === null || $this->formattedTerm->ownerDocument !== $doc ) {
105
106 11
			if ( $this->isSimpleLink() ) {
107 7
				$this->formattedTerm = $this->buildFormattedTermAsLink( $doc );
108
			} else {
109 4
				$this->formattedTerm = $this->buildFormattedTermAsTooltip( $doc );
110
			}
111
		}
112 11
	}
113
114
	/**
115
	 * @return bool
116
	 */
117 11
	private function isSimpleLink() {
118 11
		return count( $this->mDefinitions ) === 1 &&
119 11
			!is_string( $this->mDefinitions[ 0 ][ self::ELEMENT_DEFINITION ] ) &&
120 11
			is_string( $this->mDefinitions[ 0 ][ self::ELEMENT_LINK ] );
121
	}
122
123
	/**
124
	 * @param DOMDocument $doc
125
	 * @return DOMElement
126
	 */
127 7
	protected function buildFormattedTermAsLink( DOMDocument &$doc ) {
128 7
		$linkTarget = $this->mDefinitions[ 0 ][ self::ELEMENT_LINK ];
129 7
		$descriptor = $this->getDescriptorFromLinkTarget( $linkTarget );
130
131 7
		if ( $descriptor === null ) {
132 2
			$this->mDefinitions = [];
133 2
			$this->addErrorMessageForInvalidLink( $linkTarget );
134 2
			return $this->buildFormattedTermAsTooltip( $doc );
135
		}
136
137
		// create link element
138 5
		$link = $doc->createElement( 'a', htmlentities( $this->mDefinitions[ 0 ][ self::ELEMENT_TERM ] ) );
139
140
		// set the link target
141 5
		$link->setAttribute( 'href', $descriptor[ 'url' ] );
142 5
		$link->setAttribute( 'class', implode( ' ', $this->getClassesForLink( $descriptor ) ) );
143
144 5
		$title = $this->getTitleForLink( $descriptor );
145 5
		if ( $title !== null ) {
146 4
			$link->setAttribute( 'title', $title );
147
		}
148
149 5
		return $link;
150
	}
151
152
	/**
153
	 * @param DOMDocument $doc
154
	 *
155
	 * @return DOMElement
156
	 */
157 6
	protected function buildFormattedTermAsTooltip( DOMDocument &$doc ) {
158
		// Wrap term and definition in <span> tags
159 6
		$span = $doc->createElement( 'span', htmlentities( $this->mTerm ) );
160 6
		$span->setAttribute( 'class', 'mw-lingo-term' );
161 6
		$span->setAttribute( 'data-lingo-term-id', $this->getId() );
162
163 6
		return $span;
164
	}
165
166
	/**
167
	 * @param $descriptor
168
	 *
169
	 * @return string[]
170
	 */
171 5
	protected function getClassesForLink( $descriptor ) {
172
		// TODO: should this be more elaborate?
173
		// Cleanest would probably be to use Linker::link and parse it
174
		// back into a DOMElement, but we are in a somewhat time-critical
175
		// part here.
176
177
		// set style
178 5
		$classes = [ 'mw-lingo-term' ];
179
180 5
		$classes[] = $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ];
181
182 5
		if ( array_key_exists( 'title', $descriptor ) && $descriptor[ 'title' ] instanceof Title ) {
183
184 4
			if ( !$descriptor['title']->isKnown() ) {
185 2
				$classes[] = 'new';
186
			}
187
188 4
			if ( $descriptor['title']->isExternal() ) {
189 4
				$classes[] = 'extiw';
190
			}
191
192
		} else {
193 1
			$classes[] = 'ext';
194
		}
195
196 5
		return array_filter( $classes );
197
	}
198
199
	/**
200
	 * @param Title $target
201
	 * @param DOMElement $link
202
	 *
203
	 * @return string
204
	 */
205 5
	protected function getTitleForLink( $descriptor ) {
206
		/** @var \Title $target */
207 5
		$target = $descriptor[ 'title' ];
208
209 5
		if ( is_string( $target ) ) {
210 1
			return $target;
211
		}
212
213 4
		if ( $target->getPrefixedText() === '' ) {
214 1
			return null;
215
		}
216
217 3
		if ( $target->isKnown() ) {
218 1
			return $target->getPrefixedText();
219
		}
220
221 2
		return wfMessage( 'red-link-title', $target->getPrefixedText() )->text();
0 ignored issues
show
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

221
		return /** @scrutinizer ignore-call */ wfMessage( 'red-link-title', $target->getPrefixedText() )->text();
Loading history...
222
	}
223
224
	/**
225
	 * @return string[]
226
	 */
227 4
	public function getFormattedDefinitions() {
228 4
		if ( $this->formattedDefinitions === null ) {
229 4
			$this->buildFormattedDefinitions();
230
		}
231
232 4
		return $this->formattedDefinitions;
233
	}
234
235
	/**
236
	 */
237 4
	protected function buildFormattedDefinitions() {
238 4
		if ( $this->isSimpleLink() ) {
239 1
			$this->formattedDefinitions = '';
240 1
			return;
241
		}
242
243 3
		$divDefinitions = "<div class='mw-lingo-tooltip' id='{$this->getId()}'>";
244
245 3
		$definition = reset( $this->mDefinitions );
246 3
		while ( $definition !== false ) {
247
248 3
			$text = $definition[ self::ELEMENT_DEFINITION ];
249 3
			$link = $definition[ self::ELEMENT_LINK ];
250 3
			$style = $definition[ self::ELEMENT_STYLE ];
251
252 3
			// navigation-not-searchable removes definition from CirrusSearch index
253
			$divDefinitions .= "<div class='mw-lingo-definition navigation-not-searchable {$style}'>"
254 3
				. "<div class='mw-lingo-definition-text'>\n{$text}\n</div>";
255
256 2
			if ( $link !== null ) {
257
258 2
				$descriptor = $this->getDescriptorFromLinkTarget( $link );
259 1
260
				if ( $descriptor === null ) {
261 1
					$this->addErrorMessageForInvalidLink( $link );
262
				} else {
263
					$divDefinitions .= "<div class='mw-lingo-definition-link'>[{$descriptor[ 'url' ]} <nowiki/>]</div>";
264
				}
265 3
			}
266
267 3
			$divDefinitions .= "</div>";
268
269
			$definition = next( $this->mDefinitions );
270 3
		}
271
272 3
		$divDefinitions .= "\n</div>";
273 3
274
		$this->formattedDefinitions = $divDefinitions;
275
	}
276
277
	/**
278 6
	 * @return string
279 6
	 */
280
	public function getId() {
281
		return md5( $this->mTerm );
282
	}
283
284
	/**
285
	 * @param string $linkTarget
286
	 *
287 9
	 * @return string[]
288 9
	 */
289 1
	protected function getDescriptorFromLinkTarget( $linkTarget ) {
290
		if ( $this->isValidLinkTarget( $linkTarget ) ) {
291
			return [ 'url' => $linkTarget, 'title' => $this->mTerm ];
292 8
		}
293
294 8
		$title = Title::newFromText( $linkTarget );
295 5
296
		if ( $title !== null ) {
297
			return [ 'url' => $title->getFullURL(), 'title' => $title ];
298 3
		}
299
300
		return null;
301
	}
302
303
	/**
304
	 * @param string $linkTarget
305
	 *
306 9
	 * @return bool
307 9
	 */
308
	protected function isValidLinkTarget( $linkTarget ) {
309
		return wfParseUrl( $linkTarget ) !== false;
0 ignored issues
show
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

309
		return /** @scrutinizer ignore-call */ wfParseUrl( $linkTarget ) !== false;
Loading history...
310
	}
311
312
	/**
313 3
	 * @param $link
314 3
	 */
315 3
	protected function addErrorMessageForInvalidLink( $link ) {
316
		$errorMessage = wfMessage( 'lingo-invalidlinktarget', $this->mTerm, $link )->text();
0 ignored issues
show
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

316
		$errorMessage = /** @scrutinizer ignore-call */ wfMessage( 'lingo-invalidlinktarget', $this->mTerm, $link )->text();
Loading history...
317 3
		$errorDefinition = [ self::ELEMENT_DEFINITION => $errorMessage, self::ELEMENT_STYLE => 'invalid-link-target' ];
318 3
319
		$this->addDefinition( $errorDefinition );
320
	}
321
322
}
323