MediaWiki 1.43.1
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
24 public function __construct( SkinComponentRegistryContext $skinContext ) {
25 $this->skinContext = $skinContext;
26 }
27
33 private function getTemplateDataFooter(): array {
34 $data = [
35 'info' => $this->formatFooterInfoData(
36 $this->getFooterInfoData()
37 ),
38 'places' => $this->getSiteFooterLinks(),
39 ];
40 $skin = $this->skinContext->getContextSource()->getSkin();
41 foreach ( $data as $key => $existingItems ) {
42 $newItems = [];
43 $this->getHookRunner()->onSkinAddFooterLinks( $skin, $key, $newItems );
44 foreach ( $newItems as $index => $linkHTML ) {
45 $data[ $key ][ $index ] = [
46 'id' => 'footer-' . $key . '-' . $index,
47 'html' => $linkHTML,
48 ];
49 }
50 }
51 return $data;
52 }
53
57 public function getTemplateData(): array {
58 $footerData = $this->getTemplateDataFooter();
59
60 // Create the menu components from the footer data.
61 $footerInfoMenuData = new SkinComponentMenu(
62 'footer-info',
63 $footerData['info'],
64 $this->skinContext->getMessageLocalizer()
65 );
66 $footerSiteMenuData = new SkinComponentMenu(
67 'footer-places',
68 $footerData['places'],
69 $this->skinContext->getMessageLocalizer()
70 );
71
72 // To conform the footer menu data to the current SkinMustache specification,
73 // run the derived data through a cleanup function to unset unexpected data properties
74 // until the spec is updated to reflect the new properties introduced by the menu component.
75 // See https://www.mediawiki.org/wiki/Manual:SkinMustache.php#DataFooter
76 $footerMenuData = [];
77 $footerMenuData['data-info'] = $footerInfoMenuData->getTemplateData();
78 $footerMenuData['data-places'] = $footerSiteMenuData->getTemplateData();
79 $footerMenuData['data-icons'] = $this->getFooterIcons();
80 $footerMenuData = $this->formatFooterDataForCurrentSpec( $footerMenuData );
81
82 return [
83 'data-info' => $footerMenuData['data-info'],
84 'data-places' => $footerMenuData['data-places'],
85 'data-icons' => $footerMenuData['data-icons']
86 ];
87 }
88
99 private function getFooterInfoData(): array {
100 $action = null;
101 $skinContext = $this->skinContext;
102 $out = $skinContext->getOutput();
103 $ctx = $skinContext->getContextSource();
104 // This needs to be the relevant Title rather than just the raw Title for e.g. special pages that render content
105 $title = $skinContext->getRelevantTitle();
106 $titleExists = $title && $title->exists();
107 $config = $skinContext->getConfig();
108 $maxCredits = $config->get( MainConfigNames::MaxCredits );
109 $showCreditsIfMax = $config->get( MainConfigNames::ShowCreditsIfMax );
110 $useCredits = $titleExists
111 && $out->isArticle()
112 && $out->isRevisionCurrent()
113 && $maxCredits !== 0;
114
116 if ( $useCredits ) {
117 $article = Article::newFromWikiPage( $skinContext->getWikiPage(), $ctx );
118 $action = Action::factory( 'credits', $article, $ctx );
119 }
120
121 '@phan-var CreditsAction $action';
122 return [
123 'lastmod' => !$useCredits ? $this->lastModified() : null,
124 'numberofwatchingusers' => null,
125 'credits' => $useCredits && $action ?
126 $action->getCredits( $maxCredits, $showCreditsIfMax ) : null,
127 'copyright' => $titleExists &&
128 $out->showsCopyright() ? $this->getCopyright() : null,
129 ];
130 }
131
135 private function getCopyright() {
136 $copyright = new SkinComponentCopyright( $this->skinContext );
137 return $copyright->getTemplateData()[ 'html' ];
138 }
139
149 private function formatFooterInfoData( array $data ): array {
150 $formattedData = [];
151 foreach ( $data as $key => $item ) {
152 if ( $item ) {
153 $formattedData[ $key ] = [
154 'id' => 'footer-info-' . $key,
155 'html' => $item
156 ];
157 }
158 }
159 return $formattedData;
160 }
161
168 private function getSiteFooterLinks(): array {
169 $siteLinksData = [];
170 $siteLinks = [
171 'privacy' => [ 'privacy', 'privacypage' ],
172 'about' => [ 'aboutsite', 'aboutpage' ],
173 'disclaimers' => [ 'disclaimers', 'disclaimerpage' ]
174 ];
175 $localizer = $this->skinContext->getMessageLocalizer();
176
177 foreach ( $siteLinks as $key => $siteLink ) {
178 // Check if the link description has been disabled in the default language.
179 // If disabled, it is disabled for all languages.
180 if ( !$localizer->msg( $siteLink[0] )->inContentLanguage()->isDisabled() ) {
181 // Display the link for the user, described in their language (which may or may not be the same as the
182 // default language), but make the link target be the one site-wide page.
183 $title = Title::newFromText( $localizer->msg( $siteLink[1] )->inContentLanguage()->text() );
184 if ( $title !== null ) {
185 $siteLinksData[$key] = [
186 'id' => "footer-places-$key",
187 'text' => $localizer->msg( $siteLink[0] )->text(),
188 'href' => $title->fixSpecialName()->getLinkURL()
189 ];
190 }
191 }
192 }
193 return $siteLinksData;
194 }
195
207 public static function makeFooterIconHTML( Config $config, $icon, string $withImage = 'withImage' ): string {
208 if ( is_string( $icon ) ) {
209 $html = $icon;
210 } else { // Assuming array
211 $url = $icon['url'] ?? null;
212 unset( $icon['url'] );
213 if ( isset( $icon['src'] ) && $withImage === 'withImage' ) {
214 // Lazy-load footer icons, since they're not part of the printed view.
215 $icon['loading'] = 'lazy';
216 // do this the lazy way, just pass icon data as an attribute array
217 $html = Html::element( 'img', $icon );
218 } else {
219 $html = htmlspecialchars( $icon['alt'] ?? '' );
220 }
221 if ( $url ) {
222 $html = Html::rawElement(
223 'a',
224 [
225 'href' => $url,
226 // Using a fake Codex link button, as this is the long-expected UX; our apologies.
227 'class' => [
228 'cdx-button', 'cdx-button--fake-button',
229 'cdx-button--size-large', 'cdx-button--fake-button--enabled'
230 ],
231 'target' => $config->get( MainConfigNames::ExternalLinkTarget ),
232 ],
233 $html
234 );
235 }
236 }
237 return $html;
238 }
239
247 public static function getFooterIconsData( Config $config ) {
248 $footericons = [];
249 foreach (
250 $config->get( MainConfigNames::FooterIcons ) as $footerIconsKey => &$footerIconsBlock
251 ) {
252 if ( count( $footerIconsBlock ) > 0 ) {
253 $footericons[$footerIconsKey] = [];
254 foreach ( $footerIconsBlock as &$footerIcon ) {
255 if ( isset( $footerIcon['src'] ) ) {
256 if ( !isset( $footerIcon['width'] ) ) {
257 $footerIcon['width'] = 88;
258 }
259 if ( !isset( $footerIcon['height'] ) ) {
260 $footerIcon['height'] = 31;
261 }
262 }
263
264 // Only output icons which have an image.
265 // For historic reasons this mimics the `icononly` option
266 // for BaseTemplate::getFooterIcons.
267 // In some cases the icon may be an empty array.
268 // Filter these out. (See T269776)
269 if ( is_string( $footerIcon ) || isset( $footerIcon['src'] ) ) {
270 $footericons[$footerIconsKey][] = $footerIcon;
271 }
272 }
273
274 // If no valid icons with images were added, unset the parent array
275 // Should also prevent empty arrays from when no copyright is set.
276 if ( !count( $footericons[$footerIconsKey] ) ) {
277 unset( $footericons[$footerIconsKey] );
278 }
279 }
280 }
281 return $footericons;
282 }
283
291 private function getFooterIcons(): array {
292 $dataIcons = [];
293 $skinContext = $this->skinContext;
294 // If footer icons are enabled append to the end of the rows
295 $footerIcons = $skinContext->getFooterIcons();
296
297 if ( count( $footerIcons ) > 0 ) {
298 $icons = [];
299 foreach ( $footerIcons as $blockName => $blockIcons ) {
300 $html = '';
301 foreach ( $blockIcons as $icon ) {
302 $html .= $skinContext->makeFooterIcon( $icon );
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:53
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:72
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: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...