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; |
||
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(); |
|
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 | $divDefinitions .= "<div class='mw-lingo-definition {$style}'><div class='mw-lingo-definition-text'>\n{$text}\n</div>"; |
|
0 ignored issues
–
show
As per coding-style, please use concatenation or
sprintf for the variable $text instead of interpolation.
It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings. // Instead of
$x = "foo $bar $baz";
// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
|
|||
253 | |||
254 | 3 | if ( $link !== null ) { |
|
255 | |||
256 | 2 | $descriptor = $this->getDescriptorFromLinkTarget( $link ); |
|
257 | |||
258 | 2 | if ( $descriptor === null ) { |
|
259 | 1 | $this->addErrorMessageForInvalidLink( $link ); |
|
260 | } else { |
||
261 | 1 | $divDefinitions .= "<div class='mw-lingo-definition-link'>[{$descriptor[ 'url' ]} <nowiki/>]</div>"; |
|
262 | } |
||
263 | } |
||
264 | |||
265 | 3 | $divDefinitions .= "</div>"; |
|
266 | |||
267 | 3 | $definition = next( $this->mDefinitions ); |
|
268 | } |
||
269 | |||
270 | 3 | $divDefinitions .= "\n</div>"; |
|
271 | |||
272 | 3 | $this->formattedDefinitions = $divDefinitions; |
|
273 | 3 | } |
|
274 | |||
275 | /** |
||
276 | * @return string |
||
277 | */ |
||
278 | 6 | public function getId() { |
|
279 | 6 | return md5( $this->mTerm ); |
|
280 | } |
||
281 | |||
282 | /** |
||
283 | * @param string $linkTarget |
||
284 | * |
||
285 | * @return string[] |
||
286 | */ |
||
287 | 9 | protected function getDescriptorFromLinkTarget( $linkTarget ) { |
|
288 | 9 | if ( $this->isValidLinkTarget( $linkTarget ) ) { |
|
289 | 1 | return [ 'url' => $linkTarget, 'title' => $this->mTerm ]; |
|
290 | } |
||
291 | |||
292 | 8 | $title = Title::newFromText( $linkTarget ); |
|
293 | |||
294 | 8 | if ( $title !== null ) { |
|
295 | 5 | return [ 'url' => $title->getFullURL(), 'title' => $title ]; |
|
296 | } |
||
297 | |||
298 | 3 | return null; |
|
299 | } |
||
300 | |||
301 | /** |
||
302 | * @param string $linkTarget |
||
303 | * |
||
304 | * @return bool |
||
305 | */ |
||
306 | 9 | protected function isValidLinkTarget( $linkTarget ) { |
|
307 | 9 | return wfParseUrl( $linkTarget ) !== false; |
|
308 | } |
||
309 | |||
310 | /** |
||
311 | * @param $link |
||
312 | */ |
||
313 | 3 | protected function addErrorMessageForInvalidLink( $link ) { |
|
314 | 3 | $errorMessage = wfMessage( 'lingo-invalidlinktarget', $this->mTerm, $link )->text(); |
|
315 | 3 | $errorDefinition = [ self::ELEMENT_DEFINITION => $errorMessage, self::ELEMENT_STYLE => 'invalid-link-target' ]; |
|
316 | |||
317 | 3 | $this->addDefinition( $errorDefinition ); |
|
318 | 3 | } |
|
319 | |||
320 | } |
||
321 |
It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.