22use InvalidArgumentException;
26use Wikimedia\Minify\CSSMin;
131 private const FEATURE_FILES = [
133 'all' => [
'resources/src/mediawiki.skinning/accessibility.less' ],
136 'all' => [
'resources/src/mediawiki.skinning/normalize.less' ],
140 'all' => [
'resources/src/mediawiki.skinning/logo.less' ],
142 'print' => [
'resources/src/mediawiki.skinning/logo-print.less' ],
145 'content-media' => [],
147 'screen' => [
'resources/src/mediawiki.skinning/content.links.less' ]
149 'content-links-external' => [
150 'screen' => [
'resources/src/mediawiki.skinning/content.externallinks.less' ]
153 'screen' => [
'resources/src/mediawiki.skinning/content.body.less' ],
154 'print' => [
'resources/src/mediawiki.skinning/content.body-print.less' ],
156 'content-tables' => [
157 'screen' => [
'resources/src/mediawiki.skinning/content.tables.less' ],
158 'print' => [
'resources/src/mediawiki.skinning/content.tables-print.less' ]
160 'interface-category' => [
161 'screen' => [
'resources/src/mediawiki.skinning/interface.category.less' ],
162 'print' => [
'resources/src/mediawiki.skinning/interface.category-print.less' ],
164 'interface-core' => [
165 'screen' => [
'resources/src/mediawiki.skinning/interface.less' ],
166 'print' => [
'resources/src/mediawiki.skinning/interface-print.less' ],
168 'interface-edit-section-links' => [
169 'screen' => [
'resources/src/mediawiki.skinning/interface-edit-section-links.less' ],
171 'interface-indicators' => [
172 'screen' => [
'resources/src/mediawiki.skinning/interface-indicators.less' ],
174 'interface-site-notice' => [
175 'screen' => [
'resources/src/mediawiki.skinning/interface-site-notice.less' ],
177 'interface-subtitle' => [
178 'screen' => [
'resources/src/mediawiki.skinning/interface-subtitle.less' ],
180 'interface-message-box' => [
181 'all' => [
'resources/src/mediawiki.skinning/messageBoxes.less' ],
183 'interface-user-message' => [
184 'screen' => [
'resources/src/mediawiki.skinning/interface-user-message.less' ],
187 'screen' => [
'resources/src/mediawiki.skinning/elements.less' ],
188 'print' => [
'resources/src/mediawiki.skinning/elements-print.less' ],
190 'i18n-ordered-lists' => [
191 'screen' => [
'resources/src/mediawiki.skinning/i18n-ordered-lists.less' ],
193 'i18n-all-lists-margins' => [
194 'screen' => [
'resources/src/mediawiki.skinning/i18n-all-lists-margins.less' ],
197 'screen' => [
'resources/src/mediawiki.skinning/i18n-headings.less' ],
200 'all' => [
'resources/src/mediawiki.skinning/toc/common.less' ],
201 'screen' => [
'resources/src/mediawiki.skinning/toc/screen.less' ],
202 'print' => [
'resources/src/mediawiki.skinning/toc/print.less' ],
206 private const COMPAT_ALIASES = [
208 'content-parser-output' =>
'content-body',
210 'content' =>
'content-media',
211 'content-thumbnails' =>
'content-media',
227 private const DEFAULT_FEATURES_SPECIFIED = [
228 'accessibility' =>
true,
229 'content-body' =>
true,
230 'interface-core' =>
true,
240 private const DEFAULT_FEATURES_ABSENT = [
244 private const LESS_MESSAGES = [
275 $features = $options[
'features'] ?? self::DEFAULT_FEATURES_ABSENT;
276 $listMode = array_keys( $features ) === range( 0, count( $features ) - 1 );
281 $features = $listMode ?
283 array_fill_keys( $features,
true ),
289 foreach ( $features as $key => $enabled ) {
290 if ( !isset( self::FEATURE_FILES[$key] ) ) {
291 throw new InvalidArgumentException(
"Feature '$key' is not recognised" );
295 $this->features = $listMode
296 ? array_keys( array_filter( $features ) )
297 : array_keys( array_filter( $features + self::DEFAULT_FEATURES_SPECIFIED ) );
302 if ( in_array(
'toc', $this->features ) ) {
303 $options[
'lessMessages'] = array_merge(
304 $options[
'lessMessages'] ?? [],
310 $messages .=
'More information can be found at [[mw:Manual:ResourceLoaderSkinModule]]. ';
324 array $features,
bool $addUnspecifiedFeatures =
true, &
$messages =
''
326 if ( isset( $features[
'i18n-all-lists-margins' ] ) ) {
329 $messages .=
'[1.43] The use of the `i18n-all-lists-margins` feature with SkinModule'
330 .
' is deprecated as it is now provided by `elements`. Please remove and '
331 .
' add `elements`, drop support for RTL languages, or incorporate the '
332 .
' styles provided by this module into your skin.';
334 if ( isset( $features[
'interface-message-box' ] ) && $features[
'interface-message-box' ] ) {
339 $messages .=
'[1.43] The use of the `interface-message-box` feature with SkinModule'
340 .
' is deprecated in favor of CodexModule. Please remove this feature.';
343 foreach ( self::COMPAT_ALIASES as $from => $to ) {
344 if ( isset( $features[ $from ] ) && $to !==
null ) {
345 if ( isset( $features[ $to ] ) ) {
346 $messages .=
"SkinModule feature `$from` conflicts with `$to` and was ignored. ";
348 $features[ $to ] = $features[ $from ];
351 unset( $features[ $from ] );
355 if ( $addUnspecifiedFeatures
356 && isset( $features[
'content-links' ] )
357 && !isset( $features[
'content-links-external' ] )
360 $features[
'content-links-external' ] = $features[
'content-links' ];
365 if ( $addUnspecifiedFeatures && isset( $features[
'elements' ] ) && !isset( $features[
'content-links' ] ) ) {
366 $features[
'content-links' ] = $features[
'elements' ];
370 if ( isset( $features[
'interface' ] ) && $features[
'interface' ] ) {
371 $features[
'interface-core' ] =
true;
372 $features[
'interface-indicators' ] =
true;
373 $features[
'interface-subtitle' ] =
true;
374 $features[
'interface-user-message' ] =
true;
375 $features[
'interface-site-notice' ] =
true;
376 $features[
'interface-edit-section-links' ] =
true;
378 unset( $features[
'interface' ] );
391 [ $defaultLocalBasePath, $defaultRemoteBasePath ] =
398 $featureFilePaths = [];
400 foreach ( self::FEATURE_FILES as $feature => $featureFiles ) {
401 if ( in_array( $feature, $this->features ) ) {
402 foreach ( $featureFiles as $mediaType => $files ) {
403 foreach ( $files as $filepath ) {
404 $featureFilePaths[$mediaType][] =
new FilePath(
406 $defaultLocalBasePath,
407 $defaultRemoteBasePath
412 if ( $feature ===
'content-media' ) {
414 $featureFilePaths[
'all'][] =
new FilePath(
415 'resources/src/mediawiki.skinning/content.thumbnails-common.less',
416 $defaultLocalBasePath,
417 $defaultRemoteBasePath
419 $featureFilePaths[
'screen'][] =
new FilePath(
420 'resources/src/mediawiki.skinning/content.thumbnails-screen.less',
421 $defaultLocalBasePath,
422 $defaultRemoteBasePath
424 $featureFilePaths[
'print'][] =
new FilePath(
425 'resources/src/mediawiki.skinning/content.thumbnails-print.less',
426 $defaultLocalBasePath,
427 $defaultRemoteBasePath
434 $featureFilePaths[
'all'][] =
new FilePath(
435 'resources/src/mediawiki.skinning/content.media-common.less',
436 $defaultLocalBasePath,
437 $defaultRemoteBasePath
439 $featureFilePaths[
'screen'][] =
new FilePath(
440 'resources/src/mediawiki.skinning/content.media-screen.less',
441 $defaultLocalBasePath,
442 $defaultRemoteBasePath
444 $featureFilePaths[
'print'][] =
new FilePath(
445 'resources/src/mediawiki.skinning/content.media-print.less',
446 $defaultLocalBasePath,
447 $defaultRemoteBasePath
453 return $featureFilePaths;
465 private function combineFeatureAndParentStyles( $featureStyles, $parentStyles ) {
468 $combinedStyles = array_merge( $combinedFeatureStyles, $combinedParentStyles );
469 return [
'' => $combinedStyles ];
480 $logo = $this->getLogoData( $this->getConfig(), $context->getLanguage() );
481 $default = !is_array( $logo ) ? $logo : ( $logo[
'svg'] ?? $logo[
'1x'] ?? null );
485 return $featureStyles;
488 $featureStyles[
'all'][] =
'.mw-wiki-logo { background-image: ' .
489 CSSMin::buildUrlValue( $default ) .
492 if ( is_array( $logo ) ) {
493 if ( isset( $logo[
'svg'] ) ) {
494 $featureStyles[
'all'][] =
'.mw-wiki-logo { ' .
495 'background-size: 135px auto; }';
497 if ( isset( $logo[
'1.5x'] ) ) {
499 '(-webkit-min-device-pixel-ratio: 1.5), ' .
500 '(min-resolution: 1.5dppx), ' .
501 '(min-resolution: 144dpi)'
502 ][] =
'.mw-wiki-logo { background-image: ' .
503 CSSMin::buildUrlValue( $logo[
'1.5x'] ) .
';' .
504 'background-size: 135px auto; }';
506 if ( isset( $logo[
'2x'] ) ) {
508 '(-webkit-min-device-pixel-ratio: 2), ' .
509 '(min-resolution: 2dppx), ' .
510 '(min-resolution: 192dpi)'
511 ][] =
'.mw-wiki-logo { background-image: ' .
512 CSSMin::buildUrlValue( $logo[
'2x'] ) .
';' .
513 'background-size: 135px auto; }';
517 return $featureStyles;
525 $parentStyles = parent::getStyles( $context );
526 $featureFilePaths = $this->getFeatureFilePaths();
527 $featureStyles = $this->readStyleFiles( $featureFilePaths, $context );
529 $this->normalizeStyles( $featureStyles );
530 $this->normalizeStyles( $parentStyles );
532 $isLogoFeatureEnabled = in_array(
'logo', $this->features );
533 if ( $isLogoFeatureEnabled ) {
534 $featureStyles = $this->generateAndAppendLogoStyles( $featureStyles, $context );
537 return $this->combineFeatureAndParentStyles( $featureStyles, $parentStyles );
545 if ( !in_array(
'logo', $this->features ) ) {
549 $logo = $this->getLogoData( $this->getConfig(), $context->getLanguage() );
551 if ( !is_array( $logo ) ) {
553 return [ $logo => [
'as' =>
'image' ] ];
556 if ( isset( $logo[
'svg'] ) ) {
559 return [ $logo[
'svg'] => [
'as' =>
'image' ] ];
563 foreach ( $logo as $dppx => $src ) {
565 $dppx = substr( $dppx, 0, -1 );
566 $logosPerDppx[$dppx] = $src;
570 uksort( $logosPerDppx,
static function ( $a, $b ) {
578 foreach ( $logosPerDppx as $dppx => $src ) {
585 $logosCount = count( $logos );
592 for ( $i = 0; $i < $logosCount; $i++ ) {
597 $media_query =
'not all and (min-resolution: ' . $logos[1][
'dppx'] .
'dppx)';
598 } elseif ( $i !== $logosCount - 1 ) {
603 $upper_bound = floatval( $logos[$i + 1][
'dppx'] ) - 0.000001;
604 $media_query =
'(min-resolution: ' . $logos[$i][
'dppx'] .
605 'dppx) and (max-resolution: ' . $upper_bound .
'dppx)';
608 $media_query =
'(min-resolution: ' . $logos[$i][
'dppx'] .
'dppx)';
611 $preloadLinks[$logos[$i][
'src']] = [
613 'media' => $media_query
617 return $preloadLinks;
628 private function normalizeStyles( array &$styles ): void {
629 foreach ( $styles as $key => $val ) {
630 if ( !is_array( $val ) ) {
631 $styles[$key] = [ $val ];
642 private static function getRelativeSizedLogo( array $logoElement ) {
643 $width = $logoElement[
'width'];
644 $height = $logoElement[
'height'];
645 $widthRelative = $width / 16;
646 $heightRelative = $height / 16;
648 $logoElement[
'style'] =
'width: ' . $widthRelative .
'em; height: ' . $heightRelative .
'em;';
669 if ( $logos ===
false ) {
675 if ( $lang && isset( $logos[
'variants'][$lang] ) ) {
676 foreach ( $logos[
'variants'][$lang] as $type => $value ) {
677 $logos[$type] = $value;
682 if ( !isset( $logos[
'1x' ] ) ) {
683 $logo = $conf->
get( MainConfigNames::Logo );
685 $logos[
'1x'] = $logo;
689 if ( isset( $logos[
'wordmark'] ) ) {
691 $logos[
'wordmark'] = self::getRelativeSizedLogo( $logos[
'wordmark'] );
693 if ( isset( $logos[
'tagline'] ) ) {
694 $logos[
'tagline'] = self::getRelativeSizedLogo( $logos[
'tagline'] );
710 $logoHD = self::getAvailableLogos( $conf, $lang );
711 $logo = $logoHD[
'1x'];
713 $logo1Url = OutputPage::transformResourcePath( $conf, $logo );
719 if ( isset( $logoHD[
'svg'] ) ) {
720 $logoUrls[
'svg'] = OutputPage::transformResourcePath(
724 } elseif ( isset( $logoHD[
'1.5x'] ) || isset( $logoHD[
'2x'] ) ) {
726 if ( isset( $logoHD[
'1.5x'] ) ) {
727 $logoUrls[
'1.5x'] = OutputPage::transformResourcePath(
732 if ( isset( $logoHD[
'2x'] ) ) {
733 $logoUrls[
'2x'] = OutputPage::transformResourcePath(
763 $lessVars = parent::getLessVars( $context );
764 $logos = self::getAvailableLogos( $this->getConfig(), $context->
getLanguage() );
766 if ( isset( $logos[
'wordmark'] ) ) {
767 $logo = $logos[
'wordmark'];
768 $lessVars[
'logo-enabled' ] =
true;
769 $lessVars[
'logo-wordmark-url' ] = CSSMin::buildUrlValue( $logo[
'src'] );
770 $lessVars[
'logo-wordmark-width' ] = intval( $logo[
'width'] );
771 $lessVars[
'logo-wordmark-height' ] = intval( $logo[
'height'] );
773 $lessVars[
'logo-enabled' ] =
false;
779 $summary = parent::getDefinitionSummary( $context );
781 'logos' => self::getAvailableLogos( $this->getConfig(), $context->
getLanguage() ),
if(!defined('MW_SETUP_CALLBACK'))
A class containing constants representing the names of configuration variables.
const UseContentMediaStyles
Name constant for the UseContentMediaStyles setting, for use with Config::get()
const ResourceBasePath
Name constant for the ResourceBasePath setting, for use with Config::get()
const ParserEnableLegacyMediaDOM
Name constant for the ParserEnableLegacyMediaDOM 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.