mirror of
https://github.com/octfx/mediawiki-extensions-TemplateStylesExtender.git
synced 2024-11-13 18:36:55 +00:00
Merge branch 'release/v1.2.1'
This commit is contained in:
commit
aac3f78bed
|
@ -33,6 +33,8 @@ Enables or disables css variable support.
|
|||
Default: `false`
|
||||
Allows users with `editinterface` permissions to unscope css by setting a `wrapclass` attribute.
|
||||
|
||||
**Note**: This is potentially expensive, as each templatestyles tag with `wrapclass` set, will attempt to look up the user of the current page revision, and check if this user has the permission to activate css un-scoping.
|
||||
|
||||
Example:
|
||||
`<templatestyles src="Foo/style.css" wrapclass="mediawiki" />` results in the css being scoped to `.mediawiki` instead of `.mw-parser-output`.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "octfx/template-styles-extender",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"type": "mediawiki-extension",
|
||||
"description": "Extends TemplateStyles with new CSS properties",
|
||||
"homepage": "http://www.mediawiki.org/wiki/Extension:TemplateStylesExtender",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "TemplateStylesExtender",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"author": [
|
||||
"[https://www.mediawiki.org/wiki/User:Octfx Octfx]"
|
||||
],
|
||||
|
@ -48,18 +48,13 @@
|
|||
},
|
||||
"HookHandlers": {
|
||||
"MainHooks": {
|
||||
"class": "MediaWiki\\Extension\\TemplateStylesExtender\\Hooks\\MainHooks",
|
||||
"services": [
|
||||
"PermissionManager",
|
||||
"UserFactory"
|
||||
]
|
||||
"class": "MediaWiki\\Extension\\TemplateStylesExtender\\Hooks\\MainHooks"
|
||||
}
|
||||
},
|
||||
"Hooks": {
|
||||
"TemplateStylesPropertySanitizer": "MediaWiki\\Extension\\TemplateStylesExtender\\Hooks\\PropertySanitizerHook::onSanitize",
|
||||
"TemplateStylesStylesheetSanitizer": "MediaWiki\\Extension\\TemplateStylesExtender\\Hooks\\StylesheetSanitizerHook::onSanitize",
|
||||
"ParserFirstCallInit": "MainHooks",
|
||||
"EditPage::attemptSave": "MainHooks"
|
||||
"ParserFirstCallInit": "MainHooks"
|
||||
},
|
||||
"manifest_version": 2
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ declare( strict_types=1 );
|
|||
namespace MediaWiki\Extension\TemplateStylesExtender;
|
||||
|
||||
use Wikimedia\CSS\Grammar\Alternative;
|
||||
use Wikimedia\CSS\Grammar\KeywordMatcher;
|
||||
use Wikimedia\CSS\Grammar\MatcherFactory;
|
||||
use Wikimedia\CSS\Sanitizer\FontFaceAtRuleSanitizer;
|
||||
|
||||
|
@ -32,15 +33,15 @@ class FontFaceAtRuleSanitizerExtender extends FontFaceAtRuleSanitizer {
|
|||
public function __construct( MatcherFactory $matcherFactory ) {
|
||||
parent::__construct( $matcherFactory );
|
||||
|
||||
$matcher = new Alternative( [ new KeywordMatcher( 'normal' ), $matcherFactory->percentage() ] );
|
||||
|
||||
// Only allow the font-family if it begins with "TemplateStyles"
|
||||
$this->propertySanitizer->setKnownProperties( [
|
||||
'ascent-override' => new Alternative( [ $auto, $matcherFactory->lengthPercentage() ] ),
|
||||
'descent-override' => new Alternative( [ $auto, $matcherFactory->lengthPercentage() ] ),
|
||||
'font-display' => new Alternative( [
|
||||
new KeywordMatcher( [ 'auto', 'block', 'swap', 'fallback', 'optional' ] )
|
||||
] ),
|
||||
'line-gap-override' => new Alternative( [ $auto, $matcherFactory->lengthPercentage() ] ),
|
||||
'size-adjust' => new Alternative( [ $auto, $matcherFactory->lengthPercentage() ] )
|
||||
] );
|
||||
'ascent-override' => $matcher,
|
||||
'descent-override' => $matcher,
|
||||
'font-display' => new KeywordMatcher( [ 'auto', 'block', 'swap', 'fallback', 'optional' ] ),
|
||||
'line-gap-override' => $matcher,
|
||||
'size-adjust' => $matcher,
|
||||
] + $this->propertySanitizer->getKnownProperties() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,42 +2,19 @@
|
|||
|
||||
namespace MediaWiki\Extension\TemplateStylesExtender\Hooks;
|
||||
|
||||
use MediaWiki\EditPage\EditPage;
|
||||
use MediaWiki\Extension\TemplateStyles\Hooks;
|
||||
use MediaWiki\Extension\TemplateStylesExtender\TemplateStylesExtender;
|
||||
use MediaWiki\Hook\EditPage__attemptSaveHook;
|
||||
use MediaWiki\Hook\ParserFirstCallInitHook;
|
||||
use MediaWiki\Permissions\PermissionManager;
|
||||
use MediaWiki\Revision\SlotRecord;
|
||||
use MediaWiki\User\UserFactory;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use MediaWiki\User\UserIdentity;
|
||||
use MWException;
|
||||
use Parser;
|
||||
use PermissionsError;
|
||||
use PPFrame;
|
||||
|
||||
/**
|
||||
* phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
|
||||
*/
|
||||
class MainHooks implements ParserFirstCallInitHook, EditPage__attemptSaveHook {
|
||||
|
||||
/**
|
||||
* @var PermissionManager
|
||||
*/
|
||||
private PermissionManager $manager;
|
||||
|
||||
/**
|
||||
* @var UserFactory
|
||||
*/
|
||||
private UserFactory $factory;
|
||||
|
||||
/**
|
||||
* @param PermissionManager $manager
|
||||
* @param UserFactory $factory
|
||||
*/
|
||||
public function __construct( PermissionManager $manager, UserFactory $factory ) {
|
||||
$this->manager = $manager;
|
||||
$this->factory = $factory;
|
||||
}
|
||||
class MainHooks implements ParserFirstCallInitHook {
|
||||
|
||||
/**
|
||||
* @param Parser $parser
|
||||
|
@ -51,6 +28,9 @@ class MainHooks implements ParserFirstCallInitHook, EditPage__attemptSaveHook {
|
|||
* This is a wrapper for <templatestyles> tags,
|
||||
* that allows unscoping of css for users with 'editinterface' permissions
|
||||
*
|
||||
* Note this is a potentially expensive operation, as a lookup for the user of the current revision is done.
|
||||
* The unscoping will only happen, if the editor of the current revision has the rights to do so
|
||||
*
|
||||
* @param string $text
|
||||
* @param string[] $params
|
||||
* @param Parser $parser
|
||||
|
@ -59,17 +39,40 @@ class MainHooks implements ParserFirstCallInitHook, EditPage__attemptSaveHook {
|
|||
* @see Hooks::handleTag()
|
||||
*/
|
||||
public static function handleTag( $text, $params, $parser, $frame ): string {
|
||||
$getOutput = static fn() => Hooks::handleTag( $text, $params, $parser, $frame );
|
||||
|
||||
if (
|
||||
!isset( $params['wrapclass'] ) ||
|
||||
$parser->getOptions() === null ||
|
||||
!TemplateStylesExtender::getConfigValue( 'TemplateStylesExtenderEnableUnscopingSupport' )
|
||||
) {
|
||||
return Hooks::handleTag( $text, $params, $parser, $frame );
|
||||
return $getOutput();
|
||||
}
|
||||
|
||||
// 'wrapclass' option is set...
|
||||
|
||||
/** @var \ParserOptions $options - Fix typehint */
|
||||
$options = $parser->getOptions();
|
||||
$wrapClass = $options->getWrapOutputClass();
|
||||
|
||||
if ( isset( $params['wrapclass'] ) ) {
|
||||
$permission = TemplateStylesExtender::getConfigValue( 'TemplateStylesExtenderUnscopingPermission' );
|
||||
|
||||
$rev = MediaWikiServices::getInstance()->getRevisionLookup()->getRevisionByTitle( $frame->getTitle() );
|
||||
|
||||
if ( $rev === null || $rev->getUser() === null ) {
|
||||
return $getOutput();
|
||||
}
|
||||
|
||||
/** @var UserIdentity $user - Fix typehint */
|
||||
$user = $rev->getUser();
|
||||
$user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $user );
|
||||
$permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
|
||||
|
||||
$userCan = $permissionManager->userHasRight( $user, $permission )
|
||||
|| $permissionManager->userCan( $permission, $user, $frame->getTitle() );
|
||||
|
||||
// If the editor of the last revision is allowed to change the wrapclass, set it
|
||||
if ( $userCan ) {
|
||||
$options->setOption( 'wrapclass', $params['wrapclass'] );
|
||||
}
|
||||
|
||||
|
@ -78,40 +81,4 @@ class MainHooks implements ParserFirstCallInitHook, EditPage__attemptSaveHook {
|
|||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if 'wrapclass' was used in the page, if so only users with 'editinterface' permissions may save the page
|
||||
*
|
||||
* @param EditPage $editpage_Obj
|
||||
* @return true
|
||||
* @throws PermissionsError
|
||||
*/
|
||||
public function onEditPage__attemptSave( $editpage_Obj ): bool {
|
||||
$revision = $editpage_Obj->getExpectedParentRevision();
|
||||
|
||||
if (
|
||||
$revision === null ||
|
||||
!TemplateStylesExtender::getConfigValue( 'TemplateStylesExtenderEnableUnscopingSupport' )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$content = $revision->getContent( SlotRecord::MAIN );
|
||||
if ( $content === null ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$permission = TemplateStylesExtender::getConfigValue( 'TemplateStylesExtenderUnscopingPermission' );
|
||||
|
||||
$user = $this->factory->newFromUserIdentity( $editpage_Obj->getContext()->getUser() );
|
||||
|
||||
$userCan = $this->manager->userHasRight( $user, $permission ) ||
|
||||
$this->manager->userCan( $permission, $user, $editpage_Obj->getTitle() );
|
||||
|
||||
if ( strpos( $content->getText(), 'wrapclass' ) !== false && !$userCan ) {
|
||||
throw new PermissionsError( $permission, [ 'templatestylesextender-unscope-no-permisson' ] );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,8 @@ class StylesheetSanitizerHook {
|
|||
}
|
||||
|
||||
$newRules['@font-face'] = new FontFaceAtRuleSanitizerExtender( $matcherFactory );
|
||||
$newRules['@font-face']->setRuleSanitizers( $newRules );
|
||||
|
||||
$sanitizer->setRuleSanitizers( $newRules );
|
||||
|
||||
$extended = new TemplateStylesExtender();
|
||||
|
||||
|
@ -70,6 +71,7 @@ class StylesheetSanitizerHook {
|
|||
$extended->addAspectRatio( $extender, $matcherFactory );
|
||||
$extended->addInlineBlockMarginPaddingProperties( $extender, $matcherFactory );
|
||||
$extended->addInsetProperties( $extender, $matcherFactory );
|
||||
$extended->addBackdropFilter( $extender );
|
||||
|
||||
$propertySanitizer->setKnownProperties( $extender->getKnownProperties() );
|
||||
}
|
||||
|
|
|
@ -23,13 +23,17 @@ namespace MediaWiki\Extension\TemplateStylesExtender;
|
|||
|
||||
use MediaWiki\Extension\TemplateStylesExtender\Matcher\VarNameMatcher;
|
||||
use Wikimedia\CSS\Grammar\Alternative;
|
||||
use Wikimedia\CSS\Grammar\BlockMatcher;
|
||||
use Wikimedia\CSS\Grammar\FunctionMatcher;
|
||||
use Wikimedia\CSS\Grammar\Juxtaposition;
|
||||
use Wikimedia\CSS\Grammar\KeywordMatcher;
|
||||
use Wikimedia\CSS\Grammar\MatcherFactory;
|
||||
use Wikimedia\CSS\Grammar\Quantifier;
|
||||
use Wikimedia\CSS\Grammar\TokenMatcher;
|
||||
use Wikimedia\CSS\Grammar\UnorderedGroup;
|
||||
use Wikimedia\CSS\Objects\CSSObject;
|
||||
use Wikimedia\CSS\Objects\Declaration;
|
||||
use Wikimedia\CSS\Objects\Token;
|
||||
use Wikimedia\CSS\Sanitizer\StylePropertySanitizer;
|
||||
|
||||
class StylePropertySanitizerExtender extends StylePropertySanitizer {
|
||||
|
@ -39,6 +43,7 @@ class StylePropertySanitizerExtender extends StylePropertySanitizer {
|
|||
private static $extendedCssSizingAdditions = false;
|
||||
private static $extendedCssSizing3 = false;
|
||||
private static $extendedCss1Masking = false;
|
||||
private static $extendedCss1Grid = false;
|
||||
|
||||
/**
|
||||
* @param MatcherFactory $matcherFactory
|
||||
|
@ -203,6 +208,101 @@ class StylePropertySanitizerExtender extends StylePropertySanitizer {
|
|||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* Allow variables in grid-template-columns
|
||||
*/
|
||||
protected function cssGrid1( MatcherFactory $matcherFactory ) {
|
||||
// @codeCoverageIgnoreStart
|
||||
if ( self::$extendedCss1Grid && isset( $this->cache[__METHOD__] ) ) {
|
||||
return $this->cache[__METHOD__];
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$var = new FunctionMatcher( 'var', new VarNameMatcher() );
|
||||
|
||||
$props = [];
|
||||
$comma = $matcherFactory->comma();
|
||||
$customIdent = $matcherFactory->customIdent( [ 'span' ] );
|
||||
$lineNamesO = Quantifier::optional( new BlockMatcher(
|
||||
Token::T_LEFT_BRACKET, Quantifier::star( $customIdent )
|
||||
) );
|
||||
$trackBreadth = new Alternative( [
|
||||
$matcherFactory->lengthPercentage(),
|
||||
new TokenMatcher( Token::T_DIMENSION, static function ( Token $t ) {
|
||||
return $t->value() >= 0 && !strcasecmp( $t->unit(), 'fr' );
|
||||
} ),
|
||||
new KeywordMatcher( [ 'min-content', 'max-content', 'auto' ] ),
|
||||
$var
|
||||
] );
|
||||
$inflexibleBreadth = new Alternative( [
|
||||
$matcherFactory->lengthPercentage(),
|
||||
new KeywordMatcher( [ 'min-content', 'max-content', 'auto' ] ),
|
||||
$var
|
||||
] );
|
||||
$fixedBreadth = $matcherFactory->lengthPercentage();
|
||||
$trackSize = new Alternative( [
|
||||
$trackBreadth,
|
||||
new FunctionMatcher( 'minmax',
|
||||
new Juxtaposition( [ $inflexibleBreadth, $trackBreadth ], true )
|
||||
),
|
||||
new FunctionMatcher( 'fit-content', $matcherFactory->lengthPercentage() ),
|
||||
$var
|
||||
] );
|
||||
$fixedSize = new Alternative( [
|
||||
$fixedBreadth,
|
||||
new FunctionMatcher( 'minmax', new Juxtaposition( [ $fixedBreadth, $trackBreadth ], true ) ),
|
||||
new FunctionMatcher( 'minmax',
|
||||
new Juxtaposition( [ $inflexibleBreadth, $fixedBreadth ], true )
|
||||
),
|
||||
$var
|
||||
] );
|
||||
$trackRepeat = new FunctionMatcher( 'repeat', new Juxtaposition( [
|
||||
new Alternative( [ $matcherFactory->integer(), $var ] ),
|
||||
$comma,
|
||||
Quantifier::plus( new Juxtaposition( [ $lineNamesO, $trackSize ] ) ),
|
||||
$lineNamesO
|
||||
] ) );
|
||||
$autoRepeat = new FunctionMatcher( 'repeat', new Juxtaposition( [
|
||||
new Alternative( [ new KeywordMatcher( [ 'auto-fill', 'auto-fit' ] ), $var ] ),
|
||||
$comma,
|
||||
Quantifier::plus( new Juxtaposition( [ $lineNamesO, $fixedSize ] ) ),
|
||||
$lineNamesO
|
||||
] ) );
|
||||
$fixedRepeat = new FunctionMatcher( 'repeat', new Juxtaposition( [
|
||||
$matcherFactory->integer(),
|
||||
$comma,
|
||||
Quantifier::plus( new Juxtaposition( [ $lineNamesO, $fixedSize ] ) ),
|
||||
$lineNamesO
|
||||
] ) );
|
||||
$trackList = new Juxtaposition( [
|
||||
Quantifier::plus( new Juxtaposition( [
|
||||
$lineNamesO, new Alternative( [ $trackSize, $trackRepeat ] )
|
||||
] ) ),
|
||||
$lineNamesO
|
||||
] );
|
||||
$autoTrackList = new Juxtaposition( [
|
||||
Quantifier::star( new Juxtaposition( [
|
||||
$lineNamesO, new Alternative( [ $fixedSize, $fixedRepeat ] )
|
||||
] ) ),
|
||||
$lineNamesO,
|
||||
$autoRepeat,
|
||||
Quantifier::star( new Juxtaposition( [
|
||||
$lineNamesO, new Alternative( [ $fixedSize, $fixedRepeat ] )
|
||||
] ) ),
|
||||
$lineNamesO,
|
||||
] );
|
||||
|
||||
$props['grid-template-columns'] = new Alternative( [
|
||||
new KeywordMatcher( 'none' ), $trackList, $autoTrackList
|
||||
] );
|
||||
$props['grid-template-rows'] = $props['grid-template-columns'];
|
||||
|
||||
$this->cache[__METHOD__] = $props;
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CSSObject $object
|
||||
* @return CSSObject|Declaration|null
|
||||
|
|
|
@ -330,6 +330,23 @@ class TemplateStylesExtender {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the backdrop-filter matcher
|
||||
*
|
||||
* @param StylePropertySanitizer $propertySanitizer
|
||||
*/
|
||||
public function addBackdropFilter( StylePropertySanitizer $propertySanitizer ): void {
|
||||
try {
|
||||
$filter = $propertySanitizer->getKnownProperties()['filter'];
|
||||
|
||||
$propertySanitizer->addKnownProperties( [
|
||||
'backdrop-filter' => Quantifier::plus( $filter ),
|
||||
] );
|
||||
} catch ( InvalidArgumentException $e ) {
|
||||
// Fail silently
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a config value for a given key from the main config
|
||||
* Returns null on if an ConfigException was thrown
|
||||
|
|
Loading…
Reference in a new issue