MediaWiki fundraising/REL1_35
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 ) {
481 $localBasePath = $IP;
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}
$fallback
Transforms CSS data.
Definition CSSMin.php:30
static buildUrlValue( $url)
Build a CSS 'url()' value for the given URL, quoting parentheses (and other funny characters) and esc...
Definition CSSMin.php:216
Context object that contains information about the state of a specific ResourceLoader web request.
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Module for generated and embedded images.
getImage( $name, ResourceLoaderContext $context)
Get a ResourceLoaderImage object for given image.
static extractLocalBasePath(array $options, $localBasePath=null)
Extract a local base path from module definition information.
loadFromDefinition()
Parse definition and external JSON data, if referenced.
ResourceLoaderImage[][] null $imageObjects
getStyles(ResourceLoaderContext $context)
string $localBasePath
Local base path, see __construct()
__construct(array $options=[], $localBasePath=null)
Constructs a new module from an options array.
getDefinitionSummary(ResourceLoaderContext $context)
Get the definition summary for this module.
getSelectors()
Get CSS selector templates used by this module.
getCssDeclarations( $primary, $fallback)
SVG support using a transparent gradient to guarantee cross-browser compatibility (browsers able to u...
getFileHashes(ResourceLoaderContext $context)
Helper method for getDefinitionSummary.
getPrefix()
Get CSS class prefix used by this module.
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...
getGlobalVariants(ResourceLoaderContext $context)
Get list of variants in this module that are 'global', i.e., available for every image regardless of ...
getImages(ResourceLoaderContext $context)
Get ResourceLoaderImage objects for all images.
Class encapsulating an image used in a ResourceLoaderImageModule.
getUrl(ResourceLoaderContext $context, $script, $variant, $format)
Get the load.php URL that will produce this image.
getDataUri(ResourceLoaderContext $context, $variant, $format)
Get the data: URI that will produce this image.
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
string null $name
Module name.
$IP
Definition rebuild.php:19