Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 142 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
BaseTemplate | |
0.00% |
0 / 142 |
|
0.00% |
0 / 14 |
3660 | |
0.00% |
0 / 1 |
getMsg | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
msg | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPersonalTools | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSidebar | |
0.00% |
0 / 61 |
|
0.00% |
0 / 1 |
702 | |||
makeLink | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
makeListItem | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
makeSearchInput | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
makeSearchButton | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getFooterLinks | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
72 | |||
getFooterIcons | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
unsetIconsWithoutImages | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
42 | |||
getFooter | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
56 | |||
getClear | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getIndicators | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | use MediaWiki\Html\Html; |
22 | use MediaWiki\Message\Message; |
23 | use MediaWiki\Parser\Sanitizer; |
24 | use Wikimedia\Message\MessageParam; |
25 | use Wikimedia\Message\MessageSpecifier; |
26 | |
27 | /** |
28 | * Extended QuickTemplate with additional MediaWiki-specific helper methods. |
29 | * |
30 | * @todo Phase this class out and make it an alias for QuickTemplate. Move methods |
31 | * individually as-appropriate either down to QuickTemplate, or (with deprecation) |
32 | * up to SkinTemplate. |
33 | * |
34 | * @stable to extend |
35 | */ |
36 | abstract class BaseTemplate extends QuickTemplate { |
37 | |
38 | /** |
39 | * Get a Message object with its context set |
40 | * |
41 | * @param string $name Message name |
42 | * @phpcs:ignore Generic.Files.LineLength |
43 | * @param MessageParam|MessageSpecifier|string|int|float|list<MessageParam|MessageSpecifier|string|int|float> ...$params |
44 | * See Message::params() |
45 | * @return Message |
46 | */ |
47 | public function getMsg( $name, ...$params ) { |
48 | return $this->getSkin()->msg( $name, ...$params ); |
49 | } |
50 | |
51 | public function msg( $str ) { |
52 | echo $this->getMsg( $str )->escaped(); |
53 | } |
54 | |
55 | /** |
56 | * @return array |
57 | */ |
58 | public function getPersonalTools() { |
59 | return $this->getSkin()->getPersonalToolsForMakeListItem( $this->get( 'personal_urls' ) ); |
60 | } |
61 | |
62 | /** |
63 | * @param array $options (optional) allows disabling certain sidebar elements. |
64 | * The keys `search`, `toolbox` and `languages` are accepted. |
65 | * @return array representing the sidebar |
66 | */ |
67 | protected function getSidebar( $options = [] ) { |
68 | // Force the rendering of the following portals |
69 | $sidebar = $this->data['sidebar']; |
70 | if ( !isset( $sidebar['SEARCH'] ) ) { |
71 | // @phan-suppress-next-line PhanTypeMismatchDimAssignment False positive |
72 | $sidebar['SEARCH'] = true; |
73 | } |
74 | if ( !isset( $sidebar['TOOLBOX'] ) ) { |
75 | $sidebar['TOOLBOX'] = true; |
76 | } |
77 | if ( !isset( $sidebar['LANGUAGES'] ) ) { |
78 | $sidebar['LANGUAGES'] = true; |
79 | } |
80 | |
81 | if ( !isset( $options['search'] ) || $options['search'] !== true ) { |
82 | unset( $sidebar['SEARCH'] ); |
83 | } |
84 | if ( isset( $options['toolbox'] ) && $options['toolbox'] === false ) { |
85 | unset( $sidebar['TOOLBOX'] ); |
86 | } |
87 | if ( isset( $options['languages'] ) && $options['languages'] === false ) { |
88 | unset( $sidebar['LANGUAGES'] ); |
89 | } |
90 | |
91 | $boxes = []; |
92 | foreach ( $sidebar as $boxName => $content ) { |
93 | if ( $content === false ) { |
94 | continue; |
95 | } |
96 | switch ( $boxName ) { |
97 | case 'SEARCH': |
98 | // Search is a special case, skins should custom implement this |
99 | $boxes[$boxName] = [ |
100 | 'id' => 'p-search', |
101 | 'header' => $this->getMsg( 'search' )->text(), |
102 | 'generated' => false, |
103 | 'content' => true, |
104 | ]; |
105 | break; |
106 | case 'TOOLBOX': |
107 | $msgObj = $this->getMsg( 'toolbox' ); |
108 | $boxes[$boxName] = [ |
109 | 'id' => 'p-tb', |
110 | 'header' => $msgObj->exists() ? $msgObj->text() : 'toolbox', |
111 | 'generated' => false, |
112 | 'content' => $content, |
113 | ]; |
114 | break; |
115 | case 'LANGUAGES': |
116 | if ( $this->data['language_urls'] !== false ) { |
117 | $msgObj = $this->getMsg( 'otherlanguages' ); |
118 | $boxes[$boxName] = [ |
119 | 'id' => 'p-lang', |
120 | 'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages', |
121 | 'generated' => false, |
122 | 'content' => $this->data['language_urls'] ?: [], |
123 | ]; |
124 | } |
125 | break; |
126 | default: |
127 | $msgObj = $this->getMsg( $boxName ); |
128 | $boxes[$boxName] = [ |
129 | 'id' => "p-$boxName", |
130 | 'header' => $msgObj->exists() ? $msgObj->text() : $boxName, |
131 | 'generated' => true, |
132 | 'content' => $content, |
133 | ]; |
134 | break; |
135 | } |
136 | } |
137 | |
138 | if ( isset( $options['htmlOnly'] ) && $options['htmlOnly'] === true ) { |
139 | foreach ( $boxes as $boxName => $box ) { |
140 | if ( is_array( $box['content'] ) ) { |
141 | $content = '<ul>'; |
142 | foreach ( $box['content'] as $key => $val ) { |
143 | $content .= "\n " . $this->getSkin()->makeListItem( $key, $val ); |
144 | } |
145 | $content .= "\n</ul>\n"; |
146 | $boxes[$boxName]['content'] = $content; |
147 | } |
148 | } |
149 | } |
150 | |
151 | return $boxes; |
152 | } |
153 | |
154 | /** |
155 | * Wrapper for Skin method. |
156 | * |
157 | * @param string $key of link |
158 | * @param array $item to render |
159 | * @param array $options for link |
160 | * @return string |
161 | */ |
162 | protected function makeLink( $key, $item, $options = [] ) { |
163 | return $this->getSkin()->makeLink( $key, $item, $options ); |
164 | } |
165 | |
166 | /** |
167 | * Wrapper for Skin method. |
168 | * |
169 | * @param string $key of list item |
170 | * @param array $item to render |
171 | * @param array $options for list item |
172 | * @return string |
173 | */ |
174 | public function makeListItem( $key, $item, $options = [] ) { |
175 | return $this->getSkin()->makeListItem( $key, $item, $options ); |
176 | } |
177 | |
178 | /** |
179 | * Wrapper for Skin method. |
180 | * |
181 | * @param array $attrs |
182 | * @return string |
183 | */ |
184 | protected function makeSearchInput( $attrs = [] ) { |
185 | return $this->getSkin()->makeSearchInput( $attrs ); |
186 | } |
187 | |
188 | /** |
189 | * Wrapper for Skin method. |
190 | * |
191 | * @param string $mode |
192 | * @param array $attrs |
193 | * @return string |
194 | */ |
195 | protected function makeSearchButton( $mode, $attrs = [] ) { |
196 | return $this->getSkin()->makeSearchButton( $mode, $attrs ); |
197 | } |
198 | |
199 | /** |
200 | * Returns an array of footerlinks trimmed down to only those footer links that |
201 | * are valid. |
202 | * If you pass "flat" as an option then the returned array will be a flat array |
203 | * of footer icons instead of a key/value array of footerlinks arrays broken |
204 | * up into categories. |
205 | * @param string|null $option |
206 | * @return array |
207 | */ |
208 | protected function getFooterLinks( $option = null ) { |
209 | $footerlinks = $this->get( 'footerlinks' ); |
210 | |
211 | // Reduce footer links down to only those which are being used |
212 | $validFooterLinks = []; |
213 | foreach ( $footerlinks as $category => $links ) { |
214 | $validFooterLinks[$category] = []; |
215 | foreach ( $links as $link ) { |
216 | if ( isset( $this->data[$link] ) && $this->data[$link] ) { |
217 | $validFooterLinks[$category][] = $link; |
218 | } |
219 | } |
220 | if ( count( $validFooterLinks[$category] ) <= 0 ) { |
221 | unset( $validFooterLinks[$category] ); |
222 | } |
223 | } |
224 | |
225 | if ( $option == 'flat' && count( $validFooterLinks ) ) { |
226 | // fold footerlinks into a single array using a bit of trickery |
227 | $validFooterLinks = array_merge( ...array_values( $validFooterLinks ) ); |
228 | } |
229 | |
230 | return $validFooterLinks; |
231 | } |
232 | |
233 | /** |
234 | * Returns an array of footer icons filtered down by options relevant to how |
235 | * the skin wishes to display them. |
236 | * If you pass "icononly" as the option all footer icons which do not have an |
237 | * image icon set will be filtered out. |
238 | * If you pass "nocopyright" then MediaWiki's copyright icon will not be included |
239 | * in the list of footer icons. This is mostly useful for skins which only |
240 | * display the text from footericons instead of the images and don't want a |
241 | * duplicate copyright statement because footerlinks already rendered one. |
242 | * @param string|null $option |
243 | * @deprecated since 1.35 read footer icons from template data requested via |
244 | * $this->get('footericons') |
245 | * @return array |
246 | */ |
247 | protected function getFooterIcons( $option = null ) { |
248 | wfDeprecated( __METHOD__, '1.35' ); |
249 | // Generate additional footer icons |
250 | $footericons = $this->get( 'footericons' ); |
251 | |
252 | if ( $option == 'icononly' ) { |
253 | // Unset any icons which don't have an image |
254 | $this->unsetIconsWithoutImages( $footericons ); |
255 | } elseif ( $option == 'nocopyright' ) { |
256 | unset( $footericons['copyright'] ); |
257 | } |
258 | |
259 | return $footericons; |
260 | } |
261 | |
262 | /** |
263 | * Unsets any elements in an array of icon definitions which do |
264 | * not have src attributes or are not strings. |
265 | * |
266 | * @param array &$icons |
267 | */ |
268 | private function unsetIconsWithoutImages( array &$icons ) { |
269 | // Unset any icons which don't have an image |
270 | foreach ( $icons as $iconsKey => &$iconsBlock ) { |
271 | foreach ( $iconsBlock as $iconKey => $icon ) { |
272 | if ( !is_string( $icon ) && !isset( $icon['src'] ) ) { |
273 | unset( $iconsBlock[$iconKey] ); |
274 | } |
275 | } |
276 | if ( $iconsBlock === [] ) { |
277 | unset( $icons[$iconsKey] ); |
278 | } |
279 | } |
280 | } |
281 | |
282 | /** |
283 | * Renderer for getFooterIcons and getFooterLinks |
284 | * |
285 | * @param string $iconStyle $option for getFooterIcons: "icononly", "nocopyright" |
286 | * the "nocopyright" option is deprecated in 1.35 because of its association with getFooterIcons |
287 | * @param string $linkStyle $option for getFooterLinks: "flat" |
288 | * |
289 | * @return string html |
290 | * @since 1.29 |
291 | */ |
292 | protected function getFooter( $iconStyle = 'icononly', $linkStyle = 'flat' ) { |
293 | $validFooterIcons = $this->get( 'footericons' ); |
294 | if ( $iconStyle === 'icononly' ) { |
295 | $this->unsetIconsWithoutImages( $validFooterIcons ); |
296 | } else { |
297 | // take a deprecated unsupported path |
298 | $validFooterIcons = $this->getFooterIcons( $iconStyle ); |
299 | } |
300 | $validFooterLinks = $this->getFooterLinks( $linkStyle ); |
301 | |
302 | $html = ''; |
303 | |
304 | if ( count( $validFooterIcons ) + count( $validFooterLinks ) > 0 ) { |
305 | $html .= Html::openElement( 'div', [ |
306 | 'id' => 'footer-bottom', |
307 | 'class' => 'mw-footer', |
308 | 'role' => 'contentinfo', |
309 | 'lang' => $this->get( 'userlang' ), |
310 | 'dir' => $this->get( 'dir' ) |
311 | ] ); |
312 | $footerEnd = Html::closeElement( 'div' ); |
313 | } else { |
314 | $footerEnd = ''; |
315 | } |
316 | foreach ( $validFooterIcons as $blockName => $footerIcons ) { |
317 | $html .= Html::openElement( 'div', [ |
318 | 'id' => Sanitizer::escapeIdForAttribute( "f-{$blockName}ico" ), |
319 | 'class' => 'footer-icons' |
320 | ] ); |
321 | foreach ( $footerIcons as $icon ) { |
322 | $html .= $this->getSkin()->makeFooterIcon( $icon ); |
323 | } |
324 | $html .= Html::closeElement( 'div' ); |
325 | } |
326 | if ( count( $validFooterLinks ) > 0 ) { |
327 | $html .= Html::openElement( 'ul', [ 'id' => 'f-list', 'class' => 'footer-places' ] ); |
328 | foreach ( $validFooterLinks as $aLink ) { |
329 | $html .= Html::rawElement( |
330 | 'li', |
331 | [ 'id' => Sanitizer::escapeIdForAttribute( $aLink ) ], |
332 | $this->get( $aLink ) |
333 | ); |
334 | } |
335 | $html .= Html::closeElement( 'ul' ); |
336 | } |
337 | |
338 | $html .= $this->getClear() . $footerEnd; |
339 | |
340 | return $html; |
341 | } |
342 | |
343 | /** |
344 | * Get a div with the core visualClear class, for clearing floats |
345 | * |
346 | * @return string html |
347 | * @since 1.29 |
348 | */ |
349 | protected function getClear() { |
350 | return Html::element( 'div', [ 'class' => 'visualClear' ] ); |
351 | } |
352 | |
353 | /** |
354 | * Get the suggested HTML for page status indicators: icons (or short text snippets) usually |
355 | * displayed in the top-right corner of the page, outside of the main content. |
356 | * |
357 | * Your skin may implement this differently, for example by handling some indicator names |
358 | * specially with a different UI. However, it is recommended to use a `<div class="mw-indicator" |
359 | * id="mw-indicator-<id>" />` as a wrapper element for each indicator, for better compatibility |
360 | * with extensions and user scripts. |
361 | * |
362 | * The raw data is available in `$this->data['indicators']` as an associative array (keys: |
363 | * identifiers, values: contents) internally ordered by keys. |
364 | * |
365 | * @return string HTML |
366 | * @since 1.25 |
367 | */ |
368 | public function getIndicators() { |
369 | $out = "<div class=\"mw-indicators\">\n"; |
370 | foreach ( $this->data['indicators'] as $id => $content ) { |
371 | $out .= Html::rawElement( |
372 | 'div', |
373 | [ |
374 | 'id' => Sanitizer::escapeIdForAttribute( "mw-indicator-$id" ), |
375 | 'class' => 'mw-indicator', |
376 | ], |
377 | $content |
378 | ) . |
379 | // Add whitespace between the <div>s because |
380 | // they get displayed with display: inline-block |
381 | "\n"; |
382 | } |
383 | $out .= "</div>\n"; |
384 | return $out; |
385 | } |
386 | } |