Add support for SVGs to $wgLogoHD with PNG fallback
authorPaladox <thomasmulhall410@yahoo.com>
Mon, 22 May 2017 18:12:26 +0000 (19:12 +0100)
committerPaladox <thomasmulhall410@yahoo.com>
Tue, 24 Oct 2017 17:51:28 +0000 (17:51 +0000)
SVGs could already be used through $wgLogo. However, if a PNG fallback
is desired for older browsers, using SVGs was previously not possible.

This commit adds support for using an SVG image in $wgLogoHD and,
using $wgLogo as the fallback image.

Usage example:

> $wgLogo = '/path/to/png';
> $wgLogoHD = [
>     'svg' => 'path/to/svg',
> ];

Note: When the 'svg' key is set in $wgLogoHD, any '1.5x' and '2x' keys will
no longer be used because SVGs can render optimally on any screen sizes.

@Reedy, @Krinkle and @Brion VIBBER helped me alot with this.

Bug: T86229
Change-Id: I6197d96ce9110f4711ef2c4b198445bc5c6ae110

RELEASE-NOTES-1.31
includes/DefaultSettings.php
includes/OutputPage.php
includes/resourceloader/ResourceLoaderSkinModule.php
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderSkinModuleTest.php

index 3c22e78..fc50897 100644 (file)
@@ -11,6 +11,8 @@ production.
   essential.
 * $wgUsejQueryThree was removed, as it is now the default. This was documented as a
   temporary variable during the migration period, deprecated since 1.29.
+* $wgLogoHD has been updated to support svg images and uses $wgLogo where
+  possible for fallback images such as png.
 * …
 
 === New features in 1.31 ===
index c1a518a..040f1ce 100644 (file)
@@ -290,6 +290,17 @@ $wgLogo = false;
  * ];
  * @endcode
  *
+ * SVG is also supported but when enabled, it
+ * disables 1.5x and 2x as svg will already
+ * be optimised for screen resolution.
+ *
+ * @par Example:
+ * @code
+ * $wgLogoHD = [
+ *     "svg" => "path/to/svg_version.svg",
+ * ];
+ * @endcode
+ *
  * @since 1.25
  */
 $wgLogoHD = false;
index 7a2b7df..500be8d 100644 (file)
@@ -4021,6 +4021,13 @@ class OutputPage extends ContextSource {
                        return;
                }
 
+               if ( isset( $logo['svg'] ) ) {
+                       // No media queries required if we only have a 1x and svg variant
+                       // because all preload-capable browsers support SVGs
+                       $this->addLinkHeader( '<' . $logo['svg'] . '>;rel=preload;as=image' );
+                       return;
+               }
+
                foreach ( $logo as $dppx => $src ) {
                        // Keys are in this format: "1.5x"
                        $dppx = substr( $dppx, 0, -1 );
index ca6e59f..fbd0a24 100644 (file)
@@ -32,7 +32,7 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
         * @return array
         */
        public function getStyles( ResourceLoaderContext $context ) {
-               $logo = $this->getLogo( $this->getConfig() );
+               $logo = $this->getLogoData( $this->getConfig() );
                $styles = parent::getStyles( $context );
                $this->normalizeStyles( $styles );
 
@@ -42,25 +42,34 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
                                '; }';
 
                if ( is_array( $logo ) ) {
-                       if ( isset( $logo['1.5x'] ) ) {
-                               $styles[
-                                       '(-webkit-min-device-pixel-ratio: 1.5), ' .
-                                       '(min--moz-device-pixel-ratio: 1.5), ' .
+                       if ( isset( $logo['svg'] ) ) {
+                               $styles['all'][] = '.mw-wiki-logo { ' .
+                                       'background-image: -webkit-linear-gradient(transparent, transparent), ' .
+                                               CSSMin::buildUrlValue( $logo['svg'] ) . '; ' .
+                                       'background-image: linear-gradient(transparent, transparent), ' .
+                                               CSSMin::buildUrlValue( $logo['svg'] ) . ';' .
+                                       'background-size: 135px auto; }';
+                       } else {
+                               if ( isset( $logo['1.5x'] ) ) {
+                                       $styles[
+                                               '(-webkit-min-device-pixel-ratio: 1.5), ' .
+                                               '(min--moz-device-pixel-ratio: 1.5), ' .
                                        '(min-resolution: 1.5dppx), ' .
-                                       '(min-resolution: 144dpi)'
-                               ][] = '.mw-wiki-logo { background-image: ' .
-                               CSSMin::buildUrlValue( $logo['1.5x'] ) . ';' .
-                               'background-size: 135px auto; }';
-                       }
-                       if ( isset( $logo['2x'] ) ) {
-                               $styles[
-                                       '(-webkit-min-device-pixel-ratio: 2), ' .
-                                       '(min--moz-device-pixel-ratio: 2),' .
-                                       '(min-resolution: 2dppx), ' .
-                                       '(min-resolution: 192dpi)'
-                               ][] = '.mw-wiki-logo { background-image: ' .
-                               CSSMin::buildUrlValue( $logo['2x'] ) . ';' .
-                               'background-size: 135px auto; }';
+                                               '(min-resolution: 144dpi)'
+                                       ][] = '.mw-wiki-logo { background-image: ' .
+                                       CSSMin::buildUrlValue( $logo['1.5x'] ) . ';' .
+                                       'background-size: 135px auto; }';
+                               }
+                               if ( isset( $logo['2x'] ) ) {
+                                       $styles[
+                                               '(-webkit-min-device-pixel-ratio: 2), ' .
+                                               '(min--moz-device-pixel-ratio: 2), ' .
+                                               '(min-resolution: 2dppx), ' .
+                                               '(min-resolution: 192dpi)'
+                                       ][] = '.mw-wiki-logo { background-image: ' .
+                                       CSSMin::buildUrlValue( $logo['2x'] ) . ';' .
+                                       'background-size: 135px auto; }';
+                               }
                        }
                }
 
@@ -83,11 +92,21 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
                }
        }
 
+       /**
+        * @since 1.31
+        * @param Config $conf
+        * @return string|array
+        */
+       protected function getLogoData( Config $conf ) {
+               return static::getLogo( $conf );
+       }
+
        /**
         * @param Config $conf
-        * @return string|array Single url if no variants are defined
-        *  or array of logo urls keyed by dppx in form "<float>x".
-        *  Key "1x" is always defined.
+        * @return string|array Single url if no variants are defined,
+        *  or an array of logo urls keyed by dppx in form "<float>x".
+        *  Key "1x" is always defined. Key "svg" may also be defined,
+        *  in which case variants other than "1x" are omitted.
         */
        public static function getLogo( Config $conf ) {
                $logo = $conf->get( 'Logo' );
@@ -103,18 +122,25 @@ class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
                        '1x' => $logo1Url,
                ];
 
-               // Only 1.5x and 2x are supported
-               if ( isset( $logoHD['1.5x'] ) ) {
-                       $logoUrls['1.5x'] = OutputPage::transformResourcePath(
+               if ( isset( $logoHD['svg'] ) ) {
+                       $logoUrls['svg'] = OutputPage::transformResourcePath(
                                $conf,
-                               $logoHD['1.5x']
-                       );
-               }
-               if ( isset( $logoHD['2x'] ) ) {
-                       $logoUrls['2x'] = OutputPage::transformResourcePath(
-                               $conf,
-                               $logoHD['2x']
+                               $logoHD['svg']
                        );
+               } else {
+                       // Only 1.5x and 2x are supported
+                       if ( isset( $logoHD['1.5x'] ) ) {
+                               $logoUrls['1.5x'] = OutputPage::transformResourcePath(
+                                       $conf,
+                                       $logoHD['1.5x']
+                               );
+                       }
+                       if ( isset( $logoHD['2x'] ) ) {
+                               $logoUrls['2x'] = OutputPage::transformResourcePath(
+                                       $conf,
+                                       $logoHD['2x']
+                               );
+                       }
                }
 
                return $logoUrls;
index d29c79d..d5948ed 100644 (file)
@@ -640,6 +640,17 @@ class OutputPageTest extends MediaWikiTestCase {
                                'not all and (min-resolution: 2dppx),' .
                                '</img/two-x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)'
                        ],
+                       [
+                               [
+                                       'ResourceBasePath' => '/w',
+                                       'Logo' => '/img/default.png',
+                                       'LogoHD' => [
+                                               'svg' => '/img/vector.svg',
+                                       ],
+                               ],
+                               'Link: </img/vector.svg>;rel=preload;as=image'
+
+                       ],
                        [
                                [
                                        'ResourceBasePath' => '/w',
index c567698..be17a69 100644 (file)
@@ -1,15 +1,16 @@
 <?php
 
 /**
- * @group Database
  * @group ResourceLoader
  */
 class ResourceLoaderSkinModuleTest extends PHPUnit_Framework_TestCase {
 
+       // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
        public static function provideGetStyles() {
                return [
                        [
                                'parent' => [],
+                               'logo' => '/logo.png',
                                'expected' => [
                                        'all' => [ '.mw-wiki-logo { background-image: url(/logo.png); }' ],
                                ],
@@ -18,38 +19,77 @@ class ResourceLoaderSkinModuleTest extends PHPUnit_Framework_TestCase {
                                'parent' => [
                                        'screen' => '.example {}',
                                ],
+                               'logo' => '/logo.png',
                                'expected' => [
                                        'screen' => [ '.example {}' ],
                                        'all' => [ '.mw-wiki-logo { background-image: url(/logo.png); }' ],
                                ],
                        ],
+                       [
+                               'parent' => [],
+                               'logo' => [
+                                       '1x' => '/logo.png',
+                                       '1.5x' => '/logo@1.5x.png',
+                                       '2x' => '/logo@2x.png',
+                               ],
+                               'expected' => [
+                                       'all' => [ <<<CSS
+.mw-wiki-logo { background-image: url(/logo.png); }
+CSS
+                                       ],
+                                       '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx), (min-resolution: 144dpi)' => [ <<<CSS
+.mw-wiki-logo { background-image: url(/logo@1.5x.png);background-size: 135px auto; }
+CSS
+                                       ],
+                                       '(-webkit-min-device-pixel-ratio: 2), (min--moz-device-pixel-ratio: 2), (min-resolution: 2dppx), (min-resolution: 192dpi)' => [ <<<CSS
+.mw-wiki-logo { background-image: url(/logo@2x.png);background-size: 135px auto; }
+CSS
+                                       ],
+                               ],
+                       ],
+                       [
+                               'parent' => [],
+                               'logo' => [
+                                       '1x' => '/logo.png',
+                                       'svg' => '/logo.svg',
+                               ],
+                               'expected' => [
+                                       'all' => [ <<<CSS
+.mw-wiki-logo { background-image: url(/logo.png); }
+CSS
+                                       , <<<CSS
+.mw-wiki-logo { background-image: -webkit-linear-gradient(transparent, transparent), url(/logo.svg); background-image: linear-gradient(transparent, transparent), url(/logo.svg);background-size: 135px auto; }
+CSS
+                                       ],
+                               ],
+                       ],
                ];
        }
+       // @codingStandardsIgnoreEnd
 
        /**
         * @dataProvider provideGetStyles
         * @covers ResourceLoaderSkinModule::normalizeStyles
         * @covers ResourceLoaderSkinModule::getStyles
         */
-       public function testGetStyles( $parent, $expected ) {
+       public function testGetStyles( $parent, $logo, $expected ) {
                $module = $this->getMockBuilder( ResourceLoaderSkinModule::class )
                        ->disableOriginalConstructor()
-                       ->setMethods( [ 'readStyleFiles' ] )
+                       ->setMethods( [ 'readStyleFiles', 'getConfig', 'getLogoData' ] )
                        ->getMock();
                $module->expects( $this->once() )->method( 'readStyleFiles' )
                        ->willReturn( $parent );
-               $module->setConfig( new HashConfig( [
-                       'ResourceBasePath' => '/w',
-                       'Logo' => '/logo.png',
-                       'LogoHD' => false,
-               ] ) );
+               $module->expects( $this->once() )->method( 'getConfig' )
+                       ->willReturn( new HashConfig() );
+               $module->expects( $this->once() )->method( 'getLogoData' )
+                       ->willReturn( $logo );
 
                $ctx = $this->getMockBuilder( ResourceLoaderContext::class )
                        ->disableOriginalConstructor()->getMock();
 
                $this->assertEquals(
-                       $module->getStyles( $ctx ),
-                       $expected
+                       $expected,
+                       $module->getStyles( $ctx )
                );
        }
 
@@ -64,4 +104,102 @@ class ResourceLoaderSkinModuleTest extends PHPUnit_Framework_TestCase {
 
                $this->assertFalse( $module->isKnownEmpty( $ctx ) );
        }
+
+       /**
+        * @dataProvider provideGetLogo
+        * @covers ResourceLoaderSkinModule::getLogo
+        */
+       public function testGetLogo( $config, $expected, $baseDir = null ) {
+               if ( $baseDir ) {
+                       $oldIP = $GLOBALS['IP'];
+                       $GLOBALS['IP'] = $baseDir;
+                       $teardown = new Wikimedia\ScopedCallback( function () use ( $oldIP ) {
+                               $GLOBALS['IP'] = $oldIP;
+                       } );
+               }
+
+               $this->assertEquals(
+                       $expected,
+                       ResourceLoaderSkinModule::getLogo( new HashConfig( $config ) )
+               );
+       }
+
+       public function provideGetLogo() {
+               return [
+                       'simple' => [
+                               'config' => [
+                                       'ResourceBasePath' => '/w',
+                                       'Logo' => '/img/default.png',
+                                       'LogoHD' => false,
+                               ],
+                               'expected' => '/img/default.png',
+                       ],
+                       'default and 2x' => [
+                               'config' => [
+                                       'ResourceBasePath' => '/w',
+                                       'Logo' => '/img/default.png',
+                                       'LogoHD' => [
+                                               '2x' => '/img/two-x.png',
+                                       ],
+                               ],
+                               'expected' => [
+                                       '1x' => '/img/default.png',
+                                       '2x' => '/img/two-x.png',
+                               ],
+                       ],
+                       'default and all HiDPIs' => [
+                               'config' => [
+                                       'ResourceBasePath' => '/w',
+                                       'Logo' => '/img/default.png',
+                                       'LogoHD' => [
+                                               '1.5x' => '/img/one-point-five.png',
+                                               '2x' => '/img/two-x.png',
+                                       ],
+                               ],
+                               'expected' => [
+                                       '1x' => '/img/default.png',
+                                       '1.5x' => '/img/one-point-five.png',
+                                       '2x' => '/img/two-x.png',
+                               ],
+                       ],
+                       'default and SVG' => [
+                               'config' => [
+                                       'ResourceBasePath' => '/w',
+                                       'Logo' => '/img/default.png',
+                                       'LogoHD' => [
+                                               'svg' => '/img/vector.svg',
+                                       ],
+                               ],
+                               'expected' => [
+                                       '1x' => '/img/default.png',
+                                       'svg' => '/img/vector.svg',
+                               ],
+                       ],
+                       'everything' => [
+                               'config' => [
+                                       'ResourceBasePath' => '/w',
+                                       'Logo' => '/img/default.png',
+                                       'LogoHD' => [
+                                               '1.5x' => '/img/one-point-five.png',
+                                               '2x' => '/img/two-x.png',
+                                               'svg' => '/img/vector.svg',
+                                       ],
+                               ],
+                               'expected' => [
+                                       '1x' => '/img/default.png',
+                                       'svg' => '/img/vector.svg',
+                               ],
+                       ],
+                       'versioned url' => [
+                               'config' => [
+                                       'ResourceBasePath' => '/w',
+                                       'Logo' => '/w/test.jpg',
+                                       'LogoHD' => false,
+                                       'UploadPath' => '/w/images',
+                               ],
+                               'expected' => '/w/test.jpg?edcf2',
+                               'baseDir' => dirname( dirname( __DIR__ ) ) . '/data/media',
+                       ],
+               ];
+       }
 }