Completed
Push — master ( ea9a9d...003023 )
by
unknown
30:59
created

Element::getLinkTemplate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
ccs 0
cts 7
cp 0
rs 9.4285
cc 2
eloc 7
nc 2
nop 1
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;
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 1
	 * Lingo\Element constructor.
65
	 *
66 1
	 * @param $term
67
	 * @param $definition
68 1
	 */
69 1
	public function __construct( &$term, &$definition = null ) {
70
71 1
		$this->mTerm = $term;
72
73
		if ( $definition ) {
74
			$this->addDefinition( $definition );
75
		}
76
	}
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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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();
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' );
168
		$span->setAttribute( 'class', 'mw-lingo-tooltip ' . $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] );
169
		$span->setAttribute( 'data-lingo-term-id', $this->getId() );
170
171
		$spanTerm = $this->buildTerm( $doc );
172
173
		// insert term and definition
174
		$span->appendChild( $spanTerm );
175
		return $span;
176
	}
177
178
	/**
179
	 * @param DOMDocument $doc
180
	 *
181
	 * @return DOMElement
182
	 */
183
	protected function buildTerm( DOMDocument &$doc ) {
184
185
		// Wrap term in <span> tag, hidden
186
		\MediaWiki\suppressWarnings();
187
		$spanTerm = $doc->createElement( 'span', htmlentities( $this->mTerm, ENT_COMPAT, 'UTF-8' ) );
188
		\MediaWiki\restoreWarnings();
189
190
		$spanTerm->setAttribute( 'class', 'mw-lingo-tooltip-abbr' );
191
192
		return $spanTerm;
193
	}
194
195
	/**
196
	 * @param Title      $target
197
	 * @param DOMElement $link
198
	 *
199
	 * @return DOMElement
200
	 */
201
	protected function &addClassAttributeToLink( $target, &$link ) {
202
203
		// TODO: should this be more elaborate? See Linker::linkAttribs
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
204
		// Cleanest would probably be to use Linker::link and parse it
205
		// back into a DOMElement, but we are in a somewhat time-critical
206
		// part here.
207
208
		// set style
209
		$classes = [];
210
211
		if ( $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] !== null ) {
212
			$classes[] = $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ];
213
		}
214
215
		if ( !$target->isKnown() ) {
216
			$classes[] = 'new';
217
		}
218
219
		if ( $target->isExternal() ) {
220
			$classes[] = 'extiw';
221
		}
222
223
		if ( count( $classes ) > 0 ) {
224
			$link->setAttribute( 'class', join( ' ', $classes ) );
225
		}
226
227
		return $link;
228
	}
229
230
	/**
231
	 * @param Title      $target
232
	 * @param DOMElement $link
233
	 *
234
	 * @return DOMElement
235
	 */
236
	protected function &addTitleAttributeToLink( $target, &$link ) {
237
238
		if ( $target->getPrefixedText() === '' ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
239
			// A link like [[#Foo]].  This used to mean an empty title
240
			// attribute, but that's silly.  Just don't output a title.
241
		} elseif ( $target->isKnown() ) {
242
			$link->setAttribute( 'title', $target->getPrefixedText() );
243
		} else {
244
			$link->setAttribute( 'title', wfMessage( 'red-link-title', $target->getPrefixedText() )->text() );
245
		}
246
247
		return $link;
248
	}
249
250
	/**
251
	 * @return string[]
252
	 */
253
	public function getFormattedDefinitions() {
254
255
		if ( $this->formattedDefinitions === null ) {
256
			$this->buildFormattedDefinitions();
257
		}
258
259
		return $this->formattedDefinitions;
260
	}
261
262
	/**
263
	 */
264
	protected function buildFormattedDefinitions() {
265
266
		// Wrap definition in a <div> tag
267
		$divDefinitions = [];
268
		$divDefinitions[] = '<div class="mw-lingo-tooltip-tip ' . $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] . '" id="' . $this->getId() . '" >';
269
270
		foreach ( $this->mDefinitions as $definition ) {
271
272
			$divDefinitions[] = '<div class="mw-lingo-tooltip-definition">';
273
274
			$divDefinitions[] = '<div class="mw-lingo-tooltip-text ' . $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] . "\">\n";
275
			$divDefinitions[] = $definition[ self::ELEMENT_DEFINITION ];
276
			$divDefinitions[] = "\n" . '</div>';
277
278
			if ( $definition[ self::ELEMENT_LINK ] ) {
279
280
				if ( wfParseUrl( $definition[ self::ELEMENT_LINK ] ) !== false ) {
281
					$url = $definition[ self::ELEMENT_LINK ];
282
				} else {
283
					$url = Title::newFromText( $definition[ self::ELEMENT_LINK ] )->getFullURL();
284
				}
285
286
				if ( $url !== null ) {
287
					$divDefinitions[] = '<div class="mw-lingo-tooltip-link">[' . $url . ' <nowiki/>]</div>';
288
				}
289
			}
290
291
			$divDefinitions[] = '</div>';
292
		}
293
294
		$divDefinitions[] = "\n" . '</div>';
295
296
		$this->formattedDefinitions = join( $divDefinitions );
297
	}
298
299
	/**
300
	 * @return string
301
	 */
302
	public function getId() {
303
		return md5( $this->mTerm );
304
	}
305
306
	/**
307
	 * @return mixed
308
	 */
309
	public function getCurrentKey() {
310
		return key( $this->mDefinitions );
311
	}
312
313
	/**
314
	 * @param $key
315
	 *
316
	 * @return mixed
317
	 */
318
	public function getTerm( $key ) {
319
		return $this->mDefinitions[ $key ][ self::ELEMENT_TERM ];
320
	}
321
322
	/**
323
	 * @param $key
324
	 *
325
	 * @return mixed
326
	 */
327
	public function getSource( &$key ) {
328
		return $this->mDefinitions[ $key ][ self::ELEMENT_SOURCE ];
329
	}
330
331
	/**
332
	 * @param $key
333
	 *
334
	 * @return mixed
335
	 */
336
	public function getDefinition( &$key ) {
337
		return $this->mDefinitions[ $key ][ self::ELEMENT_DEFINITION ];
338
	}
339
340
	/**
341
	 * @param $key
342
	 *
343
	 * @return mixed
344
	 */
345
	public function getLink( &$key ) {
346
		return $this->mDefinitions[ $key ][ self::ELEMENT_LINK ];
347
	}
348
349
	/**
350
	 * @param $key
351
	 *
352
	 * @return mixed
353
	 */
354
	public function getStyle( &$key ) {
355
		return $this->mDefinitions[ $key ][ self::ELEMENT_STYLE ];
356
	}
357
358
	public function next() {
359
		next( $this->mDefinitions );
360
	}
361
362
}
363