2016-04-07 13:08:44 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* @file
|
|
|
|
* @ingroup Extensions
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Collects parsed CSS trees, and merges them for rendering into text.
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
*/
|
|
|
|
class CSSRenderer {
|
|
|
|
|
2016-04-21 05:07:36 +00:00
|
|
|
/** @var array $byMedia */
|
|
|
|
private $byMedia;
|
2016-04-07 13:08:44 +00:00
|
|
|
|
|
|
|
function __construct() {
|
2016-04-21 05:07:36 +00:00
|
|
|
$this->byMedia = [];
|
2016-04-07 13:08:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds (and merge) a parsed CSS tree to the render list.
|
|
|
|
*
|
|
|
|
* @param array $rules The parsed tree as created by CSSParser::rules()
|
2016-04-21 05:07:36 +00:00
|
|
|
* @param string $media Forcibly specified @media block selector.
|
2016-04-07 13:08:44 +00:00
|
|
|
*/
|
|
|
|
function add( $rules, $media = '' ) {
|
2016-04-21 05:07:36 +00:00
|
|
|
if ( !array_key_exists( $media, $this->byMedia ) ) {
|
|
|
|
$this->byMedia[$media] = [];
|
2016-04-10 13:41:46 +00:00
|
|
|
}
|
2016-04-07 13:08:44 +00:00
|
|
|
|
2016-04-21 05:07:36 +00:00
|
|
|
foreach ( $rules as $rule ) {
|
|
|
|
switch ( strtolower( $rule['name'] ) ) {
|
2016-04-07 13:08:44 +00:00
|
|
|
case '@media':
|
2016-04-10 13:41:46 +00:00
|
|
|
if ( $media == '' ) {
|
2016-04-21 05:07:36 +00:00
|
|
|
$this->add(
|
2016-10-30 22:53:31 +00:00
|
|
|
$rule['rules'],
|
|
|
|
"@media {$rule['text']}"
|
2016-04-21 05:07:36 +00:00
|
|
|
);
|
2016-04-10 13:41:46 +00:00
|
|
|
}
|
2016-04-07 13:08:44 +00:00
|
|
|
break;
|
|
|
|
case '':
|
2016-04-21 05:07:36 +00:00
|
|
|
$this->byMedia[$media] = array_merge(
|
2016-10-30 22:53:31 +00:00
|
|
|
$this->byMedia[$media],
|
|
|
|
$rule['rules']
|
2016-04-21 05:07:36 +00:00
|
|
|
);
|
2016-04-07 13:08:44 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-04-21 05:07:36 +00:00
|
|
|
* Render the collected CSS trees into a string suitable for inclusion
|
2016-04-07 13:08:44 +00:00
|
|
|
* in a <style> tag.
|
|
|
|
*
|
2016-04-21 05:07:36 +00:00
|
|
|
* @param array $functionWhitelist List of functions that are allowed
|
|
|
|
* @param array $propertyBlacklist List of properties that not allowed
|
2016-04-07 13:08:44 +00:00
|
|
|
* @return string Rendered CSS
|
|
|
|
*/
|
2016-04-21 05:07:36 +00:00
|
|
|
function render(
|
|
|
|
array $functionWhitelist = [],
|
|
|
|
array $propertyBlacklist = []
|
|
|
|
) {
|
|
|
|
// Normalize whitelist and blacklist values to lowercase
|
|
|
|
$functionWhitelist = array_map( 'strtolower', $functionWhitelist );
|
|
|
|
$propertyBlacklist = array_map( 'strtolower', $propertyBlacklist );
|
2016-04-07 13:08:44 +00:00
|
|
|
|
|
|
|
$css = '';
|
2016-04-21 05:07:36 +00:00
|
|
|
foreach ( $this->byMedia as $media => $rules ) {
|
|
|
|
if ( $media !== '' ) {
|
|
|
|
$css .= "{$media} {\n";
|
2016-04-10 13:41:46 +00:00
|
|
|
}
|
2016-04-07 13:08:44 +00:00
|
|
|
foreach ( $rules as $rule ) {
|
2016-04-21 05:07:36 +00:00
|
|
|
if ( $rule !== null ) {
|
|
|
|
$css .= $this->renderRule(
|
|
|
|
$rule, $functionWhitelist, $propertyBlacklist );
|
2016-04-07 13:08:44 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-21 05:07:36 +00:00
|
|
|
if ( $media !== '' ) {
|
|
|
|
$css .= '} ';
|
2016-04-10 13:41:46 +00:00
|
|
|
}
|
2016-04-07 13:08:44 +00:00
|
|
|
}
|
2016-04-21 05:07:36 +00:00
|
|
|
return $css;
|
|
|
|
}
|
2016-04-07 13:08:44 +00:00
|
|
|
|
2016-04-21 05:07:36 +00:00
|
|
|
/**
|
|
|
|
* 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 .= '} ';
|
|
|
|
}
|
2016-04-07 13:08:44 +00:00
|
|
|
return $css;
|
|
|
|
}
|
|
|
|
|
2016-04-21 05:07:36 +00:00
|
|
|
/**
|
|
|
|
* 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 ) . ';';
|
|
|
|
}
|
2016-04-07 13:08:44 +00:00
|
|
|
}
|