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