MediaWiki master
SkinComponentFooter.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Skin;
4
5use Action;
6use Article;
9use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
14
16 use ProtectedHookAccessorTrait;
17
19 private $skinContext;
20
21 public function __construct( SkinComponentRegistryContext $skinContext ) {
22 $this->skinContext = $skinContext;
23 }
24
28 private function getTemplateDataFooter(): array {
29 $data = [
30 'info' => $this->formatFooterInfoData(
31 $this->getFooterInfoData()
32 ),
33 'places' => $this->getSiteFooterLinks(),
34 ];
35 $skin = $this->skinContext->getContextSource()->getSkin();
36 foreach ( $data as $key => $existingItems ) {
37 $newItems = [];
38 $this->getHookRunner()->onSkinAddFooterLinks( $skin, $key, $newItems );
39 foreach ( $newItems as $index => $linkHTML ) {
40 $data[ $key ][ $index ] = [
41 'id' => 'footer-' . $key . '-' . $index,
42 'html' => $linkHTML,
43 ];
44 }
45 }
46 return $data;
47 }
48
52 public function getTemplateData(): array {
53 $footerData = $this->getTemplateDataFooter();
54
55 // Create the menu components from the footer data.
56 $footerInfoMenuData = new SkinComponentMenu(
57 'footer-info',
58 $footerData['info'],
59 $this->skinContext->getMessageLocalizer()
60 );
61 $footerSiteMenuData = new SkinComponentMenu(
62 'footer-places',
63 $footerData['places'],
64 $this->skinContext->getMessageLocalizer()
65 );
66
67 // To conform the footer menu data to the current SkinMustache specification,
68 // run the derived data through a cleanup function to unset unexpected data properties
69 // until the spec is updated to reflect the new properties introduced by the menu component.
70 // See https://www.mediawiki.org/wiki/Manual:SkinMustache.php#DataFooter
71 $footerMenuData = [];
72 $footerMenuData['data-info'] = $footerInfoMenuData->getTemplateData();
73 $footerMenuData['data-places'] = $footerSiteMenuData->getTemplateData();
74 $footerMenuData['data-icons'] = $this->getFooterIcons();
75 $footerMenuData = $this->formatFooterDataForCurrentSpec( $footerMenuData );
76
77 return [
78 'data-info' => $footerMenuData['data-info'],
79 'data-places' => $footerMenuData['data-places'],
80 'data-icons' => $footerMenuData['data-icons']
81 ];
82 }
83
94 private function getFooterInfoData(): array {
95 $action = null;
96 $skinContext = $this->skinContext;
97 $out = $skinContext->getOutput();
98 $ctx = $skinContext->getContextSource();
99 // This needs to be the relevant Title rather than just the raw Title for e.g. special pages that render content
100 $title = $skinContext->getRelevantTitle();
101 $titleExists = $title && $title->exists();
102 $config = $skinContext->getConfig();
103 $maxCredits = $config->get( MainConfigNames::MaxCredits );
104 $showCreditsIfMax = $config->get( MainConfigNames::ShowCreditsIfMax );
105 $useCredits = $titleExists
106 && $out->isArticle()
107 && $out->isRevisionCurrent()
108 && $maxCredits !== 0;
109
111 if ( $useCredits ) {
112 $article = Article::newFromWikiPage( $skinContext->getWikiPage(), $ctx );
113 $action = Action::factory( 'credits', $article, $ctx );
114 }
115
116 '@phan-var CreditsAction $action';
117 return [
118 'lastmod' => !$useCredits ? $this->lastModified() : null,
119 'numberofwatchingusers' => null,
120 'credits' => $useCredits && $action ?
121 $action->getCredits( $maxCredits, $showCreditsIfMax ) : null,
122 'copyright' => $titleExists &&
123 $out->showsCopyright() ? $this->getCopyright() : null,
124 ];
125 }
126
130 private function getCopyright() {
131 $copyright = new SkinComponentCopyright( $this->skinContext );
132 return $copyright->getTemplateData()[ 'html' ];
133 }
134
144 private function formatFooterInfoData( array $data ): array {
145 $formattedData = [];
146 foreach ( $data as $key => $item ) {
147 if ( $item ) {
148 $formattedData[ $key ] = [
149 'id' => 'footer-info-' . $key,
150 'html' => $item
151 ];
152 }
153 }
154 return $formattedData;
155 }
156
163 private function getSiteFooterLinks(): array {
164 $siteLinksData = [];
165 $siteLinks = [
166 'privacy' => [ 'privacy', 'privacypage' ],
167 'about' => [ 'aboutsite', 'aboutpage' ],
168 'disclaimers' => [ 'disclaimers', 'disclaimerpage' ]
169 ];
170 $localizer = $this->skinContext->getMessageLocalizer();
171
172 foreach ( $siteLinks as $key => $siteLink ) {
173 // Check if the link description has been disabled in the default language.
174 // If disabled, it is disabled for all languages.
175 if ( !$localizer->msg( $siteLink[0] )->inContentLanguage()->isDisabled() ) {
176 // Display the link for the user, described in their language (which may or may not be the same as the
177 // default language), but make the link target be the one site-wide page.
178 $title = Title::newFromText( $localizer->msg( $siteLink[1] )->inContentLanguage()->text() );
179 if ( $title !== null ) {
180 $siteLinksData[$key] = [
181 'id' => "footer-places-$key",
182 'text' => $localizer->msg( $siteLink[0] )->text(),
183 'href' => $title->fixSpecialName()->getLinkURL()
184 ];
185 }
186 }
187 }
188 return $siteLinksData;
189 }
190
202 public static function makeFooterIconHTML( Config $config, $icon, string $withImage = 'withImage' ): string {
203 if ( is_string( $icon ) ) {
204 $html = $icon;
205 } else { // Assuming array
206 $url = $icon['url'] ?? null;
207 unset( $icon['url'] );
208 if ( isset( $icon['src'] ) && $withImage === 'withImage' ) {
209 // Lazy-load footer icons, since they're not part of the printed view.
210 $icon['loading'] = 'lazy';
211 // do this the lazy way, just pass icon data as an attribute array
212 $html = Html::element( 'img', $icon );
213 } else {
214 $html = htmlspecialchars( $icon['alt'] ?? '' );
215 }
216 if ( $url ) {
217 $html = Html::rawElement(
218 'a',
219 [
220 'href' => $url,
221 // Using a fake Codex link button, as this is the long-expected UX; our apologies.
222 'class' => [
223 'cdx-button', 'cdx-button--fake-button',
224 'cdx-button--size-large', 'cdx-button--fake-button--enabled'
225 ],
226 'target' => $config->get( MainConfigNames::ExternalLinkTarget ),
227 ],
228 $html
229 );
230 }
231 }
232 return $html;
233 }
234
242 public static function getFooterIconsData( Config $config ) {
243 $footericons = [];
244 foreach (
245 $config->get( MainConfigNames::FooterIcons ) as $footerIconsKey => &$footerIconsBlock
246 ) {
247 if ( count( $footerIconsBlock ) > 0 ) {
248 $footericons[$footerIconsKey] = [];
249 foreach ( $footerIconsBlock as &$footerIcon ) {
250 if ( isset( $footerIcon['src'] ) ) {
251 if ( !isset( $footerIcon['width'] ) ) {
252 $footerIcon['width'] = 88;
253 }
254 if ( !isset( $footerIcon['height'] ) ) {
255 $footerIcon['height'] = 31;
256 }
257 }
258
259 // Only output icons which have an image.
260 // For historic reasons this mimics the `icononly` option
261 // for BaseTemplate::getFooterIcons.
262 // In some cases the icon may be an empty array.
263 // Filter these out. (See T269776)
264 if ( is_string( $footerIcon ) || isset( $footerIcon['src'] ) ) {
265 $footericons[$footerIconsKey][] = $footerIcon;
266 }
267 }
268
269 // If no valid icons with images were added, unset the parent array
270 // Should also prevent empty arrays from when no copyright is set.
271 if ( !count( $footericons[$footerIconsKey] ) ) {
272 unset( $footericons[$footerIconsKey] );
273 }
274 }
275 }
276 return $footericons;
277 }
278
286 private function getFooterIcons(): array {
287 $dataIcons = [];
288 $skinContext = $this->skinContext;
289 $config = $skinContext->getConfig();
290 // If footer icons are enabled append to the end of the rows
291 $footerIcons = self::getFooterIconsData(
292 $config
293 );
294
295 if ( count( $footerIcons ) > 0 ) {
296 $icons = [];
297 foreach ( $footerIcons as $blockName => $blockIcons ) {
298 $html = '';
299 foreach ( $blockIcons as $icon ) {
300 $html .= self::makeFooterIconHTML(
301 $config, $icon
302 );
303 }
304 // For historic reasons this mimics the `icononly` option
305 // for BaseTemplate::getFooterIcons. Empty rows should not be output.
306 if ( $html ) {
307 $block = htmlspecialchars( $blockName );
308 $icons[$block] = [
309 'name' => $block,
310 'id' => 'footer-' . $block . 'ico',
311 'html' => $html,
312 'class' => [ 'noprint' ],
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 $dataIcons = new SkinComponentMenu(
321 'footer-icons',
322 $icons,
323 $this->skinContext->getMessageLocalizer(),
324 '',
325 []
326 );
327 }
328 }
329
330 return $dataIcons ? $dataIcons->getTemplateData() : [];
331 }
332
344 private function formatFooterDataForCurrentSpec( array $data ): array {
345 $formattedData = [];
346 foreach ( $data as $key => $item ) {
347 unset( $item['html-tooltip'] );
348 unset( $item['html-items'] );
349 unset( $item['html-after-portal'] );
350 unset( $item['html-before-portal'] );
351 unset( $item['label'] );
352 unset( $item['class'] );
353 foreach ( $item['array-items'] ?? [] as $index => $arrayItem ) {
354 unset( $item['array-items'][$index]['html-item'] );
355 }
356 $formattedData[$key] = $item;
357 $formattedData[$key]['className'] = $key === 'data-icons' ? 'noprint' : null;
358 }
359 return $formattedData;
360 }
361
368 private function lastModified() {
369 $skinContext = $this->skinContext;
370 $out = $skinContext->getOutput();
371 $timestamp = $out->getRevisionTimestamp();
372
373 // No cached timestamp, load it from the database
374 // TODO: This code shouldn't be necessary, revision ID should always be available
375 // Move this logic to OutputPage::getRevisionTimestamp if needed.
376 if ( $timestamp === null ) {
377 $revId = $out->getRevisionId();
378 if ( $revId !== null ) {
379 $timestamp = MediaWikiServices::getInstance()->getRevisionLookup()->getTimestampFromId( $revId );
380 }
381 }
382
383 $lastModified = new SkinComponentLastModified(
384 $skinContext,
385 $timestamp
386 );
387
388 return $lastModified->getTemplateData()['text'];
389 }
390}
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:54
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:70
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.
getConfig()
Returns the config needed for the component.Config
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...