]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - skins/CologneBlue/SkinCologneBlue.php
MediaWiki 1.30.2 renames
[autoinstalls/mediawiki.git] / skins / CologneBlue / SkinCologneBlue.php
1 <?php
2 /**
3  * Cologne Blue: A nicer-looking alternative to Standard.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @todo document
21  * @file
22  * @ingroup Skins
23  */
24
25 if ( !defined( 'MEDIAWIKI' ) ) {
26         die( -1 );
27 }
28
29 /**
30  * @todo document
31  * @ingroup Skins
32  */
33 class SkinCologneBlue extends SkinTemplate {
34         public $skinname = 'cologneblue';
35         public $template = 'CologneBlueTemplate';
36
37         /**
38          * @param OutputPage $out
39          */
40         function setupSkinUserCss( OutputPage $out ) {
41                 parent::setupSkinUserCss( $out );
42                 $out->addModuleStyles( 'mediawiki.legacy.oldshared' );
43                 $out->addModuleStyles( 'skins.cologneblue' );
44         }
45
46         /**
47          * Override langlink formatting behavior not to uppercase the language names.
48          * See otherLanguages() in CologneBlueTemplate.
49          * @param string $name
50          * @return string
51          */
52         function formatLanguageName( $name ) {
53                 return $name;
54         }
55 }
56
57 class CologneBlueTemplate extends BaseTemplate {
58         function execute() {
59                 // Suppress warnings to prevent notices about missing indexes in $this->data
60                 wfSuppressWarnings();
61                 $this->html( 'headelement' );
62                 echo $this->beforeContent();
63                 $this->html( 'bodytext' );
64                 echo "\n";
65                 echo $this->afterContent();
66                 $this->html( 'dataAfterContent' );
67                 $this->printTrail();
68                 echo "\n</body></html>";
69                 wfRestoreWarnings();
70         }
71
72         /**
73          * Language/charset variant links for classic-style skins
74          * @return string
75          */
76         function variantLinks() {
77                 $s = [];
78
79                 $variants = $this->data['content_navigation']['variants'];
80
81                 foreach ( $variants as $key => $link ) {
82                         $s[] = $this->makeListItem( $key, $link, [ 'tag' => 'span' ] );
83                 }
84
85                 return $this->getSkin()->getLanguage()->pipeList( $s );
86         }
87
88         function otherLanguages() {
89                 if ( $this->config->get( 'HideInterlanguageLinks' ) ) {
90                         return "";
91                 }
92
93                 $html = '';
94
95                 // We override SkinTemplate->formatLanguageName() in SkinCologneBlue
96                 // not to capitalize the language names.
97                 $language_urls = $this->data['language_urls'];
98                 if ( !empty( $language_urls ) ) {
99                         $s = [];
100                         foreach ( $language_urls as $key => $data ) {
101                                 $s[] = $this->makeListItem( $key, $data, [ 'tag' => 'span' ] );
102                         }
103
104                         $html = wfMessage( 'otherlanguages' )->text()
105                                 . wfMessage( 'colon-separator' )->text()
106                                 . $this->getSkin()->getLanguage()->pipeList( $s );
107                 }
108
109                 $html .= $this->renderAfterPortlet( 'lang' );
110
111                 return $html;
112         }
113
114         /**
115          * @param string $name
116          */
117         protected function renderAfterPortlet( $name ) {
118                 $content = '';
119                 Hooks::run( 'BaseTemplateAfterPortlet', [ $this, $name, &$content ] );
120
121                 $html = $content !== '' ? "<div class='after-portlet after-portlet-$name'>$content</div>" : '';
122
123                 return $html;
124         }
125
126         function pageTitleLinks() {
127                 $s = [];
128                 $footlinks = $this->getFooterLinks();
129
130                 foreach ( $footlinks['places'] as $item ) {
131                         $s[] = $this->data[$item];
132                 }
133
134                 return $this->getSkin()->getLanguage()->pipeList( $s );
135         }
136
137         /**
138          * Used in bottomLinks() to eliminate repetitive code.
139          *
140          * @param string $key Key to be passed to makeListItem()
141          * @param array $navlink Navlink suitable for processNavlinkForDocument()
142          * @param string $message Key of the message to use in place of standard text
143          *
144          * @return string
145          */
146         function processBottomLink( $key, $navlink, $message = null ) {
147                 if ( !$navlink ) {
148                         // Empty navlinks might be passed.
149                         return null;
150                 }
151
152                 if ( $message ) {
153                         $navlink['text'] = wfMessage( $message )->escaped();
154                 }
155
156                 return $this->makeListItem(
157                         $key,
158                         $this->processNavlinkForDocument( $navlink ),
159                         [ 'tag' => 'span' ]
160                 );
161         }
162
163         function bottomLinks() {
164                 $toolbox = $this->getToolbox();
165                 $content_nav = $this->data['content_navigation'];
166
167                 $lines = [];
168
169                 if ( $this->getSkin()->getOutput()->isArticleRelated() ) {
170                         // First row. Regular actions.
171                         $element = [];
172
173                         $editLinkMessage = $this->getSkin()->getTitle()->exists() ? 'editthispage' : 'create-this-page';
174                         $element[] = $this->processBottomLink( 'edit', $content_nav['views']['edit'], $editLinkMessage );
175                         $element[] = $this->processBottomLink(
176                                 'viewsource',
177                                 $content_nav['views']['viewsource'],
178                                 'viewsource'
179                         );
180
181                         $element[] = $this->processBottomLink(
182                                 'watch',
183                                 $content_nav['actions']['watch'],
184                                 'watchthispage'
185                         );
186                         $element[] = $this->processBottomLink(
187                                 'unwatch',
188                                 $content_nav['actions']['unwatch'],
189                                 'unwatchthispage'
190                         );
191
192                         $element[] = $this->talkLink();
193
194                         $element[] = $this->processBottomLink( 'history', $content_nav['views']['history'], 'history' );
195                         $element[] = $this->processBottomLink( 'info', $toolbox['info'] );
196                         $element[] = $this->processBottomLink( 'whatlinkshere', $toolbox['whatlinkshere'] );
197                         $element[] = $this->processBottomLink( 'recentchangeslinked', $toolbox['recentchangeslinked'] );
198
199                         $element[] = $this->processBottomLink( 'contributions', $toolbox['contributions'] );
200                         if ( isset( $toolbox['emailuser'] ) ) {
201                                 $element[] = $this->processBottomLink( 'emailuser', $toolbox['emailuser'] );
202                         }
203
204                         $lines[] = $this->getSkin()->getLanguage()->pipeList( array_filter( $element ) );
205
206                         // Second row. Privileged actions.
207                         $element = [];
208
209                         $element[] = $this->processBottomLink(
210                                 'delete',
211                                 $content_nav['actions']['delete'],
212                                 'deletethispage'
213                         );
214                         if ( isset( $content_nav['actions']['undelete'] ) ) {
215                                 $element[] = $this->processBottomLink(
216                                         'undelete',
217                                         $content_nav['actions']['undelete'],
218                                         'undeletethispage'
219                                 );
220                         }
221
222                         if ( isset( $content_nav['actions']['protect'] ) ) {
223                                 $element[] = $this->processBottomLink(
224                                         'protect',
225                                         $content_nav['actions']['protect'],
226                                         'protectthispage'
227                                 );
228                         }
229
230                         if ( isset( $content_nav['actions']['unprotect'] ) ) {
231                                 $element[] = $this->processBottomLink(
232                                         'unprotect',
233                                         $content_nav['actions']['unprotect'],
234                                         'unprotectthispage'
235                                 );
236                         }
237
238                         $element[] = $this->processBottomLink( 'move', $content_nav['actions']['move'], 'movethispage' );
239
240                         $lines[] = $this->getSkin()->getLanguage()->pipeList( array_filter( $element ) );
241
242                         // Third row. Language links.
243                         $lines[] = $this->otherLanguages();
244                 }
245
246                 return implode( array_filter( $lines ), "<br />\n" ) . "<br />\n";
247         }
248
249         function talkLink() {
250                 $title = $this->getSkin()->getTitle();
251
252                 if ( $title->getNamespace() == NS_SPECIAL ) {
253                         // No discussion links for special pages
254                         return "";
255                 }
256
257                 $companionTitle = $title->isTalkPage() ? $title->getSubjectPage() : $title->getTalkPage();
258                 $companionNamespace = $companionTitle->getNamespace();
259
260                 // TODO these messages are only be used by CologneBlue,
261                 // kill and replace with something more sensibly named?
262                 $nsToMessage = [
263                         NS_MAIN => 'articlepage',
264                         NS_USER => 'userpage',
265                         NS_PROJECT => 'projectpage',
266                         NS_FILE => 'imagepage',
267                         NS_MEDIAWIKI => 'mediawikipage',
268                         NS_TEMPLATE => 'templatepage',
269                         NS_HELP => 'viewhelppage',
270                         NS_CATEGORY => 'categorypage',
271                         NS_FILE => 'imagepage',
272                 ];
273
274                 // Find out the message to use for link text. Use either the array above or,
275                 // for non-talk pages, a generic "discuss this" message.
276                 // Default is the same as for main namespace.
277                 if ( isset( $nsToMessage[$companionNamespace] ) ) {
278                         $message = $nsToMessage[$companionNamespace];
279                 } else {
280                         $message = $companionTitle->isTalkPage() ? 'talkpage' : 'articlepage';
281                 }
282
283                 // Obviously this can't be reasonable and just return the key for talk
284                 // namespace, only for content ones. Thus we have to mangle it in
285                 // exactly the same way SkinTemplate does. (bug 40805)
286                 $key = $companionTitle->getNamespaceKey( '' );
287                 if ( $companionTitle->isTalkPage() ) {
288                         $key = ( $key == 'main' ? 'talk' : $key . "_talk" );
289                 }
290
291                 // Use the regular navigational link, but replace its text. Everything else stays unmodified.
292                 $namespacesLinks = $this->data['content_navigation']['namespaces'];
293
294                 return $this->processBottomLink( $message, $namespacesLinks[$key], $message );
295         }
296
297         /**
298          * Takes a navigational link generated by SkinTemplate in whichever way
299          * and mangles attributes unsuitable for repeated use. In particular, this
300          * modifies the ids and removes the accesskeys. This is necessary to be
301          * able to use the same navlink twice, e.g. in sidebar and in footer.
302          *
303          * @param array $navlink Navigational link generated by SkinTemplate
304          * @param mixed $idPrefix Prefix to add to id of this navlink. If false, id
305          *   is removed entirely. Default is 'cb-'.
306          */
307         function processNavlinkForDocument( $navlink, $idPrefix = 'cb-' ) {
308                 if ( $navlink['id'] ) {
309                         $navlink['single-id'] = $navlink['id']; // to allow for tooltip generation
310                         $navlink['tooltiponly'] = true; // but no accesskeys
311
312                         // mangle or remove the id
313                         if ( $idPrefix === false ) {
314                                 unset( $navlink['id'] );
315                         } else {
316                                 $navlink['id'] = $idPrefix . $navlink['id'];
317                         }
318                 }
319
320                 return $navlink;
321         }
322
323         /**
324          * @return string
325          */
326         function beforeContent() {
327                 ob_start();
328                 ?>
329                 <div id="content">
330                 <div id="topbar">
331                         <p id="sitetitle" role="banner">
332                                 <a href="<?php echo htmlspecialchars( $this->data['nav_urls']['mainpage']['href'] ) ?>">
333                                         <?php echo wfMessage( 'sitetitle' )->escaped() ?>
334                                 </a>
335                         </p>
336
337                         <p id="sitesub"><?php echo wfMessage( 'sitesubtitle' )->escaped() ?></p>
338
339                         <div id="linkcollection" role="navigation">
340                                 <div id="langlinks"><?php echo str_replace( '<br />', '', $this->otherLanguages() ) ?></div>
341                                 <?php echo $this->getSkin()->getCategories() ?>
342                                 <div id="titlelinks"><?php echo $this->pageTitleLinks() ?></div>
343                                 <?php
344                                 if ( $this->data['newtalk'] ) {
345                                         ?>
346                                         <div class="usermessage"><strong><?php echo $this->data['newtalk'] ?></strong></div>
347                                 <?php
348                                 }
349                                 ?>
350                         </div>
351                 </div>
352                 <div id="article" class="mw-body" role="main">
353                 <?php
354                 if ( $this->getSkin()->getSiteNotice() ) {
355                         ?>
356                         <div id="siteNotice"><?php echo $this->getSkin()->getSiteNotice() ?></div>
357                 <?php
358                 }
359                 ?>
360                 <?php echo $this->getIndicators(); ?>
361                 <h1 id="firstHeading" lang="<?php
362                 $this->data['pageLanguage'] = $this->getSkin()->getTitle()->getPageViewLanguage()->getHtmlCode();
363                 $this->text( 'pageLanguage' );
364                 ?>"><?php echo $this->data['title'] ?></h1>
365                 <?php
366                 if ( $this->translator->translate( 'tagline' ) ) {
367                         ?>
368                         <p class="tagline"><?php
369                                 echo htmlspecialchars( $this->translator->translate( 'tagline' ) )
370                                 ?></p>
371                 <?php
372                 }
373                 ?>
374                 <?php
375                 if ( $this->getSkin()->getOutput()->getSubtitle() ) {
376                         ?>
377                         <p class="subtitle"><?php echo $this->getSkin()->getOutput()->getSubtitle() ?></p>
378                 <?php
379                 }
380                 ?>
381                 <?php
382                 if ( $this->getSkin()->subPageSubtitle() ) {
383                         ?>
384                         <p class="subpages"><?php echo $this->getSkin()->subPageSubtitle() ?></p>
385                 <?php
386                 }
387                 ?>
388                 <?php
389                 $s = ob_get_contents();
390                 ob_end_clean();
391
392                 return $s;
393         }
394
395         /**
396          * @return string
397          */
398         function afterContent() {
399                 ob_start();
400                 ?>
401                 </div>
402                 <div id="footer">
403                         <div id="footer-navigation" role="navigation">
404                                 <?php
405                                 // Page-related links
406                                 echo $this->bottomLinks();
407                                 echo "\n<br />";
408
409                                 // Footer and second searchbox
410                                 echo $this->getSkin()->getLanguage()->pipeList( [
411                                         $this->getSkin()->mainPageLink(),
412                                         $this->getSkin()->aboutLink(),
413                                         $this->searchForm( 'footer' )
414                                 ] );
415                                 ?>
416                         </div>
417                         <div id="footer-info" role="contentinfo">
418                                 <?php
419                                 // Standard footer info
420                                 $footlinks = $this->getFooterLinks();
421                                 if ( $footlinks['info'] ) {
422                                         foreach ( $footlinks['info'] as $item ) {
423                                                 echo $this->data[$item] . ' ';
424                                         }
425                                 }
426                                 ?>
427                         </div>
428                 </div>
429                 </div>
430                 <div id="mw-navigation">
431                         <h2><?php echo wfMessage( 'navigation-heading' )->escaped() ?></h2>
432
433                         <div id="toplinks" role="navigation">
434                                 <p id="syslinks"><?php echo $this->sysLinks() ?></p>
435
436                                 <p id="variantlinks"><?php echo $this->variantLinks() ?></p>
437                         </div>
438                         <?php echo $this->quickBar() ?>
439                 </div>
440                 <?php
441                 $s = ob_get_contents();
442                 ob_end_clean();
443
444                 return $s;
445         }
446
447         /**
448          * @return string
449          */
450         function sysLinks() {
451                 $s = [
452                         $this->getSkin()->mainPageLink(),
453                         Linker::linkKnown(
454                                 Title::newFromText( wfMessage( 'aboutpage' )->inContentLanguage()->text() ),
455                                 wfMessage( 'about' )->text()
456                         ),
457                         Linker::makeExternalLink(
458                                 Skin::makeInternalOrExternalUrl( wfMessage( 'helppage' )->inContentLanguage()->text() ),
459                                 wfMessage( 'help' )->text(),
460                                 false
461                         ),
462                         Linker::linkKnown(
463                                 Title::newFromText( wfMessage( 'faqpage' )->inContentLanguage()->text() ),
464                                 wfMessage( 'faq' )->text()
465                         ),
466                 ];
467
468                 $personalUrls = $this->getPersonalTools();
469                 foreach ( [ 'logout', 'createaccount', 'login' ] as $key ) {
470                         if ( isset( $personalUrls[$key] ) ) {
471                                 $s[] = $this->makeListItem( $key, $personalUrls[$key], [ 'tag' => 'span' ] );
472                         }
473                 }
474
475                 return $this->getSkin()->getLanguage()->pipeList( $s );
476         }
477
478         /**
479          * Adds CologneBlue-specific items to the sidebar: qbedit, qbpageoptions and qbmyoptions menus.
480          *
481          * @param array $bar Sidebar data
482          * @return array Modified sidebar data
483          */
484         function sidebarAdditions( $bar ) {
485                 // "This page" and "Edit" menus
486                 // We need to do some massaging here... we reuse all of the items,
487                 // except for $...['views']['view'], as $...['namespaces']['main'] and
488                 // $...['namespaces']['talk'] together serve the same purpose. We also
489                 // don't use $...['variants'], these are displayed in the top menu.
490                 $content_navigation = $this->data['content_navigation'];
491                 $qbpageoptions = array_merge(
492                         $content_navigation['namespaces'],
493                         [
494                                 'history' => $content_navigation['views']['history'],
495                                 'watch' => $content_navigation['actions']['watch'],
496                                 'unwatch' => $content_navigation['actions']['unwatch'],
497                         ]
498                 );
499                 $content_navigation['actions']['watch'] = null;
500                 $content_navigation['actions']['unwatch'] = null;
501                 $qbEditLinks = [ 'edit' => $content_navigation['views']['edit'] ];
502                 if ( isset( $content_navigation['views']['addsection'] ) ) {
503                         $qbEditLinks['addsection'] = $content_navigation['views']['addsection'];
504                 }
505                 $qbedit = array_merge(
506                         $qbEditLinks,
507                         $content_navigation['actions']
508                 );
509
510                 // Personal tools ("My pages")
511                 $qbmyoptions = $this->getPersonalTools();
512                 foreach ( [ 'logout', 'createaccount', 'login' ] as $key ) {
513                         $qbmyoptions[$key] = null;
514                 }
515
516                 // Use the closest reasonable name
517                 $bar['cactions'] = $qbedit;
518                 $bar['pageoptions'] = $qbpageoptions; // this is a non-standard portlet name, but nothing fits
519                 $bar['personal'] = $qbmyoptions;
520
521                 return $bar;
522         }
523
524         /**
525          * Compute the sidebar
526          * @access private
527          *
528          * @return string
529          */
530         function quickBar() {
531                 // Massage the sidebar. We want to:
532                 // * place SEARCH at the beginning
533                 // * add new portlets before TOOLBOX (or at the end, if it's missing)
534                 // * remove LANGUAGES (langlinks are displayed elsewhere)
535                 $orig_bar = $this->data['sidebar'];
536                 $bar = [];
537                 $hasToolbox = false;
538
539                 // Always display search first
540                 $bar['SEARCH'] = true;
541                 // Copy everything except for langlinks, inserting new items before toolbox
542                 foreach ( $orig_bar as $heading => $data ) {
543                         if ( $heading == 'TOOLBOX' ) {
544                                 // Insert the stuff
545                                 $bar = $this->sidebarAdditions( $bar );
546                                 $hasToolbox = true;
547                         }
548
549                         if ( $heading != 'LANGUAGES' ) {
550                                 $bar[$heading] = $data;
551                         }
552                 }
553                 // If toolbox is missing, add our items at the end
554                 if ( !$hasToolbox ) {
555                         $bar = $this->sidebarAdditions( $bar );
556                 }
557
558                 // Fill out special sidebar items with content
559                 $orig_bar = $bar;
560                 $bar = [];
561                 foreach ( $orig_bar as $heading => $data ) {
562                         if ( $heading == 'SEARCH' ) {
563                                 $bar['search'] = $this->searchForm( 'sidebar' );
564                         } elseif ( $heading == 'TOOLBOX' ) {
565                                 $bar['tb'] = $this->getToolbox();
566                         } else {
567                                 $bar[$heading] = $data;
568                         }
569                 }
570
571                 // Output the sidebar
572                 // CologneBlue uses custom messages for some portlets, but we should keep the ids for consistency
573                 $idToMessage = [
574                         'search' => 'qbfind',
575                         'navigation' => 'qbbrowse',
576                         'tb' => 'toolbox',
577                         'cactions' => 'qbedit',
578                         'personal' => 'qbmyoptions',
579                         'pageoptions' => 'qbpageoptions',
580                 ];
581
582                 $s = "<div id='quickbar'>\n";
583
584                 foreach ( $bar as $heading => $data ) {
585                         // Numeric strings gets an integer when set as key, cast back - T73639
586                         $heading = (string)$heading;
587
588                         $portletId = Sanitizer::escapeId( "p-$heading" );
589                         $headingMsg = wfMessage( $idToMessage[$heading] ? $idToMessage[$heading] : $heading );
590                         $headingHTML = "<h3>";
591                         $headingHTML .= $headingMsg->exists()
592                                 ? $headingMsg->escaped()
593                                 : htmlspecialchars( $heading );
594                         $headingHTML .= "</h3>";
595                         $listHTML = "";
596
597                         if ( is_array( $data ) ) {
598                                 // $data is an array of links
599                                 foreach ( $data as $key => $link ) {
600                                         // Can be empty due to how the sidebar additions are done
601                                         if ( $link ) {
602                                                 $listHTML .= $this->makeListItem( $key, $link );
603                                         }
604                                 }
605                                 if ( $listHTML ) {
606                                         $listHTML = "<ul>$listHTML</ul>";
607                                 }
608                         } else {
609                                 // $data is a HTML <ul>-list string
610                                 $listHTML = $data;
611                         }
612
613                         if ( $listHTML ) {
614                                 $role = ( $heading == 'search' ) ? 'search' : 'navigation';
615                                 $s .= "<div class=\"portlet\" id=\"$portletId\" "
616                                         . "role=\"$role\">\n$headingHTML\n$listHTML\n</div>\n";
617                         }
618
619                         $s .= $this->renderAfterPortlet( $heading );
620                 }
621
622                 $s .= "</div>\n";
623
624                 return $s;
625         }
626
627         /**
628          * @param string $label
629          * @return string
630          */
631         function searchForm( $which ) {
632                 $search = $this->getSkin()->getRequest()->getText( 'search' );
633                 $action = $this->data['searchaction'];
634                 $s = "<form id=\"searchform-" . htmlspecialchars( $which )
635                         . "\" method=\"get\" class=\"inline\" action=\"$action\">";
636                 if ( $which == 'footer' ) {
637                         $s .= wfMessage( 'qbfind' )->text() . ": ";
638                 }
639
640                 $s .= $this->makeSearchInput( [
641                         'class' => 'mw-searchInput',
642                         'type' => 'text',
643                         'size' => '14'
644                 ] );
645                 $s .= ( $which == 'footer' ? " " : "<br />" );
646                 $s .= $this->makeSearchButton( 'go', [ 'class' => 'searchButton' ] );
647
648                 if ( $this->config->get( 'UseTwoButtonsSearchForm' ) ) {
649                         $s .= $this->makeSearchButton( 'fulltext', [ 'class' => 'searchButton' ] );
650                 } else {
651                         $s .= '<div><a href="' . $action . '" rel="search">'
652                                 . wfMessage( 'powersearch-legend' )->escaped() . "</a></div>\n";
653                 }
654
655                 $s .= '</form>';
656
657                 return $s;
658         }
659 }