MediaWiki REL1_35
ResourceLoaderSkinModule.php
Go to the documentation of this file.
1<?php
31 public $targets = [ 'desktop', 'mobile' ];
32
84 private const FEATURE_FILES = [
85 'logo' => [
86 // Applies the logo and ensures it downloads prior to printing.
87 'all' => [ 'resources/src/mediawiki.skinning/logo.less' ],
88 // Reserves whitespace for the logo in a pseudo element.
89 'print' => [ 'resources/src/mediawiki.skinning/logo-print.less' ],
90 ],
91 'content' => [
92 'screen' => [ 'resources/src/mediawiki.skinning/content.css' ],
93 ],
94 'interface' => [
95 'screen' => [ 'resources/src/mediawiki.skinning/interface.css' ],
96 ],
97 'normalize' => [
98 'screen' => [ 'resources/src/mediawiki.skinning/normalize.less' ],
99 ],
100 'elements' => [
101 'screen' => [ 'resources/src/mediawiki.skinning/elements.css' ],
102 ],
103 'legacy' => [
104 'all' => [ 'resources/src/mediawiki.skinning/messageBoxes.less' ],
105 'print' => [ 'resources/src/mediawiki.skinning/commonPrint.css' ],
106 'screen' => [ 'resources/src/mediawiki.skinning/legacy.less' ],
107 ],
108 'i18n-ordered-lists' => [
109 'screen' => [ 'resources/src/mediawiki.skinning/i18n-ordered-lists.less' ],
110 ],
111 'i18n-all-lists-margins' => [
112 'screen' => [ 'resources/src/mediawiki.skinning/i18n-all-lists-margins.less' ],
113 ],
114 'i18n-headings' => [
115 'screen' => [ 'resources/src/mediawiki.skinning/i18n-headings.less' ],
116 ],
117 ];
118
120 private $features;
121
123 private const DEFAULT_FEATURES = [
124 'logo' => false,
125 'content' => false,
126 'interface' => false,
127 'elements' => false,
128 'legacy' => false,
129 'i18n-ordered-lists' => false,
130 'i18n-all-lists-margins' => false,
131 'i18n-headings' => false,
132 ];
133
134 public function __construct(
135 array $options = [],
136 $localBasePath = null,
137 $remoteBasePath = null
138 ) {
139 parent::__construct( $options, $localBasePath, $remoteBasePath );
140 $features = $options['features'] ??
141 // For historic reasons if nothing is declared logo and legacy features are enabled.
142 [
143 'logo' => true,
144 'legacy' => true
145 ];
146 $enabledFeatures = [];
147 $compatibilityMode = false;
148 foreach ( $features as $key => $enabled ) {
149 if ( is_bool( $enabled ) ) {
150 $enabledFeatures[$key] = $enabled;
151 } else {
152 // operating in array mode.
153 $enabledFeatures[$enabled] = true;
154 $compatibilityMode = true;
155 }
156 }
157 // If the module didn't specify an option use the default features values.
158 // This allows new features to be turned on automatically.
159 if ( !$compatibilityMode ) {
160 foreach ( self::DEFAULT_FEATURES as $key => $enabled ) {
161 if ( !isset( $enabledFeatures[$key] ) ) {
162 $enabledFeatures[$key] = $enabled;
163 }
164 }
165 }
166 $this->features = array_filter(
167 array_keys( $enabledFeatures ),
168 function ( $key ) use ( $enabledFeatures ) {
169 return $enabledFeatures[ $key ];
170 }
171 );
172 }
173
180 public function getStyleFiles( ResourceLoaderContext $context ) {
181 $styles = parent::getStyleFiles( $context );
182
183 list( $defaultLocalBasePath, $defaultRemoteBasePath ) =
185
186 foreach ( $this->features as $feature ) {
187 if ( !isset( self::FEATURE_FILES[$feature] ) ) {
188 // We could be an old version of MediaWiki and a new feature is being requested (T271441).
189 continue;
190 }
191 foreach ( self::FEATURE_FILES[$feature] as $mediaType => $files ) {
192 if ( !isset( $styles[$mediaType] ) ) {
193 $styles[$mediaType] = [];
194 }
195 foreach ( $files as $filepath ) {
196 $styles[$mediaType][] = new ResourceLoaderFilePath(
197 $filepath,
198 $defaultLocalBasePath,
199 $defaultRemoteBasePath
200 );
201 }
202 }
203 }
204
205 return $styles;
206 }
207
212 public function getStyles( ResourceLoaderContext $context ) {
213 $logo = $this->getLogoData( $this->getConfig() );
214 $styles = parent::getStyles( $context );
215 $this->normalizeStyles( $styles );
216
217 $isLogoFeatureEnabled = in_array( 'logo', $this->features );
218 if ( $isLogoFeatureEnabled ) {
219 $default = !is_array( $logo ) ? $logo : $logo['1x'];
220 $styles['all'][] = '.mw-wiki-logo { background-image: ' .
221 CSSMin::buildUrlValue( $default ) .
222 '; }';
223 if ( is_array( $logo ) ) {
224 if ( isset( $logo['svg'] ) ) {
225 $styles['all'][] = '.mw-wiki-logo { ' .
226 'background-image: -webkit-linear-gradient(transparent, transparent), ' .
227 CSSMin::buildUrlValue( $logo['svg'] ) . '; ' .
228 'background-image: linear-gradient(transparent, transparent), ' .
229 CSSMin::buildUrlValue( $logo['svg'] ) . ';' .
230 'background-size: 135px auto; }';
231 } else {
232 if ( isset( $logo['1.5x'] ) ) {
233 $styles[
234 '(-webkit-min-device-pixel-ratio: 1.5), ' .
235 '(min--moz-device-pixel-ratio: 1.5), ' .
236 '(min-resolution: 1.5dppx), ' .
237 '(min-resolution: 144dpi)'
238 ][] = '.mw-wiki-logo { background-image: ' .
239 CSSMin::buildUrlValue( $logo['1.5x'] ) . ';' .
240 'background-size: 135px auto; }';
241 }
242 if ( isset( $logo['2x'] ) ) {
243 $styles[
244 '(-webkit-min-device-pixel-ratio: 2), ' .
245 '(min--moz-device-pixel-ratio: 2), ' .
246 '(min-resolution: 2dppx), ' .
247 '(min-resolution: 192dpi)'
248 ][] = '.mw-wiki-logo { background-image: ' .
249 CSSMin::buildUrlValue( $logo['2x'] ) . ';' .
250 'background-size: 135px auto; }';
251 }
252 }
253 }
254 }
255
256 return $styles;
257 }
258
263 public function getPreloadLinks( ResourceLoaderContext $context ) {
264 return $this->getLogoPreloadlinks();
265 }
266
271 private function getLogoPreloadlinks() : array {
272 $logo = $this->getLogoData( $this->getConfig() );
273
274 $logosPerDppx = [];
275 $logos = [];
276
277 $preloadLinks = [];
278
279 if ( !is_array( $logo ) ) {
280 // No media queries required if we only have one variant
281 $preloadLinks[$logo] = [ 'as' => 'image' ];
282 return $preloadLinks;
283 }
284
285 if ( isset( $logo['svg'] ) ) {
286 // No media queries required if we only have a 1x and svg variant
287 // because all preload-capable browsers support SVGs
288 $preloadLinks[$logo['svg']] = [ 'as' => 'image' ];
289 return $preloadLinks;
290 }
291
292 foreach ( $logo as $dppx => $src ) {
293 // Keys are in this format: "1.5x"
294 $dppx = substr( $dppx, 0, -1 );
295 $logosPerDppx[$dppx] = $src;
296 }
297
298 // Because PHP can't have floats as array keys
299 uksort( $logosPerDppx, function ( $a, $b ) {
300 $a = floatval( $a );
301 $b = floatval( $b );
302 // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
303 return $a <=> $b;
304 } );
305
306 foreach ( $logosPerDppx as $dppx => $src ) {
307 $logos[] = [
308 'dppx' => $dppx,
309 'src' => $src
310 ];
311 }
312
313 $logosCount = count( $logos );
314 // Logic must match ResourceLoaderSkinModule:
315 // - 1x applies to resolution < 1.5dppx
316 // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
317 // - 2x applies to resolution >= 2dppx
318 // Note that min-resolution and max-resolution are both inclusive.
319 for ( $i = 0; $i < $logosCount; $i++ ) {
320 if ( $i === 0 ) {
321 // Smallest dppx
322 // min-resolution is ">=" (larger than or equal to)
323 // "not min-resolution" is essentially "<"
324 $media_query = 'not all and (min-resolution: ' . $logos[1]['dppx'] . 'dppx)';
325 } elseif ( $i !== $logosCount - 1 ) {
326 // In between
327 // Media query expressions can only apply "not" to the entire expression
328 // (e.g. can't express ">= 1.5 and not >= 2).
329 // Workaround: Use <= 1.9999 in place of < 2.
330 $upper_bound = floatval( $logos[$i + 1]['dppx'] ) - 0.000001;
331 $media_query = '(min-resolution: ' . $logos[$i]['dppx'] .
332 'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
333 } else {
334 // Largest dppx
335 $media_query = '(min-resolution: ' . $logos[$i]['dppx'] . 'dppx)';
336 }
337
338 $preloadLinks[$logos[$i]['src']] = [
339 'as' => 'image',
340 'media' => $media_query
341 ];
342 }
343
344 return $preloadLinks;
345 }
346
355 private function normalizeStyles( array &$styles ) : void {
356 foreach ( $styles as $key => $val ) {
357 if ( !is_array( $val ) ) {
358 $styles[$key] = [ $val ];
359 }
360 }
361 }
362
373 public static function getAvailableLogos( $conf ) : array {
374 $logos = $conf->get( 'Logos' );
375 if ( $logos === false ) {
376 // no logos were defined... this will either
377 // 1. Load from wgLogo and wgLogoHD
378 // 2. Trigger runtime exception if those are not defined.
379 $logos = [];
380 }
381
382 // If logos['1x'] is not defined, see if we can use wgLogo
383 if ( !isset( $logos[ '1x' ] ) ) {
384 $logo = $conf->get( 'Logo' );
385 if ( $logo ) {
386 $logos['1x'] = $logo;
387 }
388 }
389
390 try {
391 $logoHD = $conf->get( 'LogoHD' );
392 // make sure not false
393 if ( $logoHD ) {
394 // wfDeprecated( __METHOD__ . ' with $wgLogoHD set instead of $wgLogos', '1.35', false, 1 );
395 $logos += $logoHD;
396 }
397 } catch ( ConfigException $e ) {
398 // no backwards compatibility changes needed.
399 }
400
401 // check the configuration is valid
402 if ( !isset( $logos['1x'] ) ) {
403 throw new \RuntimeException( "The key `1x` is required for wgLogos or wgLogo must be defined." );
404 }
405 // return the modified logos!
406 return $logos;
407 }
408
417 protected function getLogoData( Config $conf ) {
418 $logoHD = self::getAvailableLogos( $conf );
419 $logo = $logoHD['1x'];
420
421 $logo1Url = OutputPage::transformResourcePath( $conf, $logo );
422
423 $logoUrls = [
424 '1x' => $logo1Url,
425 ];
426
427 if ( isset( $logoHD['svg'] ) ) {
428 $logoUrls['svg'] = OutputPage::transformResourcePath(
429 $conf,
430 $logoHD['svg']
431 );
432 } elseif ( isset( $logoHD['1.5x'] ) || isset( $logoHD['2x'] ) ) {
433 // Only 1.5x and 2x are supported
434 if ( isset( $logoHD['1.5x'] ) ) {
435 $logoUrls['1.5x'] = OutputPage::transformResourcePath(
436 $conf,
437 $logoHD['1.5x']
438 );
439 }
440 if ( isset( $logoHD['2x'] ) ) {
441 $logoUrls['2x'] = OutputPage::transformResourcePath(
442 $conf,
443 $logoHD['2x']
444 );
445 }
446 } else {
447 // Return a string rather than a one-element array, getLogoPreloadlinks depends on this
448 return $logo1Url;
449 }
450
451 return $logoUrls;
452 }
453
458 public function isKnownEmpty( ResourceLoaderContext $context ) {
459 // Regardless of whether the files are specified, we always
460 // provide mw-wiki-logo styles.
461 return false;
462 }
463
470 protected function getLessVars( ResourceLoaderContext $context ) {
471 $lessVars = parent::getLessVars( $context );
472 $logos = self::getAvailableLogos( $this->getConfig() );
473
474 if ( isset( $logos['wordmark'] ) ) {
475 $logo = $logos['wordmark'];
476 $lessVars[ 'logo-enabled' ] = true;
477 $lessVars[ 'logo-wordmark-url' ] = CSSMin::buildUrlValue( $logo['src'] );
478 $lessVars[ 'logo-wordmark-width' ] = intval( $logo['width'] );
479 $lessVars[ 'logo-wordmark-height' ] = intval( $logo['height'] );
480 } else {
481 $lessVars[ 'logo-enabled' ] = false;
482 }
483 return $lessVars;
484 }
485
486 public function getDefinitionSummary( ResourceLoaderContext $context ) {
487 $summary = parent::getDefinitionSummary( $context );
488 $summary[] = [
489 'logos' => self::getAvailableLogos( $this->getConfig() ),
490 ];
491 return $summary;
492 }
493}
static buildUrlValue( $url)
Build a CSS 'url()' value for the given URL, quoting parentheses (and other funny characters) and esc...
Definition CSSMin.php:216
Exceptions for config failures.
Context object that contains information about the state of a specific ResourceLoader web request.
Module based on local JavaScript/CSS files.
string $localBasePath
Local base path, see __construct()
static extractBasePaths(array $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
array $styles
List of paths to CSS files to always include.
string $remoteBasePath
Remote base path, see __construct()
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Module for skin stylesheets.
const FEATURE_FILES
Every skin should define which features it would like to reuse for core inside a ResourceLoader modul...
$targets
All skins are assumed to be compatible with mobile.
normalizeStyles(array &$styles)
Ensure all media keys use array values.
getDefinitionSummary(ResourceLoaderContext $context)
Get the definition summary for this module.
getStyleFiles(ResourceLoaderContext $context)
Get styles defined in the module definition, plus any enabled feature styles.
getStyles(ResourceLoaderContext $context)
getPreloadLinks(ResourceLoaderContext $context)
getLessVars(ResourceLoaderContext $context)
Get language-specific LESS variables for this module.
static getAvailableLogos( $conf)
Return an array of all available logos that a skin may use.
isKnownEmpty(ResourceLoaderContext $context)
getLogoPreloadlinks()
Helper method for getPreloadLinks()
__construct(array $options=[], $localBasePath=null, $remoteBasePath=null)
Constructs a new module from an options array.
Interface for configuration instances.
Definition Config.php:30
return true
Definition router.php:92