mediawiki-extensions-Templa.../includes/StylePropertySanitizerExten...

219 行
5.9 KiB
PHP

<?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.
*
* @file
*/
declare( strict_types=1 );
namespace MediaWiki\Extension\TemplateStylesExtender;
use MediaWiki\Extension\TemplateStylesExtender\Matcher\VarNameMatcher;
use Wikimedia\CSS\Grammar\Alternative;
use Wikimedia\CSS\Grammar\FunctionMatcher;
use Wikimedia\CSS\Grammar\KeywordMatcher;
use Wikimedia\CSS\Grammar\MatcherFactory;
use Wikimedia\CSS\Grammar\Quantifier;
use Wikimedia\CSS\Grammar\UnorderedGroup;
use Wikimedia\CSS\Objects\CSSObject;
use Wikimedia\CSS\Objects\Declaration;
use Wikimedia\CSS\Sanitizer\StylePropertySanitizer;
class StylePropertySanitizerExtender extends StylePropertySanitizer {
private static $extendedCssText3 = false;
private static $extendedCssBorderBackground = false;
private static $extendedCssSizingAdditions = false;
private static $extendedCssSizing3 = false;
private static $extendedCss1Masking = false;
/**
* @param MatcherFactory $matcherFactory
*/
public function __construct( MatcherFactory $matcherFactory ) {
parent::__construct( new MatcherFactoryExtender() );
}
/**
* @inheritDoc
* Allow overflow-wrap: anywhere
*
* T255343
*/
protected function cssText3( MatcherFactory $matcherFactory ) {
// @codeCoverageIgnoreStart
if ( self::$extendedCssText3 && isset( $this->cache[__METHOD__] ) ) {
return $this->cache[__METHOD__];
}
// @codeCoverageIgnoreEnd
$props = parent::cssText3( $matcherFactory );
$props['overflow-wrap'] = new Alternative( [
new KeywordMatcher( [ 'normal' ] ),
UnorderedGroup::someOf( [
new KeywordMatcher( [ 'break-word' ] ),
new KeywordMatcher( [ 'break-spaces' ] ),
new KeywordMatcher( [ 'anywhere' ] ),
] )
] );
$this->cache[__METHOD__] = $props;
self::$extendedCssText3 = true;
return $props;
}
/**
* @inheritDoc
* Allow rgba syntax like #aaaaaaaa
*
* T265675
*/
protected function cssBorderBackground3( MatcherFactory $matcherFactory ) {
// @codeCoverageIgnoreStart
if ( self::$extendedCssBorderBackground && isset( $this->cache[__METHOD__] ) ) {
return $this->cache[__METHOD__];
}
// @codeCoverageIgnoreEnd
$props = parent::cssBorderBackground3( $matcherFactory );
$props['border'] = UnorderedGroup::someOf( [
new KeywordMatcher( [
'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'
] ),
new Alternative( [
new KeywordMatcher( [ 'thin', 'medium', 'thick' ] ), $matcherFactory->length(),
] ),
new Alternative( [
$matcherFactory->color(),
new FunctionMatcher( 'var', new VarNameMatcher() ),
] )
] );
$props['box-shadow'] = new Alternative( [
new KeywordMatcher( 'none' ),
Quantifier::hash( UnorderedGroup::allOf( [
Quantifier::optional( new KeywordMatcher( 'inset' ) ),
Quantifier::count( $matcherFactory->length(), 2, 4 ),
Quantifier::optional( new Alternative( [
$matcherFactory->color(),
new FunctionMatcher( 'var', new VarNameMatcher() ),
] ) ),
] ) )
] );
$this->cache[__METHOD__] = $props;
self::$extendedCssBorderBackground = true;
return $props;
}
/**
* @inheritDoc
*
* Partly implement clamp
*/
protected function getSizingAdditions( MatcherFactory $matcherFactory ) {
// @codeCoverageIgnoreStart
if ( self::$extendedCssSizingAdditions && isset( $this->cache[__METHOD__] ) ) {
return $this->cache[__METHOD__];
}
// @codeCoverageIgnoreEnd
$props = parent::getSizingAdditions( $matcherFactory );
$props[] = new FunctionMatcher( 'clamp', Quantifier::hash( new Alternative( [
$matcherFactory->length(),
$matcherFactory->lengthPercentage(),
$matcherFactory->frequency(),
$matcherFactory->angle(),
$matcherFactory->anglePercentage(),
$matcherFactory->time(),
$matcherFactory->number(),
$matcherFactory->integer(),
] ), 3, 3 ) );
$this->cache[__METHOD__] = $props;
self::$extendedCssSizingAdditions = true;
return $this->cache[__METHOD__];
}
/**
* @inheritDoc
* Allow width: fit-content
*
* T271958
*/
protected function cssSizing3( MatcherFactory $matcherFactory ) {
// @codeCoverageIgnoreStart
if ( self::$extendedCssSizing3 && isset( $this->cache[__METHOD__] ) ) {
return $this->cache[__METHOD__];
}
// @codeCoverageIgnoreEnd
$props = parent::cssSizing3( $matcherFactory );
$props['width'] = new Alternative( [
$props['width'],
new KeywordMatcher( 'fit-content' )
] );
$this->cache[__METHOD__] = $props;
self::$extendedCssSizing3 = true;
return $props;
}
/**
* @inheritDoc
*
* Add webkit prefix for mask-image
*/
protected function cssMasking1( MatcherFactory $matcherFactory ) {
// @codeCoverageIgnoreStart
if ( self::$extendedCss1Masking && isset( $this->cache[__METHOD__] ) ) {
return $this->cache[__METHOD__];
}
// @codeCoverageIgnoreEnd
$props = parent::cssMasking1( $matcherFactory );
$props['-webkit-mask-image'] = $props['mask-image'];
$this->cache[__METHOD__] = $props;
self::$extendedCss1Masking = true;
return $props;
}
/**
* @param CSSObject $object
* @return CSSObject|Declaration|null
*/
protected function doSanitize( CSSObject $object ) {
if ( strpos( $object->getName(), '--' ) !== 0 ) {
return parent::doSanitize( $object );
}
$this->clearSanitizationErrors();
return $object;
}
}