22use InvalidArgumentException;
26use Wikimedia\Minify\CSSMin;
134 private const FEATURE_FILES = [
136 'all' => [
'resources/src/mediawiki.skinning/accessibility.less' ],
139 'all' => [
'resources/src/mediawiki.skinning/normalize.less' ],
143 'all' => [
'resources/src/mediawiki.skinning/logo.less' ],
145 'print' => [
'resources/src/mediawiki.skinning/logo-print.less' ],
147 'content-media' => [],
149 'screen' => [
'resources/src/mediawiki.skinning/content.links.less' ]
151 'content-links-external' => [
152 'screen' => [
'resources/src/mediawiki.skinning/content.externallinks.less' ]
155 'screen' => [
'resources/src/mediawiki.skinning/content.body.less' ],
156 'print' => [
'resources/src/mediawiki.skinning/content.body-print.less' ],
158 'content-tables' => [
159 'screen' => [
'resources/src/mediawiki.skinning/content.tables.less' ],
160 'print' => [
'resources/src/mediawiki.skinning/content.tables-print.less' ]
165 'interface-category' => [
166 'screen' => [
'resources/src/mediawiki.skinning/interface.category.less' ],
167 'print' => [
'resources/src/mediawiki.skinning/interface.category-print.less' ],
169 'interface-core' => [
170 'screen' => [
'resources/src/mediawiki.skinning/interface.less' ],
171 'print' => [
'resources/src/mediawiki.skinning/interface-print.less' ],
173 'interface-edit-section-links' => [
174 'screen' => [
'resources/src/mediawiki.skinning/interface-edit-section-links.less' ],
176 'interface-indicators' => [
177 'screen' => [
'resources/src/mediawiki.skinning/interface-indicators.less' ],
179 'interface-site-notice' => [
180 'screen' => [
'resources/src/mediawiki.skinning/interface-site-notice.less' ],
182 'interface-subtitle' => [
183 'screen' => [
'resources/src/mediawiki.skinning/interface-subtitle.less' ],
185 'interface-message-box' => [
186 'all' => [
'resources/src/mediawiki.skinning/messageBoxes.less' ],
188 'interface-user-message' => [
189 'screen' => [
'resources/src/mediawiki.skinning/interface-user-message.less' ],
192 'screen' => [
'resources/src/mediawiki.skinning/elements.less' ],
193 'print' => [
'resources/src/mediawiki.skinning/elements-print.less' ],
198 'i18n-ordered-lists' => [
199 'screen' => [
'resources/src/mediawiki.skinning/i18n-ordered-lists.less' ],
201 'i18n-all-lists-margins' => [
202 'screen' => [
'resources/src/mediawiki.skinning/i18n-all-lists-margins.less' ],
205 'screen' => [
'resources/src/mediawiki.skinning/i18n-headings.less' ],
208 'all' => [
'resources/src/mediawiki.skinning/toc/common.css' ],
209 'screen' => [
'resources/src/mediawiki.skinning/toc/screen.less' ],
210 'print' => [
'resources/src/mediawiki.skinning/toc/print.css' ],
224 private const DEFAULT_FEATURES_SPECIFIED = [
225 'accessibility' =>
true,
226 'content-body' =>
true,
227 'interface-core' =>
true,
239 private const DEFAULT_FEATURES_ABSENT = [
243 private const LESS_MESSAGES = [
274 $features = $options[
'features'] ?? self::DEFAULT_FEATURES_ABSENT;
275 $listMode = array_keys( $features ) === range( 0, count( $features ) - 1 );
281 array_fill_keys( $features,
true ),
false,
$messages
284 foreach ( $features as $key => $enabled ) {
285 if ( !isset( self::FEATURE_FILES[$key] ) ) {
286 throw new InvalidArgumentException(
"Feature '$key' is not recognised" );
290 $this->features = $listMode
291 ? array_keys( array_filter( $features ) )
292 : array_keys( array_filter( $features + self::DEFAULT_FEATURES_SPECIFIED ) );
297 if ( in_array(
'toc', $this->features ) ) {
298 $options[
'lessMessages'] = array_merge(
299 $options[
'lessMessages'] ?? [],
305 $messages .=
'More information can be found at [[mw:Manual:ResourceLoaderSkinModule]]. ';
319 array $features,
bool $addUnspecifiedFeatures =
true, &
$messages =
''
322 if ( isset( $features[
'content' ] ) ) {
323 $features[
'content-media' ] = $features[
'content' ];
324 unset( $features[
'content' ] );
325 $messages .=
'[1.37] The use of the `content` feature with SkinModule'
326 .
' is deprecated. Use `content-media` instead. ';
330 if ( isset( $features[
'content-thumbnails' ] ) ) {
331 $features[
'content-media' ] = $features[
'content-thumbnails' ];
332 $messages .=
'[1.37] The use of the `content-thumbnails` feature with SkinModule'
333 .
' is deprecated. Use `content-media` instead. ';
334 unset( $features[
'content-thumbnails' ] );
338 if ( $addUnspecifiedFeatures && isset( $features[
'content-links' ] )
339 && !isset( $features[
'content-links-external' ] )
342 $features[
'content-links-external' ] = $features[
'content-links' ];
346 if ( isset( $features[
'legacy'] ) && $features[
'legacy'] ) {
347 $messages .=
'[1.37] The use of the `legacy` feature with SkinModule is deprecated'
348 .
'(T89981) and is a NOOP since 1.39 (T304325). This should be urgently omited to retain compatibility '
349 .
'with future MediaWiki versions';
354 if ( $addUnspecifiedFeatures && isset( $features[
'element' ] ) && !isset( $features[
'content-links' ] ) ) {
355 $features[
'content-links' ] = $features[
'element' ];
362 if ( isset( $features[
'content-parser-output' ] ) ) {
363 $features[
'content-body' ] = $features[
'content-parser-output' ];
364 unset( $features[
'content-parser-output' ] );
368 if ( isset( $features[
'interface' ] ) && $features[
'interface' ] ) {
369 unset( $features[
'interface' ] );
370 $features[
'interface-core' ] =
true;
371 $features[
'interface-indicators' ] =
true;
372 $features[
'interface-subtitle' ] =
true;
373 $features[
'interface-user-message' ] =
true;
374 $features[
'interface-site-notice' ] =
true;
375 $features[
'interface-edit-section-links' ] =
true;
388 [ $defaultLocalBasePath, $defaultRemoteBasePath ] =
395 $featureFilePaths = [];
397 foreach ( self::FEATURE_FILES as $feature => $featureFiles ) {
398 if ( in_array( $feature, $this->features ) ) {
399 foreach ( $featureFiles as $mediaType => $files ) {
400 foreach ( $files as $filepath ) {
401 $featureFilePaths[$mediaType][] =
new FilePath(
403 $defaultLocalBasePath,
404 $defaultRemoteBasePath
409 if ( $feature ===
'content-media' ) {
411 $featureFilePaths[
'all'][] =
new FilePath(
412 'resources/src/mediawiki.skinning/content.thumbnails-common.less',
413 $defaultLocalBasePath,
414 $defaultRemoteBasePath
416 $featureFilePaths[
'screen'][] =
new FilePath(
417 'resources/src/mediawiki.skinning/content.thumbnails-screen.less',
418 $defaultLocalBasePath,
419 $defaultRemoteBasePath
421 $featureFilePaths[
'print'][] =
new FilePath(
422 'resources/src/mediawiki.skinning/content.thumbnails-print.less',
423 $defaultLocalBasePath,
424 $defaultRemoteBasePath
431 $featureFilePaths[
'all'][] =
new FilePath(
432 'resources/src/mediawiki.skinning/content.media-common.less',
433 $defaultLocalBasePath,
434 $defaultRemoteBasePath
436 $featureFilePaths[
'screen'][] =
new FilePath(
437 'resources/src/mediawiki.skinning/content.media-screen.less',
438 $defaultLocalBasePath,
439 $defaultRemoteBasePath
441 $featureFilePaths[
'print'][] =
new FilePath(
442 'resources/src/mediawiki.skinning/content.media-print.less',
443 $defaultLocalBasePath,
444 $defaultRemoteBasePath
450 return $featureFilePaths;
462 private function combineFeatureAndParentStyles( $featureStyles, $parentStyles ) {
465 $combinedStyles = array_merge( $combinedFeatureStyles, $combinedParentStyles );
466 return [
'' => $combinedStyles ];
477 $logo = $this->getLogoData( $this->getConfig(), $context->getLanguage() );
478 $default = !is_array( $logo ) ? $logo : ( $logo[
'svg'] ?? $logo[
'1x'] ?? null );
482 return $featureStyles;
485 $featureStyles[
'all'][] =
'.mw-wiki-logo { background-image: ' .
486 CSSMin::buildUrlValue( $default ) .
489 if ( is_array( $logo ) ) {
490 if ( isset( $logo[
'svg'] ) ) {
491 $featureStyles[
'all'][] =
'.mw-wiki-logo { ' .
492 'background-size: 135px auto; }';
494 if ( isset( $logo[
'1.5x'] ) ) {
496 '(-webkit-min-device-pixel-ratio: 1.5), ' .
497 '(min-resolution: 1.5dppx), ' .
498 '(min-resolution: 144dpi)'
499 ][] =
'.mw-wiki-logo { background-image: ' .
500 CSSMin::buildUrlValue( $logo[
'1.5x'] ) .
';' .
501 'background-size: 135px auto; }';
503 if ( isset( $logo[
'2x'] ) ) {
505 '(-webkit-min-device-pixel-ratio: 2), ' .
506 '(min-resolution: 2dppx), ' .
507 '(min-resolution: 192dpi)'
508 ][] =
'.mw-wiki-logo { background-image: ' .
509 CSSMin::buildUrlValue( $logo[
'2x'] ) .
';' .
510 'background-size: 135px auto; }';
514 return $featureStyles;
522 $parentStyles = parent::getStyles( $context );
523 $featureFilePaths = $this->getFeatureFilePaths();
524 $featureStyles = $this->readStyleFiles( $featureFilePaths, $context );
526 $this->normalizeStyles( $featureStyles );
527 $this->normalizeStyles( $parentStyles );
529 $isLogoFeatureEnabled = in_array(
'logo', $this->features );
530 if ( $isLogoFeatureEnabled ) {
531 $featureStyles = $this->generateAndAppendLogoStyles( $featureStyles, $context );
534 return $this->combineFeatureAndParentStyles( $featureStyles, $parentStyles );
542 if ( !in_array(
'logo', $this->features ) ) {
546 $logo = $this->getLogoData( $this->getConfig(), $context->getLanguage() );
548 if ( !is_array( $logo ) ) {
550 return [ $logo => [
'as' =>
'image' ] ];
553 if ( isset( $logo[
'svg'] ) ) {
556 return [ $logo[
'svg'] => [
'as' =>
'image' ] ];
560 foreach ( $logo as $dppx => $src ) {
562 $dppx = substr( $dppx, 0, -1 );
563 $logosPerDppx[$dppx] = $src;
567 uksort( $logosPerDppx,
static function ( $a, $b ) {
575 foreach ( $logosPerDppx as $dppx => $src ) {
582 $logosCount = count( $logos );
589 for ( $i = 0; $i < $logosCount; $i++ ) {
594 $media_query =
'not all and (min-resolution: ' . $logos[1][
'dppx'] .
'dppx)';
595 } elseif ( $i !== $logosCount - 1 ) {
600 $upper_bound = floatval( $logos[$i + 1][
'dppx'] ) - 0.000001;
601 $media_query =
'(min-resolution: ' . $logos[$i][
'dppx'] .
602 'dppx) and (max-resolution: ' . $upper_bound .
'dppx)';
605 $media_query =
'(min-resolution: ' . $logos[$i][
'dppx'] .
'dppx)';
608 $preloadLinks[$logos[$i][
'src']] = [
610 'media' => $media_query
614 return $preloadLinks;
625 private function normalizeStyles( array &$styles ): void {
626 foreach ( $styles as $key => $val ) {
627 if ( !is_array( $val ) ) {
628 $styles[$key] = [ $val ];
639 private static function getRelativeSizedLogo( array $logoElement ) {
640 $width = $logoElement[
'width'];
641 $height = $logoElement[
'height'];
642 $widthRelative = $width / 16;
643 $heightRelative = $height / 16;
645 $logoElement[
'style'] =
'width: ' . $widthRelative .
'em; height: ' . $heightRelative .
'em;';
666 if ( $logos ===
false ) {
672 if ( $lang && isset( $logos[
'variants'][$lang] ) ) {
673 foreach ( $logos[
'variants'][$lang] as $type => $value ) {
674 $logos[$type] = $value;
679 if ( !isset( $logos[
'1x' ] ) ) {
680 $logo = $conf->
get( MainConfigNames::Logo );
682 $logos[
'1x'] = $logo;
686 if ( isset( $logos[
'wordmark'] ) ) {
688 $logos[
'wordmark'] = self::getRelativeSizedLogo( $logos[
'wordmark'] );
690 if ( isset( $logos[
'tagline'] ) ) {
691 $logos[
'tagline'] = self::getRelativeSizedLogo( $logos[
'tagline'] );
707 $logoHD = self::getAvailableLogos( $conf, $lang );
708 $logo = $logoHD[
'1x'];
710 $logo1Url = OutputPage::transformResourcePath( $conf, $logo );
716 if ( isset( $logoHD[
'svg'] ) ) {
717 $logoUrls[
'svg'] = OutputPage::transformResourcePath(
721 } elseif ( isset( $logoHD[
'1.5x'] ) || isset( $logoHD[
'2x'] ) ) {
723 if ( isset( $logoHD[
'1.5x'] ) ) {
724 $logoUrls[
'1.5x'] = OutputPage::transformResourcePath(
729 if ( isset( $logoHD[
'2x'] ) ) {
730 $logoUrls[
'2x'] = OutputPage::transformResourcePath(
760 $lessVars = parent::getLessVars( $context );
761 $logos = self::getAvailableLogos( $this->getConfig() );
763 if ( isset( $logos[
'wordmark'] ) ) {
764 $logo = $logos[
'wordmark'];
765 $lessVars[
'logo-enabled' ] =
true;
766 $lessVars[
'logo-wordmark-url' ] = CSSMin::buildUrlValue( $logo[
'src'] );
767 $lessVars[
'logo-wordmark-width' ] = intval( $logo[
'width'] );
768 $lessVars[
'logo-wordmark-height' ] = intval( $logo[
'height'] );
770 $lessVars[
'logo-enabled' ] =
false;
776 $summary = parent::getDefinitionSummary( $context );
778 'logos' => self::getAvailableLogos( $this->getConfig() ),
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.