| Server IP : 121.121.20.254 / Your IP : 216.73.216.144 Web Server : Microsoft-IIS/10.0 System : Windows NT WEB-SERVER 10.0 build 20348 (Windows Server 2022) AMD64 User : IUSR ( 0) PHP Version : 8.3.28 Disable Function : NONE MySQL : ON | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : C:/inetpub/wwwroot/KnowledgeBase/wp-content/plugins/betterdocs/includes/Utils/ |
Upload File : |
<?php
namespace WPDeveloper\BetterDocs\Utils;
class CSSGenerator {
const VERSION = '1.0.0';
/**
* Stores the global variables
*
* @var array<string, string|int>
*/
protected $variables = [];
/**
* Store the declared CSS blocks and comments
*
* @var array<int, array<mixed>>
*/
protected $blocks = [];
/**
* Store the config options
*
* @var array<string, string|int>
*/
protected $options = [];
/**
* Store the indentation level
*
* @var int
*/
protected $indent_level = 0;
/**
* @var bool
*/
protected $compress_output;
/**
* Stores the last generated CSS in prettry format (with tabs, spaces and line breaks).
*
* @var string|null
*/
protected $cache_pretty;
/**
* Stores the last generated CSS in minified format
*
* @var string|null
*/
protected $cache_compressed;
/**
* Mods Data
* @var array $mods
*/
private $mods;
/**
* Class constructor
*
* @param array $options
*/
public function __construct( $mods = [], $options = [] ) {
$default_options = [
'indent_size' => 4, // only works with indent_style = space
'indent_style' => 'space' // or "tab"
];
$this->options = array_merge( $default_options, $options );
$this->mods = $mods;
}
/**
* Returns the generated CSS
*
* @param boolean $compressed
* @return string
*/
public function get_output( $compressed = false ) {
$result = '';
if ( ! $compressed ) {
$result = $this->cache_pretty = $this->generate( false );
} else {
$result = $this->cache_compressed = $this->generate( true );
}
return $result;
}
/**
* Print anything (be careful)
*
* @param string $string
* @return $this
*/
public function add_raw( $string ) {
$this->blocks[] = [
'type' => 'raw',
'raw' => $string
];
$this->clear_cache();
return $this;
}
/**
* Print a code comment
*
* @param string $string
* @return $this
*/
public function add_comment( $string ) {
$this->blocks[] = [
'type' => 'comment',
'comment' => $string
];
$this->clear_cache();
return $this;
}
/**
* Declares a CSS rule
*
* @param string|string[] $selectors
* @param array<string, string|numeric> $declarations
* @return $this
*/
public function add_rule( $selectors, $declarations ) {
$selectors = ! is_array( $selectors ) ? [$selectors] : $selectors;
if( ! empty( $declarations ) ) {
$this->blocks[] = [
'type' => 'rule',
'selectors' => $selectors,
'declarations' => $declarations
];
}
$this->clear_cache();
return $this;
}
/**
* Declares a global variable (in :root)
*
* @param string $name The variable name
* @param string|numeric-string $value The variable value
* @return $this
*/
public function root_variable( $name, $value ) {
$this->variables[trim( $name )] = trim( strval( $value ) );
$this->clear_cache();
return $this;
}
/**
* Opens a block like @media, @supports, etc.
*
* @param string $name
* @param string $props
* @return $this
*/
public function open_block( $name, $props = '' ) {
$this->blocks[] = [
'type' => 'open',
'name' => $name,
'props' => $props
];
$this->clear_cache();
return $this;
}
/**
* Closes the last opened block
*
* @return $this
*/
public function close_block() {
$this->blocks[] = [
'type' => 'close'
];
$this->clear_cache();
return $this;
}
/**
* @return void
*/
public function clear_cache() {
$this->cache_compressed = null;
$this->cache_pretty = null;
}
/**
* Delete all declared blocks and resets the instance.
*
* @return void
*/
public function reset() {
$this->clear_cache();
$this->blocks = [];
}
/**
* Alias for self::escape
*
* @param string $selector
* @return string
*/
public function esc( $selector ) {
return self::escape( $selector );
}
/**
* Returns one unit of indentation
*
* @return string
*/
public function get_indent_unit() {
if ( 'space' === $this->options['indent_style'] ) {
$size = intval( $this->options['indent_size'] );
return str_repeat( ' ', max( $size, 2 ) );
}
return "\t";
}
/**
* Returns the current indentation (if not generating a minified code).
*
* @return string
*/
protected function tab() {
if ( ! $this->compress_output ) {
if ( $this->indent_level > 0 ) {
return str_repeat( $this->get_indent_unit(), $this->indent_level );
}
}
return '';
}
/**
* Build the CSS code
*
* @param boolean $compressed
* @return string
*/
protected function generate( $compressed = false ) {
$this->indent_level = 0;
$this->compress_output = $compressed;
$br = $this->compress_output ? '' : "\n";
$s = $this->compress_output ? '' : ' ';
$open = $s . '{' . $br;
$close = '}' . $br;
$output = '';
$has_variables = count( $this->variables ) > 0;
if ( $has_variables ) {
$output .= ':root' . $open;
$this->indent_level++;
foreach ( $this->variables as $name => $value ) {
$output .= $this->tab();
$output .= "--$name:$s$value;$br";
}
$output .= $close . $br;
$this->indent_level--;
}
foreach ( $this->blocks as $block ) {
switch ( $block['type'] ) {
case 'comment':
if ( ! $compressed ) {
$output .= $this->tab();
$output .= "/* {$block['comment']} */$br";
}
break;
case 'raw':
$output .= $block['raw'];
break;
case 'rule':
$output .= $this->tab();
$selectors = array_map( 'trim', $block['selectors'] );
$output .= implode( ",$br" . $this->tab(), $selectors );
$output .= $open;
$this->indent_level++;
foreach ( $block['declarations'] as $key => $value ) {
if ( empty( $key ) ) {
continue;
}
$output .= $this->tab();
$output .= trim( $key ) . ":$s" . trim( $value ) . ";$br";
}
$this->indent_level--;
$output .= $this->tab() . $close;
break;
case 'open':
$output .= $this->tab();
$output .= '@' . $block['name'];
$output .= '' !== $block['props'] ? " {$block['props']}" : '';
$output .= "$open";
$this->indent_level++;
break;
case 'close':
$this->indent_level--;
$output .= $this->tab() . $close;
break;
default:
break;
}
}
while ( $this->indent_level > 0 ) {
error_log( 'level = ' . $this->indent_level );
$this->indent_level--;
$output .= $this->tab() . $close;
}
return $output;
}
public function eligible_properties( $array, $suffix = '', &$initial = [] ) {
array_walk( $array, function ( $value, $key ) use ( &$initial, $suffix ) {
if ( ! is_array( $value ) ) {
$_mod_key = $value;
} elseif ( is_array( $value ) && ! empty( $value['id'] ) ) {
$_mod_key = $value['id'];
}
if ( strpos( $_mod_key, '%' ) !== false ) {
$_origin_mod_key = $_mod_key;
preg_match_all( '/%([a-zA-Z0-9_]*)%/', $_mod_key, $matches );
$_mod_key = $matches[1];
}
if ( is_array( $_mod_key ) ) {
$_value = [];
foreach ( $_mod_key as $_mod_key_child ) {
if ( isset( $this->mods[$_mod_key_child] ) ) {
$_value[$_mod_key_child] = $this->mods[$_mod_key_child];
} elseif ( ! isset( $this->mods[$_mod_key_child] ) ) {
$_value[$_mod_key_child] = $_mod_key_child;
}
}
} else {
if ( isset( $this->mods[$_mod_key] ) ) {
$_value = $this->mods[$_mod_key];
if ( empty( $_value ) && $_value === '' ) {
return;
}
} elseif ( ! isset( $this->mods[$_mod_key] ) ) {
$_value = $_mod_key;
}
}
if ( ! empty( $matches ) ) {
$_value = str_replace( $matches[0], $_value, $_origin_mod_key );
}
$initial[$key] = $this->eligible_value( $key, $_value, $suffix );
if ( is_array( $value ) ) {
$initial = array_merge( $initial, $this->eligible_properties( $value['properties'], $suffix, $initial ) );
}
} );
return $initial;
}
public function eligible_value( $key, $value, $suffix ) {
if ( strpos( $key, 'image' ) !== false ) {
$value = 'url("' . $value . '")';
}
$_value = $value;
if ( is_string( $value ) ) {
$_value = json_decode( $value, true );
}
if ( null != $_value && is_array( $_value ) ) {
$_values = [];
foreach ( $_value as $single_value ) {
$_values[] = $single_value . $suffix;
}
$value = implode( ' ', $_values );
$suffix = '';
}
if ( ! empty( $suffix ) ) {
$value .= $suffix;
}
return $value;
}
public function properties( $properties, $suffix = '' ) {
if ( empty( $properties ) ) {
return [];
}
$_properties = [];
return $this->eligible_properties( $properties, $suffix, $_properties );
}
/**
* Escapes a CSS rule selector. Based on https://github.com/mathiasbynens/CSS.escape
*
* @static
* @param string $selector
* @return string
*/
public static function escape( $selector ) {
if ( '' === $selector ) {
return $selector;
}
$length = mb_strlen( $selector );
$result = '';
$index = -1;
$first_char = mb_substr( $selector, 0, 1 );
$first_char_code = ord( $first_char );
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
1 === $length &&
0x2D === $first_char_code
) {
return '\\' . $selector;
}
while ( ++$index < $length ) {
$char = mb_substr( $selector, $index, 1 );
if ( '' === $char ) {
continue;
}
$char_code = ord( $char );
// If the character is NULL
if ( 0 === $char_code ) {
$result .= "\u{FFFD}";
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is U+007F, […]
$char_code <= 0x1F || 0x7F === $char_code ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
( 0 === $index && $char_code >= 0x30 && $char_code <= 0x39 ) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
1 === $index &&
$char_code >= 0x0030 && $char_code <= 0x0039 &&
0x002D === $first_char_code
)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
$result .= '\\' . dechex( $char_code ) . ' ';
continue;
}
if (
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
$char_code >= 0x0080 || 0x002D === $char_code || 0x005F === $char_code ||
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to U+005A)
$char_code >= 0x0030 && $char_code <= 0x0039 ||
$char_code >= 0x0041 && $char_code <= 0x005A ||
// , or [a-z] (U+0061 to U+007A), […]
$char_code >= 0x0061 && $char_code <= 0x007A
) {
// the character itself
$result .= $char;
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
$result .= "\\" . $char;
}
return $result;
}
}