MediaWiki  master
ResourceLoaderSkinModule.php
Go to the documentation of this file.
1 <?php
31  public $targets = [ 'desktop', 'mobile' ];
32 
64  private const FEATURE_FILES = [
65  'logo' => [],
66  'content' => [
67  'screen' => [ 'resources/src/mediawiki.skinning/content.css' ],
68  ],
69  'interface' => [
70  'screen' => [ 'resources/src/mediawiki.skinning/interface.css' ],
71  ],
72  'elements' => [
73  'screen' => [ 'resources/src/mediawiki.skinning/elements.css' ],
74  ],
75  ];
76 
78  private $features;
79 
80  public function __construct(
81  array $options = [],
82  $localBasePath = null,
83  $remoteBasePath = null
84  ) {
85  parent::__construct( $options, $localBasePath, $remoteBasePath );
86  $this->features = $options['features'] ?? [ 'logo' ];
87  }
88 
96  $styles = parent::getStyleFiles( $context );
97 
98  list( $defaultLocalBasePath, $defaultRemoteBasePath ) =
100 
101  foreach ( $this->features as $feature ) {
102  if ( !isset( self::FEATURE_FILES[$feature] ) ) {
103  throw new InvalidArgumentException( "Feature `$feature` is not recognised" );
104  }
105  foreach ( self::FEATURE_FILES[$feature] as $mediaType => $files ) {
106  if ( !isset( $styles[$mediaType] ) ) {
107  $styles[$mediaType] = [];
108  }
109  foreach ( $files as $filepath ) {
110  $styles[$mediaType][] = new ResourceLoaderFilePath(
111  $filepath,
112  $defaultLocalBasePath,
113  $defaultRemoteBasePath
114  );
115  }
116  }
117  }
118 
119  return $styles;
120  }
121 
127  $logo = $this->getLogoData( $this->getConfig() );
128  $styles = parent::getStyles( $context );
129  $this->normalizeStyles( $styles );
130 
131  $isLogoFeatureEnabled = in_array( 'logo', $this->features );
132  if ( $isLogoFeatureEnabled ) {
133  $default = !is_array( $logo ) ? $logo : $logo['1x'];
134  $styles['all'][] = '.mw-wiki-logo { background-image: ' .
135  CSSMin::buildUrlValue( $default ) .
136  '; }';
137  if ( is_array( $logo ) ) {
138  if ( isset( $logo['svg'] ) ) {
139  $styles['all'][] = '.mw-wiki-logo { ' .
140  'background-image: -webkit-linear-gradient(transparent, transparent), ' .
141  CSSMin::buildUrlValue( $logo['svg'] ) . '; ' .
142  'background-image: linear-gradient(transparent, transparent), ' .
143  CSSMin::buildUrlValue( $logo['svg'] ) . ';' .
144  'background-size: 135px auto; }';
145  } else {
146  if ( isset( $logo['1.5x'] ) ) {
147  $styles[
148  '(-webkit-min-device-pixel-ratio: 1.5), ' .
149  '(min--moz-device-pixel-ratio: 1.5), ' .
150  '(min-resolution: 1.5dppx), ' .
151  '(min-resolution: 144dpi)'
152  ][] = '.mw-wiki-logo { background-image: ' .
153  CSSMin::buildUrlValue( $logo['1.5x'] ) . ';' .
154  'background-size: 135px auto; }';
155  }
156  if ( isset( $logo['2x'] ) ) {
157  $styles[
158  '(-webkit-min-device-pixel-ratio: 2), ' .
159  '(min--moz-device-pixel-ratio: 2), ' .
160  '(min-resolution: 2dppx), ' .
161  '(min-resolution: 192dpi)'
162  ][] = '.mw-wiki-logo { background-image: ' .
163  CSSMin::buildUrlValue( $logo['2x'] ) . ';' .
164  'background-size: 135px auto; }';
165  }
166  }
167  }
168  }
169 
170  return $styles;
171  }
172 
178  return $this->getLogoPreloadlinks();
179  }
180 
185  private function getLogoPreloadlinks() {
186  $logo = $this->getLogoData( $this->getConfig() );
187 
188  $logosPerDppx = [];
189  $logos = [];
190 
191  $preloadLinks = [];
192 
193  if ( !is_array( $logo ) ) {
194  // No media queries required if we only have one variant
195  $preloadLinks[$logo] = [ 'as' => 'image' ];
196  return $preloadLinks;
197  }
198 
199  if ( isset( $logo['svg'] ) ) {
200  // No media queries required if we only have a 1x and svg variant
201  // because all preload-capable browsers support SVGs
202  $preloadLinks[$logo['svg']] = [ 'as' => 'image' ];
203  return $preloadLinks;
204  }
205 
206  foreach ( $logo as $dppx => $src ) {
207  // Keys are in this format: "1.5x"
208  $dppx = substr( $dppx, 0, -1 );
209  $logosPerDppx[$dppx] = $src;
210  }
211 
212  // Because PHP can't have floats as array keys
213  uksort( $logosPerDppx, function ( $a, $b ) {
214  $a = floatval( $a );
215  $b = floatval( $b );
216  // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
217  return $a <=> $b;
218  } );
219 
220  foreach ( $logosPerDppx as $dppx => $src ) {
221  $logos[] = [
222  'dppx' => $dppx,
223  'src' => $src
224  ];
225  }
226 
227  $logosCount = count( $logos );
228  // Logic must match ResourceLoaderSkinModule:
229  // - 1x applies to resolution < 1.5dppx
230  // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
231  // - 2x applies to resolution >= 2dppx
232  // Note that min-resolution and max-resolution are both inclusive.
233  for ( $i = 0; $i < $logosCount; $i++ ) {
234  if ( $i === 0 ) {
235  // Smallest dppx
236  // min-resolution is ">=" (larger than or equal to)
237  // "not min-resolution" is essentially "<"
238  $media_query = 'not all and (min-resolution: ' . $logos[1]['dppx'] . 'dppx)';
239  } elseif ( $i !== $logosCount - 1 ) {
240  // In between
241  // Media query expressions can only apply "not" to the entire expression
242  // (e.g. can't express ">= 1.5 and not >= 2).
243  // Workaround: Use <= 1.9999 in place of < 2.
244  $upper_bound = floatval( $logos[$i + 1]['dppx'] ) - 0.000001;
245  $media_query = '(min-resolution: ' . $logos[$i]['dppx'] .
246  'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
247  } else {
248  // Largest dppx
249  $media_query = '(min-resolution: ' . $logos[$i]['dppx'] . 'dppx)';
250  }
251 
252  $preloadLinks[$logos[$i]['src']] = [
253  'as' => 'image',
254  'media' => $media_query
255  ];
256  }
257 
258  return $preloadLinks;
259  }
260 
269  private function normalizeStyles( array &$styles ) {
270  foreach ( $styles as $key => $val ) {
271  if ( !is_array( $val ) ) {
272  $styles[$key] = [ $val ];
273  }
274  }
275  }
276 
285  protected function getLogoData( Config $conf ) {
286  $logo = $conf->get( 'Logo' );
287  $logoHD = $conf->get( 'LogoHD' );
288 
289  $logo1Url = OutputPage::transformResourcePath( $conf, $logo );
290 
291  if ( !$logoHD ) {
292  return $logo1Url;
293  }
294 
295  $logoUrls = [
296  '1x' => $logo1Url,
297  ];
298 
299  if ( isset( $logoHD['svg'] ) ) {
300  $logoUrls['svg'] = OutputPage::transformResourcePath(
301  $conf,
302  $logoHD['svg']
303  );
304  } else {
305  // Only 1.5x and 2x are supported
306  if ( isset( $logoHD['1.5x'] ) ) {
307  $logoUrls['1.5x'] = OutputPage::transformResourcePath(
308  $conf,
309  $logoHD['1.5x']
310  );
311  }
312  if ( isset( $logoHD['2x'] ) ) {
313  $logoUrls['2x'] = OutputPage::transformResourcePath(
314  $conf,
315  $logoHD['2x']
316  );
317  }
318  }
319 
320  return $logoUrls;
321  }
322 
328  // Regardless of whether the files are specified, we always
329  // provide mw-wiki-logo styles.
330  return false;
331  }
332 
334  $summary = parent::getDefinitionSummary( $context );
335  $summary[] = [
336  'logo' => $this->getConfig()->get( 'Logo' ),
337  'logoHD' => $this->getConfig()->get( 'LogoHD' ),
338  ];
339  return $summary;
340  }
341 }
getDefinitionSummary(ResourceLoaderContext $context)
getStyleFiles(ResourceLoaderContext $context)
Get styles defined in the module definition, plus any enabled feature styles.
$context
Definition: load.php:45
getPreloadLinks(ResourceLoaderContext $context)
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
const FEATURE_FILES
Every skin should define which features it would like to reuse for core inside a ResourceLoader modul...
normalizeStyles(array &$styles)
Ensure all media keys use array values.
Interface for configuration instances.
Definition: Config.php:28
getLogoPreloadlinks()
Helper method for getPreloadLinks()
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
Module based on local JavaScript/CSS files.
static buildUrlValue( $url)
Build a CSS &#39;url()&#39; value for the given URL, quoting parentheses (and other funny characters) and esc...
Definition: CSSMin.php:216
string $localBasePath
Local base path, see __construct()
isKnownEmpty(ResourceLoaderContext $context)
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path...
string $remoteBasePath
Remote base path, see __construct()
getStyles(ResourceLoaderContext $context)
static extractBasePaths(array $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information. ...
$targets
All skins are assumed to be compatible with mobile.
array $styles
List of paths to CSS files to always include.
__construct(array $options=[], $localBasePath=null, $remoteBasePath=null)
Module for skin stylesheets.
Context object that contains information about the state of a specific ResourceLoader web request...