MediaWiki  master
ResourceLoaderSkinModule.php
Go to the documentation of this file.
1 <?php
31  public $targets = [ 'desktop', 'mobile' ];
32 
78  private const FEATURE_FILES = [
79  'logo' => [
80  // Applies the logo and ensures it downloads prior to printing.
81  'all' => [ 'resources/src/mediawiki.skinning/logo.less' ],
82  // Reserves whitespace for the logo in a pseudo element.
83  'print' => [ 'resources/src/mediawiki.skinning/logo-print.less' ],
84  ],
85  'content' => [
86  'screen' => [ 'resources/src/mediawiki.skinning/content.css' ],
87  ],
88  'interface' => [
89  'screen' => [ 'resources/src/mediawiki.skinning/interface.css' ],
90  ],
91  'elements' => [
92  'screen' => [ 'resources/src/mediawiki.skinning/elements.css' ],
93  ],
94  'legacy' => [
95  'print' => [ 'resources/src/mediawiki.skinning/commonPrint.css' ],
96  'screen' => [ 'resources/src/mediawiki.skinning/legacy.less' ],
97  ],
98  'i18n-ordered-lists' => [
99  'screen' => [ 'resources/src/mediawiki.skinning/i18n-ordered-lists.less' ],
100  ],
101  'i18n-all-lists-margins' => [
102  'screen' => [ 'resources/src/mediawiki.skinning/i18n-all-lists-margins.less' ],
103  ],
104  'i18n-headings' => [
105  'screen' => [ 'resources/src/mediawiki.skinning/i18n-headings.less' ],
106  ],
107  ];
108 
110  private $features;
111 
112  public function __construct(
113  array $options = [],
114  $localBasePath = null,
115  $remoteBasePath = null
116  ) {
117  parent::__construct( $options, $localBasePath, $remoteBasePath );
118  $this->features = $options['features'] ?? [ 'logo', 'legacy' ];
119  }
120 
127  public function getStyleFiles( ResourceLoaderContext $context ) {
128  $styles = parent::getStyleFiles( $context );
129 
130  list( $defaultLocalBasePath, $defaultRemoteBasePath ) =
132 
133  foreach ( $this->features as $feature ) {
134  if ( !isset( self::FEATURE_FILES[$feature] ) ) {
135  throw new InvalidArgumentException( "Feature `$feature` is not recognised" );
136  }
137  foreach ( self::FEATURE_FILES[$feature] as $mediaType => $files ) {
138  if ( !isset( $styles[$mediaType] ) ) {
139  $styles[$mediaType] = [];
140  }
141  foreach ( $files as $filepath ) {
142  $styles[$mediaType][] = new ResourceLoaderFilePath(
143  $filepath,
144  $defaultLocalBasePath,
145  $defaultRemoteBasePath
146  );
147  }
148  }
149  }
150 
151  return $styles;
152  }
153 
158  public function getStyles( ResourceLoaderContext $context ) {
159  $logo = $this->getLogoData( $this->getConfig() );
160  $styles = parent::getStyles( $context );
161  $this->normalizeStyles( $styles );
162 
163  $isLogoFeatureEnabled = in_array( 'logo', $this->features );
164  if ( $isLogoFeatureEnabled ) {
165  $default = !is_array( $logo ) ? $logo : $logo['1x'];
166  $styles['all'][] = '.mw-wiki-logo { background-image: ' .
167  CSSMin::buildUrlValue( $default ) .
168  '; }';
169  if ( is_array( $logo ) ) {
170  if ( isset( $logo['svg'] ) ) {
171  $styles['all'][] = '.mw-wiki-logo { ' .
172  'background-image: -webkit-linear-gradient(transparent, transparent), ' .
173  CSSMin::buildUrlValue( $logo['svg'] ) . '; ' .
174  'background-image: linear-gradient(transparent, transparent), ' .
175  CSSMin::buildUrlValue( $logo['svg'] ) . ';' .
176  'background-size: 135px auto; }';
177  } else {
178  if ( isset( $logo['1.5x'] ) ) {
179  $styles[
180  '(-webkit-min-device-pixel-ratio: 1.5), ' .
181  '(min--moz-device-pixel-ratio: 1.5), ' .
182  '(min-resolution: 1.5dppx), ' .
183  '(min-resolution: 144dpi)'
184  ][] = '.mw-wiki-logo { background-image: ' .
185  CSSMin::buildUrlValue( $logo['1.5x'] ) . ';' .
186  'background-size: 135px auto; }';
187  }
188  if ( isset( $logo['2x'] ) ) {
189  $styles[
190  '(-webkit-min-device-pixel-ratio: 2), ' .
191  '(min--moz-device-pixel-ratio: 2), ' .
192  '(min-resolution: 2dppx), ' .
193  '(min-resolution: 192dpi)'
194  ][] = '.mw-wiki-logo { background-image: ' .
195  CSSMin::buildUrlValue( $logo['2x'] ) . ';' .
196  'background-size: 135px auto; }';
197  }
198  }
199  }
200  }
201 
202  return $styles;
203  }
204 
209  public function getPreloadLinks( ResourceLoaderContext $context ) {
210  return $this->getLogoPreloadlinks();
211  }
212 
217  private function getLogoPreloadlinks() : array {
218  $logo = $this->getLogoData( $this->getConfig() );
219 
220  $logosPerDppx = [];
221  $logos = [];
222 
223  $preloadLinks = [];
224 
225  if ( !is_array( $logo ) ) {
226  // No media queries required if we only have one variant
227  $preloadLinks[$logo] = [ 'as' => 'image' ];
228  return $preloadLinks;
229  }
230 
231  if ( isset( $logo['svg'] ) ) {
232  // No media queries required if we only have a 1x and svg variant
233  // because all preload-capable browsers support SVGs
234  $preloadLinks[$logo['svg']] = [ 'as' => 'image' ];
235  return $preloadLinks;
236  }
237 
238  foreach ( $logo as $dppx => $src ) {
239  // Keys are in this format: "1.5x"
240  $dppx = substr( $dppx, 0, -1 );
241  $logosPerDppx[$dppx] = $src;
242  }
243 
244  // Because PHP can't have floats as array keys
245  uksort( $logosPerDppx, function ( $a, $b ) {
246  $a = floatval( $a );
247  $b = floatval( $b );
248  // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
249  return $a <=> $b;
250  } );
251 
252  foreach ( $logosPerDppx as $dppx => $src ) {
253  $logos[] = [
254  'dppx' => $dppx,
255  'src' => $src
256  ];
257  }
258 
259  $logosCount = count( $logos );
260  // Logic must match ResourceLoaderSkinModule:
261  // - 1x applies to resolution < 1.5dppx
262  // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
263  // - 2x applies to resolution >= 2dppx
264  // Note that min-resolution and max-resolution are both inclusive.
265  for ( $i = 0; $i < $logosCount; $i++ ) {
266  if ( $i === 0 ) {
267  // Smallest dppx
268  // min-resolution is ">=" (larger than or equal to)
269  // "not min-resolution" is essentially "<"
270  $media_query = 'not all and (min-resolution: ' . $logos[1]['dppx'] . 'dppx)';
271  } elseif ( $i !== $logosCount - 1 ) {
272  // In between
273  // Media query expressions can only apply "not" to the entire expression
274  // (e.g. can't express ">= 1.5 and not >= 2).
275  // Workaround: Use <= 1.9999 in place of < 2.
276  $upper_bound = floatval( $logos[$i + 1]['dppx'] ) - 0.000001;
277  $media_query = '(min-resolution: ' . $logos[$i]['dppx'] .
278  'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
279  } else {
280  // Largest dppx
281  $media_query = '(min-resolution: ' . $logos[$i]['dppx'] . 'dppx)';
282  }
283 
284  $preloadLinks[$logos[$i]['src']] = [
285  'as' => 'image',
286  'media' => $media_query
287  ];
288  }
289 
290  return $preloadLinks;
291  }
292 
301  private function normalizeStyles( array &$styles ) : void {
302  foreach ( $styles as $key => $val ) {
303  if ( !is_array( $val ) ) {
304  $styles[$key] = [ $val ];
305  }
306  }
307  }
308 
319  public static function getAvailableLogos( $conf ) : array {
320  $logos = $conf->get( 'Logos' );
321  if ( $logos === false ) {
322  // no logos were defined... this will either
323  // 1. Load from wgLogo and wgLogoHD
324  // 2. Trigger runtime exception if those are not defined.
325  $logos = [];
326  }
327 
328  // If logos['1x'] is not defined, see if we can use wgLogo
329  if ( !isset( $logos[ '1x' ] ) ) {
330  $logo = $conf->get( 'Logo' );
331  if ( $logo ) {
332  $logos['1x'] = $logo;
333  }
334  }
335 
336  try {
337  $logoHD = $conf->get( 'LogoHD' );
338  // make sure not false
339  if ( $logoHD ) {
340  // wfDeprecated( __METHOD__ . ' with $wgLogoHD set instead of $wgLogos', '1.35', false, 1 );
341  $logos += $logoHD;
342  }
343  } catch ( ConfigException $e ) {
344  // no backwards compatibility changes needed.
345  }
346 
347  // check the configuration is valid
348  if ( !isset( $logos['1x'] ) ) {
349  throw new \RuntimeException( "The key `1x` is required for wgLogos or wgLogo must be defined." );
350  }
351  // return the modified logos!
352  return $logos;
353  }
354 
363  protected function getLogoData( Config $conf ) {
364  $logoHD = self::getAvailableLogos( $conf );
365  $logo = $logoHD['1x'];
366 
367  $logo1Url = OutputPage::transformResourcePath( $conf, $logo );
368 
369  $logoUrls = [
370  '1x' => $logo1Url,
371  ];
372 
373  if ( isset( $logoHD['svg'] ) ) {
374  $logoUrls['svg'] = OutputPage::transformResourcePath(
375  $conf,
376  $logoHD['svg']
377  );
378  } elseif ( isset( $logoHD['1.5x'] ) || isset( $logoHD['2x'] ) ) {
379  // Only 1.5x and 2x are supported
380  if ( isset( $logoHD['1.5x'] ) ) {
381  $logoUrls['1.5x'] = OutputPage::transformResourcePath(
382  $conf,
383  $logoHD['1.5x']
384  );
385  }
386  if ( isset( $logoHD['2x'] ) ) {
387  $logoUrls['2x'] = OutputPage::transformResourcePath(
388  $conf,
389  $logoHD['2x']
390  );
391  }
392  } else {
393  // Return a string rather than a one-element array, getLogoPreloadlinks depends on this
394  return $logo1Url;
395  }
396 
397  return $logoUrls;
398  }
399 
404  public function isKnownEmpty( ResourceLoaderContext $context ) {
405  // Regardless of whether the files are specified, we always
406  // provide mw-wiki-logo styles.
407  return false;
408  }
409 
416  protected function getLessVars( ResourceLoaderContext $context ) {
417  $lessVars = parent::getLessVars( $context );
418  $logos = self::getAvailableLogos( $this->getConfig() );
419 
420  if ( isset( $logos['wordmark'] ) ) {
421  $logo = $logos['wordmark'];
422  $lessVars[ 'logo-enabled' ] = true;
423  $lessVars[ 'logo-wordmark-url' ] = CSSMin::buildUrlValue( $logo['src'] );
424  $lessVars[ 'logo-wordmark-width' ] = intval( $logo['width'] );
425  $lessVars[ 'logo-wordmark-height' ] = intval( $logo['height'] );
426  } else {
427  $lessVars[ 'logo-enabled' ] = false;
428  }
429  return $lessVars;
430  }
431 
432  public function getDefinitionSummary( ResourceLoaderContext $context ) {
433  $summary = parent::getDefinitionSummary( $context );
434  $summary[] = [
435  'logos' => self::getAvailableLogos( $this->getConfig() ),
436  ];
437  return $summary;
438  }
439 }
ResourceLoaderSkinModule\getPreloadLinks
getPreloadLinks(ResourceLoaderContext $context)
Definition: ResourceLoaderSkinModule.php:209
ResourceLoaderContext
Context object that contains information about the state of a specific ResourceLoader web request.
Definition: ResourceLoaderContext.php:33
ResourceLoaderFileModule\$styles
array $styles
List of paths to CSS files to always include.
Definition: ResourceLoaderFileModule.php:92
ResourceLoaderSkinModule\__construct
__construct(array $options=[], $localBasePath=null, $remoteBasePath=null)
Constructs a new module from an options array.
Definition: ResourceLoaderSkinModule.php:112
CSSMin\buildUrlValue
static buildUrlValue( $url)
Build a CSS 'url()' value for the given URL, quoting parentheses (and other funny characters) and esc...
Definition: CSSMin.php:216
ResourceLoaderSkinModule\normalizeStyles
normalizeStyles(array &$styles)
Ensure all media keys use array values.
Definition: ResourceLoaderSkinModule.php:301
ResourceLoaderFilePath
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Definition: ResourceLoaderFilePath.php:28
ResourceLoaderSkinModule\getDefinitionSummary
getDefinitionSummary(ResourceLoaderContext $context)
Get the definition summary for this module.
Definition: ResourceLoaderSkinModule.php:432
ResourceLoaderSkinModule\$features
string[] $features
Definition: ResourceLoaderSkinModule.php:110
ResourceLoaderFileModule
Module based on local JavaScript/CSS files.
Definition: ResourceLoaderFileModule.php:39
Config
Interface for configuration instances.
Definition: Config.php:28
ResourceLoaderSkinModule\getLessVars
getLessVars(ResourceLoaderContext $context)
Get language-specific LESS variables for this module.
Definition: ResourceLoaderSkinModule.php:416
ConfigException
Exceptions for config failures.
Definition: ConfigException.php:28
ResourceLoaderSkinModule\getLogoData
getLogoData(Config $conf)
Definition: ResourceLoaderSkinModule.php:363
ResourceLoaderSkinModule\getAvailableLogos
static getAvailableLogos( $conf)
Return an array of all available logos that a skin may use.
Definition: ResourceLoaderSkinModule.php:319
ResourceLoaderFileModule\extractBasePaths
static extractBasePaths(array $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
Definition: ResourceLoaderFileModule.php:300
ResourceLoaderSkinModule\FEATURE_FILES
const FEATURE_FILES
Every skin should define which features it would like to reuse for core inside a ResourceLoader modul...
Definition: ResourceLoaderSkinModule.php:78
ResourceLoaderSkinModule\getLogoPreloadlinks
getLogoPreloadlinks()
Helper method for getPreloadLinks()
Definition: ResourceLoaderSkinModule.php:217
ResourceLoaderSkinModule\getStyles
getStyles(ResourceLoaderContext $context)
Definition: ResourceLoaderSkinModule.php:158
ResourceLoaderFileModule\$localBasePath
string $localBasePath
Local base path, see __construct()
Definition: ResourceLoaderFileModule.php:41
ResourceLoaderSkinModule\isKnownEmpty
isKnownEmpty(ResourceLoaderContext $context)
Definition: ResourceLoaderSkinModule.php:404
ResourceLoaderSkinModule\getStyleFiles
getStyleFiles(ResourceLoaderContext $context)
Get styles defined in the module definition, plus any enabled feature styles.
Definition: ResourceLoaderSkinModule.php:127
ResourceLoaderFileModule\$remoteBasePath
string $remoteBasePath
Remote base path, see __construct()
Definition: ResourceLoaderFileModule.php:44
ResourceLoaderSkinModule
Module for skin stylesheets.
Definition: ResourceLoaderSkinModule.php:27
ResourceLoaderModule\getConfig
getConfig()
Definition: ResourceLoaderModule.php:215
ResourceLoaderSkinModule\$targets
$targets
All skins are assumed to be compatible with mobile.
Definition: ResourceLoaderSkinModule.php:31
OutputPage\transformResourcePath
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
Definition: OutputPage.php:3940