MediaWiki  master
ResourceLoaderImageModule.php
Go to the documentation of this file.
1 <?php
30  protected $definition;
31 
36  protected $localBasePath = '';
37 
38  protected $origin = self::ORIGIN_CORE_SITEWIDE;
39 
41  protected $imageObjects = null;
43  protected $images = [];
45  protected $defaultColor = null;
46  protected $useDataURI = true;
48  protected $globalVariants = null;
50  protected $variants = [];
52  protected $prefix = null;
53  protected $selectorWithoutVariant = '.{prefix}-{name}';
54  protected $selectorWithVariant = '.{prefix}-{name}-{variant}';
55  protected $targets = [ 'desktop', 'mobile' ];
56 
113  public function __construct( array $options = [], $localBasePath = null ) {
114  $this->localBasePath = static::extractLocalBasePath( $options, $localBasePath );
115 
116  $this->definition = $options;
117  }
118 
122  protected function loadFromDefinition() {
123  if ( $this->definition === null ) {
124  return;
125  }
126 
127  $options = $this->definition;
128  $this->definition = null;
129 
130  if ( isset( $options['data'] ) ) {
131  $dataPath = $this->getLocalPath( $options['data'] );
132  $data = json_decode( file_get_contents( $dataPath ), true );
133  $options = array_merge( $data, $options );
134  }
135 
136  // Accepted combinations:
137  // * prefix
138  // * selector
139  // * selectorWithoutVariant + selectorWithVariant
140  // * prefix + selector
141  // * prefix + selectorWithoutVariant + selectorWithVariant
142 
143  $prefix = isset( $options['prefix'] ) && $options['prefix'];
144  $selector = isset( $options['selector'] ) && $options['selector'];
145  $selectorWithoutVariant = isset( $options['selectorWithoutVariant'] )
146  && $options['selectorWithoutVariant'];
147  $selectorWithVariant = isset( $options['selectorWithVariant'] )
148  && $options['selectorWithVariant'];
149 
151  throw new InvalidArgumentException(
152  "Given 'selectorWithoutVariant' but no 'selectorWithVariant'."
153  );
154  }
156  throw new InvalidArgumentException(
157  "Given 'selectorWithVariant' but no 'selectorWithoutVariant'."
158  );
159  }
160  if ( $selector && $selectorWithVariant ) {
161  throw new InvalidArgumentException(
162  "Incompatible 'selector' and 'selectorWithVariant'+'selectorWithoutVariant' given."
163  );
164  }
165  if ( !$prefix && !$selector && !$selectorWithVariant ) {
166  throw new InvalidArgumentException(
167  "None of 'prefix', 'selector' or 'selectorWithVariant'+'selectorWithoutVariant' given."
168  );
169  }
170 
171  foreach ( $options as $member => $option ) {
172  switch ( $member ) {
173  case 'images':
174  case 'variants':
175  if ( !is_array( $option ) ) {
176  throw new InvalidArgumentException(
177  "Invalid list error. '$option' given, array expected."
178  );
179  }
180  if ( !isset( $option['default'] ) ) {
181  // Backwards compatibility
182  $option = [ 'default' => $option ];
183  }
184  foreach ( $option as $skin => $data ) {
185  if ( !is_array( $data ) ) {
186  throw new InvalidArgumentException(
187  "Invalid list error. '$data' given, array expected."
188  );
189  }
190  }
191  $this->{$member} = $option;
192  break;
193 
194  case 'useDataURI':
195  $this->{$member} = (bool)$option;
196  break;
197  case 'defaultColor':
198  case 'prefix':
199  case 'selectorWithoutVariant':
200  case 'selectorWithVariant':
201  $this->{$member} = (string)$option;
202  break;
203 
204  case 'selector':
205  $this->selectorWithoutVariant = $this->selectorWithVariant = (string)$option;
206  }
207  }
208  }
209 
214  public function getPrefix() {
215  $this->loadFromDefinition();
216  return $this->prefix;
217  }
218 
223  public function getSelectors() {
224  $this->loadFromDefinition();
225  return [
226  'selectorWithoutVariant' => $this->selectorWithoutVariant,
227  'selectorWithVariant' => $this->selectorWithVariant,
228  ];
229  }
230 
237  public function getImage( $name, ResourceLoaderContext $context ) : ?ResourceLoaderImage {
238  $this->loadFromDefinition();
239  $images = $this->getImages( $context );
240  return $images[$name] ?? null;
241  }
242 
248  public function getImages( ResourceLoaderContext $context ) : array {
249  $skin = $context->getSkin();
250  if ( $this->imageObjects === null ) {
251  $this->loadFromDefinition();
252  $this->imageObjects = [];
253  }
254  if ( !isset( $this->imageObjects[$skin] ) ) {
255  $this->imageObjects[$skin] = [];
256  if ( !isset( $this->images[$skin] ) ) {
257  $this->images[$skin] = $this->images['default'] ?? [];
258  }
259  foreach ( $this->images[$skin] as $name => $options ) {
260  $fileDescriptor = is_array( $options ) ? $options['file'] : $options;
261 
262  $allowedVariants = array_merge(
263  ( is_array( $options ) && isset( $options['variants'] ) ) ? $options['variants'] : [],
264  $this->getGlobalVariants( $context )
265  );
266  if ( isset( $this->variants[$skin] ) ) {
267  $variantConfig = array_intersect_key(
268  $this->variants[$skin],
269  array_fill_keys( $allowedVariants, true )
270  );
271  } else {
272  $variantConfig = [];
273  }
274 
275  $image = new ResourceLoaderImage(
276  $name,
277  $this->getName(),
278  $fileDescriptor,
279  $this->localBasePath,
280  $variantConfig,
281  $this->defaultColor
282  );
283  $this->imageObjects[$skin][$image->getName()] = $image;
284  }
285  }
286 
287  return $this->imageObjects[$skin];
288  }
289 
296  public function getGlobalVariants( ResourceLoaderContext $context ) : array {
297  $skin = $context->getSkin();
298  if ( $this->globalVariants === null ) {
299  $this->loadFromDefinition();
300  $this->globalVariants = [];
301  }
302  if ( !isset( $this->globalVariants[$skin] ) ) {
303  $this->globalVariants[$skin] = [];
304  if ( !isset( $this->variants[$skin] ) ) {
305  $this->variants[$skin] = $this->variants['default'] ?? [];
306  }
307  foreach ( $this->variants[$skin] as $name => $config ) {
308  if ( $config['global'] ?? false ) {
309  $this->globalVariants[$skin][] = $name;
310  }
311  }
312  }
313 
314  return $this->globalVariants[$skin];
315  }
316 
321  public function getStyles( ResourceLoaderContext $context ) : array {
322  $this->loadFromDefinition();
323 
324  // Build CSS rules
325  $rules = [];
326  $script = $context->getResourceLoader()->getLoadScript( $this->getSource() );
327  $selectors = $this->getSelectors();
328 
329  foreach ( $this->getImages( $context ) as $name => $image ) {
330  $declarations = $this->getStyleDeclarations( $context, $image, $script );
331  $selector = strtr(
332  $selectors['selectorWithoutVariant'],
333  [
334  '{prefix}' => $this->getPrefix(),
335  '{name}' => $name,
336  '{variant}' => '',
337  ]
338  );
339  $rules[] = "$selector {\n\t$declarations\n}";
340 
341  foreach ( $image->getVariants() as $variant ) {
342  $declarations = $this->getStyleDeclarations( $context, $image, $script, $variant );
343  $selector = strtr(
344  $selectors['selectorWithVariant'],
345  [
346  '{prefix}' => $this->getPrefix(),
347  '{name}' => $name,
348  '{variant}' => $variant,
349  ]
350  );
351  $rules[] = "$selector {\n\t$declarations\n}";
352  }
353  }
354 
355  $style = implode( "\n", $rules );
356  return [ 'all' => $style ];
357  }
358 
370  private function getStyleDeclarations(
371  ResourceLoaderContext $context,
372  ResourceLoaderImage $image,
373  $script,
374  $variant = null
375  ) {
376  $imageDataUri = $this->useDataURI ? $image->getDataUri( $context, $variant, 'original' ) : false;
377  $primaryUrl = $imageDataUri ?: $image->getUrl( $context, $script, $variant, 'original' );
378  $declarations = $this->getCssDeclarations(
379  $primaryUrl,
380  $image->getUrl( $context, $script, $variant, 'rasterized' )
381  );
382  return implode( "\n\t", $declarations );
383  }
384 
397  protected function getCssDeclarations( $primary, $fallback ) : array {
398  $primaryUrl = CSSMin::buildUrlValue( $primary );
399  $fallbackUrl = CSSMin::buildUrlValue( $fallback );
400  return [
401  "background-image: $fallbackUrl;",
402  "background-image: linear-gradient(transparent, transparent), $primaryUrl;",
403  ];
404  }
405 
409  public function supportsURLLoading() {
410  return false;
411  }
412 
419  public function getDefinitionSummary( ResourceLoaderContext $context ) {
420  $this->loadFromDefinition();
421  $summary = parent::getDefinitionSummary( $context );
422 
423  $options = [];
424  foreach ( [
425  'localBasePath',
426  'images',
427  'variants',
428  'prefix',
429  'selectorWithoutVariant',
430  'selectorWithVariant',
431  ] as $member ) {
432  $options[$member] = $this->{$member};
433  }
434 
435  $summary[] = [
436  'options' => $options,
437  'fileHashes' => $this->getFileHashes( $context ),
438  ];
439  return $summary;
440  }
441 
447  private function getFileHashes( ResourceLoaderContext $context ) {
448  $this->loadFromDefinition();
449  $files = [];
450  foreach ( $this->getImages( $context ) as $name => $image ) {
451  $files[] = $image->getPath( $context );
452  }
453  $files = array_values( array_unique( $files ) );
454  return array_map( [ __CLASS__, 'safeFileHash' ], $files );
455  }
456 
461  protected function getLocalPath( $path ) {
462  if ( $path instanceof ResourceLoaderFilePath ) {
463  return $path->getLocalPath();
464  }
465 
466  return "{$this->localBasePath}/$path";
467  }
468 
477  public static function extractLocalBasePath( array $options, $localBasePath = null ) {
478  global $IP;
479 
480  if ( $localBasePath === null ) {
482  }
483 
484  if ( array_key_exists( 'localBasePath', $options ) ) {
485  $localBasePath = (string)$options['localBasePath'];
486  }
487 
488  return $localBasePath;
489  }
490 
494  public function getType() {
495  return self::LOAD_STYLES;
496  }
497 }
ResourceLoaderImageModule\$globalVariants
array null $globalVariants
Definition: ResourceLoaderImageModule.php:48
ResourceLoaderContext
Context object that contains information about the state of a specific ResourceLoader web request.
Definition: ResourceLoaderContext.php:33
ResourceLoaderImageModule\$variants
array $variants
Definition: ResourceLoaderImageModule.php:50
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
ResourceLoaderImageModule\$images
array $images
Definition: ResourceLoaderImageModule.php:43
ResourceLoaderImageModule\getType
getType()
Definition: ResourceLoaderImageModule.php:494
ResourceLoaderImageModule\$definition
array $definition
Definition: ResourceLoaderImageModule.php:30
ResourceLoaderImageModule\$useDataURI
$useDataURI
Definition: ResourceLoaderImageModule.php:46
ResourceLoaderContext\getResourceLoader
getResourceLoader()
Definition: ResourceLoaderContext.php:123
$fallback
$fallback
Definition: MessagesAb.php:11
ResourceLoaderFilePath
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Definition: ResourceLoaderFilePath.php:28
ResourceLoaderImageModule\$targets
$targets
Definition: ResourceLoaderImageModule.php:55
ResourceLoaderImageModule\getGlobalVariants
getGlobalVariants(ResourceLoaderContext $context)
Get list of variants in this module that are 'global', i.e., available for every image regardless of ...
Definition: ResourceLoaderImageModule.php:296
ResourceLoaderImageModule\getStyles
getStyles(ResourceLoaderContext $context)
Definition: ResourceLoaderImageModule.php:321
ResourceLoaderImageModule\$prefix
string null $prefix
Definition: ResourceLoaderImageModule.php:52
ResourceLoaderImageModule\$localBasePath
string $localBasePath
Local base path, see __construct()
Definition: ResourceLoaderImageModule.php:36
ResourceLoaderImageModule\getPrefix
getPrefix()
Get CSS class prefix used by this module.
Definition: ResourceLoaderImageModule.php:214
ResourceLoaderImageModule\supportsURLLoading
supportsURLLoading()
Definition: ResourceLoaderImageModule.php:409
ResourceLoaderImageModule\$selectorWithVariant
$selectorWithVariant
Definition: ResourceLoaderImageModule.php:54
ResourceLoaderImageModule\$origin
$origin
Definition: ResourceLoaderImageModule.php:38
ResourceLoaderImageModule\$selectorWithoutVariant
$selectorWithoutVariant
Definition: ResourceLoaderImageModule.php:53
ResourceLoaderImageModule\getLocalPath
getLocalPath( $path)
Definition: ResourceLoaderImageModule.php:461
ResourceLoaderImage
Class encapsulating an image used in a ResourceLoaderImageModule.
Definition: ResourceLoaderImage.php:31
ResourceLoaderModule\$name
string null $name
Module name.
Definition: ResourceLoaderModule.php:55
ResourceLoaderImageModule\__construct
__construct(array $options=[], $localBasePath=null)
Constructs a new module from an options array.
Definition: ResourceLoaderImageModule.php:113
ResourceLoaderImageModule\getCssDeclarations
getCssDeclarations( $primary, $fallback)
SVG support using a transparent gradient to guarantee cross-browser compatibility (browsers able to u...
Definition: ResourceLoaderImageModule.php:397
ResourceLoaderImageModule\$imageObjects
ResourceLoaderImage[][] null $imageObjects
Definition: ResourceLoaderImageModule.php:41
ResourceLoaderImageModule\extractLocalBasePath
static extractLocalBasePath(array $options, $localBasePath=null)
Extract a local base path from module definition information.
Definition: ResourceLoaderImageModule.php:477
ResourceLoaderContext\getSkin
getSkin()
Definition: ResourceLoaderContext.php:188
ResourceLoaderImageModule\getFileHashes
getFileHashes(ResourceLoaderContext $context)
Helper method for getDefinitionSummary.
Definition: ResourceLoaderImageModule.php:447
ResourceLoaderModule
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition: ResourceLoaderModule.php:39
ResourceLoaderImageModule\getStyleDeclarations
getStyleDeclarations(ResourceLoaderContext $context, ResourceLoaderImage $image, $script, $variant=null)
This method must not be used by getDefinitionSummary as doing so would cause an infinite loop (we use...
Definition: ResourceLoaderImageModule.php:370
ResourceLoaderModule\$config
Config $config
Definition: ResourceLoaderModule.php:41
ResourceLoaderImageModule\getImages
getImages(ResourceLoaderContext $context)
Get ResourceLoaderImage objects for all images.
Definition: ResourceLoaderImageModule.php:248
$path
$path
Definition: NoLocalSettings.php:25
ResourceLoaderImageModule\loadFromDefinition
loadFromDefinition()
Parse definition and external JSON data, if referenced.
Definition: ResourceLoaderImageModule.php:122
ResourceLoaderImageModule\getDefinitionSummary
getDefinitionSummary(ResourceLoaderContext $context)
Get the definition summary for this module.
Definition: ResourceLoaderImageModule.php:419
ResourceLoaderImage\getDataUri
getDataUri(ResourceLoaderContext $context, $variant, $format)
Get the data: URI that will produce this image.
Definition: ResourceLoaderImage.php:261
ResourceLoaderModule\getSource
getSource()
Get the source of this module.
Definition: ResourceLoaderModule.php:384
ResourceLoaderImageModule\$defaultColor
string null $defaultColor
Definition: ResourceLoaderImageModule.php:45
ResourceLoaderImageModule\getImage
getImage( $name, ResourceLoaderContext $context)
Get a ResourceLoaderImage object for given image.
Definition: ResourceLoaderImageModule.php:237
ResourceLoaderImageModule
Module for generated and embedded images.
Definition: ResourceLoaderImageModule.php:28
$IP
$IP
Definition: WebStart.php:49
ResourceLoaderImage\getUrl
getUrl(ResourceLoaderContext $context, $script, $variant, $format)
Get the load.php URL that will produce this image.
Definition: ResourceLoaderImage.php:235
ResourceLoaderModule\getName
getName()
Get this module's name.
Definition: ResourceLoaderModule.php:112
ResourceLoaderImageModule\getSelectors
getSelectors()
Get CSS selector templates used by this module.
Definition: ResourceLoaderImageModule.php:223