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(
220 'a',
221 [
222 'href' => $url,
223 // Using a fake Codex link button, as this is the long-expected UX; our apologies.
224 'class' => [
225 'cdx-button', 'cdx-button--fake-button',
226 'cdx-button--size-large', 'cdx-button--fake-button--enabled'
227 ],
228 'target' => $config->get( MainConfigNames::ExternalLinkTarget ),
229 ],
230 $html
231 );
232 }
233 }
234 return $html;
235 }
236
244 public static function getFooterIconsData( Config $config ) {
245 $footericons = [];
246 foreach (
247 $config->get( MainConfigNames::FooterIcons ) as $footerIconsKey => &$footerIconsBlock
248 ) {
249 if ( count( $footerIconsBlock ) > 0 ) {
250 $footericons[$footerIconsKey] = [];
251 foreach ( $footerIconsBlock as &$footerIcon ) {
252 if ( isset( $footerIcon['src'] ) ) {
253 if ( !isset( $footerIcon['width'] ) ) {
254 $footerIcon['width'] = 88;
255 }
256 if ( !isset( $footerIcon['height'] ) ) {
257 $footerIcon['height'] = 31;
258 }
259 }
260
261 // Only output icons which have an image.
262 // For historic reasons this mimics the `icononly` option
263 // for BaseTemplate::getFooterIcons.
264 // In some cases the icon may be an empty array.
265 // Filter these out. (See T269776)
266 if ( is_string( $footerIcon ) || isset( $footerIcon['src'] ) ) {
267 $footericons[$footerIconsKey][] = $footerIcon;
268 }
269 }
270
271 // If no valid icons with images were added, unset the parent array
272 // Should also prevent empty arrays from when no copyright is set.
273 if ( !count( $footericons[$footerIconsKey] ) ) {
274 unset( $footericons[$footerIconsKey] );
275 }
276 }
277 }
278 return $footericons;
279 }
280
288 private function getFooterIcons(): array {
289 $dataIcons = [];
290 $skinContext = $this->skinContext;
291 // If footer icons are enabled append to the end of the rows
292 $footerIcons = $skinContext->getFooterIcons();
293
294 if ( count( $footerIcons ) > 0 ) {
295 $icons = [];
296 foreach ( $footerIcons as $blockName => $blockIcons ) {
297 $html = '';
298 foreach ( $blockIcons as $icon ) {
299 $html .= $skinContext->makeFooterIcon( $icon );
300 }
301 // For historic reasons this mimics the `icononly` option
302 // for BaseTemplate::getFooterIcons. Empty rows should not be output.
303 if ( $html ) {
304 $block = htmlspecialchars( $blockName );
305 $icons[$block] = [
306 'name' => $block,
307 'id' => 'footer-' . $block . 'ico',
308 'html' => $html,
309 'class' => [ 'noprint' ],
310 ];
311 }
312 }
313
314 // Empty rows should not be output.
315 // This is how Vector has behaved historically but we can revisit later if necessary.
316 if ( count( $icons ) > 0 ) {
317 $dataIcons = new SkinComponentMenu(
318 'footer-icons',
319 $icons,
320 $this->skinContext->getMessageLocalizer(),
321 '',
322 []
323 );
324 }
325 }
326
327 return $dataIcons ? $dataIcons->getTemplateData() : [];
328 }
329
341 private function formatFooterDataForCurrentSpec( array $data ): array {
342 $formattedData = [];
343 foreach ( $data as $key => $item ) {
344 unset( $item['html-tooltip'] );
345 unset( $item['html-items'] );
346 unset( $item['html-after-portal'] );
347 unset( $item['html-before-portal'] );
348 unset( $item['label'] );
349 unset( $item['class'] );
350 foreach ( $item['array-items'] ?? [] as $index => $arrayItem ) {
351 unset( $item['array-items'][$index]['html-item'] );
352 }
353 $formattedData[$key] = $item;
354 $formattedData[$key]['className'] = $key === 'data-icons' ? 'noprint' : null;
355 }
356 return $formattedData;
357 }
358
365 private function lastModified() {
366 $skinContext = $this->skinContext;
367 $out = $skinContext->getOutput();
368 $timestamp = $out->getRevisionTimestamp();
369
370 // No cached timestamp, load it from the database
371 // TODO: This code shouldn't be necessary, revision ID should always be available
372 // Move this logic to OutputPage::getRevisionTimestamp if needed.
373 if ( $timestamp === null ) {
374 $revId = $out->getRevisionId();
375 if ( $revId !== null ) {
376 $timestamp = MediaWikiServices::getInstance()->getRevisionLookup()->getTimestampFromId( $revId );
377 }
378 }
379
380 $lastModified = new SkinComponentLastModified(
381 $skinContext,
382 $timestamp
383 );
384
385 return $lastModified->getTemplateData()['text'];
386 }
387}
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: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.
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:79
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...