24use InvalidArgumentException;
27use Wikimedia\Minify\CSSMin;
139 private const FEATURE_FILES = [
141 'all' => [
'resources/src/mediawiki.skinning/accessibility.less' ],
144 'all' => [
'resources/src/mediawiki.skinning/normalize.less' ],
148 'all' => [
'resources/src/mediawiki.skinning/logo.less' ],
150 'print' => [
'resources/src/mediawiki.skinning/logo-print.less' ],
153 'all' => [
'resources/src/mediawiki.skinning/content.thumbnails-common.less' ],
154 'screen' => [
'resources/src/mediawiki.skinning/content.thumbnails-screen.less' ],
155 'print' => [
'resources/src/mediawiki.skinning/content.thumbnails-print.less' ],
158 'screen' => [
'resources/src/mediawiki.skinning/content.links.less' ]
160 'content-links-external' => [
161 'screen' => [
'resources/src/mediawiki.skinning/content.externallinks.less' ]
164 'screen' => [
'resources/src/mediawiki.skinning/content.body.less' ],
165 'print' => [
'resources/src/mediawiki.skinning/content.body-print.less' ],
167 'content-tables' => [
168 'screen' => [
'resources/src/mediawiki.skinning/content.tables.less' ],
169 'print' => [
'resources/src/mediawiki.skinning/content.tables-print.less' ]
174 'interface-category' => [
175 'screen' => [
'resources/src/mediawiki.skinning/interface.category.less' ],
176 'print' => [
'resources/src/mediawiki.skinning/interface.category-print.less' ],
178 'interface-core' => [
179 'screen' => [
'resources/src/mediawiki.skinning/interface.less' ],
180 'print' => [
'resources/src/mediawiki.skinning/interface-print.less' ],
182 'interface-edit-section-links' => [
183 'screen' => [
'resources/src/mediawiki.skinning/interface-edit-section-links.less' ],
185 'interface-indicators' => [
186 'screen' => [
'resources/src/mediawiki.skinning/interface-indicators.less' ],
188 'interface-site-notice' => [
189 'screen' => [
'resources/src/mediawiki.skinning/interface-site-notice.less' ],
191 'interface-subtitle' => [
192 'screen' => [
'resources/src/mediawiki.skinning/interface-subtitle.less' ],
194 'interface-message-box' => [
195 'all' => [
'resources/src/mediawiki.skinning/messageBoxes.less' ],
197 'interface-user-message' => [
198 'screen' => [
'resources/src/mediawiki.skinning/interface-user-message.less' ],
201 'screen' => [
'resources/src/mediawiki.skinning/elements.less' ],
202 'print' => [
'resources/src/mediawiki.skinning/elements-print.less' ],
207 'i18n-ordered-lists' => [
208 'screen' => [
'resources/src/mediawiki.skinning/i18n-ordered-lists.less' ],
210 'i18n-all-lists-margins' => [
211 'screen' => [
'resources/src/mediawiki.skinning/i18n-all-lists-margins.less' ],
214 'screen' => [
'resources/src/mediawiki.skinning/i18n-headings.less' ],
217 'all' => [
'resources/src/mediawiki.skinning/toc/common.css' ],
218 'screen' => [
'resources/src/mediawiki.skinning/toc/screen.less' ],
219 'print' => [
'resources/src/mediawiki.skinning/toc/print.css' ],
233 private const DEFAULT_FEATURES_SPECIFIED = [
234 'accessibility' =>
true,
235 'content-body' =>
true,
236 'interface-core' =>
true,
248 private const DEFAULT_FEATURES_ABSENT = [
252 private const LESS_MESSAGES = [
283 $features = $options[
'features'] ?? self::DEFAULT_FEATURES_ABSENT;
284 $listMode = array_keys( $features ) === range( 0, count( $features ) - 1 );
290 array_fill_keys( $features,
true ),
false,
$messages
293 foreach ( $features as $key => $enabled ) {
294 if ( !isset( self::FEATURE_FILES[$key] ) ) {
295 throw new InvalidArgumentException(
"Feature '$key' is not recognised" );
299 $this->features = $listMode
300 ? array_keys( array_filter( $features ) )
301 : array_keys( array_filter( $features + self::DEFAULT_FEATURES_SPECIFIED ) );
306 if ( in_array(
'toc', $this->features ) ) {
307 $options[
'lessMessages'] = array_merge(
308 $options[
'lessMessages'] ?? [],
314 $messages .=
'More information can be found at [[mw:Manual:ResourceLoaderSkinModule]]. ';
328 array $features,
bool $addUnspecifiedFeatures =
true, &
$messages =
''
331 if ( isset( $features[
'content' ] ) ) {
332 $features[
'content-media' ] = $features[
'content' ];
333 unset( $features[
'content' ] );
334 $messages .=
'[1.37] The use of the `content` feature with ResourceLoaderSkinModule'
335 .
' is deprecated. Use `content-media` instead. ';
339 if ( isset( $features[
'content-thumbnails' ] ) ) {
340 $features[
'content-media' ] = $features[
'content-thumbnails' ];
341 $messages .=
'[1.37] The use of the `content-thumbnails` feature with ResourceLoaderSkinModule'
342 .
' is deprecated. Use `content-media` instead. ';
343 unset( $features[
'content-thumbnails' ] );
347 if ( $addUnspecifiedFeatures && isset( $features[
'content-links' ] )
348 && !isset( $features[
'content-links-external' ] )
351 $features[
'content-links-external' ] = $features[
'content-links' ];
355 if ( isset( $features[
'legacy'] ) && $features[
'legacy'] ) {
356 $messages .=
'[1.37] The use of the `legacy` feature with ResourceLoaderSkinModule is deprecated'
357 .
'(T89981) and is a NOOP since 1.39 (T304325). This should be urgently omited to retain compatibility '
358 .
'with future MediaWiki versions';
363 if ( $addUnspecifiedFeatures && isset( $features[
'element' ] ) && !isset( $features[
'content-links' ] ) ) {
364 $features[
'content-links' ] = $features[
'element' ];
371 if ( isset( $features[
'content-parser-output' ] ) ) {
372 $features[
'content-body' ] = $features[
'content-parser-output' ];
373 unset( $features[
'content-parser-output' ] );
377 if ( isset( $features[
'interface' ] ) && $features[
'interface' ] ) {
378 unset( $features[
'interface' ] );
379 $features[
'interface-core' ] =
true;
380 $features[
'interface-indicators' ] =
true;
381 $features[
'interface-subtitle' ] =
true;
382 $features[
'interface-user-message' ] =
true;
383 $features[
'interface-site-notice' ] =
true;
384 $features[
'interface-edit-section-links' ] =
true;
396 $styles = parent::getStyleFiles( $context );
400 list( $defaultLocalBasePath, $defaultRemoteBasePath ) =
407 $featureFilePaths = [];
409 foreach ( self::FEATURE_FILES as $feature => $featureFiles ) {
410 if ( in_array( $feature, $this->features ) ) {
411 foreach ( $featureFiles as $mediaType => $files ) {
412 foreach ( $files as $filepath ) {
413 $featureFilePaths[$mediaType][] =
new FilePath(
415 $defaultLocalBasePath,
416 $defaultRemoteBasePath
420 if ( $feature ===
'content-media' && (
424 $featureFilePaths[
'all'][] =
new FilePath(
425 'resources/src/mediawiki.skinning/content.media-common.less',
426 $defaultLocalBasePath,
427 $defaultRemoteBasePath
429 $featureFilePaths[
'screen'][] =
new FilePath(
430 'resources/src/mediawiki.skinning/content.media-screen.less',
431 $defaultLocalBasePath,
432 $defaultRemoteBasePath
434 $featureFilePaths[
'print'][] =
new FilePath(
435 'resources/src/mediawiki.skinning/content.media-print.less',
436 $defaultLocalBasePath,
437 $defaultRemoteBasePath
447 foreach ( $styles as $mediaType => $paths ) {
448 $featureFilePaths[$mediaType] = array_merge( $featureFilePaths[$mediaType] ?? [], $paths );
451 return $featureFilePaths;
459 $logo = $this->getLogoData( $this->getConfig(), $context->
getLanguage() );
460 $styles = parent::getStyles( $context );
461 $this->normalizeStyles( $styles );
463 $isLogoFeatureEnabled = in_array(
'logo', $this->features );
464 if ( $isLogoFeatureEnabled ) {
465 $default = !is_array( $logo ) ? $logo : ( $logo[
'1x'] ?? null );
470 $styles[
'all'][] =
'.mw-wiki-logo { background-image: ' .
471 CSSMin::buildUrlValue( $default ) .
474 if ( is_array( $logo ) ) {
475 if ( isset( $logo[
'svg'] ) ) {
476 $styles[
'all'][] =
'.mw-wiki-logo { ' .
477 'background-image: linear-gradient(transparent, transparent), ' .
478 CSSMin::buildUrlValue( $logo[
'svg'] ) .
';' .
479 'background-size: 135px auto; }';
481 if ( isset( $logo[
'1.5x'] ) ) {
483 '(-webkit-min-device-pixel-ratio: 1.5), ' .
484 '(min-resolution: 1.5dppx), ' .
485 '(min-resolution: 144dpi)'
486 ][] =
'.mw-wiki-logo { background-image: ' .
487 CSSMin::buildUrlValue( $logo[
'1.5x'] ) .
';' .
488 'background-size: 135px auto; }';
490 if ( isset( $logo[
'2x'] ) ) {
492 '(-webkit-min-device-pixel-ratio: 2), ' .
493 '(min-resolution: 2dppx), ' .
494 '(min-resolution: 192dpi)'
495 ][] =
'.mw-wiki-logo { background-image: ' .
496 CSSMin::buildUrlValue( $logo[
'2x'] ) .
';' .
497 'background-size: 135px auto; }';
511 if ( !in_array(
'logo', $this->features ) ) {
515 $logo = $this->getLogoData( $this->getConfig(), $context->getLanguage() );
517 if ( !is_array( $logo ) ) {
519 return [ $logo => [
'as' =>
'image' ] ];
522 if ( isset( $logo[
'svg'] ) ) {
525 return [ $logo[
'svg'] => [
'as' =>
'image' ] ];
529 foreach ( $logo as $dppx => $src ) {
531 $dppx = substr( $dppx, 0, -1 );
532 $logosPerDppx[$dppx] = $src;
536 uksort( $logosPerDppx,
static function ( $a, $b ) {
544 foreach ( $logosPerDppx as $dppx => $src ) {
551 $logosCount = count( $logos );
558 for ( $i = 0; $i < $logosCount; $i++ ) {
563 $media_query =
'not all and (min-resolution: ' . $logos[1][
'dppx'] .
'dppx)';
564 } elseif ( $i !== $logosCount - 1 ) {
569 $upper_bound = floatval( $logos[$i + 1][
'dppx'] ) - 0.000001;
570 $media_query =
'(min-resolution: ' . $logos[$i][
'dppx'] .
571 'dppx) and (max-resolution: ' . $upper_bound .
'dppx)';
574 $media_query =
'(min-resolution: ' . $logos[$i][
'dppx'] .
'dppx)';
577 $preloadLinks[$logos[$i][
'src']] = [
579 'media' => $media_query
583 return $preloadLinks;
594 private function normalizeStyles( array &$styles ): void {
595 foreach ( $styles as $key => $val ) {
596 if ( !is_array( $val ) ) {
597 $styles[$key] = [ $val ];
608 private static function getRelativeSizedLogo( array $logoElement ) {
609 $width = $logoElement[
'width'];
610 $height = $logoElement[
'height'];
611 $widthRelative = $width / 16;
612 $heightRelative = $height / 16;
614 $logoElement[
'style'] =
'width: ' . $widthRelative .
'em; height: ' . $heightRelative .
'em;';
635 if ( $logos ===
false ) {
641 if (
$lang && isset( $logos[
'variants'][
$lang] ) ) {
642 foreach ( $logos[
'variants'][
$lang] as
$type => $value ) {
643 $logos[
$type] = $value;
648 if ( !isset( $logos[
'1x' ] ) ) {
649 $logo = $conf->
get( MainConfigNames::Logo );
651 $logos[
'1x'] = $logo;
656 $logoHD = $conf->
get( MainConfigNames::LogoHD );
666 if ( isset( $logos[
'wordmark'] ) ) {
668 $logos[
'wordmark'] = self::getRelativeSizedLogo( $logos[
'wordmark'] );
670 if ( isset( $logos[
'tagline'] ) ) {
671 $logos[
'tagline'] = self::getRelativeSizedLogo( $logos[
'tagline'] );
687 $logoHD = self::getAvailableLogos( $conf,
$lang );
688 $logo = $logoHD[
'1x'];
690 $logo1Url = OutputPage::transformResourcePath( $conf, $logo );
696 if ( isset( $logoHD[
'svg'] ) ) {
697 $logoUrls[
'svg'] = OutputPage::transformResourcePath(
701 } elseif ( isset( $logoHD[
'1.5x'] ) || isset( $logoHD[
'2x'] ) ) {
703 if ( isset( $logoHD[
'1.5x'] ) ) {
704 $logoUrls[
'1.5x'] = OutputPage::transformResourcePath(
709 if ( isset( $logoHD[
'2x'] ) ) {
710 $logoUrls[
'2x'] = OutputPage::transformResourcePath(
740 $lessVars = parent::getLessVars( $context );
741 $logos = self::getAvailableLogos( $this->getConfig() );
743 if ( isset( $logos[
'wordmark'] ) ) {
744 $logo = $logos[
'wordmark'];
745 $lessVars[
'logo-enabled' ] =
true;
746 $lessVars[
'logo-wordmark-url' ] = CSSMin::buildUrlValue( $logo[
'src'] );
747 $lessVars[
'logo-wordmark-width' ] = intval( $logo[
'width'] );
748 $lessVars[
'logo-wordmark-height' ] = intval( $logo[
'height'] );
750 $lessVars[
'logo-enabled' ] =
false;
756 $summary = parent::getDefinitionSummary( $context );
758 'logos' => self::getAvailableLogos( $this->getConfig() ),
765class_alias( SkinModule::class,
'ResourceLoaderSkinModule' );
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Exceptions for config failures.
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()
Context object that contains information about the state of a specific ResourceLoader web request.
This is one of the Core classes and should be read at least once by any new developers.
Interface for configuration instances.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
if(!isset( $args[0])) $lang