[dev] add skin version query parameter override

As described in the readme but not implemented until now, this patch
enables the skin version to be specified as a URL query parameter. This
is useful for testing both skin versions during development and on wiki,
as well as enabling sharing URLs with a specific skin (Vector) and skin
version (1 or 2).

Obtaining the actual skin version requires tying together three input
sources, WebRequest, User, and Config. It seems simple but it'd be easy
to botch. For this reason, a helper class to correctly interrogate them
and tests are provided.

Bug: T244481
Change-Id: I52d80942b4270c008d4e45050589ed9220255a50
This commit is contained in:
Stephen Niedzielski 2020-03-31 16:34:50 -06:00
parent b80ad85bb1
commit d1072d0fdf
6 changed files with 271 additions and 17 deletions

View File

@ -28,7 +28,8 @@ URL query parameters
--------------------
- `useskinversion`: Like `useskin` but for overriding the Vector skin version
user preference and configuration.
user preference and configuration. E.g.,
http://localhost:8181?useskin=vector&useskinversion=2.
Skin preferences
----------------

View File

@ -72,6 +72,13 @@ final class Constants {
*/
public const REQUIREMENT_FULLY_INITIALISED = 'FullyInitialised';
// These are used for query parameters.
/**
* Override the skin version user preference and site Config. See readme.
* @var string
*/
public const QUERY_PARAM_SKIN_VERSION = 'useskinversion';
/**
* This class is for namespacing constants only. Forbid construction.
* @throws FatalError

View File

@ -21,7 +21,7 @@
* @file
* @ingroup Skins
*/
use Vector\Constants;
use Vector\SkinVersionLookup;
/**
* Skin subclass for Vector
@ -36,6 +36,21 @@ class SkinVector extends SkinTemplate {
private $responsiveMode = false;
/**
* @var SkinVersionLookup
*/
private $skinVersionLookup;
/**
* @inheritDoc
*/
public function __construct( $skinname = null ) {
parent::__construct( $skinname );
$this->skinVersionLookup =
new SkinVersionLookup( $this->getRequest(), $this->getUser(), $this->getConfig() );
}
/**
* Enables the responsive mode
*/
@ -67,7 +82,8 @@ class SkinVector extends SkinTemplate {
public function getDefaultModules() {
$modules = parent::getDefaultModules();
// add vector skin styles and vector module
$module = $this->isLegacy() ? 'skins.vector.styles.legacy' : 'skins.vector.styles';
$module = $this->skinVersionLookup->isLegacy()
? 'skins.vector.styles.legacy' : 'skins.vector.styles';
$modules['styles']['skin'][] = $module;
$modules['core'][] = 'skins.vector.js';
@ -85,9 +101,7 @@ class SkinVector extends SkinTemplate {
*/
protected function setupTemplate( $classname ) {
$tp = new TemplateParser( __DIR__ . '/templates' );
$vectorTemplate = new VectorTemplate( $this->getConfig(), $tp, $this->isLegacy() );
return $vectorTemplate;
return new VectorTemplate( $this->getConfig(), $tp, $this->skinVersionLookup->isLegacy() );
}
/**
@ -98,15 +112,4 @@ class SkinVector extends SkinTemplate {
public function shouldPreloadLogo() {
return true;
}
/**
* Whether or not the legacy skin is being used.
*
* @return bool
*/
private function isLegacy() {
// Note: This will be replaced with FeatureManager when it is ready.
return $this->getUser()->getOption( Constants::PREF_KEY_SKIN_VERSION )
=== Constants::SKIN_VERSION_LEGACY;
}
}

View File

@ -0,0 +1,109 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @since 1.35
*/
namespace Vector;
use Config;
use User;
use WebRequest;
/**
* Given initial dependencies, retrieve the current skin version. This class does no parsing, just
* the lookup.
*
* Skin version is evaluated in the following order:
*
* - useskinversion URL query parameter override. See readme.
*
* - User preference. The User object for new and existing accounts are updated by hook according to
* VectorDefaultSkinVersionForNewAccounts and VectorDefaultSkinVersionForExistingAccounts. See
* Hooks and skin.json.
*
* If the skin version is evaluated prior to User preference hook invocations, an incorrect
* version may be returned as only query parameter and site configuration will be known.
*
* - Site configuration default. The default is controlled by VectorDefaultSkinVersion. This is used
* for anonymous users and as a fallback configuration. See skin.json.
*
* @unstable
*
* @package Vector
* @internal
*/
final class SkinVersionLookup {
/**
* @var WebRequest
*/
private $request;
/**
* @var User
*/
private $user;
/**
* @var Config
*/
private $config;
/**
* This constructor accepts all dependencies needed to obtain the skin version. The dependencies
* are lazily evaluated, not cached, meaning they always return the current results.
*
* @param WebRequest $request
* @param User $user
* @param Config $config
*/
public function __construct( WebRequest $request, User $user, Config $config ) {
$this->request = $request;
$this->user = $user;
$this->config = $config;
}
/**
* Whether or not the legacy skin is being used.
*
* @return bool
* @throws \ConfigException
*/
public function isLegacy() {
return $this->getVersion() === Constants::SKIN_VERSION_LEGACY;
}
/**
* The skin version as a string. E.g., `Constants::SKIN_VERSION_LEGACY`,
* `Constants::SKIN_VERSION_LATEST`, or maybe 'beta'. Note: it's likely someone will put arbitrary
* strings in the query parameter which means this function returns those strings as is.
*
* @return string
* @throws \ConfigException
*/
public function getVersion() {
// Obtain the skin version from the `useskinversion` URL query parameter override, the user
// preference, or the configured default.
return (string)$this->request->getVal(
Constants::QUERY_PARAM_SKIN_VERSION,
$this->user->getOption(
Constants::PREF_KEY_SKIN_VERSION,
$this->config->get( Constants::CONFIG_KEY_DEFAULT_SKIN_VERSION )
)
);
}
}

View File

@ -24,6 +24,7 @@
"AutoloadClasses": {
"Vector\\Constants": "includes/Constants.php",
"Vector\\Hooks": "includes/Hooks.php",
"Vector\\SkinVersionLookup": "includes/SkinVersionLookup.php",
"SkinVector": "includes/SkinVector.php",
"VectorTemplate": "includes/VectorTemplate.php"
},

View File

@ -0,0 +1,133 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @since 1.35
*/
use Vector\SkinVersionLookup;
/**
* @group Vector
* @coversDefaultClass \Vector\SkinVersionLookup
*/
class SkinVersionLookupTest extends \MediaWikiTestCase {
/**
* @covers ::isLegacy
* @covers ::getVersion
*/
public function testRequest() {
$request = $this->getMockBuilder( \WebRequest::class )->getMock();
$request
->expects( $this->exactly( 2 ) )
->method( 'getVal' )
->with( $this->anything(), $this->equalTo( '1' ) )
->willReturn( 'beta' );
$user = $this->createMock( \User::class );
$user
->expects( $this->exactly( 2 ) )
->method( 'getOption' )
->with( $this->anything(), $this->equalTo( '2' ) )
->willReturn( '1' );
$config = new HashConfig( [ 'VectorDefaultSkinVersion' => '2' ] );
$skinVersionLookup = new SkinVersionLookup( $request, $user, $config );
$this->assertSame(
$skinVersionLookup->getVersion(),
'beta',
'Query parameter is the first priority.'
);
$this->assertSame(
$skinVersionLookup->isLegacy(),
false,
'Version is non-legacy.'
);
}
/**
* @covers ::getVersion
* @covers ::isLegacy
*/
public function testUserPreference() {
$request = $this->getMockBuilder( \WebRequest::class )->getMock();
$request
->expects( $this->exactly( 2 ) )
->method( 'getVal' )
->with( $this->anything(), $this->equalTo( '1' ) )
->willReturn( '1' );
$user = $this->createMock( \User::class );
$user
->expects( $this->exactly( 2 ) )
->method( 'getOption' )
->with( $this->anything(), $this->equalTo( '2' ) )
->willReturn( '1' );
$config = new HashConfig( [ 'VectorDefaultSkinVersion' => '2' ] );
$skinVersionLookup = new SkinVersionLookup( $request, $user, $config );
$this->assertSame(
$skinVersionLookup->getVersion(),
'1',
'User preference is the second priority.'
);
$this->assertSame(
$skinVersionLookup->isLegacy(),
true,
'Version is legacy.'
);
}
/**
* @covers ::getVersion
* @covers ::isLegacy
*/
public function testConfig() {
$request = $this->getMockBuilder( \WebRequest::class )->getMock();
$request
->expects( $this->exactly( 2 ) )
->method( 'getVal' )
->with( $this->anything(), $this->equalTo( '2' ) )
->willReturn( '2' );
$user = $this->createMock( \User::class );
$user
->expects( $this->exactly( 2 ) )
->method( 'getOption' )
->with( $this->anything(), $this->equalTo( '2' ) )
->willReturn( '2' );
$config = new HashConfig( [ 'VectorDefaultSkinVersion' => '2' ] );
$skinVersionLookup = new SkinVersionLookup( $request, $user, $config );
$this->assertSame(
$skinVersionLookup->getVersion(),
'2',
'Config is the third priority.'
);
$this->assertSame(
$skinVersionLookup->isLegacy(),
false,
'Version is non-legacy.'
);
}
}