Merge "Fix Language::parseFormattedNumber for lzh and zh-classical"
[lhc/web/wiklou.git] / skins / Vector.php
1 <?php
2 /**
3 * Vector - Modern version of MonoBook with fresh look and many usability
4 * improvements.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @todo document
22 * @file
23 * @ingroup Skins
24 */
25
26 if ( !defined( 'MEDIAWIKI' ) ) {
27 die( -1 );
28 }
29
30 /**
31 * SkinTemplate class for Vector skin
32 * @ingroup Skins
33 */
34 class SkinVector extends SkinTemplate {
35 public $useHeadElement = true;
36 public $skinname = 'vector';
37 public $stylename = 'vector';
38 public $template = 'VectorTemplate';
39
40 protected static $bodyClasses = array( 'vector-animateLayout' );
41
42 /**
43 * Initializes output page and sets up skin-specific parameters
44 * @param OutputPage $out Object to initialize
45 */
46 public function initPage( OutputPage $out ) {
47 global $wgLocalStylePath;
48
49 parent::initPage( $out );
50
51 // Append CSS which includes IE only behavior fixes for hover support -
52 // this is better than including this in a CSS file since it doesn't
53 // wait for the CSS file to load before fetching the HTC file.
54 $min = $this->getRequest()->getFuzzyBool( 'debug' ) ? '' : '.min';
55 $out->addHeadItem( 'csshover',
56 '<!--[if lt IE 7]><style type="text/css">body{behavior:url("' .
57 htmlspecialchars( $wgLocalStylePath ) .
58 "/{$this->stylename}/csshover{$min}.htc\")}</style><![endif]-->"
59 );
60
61 $out->addModules( array( 'skins.vector.js', 'skins.vector.collapsibleNav' ) );
62 }
63
64 /**
65 * Loads skin and user CSS files.
66 * @param OutputPage $out
67 */
68 function setupSkinUserCss( OutputPage $out ) {
69 parent::setupSkinUserCss( $out );
70
71 $styles = array( 'skins.common.interface', 'skins.vector.styles' );
72 wfRunHooks( 'SkinVectorStyleModules', array( $this, &$styles ) );
73 $out->addModuleStyles( $styles );
74 }
75
76 /**
77 * Adds classes to the body element.
78 *
79 * @param OutputPage $out
80 * @param array &$bodyAttrs Array of attributes that will be set on the body element
81 */
82 function addToBodyAttributes( $out, &$bodyAttrs ) {
83 if ( isset( $bodyAttrs['class'] ) && strlen( $bodyAttrs['class'] ) > 0 ) {
84 $bodyAttrs['class'] .= ' ' . implode( ' ', static::$bodyClasses );
85 } else {
86 $bodyAttrs['class'] = implode( ' ', static::$bodyClasses );
87 }
88 }
89 }
90
91 /**
92 * QuickTemplate class for Vector skin
93 * @ingroup Skins
94 */
95 class VectorTemplate extends BaseTemplate {
96 /* Functions */
97
98 /**
99 * Outputs the entire contents of the (X)HTML page
100 */
101 public function execute() {
102 global $wgVectorUseIconWatch;
103
104 // Build additional attributes for navigation urls
105 $nav = $this->data['content_navigation'];
106
107 if ( $wgVectorUseIconWatch ) {
108 $mode = $this->getSkin()->getUser()->isWatched( $this->getSkin()->getRelevantTitle() )
109 ? 'unwatch'
110 : 'watch';
111
112 if ( isset( $nav['actions'][$mode] ) ) {
113 $nav['views'][$mode] = $nav['actions'][$mode];
114 $nav['views'][$mode]['class'] = rtrim( 'icon ' . $nav['views'][$mode]['class'], ' ' );
115 $nav['views'][$mode]['primary'] = true;
116 unset( $nav['actions'][$mode] );
117 }
118 }
119
120 $xmlID = '';
121 foreach ( $nav as $section => $links ) {
122 foreach ( $links as $key => $link ) {
123 if ( $section == 'views' && !( isset( $link['primary'] ) && $link['primary'] ) ) {
124 $link['class'] = rtrim( 'collapsible ' . $link['class'], ' ' );
125 }
126
127 $xmlID = isset( $link['id'] ) ? $link['id'] : 'ca-' . $xmlID;
128 $nav[$section][$key]['attributes'] =
129 ' id="' . Sanitizer::escapeId( $xmlID ) . '"';
130 if ( $link['class'] ) {
131 $nav[$section][$key]['attributes'] .=
132 ' class="' . htmlspecialchars( $link['class'] ) . '"';
133 unset( $nav[$section][$key]['class'] );
134 }
135 if ( isset( $link['tooltiponly'] ) && $link['tooltiponly'] ) {
136 $nav[$section][$key]['key'] =
137 Linker::tooltip( $xmlID );
138 } else {
139 $nav[$section][$key]['key'] =
140 Xml::expandAttributes( Linker::tooltipAndAccesskeyAttribs( $xmlID ) );
141 }
142 }
143 }
144 $this->data['namespace_urls'] = $nav['namespaces'];
145 $this->data['view_urls'] = $nav['views'];
146 $this->data['action_urls'] = $nav['actions'];
147 $this->data['variant_urls'] = $nav['variants'];
148
149 // Reverse horizontally rendered navigation elements
150 if ( $this->data['rtl'] ) {
151 $this->data['view_urls'] =
152 array_reverse( $this->data['view_urls'] );
153 $this->data['namespace_urls'] =
154 array_reverse( $this->data['namespace_urls'] );
155 $this->data['personal_urls'] =
156 array_reverse( $this->data['personal_urls'] );
157 }
158 // Output HTML Page
159 $this->html( 'headelement' );
160 ?>
161 <div id="mw-page-base" class="noprint"></div>
162 <div id="mw-head-base" class="noprint"></div>
163 <div id="content" class="mw-body" role="main">
164 <a id="top"></a>
165
166 <div id="mw-js-message" style="display:none;"<?php $this->html( 'userlangattributes' ) ?>></div>
167 <?php
168 if ( $this->data['sitenotice'] ) {
169 ?>
170 <div id="siteNotice"><?php $this->html( 'sitenotice' ) ?></div>
171 <?php
172 }
173 ?>
174 <h1 id="firstHeading" class="firstHeading" lang="<?php
175 $this->data['pageLanguage'] =
176 $this->getSkin()->getTitle()->getPageViewLanguage()->getHtmlCode();
177 $this->text( 'pageLanguage' );
178 ?>"><span dir="auto"><?php $this->html( 'title' ) ?></span></h1>
179 <?php $this->html( 'prebodyhtml' ) ?>
180 <div id="bodyContent">
181 <?php
182 if ( $this->data['isarticle'] ) {
183 ?>
184 <div id="siteSub"><?php $this->msg( 'tagline' ) ?></div>
185 <?php
186 }
187 ?>
188 <div id="contentSub"<?php
189 $this->html( 'userlangattributes' )
190 ?>><?php $this->html( 'subtitle' ) ?></div>
191 <?php
192 if ( $this->data['undelete'] ) {
193 ?>
194 <div id="contentSub2"><?php $this->html( 'undelete' ) ?></div>
195 <?php
196 }
197 ?>
198 <?php
199 if ( $this->data['newtalk'] ) {
200 ?>
201 <div class="usermessage"><?php $this->html( 'newtalk' ) ?></div>
202 <?php
203 }
204 ?>
205 <div id="jump-to-nav" class="mw-jump">
206 <?php $this->msg( 'jumpto' ) ?>
207 <a href="#mw-navigation"><?php
208 $this->msg( 'jumptonavigation' )
209 ?></a><?php
210 $this->msg( 'comma-separator' )
211 ?>
212 <a href="#p-search"><?php $this->msg( 'jumptosearch' ) ?></a>
213 </div>
214 <?php $this->html( 'bodycontent' ) ?>
215 <?php
216 if ( $this->data['printfooter'] ) {
217 ?>
218 <div class="printfooter">
219 <?php $this->html( 'printfooter' ); ?>
220 </div>
221 <?php
222 }
223 ?>
224 <?php
225 if ( $this->data['catlinks'] ) {
226 ?>
227 <?php
228 $this->html( 'catlinks' );
229 ?>
230 <?php
231 }
232 ?>
233 <?php
234 if ( $this->data['dataAfterContent'] ) {
235 ?>
236 <?php
237 $this->html( 'dataAfterContent' );
238 ?>
239 <?php
240 }
241 ?>
242 <div class="visualClear"></div>
243 <?php $this->html( 'debughtml' ); ?>
244 </div>
245 </div>
246 <div id="mw-navigation">
247 <h2><?php $this->msg( 'navigation-heading' ) ?></h2>
248
249 <div id="mw-head">
250 <?php $this->renderNavigation( 'PERSONAL' ); ?>
251 <div id="left-navigation">
252 <?php $this->renderNavigation( array( 'NAMESPACES', 'VARIANTS' ) ); ?>
253 </div>
254 <div id="right-navigation">
255 <?php $this->renderNavigation( array( 'VIEWS', 'ACTIONS', 'SEARCH' ) ); ?>
256 </div>
257 </div>
258 <div id="mw-panel">
259 <div id="p-logo" role="banner"><a style="background-image: url(<?php
260 $this->text( 'logopath' )
261 ?>);" href="<?php
262 echo htmlspecialchars( $this->data['nav_urls']['mainpage']['href'] )
263 ?>" <?php
264 echo Xml::expandAttributes( Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) )
265 ?>></a></div>
266 <?php $this->renderPortals( $this->data['sidebar'] ); ?>
267 </div>
268 </div>
269 <div id="footer" role="contentinfo"<?php $this->html( 'userlangattributes' ) ?>>
270 <?php
271 foreach ( $this->getFooterLinks() as $category => $links ) {
272 ?>
273 <ul id="footer-<?php
274 echo $category
275 ?>">
276 <?php
277 foreach ( $links as $link ) {
278 ?>
279 <li id="footer-<?php
280 echo $category
281 ?>-<?php
282 echo $link
283 ?>"><?php
284 $this->html( $link )
285 ?></li>
286 <?php
287 }
288 ?>
289 </ul>
290 <?php
291 }
292 ?>
293 <?php $footericons = $this->getFooterIcons( "icononly" );
294 if ( count( $footericons ) > 0 ) {
295 ?>
296 <ul id="footer-icons" class="noprint">
297 <?php
298 foreach ( $footericons as $blockName => $footerIcons ) {
299 ?>
300 <li id="footer-<?php
301 echo htmlspecialchars( $blockName ); ?>ico">
302 <?php
303 foreach ( $footerIcons as $icon ) {
304 ?>
305 <?php
306 echo $this->getSkin()->makeFooterIcon( $icon );
307 ?>
308
309 <?php
310 }
311 ?>
312 </li>
313 <?php
314 }
315 ?>
316 </ul>
317 <?php
318 }
319 ?>
320 <div style="clear:both"></div>
321 </div>
322 <?php $this->printTrail(); ?>
323
324 </body>
325 </html>
326 <?php
327 }
328
329 /**
330 * Render a series of portals
331 *
332 * @param array $portals
333 */
334 protected function renderPortals( $portals ) {
335 // Force the rendering of the following portals
336 if ( !isset( $portals['SEARCH'] ) ) {
337 $portals['SEARCH'] = true;
338 }
339 if ( !isset( $portals['TOOLBOX'] ) ) {
340 $portals['TOOLBOX'] = true;
341 }
342 if ( !isset( $portals['LANGUAGES'] ) ) {
343 $portals['LANGUAGES'] = true;
344 }
345 // Render portals
346 foreach ( $portals as $name => $content ) {
347 if ( $content === false ) {
348 continue;
349 }
350
351 switch ( $name ) {
352 case 'SEARCH':
353 break;
354 case 'TOOLBOX':
355 $this->renderPortal( 'tb', $this->getToolbox(), 'toolbox', 'SkinTemplateToolboxEnd' );
356 break;
357 case 'LANGUAGES':
358 if ( $this->data['language_urls'] !== false ) {
359 $this->renderPortal( 'lang', $this->data['language_urls'], 'otherlanguages' );
360 }
361 break;
362 default:
363 $this->renderPortal( $name, $content );
364 break;
365 }
366 }
367 }
368
369 /**
370 * @param string $name
371 * @param array $content
372 * @param null|string $msg
373 * @param null|string|array $hook
374 */
375 protected function renderPortal( $name, $content, $msg = null, $hook = null ) {
376 if ( $msg === null ) {
377 $msg = $name;
378 }
379 $msgObj = wfMessage( $msg );
380 ?>
381 <div class="portal" role="navigation" id='<?php
382 echo Sanitizer::escapeId( "p-$name" )
383 ?>'<?php
384 echo Linker::tooltip( 'p-' . $name )
385 ?> aria-labelledby='<?php echo Sanitizer::escapeId( "p-$name-label" ) ?>'>
386 <h3<?php
387 $this->html( 'userlangattributes' )
388 ?> id='<?php
389 echo Sanitizer::escapeId( "p-$name-label" )
390 ?>'><?php
391 echo htmlspecialchars( $msgObj->exists() ? $msgObj->text() : $msg );
392 ?></h3>
393
394 <div class="body">
395 <?php
396 if ( is_array( $content ) ) {
397 ?>
398 <ul>
399 <?php
400 foreach ( $content as $key => $val ) {
401 ?>
402 <?php echo $this->makeListItem( $key, $val ); ?>
403
404 <?php
405 }
406 if ( $hook !== null ) {
407 wfRunHooks( $hook, array( &$this, true ) );
408 }
409 ?>
410 </ul>
411 <?php
412 } else {
413 ?>
414 <?php
415 echo $content; /* Allow raw HTML block to be defined by extensions */
416 }
417
418 $this->renderAfterPortlet( $name );
419 ?>
420 </div>
421 </div>
422 <?php
423 }
424
425 /**
426 * Render one or more navigations elements by name, automatically reveresed
427 * when UI is in RTL mode
428 *
429 * @param array $elements
430 */
431 protected function renderNavigation( $elements ) {
432 global $wgVectorUseSimpleSearch;
433
434 // If only one element was given, wrap it in an array, allowing more
435 // flexible arguments
436 if ( !is_array( $elements ) ) {
437 $elements = array( $elements );
438 // If there's a series of elements, reverse them when in RTL mode
439 } elseif ( $this->data['rtl'] ) {
440 $elements = array_reverse( $elements );
441 }
442 // Render elements
443 foreach ( $elements as $name => $element ) {
444 switch ( $element ) {
445 case 'NAMESPACES':
446 ?>
447 <div id="p-namespaces" role="navigation" class="vectorTabs<?php
448 if ( count( $this->data['namespace_urls'] ) == 0 ) {
449 echo ' emptyPortlet';
450 }
451 ?>" aria-labelledby="p-namespaces-label">
452 <h3 id="p-namespaces-label"><?php $this->msg( 'namespaces' ) ?></h3>
453 <ul<?php $this->html( 'userlangattributes' ) ?>>
454 <?php
455 foreach ( $this->data['namespace_urls'] as $link ) {
456 ?>
457 <li <?php
458 echo $link['attributes']
459 ?>><span><a href="<?php
460 echo htmlspecialchars( $link['href'] )
461 ?>" <?php
462 echo $link['key']
463 ?>><?php
464 echo htmlspecialchars( $link['text'] )
465 ?></a></span></li>
466 <?php
467 }
468 ?>
469 </ul>
470 </div>
471 <?php
472 break;
473 case 'VARIANTS':
474 ?>
475 <div id="p-variants" role="navigation" class="vectorMenu<?php
476 if ( count( $this->data['variant_urls'] ) == 0 ) {
477 echo ' emptyPortlet';
478 }
479 ?>" aria-labelledby="p-variants-label">
480 <h3 id="mw-vector-current-variant">
481 <?php
482 foreach ( $this->data['variant_urls'] as $link ) {
483 ?>
484 <?php
485 if ( stripos( $link['attributes'], 'selected' ) !== false ) {
486 ?>
487 <?php
488 echo htmlspecialchars( $link['text'] )
489 ?>
490 <?php
491 }
492 ?>
493 <?php
494 }
495 ?>
496 </h3>
497
498 <h3 id="p-variants-label"><span><?php $this->msg( 'variants' ) ?></span><a href="#"></a></h3>
499
500 <div class="menu">
501 <ul>
502 <?php
503 foreach ( $this->data['variant_urls'] as $link ) {
504 ?>
505 <li<?php
506 echo $link['attributes']
507 ?>><a href="<?php
508 echo htmlspecialchars( $link['href'] )
509 ?>" lang="<?php
510 echo htmlspecialchars( $link['lang'] )
511 ?>" hreflang="<?php
512 echo htmlspecialchars( $link['hreflang'] )
513 ?>" <?php
514 echo $link['key']
515 ?>><?php
516 echo htmlspecialchars( $link['text'] )
517 ?></a></li>
518 <?php
519 }
520 ?>
521 </ul>
522 </div>
523 </div>
524 <?php
525 break;
526 case 'VIEWS':
527 ?>
528 <div id="p-views" role="navigation" class="vectorTabs<?php
529 if ( count( $this->data['view_urls'] ) == 0 ) {
530 echo ' emptyPortlet';
531 }
532 ?>" aria-labelledby="p-views-label">
533 <h3 id="p-views-label"><?php $this->msg( 'views' ) ?></h3>
534 <ul<?php
535 $this->html( 'userlangattributes' )
536 ?>>
537 <?php
538 foreach ( $this->data['view_urls'] as $link ) {
539 ?>
540 <li<?php
541 echo $link['attributes']
542 ?>><span><a href="<?php
543 echo htmlspecialchars( $link['href'] )
544 ?>" <?php
545 echo $link['key']
546 ?>><?php
547 // $link['text'] can be undefined - bug 27764
548 if ( array_key_exists( 'text', $link ) ) {
549 echo array_key_exists( 'img', $link )
550 ? '<img src="' . $link['img'] . '" alt="' . $link['text'] . '" />'
551 : htmlspecialchars( $link['text'] );
552 }
553 ?></a></span></li>
554 <?php
555 }
556 ?>
557 </ul>
558 </div>
559 <?php
560 break;
561 case 'ACTIONS':
562 ?>
563 <div id="p-cactions" role="navigation" class="vectorMenu<?php
564 if ( count( $this->data['action_urls'] ) == 0 ) {
565 echo ' emptyPortlet';
566 }
567 ?>" aria-labelledby="p-cactions-label">
568 <h3 id="p-cactions-label"><span><?php $this->msg( 'actions' ) ?></span><a href="#"></a></h3>
569
570 <div class="menu">
571 <ul<?php $this->html( 'userlangattributes' ) ?>>
572 <?php
573 foreach ( $this->data['action_urls'] as $link ) {
574 ?>
575 <li<?php
576 echo $link['attributes']
577 ?>>
578 <a href="<?php
579 echo htmlspecialchars( $link['href'] )
580 ?>" <?php
581 echo $link['key'] ?>><?php echo htmlspecialchars( $link['text'] )
582 ?></a>
583 </li>
584 <?php
585 }
586 ?>
587 </ul>
588 </div>
589 </div>
590 <?php
591 break;
592 case 'PERSONAL':
593 ?>
594 <div id="p-personal" role="navigation" class="<?php
595 if ( count( $this->data['personal_urls'] ) == 0 ) {
596 echo ' emptyPortlet';
597 }
598 ?>" aria-labelledby="p-personal-label">
599 <h3 id="p-personal-label"><?php $this->msg( 'personaltools' ) ?></h3>
600 <ul<?php $this->html( 'userlangattributes' ) ?>>
601 <?php
602 $personalTools = $this->getPersonalTools();
603 foreach ( $personalTools as $key => $item ) {
604 echo $this->makeListItem( $key, $item );
605 }
606 ?>
607 </ul>
608 </div>
609 <?php
610 break;
611 case 'SEARCH':
612 ?>
613 <div id="p-search" role="search">
614 <h3<?php $this->html( 'userlangattributes' ) ?>>
615 <label for="searchInput"><?php $this->msg( 'search' ) ?></label>
616 </h3>
617
618 <form action="<?php $this->text( 'wgScript' ) ?>" id="searchform">
619 <?php
620 if ($wgVectorUseSimpleSearch) {
621 ?>
622 <div id="simpleSearch">
623 <?php
624 } else {
625 ?>
626 <div>
627 <?php
628 }
629 ?>
630 <?php
631 echo $this->makeSearchInput( array( 'id' => 'searchInput' ) );
632 echo Html::hidden( 'title', $this->get( 'searchtitle' ) );
633 // We construct two buttons (for 'go' and 'fulltext' search modes),
634 // but only one will be visible and actionable at a time (they are
635 // overlaid on top of each other in CSS).
636 // * Browsers will use the 'fulltext' one by default (as it's the
637 // first in tree-order), which is desirable when they are unable
638 // to show search suggestions (either due to being broken or
639 // having JavaScript turned off).
640 // * The mediawiki.searchSuggest module, after doing tests for the
641 // broken browsers, removes the 'fulltext' button and handles
642 // 'fulltext' search itself; this will reveal the 'go' button and
643 // cause it to be used.
644 echo $this->makeSearchButton(
645 'fulltext',
646 array( 'id' => 'mw-searchButton', 'class' => 'searchButton mw-fallbackSearchButton' )
647 );
648 echo $this->makeSearchButton(
649 'go',
650 array( 'id' => 'searchButton', 'class' => 'searchButton' )
651 );
652 ?>
653 </div>
654 </form>
655 </div>
656 <?php
657
658 break;
659 }
660 }
661 }
662 }