MediaWiki  master
SkinComponentFooter.php
Go to the documentation of this file.
1 <?php
2 
3 namespace MediaWiki\Skin;
4 
5 use Action;
6 use Article;
7 use CreditsAction;
13 
16  private $skinContext;
17 
21  public function __construct( SkinComponentRegistryContext $skinContext ) {
22  $this->skinContext = $skinContext;
23  }
24 
30  private function getTemplateDataFooter(): array {
31  $data = [
32  'info' => $this->formatFooterInfoData(
33  $this->getFooterInfoData()
34  ),
35  'places' => $this->getSiteFooterLinks(),
36  ];
37  foreach ( $data as $key => $existingItems ) {
38  $newItems = [];
39  $this->skinContext->runHook( 'onSkinAddFooterLinks', [ $key, &$newItems ] );
40  // @phan-suppress-next-line PhanEmptyForeach False positive as hooks modify
41  foreach ( $newItems as $index => $linkHTML ) {
42  $data[ $key ][ $index ] = [
43  'id' => 'footer-' . $key . '-' . $index,
44  'html' => $linkHTML,
45  ];
46  }
47  }
48  return $data;
49  }
50 
54  public function getTemplateData(): array {
55  $footerData = $this->getTemplateDataFooter();
56 
57  // Create the menu components from the footer data.
58  $footerInfoMenuData = new SkinComponentMenu(
59  'footer-info',
60  $footerData['info'],
61  $this->skinContext->getMessageLocalizer()
62  );
63  $footerSiteMenuData = new SkinComponentMenu(
64  'footer-places',
65  $footerData['places'],
66  $this->skinContext->getMessageLocalizer()
67  );
68 
69  // To conform the footer menu data to the current SkinMustache specification,
70  // run the derived data through a cleanup function to unset unexpected data properties
71  // until the spec is updated to reflect the new properties introduced by the menu component.
72  // See https://www.mediawiki.org/wiki/Manual:SkinMustache.php#DataFooter
73  $footerMenuData = [];
74  $footerMenuData['data-info'] = $footerInfoMenuData->getTemplateData();
75  $footerMenuData['data-places'] = $footerSiteMenuData->getTemplateData();
76  $footerMenuData['data-icons'] = $this->getFooterIcons();
77  $footerMenuData = $this->formatFooterDataForCurrentSpec( $footerMenuData );
78 
79  return [
80  'data-info' => $footerMenuData['data-info'],
81  'data-places' => $footerMenuData['data-places'],
82  'data-icons' => $footerMenuData['data-icons']
83  ];
84  }
85 
96  private function getFooterInfoData(): array {
97  $action = null;
98  $skinContext = $this->skinContext;
99  $out = $skinContext->getOutput();
100  $ctx = $skinContext->getContextSource();
101  $title = $out->getTitle();
102  $titleExists = $title && $title->exists();
103  $config = $skinContext->getConfig();
104  $maxCredits = $config->get( MainConfigNames::MaxCredits );
105  $showCreditsIfMax = $config->get( MainConfigNames::ShowCreditsIfMax );
106  $useCredits = $titleExists
107  && $out->isArticle()
108  && $out->isRevisionCurrent()
109  && $maxCredits !== 0;
110 
112  if ( $useCredits ) {
113  $article = Article::newFromWikiPage( $skinContext->getWikiPage(), $ctx );
114  $action = Action::factory( 'credits', $article, $ctx );
115  }
116 
117  '@phan-var CreditsAction $action';
118  return [
119  'lastmod' => !$useCredits ? $this->lastModified() : null,
120  'numberofwatchingusers' => null,
121  'credits' => $useCredits && $action ?
122  $action->getCredits( $maxCredits, $showCreditsIfMax ) : null,
123  'copyright' => $titleExists &&
124  $out->showsCopyright() ? $this->getCopyright() : null,
125  ];
126  }
127 
131  private function getCopyright() {
132  $copyright = new SkinComponentCopyright( $this->skinContext );
133  return $copyright->getTemplateData()[ 'html' ];
134  }
135 
145  private function formatFooterInfoData( array $data ): array {
146  $formattedData = [];
147  foreach ( $data as $key => $item ) {
148  if ( $item ) {
149  $formattedData[ $key ] = [
150  'id' => 'footer-info-' . $key,
151  'html' => $item
152  ];
153  }
154  }
155  return $formattedData;
156  }
157 
164  private function getSiteFooterLinks(): array {
165  $siteLinksData = [];
166  $siteLinks = [
167  'privacy' => [ 'privacy', 'privacypage' ],
168  'about' => [ 'aboutsite', 'aboutpage' ],
169  'disclaimers' => [ 'disclaimers', 'disclaimerpage' ]
170  ];
171  $localizer = $this->skinContext->getMessageLocalizer();
172  $title = null;
173 
174  foreach ( $siteLinks as $key => $siteLink ) {
175  // Check if the link description has been disabled in the default language.
176  // If disabled, it is disabled for all languages.
177  if ( !$localizer->msg( $siteLink[0] )->inContentLanguage()->isDisabled() ) {
178  // Display the link for the user, described in their language (which may or may not be the same as the
179  // default language), but make the link target be the one site-wide page.
180  $title = Title::newFromText( $localizer->msg( $siteLink[1] )->inContentLanguage()->text() );
181  }
182 
183  $siteLinksData[$key] = [
184  'id' => "footer-places-$key",
185  'text' => $localizer->msg( $siteLink[0] )->text(),
186  'href' => $title === null ? '' : $title->fixSpecialName()->getLinkURL()
187  ];
188  }
189  return $siteLinksData;
190  }
191 
203  public static function makeFooterIconHTML( Config $config, $icon, string $withImage = 'withImage' ): string {
204  if ( is_string( $icon ) ) {
205  $html = $icon;
206  } else { // Assuming array
207  $url = $icon['url'] ?? null;
208  unset( $icon['url'] );
209  if ( isset( $icon['src'] ) && $withImage === 'withImage' ) {
210  // Lazy-load footer icons, since they're not part of the printed view.
211  $icon['loading'] = 'lazy';
212  // do this the lazy way, just pass icon data as an attribute array
213  $html = Html::element( 'img', $icon );
214  } else {
215  $html = htmlspecialchars( $icon['alt'] ?? '' );
216  }
217  if ( $url ) {
218  $html = Html::rawElement( 'a', [
219  'href' => $url,
220  'target' => $config->get( MainConfigNames::ExternalLinkTarget ),
221  ],
222  $html );
223  }
224  }
225  return $html;
226  }
227 
235  public static function getFooterIconsData( Config $config ) {
236  $footericons = [];
237  foreach (
238  $config->get( MainConfigNames::FooterIcons ) as $footerIconsKey => &$footerIconsBlock
239  ) {
240  if ( count( $footerIconsBlock ) > 0 ) {
241  $footericons[$footerIconsKey] = [];
242  foreach ( $footerIconsBlock as &$footerIcon ) {
243  if ( isset( $footerIcon['src'] ) ) {
244  if ( !isset( $footerIcon['width'] ) ) {
245  $footerIcon['width'] = 88;
246  }
247  if ( !isset( $footerIcon['height'] ) ) {
248  $footerIcon['height'] = 31;
249  }
250  }
251 
252  // Only output icons which have an image.
253  // For historic reasons this mimics the `icononly` option
254  // for BaseTemplate::getFooterIcons.
255  // In some cases the icon may be an empty array.
256  // Filter these out. (See T269776)
257  if ( is_string( $footerIcon ) || isset( $footerIcon['src'] ) ) {
258  $footericons[$footerIconsKey][] = $footerIcon;
259  }
260  }
261 
262  // If no valid icons with images were added, unset the parent array
263  // Should also prevent empty arrays from when no copyright is set.
264  if ( !count( $footericons[$footerIconsKey] ) ) {
265  unset( $footericons[$footerIconsKey] );
266  }
267  }
268  }
269  return $footericons;
270  }
271 
279  private function getFooterIcons(): array {
280  $dataIcons = [];
281  $skinContext = $this->skinContext;
282  // If footer icons are enabled append to the end of the rows
283  $footerIcons = $skinContext->getFooterIcons();
284 
285  if ( count( $footerIcons ) > 0 ) {
286  $icons = [];
287  foreach ( $footerIcons as $blockName => $blockIcons ) {
288  $html = '';
289  foreach ( $blockIcons as $icon ) {
290  $html .= $skinContext->makeFooterIcon( $icon );
291  }
292  // For historic reasons this mimics the `icononly` option
293  // for BaseTemplate::getFooterIcons. Empty rows should not be output.
294  if ( $html ) {
295  $block = htmlspecialchars( $blockName );
296  $icons[$block] = [
297  'name' => $block,
298  'id' => 'footer-' . $block . 'ico',
299  'html' => $html,
300  'class' => [ 'noprint' ],
301  ];
302  }
303  }
304 
305  // Empty rows should not be output.
306  // This is how Vector has behaved historically but we can revisit later if necessary.
307  if ( count( $icons ) > 0 ) {
308  $dataIcons = new SkinComponentMenu(
309  'footer-icons',
310  $icons,
311  $this->skinContext->getMessageLocalizer(),
312  '',
313  []
314  );
315  }
316  }
317 
318  return $dataIcons ? $dataIcons->getTemplateData() : [];
319  }
320 
332  private function formatFooterDataForCurrentSpec( array $data ): array {
333  $formattedData = [];
334  foreach ( $data as $key => $item ) {
335  unset( $item['html-tooltip'] );
336  unset( $item['html-items'] );
337  unset( $item['html-after-portal'] );
338  unset( $item['html-before-portal'] );
339  unset( $item['label'] );
340  unset( $item['class'] );
341  foreach ( $item['array-items'] ?? [] as $index => $arrayItem ) {
342  unset( $item['array-items'][$index]['html-item'] );
343  }
344  $formattedData[$key] = $item;
345  $formattedData[$key]['className'] = $key === 'data-icons' ? 'noprint' : null;
346  }
347  return $formattedData;
348  }
349 
356  private function lastModified() {
357  $skinContext = $this->skinContext;
358  $out = $skinContext->getOutput();
359  $timestamp = $out->getRevisionTimestamp();
360 
361  // No cached timestamp, load it from the database
362  // TODO: This code shouldn't be necessary, revision ID should always be available
363  // Move this logic to OutputPage::getRevisionTimestamp if needed.
364  if ( $timestamp === null ) {
365  $revId = $out->getRevisionId();
366  if ( $revId !== null ) {
367  $timestamp = MediaWikiServices::getInstance()->getRevisionLookup()->getTimestampFromId( $revId );
368  }
369  }
370 
371  $lastModified = new SkinComponentLastModified(
372  $skinContext,
373  $timestamp
374  );
375 
376  return $lastModified->getTemplateData()['text'];
377  }
378 }
if(!defined('MW_SETUP_CALLBACK'))
Definition: WebStart.php:88
Actions are things which can be done to pages (edit, delete, rollback, etc).
Definition: Action.php:49
static factory(string $action, Article $article, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:86
Legacy class representing an editable page and handling UI for some page actions.
Definition: Article.php:61
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:225
This class is a collection of static functions that serve two purposes:
Definition: Html.php:57
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
getTemplateData()
This returns all the data that is needed to the component.Returned array must be serialized....
__construct(SkinComponentRegistryContext $skinContext)
static makeFooterIconHTML(Config $config, $icon, string $withImage='withImage')
Renders a $wgFooterIcons icon according to the method's arguments.
static getFooterIconsData(Config $config)
Get data representation of icons.
makeFooterIcon( $icon, $withImage='withImage')
Renders a $wgFooterIcons icon according to the method's arguments It exists to support skins overridi...
getConfig()
Returns the config needed for the component.Config
getFooterIcons()
Temporarily allows access to Skin method.
Represents a title within MediaWiki.
Definition: Title.php:76
Interface for configuration instances.
Definition: Config.php:32
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...