24use InvalidArgumentException;
25use Wikimedia\Minify\CSSMin;
35 private $useMaskImage;
46 protected $origin = self::ORIGIN_CORE_SITEWIDE;
126 $this->useMaskImage = $options[
'useMaskImage'] ??
false;
127 $this->localBasePath = static::extractLocalBasePath( $options,
$localBasePath );
129 $this->definition = $options;
136 if ( $this->definition ===
null ) {
141 $this->definition =
null;
143 if ( isset( $options[
'data'] ) ) {
145 $data = json_decode( file_get_contents( $dataPath ),
true );
146 $options = array_merge( $data, $options );
156 $prefix = isset( $options[
'prefix'] ) && $options[
'prefix'];
157 $selector = isset( $options[
'selector'] ) && $options[
'selector'];
159 && $options[
'selectorWithoutVariant'];
161 && $options[
'selectorWithVariant'];
164 throw new InvalidArgumentException(
165 "Given 'selectorWithoutVariant' but no 'selectorWithVariant'."
169 throw new InvalidArgumentException(
170 "Given 'selectorWithVariant' but no 'selectorWithoutVariant'."
174 throw new InvalidArgumentException(
175 "Incompatible 'selector' and 'selectorWithVariant'+'selectorWithoutVariant' given."
179 throw new InvalidArgumentException(
180 "None of 'prefix', 'selector' or 'selectorWithVariant'+'selectorWithoutVariant' given."
184 foreach ( $options as $member => $option ) {
188 if ( !is_array( $option ) ) {
189 throw new InvalidArgumentException(
190 "Invalid list error. '$option' given, array expected."
193 if ( !isset( $option[
'default'] ) ) {
195 $option = [
'default' => $option ];
197 foreach ( $option as $data ) {
198 if ( !is_array( $data ) ) {
199 throw new InvalidArgumentException(
200 "Invalid list error. '$data' given, array expected."
204 $this->{$member} = $option;
208 $this->{$member} = (bool)$option;
212 case 'selectorWithoutVariant':
213 case 'selectorWithVariant':
214 $this->{$member} = (string)$option;
218 $this->selectorWithoutVariant = $this->selectorWithVariant = (string)$option;
262 $skin = $context->getSkin();
263 if ( $this->imageObjects ===
null ) {
264 $this->loadFromDefinition();
265 $this->imageObjects = [];
267 if ( !isset( $this->imageObjects[$skin] ) ) {
268 $this->imageObjects[$skin] = [];
269 if ( !isset( $this->images[$skin] ) ) {
270 $this->images[$skin] = $this->images[
'default'] ?? [];
272 foreach ( $this->images[$skin] as $name => $options ) {
273 $fileDescriptor = is_array( $options ) ? $options[
'file'] : $options;
275 $allowedVariants = array_merge(
276 ( is_array( $options ) && isset( $options[
'variants'] ) ) ? $options[
'variants'] : [],
277 $this->getGlobalVariants( $context )
279 if ( isset( $this->variants[$skin] ) ) {
280 $variantConfig = array_intersect_key(
281 $this->variants[$skin],
282 array_fill_keys( $allowedVariants,
true )
292 $this->localBasePath,
296 $this->imageObjects[$skin][$image->getName()] = $image;
300 return $this->imageObjects[$skin];
310 $skin = $context->getSkin();
311 if ( $this->globalVariants ===
null ) {
312 $this->loadFromDefinition();
313 $this->globalVariants = [];
315 if ( !isset( $this->globalVariants[$skin] ) ) {
316 $this->globalVariants[$skin] = [];
317 if ( !isset( $this->variants[$skin] ) ) {
318 $this->variants[$skin] = $this->variants[
'default'] ?? [];
320 foreach ( $this->variants[$skin] as $name => $config ) {
321 if ( $config[
'global'] ??
false ) {
322 $this->globalVariants[$skin][] = $name;
327 return $this->globalVariants[$skin];
335 $this->loadFromDefinition();
341 $this->getHookRunner()->onResourceLoaderModifyEmbeddedSourceUrls( $sources );
342 if ( array_keys( $sources ) !== array_keys( $oldSources ) ) {
343 throw new DomainException(
'ResourceLoaderModifyEmbeddedSourceUrls hook must not add or remove sources' );
345 $script = $sources[ $this->getSource() ];
347 $selectors = $this->getSelectors();
349 foreach ( $this->getImages( $context ) as $name => $image ) {
350 $declarations = $this->getStyleDeclarations( $context, $image, $script );
352 $selectors[
'selectorWithoutVariant'],
354 '{prefix}' => $this->getPrefix(),
359 $rules[] =
"$selector {\n\t$declarations\n}";
361 foreach ( $image->getVariants() as $variant ) {
362 $declarations = $this->getStyleDeclarations( $context, $image, $script, $variant );
364 $selectors[
'selectorWithVariant'],
366 '{prefix}' => $this->getPrefix(),
368 '{variant}' => $variant,
371 $rules[] =
"$selector {\n\t$declarations\n}";
375 $style = implode(
"\n", $rules );
377 return [
'all' => $style ];
391 private function getStyleDeclarations(
397 $imageDataUri = $this->useDataURI ? $image->getDataUri( $context, $variant,
'original' ) :
false;
398 $primaryUrl = $imageDataUri ?: $image->getUrl( $context, $script, $variant,
'original' );
399 $declarations = $this->getCssDeclarations(
402 return implode(
"\n\t", $declarations );
412 $primaryUrl = CSSMin::buildUrlValue( $primary );
413 if ( $this->supportsMaskImage() ) {
415 "-webkit-mask-image: $primaryUrl;",
416 "mask-image: $primaryUrl;",
420 "background-image: $primaryUrl;",
428 return $this->useMaskImage;
445 $this->loadFromDefinition();
446 $summary = parent::getDefinitionSummary( $context );
454 'selectorWithoutVariant',
455 'selectorWithVariant',
457 $options[$member] = $this->{$member};
461 'options' => $options,
462 'fileHashes' => $this->getFileHashes( $context ),
472 private function getFileHashes(
Context $context ) {
473 $this->loadFromDefinition();
475 foreach ( $this->getImages( $context ) as $image ) {
476 $files[] = $image->getPath( $context );
478 $files = array_values( array_unique( $files ) );
479 return array_map( [ __CLASS__,
'safeFileHash' ], $files );
488 return $path->getLocalPath();
491 return "{$this->localBasePath}/$path";
505 if ( array_key_exists(
'localBasePath', $options ) ) {
506 $localBasePath = (string)$options[
'localBasePath'];
509 return $localBasePath ??
$IP;
516 return self::LOAD_STYLES;
if(!defined( 'MEDIAWIKI')) if(ini_get('mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Context object that contains information about the state of a specific ResourceLoader web request.