MediaWiki  master
SkinMustache.php
Go to the documentation of this file.
1 <?php
20 use Wikimedia\WrappedStringList;
21 
26 class SkinMustache extends SkinTemplate {
30  private $templateParser = null;
31 
40  protected function getPortletData( $name, array $items ) {
41  // Monobook and Vector historically render this portal as an element with ID p-cactions
42  // This inconsistency is regretful from a code point of view
43  // However this ensures compatibility with gadgets.
44  // In future we should port p-#cactions to #p-actions and drop this rename.
45  if ( $name === 'actions' ) {
46  $name = 'cactions';
47  }
48 
49  // user-menu is the new personal tools, without the notifications.
50  // A lot of user code and gadgets relies on it being named personal.
51  // This allows it to function as a drop-in replacement.
52  if ( $name === 'user-menu' ) {
53  $name = 'personal';
54  }
55 
56  $id = Sanitizer::escapeIdForAttribute( "p-$name" );
57  $data = [
58  'id' => $id,
59  'class' => 'mw-portlet ' . Sanitizer::escapeClass( "mw-portlet-$name" ),
60  'html-tooltip' => Linker::tooltip( $id ),
61  'html-items' => '',
62  // Will be populated by SkinAfterPortlet hook.
63  'html-after-portal' => '',
64  'html-before-portal' => '',
65  ];
66  // Run the SkinAfterPortlet
67  // hook and if content is added appends it to the html-after-portal
68  // for output.
69  // Currently in production this supports the wikibase 'edit' link.
70  $content = $this->getAfterPortlet( $name );
71  if ( $content !== '' ) {
72  $data['html-after-portal'] = Html::rawElement(
73  'div',
74  [
75  'class' => [
76  'after-portlet',
77  Sanitizer::escapeClass( "after-portlet-$name" ),
78  ],
79  ],
80  $content
81  );
82  }
83 
84  foreach ( $items as $key => $item ) {
85  $data['html-items'] .= $this->makeListItem( $key, $item );
86  }
87 
88  $data['label'] = $this->getPortletLabel( $name );
89  $data['class'] .= ( count( $items ) === 0 && $content === '' )
90  ? ' emptyPortlet' : '';
91  return $data;
92  }
93 
98  private function getPortletLabel( $name ) {
99  // For historic reasons for some menu items,
100  // there is no language key corresponding with its menu key.
101  $mappings = [
102  'tb' => 'toolbox',
103  'personal' => 'personaltools',
104  'lang' => 'otherlanguages',
105  ];
106  $msgObj = $this->msg( $mappings[ $name ] ?? $name );
107  // If no message exists fallback to plain text (T252727)
108  $labelText = $msgObj->exists() ? $msgObj->text() : $name;
109  return $labelText;
110  }
111 
119  protected function getTemplateParser() {
120  if ( $this->templateParser === null ) {
121  $this->templateParser = new TemplateParser( $this->options['templateDirectory'] );
122  }
123  return $this->templateParser;
124  }
125 
132  public function generateHTML() {
133  $this->setupTemplateContext();
134  $out = $this->getOutput();
135  $tp = $this->getTemplateParser();
136  $template = $this->options['template'] ?? 'skin';
137  $data = $this->getTemplateData();
138 
139  // T259955: OutputPage::headElement must be called last (after getTemplateData)
140  // as it calls OutputPage::getRlClient, which freezes the ResourceLoader
141  // modules queue for the current page load.
142  $html = $out->headElement( $this );
143 
144  $html .= $tp->processTemplate( $template, $data );
145  $html .= $this->tailElement( $out );
146  return $html;
147  }
148 
167  public function getTemplateData() {
168  $out = $this->getOutput();
169  $printSource = Html::rawElement( 'div', [ 'class' => 'printfooter' ], $this->printSource() );
170  $bodyContent = $out->getHTML() . "\n" . $printSource;
171 
172  $newTalksHtml = $this->getNewtalks() ?: null;
173 
174  $data = [
175  'data-logos' => $this->getLogoData(),
176  // Array objects
177  'array-indicators' => $this->getIndicatorsData( $out->getIndicators() ),
178  // Data objects
179  'data-search-box' => $this->buildSearchProps(),
180  // HTML strings
181  'html-site-notice' => $this->getSiteNotice() ?: null,
182  'html-user-message' => $newTalksHtml ?
183  Html::rawElement( 'div', [ 'class' => 'usermessage' ], $newTalksHtml ) : null,
184  'html-title' => $out->getPageTitle(),
185  'html-subtitle' => $this->prepareSubtitle(),
186  'html-body-content' => $this->wrapHTML( $out->getTitle(), $bodyContent ),
187  'html-categories' => $this->getCategories(),
188  'html-after-content' => $this->afterContentHook(),
189  'html-undelete-link' => $this->prepareUndeleteLink(),
190  'html-user-language-attributes' => $this->prepareUserLanguageAttributes(),
191 
192  // links
193  'link-mainpage' => Title::newMainPage()->getLocalUrl(),
194  ];
195 
196  foreach ( $this->options['messages'] ?? [] as $message ) {
197  $data["msg-{$message}"] = $this->msg( $message )->text();
198  }
199  return $data + $this->getPortletsTemplateData() + $this->getFooterTemplateData();
200  }
201 
205  private function getPortletsTemplateData() {
206  $portlets = [];
207  $contentNavigation = $this->buildContentNavigationUrls();
208  $sidebar = [];
209  $sidebarData = $this->buildSidebar();
210  foreach ( $sidebarData as $name => $items ) {
211  if ( is_array( $items ) ) {
212  // Numeric strings gets an integer when set as key, cast back - T73639
213  $name = (string)$name;
214  switch ( $name ) {
215  // ignore search
216  case 'SEARCH':
217  break;
218  // Map toolbox to `tb` id.
219  case 'TOOLBOX':
220  $sidebar[] = $this->getPortletData( 'tb', $items );
221  break;
222  // Languages is no longer be tied to sidebar
223  case 'LANGUAGES':
224  // The language portal will be added provided either
225  // languages exist or there is a value in html-after-portal
226  // for example to show the add language wikidata link (T252800)
227  $portal = $this->getPortletData( 'lang', $items );
228  if ( count( $items ) || $portal['html-after-portal'] ) {
229  $portlets['data-languages'] = $portal;
230  }
231  break;
232  default:
233  $sidebar[] = $this->getPortletData( $name, $items );
234  break;
235  }
236  }
237  }
238 
239  foreach ( $contentNavigation as $name => $items ) {
240  if ( $name === 'user-menu' ) {
241  $items = $this->getPersonalToolsForMakeListItem( $items, true );
242  }
243 
244  $portlets['data-' . $name] = $this->getPortletData( $name, $items );
245  }
246 
247  // A menu that includes the notifications. This will be deprecated in future versions
248  // of the skin API spec.
249  $portlets['data-personal'] = $this->getPortletData(
250  'personal',
252  $this->injectLegacyMenusIntoPersonalTools( $contentNavigation )
253  )
254  );
255 
256  return [
257  'data-portlets' => $portlets,
258  'data-portlets-sidebar' => [
259  'data-portlets-first' => $sidebar[0] ?? null,
260  'array-portlets-rest' => array_slice( $sidebar, 1 ),
261  ],
262  ];
263  }
264 
269  private function getFooterTemplateData(): array {
270  $data = [];
271  foreach ( $this->getFooterLinks() as $category => $links ) {
272  $items = [];
273  $rowId = "footer-$category";
274 
275  foreach ( $links as $key => $link ) {
276  // Link may be null. If so don't include it.
277  if ( $link ) {
278  $items[] = [
279  // Monobook uses name rather than id.
280  // We may want to change monobook to adhere to the same contract however.
281  'name' => $key,
282  'id' => "$rowId-$key",
283  'html' => $link,
284  ];
285  }
286  }
287 
288  $data['data-' . $category] = [
289  'id' => $rowId,
290  'className' => null,
291  'array-items' => $items
292  ];
293  }
294 
295  // If footer icons are enabled append to the end of the rows
296  $footerIcons = $this->getFooterIcons();
297 
298  if ( count( $footerIcons ) > 0 ) {
299  $icons = [];
300  foreach ( $footerIcons as $blockName => $blockIcons ) {
301  $html = '';
302  foreach ( $blockIcons as $key => $icon ) {
303  $html .= $this->makeFooterIcon( $icon );
304  }
305  // For historic reasons this mimics the `icononly` option
306  // for BaseTemplate::getFooterIcons. Empty rows should not be output.
307  if ( $html ) {
308  $block = htmlspecialchars( $blockName );
309  $icons[] = [
310  'name' => $block,
311  'id' => 'footer-' . $block . 'ico',
312  'html' => $html,
313  ];
314  }
315  }
316 
317  // Empty rows should not be output.
318  // This is how Vector has behaved historically but we can revisit later if necessary.
319  if ( count( $icons ) > 0 ) {
320  $data['data-icons'] = [
321  'id' => 'footer-icons',
322  'className' => 'noprint',
323  'array-items' => $icons,
324  ];
325  }
326  }
327 
328  return [
329  'data-footer' => $data,
330  ];
331  }
332 
336  private function getLogoData(): array {
338  // check if the logo supports variants
339  $variantsLogos = $logoData['variants'] ?? null;
340  if ( $variantsLogos ) {
341  $preferred = $this->getOutput()->getTitle()
342  ->getPageViewLanguage()->getCode();
343  $variantOverrides = $variantsLogos[$preferred] ?? null;
344  // Overrides the logo
345  if ( $variantOverrides ) {
346  foreach ( $variantOverrides as $key => $val ) {
347  $logoData[$key] = $val;
348  }
349  }
350  }
351  return $logoData;
352  }
353 
357  private function buildSearchProps(): array {
358  $config = $this->getConfig();
359 
360  $props = [
361  'form-action' => $config->get( 'Script' ),
362  'html-button-search-fallback' => $this->makeSearchButton(
363  'fulltext',
364  [ 'id' => 'mw-searchButton', 'class' => 'searchButton mw-fallbackSearchButton' ]
365  ),
366  'html-button-search' => $this->makeSearchButton(
367  'go',
368  [ 'id' => 'searchButton', 'class' => 'searchButton' ]
369  ),
370  'html-input' => $this->makeSearchInput( [ 'id' => 'searchInput' ] ),
371  'msg-search' => $this->msg( 'search' )->text(),
372  'page-title' => $this->getSearchPageTitle()->getPrefixedDBkey(),
373  ];
374 
375  return $props;
376  }
377 
385  private function tailElement( $out ) {
386  $tail = [
387  MWDebug::getDebugHTML( $this ),
388  $this->bottomScripts(),
389  wfReportTime( $out->getCSP()->getNonce() ),
391  . Html::closeElement( 'body' )
392  . Html::closeElement( 'html' )
393  ];
394 
395  return WrappedStringList::join( "\n", $tail );
396  }
397 }
SkinMustache\buildSearchProps
buildSearchProps()
Definition: SkinMustache.php:357
Skin\prepareSubtitle
prepareSubtitle()
Prepare the subtitle of the page for output in the skin if one has been set.
Definition: Skin.php:2561
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:72
Skin\getSiteNotice
getSiteNotice()
Definition: Skin.php:1987
MWDebug\getDebugHTML
static getDebugHTML(IContextSource $context)
Returns the HTML to add to the page for the toolbar.
Definition: MWDebug.php:609
SkinMustache\getPortletLabel
getPortletLabel( $name)
Definition: SkinMustache.php:98
SkinTemplate\buildContentNavigationUrls
buildContentNavigationUrls()
a structured array of links usually used for the tabs in a skin
Definition: SkinTemplate.php:956
Skin\getPersonalToolsForMakeListItem
getPersonalToolsForMakeListItem( $urls, $applyClassesToListItems=false)
Create an array of personal tools items from the data in the quicktemplate stored by SkinTemplate.
Definition: Skin.php:2192
Sanitizer\escapeIdForAttribute
static escapeIdForAttribute( $id, $mode=self::ID_PRIMARY)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid HTM...
Definition: Sanitizer.php:811
SkinMustache\generateHTML
generateHTML()
Subclasses not wishing to use the QuickTemplate render method can rewrite this method,...
Definition: SkinMustache.php:132
SkinMustache\getPortletData
getPortletData( $name, array $items)
Definition: SkinMustache.php:40
MWDebug\getHTMLDebugLog
static getHTMLDebugLog()
Generate debug log in HTML for displaying at the bottom of the main content area.
Definition: MWDebug.php:643
SkinMustache\getLogoData
getLogoData()
Definition: SkinMustache.php:336
Sanitizer\escapeClass
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:972
Title\newMainPage
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:742
SkinTemplate\wrapHTML
wrapHTML( $title, $html)
Wrap the body text with language information and identifiable element.
Definition: SkinTemplate.php:168
Skin\makeListItem
makeListItem( $key, $item, $options=[])
Generates a list item for a navigation, portlet, portal, sidebar...
Definition: Skin.php:2411
Skin\bottomScripts
bottomScripts()
This gets called shortly before the "</body>" tag.
Definition: Skin.php:710
Skin\afterContentHook
afterContentHook()
This runs a hook to allow extensions placing their stuff after content and article metadata (e....
Definition: Skin.php:684
SkinMustache\getPortletsTemplateData
getPortletsTemplateData()
Definition: SkinMustache.php:205
Html\closeElement
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:316
wfReportTime
wfReportTime( $nonce=null)
Returns a script tag that stores the amount of time it took MediaWiki to handle the request in millis...
Definition: GlobalFunctions.php:1265
SkinMustache\tailElement
tailElement( $out)
The final bits that go to the bottom of a page HTML document including the closing tags.
Definition: SkinMustache.php:385
Skin\getIndicatorsData
getIndicatorsData( $indicators)
Return an array of indicator data.
Definition: Skin.php:2166
Skin\buildSidebar
buildSidebar()
Build an array that represents the sidebar(s), the navigation bar among them.
Definition: Skin.php:1673
SkinTemplate\getFooterIcons
getFooterIcons()
Get template representation of the footer.
Definition: SkinTemplate.php:221
Skin\getCategories
getCategories()
Definition: Skin.php:649
Skin\getSearchPageTitle
getSearchPageTitle()
Definition: Skin.php:2619
ContextSource\getOutput
getOutput()
Definition: ContextSource.php:126
ResourceLoaderSkinModule\getAvailableLogos
static getAvailableLogos( $conf)
Return an array of all available logos that a skin may use.
Definition: ResourceLoaderSkinModule.php:529
ContextSource\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:197
SkinTemplate\setupTemplateContext
setupTemplateContext()
Setup class properties that are necessary prior to calling setupTemplateForOutput.
Definition: SkinTemplate.php:93
$content
$content
Definition: router.php:76
Skin\makeSearchInput
makeSearchInput( $attrs=[])
Definition: Skin.php:2475
SkinMustache\$templateParser
TemplateParser null $templateParser
Definition: SkinMustache.php:30
SkinTemplate\$template
string $template
For QuickTemplate, the name of the subclass which will actually fill the template.
Definition: SkinTemplate.php:45
Skin\printSource
printSource()
Text with the permalink to the source page, usually shown on the footer of a printed page.
Definition: Skin.php:733
Skin\getAfterPortlet
getAfterPortlet(string $name)
Allows extensions to hook into known portlets and add stuff to them.
Definition: Skin.php:2548
Linker\tooltip
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition: Linker.php:2507
Skin\getNewtalks
getNewtalks()
Gets new talk page messages for the current user and returns an appropriate alert message (or an empt...
Definition: Skin.php:1833
SkinMustache\getFooterTemplateData
getFooterTemplateData()
Get rows that make up the footer.
Definition: SkinMustache.php:269
SkinTemplate\prepareUserLanguageAttributes
prepareUserLanguageAttributes()
Prepare user language attribute links.
Definition: SkinTemplate.php:198
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
Skin\getFooterLinks
getFooterLinks()
Get template representation of the footer containing site footer links as well as standard footer lin...
Definition: Skin.php:2581
SkinTemplate\prepareUndeleteLink
prepareUndeleteLink()
Prepare undelete link for output in page.
Definition: SkinTemplate.php:263
SkinMustache
Generic template for use with Mustache templates.
Definition: SkinMustache.php:26
SkinMustache\getTemplateData
getTemplateData()
Subclasses may extend this method to add additional template data.
Definition: SkinMustache.php:167
Skin\makeSearchButton
makeSearchButton( $mode, $attrs=[])
Definition: Skin.php:2497
TemplateParser
Definition: TemplateParser.php:27
SkinTemplate
Base class for template-based skins.
Definition: SkinTemplate.php:40
SkinTemplate\injectLegacyMenusIntoPersonalTools
injectLegacyMenusIntoPersonalTools(array $contentNavigation)
Insert legacy menu items from content navigation into the personal toolbar.
Definition: SkinTemplate.php:1351
SkinMustache\getTemplateParser
getTemplateParser()
Get the template parser, it will be lazily created if not already set.
Definition: SkinMustache.php:119
Skin\makeFooterIcon
makeFooterIcon( $icon, $withImage='withImage')
Renders a $wgFooterIcons icon according to the method's arguments.
Definition: Skin.php:1043