mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/TemplateStyles
synced 2024-11-28 10:10:42 +00:00
General cleanup of CSSRenderer
* Add phpdoc comments * Rename some variables to be a bit more clear for new readers * Break up render() to make things more readable and reduce cyclomatic complexity Change-Id: Iceeb1f6eb09b61efe6b81f359d28741f54fe88ad
This commit is contained in:
parent
1a6879c457
commit
b39d76be08
132
CSSRenderer.php
132
CSSRenderer.php
|
@ -1,6 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* @ingroup Extensions
|
* @ingroup Extensions
|
||||||
|
@ -13,85 +11,131 @@
|
||||||
*/
|
*/
|
||||||
class CSSRenderer {
|
class CSSRenderer {
|
||||||
|
|
||||||
private $bymedia;
|
/** @var array $byMedia */
|
||||||
|
private $byMedia;
|
||||||
|
|
||||||
function __construct() {
|
function __construct() {
|
||||||
$this->bymedia = [];
|
$this->byMedia = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds (and merge) a parsed CSS tree to the render list.
|
* Adds (and merge) a parsed CSS tree to the render list.
|
||||||
*
|
*
|
||||||
* @param array $rules The parsed tree as created by CSSParser::rules()
|
* @param array $rules The parsed tree as created by CSSParser::rules()
|
||||||
* @param string $media Forcibly specified @media block selector. Normally unspecified
|
* @param string $media Forcibly specified @media block selector.
|
||||||
* and defaults to the empty string.
|
|
||||||
*/
|
*/
|
||||||
function add( $rules, $media = '' ) {
|
function add( $rules, $media = '' ) {
|
||||||
if ( !array_key_exists( $media, $this->bymedia ) ) {
|
if ( !array_key_exists( $media, $this->byMedia ) ) {
|
||||||
$this->bymedia[$media] = [];
|
$this->byMedia[$media] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ( $rules as $at ) {
|
foreach ( $rules as $rule ) {
|
||||||
switch ( strtolower( $at['name'] ) ) {
|
switch ( strtolower( $rule['name'] ) ) {
|
||||||
case '@media':
|
case '@media':
|
||||||
if ( $media == '' ) {
|
if ( $media == '' ) {
|
||||||
$this->add( $at['rules'], "@media ".$at['text'] );
|
$this->add(
|
||||||
|
$rule['rules'], "@media {$rule['text']}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '':
|
case '':
|
||||||
$this->bymedia[$media] = array_merge( $this->bymedia[$media], $at['rules'] );
|
$this->byMedia[$media] = array_merge(
|
||||||
|
$this->byMedia[$media], $rule['rules']
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the collected CSS trees into a string suitable for inclusion
|
* Render the collected CSS trees into a string suitable for inclusion
|
||||||
* in a <style> tag.
|
* in a <style> tag.
|
||||||
*
|
*
|
||||||
|
* @param array $functionWhitelist List of functions that are allowed
|
||||||
|
* @param array $propertyBlacklist List of properties that not allowed
|
||||||
* @return string Rendered CSS
|
* @return string Rendered CSS
|
||||||
*/
|
*/
|
||||||
function render( $functionWhitelist = [], $propertyBlacklist = [] ) {
|
function render(
|
||||||
|
array $functionWhitelist = [],
|
||||||
|
array $propertyBlacklist = []
|
||||||
|
) {
|
||||||
|
// Normalize whitelist and blacklist values to lowercase
|
||||||
|
$functionWhitelist = array_map( 'strtolower', $functionWhitelist );
|
||||||
|
$propertyBlacklist = array_map( 'strtolower', $propertyBlacklist );
|
||||||
|
|
||||||
$css = '';
|
$css = '';
|
||||||
|
foreach ( $this->byMedia as $media => $rules ) {
|
||||||
foreach ( $this->bymedia as $at => $rules ) {
|
if ( $media !== '' ) {
|
||||||
if ( $at != '' ) {
|
$css .= "{$media} {\n";
|
||||||
$css .= "$at {\n";
|
|
||||||
}
|
}
|
||||||
foreach ( $rules as $rule ) {
|
foreach ( $rules as $rule ) {
|
||||||
if ( $rule
|
if ( $rule !== null ) {
|
||||||
and array_key_exists( 'selectors', $rule )
|
$css .= $this->renderRule(
|
||||||
and array_key_exists( 'decls', $rule ) )
|
$rule, $functionWhitelist, $propertyBlacklist );
|
||||||
{
|
|
||||||
$css .= implode( ',', $rule['selectors'] ) . "{";
|
|
||||||
foreach ( $rule['decls'] as $key => $value ) {
|
|
||||||
if ( !in_array( strtolower( $key ), $propertyBlacklist ) ) {
|
|
||||||
$blacklisted = false;
|
|
||||||
foreach ( $value as $prop ) {
|
|
||||||
if ( preg_match( '/^ ([^ \n\t]+) [ \n\t]* \( $/x', $prop, $match ) ) {
|
|
||||||
if ( !in_array( strtolower( $match[1] ), $functionWhitelist ) ) {
|
|
||||||
$blacklisted = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if ( $media !== '' ) {
|
||||||
if ( !$blacklisted ) {
|
$css .= '} ';
|
||||||
$css .= "$key:" . implode( '', $value ) . ';';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$css .= "} ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( $at != '' ) {
|
|
||||||
$css .= "} ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $css;
|
return $css;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a single rule.
|
||||||
|
*
|
||||||
|
* @param array $rule Parsed rule
|
||||||
|
* @param array $functionWhitelist List of functions that are allowed
|
||||||
|
* @param array $propertyBlacklist List of properties that not allowed
|
||||||
|
* @return string Rendered CSS
|
||||||
|
*/
|
||||||
|
private function renderRule(
|
||||||
|
array $rule,
|
||||||
|
array $functionWhitelist,
|
||||||
|
array $propertyBlacklist
|
||||||
|
) {
|
||||||
|
$css = '';
|
||||||
|
if ( $rule &&
|
||||||
|
array_key_exists( 'selectors', $rule ) &&
|
||||||
|
array_key_exists( 'decls', $rule )
|
||||||
|
) {
|
||||||
|
$css .= implode( ',', $rule['selectors'] ) . '{';
|
||||||
|
foreach ( $rule['decls'] as $prop => $values ) {
|
||||||
|
$css .= $this->renderDecl(
|
||||||
|
$prop, $values, $functionWhitelist, $propertyBlacklist );
|
||||||
|
}
|
||||||
|
$css .= '} ';
|
||||||
|
}
|
||||||
|
return $css;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a property declaration.
|
||||||
|
*
|
||||||
|
* @param string $prop Property name
|
||||||
|
* @param array $values Parsed property values
|
||||||
|
* @param array $functionWhitelist List of functions that are allowed
|
||||||
|
* @param array $propertyBlacklist List of properties that not allowed
|
||||||
|
* @return string Rendered CSS
|
||||||
|
*/
|
||||||
|
private function renderDecl(
|
||||||
|
$prop,
|
||||||
|
array $values,
|
||||||
|
array $functionWhitelist,
|
||||||
|
array $propertyBlacklist
|
||||||
|
) {
|
||||||
|
if ( in_array( strtolower( $prop ), $propertyBlacklist ) ) {
|
||||||
|
// Property is blacklisted
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
foreach ( $values as $value ) {
|
||||||
|
if ( preg_match( '/^ (\S+) \s* \( $/x', $value, $match ) ) {
|
||||||
|
if ( !in_array( strtolower( $match[1] ), $functionWhitelist ) ) {
|
||||||
|
// Function is blacklisted
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $prop . ':' . implode( '', $values ) . ';';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@ class CSSParseRenderTest extends MediaWikiTestCase {
|
||||||
$expect,
|
$expect,
|
||||||
$source,
|
$source,
|
||||||
$baseSelector = '.X ',
|
$baseSelector = '.X ',
|
||||||
$functionWhitelist = [ 'whitelisted' ],
|
array $functionWhitelist = [ 'whitelisted' ],
|
||||||
$propertyBlacklist = [ '-evil' ]
|
array $propertyBlacklist = [ '-evil' ]
|
||||||
) {
|
) {
|
||||||
$tree = new CSSParser( $source );
|
$tree = new CSSParser( $source );
|
||||||
$rules = $tree->rules( $baseSelector );
|
$rules = $tree->rules( $baseSelector );
|
||||||
|
@ -239,6 +239,19 @@ prop2/*
|
||||||
;prop3 :not/**/whitelisted( val3 );}
|
;prop3 :not/**/whitelisted( val3 );}
|
||||||
CSS
|
CSS
|
||||||
],
|
],
|
||||||
|
'Whitelist normalized' => [
|
||||||
|
'expect' => '.foo {bar:whitelisted(1);} ',
|
||||||
|
'css' => '.foo { bar: whitelisted(1); baz: url(1); }',
|
||||||
|
'prefix' => '',
|
||||||
|
'whitelist' => [ 'WHITELISTED' ],
|
||||||
|
],
|
||||||
|
'Blacklist normalized' => [
|
||||||
|
'expect' => '.foo {baz:1;} ',
|
||||||
|
'css' => '.foo { blacklisted: 1; baz: 1; }',
|
||||||
|
'prefix' => '',
|
||||||
|
'whitelist' => [],
|
||||||
|
'blacklist' => [ 'BLACKLISTED' ],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue