Completed
Branch master (fa787a)
by Stephan
02:24
created

Element   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 4.9%

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 1
dl 0
loc 283
ccs 5
cts 102
cp 0.049
rs 9
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A addDefinition() 0 3 1
A getFullDefinition() 0 13 3
A isSimpleLink() 0 5 3
B getFullDefinitionAsLink() 0 24 2
A getCurrentKey() 0 3 1
A getTerm() 0 3 1
A getSource() 0 3 1
A getDefinition() 0 3 1
A getLink() 0 3 1
A getStyle() 0 3 1
A next() 0 3 1
A __construct() 0 8 2
A buildFullDefinition() 0 12 4
B addClassAttributeToLink() 0 26 4
A addTitleAttributeToLink() 0 13 3
B getFullDefinitionAsTooltip() 0 40 4
A getLinkTemplate() 0 19 2
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 - 2016, 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 DOMElement;
33
use DOMNode;
34
use DOMText;
35
use Title;
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 $mFullDefinition = null;
55
	private $mDefinitions = array();
56
	private $mTerm = null;
57
	private $mHasBeenDisplayed = false;
58
59
	/**
60
	 * Lingo\Element constructor.
61
	 * @param $term
62
	 * @param $definition
63
	 */
64 1
	public function __construct( &$term, &$definition = null ) {
65
66 1
		$this->mTerm = $term;
67
68 1
		if ( $definition ) {
69 1
			$this->addDefinition( $definition );
70
		}
71 1
	}
72
73
	/**
74
	 * @param $definition
75
	 */
76
	public function addDefinition( &$definition ) {
77
		$this->mDefinitions[] = array_pad( $definition, self::ELEMENT_FIELDCOUNT, null );
78
	}
79
80
	/**
81
	 * @param StashingDOMDocument $doc
82
	 * @return DOMNode|DOMText
83
	 */
84
	public function getFullDefinition( StashingDOMDocument &$doc ) {
85
86
		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...
87
88
		if ( $wgexLingoDisplayOnce && $this->mHasBeenDisplayed ) {
89
			return $doc->createTextNode( $this->mTerm );
90
		}
91
92
		$this->buildFullDefinition( $doc );
93
		$this->mHasBeenDisplayed = true;
94
95
		return $this->mFullDefinition->cloneNode( true );
96
	}
97
98
	/**
99
	 * @param StashingDOMDocument $doc
100
	 */
101
	private function buildFullDefinition( StashingDOMDocument &$doc ) {
102
103
		// only create if not yet created
104
		if ( $this->mFullDefinition === null || $this->mFullDefinition->ownerDocument !== $doc ) {
105
106
			if ( $this->isSimpleLink() ) {
107
				$this->mFullDefinition = $this->getFullDefinitionAsLink( $doc );
108
			} else {
109
				$this->mFullDefinition = $this->getFullDefinitionAsTooltip( $doc );
110
			}
111
		}
112
	}
113
114
	/**
115
	 * @return bool
116
	 */
117
	private function isSimpleLink() {
118
		return count( $this->mDefinitions ) === 1 &&
119
			!is_string( $this->mDefinitions[ 0 ][ self::ELEMENT_DEFINITION ] ) &&
120
			is_string( $this->mDefinitions[ 0 ][ self::ELEMENT_LINK ] );
121
	}
122
123
	/**
124
	 * @param StashingDOMDocument $doc
125
	 * @return DOMElement
126
	 * @throws \MWException
127
	 */
128
	protected function getFullDefinitionAsLink( StashingDOMDocument &$doc ) {
129
130
		// create Title object for target page
131
		$target = Title::newFromText( $this->mDefinitions[ 0 ][ self::ELEMENT_LINK ] );
132
133
		if ( !$target instanceof Title ) {
134
			$errorMessage = wfMessage( 'lingo-invalidlinktarget', $this->mTerm, $this->mDefinitions[ 0 ][ self::ELEMENT_LINK ] )->text();
135
			$errorDefinition = array( self::ELEMENT_DEFINITION => $errorMessage, self::ELEMENT_STYLE => 'invalid-link-target' );
136
			$this->addDefinition( $errorDefinition );
137
			return $this->getFullDefinitionAsTooltip( $doc );
138
		}
139
140
		// create link element
141
		$link = $doc->createElement( 'a', $this->mDefinitions[ 0 ][ self::ELEMENT_TERM ] );
142
143
		// set the link target
144
		$link->setAttribute( 'href', $target->getLinkUrl() );
145
146
147
		$link = $this->addClassAttributeToLink( $target, $link );
148
		$link = $this->addTitleAttributeToLink( $target, $link );
149
150
		return $link;
151
	}
152
153
	/**
154
	 * @param $target
155
	 * @param $link
156
	 * @return mixed
157
	 */
158
	protected function &addClassAttributeToLink( $target, &$link ) {
159
160
		// 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...
161
		// Cleanest would probably be to use Linker::link and parse it
162
		// back into a DOMElement, but we are in a somewhat time-critical
163
		// part here.
164
165
		// set style
166
		$classes = string( $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] );
167
168
		if ( !$target->isKnown() ) {
169
			$classes .= ' new';
170
		}
171
172
		if ( $target->isExternal() ) {
173
			$classes .= ' extiw';
174
		}
175
176
		$classes = trim( $classes );
177
178
		if ( $classes !== '' ) {
179
			$link->setAttribute( 'class', $classes );
180
		}
181
182
		return $link;
183
	}
184
185
	/**
186
	 * @param $target
187
	 * @param $link
188
	 * @return mixed
189
	 */
190
	protected function &addTitleAttributeToLink( $target, &$link ) {
191
192
		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...
193
			// A link like [[#Foo]].  This used to mean an empty title
194
			// attribute, but that's silly.  Just don't output a title.
195
		} elseif ( $target->isKnown() ) {
196
			$link->setAttribute( 'title', $target->getPrefixedText() );
197
		} else {
198
			$link->setAttribute( 'title', wfMessage( 'red-link-title', $target->getPrefixedText() )->text() );
199
		}
200
201
		return $link;
202
	}
203
204
	/**
205
	 * @param StashingDOMDocument $doc
206
	 * @return string
207
	 * @throws \MWException
208
	 */
209
	protected function getFullDefinitionAsTooltip( StashingDOMDocument &$doc ) {
210
211
		// Wrap term and definition in <span> tags
212
		$span = $doc->createElement( 'span' );
213
		$span->setAttribute( 'class', 'mw-lingo-tooltip ' . $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] );
214
215
		// Wrap term in <span> tag, hidden
216
		\MediaWiki\suppressWarnings();
217
		$spanTerm = $doc->createElement( 'span', htmlentities( $this->mTerm, ENT_COMPAT, 'UTF-8' ) );
218
		\MediaWiki\restoreWarnings();
219
220
		$spanTerm->setAttribute( 'class', 'mw-lingo-tooltip-abbr' );
221
222
		// Wrap definition in a <span> tag
223
		$spanDefinition = $doc->createElement( 'span' );
224
		$spanDefinition->setAttribute( 'class', 'mw-lingo-tooltip-tip ' . $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] );
225
226
		foreach ( $this->mDefinitions as $definition ) {
227
228
			\MediaWiki\suppressWarnings();
229
			$element = $doc->createElement( 'span', htmlentities( $definition[ self::ELEMENT_DEFINITION ], ENT_COMPAT, 'UTF-8' ) );
230
			$element->setAttribute( 'class', 'mw-lingo-tooltip-definition ' . $this->mDefinitions[ 0 ][ self::ELEMENT_STYLE ] );
231
			\MediaWiki\restoreWarnings();
232
233
			if ( $definition[ self::ELEMENT_LINK ] ) {
234
				$linkedTitle = Title::newFromText( $definition[ self::ELEMENT_LINK ] );
235
				if ( $linkedTitle ) {
236
					$link = $this->getLinkTemplate( $doc );
237
					$link->setAttribute( 'href', $linkedTitle->getFullURL() );
238
					$element->appendChild( $link );
239
				}
240
			}
241
			$spanDefinition->appendChild( $element );
242
		}
243
244
		// insert term and definition
245
		$span->appendChild( $spanTerm );
246
		$span->appendChild( $spanDefinition );
247
		return $span;
248
	}
249
250
	/**
251
	 * @param StashingDOMDocument $doc
252
	 * @return DOMNode
253
	 */
254
	private function getLinkTemplate( StashingDOMDocument &$doc ) {
255
256
		$mLinkTemplate = $doc->stashGet( self::LINK_TEMPLATE_ID );
257
258
		// create template if it does not yet exist
259
		if ( $mLinkTemplate === null ) {
260
			global $wgScriptPath;
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...
261
262
			$linkimage = $doc->createElement( 'img' );
263
			$linkimage->setAttribute( 'src', $wgScriptPath . '/extensions/Lingo/styles/linkicon.png' );
264
265
			$mLinkTemplate = $doc->createElement( 'a' );
266
			$mLinkTemplate->appendChild( $linkimage );
267
268
			$doc->stashSet( $mLinkTemplate, self::LINK_TEMPLATE_ID );
269
		}
270
271
		return $mLinkTemplate->cloneNode( true );
272
	}
273
274
	/**
275
	 * @return mixed
276
	 */
277
	public function getCurrentKey() {
278
		return key( $this->mDefinitions );
279
	}
280
281
	/**
282
	 * @param $key
283
	 * @return mixed
284
	 */
285
	public function getTerm( $key ) {
286
		return $this->mDefinitions[ $key ][ self::ELEMENT_TERM ];
287
	}
288
289
	/**
290
	 * @param $key
291
	 * @return mixed
292
	 */
293
	public function getSource( &$key ) {
294
		return $this->mDefinitions[ $key ][ self::ELEMENT_SOURCE ];
295
	}
296
297
	/**
298
	 * @param $key
299
	 * @return mixed
300
	 */
301
	public function getDefinition( &$key ) {
302
		return $this->mDefinitions[ $key ][ self::ELEMENT_DEFINITION ];
303
	}
304
305
	/**
306
	 * @param $key
307
	 * @return mixed
308
	 */
309
	public function getLink( &$key ) {
310
		return $this->mDefinitions[ $key ][ self::ELEMENT_LINK ];
311
	}
312
313
	/**
314
	 * @param $key
315
	 * @return mixed
316
	 */
317
	public function getStyle( &$key ) {
318
		return $this->mDefinitions[ $key ][ self::ELEMENT_STYLE ];
319
	}
320
321
	public function next() {
322
		next( $this->mDefinitions );
323
	}
324
325
}
326