MediaWiki master
SkinComponentFooter.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Skin;
4
5use Action;
6use Article;
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 // This needs to be the relevant Title rather than just the raw Title for e.g. special pages that render content
102 $title = $skinContext->getRelevantTitle();
103 $titleExists = $title && $title->exists();
104 $config = $skinContext->getConfig();
105 $maxCredits = $config->get( MainConfigNames::MaxCredits );
106 $showCreditsIfMax = $config->get( MainConfigNames::ShowCreditsIfMax );
107 $useCredits = $titleExists
108 && $out->isArticle()
109 && $out->isRevisionCurrent()
110 && $maxCredits !== 0;
111
113 if ( $useCredits ) {
114 $article = Article::newFromWikiPage( $skinContext->getWikiPage(), $ctx );
115 $action = Action::factory( 'credits', $article, $ctx );
116 }
117
118 '@phan-var CreditsAction $action';
119 return [
120 'lastmod' => !$useCredits ? $this->lastModified() : null,
121 'numberofwatchingusers' => null,
122 'credits' => $useCredits && $action ?
123 $action->getCredits( $maxCredits, $showCreditsIfMax ) : null,
124 'copyright' => $titleExists &&
125 $out->showsCopyright() ? $this->getCopyright() : null,
126 ];
127 }
128
132 private function getCopyright() {
133 $copyright = new SkinComponentCopyright( $this->skinContext );
134 return $copyright->getTemplateData()[ 'html' ];
135 }
136
146 private function formatFooterInfoData( array $data ): array {
147 $formattedData = [];
148 foreach ( $data as $key => $item ) {
149 if ( $item ) {
150 $formattedData[ $key ] = [
151 'id' => 'footer-info-' . $key,
152 'html' => $item
153 ];
154 }
155 }
156 return $formattedData;
157 }
158
165 private function getSiteFooterLinks(): array {
166 $siteLinksData = [];
167 $siteLinks = [
168 'privacy' => [ 'privacy', 'privacypage' ],
169 'about' => [ 'aboutsite', 'aboutpage' ],
170 'disclaimers' => [ 'disclaimers', 'disclaimerpage' ]
171 ];
172 $localizer = $this->skinContext->getMessageLocalizer();
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 if ( $title !== null ) {
182 $siteLinksData[$key] = [
183 'id' => "footer-places-$key",
184 'text' => $localizer->msg( $siteLink[0] )->text(),
185 'href' => $title->fixSpecialName()->getLinkURL()
186 ];
187 }
188 }
189 }
190 return $siteLinksData;
191 }
192
204 public static function makeFooterIconHTML( Config $config, $icon, string $withImage = 'withImage' ): string {
205 if ( is_string( $icon ) ) {
206 $html = $icon;
207 } else { // Assuming array
208 $url = $icon['url'] ?? null;
209 unset( $icon['url'] );
210 if ( isset( $icon['src'] ) && $withImage === 'withImage' ) {
211 // Lazy-load footer icons, since they're not part of the printed view.
212 $icon['loading'] = 'lazy';
213 // do this the lazy way, just pass icon data as an attribute array
214 $html = Html::element( 'img', $icon );
215 } else {
216 $html = htmlspecialchars( $icon['alt'] ?? '' );
217 }
218 if ( $url ) {
219 $html = Html::rawElement( 'a', [
220 'href' => $url,
221 'target' => $config->get( MainConfigNames::ExternalLinkTarget ),
222 ],
223 $html );
224 }
225 }
226 return $html;
227 }
228
236 public static function getFooterIconsData( Config $config ) {
237 $footericons = [];
238 foreach (
239 $config->get( MainConfigNames::FooterIcons ) as $footerIconsKey => &$footerIconsBlock
240 ) {
241 if ( count( $footerIconsBlock ) > 0 ) {
242 $footericons[$footerIconsKey] = [];
243 foreach ( $footerIconsBlock as &$footerIcon ) {
244 if ( isset( $footerIcon['src'] ) ) {
245 if ( !isset( $footerIcon['width'] ) ) {
246 $footerIcon['width'] = 88;
247 }
248 if ( !isset( $footerIcon['height'] ) ) {
249 $footerIcon['height'] = 31;
250 }
251 }
252
253 // Only output icons which have an image.
254 // For historic reasons this mimics the `icononly` option
255 // for BaseTemplate::getFooterIcons.
256 // In some cases the icon may be an empty array.
257 // Filter these out. (See T269776)
258 if ( is_string( $footerIcon ) || isset( $footerIcon['src'] ) ) {
259 $footericons[$footerIconsKey][] = $footerIcon;
260 }
261 }
262
263 // If no valid icons with images were added, unset the parent array
264 // Should also prevent empty arrays from when no copyright is set.
265 if ( !count( $footericons[$footerIconsKey] ) ) {
266 unset( $footericons[$footerIconsKey] );
267 }
268 }
269 }
270 return $footericons;
271 }
272
280 private function getFooterIcons(): array {
281 $dataIcons = [];
282 $skinContext = $this->skinContext;
283 // If footer icons are enabled append to the end of the rows
284 $footerIcons = $skinContext->getFooterIcons();
285
286 if ( count( $footerIcons ) > 0 ) {
287 $icons = [];
288 foreach ( $footerIcons as $blockName => $blockIcons ) {
289 $html = '';
290 foreach ( $blockIcons as $icon ) {
291 $html .= $skinContext->makeFooterIcon( $icon );
292 }
293 // For historic reasons this mimics the `icononly` option
294 // for BaseTemplate::getFooterIcons. Empty rows should not be output.
295 if ( $html ) {
296 $block = htmlspecialchars( $blockName );
297 $icons[$block] = [
298 'name' => $block,
299 'id' => 'footer-' . $block . 'ico',
300 'html' => $html,
301 'class' => [ 'noprint' ],
302 ];
303 }
304 }
305
306 // Empty rows should not be output.
307 // This is how Vector has behaved historically but we can revisit later if necessary.
308 if ( count( $icons ) > 0 ) {
309 $dataIcons = new SkinComponentMenu(
310 'footer-icons',
311 $icons,
312 $this->skinContext->getMessageLocalizer(),
313 '',
314 []
315 );
316 }
317 }
318
319 return $dataIcons ? $dataIcons->getTemplateData() : [];
320 }
321
333 private function formatFooterDataForCurrentSpec( array $data ): array {
334 $formattedData = [];
335 foreach ( $data as $key => $item ) {
336 unset( $item['html-tooltip'] );
337 unset( $item['html-items'] );
338 unset( $item['html-after-portal'] );
339 unset( $item['html-before-portal'] );
340 unset( $item['label'] );
341 unset( $item['class'] );
342 foreach ( $item['array-items'] ?? [] as $index => $arrayItem ) {
343 unset( $item['array-items'][$index]['html-item'] );
344 }
345 $formattedData[$key] = $item;
346 $formattedData[$key]['className'] = $key === 'data-icons' ? 'noprint' : null;
347 }
348 return $formattedData;
349 }
350
357 private function lastModified() {
358 $skinContext = $this->skinContext;
359 $out = $skinContext->getOutput();
360 $timestamp = $out->getRevisionTimestamp();
361
362 // No cached timestamp, load it from the database
363 // TODO: This code shouldn't be necessary, revision ID should always be available
364 // Move this logic to OutputPage::getRevisionTimestamp if needed.
365 if ( $timestamp === null ) {
366 $revId = $out->getRevisionId();
367 if ( $revId !== null ) {
368 $timestamp = MediaWikiServices::getInstance()->getRevisionLookup()->getTimestampFromId( $revId );
369 }
370 }
371
372 $lastModified = new SkinComponentLastModified(
373 $skinContext,
374 $timestamp
375 );
376
377 return $lastModified->getTemplateData()['text'];
378 }
379}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
Actions are things which can be done to pages (edit, delete, rollback, etc).
Definition Action.php:51
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:67
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
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:78
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...