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