8use InvalidArgumentException;
12use Wikimedia\Minify\CSSMin;
116 private const FEATURE_FILES = [
118 'all' => [
'resources/src/mediawiki.skinning/accessibility.less' ],
121 'all' => [
'resources/src/mediawiki.skinning/normalize.less' ],
125 'all' => [
'resources/src/mediawiki.skinning/logo.less' ],
127 'print' => [
'resources/src/mediawiki.skinning/logo-print.less' ],
130 'content-media' => [],
131 'content-media-dark' => [
132 'screen' => [
'resources/src/mediawiki.skinning/content.media-dark.less' ],
135 'screen' => [
'resources/src/mediawiki.skinning/content.links.less' ]
137 'content-links-external' => [
138 'screen' => [
'resources/src/mediawiki.skinning/content.externallinks.less' ]
141 'screen' => [
'resources/src/mediawiki.skinning/content.body.less' ],
142 'print' => [
'resources/src/mediawiki.skinning/content.body-print.less' ],
144 'content-tables' => [
145 'screen' => [
'resources/src/mediawiki.skinning/content.tables.less' ],
146 'print' => [
'resources/src/mediawiki.skinning/content.tables-print.less' ]
148 'interface-category' => [
149 'screen' => [
'resources/src/mediawiki.skinning/interface.category.less' ],
150 'print' => [
'resources/src/mediawiki.skinning/interface.category-print.less' ],
152 'interface-core' => [
153 'screen' => [
'resources/src/mediawiki.skinning/interface.less' ],
154 'print' => [
'resources/src/mediawiki.skinning/interface-print.less' ],
156 'interface-edit-section-links' => [
157 'screen' => [
'resources/src/mediawiki.skinning/interface-edit-section-links.less' ],
159 'interface-indicators' => [
160 'screen' => [
'resources/src/mediawiki.skinning/interface-indicators.less' ],
162 'interface-site-notice' => [
163 'screen' => [
'resources/src/mediawiki.skinning/interface-site-notice.less' ],
165 'interface-subtitle' => [
166 'screen' => [
'resources/src/mediawiki.skinning/interface-subtitle.less' ],
168 'interface-message-box' => [
169 'all' => [
'resources/src/mediawiki.skinning/messageBoxes.less' ],
171 'interface-user-message' => [
172 'screen' => [
'resources/src/mediawiki.skinning/interface-user-message.less' ],
175 'screen' => [
'resources/src/mediawiki.skinning/elements.less' ],
176 'print' => [
'resources/src/mediawiki.skinning/elements-print.less' ],
178 'i18n-ordered-lists' => [
179 'screen' => [
'resources/src/mediawiki.skinning/i18n-ordered-lists.less' ],
181 'i18n-all-lists-margins' => [
182 'screen' => [
'resources/src/mediawiki.skinning/i18n-all-lists-margins.less' ],
185 'screen' => [
'resources/src/mediawiki.skinning/i18n-headings.less' ],
188 'all' => [
'resources/src/mediawiki.skinning/toc/common.less' ],
189 'screen' => [
'resources/src/mediawiki.skinning/toc/screen.less' ],
190 'print' => [
'resources/src/mediawiki.skinning/toc/print.less' ],
194 private const COMPAT_ALIASES = [
196 'content-parser-output' =>
'content-body',
198 'content' =>
'content-media',
199 'content-thumbnails' =>
'content-media',
215 private const DEFAULT_FEATURES_SPECIFIED = [
216 'accessibility' =>
true,
217 'content-body' =>
true,
218 'interface-core' =>
true,
228 private const DEFAULT_FEATURES_ABSENT = [
232 private const LESS_MESSAGES = [
263 $features = $options[
'features'] ?? self::DEFAULT_FEATURES_ABSENT;
264 $listMode = array_keys( $features ) === range( 0, count( $features ) - 1 );
269 $features = $listMode ?
271 array_fill_keys( $features,
true ),
277 foreach ( $features as $key => $enabled ) {
278 if ( !isset( self::FEATURE_FILES[$key] ) ) {
279 throw new InvalidArgumentException(
"Feature '$key' is not recognised" );
283 $this->features = $listMode
284 ? array_keys( array_filter( $features ) )
285 : array_keys( array_filter( $features + self::DEFAULT_FEATURES_SPECIFIED ) );
290 if ( in_array(
'toc', $this->features ) ) {
291 $options[
'lessMessages'] = array_merge(
292 $options[
'lessMessages'] ?? [],
298 $messages .=
'More information can be found at [[mw:Manual:ResourceLoaderSkinModule]]. ';
312 array $features,
bool $addUnspecifiedFeatures =
true, &
$messages =
''
314 if ( isset( $features[
'i18n-all-lists-margins' ] ) ) {
317 $messages .=
'[1.43] The use of the `i18n-all-lists-margins` feature with SkinModule'
318 .
' is deprecated as it is now provided by `elements`. Please remove and '
319 .
' add `elements`, drop support for RTL languages, or incorporate the '
320 .
' styles provided by this module into your skin.';
322 if ( isset( $features[
'interface-message-box' ] ) && $features[
'interface-message-box' ] ) {
327 $messages .=
'[1.43] The use of the `interface-message-box` feature with SkinModule'
328 .
' is deprecated in favor of CodexModule. Please remove this feature.';
331 foreach ( self::COMPAT_ALIASES as $from => $to ) {
332 if ( isset( $features[ $from ] ) && $to !==
null ) {
333 if ( isset( $features[ $to ] ) ) {
334 $messages .=
"SkinModule feature `$from` conflicts with `$to` and was ignored. ";
336 $features[ $to ] = $features[ $from ];
339 unset( $features[ $from ] );
343 if ( $addUnspecifiedFeatures
344 && isset( $features[
'content-links' ] )
345 && !isset( $features[
'content-links-external' ] )
348 $features[
'content-links-external' ] = $features[
'content-links' ];
353 if ( $addUnspecifiedFeatures && isset( $features[
'elements' ] ) && !isset( $features[
'content-links' ] ) ) {
354 $features[
'content-links' ] = $features[
'elements' ];
358 if ( isset( $features[
'interface' ] ) && $features[
'interface' ] ) {
359 $features[
'interface-core' ] =
true;
360 $features[
'interface-indicators' ] =
true;
361 $features[
'interface-subtitle' ] =
true;
362 $features[
'interface-user-message' ] =
true;
363 $features[
'interface-site-notice' ] =
true;
364 $features[
'interface-edit-section-links' ] =
true;
366 unset( $features[
'interface' ] );
379 [ $defaultLocalBasePath, $defaultRemoteBasePath ] =
386 $featureFilePaths = [];
388 foreach ( self::FEATURE_FILES as $feature => $featureFiles ) {
389 if ( in_array( $feature, $this->features ) ) {
390 foreach ( $featureFiles as $mediaType => $files ) {
391 foreach ( $files as $filepath ) {
392 $featureFilePaths[$mediaType][] =
new FilePath(
394 $defaultLocalBasePath,
395 $defaultRemoteBasePath
400 if ( $feature ===
'content-media' ) {
402 $featureFilePaths[
'all'][] =
new FilePath(
403 'resources/src/mediawiki.skinning/content.thumbnails-common.less',
404 $defaultLocalBasePath,
405 $defaultRemoteBasePath
407 $featureFilePaths[
'screen'][] =
new FilePath(
408 'resources/src/mediawiki.skinning/content.thumbnails-screen.less',
409 $defaultLocalBasePath,
410 $defaultRemoteBasePath
412 $featureFilePaths[
'print'][] =
new FilePath(
413 'resources/src/mediawiki.skinning/content.thumbnails-print.less',
414 $defaultLocalBasePath,
415 $defaultRemoteBasePath
418 $featureFilePaths[
'all'][] =
new FilePath(
419 'resources/src/mediawiki.skinning/content.media-common.less',
420 $defaultLocalBasePath,
421 $defaultRemoteBasePath
423 $featureFilePaths[
'screen'][] =
new FilePath(
424 'resources/src/mediawiki.skinning/content.media-screen.less',
425 $defaultLocalBasePath,
426 $defaultRemoteBasePath
428 $featureFilePaths[
'print'][] =
new FilePath(
429 'resources/src/mediawiki.skinning/content.media-print.less',
430 $defaultLocalBasePath,
431 $defaultRemoteBasePath
436 return $featureFilePaths;
448 private function combineFeatureAndParentStyles( $featureStyles, $parentStyles ) {
451 $combinedStyles = array_merge( $combinedFeatureStyles, $combinedParentStyles );
452 return [
'' => $combinedStyles ];
463 $logo = $this->getLogoData( $this->getConfig(), $context->getLanguage() );
464 $default = !is_array( $logo ) ? $logo : ( $logo[
'svg'] ?? $logo[
'1x'] ?? null );
468 return $featureStyles;
471 $featureStyles[
'all'][] =
'.mw-wiki-logo { background-image: ' .
472 CSSMin::buildUrlValue( $default ) .
475 if ( is_array( $logo ) ) {
476 if ( isset( $logo[
'svg'] ) ) {
477 $featureStyles[
'all'][] =
'.mw-wiki-logo { ' .
478 'background-size: 135px auto; }';
480 if ( isset( $logo[
'1.5x'] ) ) {
482 '(-webkit-min-device-pixel-ratio: 1.5), ' .
483 '(min-resolution: 1.5dppx), ' .
484 '(min-resolution: 144dpi)'
485 ][] =
'.mw-wiki-logo { background-image: ' .
486 CSSMin::buildUrlValue( $logo[
'1.5x'] ) .
';' .
487 'background-size: 135px auto; }';
489 if ( isset( $logo[
'2x'] ) ) {
491 '(-webkit-min-device-pixel-ratio: 2), ' .
492 '(min-resolution: 2dppx), ' .
493 '(min-resolution: 192dpi)'
494 ][] =
'.mw-wiki-logo { background-image: ' .
495 CSSMin::buildUrlValue( $logo[
'2x'] ) .
';' .
496 'background-size: 135px auto; }';
500 return $featureStyles;
508 $parentStyles = parent::getStyles( $context );
509 $featureFilePaths = $this->getFeatureFilePaths();
510 $featureStyles = $this->readStyleFiles( $featureFilePaths, $context );
512 $this->normalizeStyles( $featureStyles );
513 $this->normalizeStyles( $parentStyles );
515 $isLogoFeatureEnabled = in_array(
'logo', $this->features );
516 if ( $isLogoFeatureEnabled ) {
517 $featureStyles = $this->generateAndAppendLogoStyles( $featureStyles, $context );
520 return $this->combineFeatureAndParentStyles( $featureStyles, $parentStyles );
524 if ( !in_array(
'logo', $this->features ) ) {
528 $logo = $this->getLogoData( $this->getConfig(), $context->getLanguage() );
530 if ( !is_array( $logo ) ) {
532 return [ $logo => [
'as' =>
'image' ] ];
535 if ( isset( $logo[
'svg'] ) ) {
538 return [ $logo[
'svg'] => [
'as' =>
'image' ] ];
542 foreach ( $logo as $dppx => $src ) {
544 $dppx = substr( $dppx, 0, -1 );
545 $logosPerDppx[$dppx] = $src;
549 uksort( $logosPerDppx,
static function ( $a, $b ) {
557 foreach ( $logosPerDppx as $dppx => $src ) {
564 $logosCount = count( $logos );
571 for ( $i = 0; $i < $logosCount; $i++ ) {
576 $media_query =
'not all and (min-resolution: ' . $logos[1][
'dppx'] .
'dppx)';
577 } elseif ( $i !== $logosCount - 1 ) {
582 $upper_bound = floatval( $logos[$i + 1][
'dppx'] ) - 0.000001;
583 $media_query =
'(min-resolution: ' . $logos[$i][
'dppx'] .
584 'dppx) and (max-resolution: ' . $upper_bound .
'dppx)';
587 $media_query =
'(min-resolution: ' . $logos[$i][
'dppx'] .
'dppx)';
590 $preloadLinks[$logos[$i][
'src']] = [
592 'media' => $media_query
596 return $preloadLinks;
607 private function normalizeStyles( array &$styles ): void {
608 foreach ( $styles as $key => $val ) {
609 if ( !is_array( $val ) ) {
610 $styles[$key] = [ $val ];
621 private static function getRelativeSizedLogo( array $logoElement ) {
622 $width = $logoElement[
'width'];
623 $height = $logoElement[
'height'];
624 $widthRelative = $width / 16;
625 $heightRelative = $height / 16;
627 $logoElement[
'style'] =
'width: ' . $widthRelative .
'em; height: ' . $heightRelative .
'em;';
648 if ( $logos ===
false ) {
654 if ( $lang && isset( $logos[
'variants'][$lang] ) ) {
655 foreach ( $logos[
'variants'][$lang] as $type => $value ) {
656 $logos[$type] = $value;
661 if ( !isset( $logos[
'1x' ] ) ) {
662 $logo = $conf->
get( MainConfigNames::Logo );
664 $logos[
'1x'] = $logo;
668 if ( isset( $logos[
'wordmark'] ) ) {
670 $logos[
'wordmark'] = self::getRelativeSizedLogo( $logos[
'wordmark'] );
672 if ( isset( $logos[
'tagline'] ) ) {
673 $logos[
'tagline'] = self::getRelativeSizedLogo( $logos[
'tagline'] );
689 $logoHD = self::getAvailableLogos( $conf, $lang );
690 $logo = $logoHD[
'1x'];
692 $logo1Url = OutputPage::transformResourcePath( $conf, $logo );
698 if ( isset( $logoHD[
'svg'] ) ) {
699 $logoUrls[
'svg'] = OutputPage::transformResourcePath(
703 } elseif ( isset( $logoHD[
'1.5x'] ) || isset( $logoHD[
'2x'] ) ) {
705 if ( isset( $logoHD[
'1.5x'] ) ) {
706 $logoUrls[
'1.5x'] = OutputPage::transformResourcePath(
711 if ( isset( $logoHD[
'2x'] ) ) {
712 $logoUrls[
'2x'] = OutputPage::transformResourcePath(
742 $lessVars = parent::getLessVars( $context );
743 $logos = self::getAvailableLogos( $this->getConfig(), $context->
getLanguage() );
745 if ( isset( $logos[
'wordmark'] ) ) {
746 $logo = $logos[
'wordmark'];
747 $lessVars[
'logo-enabled' ] =
true;
748 $lessVars[
'logo-wordmark-url' ] = CSSMin::buildUrlValue( $logo[
'src'] );
749 $lessVars[
'logo-wordmark-width' ] = intval( $logo[
'width'] );
750 $lessVars[
'logo-wordmark-height' ] = intval( $logo[
'height'] );
752 $lessVars[
'logo-enabled' ] =
false;
759 $summary = parent::getDefinitionSummary( $context );
761 'logos' => self::getAvailableLogos( $this->getConfig(), $context->
getLanguage() ),
if(!defined('MW_SETUP_CALLBACK'))
A class containing constants representing the names of configuration variables.
const ResourceBasePath
Name constant for the ResourceBasePath setting, for use with Config::get()
const UseLegacyMediaStyles
Name constant for the UseLegacyMediaStyles setting, for use with Config::get()
This is one of the Core classes and should be read at least once by any new developers.
Context object that contains information about the state of a specific ResourceLoader web request.