summaryrefslogtreecommitdiffstats
path: root/vendor/wikimedia/less.php/lib/Less
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/wikimedia/less.php/lib/Less')
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Autoloader.php57
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Cache.php276
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Colors.php176
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Configurable.php60
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Environment.php153
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Exception/Chunk.php199
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Exception/Compiler.php8
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Exception/Parser.php103
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Functions.php1222
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Mime.php36
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Output.php46
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Output/Mapped.php117
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Parser.php2729
-rw-r--r--vendor/wikimedia/less.php/lib/Less/SourceMap/Base64VLQ.php185
-rw-r--r--vendor/wikimedia/less.php/lib/Less/SourceMap/Generator.php354
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree.php95
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Alpha.php44
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Anonymous.php54
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Assignment.php35
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Attribute.php49
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Call.php116
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Color.php228
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Comment.php43
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Condition.php68
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/DefaultFunc.php30
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/DetachedRuleset.php35
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Dimension.php190
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Directive.php92
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Element.php73
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Expression.php90
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Extend.php76
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Import.php299
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Javascript.php26
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Keyword.php35
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Media.php183
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Mixin/Call.php197
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Mixin/Definition.php236
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/NameValue.php49
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Negative.php33
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Operation.php64
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Paren.php35
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Quoted.php75
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Rule.php115
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Ruleset.php730
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/RulesetCall.php26
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Selector.php169
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/UnicodeDescriptor.php20
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Unit.php138
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/UnitConversions.php31
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Url.php77
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Value.php46
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Tree/Variable.php56
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Version.php12
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Visitor.php44
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Visitor/extendFinder.php105
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Visitor/import.php137
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Visitor/joinSelector.php67
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Visitor/processExtends.php447
-rw-r--r--vendor/wikimedia/less.php/lib/Less/Visitor/toCSS.php275
-rw-r--r--vendor/wikimedia/less.php/lib/Less/VisitorReplacing.php66
60 files changed, 10832 insertions, 0 deletions
diff --git a/vendor/wikimedia/less.php/lib/Less/Autoloader.php b/vendor/wikimedia/less.php/lib/Less/Autoloader.php
new file mode 100644
index 0000000..a4f7a4a
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Autoloader.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * Autoloader
+ */
+class Less_Autoloader {
+
+ /** @var bool */
+ protected static $registered = false;
+
+ /**
+ * Register the autoloader in the SPL autoloader
+ *
+ * @return void
+ * @throws Exception If there was an error in registration
+ */
+ public static function register() {
+ if ( self::$registered ) {
+ return;
+ }
+
+ if ( !spl_autoload_register( [ 'Less_Autoloader', 'loadClass' ] ) ) {
+ throw new Exception( 'Unable to register Less_Autoloader::loadClass as an autoloading method.' );
+ }
+
+ self::$registered = true;
+ }
+
+ /**
+ * Unregister the autoloader
+ *
+ * @return void
+ */
+ public static function unregister() {
+ spl_autoload_unregister( [ 'Less_Autoloader', 'loadClass' ] );
+ self::$registered = false;
+ }
+
+ /**
+ * Load the class
+ *
+ * @param string $className The class to load
+ */
+ public static function loadClass( $className ) {
+ // handle only package classes
+ if ( strpos( $className, 'Less_' ) !== 0 ) {
+ return;
+ }
+
+ $className = substr( $className, 5 );
+ $fileName = __DIR__ . DIRECTORY_SEPARATOR . str_replace( '_', DIRECTORY_SEPARATOR, $className ) . '.php';
+
+ require $fileName;
+ return true;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Cache.php b/vendor/wikimedia/less.php/lib/Less/Cache.php
new file mode 100644
index 0000000..c8c4b44
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Cache.php
@@ -0,0 +1,276 @@
+<?php
+
+/**
+ * Utility for handling the generation and caching of css files
+ */
+class Less_Cache {
+
+ // directory less.php can use for storing data
+ public static $cache_dir = false;
+
+ // prefix for the storing data
+ public static $prefix = 'lessphp_';
+
+ // prefix for the storing vars
+ public static $prefix_vars = 'lessphpvars_';
+
+ // specifies the number of seconds after which data created by less.php will be seen as 'garbage' and potentially cleaned up
+ public static $gc_lifetime = 604800;
+
+ /**
+ * Save and reuse the results of compiled less files.
+ * The first call to Get() will generate css and save it.
+ * Subsequent calls to Get() with the same arguments will return the same css filename
+ *
+ * @param array $less_files Array of .less files to compile
+ * @param array $parser_options Array of compiler options
+ * @param array $modify_vars Array of variables
+ * @return string|false Name of the css file
+ */
+ public static function Get( $less_files, $parser_options = [], $modify_vars = [] ) {
+ // check $cache_dir
+ if ( isset( $parser_options['cache_dir'] ) ) {
+ self::$cache_dir = $parser_options['cache_dir'];
+ }
+
+ if ( empty( self::$cache_dir ) ) {
+ throw new Exception( 'cache_dir not set' );
+ }
+
+ if ( isset( $parser_options['prefix'] ) ) {
+ self::$prefix = $parser_options['prefix'];
+ }
+
+ if ( empty( self::$prefix ) ) {
+ throw new Exception( 'prefix not set' );
+ }
+
+ if ( isset( $parser_options['prefix_vars'] ) ) {
+ self::$prefix_vars = $parser_options['prefix_vars'];
+ }
+
+ if ( empty( self::$prefix_vars ) ) {
+ throw new Exception( 'prefix_vars not set' );
+ }
+
+ self::CheckCacheDir();
+ $less_files = (array)$less_files;
+
+ // create a file for variables
+ if ( !empty( $modify_vars ) ) {
+ $lessvars = Less_Parser::serializeVars( $modify_vars );
+ $vars_file = self::$cache_dir . self::$prefix_vars . sha1( $lessvars ) . '.less';
+
+ if ( !file_exists( $vars_file ) ) {
+ file_put_contents( $vars_file, $lessvars );
+ }
+
+ $less_files += [ $vars_file => '/' ];
+ }
+
+ // generate name for compiled css file
+ $hash = md5( json_encode( $less_files ) );
+ $list_file = self::$cache_dir . self::$prefix . $hash . '.list';
+
+ // check cached content
+ if ( !isset( $parser_options['use_cache'] ) || $parser_options['use_cache'] === true ) {
+ if ( file_exists( $list_file ) ) {
+
+ self::ListFiles( $list_file, $list, $cached_name );
+ $compiled_name = self::CompiledName( $list, $hash );
+
+ // if $cached_name is the same as the $compiled name, don't regenerate
+ if ( !$cached_name || $cached_name === $compiled_name ) {
+
+ $output_file = self::OutputFile( $compiled_name, $parser_options );
+
+ if ( $output_file && file_exists( $output_file ) ) {
+ @touch( $list_file );
+ return basename( $output_file ); // for backwards compatibility, we just return the name of the file
+ }
+ }
+ }
+ }
+
+ $compiled = self::Cache( $less_files, $parser_options );
+ if ( !$compiled ) {
+ return false;
+ }
+
+ $compiled_name = self::CompiledName( $less_files, $hash );
+ $output_file = self::OutputFile( $compiled_name, $parser_options );
+
+ // save the file list
+ $list = $less_files;
+ $list[] = $compiled_name;
+ $cache = implode( "\n", $list );
+ file_put_contents( $list_file, $cache );
+
+ // save the css
+ file_put_contents( $output_file, $compiled );
+
+ // clean up
+ self::CleanCache();
+
+ return basename( $output_file );
+ }
+
+ /**
+ * Force the compiler to regenerate the cached css file
+ *
+ * @param array $less_files Array of .less files to compile
+ * @param array $parser_options Array of compiler options
+ * @param array $modify_vars Array of variables
+ * @return string Name of the css file
+ */
+ public static function Regen( $less_files, $parser_options = [], $modify_vars = [] ) {
+ $parser_options['use_cache'] = false;
+ return self::Get( $less_files, $parser_options, $modify_vars );
+ }
+
+ public static function Cache( &$less_files, $parser_options = [] ) {
+ $parser_options['cache_dir'] = self::$cache_dir;
+ $parser = new Less_Parser( $parser_options );
+
+ // combine files
+ foreach ( $less_files as $file_path => $uri_or_less ) {
+
+ // treat as less markup if there are newline characters
+ if ( strpos( $uri_or_less, "\n" ) !== false ) {
+ $parser->Parse( $uri_or_less );
+ continue;
+ }
+
+ $parser->ParseFile( $file_path, $uri_or_less );
+ }
+
+ $compiled = $parser->getCss();
+
+ $less_files = $parser->allParsedFiles();
+
+ return $compiled;
+ }
+
+ private static function OutputFile( $compiled_name, $parser_options ) {
+ // custom output file
+ if ( !empty( $parser_options['output'] ) ) {
+
+ // relative to cache directory?
+ if ( preg_match( '#[\\\\/]#', $parser_options['output'] ) ) {
+ return $parser_options['output'];
+ }
+
+ return self::$cache_dir . $parser_options['output'];
+ }
+
+ return self::$cache_dir . $compiled_name;
+ }
+
+ private static function CompiledName( $files, $extrahash ) {
+ // save the file list
+ $temp = [ Less_Version::cache_version ];
+ foreach ( $files as $file ) {
+ $temp[] = filemtime( $file ) . "\t" . filesize( $file ) . "\t" . $file;
+ }
+
+ return self::$prefix . sha1( json_encode( $temp ) . $extrahash ) . '.css';
+ }
+
+ public static function SetCacheDir( $dir ) {
+ self::$cache_dir = $dir;
+ self::CheckCacheDir();
+ }
+
+ public static function CheckCacheDir() {
+ self::$cache_dir = str_replace( '\\', '/', self::$cache_dir );
+ self::$cache_dir = rtrim( self::$cache_dir, '/' ) . '/';
+
+ if ( !file_exists( self::$cache_dir ) ) {
+ if ( !mkdir( self::$cache_dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: ' . self::$cache_dir );
+ }
+
+ } elseif ( !is_dir( self::$cache_dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: ' . self::$cache_dir );
+
+ } elseif ( !is_writable( self::$cache_dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: ' . self::$cache_dir );
+
+ }
+ }
+
+ /**
+ * Delete unused less.php files
+ */
+ public static function CleanCache() {
+ static $clean = false;
+
+ if ( $clean || empty( self::$cache_dir ) ) {
+ return;
+ }
+
+ $clean = true;
+
+ // only remove files with extensions created by less.php
+ // css files removed based on the list files
+ $remove_types = [ 'lesscache' => 1,'list' => 1,'less' => 1,'map' => 1 ];
+
+ $files = scandir( self::$cache_dir );
+ if ( !$files ) {
+ return;
+ }
+
+ $check_time = time() - self::$gc_lifetime;
+ foreach ( $files as $file ) {
+
+ // don't delete if the file wasn't created with less.php
+ if ( strpos( $file, self::$prefix ) !== 0 ) {
+ continue;
+ }
+
+ $parts = explode( '.', $file );
+ $type = array_pop( $parts );
+
+ if ( !isset( $remove_types[$type] ) ) {
+ continue;
+ }
+
+ $full_path = self::$cache_dir . $file;
+ $mtime = filemtime( $full_path );
+
+ // don't delete if it's a relatively new file
+ if ( $mtime > $check_time ) {
+ continue;
+ }
+
+ // delete the list file and associated css file
+ if ( $type === 'list' ) {
+ self::ListFiles( $full_path, $list, $css_file_name );
+ if ( $css_file_name ) {
+ $css_file = self::$cache_dir . $css_file_name;
+ if ( file_exists( $css_file ) ) {
+ unlink( $css_file );
+ }
+ }
+ }
+
+ unlink( $full_path );
+ }
+ }
+
+ /**
+ * Get the list of less files and generated css file from a list file
+ */
+ static function ListFiles( $list_file, &$list, &$css_file_name ) {
+ $list = explode( "\n", file_get_contents( $list_file ) );
+
+ // pop the cached name that should match $compiled_name
+ $css_file_name = array_pop( $list );
+
+ if ( !preg_match( '/^' . self::$prefix . '[a-f0-9]+\.css$/', $css_file_name ) ) {
+ $list[] = $css_file_name;
+ $css_file_name = false;
+ }
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Colors.php b/vendor/wikimedia/less.php/lib/Less/Colors.php
new file mode 100644
index 0000000..2f715c2
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Colors.php
@@ -0,0 +1,176 @@
+<?php
+/**
+ * Utility for css colors
+ *
+ * @private
+ */
+class Less_Colors {
+
+ private const COLORS = [
+ 'aliceblue' => '#f0f8ff',
+ 'antiquewhite' => '#faebd7',
+ 'aqua' => '#00ffff',
+ 'aquamarine' => '#7fffd4',
+ 'azure' => '#f0ffff',
+ 'beige' => '#f5f5dc',
+ 'bisque' => '#ffe4c4',
+ 'black' => '#000000',
+ 'blanchedalmond' => '#ffebcd',
+ 'blue' => '#0000ff',
+ 'blueviolet' => '#8a2be2',
+ 'brown' => '#a52a2a',
+ 'burlywood' => '#deb887',
+ 'cadetblue' => '#5f9ea0',
+ 'chartreuse' => '#7fff00',
+ 'chocolate' => '#d2691e',
+ 'coral' => '#ff7f50',
+ 'cornflowerblue' => '#6495ed',
+ 'cornsilk' => '#fff8dc',
+ 'crimson' => '#dc143c',
+ 'cyan' => '#00ffff',
+ 'darkblue' => '#00008b',
+ 'darkcyan' => '#008b8b',
+ 'darkgoldenrod' => '#b8860b',
+ 'darkgray' => '#a9a9a9',
+ 'darkgrey' => '#a9a9a9',
+ 'darkgreen' => '#006400',
+ 'darkkhaki' => '#bdb76b',
+ 'darkmagenta' => '#8b008b',
+ 'darkolivegreen' => '#556b2f',
+ 'darkorange' => '#ff8c00',
+ 'darkorchid' => '#9932cc',
+ 'darkred' => '#8b0000',
+ 'darksalmon' => '#e9967a',
+ 'darkseagreen' => '#8fbc8f',
+ 'darkslateblue' => '#483d8b',
+ 'darkslategray' => '#2f4f4f',
+ 'darkslategrey' => '#2f4f4f',
+ 'darkturquoise' => '#00ced1',
+ 'darkviolet' => '#9400d3',
+ 'deeppink' => '#ff1493',
+ 'deepskyblue' => '#00bfff',
+ 'dimgray' => '#696969',
+ 'dimgrey' => '#696969',
+ 'dodgerblue' => '#1e90ff',
+ 'firebrick' => '#b22222',
+ 'floralwhite' => '#fffaf0',
+ 'forestgreen' => '#228b22',
+ 'fuchsia' => '#ff00ff',
+ 'gainsboro' => '#dcdcdc',
+ 'ghostwhite' => '#f8f8ff',
+ 'gold' => '#ffd700',
+ 'goldenrod' => '#daa520',
+ 'gray' => '#808080',
+ 'grey' => '#808080',
+ 'green' => '#008000',
+ 'greenyellow' => '#adff2f',
+ 'honeydew' => '#f0fff0',
+ 'hotpink' => '#ff69b4',
+ 'indianred' => '#cd5c5c',
+ 'indigo' => '#4b0082',
+ 'ivory' => '#fffff0',
+ 'khaki' => '#f0e68c',
+ 'lavender' => '#e6e6fa',
+ 'lavenderblush' => '#fff0f5',
+ 'lawngreen' => '#7cfc00',
+ 'lemonchiffon' => '#fffacd',
+ 'lightblue' => '#add8e6',
+ 'lightcoral' => '#f08080',
+ 'lightcyan' => '#e0ffff',
+ 'lightgoldenrodyellow' => '#fafad2',
+ 'lightgray' => '#d3d3d3',
+ 'lightgrey' => '#d3d3d3',
+ 'lightgreen' => '#90ee90',
+ 'lightpink' => '#ffb6c1',
+ 'lightsalmon' => '#ffa07a',
+ 'lightseagreen' => '#20b2aa',
+ 'lightskyblue' => '#87cefa',
+ 'lightslategray' => '#778899',
+ 'lightslategrey' => '#778899',
+ 'lightsteelblue' => '#b0c4de',
+ 'lightyellow' => '#ffffe0',
+ 'lime' => '#00ff00',
+ 'limegreen' => '#32cd32',
+ 'linen' => '#faf0e6',
+ 'magenta' => '#ff00ff',
+ 'maroon' => '#800000',
+ 'mediumaquamarine' => '#66cdaa',
+ 'mediumblue' => '#0000cd',
+ 'mediumorchid' => '#ba55d3',
+ 'mediumpurple' => '#9370d8',
+ 'mediumseagreen' => '#3cb371',
+ 'mediumslateblue' => '#7b68ee',
+ 'mediumspringgreen' => '#00fa9a',
+ 'mediumturquoise' => '#48d1cc',
+ 'mediumvioletred' => '#c71585',
+ 'midnightblue' => '#191970',
+ 'mintcream' => '#f5fffa',
+ 'mistyrose' => '#ffe4e1',
+ 'moccasin' => '#ffe4b5',
+ 'navajowhite' => '#ffdead',
+ 'navy' => '#000080',
+ 'oldlace' => '#fdf5e6',
+ 'olive' => '#808000',
+ 'olivedrab' => '#6b8e23',
+ 'orange' => '#ffa500',
+ 'orangered' => '#ff4500',
+ 'orchid' => '#da70d6',
+ 'palegoldenrod' => '#eee8aa',
+ 'palegreen' => '#98fb98',
+ 'paleturquoise' => '#afeeee',
+ 'palevioletred' => '#d87093',
+ 'papayawhip' => '#ffefd5',
+ 'peachpuff' => '#ffdab9',
+ 'peru' => '#cd853f',
+ 'pink' => '#ffc0cb',
+ 'plum' => '#dda0dd',
+ 'powderblue' => '#b0e0e6',
+ 'purple' => '#800080',
+ 'red' => '#ff0000',
+ 'rosybrown' => '#bc8f8f',
+ 'royalblue' => '#4169e1',
+ 'saddlebrown' => '#8b4513',
+ 'salmon' => '#fa8072',
+ 'sandybrown' => '#f4a460',
+ 'seagreen' => '#2e8b57',
+ 'seashell' => '#fff5ee',
+ 'sienna' => '#a0522d',
+ 'silver' => '#c0c0c0',
+ 'skyblue' => '#87ceeb',
+ 'slateblue' => '#6a5acd',
+ 'slategray' => '#708090',
+ 'slategrey' => '#708090',
+ 'snow' => '#fffafa',
+ 'springgreen' => '#00ff7f',
+ 'steelblue' => '#4682b4',
+ 'tan' => '#d2b48c',
+ 'teal' => '#008080',
+ 'thistle' => '#d8bfd8',
+ 'tomato' => '#ff6347',
+ 'turquoise' => '#40e0d0',
+ 'violet' => '#ee82ee',
+ 'wheat' => '#f5deb3',
+ 'white' => '#ffffff',
+ 'whitesmoke' => '#f5f5f5',
+ 'yellow' => '#ffff00',
+ 'yellowgreen' => '#9acd32',
+ ];
+
+ /**
+ * @param string $color
+ * @return bool
+ */
+ public static function hasOwnProperty( string $color ): bool {
+ return isset( self::COLORS[$color] );
+ }
+
+ /**
+ * @param string $color Should be an existing color name,
+ * checked via hasOwnProperty()
+ * @return string the corresponding hexadecimal representation
+ */
+ public static function color( string $color ): string {
+ return self::COLORS[$color];
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Configurable.php b/vendor/wikimedia/less.php/lib/Less/Configurable.php
new file mode 100644
index 0000000..ec16d80
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Configurable.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * @private
+ */
+abstract class Less_Configurable {
+
+ /**
+ * Array of options
+ *
+ * @var array
+ */
+ protected $options = [];
+
+ /**
+ * Array of default options
+ *
+ * @var array
+ */
+ protected $defaultOptions = [];
+
+ /**
+ * Set options
+ *
+ * If $options is an object it will be converted into an array by called
+ * it's toArray method.
+ *
+ * @param array|object $options
+ */
+ public function setOptions( $options ) {
+ $options = array_intersect_key( $options, $this->defaultOptions );
+ $this->options = array_merge( $this->defaultOptions, $this->options, $options );
+ }
+
+ /**
+ * Get an option value by name
+ *
+ * If the option is empty or not set a NULL value will be returned.
+ *
+ * @param string $name
+ * @param mixed $default Default value if confiuration of $name is not present
+ * @return mixed
+ */
+ public function getOption( $name, $default = null ) {
+ if ( isset( $this->options[$name] ) ) {
+ return $this->options[$name];
+ }
+ return $default;
+ }
+
+ /**
+ * Set an option
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setOption( $name, $value ) {
+ $this->options[$name] = $value;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Environment.php b/vendor/wikimedia/less.php/lib/Less/Environment.php
new file mode 100644
index 0000000..bfc3825
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Environment.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * @private
+ */
+class Less_Environment {
+
+ /**
+ * Information about the current file - for error reporting and importing and making urls relative etc.
+ *
+ * - rootpath: rootpath to append to URLs
+ *
+ * @var array|null $currentFileInfo;
+ */
+ public $currentFileInfo;
+
+ /* Whether we are currently importing multiple copies */
+ public $importMultiple = false;
+
+ /**
+ * @var array
+ */
+ public $frames = [];
+
+ /**
+ * @var array
+ */
+ public $mediaBlocks = [];
+
+ /**
+ * @var array
+ */
+ public $mediaPath = [];
+
+ public static $parensStack = 0;
+
+ public static $tabLevel = 0;
+
+ public static $lastRule = false;
+
+ public static $_outputMap;
+
+ public static $mixin_stack = 0;
+
+ /**
+ * @var array
+ */
+ public $functions = [];
+
+ public function Init() {
+ self::$parensStack = 0;
+ self::$tabLevel = 0;
+ self::$lastRule = false;
+ self::$mixin_stack = 0;
+
+ if ( Less_Parser::$options['compress'] ) {
+
+ self::$_outputMap = [
+ ',' => ',',
+ ': ' => ':',
+ '' => '',
+ ' ' => ' ',
+ ':' => ' :',
+ '+' => '+',
+ '~' => '~',
+ '>' => '>',
+ '|' => '|',
+ '^' => '^',
+ '^^' => '^^'
+ ];
+
+ } else {
+
+ self::$_outputMap = [
+ ',' => ', ',
+ ': ' => ': ',
+ '' => '',
+ ' ' => ' ',
+ ':' => ' :',
+ '+' => ' + ',
+ '~' => ' ~ ',
+ '>' => ' > ',
+ '|' => '|',
+ '^' => ' ^ ',
+ '^^' => ' ^^ '
+ ];
+
+ }
+ }
+
+ public function copyEvalEnv( $frames = [] ) {
+ $new_env = new Less_Environment();
+ $new_env->frames = $frames;
+ return $new_env;
+ }
+
+ public static function isMathOn() {
+ return !Less_Parser::$options['strictMath'] || self::$parensStack;
+ }
+
+ public static function isPathRelative( $path ) {
+ return !preg_match( '/^(?:[a-z-]+:|\/)/', $path );
+ }
+
+ /**
+ * Canonicalize a path by resolving references to '/./', '/../'
+ * Does not remove leading "../"
+ * @param string $path or url
+ * @return string Canonicalized path
+ */
+ public static function normalizePath( $path ) {
+ $segments = explode( '/', $path );
+ $segments = array_reverse( $segments );
+
+ $path = [];
+ $path_len = 0;
+
+ while ( $segments ) {
+ $segment = array_pop( $segments );
+ switch ( $segment ) {
+
+ case '.':
+ break;
+
+ case '..':
+ // @phan-suppress-next-line PhanTypeInvalidDimOffset False positive
+ if ( !$path_len || ( $path[$path_len - 1] === '..' ) ) {
+ $path[] = $segment;
+ $path_len++;
+ } else {
+ array_pop( $path );
+ $path_len--;
+ }
+ break;
+
+ default:
+ $path[] = $segment;
+ $path_len++;
+ break;
+ }
+ }
+
+ return implode( '/', $path );
+ }
+
+ public function unshiftFrame( $frame ) {
+ array_unshift( $this->frames, $frame );
+ }
+
+ public function shiftFrame() {
+ return array_shift( $this->frames );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Exception/Chunk.php b/vendor/wikimedia/less.php/lib/Less/Exception/Chunk.php
new file mode 100644
index 0000000..8071eb8
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Exception/Chunk.php
@@ -0,0 +1,199 @@
+<?php
+/**
+ * @private
+ */
+class Less_Exception_Chunk extends Less_Exception_Parser {
+
+ protected $parserCurrentIndex = 0;
+
+ protected $emitFrom = 0;
+
+ protected $input_len;
+
+ /**
+ * @param string $input
+ * @param Exception|null $previous Previous exception
+ * @param int|null $index The current parser index
+ * @param array|null $currentFile The file
+ * @param int $code The exception code
+ */
+ public function __construct( $input, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
+ $this->message = 'ParseError: Unexpected input'; // default message
+
+ $this->index = $index;
+
+ $this->currentFile = $currentFile;
+
+ $this->input = $input;
+ $this->input_len = strlen( $input );
+
+ $this->Chunks();
+ $this->genMessage();
+ }
+
+ /**
+ * See less.js chunks()
+ * We don't actually need the chunks
+ */
+ protected function Chunks() {
+ $level = 0;
+ $parenLevel = 0;
+ $lastMultiCommentEndBrace = null;
+ $lastOpening = null;
+ $lastMultiComment = null;
+ $lastParen = null;
+
+ for ( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
+ $cc = $this->CharCode( $this->parserCurrentIndex );
+ if ( ( ( $cc >= 97 ) && ( $cc <= 122 ) ) || ( $cc < 34 ) ) {
+ // a-z or whitespace
+ continue;
+ }
+
+ switch ( $cc ) {
+
+ // (
+ case 40:
+ $parenLevel++;
+ $lastParen = $this->parserCurrentIndex;
+ break;
+
+ // )
+ case 41:
+ $parenLevel--;
+ if ( $parenLevel < 0 ) {
+ return $this->fail( "missing opening `(`" );
+ }
+ break;
+
+ // ;
+ case 59:
+ // if (!$parenLevel) { $this->emitChunk(); }
+ break;
+
+ // {
+ case 123:
+ $level++;
+ $lastOpening = $this->parserCurrentIndex;
+ break;
+
+ // }
+ case 125:
+ $level--;
+ if ( $level < 0 ) {
+ return $this->fail( "missing opening `{`" );
+
+ }
+ // if (!$level && !$parenLevel) { $this->emitChunk(); }
+ break;
+ // \
+ case 92:
+ if ( $this->parserCurrentIndex < $this->input_len - 1 ) {
+ $this->parserCurrentIndex++;
+ break;
+ }
+ return $this->fail( "unescaped `\\`" );
+
+ // ", ' and `
+ case 34:
+ case 39:
+ case 96:
+ $matched = 0;
+ $currentChunkStartIndex = $this->parserCurrentIndex;
+ for ( $this->parserCurrentIndex += 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
+ $cc2 = $this->CharCode( $this->parserCurrentIndex );
+ if ( $cc2 > 96 ) { continue;
+ }
+ if ( $cc2 == $cc ) { $matched = 1;
+break;
+ }
+ if ( $cc2 == 92 ) { // \
+ if ( $this->parserCurrentIndex == $this->input_len - 1 ) {
+ return $this->fail( "unescaped `\\`" );
+ }
+ $this->parserCurrentIndex++;
+ }
+ }
+ if ( $matched ) { break;
+ }
+ return $this->fail( "unmatched `" . chr( $cc ) . "`", $currentChunkStartIndex );
+
+ // /, check for comment
+ case 47:
+ if ( $parenLevel || ( $this->parserCurrentIndex == $this->input_len - 1 ) ) { break;
+ }
+ $cc2 = $this->CharCode( $this->parserCurrentIndex + 1 );
+ if ( $cc2 == 47 ) {
+ // //, find lnfeed
+ for ( $this->parserCurrentIndex += 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
+ $cc2 = $this->CharCode( $this->parserCurrentIndex );
+ if ( ( $cc2 <= 13 ) && ( ( $cc2 == 10 ) || ( $cc2 == 13 ) ) ) { break;
+ }
+ }
+ } elseif ( $cc2 == 42 ) {
+ // /*, find */
+ $lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex;
+ for ( $this->parserCurrentIndex += 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++ ) {
+ $cc2 = $this->CharCode( $this->parserCurrentIndex );
+ if ( $cc2 == 125 ) { $lastMultiCommentEndBrace = $this->parserCurrentIndex;
+ }
+ if ( $cc2 != 42 ) { continue;
+ }
+ if ( $this->CharCode( $this->parserCurrentIndex + 1 ) == 47 ) { break;
+ }
+ }
+ if ( $this->parserCurrentIndex == $this->input_len - 1 ) {
+ return $this->fail( "missing closing `*/`", $currentChunkStartIndex );
+ }
+ }
+ break;
+
+ // *, check for unmatched */
+ case 42:
+ if ( ( $this->parserCurrentIndex < $this->input_len - 1 ) && ( $this->CharCode( $this->parserCurrentIndex + 1 ) == 47 ) ) {
+ return $this->fail( "unmatched `/*`" );
+ }
+ break;
+ }
+ }
+
+ if ( $level !== 0 ) {
+ if ( ( $lastMultiComment > $lastOpening ) && ( $lastMultiCommentEndBrace > $lastMultiComment ) ) {
+ return $this->fail( "missing closing `}` or `*/`", $lastOpening );
+ } else {
+ return $this->fail( "missing closing `}`", $lastOpening );
+ }
+ } elseif ( $parenLevel !== 0 ) {
+ return $this->fail( "missing closing `)`", $lastParen );
+ }
+
+ // chunk didn't fail
+
+ //$this->emitChunk(true);
+ }
+
+ public function CharCode( $pos ) {
+ return ord( $this->input[$pos] );
+ }
+
+ public function fail( $msg, $index = null ) {
+ if ( !$index ) {
+ $this->index = $this->parserCurrentIndex;
+ } else {
+ $this->index = $index;
+ }
+ $this->message = 'ParseError: ' . $msg;
+ }
+
+ /*
+ function emitChunk( $force = false ){
+ $len = $this->parserCurrentIndex - $this->emitFrom;
+ if ((($len < 512) && !$force) || !$len) {
+ return;
+ }
+ $chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom );
+ $this->emitFrom = $this->parserCurrentIndex + 1;
+ }
+ */
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Exception/Compiler.php b/vendor/wikimedia/less.php/lib/Less/Exception/Compiler.php
new file mode 100644
index 0000000..e963b69
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Exception/Compiler.php
@@ -0,0 +1,8 @@
+<?php
+
+/**
+ * Compiler Exception
+ */
+class Less_Exception_Compiler extends Less_Exception_Parser {
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Exception/Parser.php b/vendor/wikimedia/less.php/lib/Less/Exception/Parser.php
new file mode 100644
index 0000000..22d9d19
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Exception/Parser.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * Parser Exception
+ */
+class Less_Exception_Parser extends Exception {
+
+ /**
+ * The current file
+ *
+ * @var array
+ */
+ public $currentFile;
+
+ /**
+ * The current parser index
+ *
+ * @var int
+ */
+ public $index;
+
+ protected $input;
+
+ protected $details = [];
+
+ /**
+ * @param string|null $message
+ * @param Exception|null $previous Previous exception
+ * @param int|null $index The current parser index
+ * @param array|null $currentFile The file
+ * @param int $code The exception code
+ */
+ public function __construct( $message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
+ parent::__construct( $message, $code, $previous );
+
+ $this->currentFile = $currentFile;
+ $this->index = $index;
+
+ $this->genMessage();
+ }
+
+ protected function getInput() {
+ if ( !$this->input && $this->currentFile && $this->currentFile['filename'] && file_exists( $this->currentFile['filename'] ) ) {
+ $this->input = file_get_contents( $this->currentFile['filename'] );
+ }
+ }
+
+ /**
+ * Set a message based on the exception info
+ */
+ public function genMessage() {
+ if ( $this->currentFile && $this->currentFile['filename'] ) {
+ $this->message .= ' in ' . basename( $this->currentFile['filename'] );
+ }
+
+ if ( $this->index !== null ) {
+ $this->getInput();
+ if ( $this->input ) {
+ $line = self::getLineNumber();
+ $this->message .= ' on line ' . $line . ', column ' . self::getColumn();
+
+ $lines = explode( "\n", $this->input );
+
+ $count = count( $lines );
+ $start_line = max( 0, $line - 3 );
+ $last_line = min( $count, $start_line + 6 );
+ $num_len = strlen( $last_line );
+ for ( $i = $start_line; $i < $last_line; $i++ ) {
+ $this->message .= "\n" . str_pad( (string)( $i + 1 ), $num_len, '0', STR_PAD_LEFT ) . '| ' . $lines[$i];
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the line number the error was encountered
+ *
+ * @return int
+ */
+ public function getLineNumber() {
+ if ( $this->index ) {
+ // https://bugs.php.net/bug.php?id=49790
+ if ( ini_get( "mbstring.func_overload" ) ) {
+ return substr_count( substr( $this->input, 0, $this->index ), "\n" ) + 1;
+ } else {
+ return substr_count( $this->input, "\n", 0, $this->index ) + 1;
+ }
+ }
+ return 1;
+ }
+
+ /**
+ * Returns the column the error was encountered
+ *
+ * @return int
+ */
+ public function getColumn() {
+ $part = substr( $this->input, 0, $this->index );
+ $pos = strrpos( $part, "\n" );
+ return $this->index - $pos;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Functions.php b/vendor/wikimedia/less.php/lib/Less/Functions.php
new file mode 100644
index 0000000..43a964c
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Functions.php
@@ -0,0 +1,1222 @@
+<?php
+
+/**
+ * Builtin functions
+ * @see https://lesscss.org/functions/
+ */
+class Less_Functions {
+
+ public $env;
+ public $currentFileInfo;
+
+ function __construct( $env, array $currentFileInfo = null ) {
+ $this->env = $env;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ /**
+ * @param string $op
+ * @param float $a
+ * @param float $b
+ */
+ public static function operate( $op, $a, $b ) {
+ switch ( $op ) {
+ case '+':
+ return $a + $b;
+ case '-':
+ return $a - $b;
+ case '*':
+ return $a * $b;
+ case '/':
+ return $a / $b;
+ }
+ }
+
+ public static function clamp( $val, $max = 1 ) {
+ return min( max( $val, 0 ), $max );
+ }
+
+ public static function fround( $value ) {
+ if ( $value === 0 ) {
+ return $value;
+ }
+
+ if ( Less_Parser::$options['numPrecision'] ) {
+ $p = pow( 10, Less_Parser::$options['numPrecision'] );
+ return round( $value * $p ) / $p;
+ }
+ return $value;
+ }
+
+ public static function number( $n ) {
+ if ( $n instanceof Less_Tree_Dimension ) {
+ return floatval( $n->unit->is( '%' ) ? $n->value / 100 : $n->value );
+ } elseif ( is_numeric( $n ) ) {
+ return $n;
+ } else {
+ throw new Less_Exception_Compiler( "color functions take numbers as parameters" );
+ }
+ }
+
+ public static function scaled( $n, $size = 255 ) {
+ if ( $n instanceof Less_Tree_Dimension && $n->unit->is( '%' ) ) {
+ return (float)$n->value * $size / 100;
+ } else {
+ return self::number( $n );
+ }
+ }
+
+ public function rgb( $r = null, $g = null, $b = null ) {
+ if ( $r === null || $g === null || $b === null ) {
+ throw new Less_Exception_Compiler( "rgb expects three parameters" );
+ }
+ return $this->rgba( $r, $g, $b, 1.0 );
+ }
+
+ public function rgba( $r = null, $g = null, $b = null, $a = null ) {
+ $rgb = [ $r, $g, $b ];
+ $rgb = array_map( [ 'Less_Functions','scaled' ], $rgb );
+
+ $a = self::number( $a );
+ return new Less_Tree_Color( $rgb, $a );
+ }
+
+ public function hsl( $h, $s, $l ) {
+ return $this->hsla( $h, $s, $l, 1.0 );
+ }
+
+ public function hsla( $h, $s, $l, $a ) {
+ $h = fmod( self::number( $h ), 360 ) / 360; // Classic % operator will change float to int
+ $s = self::clamp( self::number( $s ) );
+ $l = self::clamp( self::number( $l ) );
+ $a = self::clamp( self::number( $a ) );
+
+ $m2 = $l <= 0.5 ? $l * ( $s + 1 ) : $l + $s - $l * $s;
+
+ $m1 = $l * 2 - $m2;
+
+ return $this->rgba(
+ self::hsla_hue( $h + 1 / 3, $m1, $m2 ) * 255,
+ self::hsla_hue( $h, $m1, $m2 ) * 255,
+ self::hsla_hue( $h - 1 / 3, $m1, $m2 ) * 255,
+ $a
+ );
+ }
+
+ /**
+ * @param float $h
+ * @param float $m1
+ * @param float $m2
+ */
+ public function hsla_hue( $h, $m1, $m2 ) {
+ $h = $h < 0 ? $h + 1 : ( $h > 1 ? $h - 1 : $h );
+ if ( $h * 6 < 1 ) {
+ return $m1 + ( $m2 - $m1 ) * $h * 6;
+ } elseif ( $h * 2 < 1 ) {
+ return $m2;
+ } elseif ( $h * 3 < 2 ) {
+ return $m1 + ( $m2 - $m1 ) * ( 2 / 3 - $h ) * 6;
+ } else {
+ return $m1;
+ }
+ }
+
+ public function hsv( $h, $s, $v ) {
+ return $this->hsva( $h, $s, $v, 1.0 );
+ }
+
+ /**
+ * @param Less_Tree|float $h
+ * @param Less_Tree|float $s
+ * @param Less_Tree|float $v
+ * @param float $a
+ */
+ public function hsva( $h, $s, $v, $a ) {
+ $h = ( ( self::number( $h ) % 360 ) / 360 ) * 360;
+ $s = self::number( $s );
+ $v = self::number( $v );
+ $a = self::number( $a );
+
+ $i = floor( (int)( $h / 60 ) % 6 );
+ $f = ( $h / 60 ) - $i;
+
+ $vs = [
+ $v,
+ $v * ( 1 - $s ),
+ $v * ( 1 - $f * $s ),
+ $v * ( 1 - ( 1 - $f ) * $s )
+ ];
+
+ $perm = [
+ [ 0, 3, 1 ],
+ [ 2, 0, 1 ],
+ [ 1, 0, 3 ],
+ [ 1, 2, 0 ],
+ [ 3, 1, 0 ],
+ [ 0, 1, 2 ]
+ ];
+
+ return $this->rgba(
+ $vs[$perm[$i][0]] * 255,
+ $vs[$perm[$i][1]] * 255,
+ $vs[$perm[$i][2]] * 255,
+ $a
+ );
+ }
+
+ public function hue( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $c = $color->toHSL();
+ return new Less_Tree_Dimension( Less_Parser::round( $c['h'] ) );
+ }
+
+ public function saturation( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to saturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $c = $color->toHSL();
+ return new Less_Tree_Dimension( Less_Parser::round( $c['s'] * 100 ), '%' );
+ }
+
+ public function lightness( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to lightness must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $c = $color->toHSL();
+ return new Less_Tree_Dimension( Less_Parser::round( $c['l'] * 100 ), '%' );
+ }
+
+ public function hsvhue( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hsvhue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsv = $color->toHSV();
+ return new Less_Tree_Dimension( Less_Parser::round( $hsv['h'] ) );
+ }
+
+ public function hsvsaturation( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hsvsaturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsv = $color->toHSV();
+ return new Less_Tree_Dimension( Less_Parser::round( $hsv['s'] * 100 ), '%' );
+ }
+
+ public function hsvvalue( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hsvvalue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsv = $color->toHSV();
+ return new Less_Tree_Dimension( Less_Parser::round( $hsv['v'] * 100 ), '%' );
+ }
+
+ public function red( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to red must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Dimension( $color->rgb[0] );
+ }
+
+ public function green( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to green must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Dimension( $color->rgb[1] );
+ }
+
+ public function blue( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to blue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Dimension( $color->rgb[2] );
+ }
+
+ public function alpha( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to alpha must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $c = $color->toHSL();
+ return new Less_Tree_Dimension( $c['a'] );
+ }
+
+ public function luma( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to luma must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Dimension( Less_Parser::round( $color->luma() * $color->alpha * 100 ), '%' );
+ }
+
+ public function luminance( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to luminance must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $luminance =
+ ( 0.2126 * $color->rgb[0] / 255 )
+ + ( 0.7152 * $color->rgb[1] / 255 )
+ + ( 0.0722 * $color->rgb[2] / 255 );
+
+ return new Less_Tree_Dimension( Less_Parser::round( $luminance * $color->alpha * 100 ), '%' );
+ }
+
+ public function saturate( $color = null, $amount = null ) {
+ // filter: saturate(3.2);
+ // should be kept as is, so check for color
+ if ( $color instanceof Less_Tree_Dimension ) {
+ return null;
+ }
+
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to saturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to saturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+
+ $hsl['s'] += $amount->value / 100;
+ $hsl['s'] = self::clamp( $hsl['s'] );
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ /**
+ * @param Less_Tree_Color|null $color
+ * @param Less_Tree_Dimension|null $amount
+ */
+ public function desaturate( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to desaturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to desaturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+ $hsl['s'] -= $amount->value / 100;
+ $hsl['s'] = self::clamp( $hsl['s'] );
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function lighten( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to lighten must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to lighten must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+
+ $hsl['l'] += $amount->value / 100;
+ $hsl['l'] = self::clamp( $hsl['l'] );
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function darken( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to darken must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to darken must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+ $hsl['l'] -= $amount->value / 100;
+ $hsl['l'] = self::clamp( $hsl['l'] );
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function fadein( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to fadein must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to fadein must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+ $hsl['a'] += $amount->value / 100;
+ $hsl['a'] = self::clamp( $hsl['a'] );
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function fadeout( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to fadeout must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to fadeout must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+ $hsl['a'] -= $amount->value / 100;
+ $hsl['a'] = self::clamp( $hsl['a'] );
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function fade( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to fade must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to fade must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+
+ $hsl['a'] = $amount->value / 100;
+ $hsl['a'] = self::clamp( $hsl['a'] );
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function spin( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to spin must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to spin must be a number' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+ $hue = fmod( $hsl['h'] + $amount->value, 360 );
+
+ $hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ //
+ // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
+ // https://sass-lang.com/
+ //
+
+ /**
+ * @param Less_Tree|null $color1
+ * @param Less_Tree|null $color2
+ * @param Less_Tree|null $weight
+ */
+ public function mix( $color1 = null, $color2 = null, $weight = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to mix must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to mix must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$weight ) {
+ $weight = new Less_Tree_Dimension( '50', '%' );
+ }
+ if ( !$weight instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The third argument to contrast must be a percentage' . ( $weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $p = $weight->value / 100.0;
+ $w = $p * 2 - 1;
+ $hsl1 = $color1->toHSL();
+ $hsl2 = $color2->toHSL();
+ $a = $hsl1['a'] - $hsl2['a'];
+
+ $w1 = ( ( ( ( $w * $a ) == -1 ) ? $w : ( $w + $a ) / ( 1 + $w * $a ) ) + 1 ) / 2;
+ $w2 = 1 - $w1;
+
+ $rgb = [
+ $color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
+ $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
+ $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2
+ ];
+
+ $alpha = $color1->alpha * $p + $color2->alpha * ( 1 - $p );
+
+ return new Less_Tree_Color( $rgb, $alpha );
+ }
+
+ public function greyscale( $color ) {
+ return $this->desaturate( $color, new Less_Tree_Dimension( 100, '%' ) );
+ }
+
+ public function contrast( $color, $dark = null, $light = null, $threshold = null ) {
+ // filter: contrast(3.2);
+ // should be kept as is, so check for color
+ if ( !$color instanceof Less_Tree_Color ) {
+ return null;
+ }
+ if ( !$light ) {
+ $light = $this->rgba( 255, 255, 255, 1.0 );
+ }
+ if ( !$dark ) {
+ $dark = $this->rgba( 0, 0, 0, 1.0 );
+ }
+
+ if ( !$dark instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to contrast must be a color' . ( $dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$light instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The third argument to contrast must be a color' . ( $light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ // Figure out which is actually light and dark!
+ if ( $dark->luma() > $light->luma() ) {
+ $t = $light;
+ $light = $dark;
+ $dark = $t;
+ }
+ if ( !$threshold ) {
+ $threshold = 0.43;
+ } else {
+ $threshold = self::number( $threshold );
+ }
+
+ if ( $color->luma() < $threshold ) {
+ return $light;
+ } else {
+ return $dark;
+ }
+ }
+
+ public function e( $str ) {
+ if ( is_string( $str ) ) {
+ return new Less_Tree_Anonymous( $str );
+ }
+ return new Less_Tree_Anonymous( $str instanceof Less_Tree_JavaScript ? $str->expression : $str->value );
+ }
+
+ public function escape( $str ) {
+ $revert = [ '%21' => '!', '%2A' => '*', '%27' => "'",'%3F' => '?','%26' => '&','%2C' => ',','%2F' => '/','%40' => '@','%2B' => '+','%24' => '$' ];
+
+ return new Less_Tree_Anonymous( strtr( rawurlencode( $str->value ), $revert ) );
+ }
+
+ /**
+ * todo: This function will need some additional work to make it work the same as less.js
+ *
+ */
+ public function replace( $string, $pattern, $replacement, $flags = null ) {
+ $result = $string->value;
+
+ $expr = '/' . str_replace( '/', '\\/', $pattern->value ) . '/';
+ if ( $flags && $flags->value ) {
+ $expr .= self::replace_flags( $flags->value );
+ }
+
+ $result = preg_replace( $expr, $replacement->value, $result );
+
+ if ( property_exists( $string, 'quote' ) ) {
+ return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
+ }
+ return new Less_Tree_Quoted( '', $result );
+ }
+
+ public static function replace_flags( $flags ) {
+ $flags = str_split( $flags, 1 );
+ $new_flags = '';
+
+ foreach ( $flags as $flag ) {
+ switch ( $flag ) {
+ case 'e':
+ case 'g':
+ break;
+
+ default:
+ $new_flags .= $flag;
+ break;
+ }
+ }
+
+ return $new_flags;
+ }
+
+ public function _percent() {
+ $string = func_get_arg( 0 );
+
+ $args = func_get_args();
+ array_shift( $args );
+ $result = $string->value;
+
+ foreach ( $args as $arg ) {
+ if ( preg_match( '/%[sda]/i', $result, $token ) ) {
+ $token = $token[0];
+ $value = stristr( $token, 's' ) ? $arg->value : $arg->toCSS();
+ $value = preg_match( '/[A-Z]$/', $token ) ? urlencode( $value ) : $value;
+ $result = preg_replace( '/%[sda]/i', $value, $result, 1 );
+ }
+ }
+ $result = str_replace( '%%', '%', $result );
+
+ return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
+ }
+
+ public function unit( $val, $unit = null ) {
+ if ( !( $val instanceof Less_Tree_Dimension ) ) {
+ throw new Less_Exception_Compiler( 'The first argument to unit must be a number' . ( $val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.' ) );
+ }
+
+ if ( $unit ) {
+ if ( $unit instanceof Less_Tree_Keyword ) {
+ $unit = $unit->value;
+ } else {
+ $unit = $unit->toCSS();
+ }
+ } else {
+ $unit = "";
+ }
+ return new Less_Tree_Dimension( $val->value, $unit );
+ }
+
+ public function convert( $val, $unit ) {
+ return $val->convertTo( $unit->value );
+ }
+
+ public function round( $n, $f = false ) {
+ $fraction = 0;
+ if ( $f !== false ) {
+ $fraction = $f->value;
+ }
+
+ return $this->_math( 'Less_Parser::round', null, $n, $fraction );
+ }
+
+ public function pi() {
+ return new Less_Tree_Dimension( M_PI );
+ }
+
+ public function mod( $a, $b ) {
+ return new Less_Tree_Dimension( $a->value % $b->value, $a->unit );
+ }
+
+ public function pow( $x, $y ) {
+ if ( is_numeric( $x ) && is_numeric( $y ) ) {
+ $x = new Less_Tree_Dimension( $x );
+ $y = new Less_Tree_Dimension( $y );
+ } elseif ( !( $x instanceof Less_Tree_Dimension ) || !( $y instanceof Less_Tree_Dimension ) ) {
+ throw new Less_Exception_Compiler( 'Arguments must be numbers' );
+ }
+
+ return new Less_Tree_Dimension( pow( $x->value, $y->value ), $x->unit );
+ }
+
+ // var mathFunctions = [{name:"ce ...
+ public function ceil( $n ) {
+ return $this->_math( 'ceil', null, $n );
+ }
+
+ public function floor( $n ) {
+ return $this->_math( 'floor', null, $n );
+ }
+
+ public function sqrt( $n ) {
+ return $this->_math( 'sqrt', null, $n );
+ }
+
+ public function abs( $n ) {
+ return $this->_math( 'abs', null, $n );
+ }
+
+ public function tan( $n ) {
+ return $this->_math( 'tan', '', $n );
+ }
+
+ public function sin( $n ) {
+ return $this->_math( 'sin', '', $n );
+ }
+
+ public function cos( $n ) {
+ return $this->_math( 'cos', '', $n );
+ }
+
+ public function atan( $n ) {
+ return $this->_math( 'atan', 'rad', $n );
+ }
+
+ public function asin( $n ) {
+ return $this->_math( 'asin', 'rad', $n );
+ }
+
+ public function acos( $n ) {
+ return $this->_math( 'acos', 'rad', $n );
+ }
+
+ private function _math() {
+ $args = func_get_args();
+ $fn = array_shift( $args );
+ $unit = array_shift( $args );
+
+ if ( $args[0] instanceof Less_Tree_Dimension ) {
+
+ if ( $unit === null ) {
+ $unit = $args[0]->unit;
+ } else {
+ $args[0] = $args[0]->unify();
+ }
+ $args[0] = (float)$args[0]->value;
+ return new Less_Tree_Dimension( call_user_func_array( $fn, $args ), $unit );
+ } elseif ( is_numeric( $args[0] ) ) {
+ return call_user_func_array( $fn, $args );
+ } else {
+ throw new Less_Exception_Compiler( "math functions take numbers as parameters" );
+ }
+ }
+
+ /**
+ * @param bool $isMin
+ * @param array<Less_Tree> $args
+ */
+ private function _minmax( $isMin, $args ) {
+ $arg_count = count( $args );
+
+ if ( $arg_count < 1 ) {
+ throw new Less_Exception_Compiler( 'one or more arguments required' );
+ }
+
+ $j = null;
+ $unitClone = null;
+ $unitStatic = null;
+
+ // elems only contains original argument values.
+ $order = [];
+ // key is the unit.toString() for unified tree.Dimension values,
+ // value is the index into the order array.
+ $values = [];
+
+ for ( $i = 0; $i < $arg_count; $i++ ) {
+ $current = $args[$i];
+ if ( !( $current instanceof Less_Tree_Dimension ) ) {
+ // @phan-suppress-next-line PhanUndeclaredProperty Checked Less_Tree->value
+ if ( property_exists( $args[$i], 'value' ) && is_array( $args[$i]->value ) ) {
+ // @phan-suppress-next-line PhanUndeclaredProperty Checked Less_Tree->value
+ $args[] = $args[$i]->value;
+ }
+ continue;
+ }
+ // PhanTypeInvalidDimOffset -- False positive, safe after continue or non-first iterations
+ '@phan-var non-empty-list<Less_Tree_Dimension> $order';
+
+ if ( $current->unit->toString() === '' && !$unitClone ) {
+ $temp = new Less_Tree_Dimension( $current->value, $unitClone );
+ $currentUnified = $temp->unify();
+ } else {
+ $currentUnified = $current->unify();
+ }
+
+ if ( $currentUnified->unit->toString() === "" && !$unitStatic ) {
+ $unit = $unitStatic;
+ } else {
+ $unit = $currentUnified->unit->toString();
+ }
+
+ if ( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ) {
+ $unitStatic = $unit;
+ }
+
+ if ( $unit != '' && !$unitClone ) {
+ $unitClone = $current->unit->toString();
+ }
+
+ if ( isset( $values[''] ) && $unit !== '' && $unit === $unitStatic ) {
+ $j = $values[''];
+ } elseif ( isset( $values[$unit] ) ) {
+ $j = $values[$unit];
+ } else {
+
+ if ( $unitStatic && $unit !== $unitStatic ) {
+ throw new Less_Exception_Compiler( 'incompatible types' );
+ }
+ $values[$unit] = count( $order );
+ $order[] = $current;
+ continue;
+ }
+
+ if ( $order[$j]->unit->toString() === "" && $unitClone ) {
+ $temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone );
+ $referenceUnified = $temp->unify();
+ } else {
+ $referenceUnified = $order[$j]->unify();
+ }
+ if ( ( $isMin && $currentUnified->value < $referenceUnified->value ) || ( !$isMin && $currentUnified->value > $referenceUnified->value ) ) {
+ $order[$j] = $current;
+ }
+ }
+
+ if ( count( $order ) == 1 ) {
+ return $order[0];
+ }
+ $args = [];
+ foreach ( $order as $a ) {
+ $args[] = $a->toCSS();
+ }
+ return new Less_Tree_Anonymous( ( $isMin ? 'min(' : 'max(' ) . implode( Less_Environment::$_outputMap[','], $args ) . ')' );
+ }
+
+ public function min() {
+ $args = func_get_args();
+ return $this->_minmax( true, $args );
+ }
+
+ public function max() {
+ $args = func_get_args();
+ return $this->_minmax( false, $args );
+ }
+
+ public function getunit( $n ) {
+ return new Less_Tree_Anonymous( $n->unit );
+ }
+
+ public function argb( $color ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to argb must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Anonymous( $color->toARGB() );
+ }
+
+ public function percentage( $n ) {
+ return new Less_Tree_Dimension( $n->value * 100, '%' );
+ }
+
+ public function color( $n ) {
+ if ( $n instanceof Less_Tree_Quoted ) {
+ $colorCandidate = $n->value;
+ $returnColor = Less_Tree_Color::fromKeyword( $colorCandidate );
+ if ( $returnColor ) {
+ return $returnColor;
+ }
+ if ( preg_match( '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/', $colorCandidate ) ) {
+ return new Less_Tree_Color( substr( $colorCandidate, 1 ) );
+ }
+ throw new Less_Exception_Compiler( "argument must be a color keyword or 3/6 digit hex e.g. #FFF" );
+ } else {
+ throw new Less_Exception_Compiler( "argument must be a string" );
+ }
+ }
+
+ public function iscolor( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Color' );
+ }
+
+ public function isnumber( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Dimension' );
+ }
+
+ public function isstring( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Quoted' );
+ }
+
+ public function iskeyword( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Keyword' );
+ }
+
+ public function isurl( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Url' );
+ }
+
+ public function ispixel( $n ) {
+ return $this->isunit( $n, 'px' );
+ }
+
+ public function ispercentage( $n ) {
+ return $this->isunit( $n, '%' );
+ }
+
+ public function isem( $n ) {
+ return $this->isunit( $n, 'em' );
+ }
+
+ /**
+ * @param Less_Tree $n
+ * @param Less_Tree|string $unit
+ */
+ public function isunit( $n, $unit ) {
+ if ( is_object( $unit ) && property_exists( $unit, 'value' ) ) {
+ // @phan-suppress-next-line PhanUndeclaredProperty Checked Less_Tree->value
+ $unit = $unit->value;
+ }
+
+ return ( $n instanceof Less_Tree_Dimension ) && $n->unit->is( $unit ) ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
+ }
+
+ /**
+ * @param Less_Tree $n
+ * @param string $type
+ */
+ private function _isa( $n, $type ) {
+ return is_a( $n, $type ) ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
+ }
+
+ public function tint( $color, $amount = null ) {
+ return $this->mix( $this->rgb( 255, 255, 255 ), $color, $amount );
+ }
+
+ public function shade( $color, $amount = null ) {
+ return $this->mix( $this->rgb( 0, 0, 0 ), $color, $amount );
+ }
+
+ public function extract( $values, $index ) {
+ $index = (int)$index->value - 1; // (1-based index)
+ // handle non-array values as an array of length 1
+ // return 'undefined' if index is invalid
+ if ( property_exists( $values, 'value' ) && is_array( $values->value ) ) {
+ if ( isset( $values->value[$index] ) ) {
+ return $values->value[$index];
+ }
+ return null;
+
+ } elseif ( (int)$index === 0 ) {
+ return $values;
+ }
+
+ return null;
+ }
+
+ public function length( $values ) {
+ $n = ( property_exists( $values, 'value' ) && is_array( $values->value ) ) ? count( $values->value ) : 1;
+ return new Less_Tree_Dimension( $n );
+ }
+
+ public function datauri( $mimetypeNode, $filePathNode = null ) {
+ $filePath = ( $filePathNode ? $filePathNode->value : null );
+ $mimetype = $mimetypeNode->value;
+
+ $args = 2;
+ if ( !$filePath ) {
+ $filePath = $mimetype;
+ $args = 1;
+ }
+
+ $filePath = str_replace( '\\', '/', $filePath );
+ if ( Less_Environment::isPathRelative( $filePath ) ) {
+ $currentFileInfo = $this->currentFileInfo;
+ '@phan-var array $currentFileInfo';
+ if ( Less_Parser::$options['relativeUrls'] ) {
+ $temp = $currentFileInfo['currentDirectory'];
+ } else {
+ $temp = $currentFileInfo['entryPath'];
+ }
+
+ if ( !empty( $temp ) ) {
+ $filePath = Less_Environment::normalizePath( rtrim( $temp, '/' ) . '/' . $filePath );
+ }
+
+ }
+
+ // detect the mimetype if not given
+ if ( $args < 2 ) {
+
+ /* incomplete
+ $mime = require('mime');
+ mimetype = mime.lookup(path);
+
+ // use base 64 unless it's an ASCII or UTF-8 format
+ var charset = mime.charsets.lookup(mimetype);
+ useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
+ if (useBase64) mimetype += ';base64';
+ */
+
+ $mimetype = Less_Mime::lookup( $filePath );
+
+ $charset = Less_Mime::charsets_lookup( $mimetype );
+ $useBase64 = !in_array( $charset, [ 'US-ASCII', 'UTF-8' ] );
+ if ( $useBase64 ) { $mimetype .= ';base64';
+ }
+
+ } else {
+ $useBase64 = preg_match( '/;base64$/', $mimetype );
+ }
+
+ if ( file_exists( $filePath ) ) {
+ $buf = @file_get_contents( $filePath );
+ } else {
+ $buf = false;
+ }
+
+ // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
+ // and the --ieCompat flag is enabled, return a normal url() instead.
+ $DATA_URI_MAX_KB = 32;
+ $fileSizeInKB = round( strlen( $buf ) / 1024 );
+ if ( $fileSizeInKB >= $DATA_URI_MAX_KB ) {
+ $url = new Less_Tree_Url( ( $filePathNode ?: $mimetypeNode ), $this->currentFileInfo );
+ return $url->compile( $this->env );
+ }
+
+ if ( $buf ) {
+ $buf = $useBase64 ? base64_encode( $buf ) : rawurlencode( $buf );
+ $filePath = '"data:' . $mimetype . ',' . $buf . '"';
+ }
+
+ return new Less_Tree_Url( new Less_Tree_Anonymous( $filePath ) );
+ }
+
+ // svg-gradient
+ public function svggradient( $direction ) {
+ $throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
+ $arguments = func_get_args();
+
+ if ( count( $arguments ) < 3 ) {
+ throw new Less_Exception_Compiler( $throw_message );
+ }
+
+ $stops = array_slice( $arguments, 1 );
+ $gradientType = 'linear';
+ $rectangleDimension = 'x="0" y="0" width="1" height="1"';
+ $useBase64 = true;
+ $directionValue = $direction->toCSS();
+
+ switch ( $directionValue ) {
+ case "to bottom":
+ $gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
+ break;
+ case "to right":
+ $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
+ break;
+ case "to bottom right":
+ $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
+ break;
+ case "to top right":
+ $gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
+ break;
+ case "ellipse":
+ case "ellipse at center":
+ $gradientType = "radial";
+ $gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
+ $rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
+ break;
+ default:
+ throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
+ }
+
+ $returner = '<?xml version="1.0" ?>' .
+ '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
+ '<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
+
+ for ( $i = 0; $i < count( $stops ); $i++ ) {
+ if ( is_object( $stops[$i] ) && property_exists( $stops[$i], 'value' ) ) {
+ $color = $stops[$i]->value[0];
+ $position = $stops[$i]->value[1];
+ } else {
+ $color = $stops[$i];
+ $position = null;
+ }
+
+ if ( !( $color instanceof Less_Tree_Color ) || ( !( ( $i === 0 || $i + 1 === count( $stops ) ) && $position === null ) && !( $position instanceof Less_Tree_Dimension ) ) ) {
+ throw new Less_Exception_Compiler( $throw_message );
+ }
+ if ( $position ) {
+ $positionValue = $position->toCSS();
+ } elseif ( $i === 0 ) {
+ $positionValue = '0%';
+ } else {
+ $positionValue = '100%';
+ }
+ $alpha = $color->alpha;
+ $returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ( $alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '' ) . '/>';
+ }
+
+ $returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
+
+ if ( $useBase64 ) {
+ $returner = "'data:image/svg+xml;base64," . base64_encode( $returner ) . "'";
+ } else {
+ $returner = "'data:image/svg+xml," . $returner . "'";
+ }
+
+ return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) );
+ }
+
+ /**
+ * Php version of javascript's `encodeURIComponent` function
+ *
+ * @param string $string The string to encode
+ * @return string The encoded string
+ */
+ public static function encodeURIComponent( $string ) {
+ $revert = [ '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' ];
+ return strtr( rawurlencode( $string ), $revert );
+ }
+
+ // Color Blending
+ // ref: https://www.w3.org/TR/compositing-1/
+ public function colorBlend( $mode, $color1, $color2 ) {
+ // backdrop
+ $ab = $color1->alpha;
+ // source
+ $as = $color2->alpha;
+ $result = [];
+
+ $ar = $as + $ab * ( 1 - $as );
+ for ( $i = 0; $i < 3; $i++ ) {
+ $cb = $color1->rgb[$i] / 255;
+ $cs = $color2->rgb[$i] / 255;
+ $cr = call_user_func( $mode, $cb, $cs );
+ if ( $ar ) {
+ $cr = ( $as * $cs + $ab * ( $cb - $as * ( $cb + $cs - $cr ) ) ) / $ar;
+ }
+ $result[$i] = $cr * 255;
+ }
+
+ return new Less_Tree_Color( $result, $ar );
+ }
+
+ public function multiply( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to multiply must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to multiply must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( [ $this,'colorBlendMultiply' ], $color1, $color2 );
+ }
+
+ private function colorBlendMultiply( $cb, $cs ) {
+ return $cb * $cs;
+ }
+
+ public function screen( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to screen must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to screen must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( [ $this,'colorBlendScreen' ], $color1, $color2 );
+ }
+
+ private function colorBlendScreen( $cb, $cs ) {
+ return $cb + $cs - $cb * $cs;
+ }
+
+ public function overlay( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to overlay must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to overlay must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( [ $this,'colorBlendOverlay' ], $color1, $color2 );
+ }
+
+ private function colorBlendOverlay( $cb, $cs ) {
+ $cb *= 2;
+ return ( $cb <= 1 )
+ ? $this->colorBlendMultiply( $cb, $cs )
+ : $this->colorBlendScreen( $cb - 1, $cs );
+ }
+
+ public function softlight( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to softlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to softlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( [ $this,'colorBlendSoftlight' ], $color1, $color2 );
+ }
+
+ private function colorBlendSoftlight( $cb, $cs ) {
+ $d = 1;
+ $e = $cb;
+ if ( $cs > 0.5 ) {
+ $e = 1;
+ $d = ( $cb > 0.25 ) ? sqrt( $cb )
+ : ( ( 16 * $cb - 12 ) * $cb + 4 ) * $cb;
+ }
+ return $cb - ( 1 - 2 * $cs ) * $e * ( $d - $cb );
+ }
+
+ public function hardlight( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hardlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to hardlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( [ $this,'colorBlendHardlight' ], $color1, $color2 );
+ }
+
+ private function colorBlendHardlight( $cb, $cs ) {
+ return $this->colorBlendOverlay( $cs, $cb );
+ }
+
+ public function difference( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to difference must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to difference must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( [ $this,'colorBlendDifference' ], $color1, $color2 );
+ }
+
+ private function colorBlendDifference( $cb, $cs ) {
+ return abs( $cb - $cs );
+ }
+
+ public function exclusion( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to exclusion must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to exclusion must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( [ $this,'colorBlendExclusion' ], $color1, $color2 );
+ }
+
+ private function colorBlendExclusion( $cb, $cs ) {
+ return $cb + $cs - 2 * $cb * $cs;
+ }
+
+ public function average( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to average must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to average must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( [ $this,'colorBlendAverage' ], $color1, $color2 );
+ }
+
+ // non-w3c functions:
+ public function colorBlendAverage( $cb, $cs ) {
+ return ( $cb + $cs ) / 2;
+ }
+
+ public function negation( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to negation must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to negation must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( [ $this,'colorBlendNegation' ], $color1, $color2 );
+ }
+
+ public function colorBlendNegation( $cb, $cs ) {
+ return 1 - abs( $cb + $cs - 1 );
+ }
+
+ // ~ End of Color Blending
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Mime.php b/vendor/wikimedia/less.php/lib/Less/Mime.php
new file mode 100644
index 0000000..45a7bf3
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Mime.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Mime lookup
+ *
+ * @private
+ */
+class Less_Mime {
+
+ // this map is intentionally incomplete
+ // if you want more, install 'mime' dep
+ private static $types = [
+ '.htm' => 'text/html',
+ '.html' => 'text/html',
+ '.gif' => 'image/gif',
+ '.jpg' => 'image/jpeg',
+ '.jpeg' => 'image/jpeg',
+ '.png' => 'image/png',
+ '.ttf' => 'application/x-font-ttf',
+ '.otf' => 'application/x-font-otf',
+ '.eot' => 'application/vnd.ms-fontobject',
+ '.woff' => 'application/x-font-woff',
+ '.svg' => 'image/svg+xml',
+ ];
+
+ public static function lookup( $filepath ) {
+ $parts = explode( '.', $filepath );
+ $ext = '.' . strtolower( array_pop( $parts ) );
+
+ return self::$types[$ext] ?? null;
+ }
+
+ public static function charsets_lookup( $type = null ) {
+ // assumes all text types are UTF-8
+ return $type && preg_match( '/^text\//', $type ) ? 'UTF-8' : '';
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Output.php b/vendor/wikimedia/less.php/lib/Less/Output.php
new file mode 100644
index 0000000..be6c160
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Output.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Parser output
+ *
+ * @private
+ */
+class Less_Output {
+
+ /**
+ * Output holder
+ *
+ * @var string[]
+ */
+ protected $strs = [];
+
+ /**
+ * Adds a chunk to the stack
+ *
+ * @param string $chunk The chunk to output
+ * @param array|null $fileInfo The file information
+ * @param int $index The index
+ * @param mixed $mapLines
+ */
+ public function add( $chunk, $fileInfo = null, $index = 0, $mapLines = null ) {
+ $this->strs[] = $chunk;
+ }
+
+ /**
+ * Is the output empty?
+ *
+ * @return bool
+ */
+ public function isEmpty() {
+ return count( $this->strs ) === 0;
+ }
+
+ /**
+ * Converts the output to string
+ *
+ * @return string
+ */
+ public function toString() {
+ return implode( '', $this->strs );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Output/Mapped.php b/vendor/wikimedia/less.php/lib/Less/Output/Mapped.php
new file mode 100644
index 0000000..ece5172
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Output/Mapped.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * Parser output with source map
+ *
+ * @private
+ */
+class Less_Output_Mapped extends Less_Output {
+
+ /**
+ * The source map generator
+ *
+ * @var Less_SourceMap_Generator
+ */
+ protected $generator;
+
+ /**
+ * Current line
+ *
+ * @var int
+ */
+ protected $lineNumber = 0;
+
+ /**
+ * Current column
+ *
+ * @var int
+ */
+ protected $column = 0;
+
+ /**
+ * Array of contents map (file and its content)
+ *
+ * @var array
+ */
+ protected $contentsMap = [];
+
+ /**
+ * Constructor
+ *
+ * @param array $contentsMap Array of filename to contents map
+ * @param Less_SourceMap_Generator $generator
+ */
+ public function __construct( array $contentsMap, $generator ) {
+ $this->contentsMap = $contentsMap;
+ $this->generator = $generator;
+ }
+
+ /**
+ * Adds a chunk to the stack
+ * The $index for less.php may be different from less.js since less.php does not chunkify inputs
+ *
+ * @param string $chunk
+ * @param array|null $fileInfo
+ * @param int $index
+ * @param mixed $mapLines
+ */
+ public function add( $chunk, $fileInfo = null, $index = 0, $mapLines = null ) {
+ // ignore adding empty strings
+ if ( $chunk === '' ) {
+ return;
+ }
+
+ $sourceLines = [];
+ $sourceColumns = ' ';
+
+ if ( $fileInfo ) {
+
+ $url = $fileInfo['currentUri'];
+
+ if ( isset( $this->contentsMap[$url] ) ) {
+ $inputSource = substr( $this->contentsMap[$url], 0, $index );
+ $sourceLines = explode( "\n", $inputSource );
+ $sourceColumns = end( $sourceLines );
+ } else {
+ throw new Exception( 'Filename ' . $url . ' not in contentsMap' );
+ }
+
+ }
+
+ $lines = explode( "\n", $chunk );
+ $columns = end( $lines );
+
+ if ( $fileInfo ) {
+
+ if ( !$mapLines ) {
+ $this->generator->addMapping(
+ $this->lineNumber + 1, // generated_line
+ $this->column, // generated_column
+ count( $sourceLines ), // original_line
+ strlen( $sourceColumns ), // original_column
+ $fileInfo
+ );
+ } else {
+ for ( $i = 0, $count = count( $lines ); $i < $count; $i++ ) {
+ $this->generator->addMapping(
+ $this->lineNumber + $i + 1, // generated_line
+ $i === 0 ? $this->column : 0, // generated_column
+ count( $sourceLines ) + $i, // original_line
+ $i === 0 ? strlen( $sourceColumns ) : 0, // original_column
+ $fileInfo
+ );
+ }
+ }
+ }
+
+ if ( count( $lines ) === 1 ) {
+ $this->column += strlen( $columns );
+ } else {
+ $this->lineNumber += count( $lines ) - 1;
+ $this->column = strlen( $columns );
+ }
+
+ // add only chunk
+ parent::add( $chunk );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Parser.php b/vendor/wikimedia/less.php/lib/Less/Parser.php
new file mode 100644
index 0000000..b05bf14
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Parser.php
@@ -0,0 +1,2729 @@
+<?php
+
+/**
+ * Parse and compile Less files into CSS
+ */
+class Less_Parser {
+
+ /**
+ * Default parser options
+ */
+ public static $default_options = [
+ 'compress' => false, // option - whether to compress
+ 'strictUnits' => false, // whether units need to evaluate correctly
+ 'strictMath' => false, // whether math has to be within parenthesis
+ 'relativeUrls' => true, // option - whether to adjust URL's to be relative
+ 'urlArgs' => '', // whether to add args into url tokens
+ 'numPrecision' => 8,
+
+ 'import_dirs' => [],
+ 'import_callback' => null,
+ 'cache_dir' => null,
+ 'cache_method' => 'php', // false, 'serialize', 'php', 'var_export', 'callback';
+ 'cache_callback_get' => null,
+ 'cache_callback_set' => null,
+
+ 'sourceMap' => false, // whether to output a source map
+ 'sourceMapBasepath' => null,
+ 'sourceMapWriteTo' => null,
+ 'sourceMapURL' => null,
+
+ 'indentation' => ' ',
+
+ 'plugins' => [],
+
+ ];
+
+ /** @var array{compress:bool,strictUnits:bool,strictMath:bool,numPrecision:int,import_dirs:array,import_callback:null|callable,indentation:string} */
+ public static $options = [];
+
+ private $input; // Less input string
+ private $input_len; // input string length
+ private $pos; // current index in `input`
+ private $saveStack = []; // holds state for backtracking
+ private $furthest;
+ private $mb_internal_encoding = ''; // for remember exists value of mbstring.internal_encoding
+
+ /**
+ * @var Less_Environment
+ */
+ private $env;
+
+ protected $rules = [];
+
+ private static $imports = [];
+
+ public static $has_extends = false;
+
+ public static $next_id = 0;
+
+ /**
+ * Filename to contents of all parsed the files
+ *
+ * @var array
+ */
+ public static $contentsMap = [];
+
+ /**
+ * @param Less_Environment|array|null $env
+ */
+ public function __construct( $env = null ) {
+ // Top parser on an import tree must be sure there is one "env"
+ // which will then be passed around by reference.
+ if ( $env instanceof Less_Environment ) {
+ $this->env = $env;
+ } else {
+ $this->SetOptions( self::$default_options );
+ $this->Reset( $env );
+ }
+
+ // mbstring.func_overload > 1 bugfix
+ // The encoding value must be set for each source file,
+ // therefore, to conserve resources and improve the speed of this design is taken here
+ if ( ini_get( 'mbstring.func_overload' ) ) {
+ $this->mb_internal_encoding = ini_get( 'mbstring.internal_encoding' );
+ @ini_set( 'mbstring.internal_encoding', 'ascii' );
+ }
+ }
+
+ /**
+ * Reset the parser state completely
+ */
+ public function Reset( $options = null ) {
+ $this->rules = [];
+ self::$imports = [];
+ self::$has_extends = false;
+ self::$imports = [];
+ self::$contentsMap = [];
+
+ $this->env = new Less_Environment();
+
+ // set new options
+ if ( is_array( $options ) ) {
+ $this->SetOptions( self::$default_options );
+ $this->SetOptions( $options );
+ }
+
+ $this->env->Init();
+ }
+
+ /**
+ * Set one or more compiler options
+ * options: import_dirs, cache_dir, cache_method
+ */
+ public function SetOptions( $options ) {
+ foreach ( $options as $option => $value ) {
+ $this->SetOption( $option, $value );
+ }
+ }
+
+ /**
+ * Set one compiler option
+ */
+ public function SetOption( $option, $value ) {
+ switch ( $option ) {
+
+ case 'import_dirs':
+ $this->SetImportDirs( $value );
+ return;
+
+ case 'cache_dir':
+ if ( is_string( $value ) ) {
+ Less_Cache::SetCacheDir( $value );
+ Less_Cache::CheckCacheDir();
+ }
+ return;
+ }
+
+ self::$options[$option] = $value;
+ }
+
+ /**
+ * Registers a new custom function
+ *
+ * @param string $name function name
+ * @param callable $callback callback
+ */
+ public function registerFunction( $name, $callback ) {
+ $this->env->functions[$name] = $callback;
+ }
+
+ /**
+ * Removed an already registered function
+ *
+ * @param string $name function name
+ */
+ public function unregisterFunction( $name ) {
+ if ( isset( $this->env->functions[$name] ) ) {
+ unset( $this->env->functions[$name] );
+ }
+ }
+
+ /**
+ * Get the current css buffer
+ *
+ * @return string
+ */
+ public function getCss() {
+ $precision = ini_get( 'precision' );
+ @ini_set( 'precision', '16' );
+ $locale = setlocale( LC_NUMERIC, 0 );
+ setlocale( LC_NUMERIC, "C" );
+
+ try {
+ $root = new Less_Tree_Ruleset( null, $this->rules );
+ $root->root = true;
+ $root->firstRoot = true;
+
+ $this->PreVisitors( $root );
+
+ self::$has_extends = false;
+ $evaldRoot = $root->compile( $this->env );
+
+ $this->PostVisitors( $evaldRoot );
+
+ if ( self::$options['sourceMap'] ) {
+ $generator = new Less_SourceMap_Generator( $evaldRoot, self::$contentsMap, self::$options );
+ // will also save file
+ // FIXME: should happen somewhere else?
+ $css = $generator->generateCSS();
+ } else {
+ $css = $evaldRoot->toCSS();
+ }
+
+ if ( self::$options['compress'] ) {
+ $css = preg_replace( '/(^(\s)+)|((\s)+$)/', '', $css );
+ }
+
+ } catch ( Exception $exc ) {
+ // Intentional fall-through so we can reset environment
+ }
+
+ // reset php settings
+ @ini_set( 'precision', $precision );
+ setlocale( LC_NUMERIC, $locale );
+
+ // If you previously defined $this->mb_internal_encoding
+ // is required to return the encoding as it was before
+ if ( $this->mb_internal_encoding != '' ) {
+ @ini_set( "mbstring.internal_encoding", $this->mb_internal_encoding );
+ $this->mb_internal_encoding = '';
+ }
+
+ // Rethrow exception after we handled resetting the environment
+ if ( !empty( $exc ) ) {
+ throw $exc;
+ }
+
+ return $css;
+ }
+
+ public function findValueOf( $varName ) {
+ foreach ( $this->rules as $rule ) {
+ if ( isset( $rule->variable ) && ( $rule->variable == true ) && ( str_replace( "@", "", $rule->name ) == $varName ) ) {
+ return $this->getVariableValue( $rule );
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the private rules variable and returns an array of the found variables
+ * it uses a helper method getVariableValue() that contains the logic ot fetch the value
+ * from the rule object
+ *
+ * @return array
+ */
+ public function getVariables() {
+ $variables = [];
+
+ $not_variable_type = [
+ 'Comment', // this include less comments ( // ) and css comments (/* */)
+ 'Import', // do not search variables in included files @import
+ 'Ruleset', // selectors (.someclass, #someid, …)
+ 'Operation', //
+ ];
+
+ // @TODO run compilation if not runned yet
+ foreach ( $this->rules as $key => $rule ) {
+ if ( in_array( $rule->type, $not_variable_type ) ) {
+ continue;
+ }
+
+ // Note: it seems rule->type is always Rule when variable = true
+ if ( $rule->type == 'Rule' && $rule->variable ) {
+ $variables[$rule->name] = $this->getVariableValue( $rule );
+ } else {
+ if ( $rule->type == 'Comment' ) {
+ $variables[] = $this->getVariableValue( $rule );
+ }
+ }
+ }
+ return $variables;
+ }
+
+ public function findVarByName( $var_name ) {
+ foreach ( $this->rules as $rule ) {
+ if ( isset( $rule->variable ) && ( $rule->variable == true ) ) {
+ if ( $rule->name == $var_name ) {
+ return $this->getVariableValue( $rule );
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This method gets the value of the less variable from the rules object.
+ * Since the objects vary here we add the logic for extracting the css/less value.
+ *
+ * @param Less_Tree $var
+ * @return string
+ */
+ private function getVariableValue( Less_Tree $var ) {
+ switch ( get_class( $var ) ) {
+ case Less_Tree_Color::class:
+ return $this->rgb2html( $var->rgb );
+ case Less_Tree_Variable::class:
+ return $this->findVarByName( $var->name );
+ case Less_Tree_Keyword::class:
+ return $var->value;
+ case Less_Tree_Url::class:
+ // Based on Less_Tree_Url::genCSS()
+ // Recurse to serialize the Less_Tree_Quoted value
+ return 'url(' . $this->getVariableValue( $var->value ) . ')';
+ case Less_Tree_Rule::class:
+ return $this->getVariableValue( $var->value );
+ case Less_Tree_Value::class:
+ $value = '';
+ foreach ( $var->value as $sub_value ) {
+ $value .= $this->getVariableValue( $sub_value ) . ' ';
+ }
+ return $value;
+ case Less_Tree_Quoted::class:
+ return $var->quote . $var->value . $var->quote;
+ case Less_Tree_Dimension::class:
+ $value = $var->value;
+ if ( $var->unit && $var->unit->numerator ) {
+ $value .= $var->unit->numerator[0];
+ }
+ return $value;
+ case Less_Tree_Expression::class:
+ $value = '';
+ foreach ( $var->value as $item ) {
+ $value .= $this->getVariableValue( $item ) . " ";
+ }
+ return $value;
+ case Less_Tree_Operation::class:
+ throw new Exception( 'getVariables() require Less to be compiled. please use $parser->getCss() before calling getVariables()' );
+ case Less_Tree_Unit::class:
+ case Less_Tree_Comment::class:
+ case Less_Tree_Import::class:
+ case Less_Tree_Ruleset::class:
+ default:
+ throw new Exception( "type missing in switch/case getVariableValue for " . $var->type );
+ }
+ }
+
+ private function rgb2html( $r, $g = -1, $b = -1 ) {
+ if ( is_array( $r ) && count( $r ) == 3 ) {
+ list( $r, $g, $b ) = $r;
+ }
+
+ $r = intval( $r );
+$g = intval( $g );
+ $b = intval( $b );
+
+ $r = dechex( $r < 0 ? 0 : ( $r > 255 ? 255 : $r ) );
+ $g = dechex( $g < 0 ? 0 : ( $g > 255 ? 255 : $g ) );
+ $b = dechex( $b < 0 ? 0 : ( $b > 255 ? 255 : $b ) );
+
+ $color = ( strlen( $r ) < 2 ? '0' : '' ) . $r;
+ $color .= ( strlen( $g ) < 2 ? '0' : '' ) . $g;
+ $color .= ( strlen( $b ) < 2 ? '0' : '' ) . $b;
+ return '#' . $color;
+ }
+
+ /**
+ * Run pre-compile visitors
+ */
+ private function PreVisitors( $root ) {
+ if ( self::$options['plugins'] ) {
+ foreach ( self::$options['plugins'] as $plugin ) {
+ if ( !empty( $plugin->isPreEvalVisitor ) ) {
+ $plugin->run( $root );
+ }
+ }
+ }
+ }
+
+ /**
+ * Run post-compile visitors
+ */
+ private function PostVisitors( $evaldRoot ) {
+ $visitors = [];
+ $visitors[] = new Less_Visitor_joinSelector();
+ if ( self::$has_extends ) {
+ $visitors[] = new Less_Visitor_processExtends();
+ }
+ $visitors[] = new Less_Visitor_toCSS();
+
+ if ( self::$options['plugins'] ) {
+ foreach ( self::$options['plugins'] as $plugin ) {
+ if ( property_exists( $plugin, 'isPreEvalVisitor' ) && $plugin->isPreEvalVisitor ) {
+ continue;
+ }
+
+ if ( property_exists( $plugin, 'isPreVisitor' ) && $plugin->isPreVisitor ) {
+ array_unshift( $visitors, $plugin );
+ } else {
+ $visitors[] = $plugin;
+ }
+ }
+ }
+
+ for ( $i = 0; $i < count( $visitors ); $i++ ) {
+ $visitors[$i]->run( $evaldRoot );
+ }
+ }
+
+ /**
+ * Parse a Less string
+ *
+ * @throws Less_Exception_Parser If the compiler encounters invalid syntax
+ * @param string $str The string to convert
+ * @param string|null $file_uri The url of the file
+ * @return Less_Parser
+ */
+ public function parse( $str, $file_uri = null ) {
+ if ( !$file_uri ) {
+ $uri_root = '';
+ $filename = 'anonymous-file-' . self::$next_id++ . '.less';
+ } else {
+ $file_uri = self::WinPath( $file_uri );
+ $filename = $file_uri;
+ $uri_root = dirname( $file_uri );
+ }
+
+ $previousFileInfo = $this->env->currentFileInfo;
+ $uri_root = self::WinPath( $uri_root );
+ $this->SetFileInfo( $filename, $uri_root );
+
+ $this->input = $str;
+ $this->_parse();
+
+ if ( $previousFileInfo ) {
+ $this->env->currentFileInfo = $previousFileInfo;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Parse a Less string from a given file
+ *
+ * @throws Less_Exception_Parser If the compiler encounters invalid syntax
+ * @param string $filename The file to parse
+ * @param string $uri_root The url of the file
+ * @param bool $returnRoot Indicates whether the return value should be a css string a root node
+ * @return Less_Tree_Ruleset|Less_Parser
+ */
+ public function parseFile( $filename, $uri_root = '', $returnRoot = false ) {
+ if ( !file_exists( $filename ) ) {
+ $this->Error( sprintf( 'File `%s` not found.', $filename ) );
+ }
+
+ // fix uri_root?
+ // Instead of The mixture of file path for the first argument and directory path for the second argument has bee
+ if ( !$returnRoot && !empty( $uri_root ) && basename( $uri_root ) == basename( $filename ) ) {
+ $uri_root = dirname( $uri_root );
+ }
+
+ $previousFileInfo = $this->env->currentFileInfo;
+
+ if ( $filename ) {
+ $filename = self::AbsPath( $filename, true );
+ }
+ $uri_root = self::WinPath( $uri_root );
+
+ $this->SetFileInfo( $filename, $uri_root );
+
+ self::AddParsedFile( $filename );
+
+ if ( $returnRoot ) {
+ $rules = $this->GetRules( $filename );
+ $return = new Less_Tree_Ruleset( null, $rules );
+ } else {
+ $this->_parse( $filename );
+ $return = $this;
+ }
+
+ if ( $previousFileInfo ) {
+ $this->env->currentFileInfo = $previousFileInfo;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Allows a user to set variables values
+ * @param array $vars
+ * @return Less_Parser
+ */
+ public function ModifyVars( $vars ) {
+ $this->input = self::serializeVars( $vars );
+ $this->_parse();
+
+ return $this;
+ }
+
+ /**
+ * @param string $filename
+ * @param string $uri_root
+ */
+ public function SetFileInfo( $filename, $uri_root = '' ) {
+ $filename = Less_Environment::normalizePath( $filename );
+ $dirname = preg_replace( '/[^\/\\\\]*$/', '', $filename );
+
+ if ( !empty( $uri_root ) ) {
+ $uri_root = rtrim( $uri_root, '/' ) . '/';
+ }
+
+ $currentFileInfo = [];
+
+ // entry info
+ if ( isset( $this->env->currentFileInfo ) ) {
+ $currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
+ $currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
+ $currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];
+
+ } else {
+ $currentFileInfo['entryPath'] = $dirname;
+ $currentFileInfo['entryUri'] = $uri_root;
+ $currentFileInfo['rootpath'] = $dirname;
+ }
+
+ $currentFileInfo['currentDirectory'] = $dirname;
+ $currentFileInfo['currentUri'] = $uri_root . basename( $filename );
+ $currentFileInfo['filename'] = $filename;
+ $currentFileInfo['uri_root'] = $uri_root;
+
+ // inherit reference
+ if ( isset( $this->env->currentFileInfo['reference'] ) && $this->env->currentFileInfo['reference'] ) {
+ $currentFileInfo['reference'] = true;
+ }
+
+ $this->env->currentFileInfo = $currentFileInfo;
+ }
+
+ /**
+ * @deprecated 1.5.1.2
+ */
+ public function SetCacheDir( $dir ) {
+ if ( !file_exists( $dir ) ) {
+ if ( mkdir( $dir ) ) {
+ return true;
+ }
+ throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: ' . $dir );
+
+ } elseif ( !is_dir( $dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: ' . $dir );
+
+ } elseif ( !is_writable( $dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: ' . $dir );
+
+ } else {
+ $dir = self::WinPath( $dir );
+ Less_Cache::$cache_dir = rtrim( $dir, '/' ) . '/';
+ return true;
+ }
+ }
+
+ /**
+ * Set a list of directories or callbacks the parser should use for determining import paths
+ *
+ * @param array $dirs
+ */
+ public function SetImportDirs( $dirs ) {
+ self::$options['import_dirs'] = [];
+
+ foreach ( $dirs as $path => $uri_root ) {
+
+ $path = self::WinPath( $path );
+ if ( !empty( $path ) ) {
+ $path = rtrim( $path, '/' ) . '/';
+ }
+
+ if ( !is_callable( $uri_root ) ) {
+ $uri_root = self::WinPath( $uri_root );
+ if ( !empty( $uri_root ) ) {
+ $uri_root = rtrim( $uri_root, '/' ) . '/';
+ }
+ }
+
+ self::$options['import_dirs'][$path] = $uri_root;
+ }
+ }
+
+ /**
+ * @param string|null $file_path
+ */
+ private function _parse( $file_path = null ) {
+ $this->rules = array_merge( $this->rules, $this->GetRules( $file_path ) );
+ }
+
+ /**
+ * Return the results of parsePrimary for $file_path
+ * Use cache and save cached results if possible
+ *
+ * @param string|null $file_path
+ */
+ private function GetRules( $file_path ) {
+ $this->SetInput( $file_path );
+
+ $cache_file = $this->CacheFile( $file_path );
+ if ( $cache_file ) {
+ if ( self::$options['cache_method'] == 'callback' ) {
+ if ( is_callable( self::$options['cache_callback_get'] ) ) {
+ $cache = call_user_func_array(
+ self::$options['cache_callback_get'],
+ [ $this, $file_path, $cache_file ]
+ );
+
+ if ( $cache ) {
+ $this->UnsetInput();
+ return $cache;
+ }
+ }
+
+ } elseif ( file_exists( $cache_file ) ) {
+ switch ( self::$options['cache_method'] ) {
+
+ // Using serialize
+ // Faster but uses more memory
+ case 'serialize':
+ $cache = unserialize( file_get_contents( $cache_file ) );
+ if ( $cache ) {
+ touch( $cache_file );
+ $this->UnsetInput();
+ return $cache;
+ }
+ break;
+
+ // Using generated php code
+ case 'var_export':
+ case 'php':
+ $this->UnsetInput();
+ return include $cache_file;
+ }
+ }
+ }
+
+ $rules = $this->parsePrimary();
+
+ if ( $this->pos < $this->input_len ) {
+ throw new Less_Exception_Chunk( $this->input, null, $this->furthest, $this->env->currentFileInfo );
+ }
+
+ $this->UnsetInput();
+
+ // save the cache
+ if ( $cache_file ) {
+ if ( self::$options['cache_method'] == 'callback' ) {
+ if ( is_callable( self::$options['cache_callback_set'] ) ) {
+ call_user_func_array(
+ self::$options['cache_callback_set'],
+ [ $this, $file_path, $cache_file, $rules ]
+ );
+ }
+
+ } else {
+ switch ( self::$options['cache_method'] ) {
+ case 'serialize':
+ file_put_contents( $cache_file, serialize( $rules ) );
+ break;
+ case 'php':
+ // Mask PHP open tag to avoid breaking Doxygen
+ file_put_contents( $cache_file, '<' . '?php return ' . self::ArgString( $rules ) . '; ?>' );
+ break;
+ case 'var_export':
+ // Requires __set_state()
+ file_put_contents( $cache_file, '<' . '?php return ' . var_export( $rules, true ) . '; ?>' );
+ break;
+ }
+
+ Less_Cache::CleanCache();
+ }
+ }
+
+ return $rules;
+ }
+
+ /**
+ * Set up the input buffer
+ */
+ public function SetInput( $file_path ) {
+ if ( $file_path ) {
+ $this->input = file_get_contents( $file_path );
+ }
+
+ $this->pos = $this->furthest = 0;
+
+ // Remove potential UTF Byte Order Mark
+ $this->input = preg_replace( '/\\G\xEF\xBB\xBF/', '', $this->input );
+ $this->input_len = strlen( $this->input );
+
+ if ( self::$options['sourceMap'] && $this->env->currentFileInfo ) {
+ $uri = $this->env->currentFileInfo['currentUri'];
+ self::$contentsMap[$uri] = $this->input;
+ }
+ }
+
+ /**
+ * Free up some memory
+ */
+ public function UnsetInput() {
+ $this->input = $this->pos = $this->input_len = $this->furthest = null;
+ $this->saveStack = [];
+ }
+
+ public function CacheFile( $file_path ) {
+ if ( $file_path && $this->CacheEnabled() ) {
+
+ $env = get_object_vars( $this->env );
+ unset( $env['frames'] );
+
+ $parts = [];
+ $parts[] = $file_path;
+ $parts[] = filesize( $file_path );
+ $parts[] = filemtime( $file_path );
+ $parts[] = $env;
+ $parts[] = Less_Version::cache_version;
+ $parts[] = self::$options['cache_method'];
+ return Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1( json_encode( $parts ) ), 16, 36 ) . '.lesscache';
+ }
+ }
+
+ static function AddParsedFile( $file ) {
+ self::$imports[] = $file;
+ }
+
+ static function AllParsedFiles() {
+ return self::$imports;
+ }
+
+ /**
+ * @param string $file
+ */
+ static function FileParsed( $file ) {
+ return in_array( $file, self::$imports );
+ }
+
+ function save() {
+ $this->saveStack[] = $this->pos;
+ }
+
+ private function restore() {
+ if ( $this->pos > $this->furthest ) {
+ $this->furthest = $this->pos;
+ }
+ $this->pos = array_pop( $this->saveStack );
+ }
+
+ private function forget() {
+ array_pop( $this->saveStack );
+ }
+
+ /**
+ * Determine if the character at the specified offset from the current position is a white space.
+ *
+ * @param int $offset
+ * @return bool
+ */
+ private function isWhitespace( $offset = 0 ) {
+ // @phan-suppress-next-line PhanParamSuspiciousOrder False positive
+ return strpos( " \t\n\r\v\f", $this->input[$this->pos + $offset] ) !== false;
+ }
+
+ /**
+ * Parse from a token, regexp or string, and move forward if match
+ *
+ * @param array $toks
+ * @return null|string|array|Less_Tree
+ */
+ private function matcher( $toks ) {
+ // The match is confirmed, add the match length to `this::pos`,
+ // and consume any extra white-space characters (' ' || '\n')
+ // which come after that. The reason for this is that LeSS's
+ // grammar is mostly white-space insensitive.
+ //
+
+ foreach ( $toks as $tok ) {
+
+ $char = $tok[0];
+
+ if ( $char === '/' ) {
+ $match = $this->MatchReg( $tok );
+
+ if ( $match ) {
+ return count( $match ) === 1 ? $match[0] : $match;
+ }
+
+ } elseif ( $char === '#' ) {
+ $match = $this->MatchChar( $tok[1] );
+
+ } else {
+ // Non-terminal, match using a function call
+ $match = $this->$tok();
+
+ }
+
+ if ( $match ) {
+ return $match;
+ }
+ }
+ }
+
+ /**
+ * @param string[] $toks
+ * @return null|string|array|Less_Tree
+ */
+ private function MatchFuncs( $toks ) {
+ if ( $this->pos < $this->input_len ) {
+ foreach ( $toks as $tok ) {
+ $match = $this->$tok();
+ if ( $match ) {
+ return $match;
+ }
+ }
+ }
+ }
+
+ /**
+ * Match a single character in the input.
+ *
+ * @param string $tok
+ * @see less-2.5.3.js#parserInput.$char
+ */
+ private function MatchChar( $tok ) {
+ if ( ( $this->pos < $this->input_len ) && ( $this->input[$this->pos] === $tok ) ) {
+ $this->skipWhitespace( 1 );
+ return $tok;
+ }
+ }
+
+ /**
+ * Match a regexp from the current start point
+ *
+ * @return array|null
+ */
+ private function MatchReg( $tok ) {
+ if ( preg_match( $tok, $this->input, $match, 0, $this->pos ) ) {
+ $this->skipWhitespace( strlen( $match[0] ) );
+ return $match;
+ }
+ }
+
+ /**
+ * Same as match(), but don't change the state of the parser,
+ * just return the match.
+ *
+ * @param string $tok
+ * @return int|false
+ */
+ public function PeekReg( $tok ) {
+ return preg_match( $tok, $this->input, $match, 0, $this->pos );
+ }
+
+ /**
+ * @param string $tok
+ */
+ public function PeekChar( $tok ) {
+ return ( $this->pos < $this->input_len ) && ( $this->input[$this->pos] === $tok );
+ }
+
+ /**
+ * @param int $length
+ * @see less-2.5.3.js#skipWhitespace
+ */
+ public function skipWhitespace( $length ) {
+ $this->pos += $length;
+
+ for ( ; $this->pos < $this->input_len; $this->pos++ ) {
+ $c = $this->input[$this->pos];
+
+ if ( ( $c !== "\n" ) && ( $c !== "\r" ) && ( $c !== "\t" ) && ( $c !== ' ' ) ) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param string $tok
+ * @param string|null $msg
+ */
+ public function expect( $tok, $msg = null ) {
+ $result = $this->matcher( [ $tok ] );
+ if ( !$result ) {
+ $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * @param string $tok
+ * @param string|null $msg
+ */
+ public function expectChar( $tok, $msg = null ) {
+ $result = $this->MatchChar( $tok );
+ if ( !$result ) {
+ $msg = $msg ?: "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'";
+ $this->Error( $msg );
+ } else {
+ return $result;
+ }
+ }
+
+ //
+ // Here in, the parsing rules/functions
+ //
+ // The basic structure of the syntax tree generated is as follows:
+ //
+ // Ruleset -> Rule -> Value -> Expression -> Entity
+ //
+ // Here's some LESS code:
+ //
+ // .class {
+ // color: #fff;
+ // border: 1px solid #000;
+ // width: @w + 4px;
+ // > .child {...}
+ // }
+ //
+ // And here's what the parse tree might look like:
+ //
+ // Ruleset (Selector '.class', [
+ // Rule ("color", Value ([Expression [Color #fff]]))
+ // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
+ // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
+ // Ruleset (Selector [Element '>', '.child'], [...])
+ // ])
+ //
+ // In general, most rules will try to parse a token with the `$()` function, and if the return
+ // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
+ // first, before parsing, that's when we use `peek()`.
+ //
+
+ //
+ // The `primary` rule is the *entry* and *exit* point of the parser.
+ // The rules here can appear at any level of the parse tree.
+ //
+ // The recursive nature of the grammar is an interplay between the `block`
+ // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
+ // as represented by this simplified grammar:
+ //
+ // primary → (ruleset | rule)+
+ // ruleset → selector+ block
+ // block → '{' primary '}'
+ //
+ // Only at one point is the primary rule not called from the
+ // block rule: at the root level.
+ //
+ // @see less-2.5.3.js#parsers.primary
+ private function parsePrimary() {
+ $root = [];
+
+ while ( true ) {
+
+ if ( $this->pos >= $this->input_len ) {
+ break;
+ }
+
+ $node = $this->parseExtend( true );
+ if ( $node ) {
+ $root = array_merge( $root, $node );
+ continue;
+ }
+
+ $node = $this->MatchFuncs( [
+ 'parseMixinDefinition',
+ 'parseNameValue',
+ 'parseRule',
+ 'parseRuleset',
+ 'parseMixinCall',
+ 'parseComment',
+ 'parseRulesetCall',
+ 'parseDirective'
+ ] );
+
+ if ( $node ) {
+ $root[] = $node;
+ } elseif ( !$this->MatchReg( '/\\G[\s\n;]+/' ) ) {
+ break;
+ }
+
+ if ( $this->PeekChar( '}' ) ) {
+ break;
+ }
+ }
+
+ return $root;
+ }
+
+ // We create a Comment node for CSS comments `/* */`,
+ // but keep the LeSS comments `//` silent, by just skipping
+ // over them.
+ private function parseComment() {
+ if ( $this->input[$this->pos] !== '/' ) {
+ return;
+ }
+
+ if ( $this->input[$this->pos + 1] === '/' ) {
+ $match = $this->MatchReg( '/\\G\/\/.*/' );
+ return $this->NewObj( 'Less_Tree_Comment', [ $match[0], true, $this->pos, $this->env->currentFileInfo ] );
+ }
+
+ // $comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
+ $comment = $this->MatchReg( '/\\G\/\*(?s).*?\*+\/\n?/' );// not the same as less.js to prevent fatal errors
+ if ( $comment ) {
+ return $this->NewObj( 'Less_Tree_Comment', [ $comment[0], false, $this->pos, $this->env->currentFileInfo ] );
+ }
+ }
+
+ private function parseComments() {
+ $comments = [];
+
+ while ( $this->pos < $this->input_len ) {
+ $comment = $this->parseComment();
+ if ( !$comment ) {
+ break;
+ }
+
+ $comments[] = $comment;
+ }
+
+ return $comments;
+ }
+
+ /**
+ * A string, which supports escaping " and '
+ *
+ * "milky way" 'he\'s the one!'
+ *
+ * @return Less_Tree_Quoted|null
+ */
+ private function parseEntitiesQuoted() {
+ $j = $this->pos;
+ $e = false;
+ $index = $this->pos;
+
+ if ( $this->input[$this->pos] === '~' ) {
+ $j++;
+ $e = true; // Escaped strings
+ }
+
+ $char = $this->input[$j];
+ if ( $char !== '"' && $char !== "'" ) {
+ return;
+ }
+
+ if ( $e ) {
+ $this->MatchChar( '~' );
+ }
+
+ $matched = $this->MatchQuoted( $char, $j + 1 );
+ if ( $matched === false ) {
+ return;
+ }
+
+ $quoted = $char . $matched . $char;
+ return $this->NewObj( 'Less_Tree_Quoted', [ $quoted, $matched, $e, $index, $this->env->currentFileInfo ] );
+ }
+
+ /**
+ * When PCRE JIT is enabled in php, regular expressions don't work for matching quoted strings
+ *
+ * $regex = '/\\G\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/';
+ * $regex = '/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"/';
+ *
+ */
+ private function MatchQuoted( $quote_char, $i ) {
+ $matched = '';
+ while ( $i < $this->input_len ) {
+ $c = $this->input[$i];
+
+ // escaped character
+ if ( $c === '\\' ) {
+ $matched .= $c . $this->input[$i + 1];
+ $i += 2;
+ continue;
+ }
+
+ if ( $c === $quote_char ) {
+ $this->pos = $i + 1;
+ $this->skipWhitespace( 0 );
+ return $matched;
+ }
+
+ if ( $c === "\r" || $c === "\n" ) {
+ return false;
+ }
+
+ $i++;
+ $matched .= $c;
+ }
+
+ return false;
+ }
+
+ /**
+ * A catch-all word, such as:
+ *
+ * black border-collapse
+ *
+ * @return Less_Tree_Keyword|Less_Tree_Color|null
+ */
+ private function parseEntitiesKeyword() {
+ // $k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
+ $k = $this->MatchReg( '/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/' );
+ if ( $k ) {
+ $k = $k[0];
+ $color = $this->fromKeyword( $k );
+ if ( $color ) {
+ return $color;
+ }
+ return $this->NewObj( 'Less_Tree_Keyword', [ $k ] );
+ }
+ }
+
+ // duplicate of Less_Tree_Color::FromKeyword
+ private function FromKeyword( $keyword ) {
+ $keyword = strtolower( $keyword );
+
+ if ( Less_Colors::hasOwnProperty( $keyword ) ) {
+ // detect named color
+ return $this->NewObj( 'Less_Tree_Color', [ substr( Less_Colors::color( $keyword ), 1 ) ] );
+ }
+
+ if ( $keyword === 'transparent' ) {
+ return $this->NewObj( 'Less_Tree_Color', [ [ 0, 0, 0 ], 0, true ] );
+ }
+ }
+
+ //
+ // A function call
+ //
+ // rgb(255, 0, 255)
+ //
+ // We also try to catch IE's `alpha()`, but let the `alpha` parser
+ // deal with the details.
+ //
+ // The arguments are parsed with the `entities.arguments` parser.
+ //
+ private function parseEntitiesCall() {
+ $index = $this->pos;
+
+ if ( !preg_match( '/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name, 0, $this->pos ) ) {
+ return;
+ }
+ $name = $name[1];
+ $nameLC = strtolower( $name );
+
+ if ( $nameLC === 'url' ) {
+ return null;
+ }
+
+ $this->pos += strlen( $name );
+
+ if ( $nameLC === 'alpha' ) {
+ $alpha_ret = $this->parseAlpha();
+ if ( $alpha_ret ) {
+ return $alpha_ret;
+ }
+ }
+
+ $this->MatchChar( '(' ); // Parse the '(' and consume whitespace.
+
+ $args = $this->parseEntitiesArguments();
+
+ if ( !$this->MatchChar( ')' ) ) {
+ return;
+ }
+
+ if ( $name ) {
+ return $this->NewObj( 'Less_Tree_Call', [ $name, $args, $index, $this->env->currentFileInfo ] );
+ }
+ }
+
+ /**
+ * Parse a list of arguments
+ *
+ * @return array<Less_Tree_Assignment|Less_Tree_Expression>
+ */
+ private function parseEntitiesArguments() {
+ $args = [];
+ while ( true ) {
+ $arg = $this->MatchFuncs( [ 'parseEntitiesAssignment', 'parseExpression' ] );
+ if ( !$arg ) {
+ break;
+ }
+
+ $args[] = $arg;
+ if ( !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ }
+ return $args;
+ }
+
+ /** @return Less_Tree_Dimension|Less_Tree_Color|Less_Tree_Quoted|Less_Tree_UnicodeDescriptor|null */
+ private function parseEntitiesLiteral() {
+ return $this->MatchFuncs( [ 'parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor' ] );
+ }
+
+ /**
+ * Assignments are argument entities for calls.
+ *
+ * They are present in IE filter properties as shown below.
+ *
+ * filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
+ *
+ * @return Less_Tree_Assignment|null
+ */
+ private function parseEntitiesAssignment() {
+ $key = $this->MatchReg( '/\\G\w+(?=\s?=)/' );
+ if ( !$key ) {
+ return;
+ }
+
+ if ( !$this->MatchChar( '=' ) ) {
+ return;
+ }
+
+ $value = $this->parseEntity();
+ if ( $value ) {
+ return $this->NewObj( 'Less_Tree_Assignment', [ $key[0], $value ] );
+ }
+ }
+
+ //
+ // Parse url() tokens
+ //
+ // We use a specific rule for urls, because they don't really behave like
+ // standard function calls. The difference is that the argument doesn't have
+ // to be enclosed within a string, so it can't be parsed as an Expression.
+ //
+ private function parseEntitiesUrl() {
+ if ( $this->input[$this->pos] !== 'u' || !$this->matchReg( '/\\Gurl\(/' ) ) {
+ return;
+ }
+
+ $value = $this->matcher( [ 'parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/' ] );
+ if ( !$value ) {
+ $value = '';
+ }
+
+ $this->expectChar( ')' );
+
+ // @phan-suppress-next-line PhanUndeclaredProperty
+ if ( isset( $value->value ) || $value instanceof Less_Tree_Variable ) {
+ return $this->NewObj( 'Less_Tree_Url', [ $value, $this->env->currentFileInfo ] );
+ }
+
+ return $this->NewObj( 'Less_Tree_Url', [ $this->NewObj( 'Less_Tree_Anonymous', [ $value ] ), $this->env->currentFileInfo ] );
+ }
+
+ /**
+ * A Variable entity, such as `@fink`, in
+ *
+ * width: @fink + 2px
+ *
+ * We use a different parser for variable definitions,
+ * see `parsers.variable`.
+ *
+ * @return Less_Tree_Variable|null
+ */
+ private function parseEntitiesVariable() {
+ $index = $this->pos;
+ if ( $this->PeekChar( '@' ) && ( $name = $this->MatchReg( '/\\G@@?[\w-]+/' ) ) ) {
+ return $this->NewObj( 'Less_Tree_Variable', [ $name[0], $index, $this->env->currentFileInfo ] );
+ }
+ }
+
+ /**
+ * A variable entity using the protective `{}` e.g. `@{var}`.
+ *
+ * @return Less_Tree_Variable|null
+ */
+ private function parseEntitiesVariableCurly() {
+ $index = $this->pos;
+
+ if ( $this->input_len > ( $this->pos + 1 ) && $this->input[$this->pos] === '@' && ( $curly = $this->MatchReg( '/\\G@\{([\w-]+)\}/' ) ) ) {
+ return $this->NewObj( 'Less_Tree_Variable', [ '@' . $curly[1], $index, $this->env->currentFileInfo ] );
+ }
+ }
+
+ /**
+ * A Hexadecimal color
+ *
+ * #4F3C2F
+ *
+ * `rgb` and `hsl` colors are parsed through the `entities.call` parser.
+ *
+ * @return Less_Tree_Color|null
+ */
+ private function parseEntitiesColor() {
+ if ( $this->PeekChar( '#' ) && ( $rgb = $this->MatchReg( '/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/' ) ) ) {
+ return $this->NewObj( 'Less_Tree_Color', [ $rgb[1] ] );
+ }
+ }
+
+ /**
+ * A Dimension, that is, a number and a unit
+ *
+ * 0.5em 95%
+ *
+ * @return Less_Tree_Dimension|null
+ */
+ private function parseEntitiesDimension() {
+ $c = @ord( $this->input[$this->pos] );
+
+ // Is the first char of the dimension 0-9, '.', '+' or '-'
+ if ( ( $c > 57 || $c < 43 ) || $c === 47 || $c == 44 ) {
+ return;
+ }
+
+ $value = $this->MatchReg( '/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/' );
+ if ( $value ) {
+ if ( isset( $value[2] ) ) {
+ return $this->NewObj( 'Less_Tree_Dimension', [ $value[1],$value[2] ] );
+ }
+ return $this->NewObj( 'Less_Tree_Dimension', [ $value[1] ] );
+ }
+ }
+
+ /**
+ * A unicode descriptor, as is used in unicode-range
+ *
+ * U+0?? or U+00A1-00A9
+ *
+ * @return Less_Tree_UnicodeDescriptor|null
+ */
+ function parseUnicodeDescriptor() {
+ $ud = $this->MatchReg( '/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/' );
+ if ( $ud ) {
+ return $this->NewObj( 'Less_Tree_UnicodeDescriptor', [ $ud[0] ] );
+ }
+ }
+
+ //
+ // JavaScript code to be evaluated
+ //
+ // `window.location.href`
+ //
+ private function parseEntitiesJavascript() {
+ $e = false;
+ $j = $this->pos;
+ if ( $this->input[$j] === '~' ) {
+ $j++;
+ $e = true;
+ }
+ if ( $this->input[$j] !== '`' ) {
+ return;
+ }
+ if ( $e ) {
+ $this->MatchChar( '~' );
+ }
+ $str = $this->MatchReg( '/\\G`([^`]*)`/' );
+ if ( $str ) {
+ return $this->NewObj( 'Less_Tree_Javascript', [ $str[1], $this->pos, $e ] );
+ }
+ }
+
+ //
+ // The variable part of a variable definition. Used in the `rule` parser
+ //
+ // @fink:
+ //
+ private function parseVariable() {
+ if ( $this->PeekChar( '@' ) && ( $name = $this->MatchReg( '/\\G(@[\w-]+)\s*:/' ) ) ) {
+ return $name[1];
+ }
+ }
+
+ //
+ // The variable part of a variable definition. Used in the `rule` parser
+ //
+ // @fink();
+ //
+ private function parseRulesetCall() {
+ if ( $this->input[$this->pos] === '@' && ( $name = $this->MatchReg( '/\\G(@[\w-]+)\s*\(\s*\)\s*;/' ) ) ) {
+ return $this->NewObj( 'Less_Tree_RulesetCall', [ $name[1] ] );
+ }
+ }
+
+ //
+ // extend syntax - used to extend selectors
+ //
+ function parseExtend( $isRule = false ) {
+ $index = $this->pos;
+ $extendList = [];
+
+ if ( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ) {
+ return;
+ }
+
+ do {
+ $option = null;
+ $elements = [];
+ while ( true ) {
+ $option = $this->MatchReg( '/\\G(all)(?=\s*(\)|,))/' );
+ if ( $option ) { break;
+ }
+ $e = $this->parseElement();
+ if ( !$e ) {
+ break;
+ }
+ $elements[] = $e;
+ }
+
+ if ( $option ) {
+ $option = $option[1];
+ }
+
+ $extendList[] = $this->NewObj( 'Less_Tree_Extend', [ $this->NewObj( 'Less_Tree_Selector', [ $elements ] ), $option, $index ] );
+
+ } while ( $this->MatchChar( "," ) );
+
+ $this->expect( '/\\G\)/' );
+
+ if ( $isRule ) {
+ $this->expect( '/\\G;/' );
+ }
+
+ return $extendList;
+ }
+
+ //
+ // A Mixin call, with an optional argument list
+ //
+ // #mixins > .square(#fff);
+ // .rounded(4px, black);
+ // .button;
+ //
+ // The `while` loop is there because mixins can be
+ // namespaced, but we only support the child and descendant
+ // selector for now.
+ //
+ private function parseMixinCall() {
+ $char = $this->input[$this->pos];
+ if ( $char !== '.' && $char !== '#' ) {
+ return;
+ }
+
+ $index = $this->pos;
+ $this->save(); // stop us absorbing part of an invalid selector
+
+ $elements = $this->parseMixinCallElements();
+
+ if ( $elements ) {
+
+ if ( $this->MatchChar( '(' ) ) {
+ $returned = $this->parseMixinArgs( true );
+ $args = $returned['args'];
+ $this->expectChar( ')' );
+ } else {
+ $args = [];
+ }
+
+ $important = $this->parseImportant();
+
+ if ( $this->parseEnd() ) {
+ $this->forget();
+ return $this->NewObj( 'Less_Tree_Mixin_Call', [ $elements, $args, $index, $this->env->currentFileInfo, $important ] );
+ }
+ }
+
+ $this->restore();
+ }
+
+ private function parseMixinCallElements() {
+ $elements = [];
+ $c = null;
+
+ while ( true ) {
+ $elemIndex = $this->pos;
+ $e = $this->MatchReg( '/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/' );
+ if ( !$e ) {
+ break;
+ }
+ $elements[] = $this->NewObj( 'Less_Tree_Element', [ $c, $e[0], $elemIndex, $this->env->currentFileInfo ] );
+ $c = $this->MatchChar( '>' );
+ }
+
+ return $elements;
+ }
+
+ /**
+ * @param bool $isCall
+ */
+ private function parseMixinArgs( $isCall ) {
+ $expressions = [];
+ $argsSemiColon = [];
+ $isSemiColonSeperated = null;
+ $argsComma = [];
+ $expressionContainsNamed = null;
+ $name = null;
+ $returner = [ 'args' => [], 'variadic' => false ];
+
+ $this->save();
+
+ while ( true ) {
+ if ( $isCall ) {
+ $arg = $this->MatchFuncs( [ 'parseDetachedRuleset', 'parseExpression' ] );
+ } else {
+ $this->parseComments();
+ if ( $this->input[ $this->pos ] === '.' && $this->MatchReg( '/\\G\.{3}/' ) ) {
+ $returner['variadic'] = true;
+ if ( $this->MatchChar( ";" ) && !$isSemiColonSeperated ) {
+ $isSemiColonSeperated = true;
+ }
+
+ if ( $isSemiColonSeperated ) {
+ $argsSemiColon[] = [ 'variadic' => true ];
+ } else {
+ $argsComma[] = [ 'variadic' => true ];
+ }
+ break;
+ }
+ $arg = $this->MatchFuncs( [ 'parseEntitiesVariable', 'parseEntitiesLiteral', 'parseEntitiesKeyword' ] );
+ }
+ '@phan-var Less_Tree_DetachedRuleset|Less_Tree_Expression|Less_Tree_Variable|Less_Tree_Dimension|Less_Tree_Color|Less_Tree_Quoted|Less_Tree_UnicodeDescriptor|Less_Tree_Keyword|null $arg';
+
+ if ( !$arg ) {
+ break;
+ }
+
+ $nameLoop = null;
+ if ( $arg instanceof Less_Tree_Expression ) {
+ $arg->throwAwayComments();
+ }
+ $value = $arg;
+ $val = null;
+
+ if ( $isCall ) {
+ // Variable
+ if ( property_exists( $arg, 'value' ) && count( $arg->value ) == 1 ) {
+ $val = $arg->value[0];
+ }
+ } else {
+ $val = $arg;
+ }
+
+ if ( $val instanceof Less_Tree_Variable ) {
+
+ if ( $this->MatchChar( ':' ) ) {
+ if ( $expressions ) {
+ if ( $isSemiColonSeperated ) {
+ $this->Error( 'Cannot mix ; and , as delimiter types' );
+ }
+ $expressionContainsNamed = true;
+ }
+
+ // we do not support setting a ruleset as a default variable - it doesn't make sense
+ // However if we do want to add it, there is nothing blocking it, just don't error
+ // and remove isCall dependency below
+ $value = null;
+ if ( $isCall ) {
+ $value = $this->parseDetachedRuleset();
+ }
+ if ( !$value ) {
+ $value = $this->parseExpression();
+ }
+
+ if ( !$value ) {
+ if ( $isCall ) {
+ $this->Error( 'could not understand value for named argument' );
+ } else {
+ $this->restore();
+ $returner['args'] = [];
+ return $returner;
+ }
+ }
+
+ $nameLoop = ( $name = $val->name );
+ } elseif ( !$isCall && $this->MatchReg( '/\\G\.{3}/' ) ) {
+ $returner['variadic'] = true;
+ if ( $this->MatchChar( ";" ) && !$isSemiColonSeperated ) {
+ $isSemiColonSeperated = true;
+ }
+ if ( $isSemiColonSeperated ) {
+ $argsSemiColon[] = [ 'name' => $arg->name, 'variadic' => true ];
+ } else {
+ $argsComma[] = [ 'name' => $arg->name, 'variadic' => true ];
+ }
+ break;
+ } elseif ( !$isCall ) {
+ $name = $nameLoop = $val->name;
+ $value = null;
+ }
+ }
+
+ if ( $value ) {
+ $expressions[] = $value;
+ }
+
+ $argsComma[] = [ 'name' => $nameLoop, 'value' => $value ];
+
+ if ( $this->MatchChar( ',' ) ) {
+ continue;
+ }
+
+ if ( $this->MatchChar( ';' ) || $isSemiColonSeperated ) {
+
+ if ( $expressionContainsNamed ) {
+ $this->Error( 'Cannot mix ; and , as delimiter types' );
+ }
+
+ $isSemiColonSeperated = true;
+
+ if ( count( $expressions ) > 1 ) {
+ $value = $this->NewObj( 'Less_Tree_Value', [ $expressions ] );
+ }
+ $argsSemiColon[] = [ 'name' => $name, 'value' => $value ];
+
+ $name = null;
+ $expressions = [];
+ $expressionContainsNamed = false;
+ }
+ }
+
+ $this->forget();
+ $returner['args'] = ( $isSemiColonSeperated ? $argsSemiColon : $argsComma );
+ return $returner;
+ }
+
+ //
+ // A Mixin definition, with a list of parameters
+ //
+ // .rounded (@radius: 2px, @color) {
+ // ...
+ // }
+ //
+ // Until we have a finer grained state-machine, we have to
+ // do a look-ahead, to make sure we don't have a mixin call.
+ // See the `rule` function for more information.
+ //
+ // We start by matching `.rounded (`, and then proceed on to
+ // the argument list, which has optional default values.
+ // We store the parameters in `params`, with a `value` key,
+ // if there is a value, such as in the case of `@radius`.
+ //
+ // Once we've got our params list, and a closing `)`, we parse
+ // the `{...}` block.
+ //
+ private function parseMixinDefinition() {
+ $cond = null;
+
+ $char = $this->input[$this->pos];
+ // TODO: Less.js doesn't limit this to $char == '{'.
+ if ( ( $char !== '.' && $char !== '#' ) || ( $char === '{' && $this->PeekReg( '/\\G[^{]*\}/' ) ) ) {
+ return;
+ }
+
+ $this->save();
+
+ $match = $this->MatchReg( '/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/' );
+ if ( $match ) {
+ $name = $match[1];
+
+ $argInfo = $this->parseMixinArgs( false );
+ $params = $argInfo['args'];
+ $variadic = $argInfo['variadic'];
+
+ // .mixincall("@{a}");
+ // looks a bit like a mixin definition..
+ // also
+ // .mixincall(@a: {rule: set;});
+ // so we have to be nice and restore
+ if ( !$this->MatchChar( ')' ) ) {
+ $this->restore();
+ return;
+ }
+
+ $this->parseComments();
+
+ if ( $this->MatchReg( '/\\Gwhen/' ) ) { // Guard
+ $cond = $this->expect( 'parseConditions', 'Expected conditions' );
+ }
+
+ $ruleset = $this->parseBlock();
+
+ if ( $ruleset !== null ) {
+ $this->forget();
+ return $this->NewObj( 'Less_Tree_Mixin_Definition', [ $name, $params, $ruleset, $cond, $variadic ] );
+ }
+
+ $this->restore();
+ } else {
+ $this->forget();
+ }
+ }
+
+ //
+ // Entities are the smallest recognized token,
+ // and can be found inside a rule's value.
+ //
+ private function parseEntity() {
+ return $this->MatchFuncs( [ 'parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment' ] );
+ }
+
+ //
+ // A Rule terminator. Note that we use `peek()` to check for '}',
+ // because the `block` rule will be expecting it, but we still need to make sure
+ // it's there, if ';' was omitted.
+ //
+ private function parseEnd() {
+ return $this->MatchChar( ';' ) || $this->PeekChar( '}' );
+ }
+
+ //
+ // IE's alpha function
+ //
+ // alpha(opacity=88)
+ //
+ private function parseAlpha() {
+ if ( !$this->MatchReg( '/\\G\(opacity=/i' ) ) {
+ return;
+ }
+
+ $value = $this->MatchReg( '/\\G[0-9]+/' );
+ if ( $value ) {
+ $value = $value[0];
+ } else {
+ $value = $this->parseEntitiesVariable();
+ if ( !$value ) {
+ return;
+ }
+ }
+
+ $this->expectChar( ')' );
+ return $this->NewObj( 'Less_Tree_Alpha', [ $value ] );
+ }
+
+ /**
+ * A Selector Element
+ *
+ * div
+ * + h1
+ * #socks
+ * input[type="text"]
+ *
+ * Elements are the building blocks for Selectors,
+ * they are made out of a `Combinator` (see combinator rule),
+ * and an element name, such as a tag a class, or `*`.
+ *
+ * @return Less_Tree_Element|null
+ * @see less-2.5.3.js#parsers.element
+ */
+ private function parseElement() {
+ $c = $this->parseCombinator();
+ $index = $this->pos;
+
+ // TODO: Speed up by calling MatchChar directly, like less.js does
+ $e = $this->matcher( [
+ '/\\G(?:\d+\.\d+|\d+)%/',
+ '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
+ '#*',
+ '#&',
+ 'parseAttribute',
+ '/\\G\([^&()@]+\)/',
+ '/\\G[\.#:](?=@)/',
+ 'parseEntitiesVariableCurly'
+ ] );
+
+ if ( $e === null ) {
+ $this->save();
+ if ( $this->MatchChar( '(' ) ) {
+ if ( ( $v = $this->parseSelector() ) && $this->MatchChar( ')' ) ) {
+ $e = $this->NewObj( 'Less_Tree_Paren', [ $v ] );
+ $this->forget();
+ } else {
+ $this->restore();
+ }
+ } else {
+ $this->forget();
+ }
+ }
+
+ if ( $e !== null ) {
+ return $this->NewObj( 'Less_Tree_Element', [ $c, $e, $index, $this->env->currentFileInfo ] );
+ }
+ }
+
+ //
+ // Combinators combine elements together, in a Selector.
+ //
+ // Because our parser isn't white-space sensitive, special care
+ // has to be taken, when parsing the descendant combinator, ` `,
+ // as it's an empty space. We have to check the previous character
+ // in the input, to see if it's a ` ` character.
+ //
+ // @see less-2.5.3.js#parsers.combinator
+ private function parseCombinator() {
+ if ( $this->pos < $this->input_len ) {
+ $c = $this->input[$this->pos];
+ // TODO: Figure out why less.js also handles '/' here, and implement with regression test.
+ if ( $c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ) {
+
+ $this->pos++;
+ if ( $this->input[$this->pos] === '^' ) {
+ $c = '^^';
+ $this->pos++;
+ }
+
+ $this->skipWhitespace( 0 );
+
+ return $c;
+ }
+
+ if ( $this->pos > 0 && $this->isWhitespace( -1 ) ) {
+ return ' ';
+ }
+ }
+ }
+
+ /**
+ * A CSS selector (see selector below)
+ * with less extensions e.g. the ability to extend and guard
+ *
+ * @return Less_Tree_Selector|null
+ * @see less-2.5.3.js#parsers.lessSelector
+ */
+ private function parseLessSelector() {
+ return $this->parseSelector( true );
+ }
+
+ /**
+ * A CSS Selector
+ *
+ * .class > div + h1
+ * li a:hover
+ *
+ * Selectors are made out of one or more Elements, see ::parseElement.
+ *
+ * @return Less_Tree_Selector|null
+ * @see less-2.5.3.js#parsers.selector
+ */
+ private function parseSelector( $isLess = false ) {
+ $elements = [];
+ $extendList = [];
+ $condition = null;
+ $when = false;
+ $extend = false;
+ $e = null;
+ $c = null;
+ $index = $this->pos;
+
+ while ( ( $isLess && ( $extend = $this->parseExtend() ) ) || ( $isLess && ( $when = $this->MatchReg( '/\\Gwhen/' ) ) ) || ( $e = $this->parseElement() ) ) {
+ if ( $when ) {
+ $condition = $this->expect( 'parseConditions', 'expected condition' );
+ } elseif ( $condition ) {
+ // error("CSS guard can only be used at the end of selector");
+ } elseif ( $extend ) {
+ $extendList = array_merge( $extendList, $extend );
+ } else {
+ // if( count($extendList) ){
+ //error("Extend can only be used at the end of selector");
+ //}
+ if ( $this->pos < $this->input_len ) {
+ $c = $this->input[ $this->pos ];
+ }
+ $elements[] = $e;
+ $e = null;
+ }
+
+ if ( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')' ) {
+ break;
+ }
+ }
+
+ if ( $elements ) {
+ return $this->NewObj( 'Less_Tree_Selector', [ $elements, $extendList, $condition, $index, $this->env->currentFileInfo ] );
+ }
+ if ( $extendList ) {
+ $this->Error( 'Extend must be used to extend a selector, it cannot be used on its own' );
+ }
+ }
+
+ private function parseTag() {
+ return ( $tag = $this->MatchReg( '/\\G[A-Za-z][A-Za-z-]*[0-9]?/' ) ) ? $tag : $this->MatchChar( '*' );
+ }
+
+ private function parseAttribute() {
+ $val = null;
+
+ if ( !$this->MatchChar( '[' ) ) {
+ return;
+ }
+
+ $key = $this->parseEntitiesVariableCurly();
+ if ( !$key ) {
+ $key = $this->expect( '/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/' );
+ }
+
+ $op = $this->MatchReg( '/\\G[|~*$^]?=/' );
+ if ( $op ) {
+ $val = $this->matcher( [ 'parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly' ] );
+ }
+
+ $this->expectChar( ']' );
+
+ return $this->NewObj( 'Less_Tree_Attribute', [ $key, $op === null ? null : $op[0], $val ] );
+ }
+
+ /**
+ * The `block` rule is used by `ruleset` and `mixin.definition`.
+ * It's a wrapper around the `primary` rule, with added `{}`.
+ *
+ * @return array<Less_Tree>|null
+ * @see less-2.5.3.js#parsers.block
+ */
+ private function parseBlock() {
+ if ( $this->MatchChar( '{' ) ) {
+ $content = $this->parsePrimary();
+ if ( $this->MatchChar( '}' ) ) {
+ return $content;
+ }
+ }
+ }
+
+ private function parseBlockRuleset() {
+ $block = $this->parseBlock();
+
+ if ( $block ) {
+ $block = $this->NewObj( 'Less_Tree_Ruleset', [ null, $block ] );
+ }
+
+ return $block;
+ }
+
+ /** @return Less_Tree_DetachedRuleset|null */
+ private function parseDetachedRuleset() {
+ $blockRuleset = $this->parseBlockRuleset();
+ if ( $blockRuleset ) {
+ return $this->NewObj( 'Less_Tree_DetachedRuleset', [ $blockRuleset ] );
+ }
+ }
+
+ /**
+ * Ruleset such as:
+ *
+ * div, .class, body > p {
+ * }
+ *
+ * @return Less_Tree_Ruleset|null
+ * @see less-2.5.3.js#parsers.ruleset
+ */
+ private function parseRuleset() {
+ $selectors = [];
+
+ $this->save();
+
+ while ( true ) {
+ $s = $this->parseLessSelector();
+ if ( !$s ) {
+ break;
+ }
+ $selectors[] = $s;
+ $this->parseComments();
+
+ if ( $s->condition && count( $selectors ) > 1 ) {
+ $this->Error( 'Guards are only currently allowed on a single selector.' );
+ }
+
+ if ( !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ if ( $s->condition ) {
+ $this->Error( 'Guards are only currently allowed on a single selector.' );
+ }
+ $this->parseComments();
+ }
+
+ if ( $selectors ) {
+ $rules = $this->parseBlock();
+ if ( is_array( $rules ) ) {
+ $this->forget();
+ // TODO: Less_Environment::$strictImports is not yet ported
+ // It is passed here by less.js
+ return $this->NewObj( 'Less_Tree_Ruleset', [ $selectors, $rules ] );
+ }
+ }
+
+ // Backtrack
+ $this->restore();
+ }
+
+ /**
+ * Custom less.php parse function for finding simple name-value css pairs
+ * ex: width:100px;
+ */
+ private function parseNameValue() {
+ $index = $this->pos;
+ $this->save();
+
+ $match = $this->MatchReg( '/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/' );
+ if ( $match ) {
+
+ if ( $match[4] == '}' ) {
+ $this->pos = $index + strlen( $match[0] ) - 1;
+ }
+
+ if ( $match[3] ) {
+ $match[2] .= ' !important';
+ }
+
+ return $this->NewObj( 'Less_Tree_NameValue', [ $match[1], $match[2], $index, $this->env->currentFileInfo ] );
+ }
+
+ $this->restore();
+ }
+
+ // @see less-2.5.3.js#parsers.rule
+ private function parseRule( $tryAnonymous = null ) {
+ $value = null;
+ $startOfRule = $this->pos;
+ $c = $this->input[$this->pos];
+ $important = null;
+ $merge = false;
+
+ // TODO: Figure out why less.js also handles ':' here, and implement with regression test.
+ if ( $c === '.' || $c === '#' || $c === '&' ) {
+ return;
+ }
+
+ $this->save();
+ $name = $this->MatchFuncs( [ 'parseVariable', 'parseRuleProperty' ] );
+
+ if ( $name ) {
+ $isVariable = is_string( $name );
+
+ if ( $isVariable ) {
+ $value = $this->parseDetachedRuleset();
+ }
+
+ if ( !$value ) {
+ // a name returned by this.ruleProperty() is always an array of the form:
+ // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
+ // where each item is a tree.Keyword or tree.Variable
+ if ( !$isVariable && count( $name ) > 1 ) {
+ $merge = array_pop( $name )->value;
+ }
+
+ // prefer to try to parse first if its a variable or we are compressing
+ // but always fallback on the other one
+ $tryValueFirst = ( !$tryAnonymous && ( self::$options['compress'] || $isVariable ) );
+ if ( $tryValueFirst ) {
+ $value = $this->parseValue();
+ }
+ if ( !$value ) {
+ $value = $this->parseAnonymousValue();
+ if ( $value ) {
+ $this->forget();
+ // anonymous values absorb the end ';' which is required for them to work
+ return $this->NewObj( 'Less_Tree_Rule', [ $name, $value, false, $merge, $startOfRule, $this->env->currentFileInfo ] );
+ }
+ }
+ if ( !$tryValueFirst && !$value ) {
+ $value = $this->parseValue();
+ }
+
+ $important = $this->parseImportant();
+ }
+
+ if ( $value && $this->parseEnd() ) {
+ $this->forget();
+ return $this->NewObj( 'Less_Tree_Rule', [ $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo ] );
+ } else {
+ $this->restore();
+ if ( $value && !$tryAnonymous ) {
+ return $this->parseRule( true );
+ }
+ }
+ } else {
+ $this->forget();
+ }
+ }
+
+ function parseAnonymousValue() {
+ $match = $this->MatchReg( '/\\G([^@+\/\'"*`(;{}-]*);/' );
+ if ( $match ) {
+ return $this->NewObj( 'Less_Tree_Anonymous', [ $match[1] ] );
+ }
+ }
+
+ //
+ // An @import directive
+ //
+ // @import "lib";
+ //
+ // Depending on our environment, importing is done differently:
+ // In the browser, it's an XHR request, in Node, it would be a
+ // file-system operation. The function used for importing is
+ // stored in `import`, which we pass to the Import constructor.
+ //
+ private function parseImport() {
+ $this->save();
+
+ $dir = $this->MatchReg( '/\\G@import?\s+/' );
+
+ if ( $dir ) {
+ $options = $this->parseImportOptions();
+ $path = $this->MatchFuncs( [ 'parseEntitiesQuoted','parseEntitiesUrl' ] );
+
+ if ( $path ) {
+ $features = $this->parseMediaFeatures();
+ if ( $this->MatchChar( ';' ) ) {
+ if ( $features ) {
+ $features = $this->NewObj( 'Less_Tree_Value', [ $features ] );
+ }
+
+ $this->forget();
+ return $this->NewObj( 'Less_Tree_Import', [ $path, $features, $options, $this->pos, $this->env->currentFileInfo ] );
+ }
+ }
+ }
+
+ $this->restore();
+ }
+
+ private function parseImportOptions() {
+ $options = [];
+
+ // list of options, surrounded by parens
+ if ( !$this->MatchChar( '(' ) ) {
+ return $options;
+ }
+ do{
+ $optionName = $this->parseImportOption();
+ if ( $optionName ) {
+ $value = true;
+ switch ( $optionName ) {
+ case "css":
+ $optionName = "less";
+ $value = false;
+ break;
+ case "once":
+ $optionName = "multiple";
+ $value = false;
+ break;
+ }
+ $options[$optionName] = $value;
+ if ( !$this->MatchChar( ',' ) ) { break;
+ }
+ }
+ }while ( $optionName );
+ $this->expectChar( ')' );
+ return $options;
+ }
+
+ private function parseImportOption() {
+ $opt = $this->MatchReg( '/\\G(less|css|multiple|once|inline|reference|optional)/' );
+ if ( $opt ) {
+ return $opt[1];
+ }
+ }
+
+ private function parseMediaFeature() {
+ $nodes = [];
+
+ do{
+ $e = $this->MatchFuncs( [ 'parseEntitiesKeyword','parseEntitiesVariable' ] );
+ if ( $e ) {
+ $nodes[] = $e;
+ } elseif ( $this->MatchChar( '(' ) ) {
+ $p = $this->parseProperty();
+ $e = $this->parseValue();
+ if ( $this->MatchChar( ')' ) ) {
+ if ( $p && $e ) {
+ $r = $this->NewObj( 'Less_Tree_Rule', [ $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true ] );
+ $nodes[] = $this->NewObj( 'Less_Tree_Paren', [ $r ] );
+ } elseif ( $e ) {
+ $nodes[] = $this->NewObj( 'Less_Tree_Paren', [ $e ] );
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ } while ( $e );
+
+ if ( $nodes ) {
+ return $this->NewObj( 'Less_Tree_Expression', [ $nodes ] );
+ }
+ }
+
+ private function parseMediaFeatures() {
+ $features = [];
+
+ do {
+ $e = $this->parseMediaFeature();
+ if ( $e ) {
+ $features[] = $e;
+ if ( !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ } else {
+ $e = $this->parseEntitiesVariable();
+ if ( $e ) {
+ $features[] = $e;
+ if ( !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ }
+ }
+ } while ( $e );
+
+ return $features ?: null;
+ }
+
+ private function parseMedia() {
+ if ( $this->MatchReg( '/\\G@media/' ) ) {
+ $this->save();
+
+ $features = $this->parseMediaFeatures();
+ $rules = $this->parseBlock();
+
+ if ( $rules === null ) {
+ $this->restore();
+ return;
+ }
+
+ $this->forget();
+ return $this->NewObj( 'Less_Tree_Media', [ $rules, $features, $this->pos, $this->env->currentFileInfo ] );
+ }
+ }
+
+ //
+ // A CSS Directive
+ //
+ // @charset "utf-8";
+ //
+ private function parseDirective() {
+ if ( !$this->PeekChar( '@' ) ) {
+ return;
+ }
+
+ $rules = null;
+ $index = $this->pos;
+ $hasBlock = true;
+ $hasIdentifier = false;
+ $hasExpression = false;
+ $hasUnknown = false;
+
+ $value = $this->MatchFuncs( [
+ 'parseImport',
+ 'parseMedia'
+ ] );
+ if ( $value ) {
+ return $value;
+ }
+
+ $this->save();
+
+ $name = $this->MatchReg( '/\\G@[a-z-]+/' );
+
+ if ( !$name ) {
+ return;
+ }
+ $name = $name[0];
+
+ $nonVendorSpecificName = $name;
+ $pos = strpos( $name, '-', 2 );
+ if ( $name[1] == '-' && $pos > 0 ) {
+ $nonVendorSpecificName = "@" . substr( $name, $pos + 1 );
+ }
+
+ switch ( $nonVendorSpecificName ) {
+ /*
+ case "@font-face":
+ case "@viewport":
+ case "@top-left":
+ case "@top-left-corner":
+ case "@top-center":
+ case "@top-right":
+ case "@top-right-corner":
+ case "@bottom-left":
+ case "@bottom-left-corner":
+ case "@bottom-center":
+ case "@bottom-right":
+ case "@bottom-right-corner":
+ case "@left-top":
+ case "@left-middle":
+ case "@left-bottom":
+ case "@right-top":
+ case "@right-middle":
+ case "@right-bottom":
+ hasBlock = true;
+ break;
+ */
+ case "@charset":
+ $hasIdentifier = true;
+ $hasBlock = false;
+ break;
+ case "@namespace":
+ $hasExpression = true;
+ $hasBlock = false;
+ break;
+ case "@keyframes":
+ $hasIdentifier = true;
+ break;
+ case "@host":
+ case "@page":
+ case "@document":
+ case "@supports":
+ $hasUnknown = true;
+ break;
+ }
+
+ if ( $hasIdentifier ) {
+ $value = $this->parseEntity();
+ if ( !$value ) {
+ $this->error( "expected " . $name . " identifier" );
+ }
+ } elseif ( $hasExpression ) {
+ $value = $this->parseExpression();
+ if ( !$value ) {
+ $this->error( "expected " . $name . " expression" );
+ }
+ } elseif ( $hasUnknown ) {
+
+ $value = $this->MatchReg( '/\\G[^{;]+/' );
+ if ( $value ) {
+ $value = $this->NewObj( 'Less_Tree_Anonymous', [ trim( $value[0] ) ] );
+ }
+ }
+
+ if ( $hasBlock ) {
+ $rules = $this->parseBlockRuleset();
+ }
+
+ if ( $rules || ( !$hasBlock && $value && $this->MatchChar( ';' ) ) ) {
+ $this->forget();
+ return $this->NewObj( 'Less_Tree_Directive', [ $name, $value, $rules, $index, $this->env->currentFileInfo ] );
+ }
+
+ $this->restore();
+ }
+
+ //
+ // A Value is a comma-delimited list of Expressions
+ //
+ // font-family: Baskerville, Georgia, serif;
+ //
+ // In a Rule, a Value represents everything after the `:`,
+ // and before the `;`.
+ //
+ private function parseValue() {
+ $expressions = [];
+
+ do{
+ $e = $this->parseExpression();
+ if ( $e ) {
+ $expressions[] = $e;
+ if ( !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ }
+ } while ( $e );
+
+ if ( $expressions ) {
+ return $this->NewObj( 'Less_Tree_Value', [ $expressions ] );
+ }
+ }
+
+ private function parseImportant() {
+ if ( $this->PeekChar( '!' ) && $this->MatchReg( '/\\G! *important/' ) ) {
+ return ' !important';
+ }
+ }
+
+ private function parseSub() {
+ if ( $this->MatchChar( '(' ) ) {
+ $a = $this->parseAddition();
+ if ( $a ) {
+ $this->expectChar( ')' );
+ return $this->NewObj( 'Less_Tree_Expression', [ [ $a ], true ] ); // instead of $e->parens = true so the value is cached
+ }
+ }
+ }
+
+ /**
+ * Parses multiplication operation
+ *
+ * @return Less_Tree_Operation|null
+ */
+ function parseMultiplication() {
+ $return = $m = $this->parseOperand();
+ if ( $return ) {
+ while ( true ) {
+
+ $isSpaced = $this->isWhitespace( -1 );
+
+ if ( $this->PeekReg( '/\\G\/[*\/]/' ) ) {
+ break;
+ }
+
+ $op = $this->MatchChar( '/' );
+ if ( !$op ) {
+ $op = $this->MatchChar( '*' );
+ if ( !$op ) {
+ break;
+ }
+ }
+
+ $a = $this->parseOperand();
+
+ if ( !$a ) { break;
+ }
+
+ $m->parensInOp = true;
+ $a->parensInOp = true;
+ $return = $this->NewObj( 'Less_Tree_Operation', [ $op, [ $return, $a ], $isSpaced ] );
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Parses an addition operation
+ *
+ * @return Less_Tree_Operation|null
+ */
+ private function parseAddition() {
+ $return = $m = $this->parseMultiplication();
+ if ( $return ) {
+ while ( true ) {
+
+ $isSpaced = $this->isWhitespace( -1 );
+
+ $op = $this->MatchReg( '/\\G[-+]\s+/' );
+ if ( $op ) {
+ $op = $op[0];
+ } else {
+ if ( !$isSpaced ) {
+ $op = $this->matcher( [ '#+','#-' ] );
+ }
+ if ( !$op ) {
+ break;
+ }
+ }
+
+ $a = $this->parseMultiplication();
+ if ( !$a ) {
+ break;
+ }
+
+ $m->parensInOp = true;
+ $a->parensInOp = true;
+ $return = $this->NewObj( 'Less_Tree_Operation', [ $op, [ $return, $a ], $isSpaced ] );
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Parses the conditions
+ *
+ * @return Less_Tree_Condition|null
+ */
+ private function parseConditions() {
+ $index = $this->pos;
+ $return = $a = $this->parseCondition();
+ if ( $a ) {
+ while ( true ) {
+ if ( !$this->PeekReg( '/\\G,\s*(not\s*)?\(/' ) || !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ $b = $this->parseCondition();
+ if ( !$b ) {
+ break;
+ }
+
+ $return = $this->NewObj( 'Less_Tree_Condition', [ 'or', $return, $b, $index ] );
+ }
+ return $return;
+ }
+ }
+
+ private function parseCondition() {
+ $index = $this->pos;
+ $negate = false;
+ $c = null;
+
+ if ( $this->MatchReg( '/\\Gnot/' ) ) {
+ $negate = true;
+ }
+ $this->expectChar( '(' );
+ $a = $this->MatchFuncs( [ 'parseAddition','parseEntitiesKeyword','parseEntitiesQuoted' ] );
+
+ if ( $a ) {
+ $op = $this->MatchReg( '/\\G(?:>=|<=|=<|[<=>])/' );
+ if ( $op ) {
+ $b = $this->MatchFuncs( [ 'parseAddition','parseEntitiesKeyword','parseEntitiesQuoted' ] );
+ if ( $b ) {
+ $c = $this->NewObj( 'Less_Tree_Condition', [ $op[0], $a, $b, $index, $negate ] );
+ } else {
+ $this->Error( 'Unexpected expression' );
+ }
+ } else {
+ $k = $this->NewObj( 'Less_Tree_Keyword', [ 'true' ] );
+ $c = $this->NewObj( 'Less_Tree_Condition', [ '=', $a, $k, $index, $negate ] );
+ }
+ $this->expectChar( ')' );
+ // @phan-suppress-next-line PhanPossiblyInfiniteRecursionSameParams
+ return $this->MatchReg( '/\\Gand/' ) ? $this->NewObj( 'Less_Tree_Condition', [ 'and', $c, $this->parseCondition() ] ) : $c;
+ }
+ }
+
+ /**
+ * An operand is anything that can be part of an operation,
+ * such as a Color, or a Variable
+ */
+ private function parseOperand() {
+ $negate = false;
+ $offset = $this->pos + 1;
+ if ( $offset >= $this->input_len ) {
+ return;
+ }
+ $char = $this->input[$offset];
+ if ( $char === '@' || $char === '(' ) {
+ $negate = $this->MatchChar( '-' );
+ }
+
+ $o = $this->MatchFuncs( [ 'parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall' ] );
+
+ if ( $negate ) {
+ $o->parensInOp = true;
+ $o = $this->NewObj( 'Less_Tree_Negative', [ $o ] );
+ }
+
+ return $o;
+ }
+
+ /**
+ * Expressions either represent mathematical operations,
+ * or white-space delimited Entities.
+ *
+ * @return Less_Tree_Expression|null
+ */
+ private function parseExpression() {
+ $entities = [];
+
+ do {
+ $e = $this->MatchFuncs( [ 'parseAddition','parseEntity' ] );
+ if ( $e ) {
+ $entities[] = $e;
+ // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
+ if ( !$this->PeekReg( '/\\G\/[\/*]/' ) ) {
+ $delim = $this->MatchChar( '/' );
+ if ( $delim ) {
+ $entities[] = $this->NewObj( 'Less_Tree_Anonymous', [ $delim ] );
+ }
+ }
+ }
+ } while ( $e );
+
+ if ( $entities ) {
+ return $this->NewObj( 'Less_Tree_Expression', [ $entities ] );
+ }
+ }
+
+ /**
+ * Parse a property
+ * eg: 'min-width', 'orientation', etc
+ *
+ * @return string
+ */
+ private function parseProperty() {
+ $name = $this->MatchReg( '/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/' );
+ if ( $name ) {
+ return $name[1];
+ }
+ }
+
+ /**
+ * Parse a rule property
+ * eg: 'color', 'width', 'height', etc
+ *
+ * @return array<Less_Tree_Keyword|Less_Tree_Variable>
+ */
+ private function parseRuleProperty() {
+ $name = [];
+ $index = [];
+
+ $this->save();
+
+ $simpleProperty = $this->MatchReg( '/\\G([_a-zA-Z0-9-]+)\s*:/' );
+ if ( $simpleProperty ) {
+ $name[] = $this->NewObj( 'Less_Tree_Keyword', [ $simpleProperty[1] ] );
+ $this->forget();
+ return $name;
+ }
+
+ $this->rulePropertyMatch( '/\\G(\*?)/', $index, $name );
+
+ // Consume!
+ // @phan-suppress-next-line PhanPluginEmptyStatementWhileLoop
+ while ( $this->rulePropertyMatch( '/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $index, $name ) );
+
+ if ( ( count( $name ) > 1 ) && $this->rulePropertyMatch( '/\\G\s*((?:\+_|\+)?)\s*:/', $index, $name ) ) {
+ $this->forget();
+
+ // at last, we have the complete match now. move forward,
+ // convert name particles to tree objects and return:
+ if ( $name[0] === '' ) {
+ array_shift( $name );
+ array_shift( $index );
+ }
+ foreach ( $name as $k => $s ) {
+ if ( !$s || $s[0] !== '@' ) {
+ $name[$k] = $this->NewObj( 'Less_Tree_Keyword', [ $s ] );
+ } else {
+ $name[$k] = $this->NewObj( 'Less_Tree_Variable', [ '@' . substr( $s, 2, -1 ), $index[$k], $this->env->currentFileInfo ] );
+ }
+ }
+ return $name;
+ } else {
+ $this->restore();
+ }
+ }
+
+ private function rulePropertyMatch( $re, &$index, &$name ) {
+ $i = $this->pos;
+ $chunk = $this->MatchReg( $re );
+ if ( $chunk ) {
+ $index[] = $i;
+ $name[] = $chunk[1];
+ return true;
+ }
+ }
+
+ public static function serializeVars( $vars ) {
+ $s = '';
+
+ foreach ( $vars as $name => $value ) {
+ $s .= ( ( $name[0] === '@' ) ? '' : '@' ) . $name . ': ' . $value . ( ( substr( $value, -1 ) === ';' ) ? '' : ';' );
+ }
+
+ return $s;
+ }
+
+ /**
+ * Some versions of PHP have trouble with method_exists($a,$b) if $a is not an object
+ *
+ * @param mixed $a
+ * @param string $b
+ */
+ public static function is_method( $a, $b ) {
+ return is_object( $a ) && method_exists( $a, $b );
+ }
+
+ /**
+ * Round numbers similarly to javascript
+ * eg: 1.499999 to 1 instead of 2
+ */
+ public static function round( $input, $precision = 0 ) {
+ $precision = pow( 10, $precision );
+ $i = $input * $precision;
+
+ $ceil = ceil( $i );
+ $floor = floor( $i );
+ if ( ( $ceil - $i ) <= ( $i - $floor ) ) {
+ return $ceil / $precision;
+ } else {
+ return $floor / $precision;
+ }
+ }
+
+ /**
+ * Create a new instance of $class with args $args, and optionally generates a cache string.
+ * $class should be a Less_Tree_* class.
+ *
+ * @phan-template TClassName
+ * @phan-param class-string<TClassName> $class
+ * @phan-param array<int,mixed> $args
+ * @phan-return TClassName
+ *
+ * @param string $class
+ * @param mixed[] $args
+ * @return Less_Tree Instance of $class subclass created with $args
+ */
+ public function NewObj( $class, $args = [] ) {
+ $obj = new $class( ...$args );
+ if ( $this->CacheEnabled() ) {
+ $argStrings = array_map(
+ [ __CLASS__, 'ArgString' ],
+ $args
+ );
+ $argCache = implode( ',', $argStrings );
+ // @phan-suppress-next-line PhanTypeExpectedObjectPropAccess False positive
+ $obj->cache_string = " new $class($argCache)";
+ }
+ return $obj;
+ }
+
+ /**
+ * Convert an argument to a string for use in the parser cache
+ *
+ * @return string
+ */
+ public static function ArgString( $arg ) {
+ $type = gettype( $arg );
+
+ if ( $type === 'object' ) {
+ $string = $arg->cache_string;
+ unset( $arg->cache_string );
+ return $string;
+
+ } elseif ( $type === 'array' ) {
+ $string = ' Array(';
+ foreach ( $arg as $k => $a ) {
+ $string .= var_export( $k, true ) . ' => ' . self::ArgString( $a ) . ',';
+ }
+ return $string . ')';
+ }
+
+ return var_export( $arg, true );
+ }
+
+ /** @return never */
+ public function Error( $msg ) {
+ throw new Less_Exception_Parser( $msg, null, $this->furthest, $this->env->currentFileInfo );
+ }
+
+ public static function WinPath( $path ) {
+ return str_replace( '\\', '/', $path );
+ }
+
+ public static function AbsPath( $path, $winPath = false ) {
+ if ( strpos( $path, '//' ) !== false && preg_match( '/^(https?:)?\/\//i', $path ) ) {
+ return $winPath ? '' : false;
+ } else {
+ $path = realpath( $path );
+ if ( $winPath ) {
+ $path = self::WinPath( $path );
+ }
+ return $path;
+ }
+ }
+
+ public function CacheEnabled() {
+ return ( self::$options['cache_method'] && ( Less_Cache::$cache_dir || ( self::$options['cache_method'] == 'callback' ) ) );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/SourceMap/Base64VLQ.php b/vendor/wikimedia/less.php/lib/Less/SourceMap/Base64VLQ.php
new file mode 100644
index 0000000..c27c6ed
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/SourceMap/Base64VLQ.php
@@ -0,0 +1,185 @@
+<?php
+/**
+ * Encode / Decode Base64 VLQ.
+ *
+ * @private
+ */
+class Less_SourceMap_Base64VLQ {
+
+ /**
+ * Shift
+ *
+ * @var int
+ */
+ private $shift = 5;
+
+ /**
+ * Mask
+ *
+ * @var int
+ */
+ private $mask = 0x1F; // == (1 << shift) == 0b00011111
+
+ /**
+ * Continuation bit
+ *
+ * @var int
+ */
+ private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
+
+ /**
+ * Char to integer map
+ *
+ * @var array
+ */
+ private $charToIntMap = [
+ 'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
+ 'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
+ 'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
+ 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27,
+ 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34,
+ 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41,
+ 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48,
+ 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56,
+ 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
+ ];
+
+ /**
+ * Integer to char map
+ *
+ * @var array
+ */
+ private $intToCharMap = [
+ 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G',
+ 7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N',
+ 14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
+ 21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b',
+ 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i',
+ 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p',
+ 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w',
+ 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
+ 56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+',
+ 63 => '/',
+ ];
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ // I leave it here for future reference
+ // foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
+ // {
+ // $this->charToIntMap[$char] = $i;
+ // $this->intToCharMap[$i] = $char;
+ // }
+ }
+
+ /**
+ * Convert from a two-complement value to a value where the sign bit is
+ * is placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ * We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
+ * even on a 64 bit machine.
+ * @param int $aValue
+ */
+ public function toVLQSigned( $aValue ) {
+ return 0xffffffff & ( $aValue < 0 ? ( ( -$aValue ) << 1 ) + 1 : ( $aValue << 1 ) + 0 );
+ }
+
+ /**
+ * Convert to a two-complement value from a value where the sign bit is
+ * is placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ * We assume that the value was generated with a 32 bit machine in mind.
+ * Hence
+ * 1 becomes -2147483648
+ * even on a 64 bit machine.
+ * @param int $aValue
+ */
+ public function fromVLQSigned( $aValue ) {
+ return $aValue & 1 ? $this->zeroFill( ~$aValue + 2, 1 ) | ( -1 - 0x7fffffff ) : $this->zeroFill( $aValue, 1 );
+ }
+
+ /**
+ * Return the base 64 VLQ encoded value.
+ *
+ * @param int $aValue The value to encode
+ * @return string The encoded value
+ */
+ public function encode( $aValue ) {
+ $encoded = '';
+ $vlq = $this->toVLQSigned( $aValue );
+ do
+ {
+ $digit = $vlq & $this->mask;
+ $vlq = $this->zeroFill( $vlq, $this->shift );
+ if ( $vlq > 0 ) {
+ $digit |= $this->continuationBit;
+ }
+ $encoded .= $this->base64Encode( $digit );
+ } while ( $vlq > 0 );
+
+ return $encoded;
+ }
+
+ /**
+ * Return the value decoded from base 64 VLQ.
+ *
+ * @param string $encoded The encoded value to decode
+ * @return int The decoded value
+ */
+ public function decode( $encoded ) {
+ $vlq = 0;
+ $i = 0;
+ do
+ {
+ $digit = $this->base64Decode( $encoded[$i] );
+ $vlq |= ( $digit & $this->mask ) << ( $i * $this->shift );
+ $i++;
+ } while ( $digit & $this->continuationBit );
+
+ return $this->fromVLQSigned( $vlq );
+ }
+
+ /**
+ * Right shift with zero fill.
+ *
+ * @param int $a number to shift
+ * @param int $b number of bits to shift
+ * @return int
+ */
+ public function zeroFill( $a, $b ) {
+ return ( $a >= 0 ) ? ( $a >> $b ) : ( $a >> $b ) & ( PHP_INT_MAX >> ( $b - 1 ) );
+ }
+
+ /**
+ * Encode single 6-bit digit as base64.
+ *
+ * @param int $number
+ * @return string
+ * @throws Exception If the number is invalid
+ */
+ public function base64Encode( $number ) {
+ if ( $number < 0 || $number > 63 ) {
+ throw new Exception( sprintf( 'Invalid number "%s" given. Must be between 0 and 63.', (string)$number ) );
+ }
+ return $this->intToCharMap[$number];
+ }
+
+ /**
+ * Decode single 6-bit digit from base64
+ *
+ * @param string $char
+ * @return int
+ * @throws Exception If the number is invalid
+ */
+ public function base64Decode( $char ) {
+ if ( !array_key_exists( $char, $this->charToIntMap ) ) {
+ throw new Exception( sprintf( 'Invalid base 64 digit "%s" given.', $char ) );
+ }
+ return $this->charToIntMap[$char];
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/SourceMap/Generator.php b/vendor/wikimedia/less.php/lib/Less/SourceMap/Generator.php
new file mode 100644
index 0000000..7da9609
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/SourceMap/Generator.php
@@ -0,0 +1,354 @@
+<?php
+/**
+ * Source map generator
+ *
+ * @private
+ */
+class Less_SourceMap_Generator extends Less_Configurable {
+
+ /**
+ * What version of source map does the generator generate?
+ */
+ private const VERSION = 3;
+
+ /**
+ * Array of default options
+ *
+ * @var array
+ */
+ protected $defaultOptions = [
+ // an optional source root, useful for relocating source files
+ // on a server or removing repeated values in the 'sources' entry.
+ // This value is prepended to the individual entries in the 'source' field.
+ 'sourceRoot' => '',
+
+ // an optional name of the generated code that this source map is associated with.
+ 'sourceMapFilename' => null,
+
+ // url of the map
+ 'sourceMapURL' => null,
+
+ // absolute path to a file to write the map to
+ 'sourceMapWriteTo' => null,
+
+ // output source contents?
+ 'outputSourceFiles' => false,
+
+ // base path for filename normalization
+ 'sourceMapRootpath' => '',
+
+ // base path for filename normalization
+ 'sourceMapBasepath' => ''
+ ];
+
+ /**
+ * The base64 VLQ encoder
+ *
+ * @var Less_SourceMap_Base64VLQ
+ */
+ protected $encoder;
+
+ /**
+ * Array of mappings
+ *
+ * @var array
+ */
+ protected $mappings = [];
+
+ /**
+ * The root node
+ *
+ * @var Less_Tree_Ruleset
+ */
+ protected $root;
+
+ /**
+ * Array of contents map
+ *
+ * @var array
+ */
+ protected $contentsMap = [];
+
+ /**
+ * File to content map
+ *
+ * @var array
+ */
+ protected $sources = [];
+ protected $source_keys = [];
+
+ /**
+ * Constructor
+ *
+ * @param Less_Tree_Ruleset $root The root node
+ * @param array $contentsMap
+ * @param array $options Array of options
+ */
+ public function __construct( Less_Tree_Ruleset $root, $contentsMap, $options = [] ) {
+ $this->root = $root;
+ $this->contentsMap = $contentsMap;
+ $this->encoder = new Less_SourceMap_Base64VLQ();
+
+ $this->SetOptions( $options );
+
+ $this->options['sourceMapRootpath'] = $this->fixWindowsPath( $this->options['sourceMapRootpath'], true );
+ $this->options['sourceMapBasepath'] = $this->fixWindowsPath( $this->options['sourceMapBasepath'], true );
+ }
+
+ /**
+ * Generates the CSS
+ *
+ * @return string
+ */
+ public function generateCSS() {
+ $output = new Less_Output_Mapped( $this->contentsMap, $this );
+
+ // catch the output
+ $this->root->genCSS( $output );
+
+ $sourceMapUrl = $this->getOption( 'sourceMapURL' );
+ $sourceMapFilename = $this->getOption( 'sourceMapFilename' );
+ $sourceMapContent = $this->generateJson();
+ $sourceMapWriteTo = $this->getOption( 'sourceMapWriteTo' );
+
+ if ( !$sourceMapUrl && $sourceMapFilename ) {
+ $sourceMapUrl = $this->normalizeFilename( $sourceMapFilename );
+ }
+
+ // write map to a file
+ if ( $sourceMapWriteTo ) {
+ $this->saveMap( $sourceMapWriteTo, $sourceMapContent );
+ }
+
+ // inline the map
+ if ( !$sourceMapUrl ) {
+ $sourceMapUrl = sprintf( 'data:application/json,%s', Less_Functions::encodeURIComponent( $sourceMapContent ) );
+ }
+
+ if ( $sourceMapUrl ) {
+ $output->add( sprintf( '/*# sourceMappingURL=%s */', $sourceMapUrl ) );
+ }
+
+ return $output->toString();
+ }
+
+ /**
+ * Saves the source map to a file
+ *
+ * @param string $file The absolute path to a file
+ * @param string $content The content to write
+ * @throws Exception If the file could not be saved
+ */
+ protected function saveMap( $file, $content ) {
+ $dir = dirname( $file );
+ // directory does not exist
+ if ( !is_dir( $dir ) ) {
+ // FIXME: create the dir automatically?
+ throw new Exception( sprintf( 'The directory "%s" does not exist. Cannot save the source map.', $dir ) );
+ }
+ // FIXME: proper saving, with dir write check!
+ if ( file_put_contents( $file, $content ) === false ) {
+ throw new Exception( sprintf( 'Cannot save the source map to "%s"', $file ) );
+ }
+ return true;
+ }
+
+ /**
+ * Normalizes the filename
+ *
+ * @param string $filename
+ * @return string
+ */
+ protected function normalizeFilename( $filename ) {
+ $filename = $this->fixWindowsPath( $filename );
+
+ $rootpath = $this->getOption( 'sourceMapRootpath' );
+ $basePath = $this->getOption( 'sourceMapBasepath' );
+
+ // "Trim" the 'sourceMapBasepath' from the output filename.
+ if ( is_string( $basePath ) && strpos( $filename, $basePath ) === 0 ) {
+ $filename = substr( $filename, strlen( $basePath ) );
+ }
+
+ // Remove extra leading path separators.
+ if ( strpos( $filename, '\\' ) === 0 || strpos( $filename, '/' ) === 0 ) {
+ $filename = substr( $filename, 1 );
+ }
+
+ return $rootpath . $filename;
+ }
+
+ /**
+ * Adds a mapping
+ *
+ * @param int $generatedLine The line number in generated file
+ * @param int $generatedColumn The column number in generated file
+ * @param int $originalLine The line number in original file
+ * @param int $originalColumn The column number in original file
+ * @param array $fileInfo The original source file
+ */
+ public function addMapping( $generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ) {
+ $this->mappings[] = [
+ 'generated_line' => $generatedLine,
+ 'generated_column' => $generatedColumn,
+ 'original_line' => $originalLine,
+ 'original_column' => $originalColumn,
+ 'source_file' => $fileInfo['currentUri']
+ ];
+
+ $this->sources[$fileInfo['currentUri']] = $fileInfo['filename'];
+ }
+
+ /**
+ * Generates the JSON source map
+ *
+ * @return string
+ * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
+ */
+ protected function generateJson() {
+ $sourceMap = [];
+ $mappings = $this->generateMappings();
+
+ // File version (always the first entry in the object) and must be a positive integer.
+ $sourceMap['version'] = self::VERSION;
+
+ // An optional name of the generated code that this source map is associated with.
+ $file = $this->getOption( 'sourceMapFilename' );
+ if ( $file ) {
+ $sourceMap['file'] = $file;
+ }
+
+ // An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry. This value is prepended to the individual entries in the 'source' field.
+ $root = $this->getOption( 'sourceRoot' );
+ if ( $root ) {
+ $sourceMap['sourceRoot'] = $root;
+ }
+
+ // A list of original sources used by the 'mappings' entry.
+ $sourceMap['sources'] = [];
+ foreach ( $this->sources as $source_uri => $source_filename ) {
+ $sourceMap['sources'][] = $this->normalizeFilename( $source_filename );
+ }
+
+ // A list of symbol names used by the 'mappings' entry.
+ $sourceMap['names'] = [];
+
+ // A string with the encoded mapping data.
+ $sourceMap['mappings'] = $mappings;
+
+ if ( $this->getOption( 'outputSourceFiles' ) ) {
+ // An optional list of source content, useful when the 'source' can't be hosted.
+ // The contents are listed in the same order as the sources above.
+ // 'null' may be used if some original sources should be retrieved by name.
+ $sourceMap['sourcesContent'] = $this->getSourcesContent();
+ }
+
+ // less.js compat fixes
+ if ( count( $sourceMap['sources'] ) && empty( $sourceMap['sourceRoot'] ) ) {
+ unset( $sourceMap['sourceRoot'] );
+ }
+
+ return json_encode( $sourceMap );
+ }
+
+ /**
+ * Returns the sources contents
+ *
+ * @return array|null
+ */
+ protected function getSourcesContent() {
+ if ( empty( $this->sources ) ) {
+ return;
+ }
+ $content = [];
+ foreach ( $this->sources as $sourceFile ) {
+ $content[] = file_get_contents( $sourceFile );
+ }
+ return $content;
+ }
+
+ /**
+ * Generates the mappings string
+ *
+ * @return string
+ */
+ public function generateMappings() {
+ if ( !count( $this->mappings ) ) {
+ return '';
+ }
+
+ $this->source_keys = array_flip( array_keys( $this->sources ) );
+
+ // group mappings by generated line number.
+ $groupedMap = $groupedMapEncoded = [];
+ foreach ( $this->mappings as $m ) {
+ $groupedMap[$m['generated_line']][] = $m;
+ }
+ ksort( $groupedMap );
+
+ $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
+
+ foreach ( $groupedMap as $lineNumber => $line_map ) {
+ while ( ++$lastGeneratedLine < $lineNumber ) {
+ $groupedMapEncoded[] = ';';
+ }
+
+ $lineMapEncoded = [];
+ $lastGeneratedColumn = 0;
+
+ foreach ( $line_map as $m ) {
+ $mapEncoded = $this->encoder->encode( $m['generated_column'] - $lastGeneratedColumn );
+ $lastGeneratedColumn = $m['generated_column'];
+
+ // find the index
+ if ( $m['source_file'] ) {
+ $index = $this->findFileIndex( $m['source_file'] );
+ if ( $index !== false ) {
+ $mapEncoded .= $this->encoder->encode( $index - $lastOriginalIndex );
+ $lastOriginalIndex = $index;
+
+ // lines are stored 0-based in SourceMap spec version 3
+ $mapEncoded .= $this->encoder->encode( $m['original_line'] - 1 - $lastOriginalLine );
+ $lastOriginalLine = $m['original_line'] - 1;
+
+ $mapEncoded .= $this->encoder->encode( $m['original_column'] - $lastOriginalColumn );
+ $lastOriginalColumn = $m['original_column'];
+ }
+ }
+
+ $lineMapEncoded[] = $mapEncoded;
+ }
+
+ $groupedMapEncoded[] = implode( ',', $lineMapEncoded ) . ';';
+ }
+
+ return rtrim( implode( $groupedMapEncoded ), ';' );
+ }
+
+ /**
+ * Finds the index for the filename
+ *
+ * @param string $filename
+ * @return int|false
+ */
+ protected function findFileIndex( $filename ) {
+ return $this->source_keys[$filename];
+ }
+
+ /**
+ * fix windows paths
+ * @param string $path
+ * @param bool $addEndSlash
+ * @return string
+ */
+ public function fixWindowsPath( $path, $addEndSlash = false ) {
+ $slash = ( $addEndSlash ) ? '/' : '';
+ if ( !empty( $path ) ) {
+ $path = str_replace( '\\', '/', $path );
+ $path = rtrim( $path, '/' ) . $slash;
+ }
+
+ return $path;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree.php b/vendor/wikimedia/less.php/lib/Less/Tree.php
new file mode 100644
index 0000000..f606b58
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * Tree
+ *
+ * TODO: Callers often use `property_exists(, 'value')` to distinguish
+ * tree nodes that are considerd value-holding. Refactor this to move
+ * the 'value' property that most subclasses implement to there, and use
+ * something else (special value, method, or intermediate class?) to
+ * signal whether a subclass is considered value-holding.
+ */
+class Less_Tree {
+
+ public $cache_string;
+ public $parensInOp = false;
+ public $extendOnEveryPath;
+ public $allExtends;
+
+ public function toCSS() {
+ $output = new Less_Output();
+ $this->genCSS( $output );
+ return $output->toString();
+ }
+
+ /**
+ * Generate CSS by adding it to the output object
+ *
+ * @param Less_Output $output The output
+ * @return void
+ */
+ public function genCSS( $output ) {
+ }
+
+ public function compile( $env ) {
+ return $this;
+ }
+
+ /**
+ * @param Less_Output $output
+ * @param Less_Tree_Ruleset[] $rules
+ */
+ public static function outputRuleset( $output, $rules ) {
+ $ruleCnt = count( $rules );
+ Less_Environment::$tabLevel++;
+
+ // Compressed
+ if ( Less_Parser::$options['compress'] ) {
+ $output->add( '{' );
+ for ( $i = 0; $i < $ruleCnt; $i++ ) {
+ $rules[$i]->genCSS( $output );
+ }
+
+ $output->add( '}' );
+ Less_Environment::$tabLevel--;
+ return;
+ }
+
+ // Non-compressed
+ $tabSetStr = "\n" . str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel - 1 );
+ $tabRuleStr = $tabSetStr . Less_Parser::$options['indentation'];
+
+ $output->add( " {" );
+ for ( $i = 0; $i < $ruleCnt; $i++ ) {
+ $output->add( $tabRuleStr );
+ $rules[$i]->genCSS( $output );
+ }
+ Less_Environment::$tabLevel--;
+ $output->add( $tabSetStr . '}' );
+ }
+
+ public function accept( $visitor ) {
+ }
+
+ public static function ReferencedArray( $rules ) {
+ foreach ( $rules as $rule ) {
+ if ( method_exists( $rule, 'markReferenced' ) ) {
+ // @phan-suppress-next-line PhanUndeclaredMethod
+ $rule->markReferenced();
+ }
+ }
+ }
+
+ /**
+ * Requires php 5.3+
+ */
+ public static function __set_state( $args ) {
+ $class = get_called_class();
+ $obj = new $class( null, null, null, null );
+ foreach ( $args as $key => $val ) {
+ $obj->$key = $val;
+ }
+ return $obj;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Alpha.php b/vendor/wikimedia/less.php/lib/Less/Tree/Alpha.php
new file mode 100644
index 0000000..bdf5dee
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Alpha.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Alpha extends Less_Tree {
+ public $value;
+ public $type = 'Alpha';
+
+ public function __construct( $val ) {
+ $this->value = $val;
+ }
+
+ // function accept( $visitor ){
+ // $this->value = $visitor->visit( $this->value );
+ //}
+
+ public function compile( $env ) {
+ if ( is_object( $this->value ) ) {
+ $this->value = $this->value->compile( $env );
+ }
+
+ return $this;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( "alpha(opacity=" );
+
+ if ( is_string( $this->value ) ) {
+ $output->add( $this->value );
+ } else {
+ $this->value->genCSS( $output );
+ }
+
+ $output->add( ')' );
+ }
+
+ public function toCSS() {
+ return "alpha(opacity=" . ( is_string( $this->value ) ? $this->value : $this->value->toCSS() ) . ")";
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Anonymous.php b/vendor/wikimedia/less.php/lib/Less/Tree/Anonymous.php
new file mode 100644
index 0000000..6d588e9
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Anonymous.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Anonymous extends Less_Tree {
+ public $value;
+ public $quote;
+ public $index;
+ public $mapLines;
+ public $currentFileInfo;
+ public $type = 'Anonymous';
+
+ /**
+ * @param int $index
+ * @param bool|null $mapLines
+ */
+ public function __construct( $value, $index = null, $currentFileInfo = null, $mapLines = null ) {
+ $this->value = $value;
+ $this->index = $index;
+ $this->mapLines = $mapLines;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ public function compile( $env ) {
+ return new Less_Tree_Anonymous( $this->value, $this->index, $this->currentFileInfo, $this->mapLines );
+ }
+
+ public function compare( $x ) {
+ if ( !is_object( $x ) ) {
+ return -1;
+ }
+
+ $left = $this->toCSS();
+ $right = $x->toCSS();
+
+ if ( $left === $right ) {
+ return 0;
+ }
+
+ return $left < $right ? -1 : 1;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
+ }
+
+ public function toCSS() {
+ return $this->value;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Assignment.php b/vendor/wikimedia/less.php/lib/Less/Tree/Assignment.php
new file mode 100644
index 0000000..1f939a1
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Assignment.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Assignment extends Less_Tree {
+
+ public $key;
+ public $value;
+ public $type = 'Assignment';
+
+ public function __construct( $key, $val ) {
+ $this->key = $key;
+ $this->value = $val;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+
+ public function compile( $env ) {
+ return new Less_Tree_Assignment( $this->key, $this->value->compile( $env ) );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->key . '=' );
+ $this->value->genCSS( $output );
+ }
+
+ public function toCss() {
+ return $this->key . '=' . $this->value->toCSS();
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Attribute.php b/vendor/wikimedia/less.php/lib/Less/Tree/Attribute.php
new file mode 100644
index 0000000..dd36595
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Attribute.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Attribute extends Less_Tree {
+
+ public $key;
+ public $op;
+ public $value;
+ public $type = 'Attribute';
+
+ public function __construct( $key, $op, $value ) {
+ $this->key = $key;
+ $this->op = $op;
+ $this->value = $value;
+ }
+
+ public function compile( $env ) {
+ $key_obj = is_object( $this->key );
+ $val_obj = is_object( $this->value );
+
+ if ( !$key_obj && !$val_obj ) {
+ return $this;
+ }
+
+ return new Less_Tree_Attribute(
+ $key_obj ? $this->key->compile( $env ) : $this->key,
+ $this->op,
+ $val_obj ? $this->value->compile( $env ) : $this->value );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->toCSS() );
+ }
+
+ public function toCSS() {
+ $value = $this->key;
+
+ if ( $this->op ) {
+ $value .= $this->op;
+ $value .= ( is_object( $this->value ) ? $this->value->toCSS() : $this->value );
+ }
+
+ return '[' . $value . ']';
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Call.php b/vendor/wikimedia/less.php/lib/Less/Tree/Call.php
new file mode 100644
index 0000000..28a3a19
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Call.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Call extends Less_Tree {
+ public $value;
+
+ public $name;
+ public $args;
+ public $index;
+ public $currentFileInfo;
+ public $type = 'Call';
+
+ public function __construct( $name, $args, $index, $currentFileInfo = null ) {
+ $this->name = $name;
+ $this->args = $args;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ public function accept( $visitor ) {
+ $this->args = $visitor->visitArray( $this->args );
+ }
+
+ //
+ // When evaluating a function call,
+ // we either find the function in `tree.functions` [1],
+ // in which case we call it, passing the evaluated arguments,
+ // or we simply print it out as it appeared originally [2].
+ //
+ // The *functions.js* file contains the built-in functions.
+ //
+ // The reason why we evaluate the arguments, is in the case where
+ // we try to pass a variable to a function, like: `saturate(@color)`.
+ // The function should receive the value, not the variable.
+ //
+ public function compile( $env = null ) {
+ $args = [];
+ foreach ( $this->args as $a ) {
+ $args[] = $a->compile( $env );
+ }
+
+ $nameLC = strtolower( $this->name );
+ switch ( $nameLC ) {
+ case '%':
+ $nameLC = '_percent';
+ break;
+
+ case 'get-unit':
+ $nameLC = 'getunit';
+ break;
+
+ case 'data-uri':
+ $nameLC = 'datauri';
+ break;
+
+ case 'svg-gradient':
+ $nameLC = 'svggradient';
+ break;
+ }
+
+ $result = null;
+ if ( $nameLC === 'default' ) {
+ $result = Less_Tree_DefaultFunc::compile();
+ } else {
+ $func = null;
+ if ( method_exists( 'Less_Functions', $nameLC ) ) {
+ $functions = new Less_Functions( $env, $this->currentFileInfo );
+ $func = [ $functions, $nameLC ];
+ } elseif ( isset( $env->functions[$nameLC] ) && is_callable( $env->functions[$nameLC] ) ) {
+ $func = $env->functions[$nameLC];
+ }
+ // If the function name isn't known to LESS, output it unchanged as CSS.
+ if ( $func ) {
+ try {
+ $result = call_user_func_array( $func, $args );
+ } catch ( Exception $e ) {
+ // Preserve original trace, especially from custom functions.
+ // https://github.com/wikimedia/less.php/issues/38
+ throw new Less_Exception_Compiler(
+ 'error evaluating function `' . $this->name . '` ' . $e->getMessage()
+ . ' index: ' . $this->index,
+ $e
+ );
+ }
+ }
+ }
+
+ if ( $result !== null ) {
+ return $result;
+ }
+
+ return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->name . '(', $this->currentFileInfo, $this->index );
+ $args_len = count( $this->args );
+ for ( $i = 0; $i < $args_len; $i++ ) {
+ $this->args[$i]->genCSS( $output );
+ if ( $i + 1 < $args_len ) {
+ $output->add( ', ' );
+ }
+ }
+
+ $output->add( ')' );
+ }
+
+ // public function toCSS(){
+ // return $this->compile()->toCSS();
+ //}
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Color.php b/vendor/wikimedia/less.php/lib/Less/Tree/Color.php
new file mode 100644
index 0000000..427f47d
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Color.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Color extends Less_Tree {
+ public $rgb;
+ public $alpha;
+ public $isTransparentKeyword;
+ public $type = 'Color';
+
+ public function __construct( $rgb, $a = 1, $isTransparentKeyword = null ) {
+ if ( $isTransparentKeyword ) {
+ $this->rgb = $rgb;
+ $this->alpha = $a;
+ $this->isTransparentKeyword = true;
+ return;
+ }
+
+ $this->rgb = [];
+ if ( is_array( $rgb ) ) {
+ $this->rgb = $rgb;
+ } elseif ( strlen( $rgb ) == 6 ) {
+ foreach ( str_split( $rgb, 2 ) as $c ) {
+ $this->rgb[] = hexdec( $c );
+ }
+ } else {
+ foreach ( str_split( $rgb, 1 ) as $c ) {
+ $this->rgb[] = hexdec( $c . $c );
+ }
+ }
+ $this->alpha = is_numeric( $a ) ? $a : 1;
+ }
+
+ public function luma() {
+ $r = $this->rgb[0] / 255;
+ $g = $this->rgb[1] / 255;
+ $b = $this->rgb[2] / 255;
+
+ $r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( ( $r + 0.055 ) / 1.055 ), 2.4 );
+ $g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( ( $g + 0.055 ) / 1.055 ), 2.4 );
+ $b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( ( $b + 0.055 ) / 1.055 ), 2.4 );
+
+ return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->toCSS() );
+ }
+
+ public function toCSS( $doNotCompress = false ) {
+ $compress = Less_Parser::$options['compress'] && !$doNotCompress;
+ $alpha = Less_Functions::fround( $this->alpha );
+
+ //
+ // If we have some transparency, the only way to represent it
+ // is via `rgba`. Otherwise, we use the hex representation,
+ // which has better compatibility with older browsers.
+ // Values are capped between `0` and `255`, rounded and zero-padded.
+ //
+ if ( $alpha < 1 ) {
+ if ( ( $alpha === 0 || $alpha === 0.0 ) && isset( $this->isTransparentKeyword ) && $this->isTransparentKeyword ) {
+ return 'transparent';
+ }
+
+ $values = [];
+ foreach ( $this->rgb as $c ) {
+ $values[] = Less_Functions::clamp( round( $c ), 255 );
+ }
+ $values[] = $alpha;
+
+ $glue = ( $compress ? ',' : ', ' );
+ return "rgba(" . implode( $glue, $values ) . ")";
+ } else {
+
+ $color = $this->toRGB();
+
+ if ( $compress ) {
+
+ // Convert color to short format
+ if ( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6] ) {
+ $color = '#' . $color[1] . $color[3] . $color[5];
+ }
+ }
+
+ return $color;
+ }
+ }
+
+ //
+ // Operations have to be done per-channel, if not,
+ // channels will spill onto each other. Once we have
+ // our result, in the form of an integer triplet,
+ // we create a new Color node to hold the result.
+ //
+
+ /**
+ * @param string $op
+ */
+ public function operate( $op, $other ) {
+ $rgb = [];
+ $alpha = $this->alpha * ( 1 - $other->alpha ) + $other->alpha;
+ for ( $c = 0; $c < 3; $c++ ) {
+ $rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c] );
+ }
+ return new Less_Tree_Color( $rgb, $alpha );
+ }
+
+ public function toRGB() {
+ return $this->toHex( $this->rgb );
+ }
+
+ public function toHSL() {
+ $r = $this->rgb[0] / 255;
+ $g = $this->rgb[1] / 255;
+ $b = $this->rgb[2] / 255;
+ $a = $this->alpha;
+
+ $max = max( $r, $g, $b );
+ $min = min( $r, $g, $b );
+ $l = ( $max + $min ) / 2;
+ $d = $max - $min;
+
+ $h = $s = 0;
+ if ( $max !== $min ) {
+ $s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
+
+ switch ( $max ) {
+ case $r:
+ $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
+ break;
+ case $g:
+ $h = ( $b - $r ) / $d + 2;
+ break;
+ case $b:
+ $h = ( $r - $g ) / $d + 4;
+ break;
+ }
+ $h /= 6;
+ }
+ return [ 'h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a ];
+ }
+
+ // Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
+ public function toHSV() {
+ $r = $this->rgb[0] / 255;
+ $g = $this->rgb[1] / 255;
+ $b = $this->rgb[2] / 255;
+ $a = $this->alpha;
+
+ $max = max( $r, $g, $b );
+ $min = min( $r, $g, $b );
+
+ $v = $max;
+
+ $d = $max - $min;
+ if ( $max === 0 ) {
+ $s = 0;
+ } else {
+ $s = $d / $max;
+ }
+
+ $h = 0;
+ if ( $max !== $min ) {
+ switch ( $max ) {
+ case $r:
+ $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
+ break;
+ case $g:
+ $h = ( $b - $r ) / $d + 2;
+ break;
+ case $b:
+ $h = ( $r - $g ) / $d + 4;
+ break;
+ }
+ $h /= 6;
+ }
+ return [ 'h' => $h * 360, 's' => $s, 'v' => $v, 'a' => $a ];
+ }
+
+ public function toARGB() {
+ $argb = array_merge( (array)Less_Parser::round( $this->alpha * 255 ), $this->rgb );
+ return $this->toHex( $argb );
+ }
+
+ public function compare( $x ) {
+ if ( !property_exists( $x, 'rgb' ) ) {
+ return -1;
+ }
+
+ return ( $x->rgb[0] === $this->rgb[0] &&
+ $x->rgb[1] === $this->rgb[1] &&
+ $x->rgb[2] === $this->rgb[2] &&
+ $x->alpha === $this->alpha ) ? 0 : -1;
+ }
+
+ public function toHex( $v ) {
+ $ret = '#';
+ foreach ( $v as $c ) {
+ $c = Less_Functions::clamp( Less_Parser::round( $c ), 255 );
+ if ( $c < 16 ) {
+ $ret .= '0';
+ }
+ $ret .= dechex( $c );
+ }
+
+ return $ret;
+ }
+
+ /**
+ * @param string $keyword
+ */
+ public static function fromKeyword( $keyword ) {
+ $keyword = strtolower( $keyword );
+
+ if ( Less_Colors::hasOwnProperty( $keyword ) ) {
+ // detect named color
+ return new Less_Tree_Color( substr( Less_Colors::color( $keyword ), 1 ) );
+ }
+
+ if ( $keyword === 'transparent' ) {
+ return new Less_Tree_Color( [ 0, 0, 0 ], 0, true );
+ }
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Comment.php b/vendor/wikimedia/less.php/lib/Less/Tree/Comment.php
new file mode 100644
index 0000000..b4dd68c
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Comment.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Comment extends Less_Tree {
+
+ public $value;
+ public $silent;
+ public $isReferenced;
+ public $currentFileInfo;
+ public $type = 'Comment';
+
+ public function __construct( $value, $silent, $index = null, $currentFileInfo = null ) {
+ $this->value = $value;
+ $this->silent = (bool)$silent;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ // if( $this->debugInfo ){
+ //$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index);
+ //}
+ $output->add( trim( $this->value ) );// TODO shouldn't need to trim, we shouldn't grab the \n
+ }
+
+ public function toCSS() {
+ return Less_Parser::$options['compress'] ? '' : $this->value;
+ }
+
+ public function isSilent() {
+ $isReference = ( $this->currentFileInfo && isset( $this->currentFileInfo['reference'] ) && ( !isset( $this->isReferenced ) || !$this->isReferenced ) );
+ $isCompressed = Less_Parser::$options['compress'] && !preg_match( '/^\/\*!/', $this->value );
+ return $this->silent || $isReference || $isCompressed;
+ }
+
+ public function markReferenced() {
+ $this->isReferenced = true;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Condition.php b/vendor/wikimedia/less.php/lib/Less/Tree/Condition.php
new file mode 100644
index 0000000..5204e83
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Condition.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Condition extends Less_Tree {
+
+ public $op;
+ public $lvalue;
+ public $rvalue;
+ public $index;
+ public $negate;
+ public $type = 'Condition';
+
+ public function __construct( $op, $l, $r, $i = 0, $negate = false ) {
+ $this->op = trim( $op );
+ $this->lvalue = $l;
+ $this->rvalue = $r;
+ $this->index = $i;
+ $this->negate = $negate;
+ }
+
+ public function accept( $visitor ) {
+ $this->lvalue = $visitor->visitObj( $this->lvalue );
+ $this->rvalue = $visitor->visitObj( $this->rvalue );
+ }
+
+ public function compile( $env ) {
+ $a = $this->lvalue->compile( $env );
+ $b = $this->rvalue->compile( $env );
+
+ switch ( $this->op ) {
+ case 'and':
+ $result = $a && $b;
+ break;
+
+ case 'or':
+ $result = $a || $b;
+ break;
+
+ default:
+ if ( Less_Parser::is_method( $a, 'compare' ) ) {
+ $result = $a->compare( $b );
+ } elseif ( Less_Parser::is_method( $b, 'compare' ) ) {
+ $result = $b->compare( $a );
+ } else {
+ throw new Less_Exception_Compiler( 'Unable to perform comparison', null, $this->index );
+ }
+
+ switch ( $result ) {
+ case -1:
+ $result = $this->op === '<' || $this->op === '=<' || $this->op === '<=';
+ break;
+
+ case 0:
+ $result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<=';
+ break;
+
+ case 1:
+ $result = $this->op === '>' || $this->op === '>=';
+ break;
+ }
+ break;
+ }
+
+ return $this->negate ? !$result : $result;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/DefaultFunc.php b/vendor/wikimedia/less.php/lib/Less/Tree/DefaultFunc.php
new file mode 100644
index 0000000..66f0008
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/DefaultFunc.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_DefaultFunc {
+
+ static $error_;
+ static $value_;
+
+ public static function compile() {
+ if ( self::$error_ ) {
+ throw new Exception( self::$error_ );
+ }
+ if ( self::$value_ !== null ) {
+ return self::$value_ ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
+ }
+ }
+
+ public static function value( $v ) {
+ self::$value_ = $v;
+ }
+
+ public static function error( $e ) {
+ self::$error_ = $e;
+ }
+
+ public static function reset() {
+ self::$value_ = self::$error_ = null;
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/DetachedRuleset.php b/vendor/wikimedia/less.php/lib/Less/Tree/DetachedRuleset.php
new file mode 100644
index 0000000..6ba1ef3
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/DetachedRuleset.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_DetachedRuleset extends Less_Tree {
+
+ public $ruleset;
+ public $frames;
+ public $type = 'DetachedRuleset';
+
+ public function __construct( $ruleset, $frames = null ) {
+ $this->ruleset = $ruleset;
+ $this->frames = $frames;
+ }
+
+ public function accept( $visitor ) {
+ $this->ruleset = $visitor->visitObj( $this->ruleset );
+ }
+
+ public function compile( $env ) {
+ if ( $this->frames ) {
+ $frames = $this->frames;
+ } else {
+ $frames = $env->frames;
+ }
+ return new Less_Tree_DetachedRuleset( $this->ruleset, $frames );
+ }
+
+ public function callEval( $env ) {
+ if ( $this->frames ) {
+ return $this->ruleset->compile( $env->copyEvalEnv( array_merge( $this->frames, $env->frames ) ) );
+ }
+ return $this->ruleset->compile( $env );
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Dimension.php b/vendor/wikimedia/less.php/lib/Less/Tree/Dimension.php
new file mode 100644
index 0000000..4dd7198
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Dimension.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Dimension extends Less_Tree {
+
+ public $value;
+ public $unit;
+ public $type = 'Dimension';
+
+ public function __construct( $value, $unit = null ) {
+ $this->value = floatval( $value );
+
+ if ( $unit && ( $unit instanceof Less_Tree_Unit ) ) {
+ $this->unit = $unit;
+ } elseif ( $unit ) {
+ $this->unit = new Less_Tree_Unit( [ $unit ] );
+ } else {
+ $this->unit = new Less_Tree_Unit();
+ }
+ }
+
+ public function accept( $visitor ) {
+ $this->unit = $visitor->visitObj( $this->unit );
+ }
+
+ public function toColor() {
+ return new Less_Tree_Color( [ $this->value, $this->value, $this->value ] );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ) {
+ throw new Less_Exception_Compiler( "Multiple units in dimension. Correct the units or use the unit function. Bad unit: " . $this->unit->toString() );
+ }
+
+ $value = Less_Functions::fround( $this->value );
+ $strValue = (string)$value;
+
+ if ( $value !== 0 && $value < 0.000001 && $value > -0.000001 ) {
+ // would be output 1e-6 etc.
+ $strValue = number_format( (float)$strValue, 10 );
+ $strValue = preg_replace( '/\.?0+$/', '', $strValue );
+ }
+
+ if ( Less_Parser::$options['compress'] ) {
+ // Zero values doesn't need a unit
+ if ( $value === 0 && $this->unit->isLength() ) {
+ $output->add( $strValue );
+ return;
+ }
+
+ // Float values doesn't need a leading zero
+ if ( $value > 0 && $value < 1 && $strValue[0] === '0' ) {
+ $strValue = substr( $strValue, 1 );
+ }
+ }
+
+ $output->add( $strValue );
+ $this->unit->genCSS( $output );
+ }
+
+ public function __toString() {
+ return $this->toCSS();
+ }
+
+ // In an operation between two Dimensions,
+ // we default to the first Dimension's unit,
+ // so `1px + 2em` will yield `3px`.
+
+ /**
+ * @param string $op
+ */
+ public function operate( $op, $other ) {
+ $value = Less_Functions::operate( $op, $this->value, $other->value );
+ $unit = clone $this->unit;
+
+ if ( $op === '+' || $op === '-' ) {
+
+ if ( !$unit->numerator && !$unit->denominator ) {
+ $unit->numerator = $other->unit->numerator;
+ $unit->denominator = $other->unit->denominator;
+ } elseif ( !$other->unit->numerator && !$other->unit->denominator ) {
+ // do nothing
+ } else {
+ $other = $other->convertTo( $this->unit->usedUnits() );
+
+ if ( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ) {
+ throw new Less_Exception_Compiler( "Incompatible units. Change the units or use the unit function. Bad units: '" . $unit->toString() . "' and " . $other->unit->toString() . "'." );
+ }
+
+ $value = Less_Functions::operate( $op, $this->value, $other->value );
+ }
+ } elseif ( $op === '*' ) {
+ $unit->numerator = array_merge( $unit->numerator, $other->unit->numerator );
+ $unit->denominator = array_merge( $unit->denominator, $other->unit->denominator );
+ sort( $unit->numerator );
+ sort( $unit->denominator );
+ $unit->cancel();
+ } elseif ( $op === '/' ) {
+ $unit->numerator = array_merge( $unit->numerator, $other->unit->denominator );
+ $unit->denominator = array_merge( $unit->denominator, $other->unit->numerator );
+ sort( $unit->numerator );
+ sort( $unit->denominator );
+ $unit->cancel();
+ }
+ return new Less_Tree_Dimension( $value, $unit );
+ }
+
+ public function compare( $other ) {
+ if ( $other instanceof Less_Tree_Dimension ) {
+
+ if ( $this->unit->isEmpty() || $other->unit->isEmpty() ) {
+ $a = $this;
+ $b = $other;
+ } else {
+ $a = $this->unify();
+ $b = $other->unify();
+ if ( $a->unit->compare( $b->unit ) !== 0 ) {
+ return -1;
+ }
+ }
+ $aValue = $a->value;
+ $bValue = $b->value;
+
+ if ( $bValue > $aValue ) {
+ return -1;
+ } elseif ( $bValue < $aValue ) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ return -1;
+ }
+ }
+
+ public function unify() {
+ return $this->convertTo( [ 'length' => 'px', 'duration' => 's', 'angle' => 'rad' ] );
+ }
+
+ public function convertTo( $conversions ) {
+ $value = $this->value;
+ $unit = clone $this->unit;
+
+ if ( is_string( $conversions ) ) {
+ $derivedConversions = [];
+ foreach ( Less_Tree_UnitConversions::$groups as $i ) {
+ if ( isset( Less_Tree_UnitConversions::${$i}[$conversions] ) ) {
+ $derivedConversions = [ $i => $conversions ];
+ }
+ }
+ $conversions = $derivedConversions;
+ }
+
+ foreach ( $conversions as $groupName => $targetUnit ) {
+ $group = Less_Tree_UnitConversions::${$groupName};
+
+ // numerator
+ foreach ( $unit->numerator as $i => $atomicUnit ) {
+ $atomicUnit = $unit->numerator[$i];
+ if ( !isset( $group[$atomicUnit] ) ) {
+ continue;
+ }
+
+ $value = $value * ( $group[$atomicUnit] / $group[$targetUnit] );
+
+ $unit->numerator[$i] = $targetUnit;
+ }
+
+ // denominator
+ foreach ( $unit->denominator as $i => $atomicUnit ) {
+ $atomicUnit = $unit->denominator[$i];
+ if ( !isset( $group[$atomicUnit] ) ) {
+ continue;
+ }
+
+ $value = $value / ( $group[$atomicUnit] / $group[$targetUnit] );
+
+ $unit->denominator[$i] = $targetUnit;
+ }
+ }
+
+ $unit->cancel();
+
+ return new Less_Tree_Dimension( $value, $unit );
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Directive.php b/vendor/wikimedia/less.php/lib/Less/Tree/Directive.php
new file mode 100644
index 0000000..dfada84
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Directive.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Directive extends Less_Tree {
+
+ public $name;
+ public $value;
+ public $rules;
+ public $index;
+ public $isReferenced;
+ public $currentFileInfo;
+ public $debugInfo;
+ public $type = 'Directive';
+
+ public function __construct( $name, $value = null, $rules = null, $index = null, $currentFileInfo = null, $debugInfo = null ) {
+ $this->name = $name;
+ $this->value = $value;
+ if ( $rules ) {
+ $this->rules = $rules;
+ $this->rules->allowImports = true;
+ }
+
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ $this->debugInfo = $debugInfo;
+ }
+
+ public function accept( $visitor ) {
+ if ( $this->rules ) {
+ $this->rules = $visitor->visitObj( $this->rules );
+ }
+ if ( $this->value ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $value = $this->value;
+ $rules = $this->rules;
+ $output->add( $this->name, $this->currentFileInfo, $this->index );
+ if ( $this->value ) {
+ $output->add( ' ' );
+ $this->value->genCSS( $output );
+ }
+ if ( $this->rules ) {
+ Less_Tree::outputRuleset( $output, [ $this->rules ] );
+ } else {
+ $output->add( ';' );
+ }
+ }
+
+ public function compile( $env ) {
+ $value = $this->value;
+ $rules = $this->rules;
+ if ( $value ) {
+ $value = $value->compile( $env );
+ }
+
+ if ( $rules ) {
+ $rules = $rules->compile( $env );
+ $rules->root = true;
+ }
+
+ return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo );
+ }
+
+ public function variable( $name ) {
+ if ( $this->rules ) {
+ return $this->rules->variable( $name );
+ }
+ }
+
+ public function find( $selector ) {
+ if ( $this->rules ) {
+ return $this->rules->find( $selector, $this );
+ }
+ }
+
+ // rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); },
+
+ public function markReferenced() {
+ $this->isReferenced = true;
+ if ( $this->rules ) {
+ Less_Tree::ReferencedArray( $this->rules->rules );
+ }
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Element.php b/vendor/wikimedia/less.php/lib/Less/Tree/Element.php
new file mode 100644
index 0000000..11b9ce4
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Element.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Element extends Less_Tree {
+
+ /** @var string */
+ public $combinator;
+ /** @var bool Whether combinator is null (represented by empty string) or child (single space) */
+ public $combinatorIsEmptyOrWhitespace;
+ /** @var string|Less_Tree */
+ public $value;
+ public $index;
+ public $currentFileInfo;
+ public $type = 'Element';
+
+ public $value_is_object = false;
+
+ /**
+ * @param null|string $combinator
+ * @param string|Less_Tree $value
+ * @param int|null $index
+ * @param array|null $currentFileInfo
+ */
+ public function __construct( $combinator, $value, $index = null, $currentFileInfo = null ) {
+ $this->value = $value;
+ $this->value_is_object = is_object( $value );
+
+ // see less-2.5.3.js#Combinator
+ $this->combinator = $combinator ?? '';
+ $this->combinatorIsEmptyOrWhitespace = ( $combinator === null || trim( $combinator ) === '' );
+
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ public function accept( $visitor ) {
+ if ( $this->value_is_object ) { // object or string
+ $this->value = $visitor->visitObj( $this->value );
+ }
+ }
+
+ public function compile( $env ) {
+ return new Less_Tree_Element(
+ $this->combinator,
+ ( $this->value_is_object ? $this->value->compile( $env ) : $this->value ),
+ $this->index,
+ $this->currentFileInfo
+ );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->toCSS(), $this->currentFileInfo, $this->index );
+ }
+
+ public function toCSS() {
+ if ( $this->value_is_object ) {
+ $value = $this->value->toCSS();
+ } else {
+ $value = $this->value;
+ }
+
+ if ( $value === '' && $this->combinator && $this->combinator === '&' ) {
+ return '';
+ }
+
+ return Less_Environment::$_outputMap[$this->combinator] . $value;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Expression.php b/vendor/wikimedia/less.php/lib/Less/Tree/Expression.php
new file mode 100644
index 0000000..8324141
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Expression.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Expression extends Less_Tree {
+ /** @var array */
+ public $value = [];
+ public $parens = false;
+ public $type = 'Expression';
+
+ public function __construct( $value, $parens = null ) {
+ $this->value = $value;
+ $this->parens = $parens;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitArray( $this->value );
+ }
+
+ public function compile( $env ) {
+ $doubleParen = false;
+
+ if ( $this->parens && !$this->parensInOp ) {
+ Less_Environment::$parensStack++;
+ }
+
+ $returnValue = null;
+ if ( $this->value ) {
+
+ $count = count( $this->value );
+
+ if ( $count > 1 ) {
+
+ $ret = [];
+ foreach ( $this->value as $e ) {
+ $ret[] = $e->compile( $env );
+ }
+ $returnValue = new Less_Tree_Expression( $ret );
+
+ } else {
+
+ if ( ( $this->value[0] instanceof Less_Tree_Expression ) && $this->value[0]->parens && !$this->value[0]->parensInOp ) {
+ $doubleParen = true;
+ }
+
+ $returnValue = $this->value[0]->compile( $env );
+ }
+
+ } else {
+ $returnValue = $this;
+ }
+
+ if ( $this->parens ) {
+ if ( !$this->parensInOp ) {
+ Less_Environment::$parensStack--;
+
+ } elseif ( !Less_Environment::isMathOn() && !$doubleParen ) {
+ $returnValue = new Less_Tree_Paren( $returnValue );
+
+ }
+ }
+ return $returnValue;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $val_len = count( $this->value );
+ for ( $i = 0; $i < $val_len; $i++ ) {
+ $this->value[$i]->genCSS( $output );
+ if ( $i + 1 < $val_len ) {
+ $output->add( ' ' );
+ }
+ }
+ }
+
+ public function throwAwayComments() {
+ if ( is_array( $this->value ) ) {
+ $new_value = [];
+ foreach ( $this->value as $v ) {
+ if ( $v instanceof Less_Tree_Comment ) {
+ continue;
+ }
+ $new_value[] = $v;
+ }
+ $this->value = $new_value;
+ }
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Extend.php b/vendor/wikimedia/less.php/lib/Less/Tree/Extend.php
new file mode 100644
index 0000000..362e284
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Extend.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Extend extends Less_Tree {
+
+ public $selector;
+ public $option;
+ public $index;
+ public $selfSelectors = [];
+ public $allowBefore;
+ public $allowAfter;
+ public $firstExtendOnThisSelectorPath;
+ public $type = 'Extend';
+ public $ruleset;
+
+ public $object_id;
+ public $parent_ids = [];
+
+ /**
+ * @param int $index
+ */
+ public function __construct( $selector, $option, $index ) {
+ static $i = 0;
+ $this->selector = $selector;
+ $this->option = $option;
+ $this->index = $index;
+
+ switch ( $option ) {
+ case "all":
+ $this->allowBefore = true;
+ $this->allowAfter = true;
+ break;
+ default:
+ $this->allowBefore = false;
+ $this->allowAfter = false;
+ break;
+ }
+
+ // This must use a string (instead of int) so that array_merge()
+ // preserves keys on arrays that use IDs in their keys.
+ $this->object_id = 'id_' . $i++;
+
+ $this->parent_ids = [
+ $this->object_id => true
+ ];
+ }
+
+ public function accept( $visitor ) {
+ $this->selector = $visitor->visitObj( $this->selector );
+ }
+
+ public function compile( $env ) {
+ Less_Parser::$has_extends = true;
+ $this->selector = $this->selector->compile( $env );
+ return $this;
+ // return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index);
+ }
+
+ public function findSelfSelectors( $selectors ) {
+ $selfElements = [];
+
+ for ( $i = 0, $selectors_len = count( $selectors ); $i < $selectors_len; $i++ ) {
+ $selectorElements = $selectors[$i]->elements;
+ // duplicate the logic in genCSS function inside the selector node.
+ // future TODO - move both logics into the selector joiner visitor
+ if ( $i && $selectorElements && $selectorElements[0]->combinator === "" ) {
+ $selectorElements[0]->combinator = ' ';
+ }
+ $selfElements = array_merge( $selfElements, $selectors[$i]->elements );
+ }
+
+ $this->selfSelectors = [ new Less_Tree_Selector( $selfElements ) ];
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Import.php b/vendor/wikimedia/less.php/lib/Less/Tree/Import.php
new file mode 100644
index 0000000..fc5e81d
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Import.php
@@ -0,0 +1,299 @@
+<?php
+/**
+ * CSS `@import` node
+ *
+ * The general strategy here is that we don't want to wait
+ * for the parsing to be completed, before we start importing
+ * the file. That's because in the context of a browser,
+ * most of the time will be spent waiting for the server to respond.
+ *
+ * On creation, we push the import path to our import queue, though
+ * `import,push`, we also pass it a callback, which it'll call once
+ * the file has been fetched, and parsed.
+ *
+ * @private
+ */
+class Less_Tree_Import extends Less_Tree {
+
+ public $options;
+ public $index;
+ public $path;
+ public $features;
+ public $currentFileInfo;
+ public $css;
+ public $skip;
+ public $root;
+ public $type = 'Import';
+
+ public function __construct( $path, $features, $options, $index, $currentFileInfo = null ) {
+ $this->options = $options;
+ $this->index = $index;
+ $this->path = $path;
+ $this->features = $features;
+ $this->currentFileInfo = $currentFileInfo;
+
+ if ( is_array( $options ) ) {
+ $this->options += [ 'inline' => false ];
+
+ if ( isset( $this->options['less'] ) || $this->options['inline'] ) {
+ $this->css = !isset( $this->options['less'] ) || !$this->options['less'] || $this->options['inline'];
+ } else {
+ $pathValue = $this->getPath();
+ // Leave any ".css" file imports as literals for the browser.
+ // Also leave any remote HTTP resources as literals regardless of whether
+ // they contain ".css" in their filename.
+ if ( $pathValue && preg_match( '/^(https?:)?\/\/|\.css$/i', $pathValue ) ) {
+ $this->css = true;
+ }
+ }
+ }
+ }
+
+//
+// The actual import node doesn't return anything, when converted to CSS.
+// The reason is that it's used at the evaluation stage, so that the rules
+// it imports can be treated like any other rules.
+//
+// In `eval`, we make sure all Import nodes get evaluated, recursively, so
+// we end up with a flat structure, which can easily be imported in the parent
+// ruleset.
+//
+
+ public function accept( $visitor ) {
+ if ( $this->features ) {
+ $this->features = $visitor->visitObj( $this->features );
+ }
+ $this->path = $visitor->visitObj( $this->path );
+
+ if ( !$this->options['inline'] && $this->root ) {
+ $this->root = $visitor->visit( $this->root );
+ }
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( $this->css ) {
+
+ $output->add( '@import ', $this->currentFileInfo, $this->index );
+
+ $this->path->genCSS( $output );
+ if ( $this->features ) {
+ $output->add( ' ' );
+ $this->features->genCSS( $output );
+ }
+ $output->add( ';' );
+ }
+ }
+
+ public function toCSS() {
+ $features = $this->features ? ' ' . $this->features->toCSS() : '';
+
+ if ( $this->css ) {
+ return "@import " . $this->path->toCSS() . $features . ";\n";
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getPath() {
+ if ( $this->path instanceof Less_Tree_Quoted ) {
+ $path = $this->path->value;
+ $path = ( isset( $this->css ) || preg_match( '/(\.[a-z]*$)|([\?;].*)$/', $path ) ) ? $path : $path . '.less';
+
+ // During the first pass, Less_Tree_URL may contain a Less_Tree_Variable (not yet expanded),
+ // and thus has no value property defined yet. Return null until we reach the next phase.
+ // https://github.com/wikimedia/less.php/issues/29
+ } elseif ( $this->path instanceof Less_Tree_URL && !( $this->path->value instanceof Less_Tree_Variable ) ) {
+ $path = $this->path->value->value;
+ } else {
+ return null;
+ }
+
+ // remove query string and fragment
+ return preg_replace( '/[\?#][^\?]*$/', '', $path );
+ }
+
+ public function compileForImport( $env ) {
+ return new Less_Tree_Import( $this->path->compile( $env ), $this->features, $this->options, $this->index, $this->currentFileInfo );
+ }
+
+ public function compilePath( $env ) {
+ $path = $this->path->compile( $env );
+ $rootpath = '';
+ if ( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ) {
+ $rootpath = $this->currentFileInfo['rootpath'];
+ }
+
+ if ( !( $path instanceof Less_Tree_URL ) ) {
+ if ( $rootpath ) {
+ $pathValue = $path->value;
+ // Add the base path if the import is relative
+ if ( $pathValue && Less_Environment::isPathRelative( $pathValue ) ) {
+ $path->value = $this->currentFileInfo['uri_root'] . $pathValue;
+ }
+ }
+ $path->value = Less_Environment::normalizePath( $path->value );
+ }
+
+ return $path;
+ }
+
+ public function compile( $env ) {
+ $evald = $this->compileForImport( $env );
+
+ // get path & uri
+ $path_and_uri = null;
+ if ( is_callable( Less_Parser::$options['import_callback'] ) ) {
+ $path_and_uri = call_user_func( Less_Parser::$options['import_callback'], $evald );
+ }
+
+ if ( !$path_and_uri ) {
+ $path_and_uri = $evald->PathAndUri();
+ }
+
+ if ( $path_and_uri ) {
+ list( $full_path, $uri ) = $path_and_uri;
+ } else {
+ $full_path = $uri = $evald->getPath();
+ }
+
+ // import once
+ if ( $evald->skip( $full_path, $env ) ) {
+ return [];
+ }
+ '@phan-var string $full_path';
+
+ if ( $this->options['inline'] ) {
+ // todo needs to reference css file not import
+ //$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true );
+
+ Less_Parser::AddParsedFile( $full_path );
+ $contents = new Less_Tree_Anonymous( file_get_contents( $full_path ), 0, [], true );
+
+ if ( $this->features ) {
+ return new Less_Tree_Media( [ $contents ], $this->features->value );
+ }
+
+ return [ $contents ];
+ }
+
+ // optional (need to be before "CSS" to support optional CSS imports. CSS should be checked only if empty($this->currentFileInfo))
+ if ( isset( $this->options['optional'] ) && $this->options['optional'] && !file_exists( $full_path ) && ( !$evald->css || !empty( $this->currentFileInfo ) ) ) {
+ return [];
+ }
+
+ // css ?
+ if ( $evald->css ) {
+ $features = ( $evald->features ? $evald->features->compile( $env ) : null );
+ return new Less_Tree_Import( $this->compilePath( $env ), $features, $this->options, $this->index );
+ }
+
+ return $this->ParseImport( $full_path, $uri, $env );
+ }
+
+ /**
+ * Using the import directories, get the full absolute path and uri of the import
+ */
+ public function PathAndUri() {
+ $evald_path = $this->getPath();
+
+ if ( $evald_path ) {
+
+ $import_dirs = [];
+
+ if ( Less_Environment::isPathRelative( $evald_path ) ) {
+ // if the path is relative, the file should be in the current directory
+ if ( $this->currentFileInfo ) {
+ $import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root'];
+ }
+
+ } else {
+ // otherwise, the file should be relative to the server root
+ if ( $this->currentFileInfo ) {
+ $import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri'];
+ }
+ // if the user supplied entryPath isn't the actual root
+ $import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = '';
+
+ }
+
+ // always look in user supplied import directories
+ $import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] );
+
+ foreach ( $import_dirs as $rootpath => $rooturi ) {
+ if ( is_callable( $rooturi ) ) {
+ list( $path, $uri ) = call_user_func( $rooturi, $evald_path );
+ if ( is_string( $path ) ) {
+ $full_path = $path;
+ return [ $full_path, $uri ];
+ }
+ } elseif ( !empty( $rootpath ) ) {
+
+ $path = rtrim( $rootpath, '/\\' ) . '/' . ltrim( $evald_path, '/\\' );
+
+ if ( file_exists( $path ) ) {
+ $full_path = Less_Environment::normalizePath( $path );
+ $uri = Less_Environment::normalizePath( dirname( $rooturi . $evald_path ) );
+ return [ $full_path, $uri ];
+ } elseif ( file_exists( $path . '.less' ) ) {
+ $full_path = Less_Environment::normalizePath( $path . '.less' );
+ $uri = Less_Environment::normalizePath( dirname( $rooturi . $evald_path . '.less' ) );
+ return [ $full_path, $uri ];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Parse the import url and return the rules
+ *
+ * @param string $full_path
+ * @param string|null $uri
+ * @param mixed $env
+ * @return Less_Tree_Media|array
+ */
+ public function ParseImport( $full_path, $uri, $env ) {
+ $import_env = clone $env;
+ if ( ( isset( $this->options['reference'] ) && $this->options['reference'] ) || isset( $this->currentFileInfo['reference'] ) ) {
+ $import_env->currentFileInfo['reference'] = true;
+ }
+
+ if ( ( isset( $this->options['multiple'] ) && $this->options['multiple'] ) ) {
+ $import_env->importMultiple = true;
+ }
+
+ $parser = new Less_Parser( $import_env );
+ $root = $parser->parseFile( $full_path, $uri, true );
+
+ $ruleset = new Less_Tree_Ruleset( null, $root->rules );
+ $ruleset->evalImports( $import_env );
+
+ return $this->features ? new Less_Tree_Media( $ruleset->rules, $this->features->value ) : $ruleset->rules;
+ }
+
+ /**
+ * Should the import be skipped?
+ *
+ * @param string|null $path
+ * @param Less_Environment $env
+ * @return bool|null
+ */
+ private function skip( $path, $env ) {
+ $path = Less_Parser::AbsPath( $path, true );
+
+ if ( $path && Less_Parser::FileParsed( $path ) ) {
+
+ if ( isset( $this->currentFileInfo['reference'] ) ) {
+ return true;
+ }
+
+ return !isset( $this->options['multiple'] ) && !$env->importMultiple;
+ }
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Javascript.php b/vendor/wikimedia/less.php/lib/Less/Tree/Javascript.php
new file mode 100644
index 0000000..218d481
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Javascript.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Javascript extends Less_Tree {
+
+ public $type = 'Javascript';
+ public $escaped;
+ public $expression;
+ public $index;
+
+ /**
+ * @param bool $index
+ * @param bool $escaped
+ */
+ public function __construct( $string, $index, $escaped ) {
+ $this->escaped = $escaped;
+ $this->expression = $string;
+ $this->index = $index;
+ }
+
+ public function compile( $env ) {
+ return new Less_Tree_Anonymous( '/* Sorry, can not do JavaScript evaluation in PHP... :( */' );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Keyword.php b/vendor/wikimedia/less.php/lib/Less/Tree/Keyword.php
new file mode 100644
index 0000000..60663f0
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Keyword.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Keyword extends Less_Tree {
+
+ public $value;
+ public $type = 'Keyword';
+
+ /**
+ * @param string $value
+ */
+ public function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( $this->value === '%' ) {
+ throw new Less_Exception_Compiler( "Invalid % without number" );
+ }
+
+ $output->add( $this->value );
+ }
+
+ public function compare( $other ) {
+ if ( $other instanceof Less_Tree_Keyword ) {
+ return $other->value === $this->value ? 0 : 1;
+ } else {
+ return -1;
+ }
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Media.php b/vendor/wikimedia/less.php/lib/Less/Tree/Media.php
new file mode 100644
index 0000000..3cb1fc4
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Media.php
@@ -0,0 +1,183 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Media extends Less_Tree {
+
+ public $features;
+ public $rules;
+ public $index;
+ public $currentFileInfo;
+ public $isReferenced;
+ public $type = 'Media';
+
+ public function __construct( $value = [], $features = [], $index = null, $currentFileInfo = null ) {
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+
+ $selectors = $this->emptySelectors();
+
+ $this->features = new Less_Tree_Value( $features );
+
+ $this->rules = [ new Less_Tree_Ruleset( $selectors, $value ) ];
+ $this->rules[0]->allowImports = true;
+ }
+
+ public function accept( $visitor ) {
+ $this->features = $visitor->visitObj( $this->features );
+ $this->rules = $visitor->visitArray( $this->rules );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( '@media ', $this->currentFileInfo, $this->index );
+ $this->features->genCSS( $output );
+ Less_Tree::outputRuleset( $output, $this->rules );
+ }
+
+ /**
+ * @param Less_Environment $env
+ * @return Less_Tree_Media|Less_Tree_Ruleset
+ * @see less-2.5.3.js#Media.prototype.eval
+ */
+ public function compile( $env ) {
+ $media = new Less_Tree_Media( [], [], $this->index, $this->currentFileInfo );
+
+ $strictMathBypass = false;
+ if ( Less_Parser::$options['strictMath'] === false ) {
+ $strictMathBypass = true;
+ Less_Parser::$options['strictMath'] = true;
+ }
+
+ $media->features = $this->features->compile( $env );
+
+ if ( $strictMathBypass ) {
+ Less_Parser::$options['strictMath'] = false;
+ }
+
+ $env->mediaPath[] = $media;
+ $env->mediaBlocks[] = $media;
+
+ array_unshift( $env->frames, $this->rules[0] );
+ $media->rules = [ $this->rules[0]->compile( $env ) ];
+ array_shift( $env->frames );
+
+ array_pop( $env->mediaPath );
+
+ return !$env->mediaPath ? $media->compileTop( $env ) : $media->compileNested( $env );
+ }
+
+ public function variable( $name ) {
+ return $this->rules[0]->variable( $name );
+ }
+
+ public function find( $selector ) {
+ return $this->rules[0]->find( $selector, $this );
+ }
+
+ public function emptySelectors() {
+ $el = new Less_Tree_Element( '', '&', $this->index, $this->currentFileInfo );
+ $sels = [ new Less_Tree_Selector( [ $el ], [], null, $this->index, $this->currentFileInfo ) ];
+ $sels[0]->mediaEmpty = true;
+ return $sels;
+ }
+
+ public function markReferenced() {
+ $this->rules[0]->markReferenced();
+ $this->isReferenced = true;
+ Less_Tree::ReferencedArray( $this->rules[0]->rules );
+ }
+
+ // evaltop
+ public function compileTop( $env ) {
+ $result = $this;
+
+ if ( count( $env->mediaBlocks ) > 1 ) {
+ $selectors = $this->emptySelectors();
+ $result = new Less_Tree_Ruleset( $selectors, $env->mediaBlocks );
+ $result->multiMedia = true;
+ }
+
+ $env->mediaBlocks = [];
+ $env->mediaPath = [];
+
+ return $result;
+ }
+
+ /**
+ * @param Less_Environment $env
+ * @return Less_Tree_Ruleset
+ */
+ public function compileNested( $env ) {
+ $path = array_merge( $env->mediaPath, [ $this ] );
+ '@phan-var array<Less_Tree_Media> $path';
+
+ // Extract the media-query conditions separated with `,` (OR).
+ foreach ( $path as $key => $p ) {
+ $value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features;
+ $path[$key] = is_array( $value ) ? $value : [ $value ];
+ }
+ '@phan-var array<array<Less_Tree>> $path';
+
+ // Trace all permutations to generate the resulting media-query.
+ //
+ // (a, b and c) with nested (d, e) ->
+ // a and d
+ // a and e
+ // b and c and d
+ // b and c and e
+
+ $permuted = $this->permute( $path );
+ $expressions = [];
+ foreach ( $permuted as $path ) {
+
+ for ( $i = 0, $len = count( $path ); $i < $len; $i++ ) {
+ $path[$i] = Less_Parser::is_method( $path[$i], 'toCSS' ) ? $path[$i] : new Less_Tree_Anonymous( $path[$i] );
+ }
+
+ for ( $i = count( $path ) - 1; $i > 0; $i-- ) {
+ array_splice( $path, $i, 0, [ new Less_Tree_Anonymous( 'and' ) ] );
+ }
+
+ $expressions[] = new Less_Tree_Expression( $path );
+ }
+ $this->features = new Less_Tree_Value( $expressions );
+
+ // Fake a tree-node that doesn't output anything.
+ return new Less_Tree_Ruleset( [], [] );
+ }
+
+ public function permute( $arr ) {
+ if ( !$arr ) {
+ return [];
+ }
+
+ if ( count( $arr ) == 1 ) {
+ return $arr[0];
+ }
+
+ $result = [];
+ $rest = $this->permute( array_slice( $arr, 1 ) );
+ foreach ( $rest as $r ) {
+ foreach ( $arr[0] as $a ) {
+ $result[] = array_merge(
+ is_array( $a ) ? $a : [ $a ],
+ is_array( $r ) ? $r : [ $r ]
+ );
+ }
+ }
+
+ return $result;
+ }
+
+ public function bubbleSelectors( $selectors ) {
+ if ( !$selectors ) {
+ return;
+ }
+
+ $this->rules = [ new Less_Tree_Ruleset( $selectors, [ $this->rules[0] ] ) ];
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Mixin/Call.php b/vendor/wikimedia/less.php/lib/Less/Tree/Mixin/Call.php
new file mode 100644
index 0000000..61e10fe
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Mixin/Call.php
@@ -0,0 +1,197 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Mixin_Call extends Less_Tree {
+
+ public $selector;
+ public $arguments;
+ public $index;
+ public $currentFileInfo;
+
+ public $important;
+ public $type = 'MixinCall';
+
+ /**
+ * less.js: tree.mixin.Call
+ *
+ */
+ public function __construct( $elements, $args, $index, $currentFileInfo, $important = false ) {
+ $this->selector = new Less_Tree_Selector( $elements );
+ $this->arguments = $args;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ $this->important = $important;
+ }
+
+ // function accept($visitor){
+ // $this->selector = $visitor->visit($this->selector);
+ // $this->arguments = $visitor->visit($this->arguments);
+ //}
+
+ public function compile( $env ) {
+ $rules = [];
+ $match = false;
+ $isOneFound = false;
+ $candidates = [];
+ $defaultUsed = false;
+ $conditionResult = [];
+
+ $args = [];
+ foreach ( $this->arguments as $a ) {
+ $args[] = [ 'name' => $a['name'], 'value' => $a['value']->compile( $env ) ];
+ }
+
+ foreach ( $env->frames as $frame ) {
+
+ $mixins = $frame->find( $this->selector );
+
+ if ( !$mixins ) {
+ continue;
+ }
+
+ $isOneFound = true;
+ $defNone = 0;
+ $defTrue = 1;
+ $defFalse = 2;
+
+ // To make `default()` function independent of definition order we have two "subpasses" here.
+ // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
+ // and build candidate list with corresponding flags. Then, when we know all possible matches,
+ // we make a final decision.
+
+ $mixins_len = count( $mixins );
+ for ( $m = 0; $m < $mixins_len; $m++ ) {
+ $mixin = $mixins[$m];
+
+ if ( $this->IsRecursive( $env, $mixin ) ) {
+ continue;
+ }
+
+ if ( $mixin->matchArgs( $args, $env ) ) {
+
+ $candidate = [ 'mixin' => $mixin, 'group' => $defNone ];
+
+ if ( $mixin instanceof Less_Tree_Ruleset ) {
+ for ( $f = 0; $f < 2; $f++ ) {
+ Less_Tree_DefaultFunc::value( $f );
+ $conditionResult[$f] = $mixin->matchCondition( $args, $env );
+ }
+
+ // PhanTypeInvalidDimOffset -- False positive
+ '@phan-var array{0:bool,1:bool} $conditionResult';
+
+ if ( $conditionResult[0] || $conditionResult[1] ) {
+ if ( $conditionResult[0] != $conditionResult[1] ) {
+ $candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse;
+ }
+
+ $candidates[] = $candidate;
+ }
+ } else {
+ $candidates[] = $candidate;
+ }
+
+ $match = true;
+ }
+ }
+
+ Less_Tree_DefaultFunc::reset();
+
+ $count = [ 0, 0, 0 ];
+ for ( $m = 0; $m < count( $candidates ); $m++ ) {
+ $count[ $candidates[$m]['group'] ]++;
+ }
+
+ if ( $count[$defNone] > 0 ) {
+ $defaultResult = $defFalse;
+ } else {
+ $defaultResult = $defTrue;
+ if ( ( $count[$defTrue] + $count[$defFalse] ) > 1 ) {
+ throw new Exception( 'Ambiguous use of `default()` found when matching for `' . $this->format( $args ) . '`' );
+ }
+ }
+
+ $candidates_length = count( $candidates );
+ $length_1 = ( $candidates_length == 1 );
+
+ for ( $m = 0; $m < $candidates_length; $m++ ) {
+ $candidate = $candidates[$m]['group'];
+ if ( ( $candidate === $defNone ) || ( $candidate === $defaultResult ) ) {
+ try{
+ $mixin = $candidates[$m]['mixin'];
+ if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
+ $mixin = new Less_Tree_Mixin_Definition( '', [], $mixin->rules, null, false );
+ $mixin->originalRuleset = $mixins[$m]->originalRuleset;
+ }
+ $rules = array_merge( $rules, $mixin->evalCall( $env, $args, $this->important )->rules );
+ } catch ( Exception $e ) {
+ // throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']);
+ throw new Less_Exception_Compiler( $e->getMessage(), null, null, $this->currentFileInfo );
+ }
+ }
+ }
+
+ if ( $match ) {
+ if ( !$this->currentFileInfo || !isset( $this->currentFileInfo['reference'] ) || !$this->currentFileInfo['reference'] ) {
+ Less_Tree::ReferencedArray( $rules );
+ }
+
+ return $rules;
+ }
+ }
+
+ if ( $isOneFound ) {
+ throw new Less_Exception_Compiler( 'No matching definition was found for `' . $this->Format( $args ) . '`', null, $this->index, $this->currentFileInfo );
+
+ } else {
+ throw new Less_Exception_Compiler( trim( $this->selector->toCSS() ) . " is undefined in " . $this->currentFileInfo['filename'], null, $this->index );
+ }
+ }
+
+ /**
+ * Format the args for use in exception messages
+ *
+ */
+ private function Format( $args ) {
+ $message = [];
+ if ( $args ) {
+ foreach ( $args as $a ) {
+ $argValue = '';
+ if ( $a['name'] ) {
+ $argValue .= $a['name'] . ':';
+ }
+ if ( is_object( $a['value'] ) ) {
+ $argValue .= $a['value']->toCSS();
+ } else {
+ $argValue .= '???';
+ }
+ $message[] = $argValue;
+ }
+ }
+ return implode( ', ', $message );
+ }
+
+ /**
+ * Are we in a recursive mixin call?
+ *
+ * @return bool
+ */
+ private function IsRecursive( $env, $mixin ) {
+ foreach ( $env->frames as $recur_frame ) {
+ if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
+
+ if ( $mixin === $recur_frame ) {
+ return true;
+ }
+
+ if ( isset( $recur_frame->originalRuleset ) && $mixin->ruleset_id === $recur_frame->originalRuleset ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Mixin/Definition.php b/vendor/wikimedia/less.php/lib/Less/Tree/Mixin/Definition.php
new file mode 100644
index 0000000..08bcd7a
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Mixin/Definition.php
@@ -0,0 +1,236 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
+ public $name;
+ public $selectors;
+ public $params;
+ public $arity = 0;
+ public $rules;
+ public $lookups = [];
+ public $required = 0;
+ public $frames = [];
+ public $condition;
+ public $variadic;
+ public $type = 'MixinDefinition';
+
+ // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition
+ public function __construct( $name, $params, $rules, $condition, $variadic = false, $frames = [] ) {
+ $this->name = $name;
+ $this->selectors = [ new Less_Tree_Selector( [ new Less_Tree_Element( null, $name ) ] ) ];
+
+ $this->params = $params;
+ $this->condition = $condition;
+ $this->variadic = $variadic;
+ $this->rules = $rules;
+
+ if ( $params ) {
+ $this->arity = count( $params );
+ foreach ( $params as $p ) {
+ if ( !isset( $p['name'] ) || ( $p['name'] && !isset( $p['value'] ) ) ) {
+ $this->required++;
+ }
+ }
+ }
+
+ $this->frames = $frames;
+ $this->SetRulesetIndex();
+ }
+
+ // function accept( $visitor ){
+ // $this->params = $visitor->visit($this->params);
+ // $this->rules = $visitor->visit($this->rules);
+ // $this->condition = $visitor->visit($this->condition);
+ //}
+
+ public function toCSS() {
+ return '';
+ }
+
+ // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams
+ public function compileParams( $env, $mixinFrames, $args = [], &$evaldArguments = [] ) {
+ $frame = new Less_Tree_Ruleset( null, [] );
+ $params = $this->params;
+ $mixinEnv = null;
+ $argsLength = 0;
+
+ if ( $args ) {
+ $argsLength = count( $args );
+ for ( $i = 0; $i < $argsLength; $i++ ) {
+ $arg = $args[$i];
+
+ if ( $arg && $arg['name'] ) {
+ $isNamedFound = false;
+
+ foreach ( $params as $j => $param ) {
+ if ( !isset( $evaldArguments[$j] ) && $arg['name'] === $params[$j]['name'] ) {
+ $evaldArguments[$j] = $arg['value']->compile( $env );
+ array_unshift( $frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile( $env ) ) );
+ $isNamedFound = true;
+ break;
+ }
+ }
+ if ( $isNamedFound ) {
+ array_splice( $args, $i, 1 );
+ $i--;
+ $argsLength--;
+ continue;
+ } else {
+ throw new Less_Exception_Compiler( "Named argument for " . $this->name . ' ' . $args[$i]['name'] . ' not found' );
+ }
+ }
+ }
+ }
+
+ $argIndex = 0;
+ foreach ( $params as $i => $param ) {
+
+ if ( isset( $evaldArguments[$i] ) ) { continue;
+ }
+
+ $arg = null;
+ if ( isset( $args[$argIndex] ) ) {
+ $arg = $args[$argIndex];
+ }
+
+ if ( isset( $param['name'] ) && $param['name'] ) {
+
+ if ( isset( $param['variadic'] ) ) {
+ $varargs = [];
+ for ( $j = $argIndex; $j < $argsLength; $j++ ) {
+ $varargs[] = $args[$j]['value']->compile( $env );
+ }
+ $expression = new Less_Tree_Expression( $varargs );
+ array_unshift( $frame->rules, new Less_Tree_Rule( $param['name'], $expression->compile( $env ) ) );
+ } else {
+ $val = ( $arg && $arg['value'] ) ? $arg['value'] : false;
+
+ if ( $val ) {
+ $val = $val->compile( $env );
+ } elseif ( isset( $param['value'] ) ) {
+
+ if ( !$mixinEnv ) {
+ $mixinEnv = new Less_Environment();
+ $mixinEnv->frames = array_merge( [ $frame ], $mixinFrames );
+ }
+
+ $val = $param['value']->compile( $mixinEnv );
+ $frame->resetCache();
+ } else {
+ throw new Less_Exception_Compiler( "Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")" );
+ }
+
+ array_unshift( $frame->rules, new Less_Tree_Rule( $param['name'], $val ) );
+ $evaldArguments[$i] = $val;
+ }
+ }
+
+ if ( isset( $param['variadic'] ) && $args ) {
+ for ( $j = $argIndex; $j < $argsLength; $j++ ) {
+ $evaldArguments[$j] = $args[$j]['value']->compile( $env );
+ }
+ }
+ $argIndex++;
+ }
+
+ ksort( $evaldArguments );
+ $evaldArguments = array_values( $evaldArguments );
+
+ return $frame;
+ }
+
+ public function compile( $env ) {
+ if ( $this->frames ) {
+ return new Less_Tree_Mixin_Definition( $this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames );
+ }
+ return new Less_Tree_Mixin_Definition( $this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames );
+ }
+
+ public function evalCall( $env, $args = null, $important = null ) {
+ Less_Environment::$mixin_stack++;
+
+ $_arguments = [];
+
+ if ( $this->frames ) {
+ $mixinFrames = array_merge( $this->frames, $env->frames );
+ } else {
+ $mixinFrames = $env->frames;
+ }
+
+ $frame = $this->compileParams( $env, $mixinFrames, $args, $_arguments );
+
+ $ex = new Less_Tree_Expression( $_arguments );
+ array_unshift( $frame->rules, new Less_Tree_Rule( '@arguments', $ex->compile( $env ) ) );
+
+ $ruleset = new Less_Tree_Ruleset( null, $this->rules );
+ $ruleset->originalRuleset = $this->ruleset_id;
+
+ $ruleSetEnv = new Less_Environment();
+ $ruleSetEnv->frames = array_merge( [ $this, $frame ], $mixinFrames );
+ $ruleset = $ruleset->compile( $ruleSetEnv );
+
+ if ( $important ) {
+ $ruleset = $ruleset->makeImportant();
+ }
+
+ Less_Environment::$mixin_stack--;
+
+ return $ruleset;
+ }
+
+ /** @return bool */
+ public function matchCondition( $args, $env ) {
+ if ( !$this->condition ) {
+ return true;
+ }
+
+ // set array to prevent error on array_merge
+ if ( !is_array( $this->frames ) ) {
+ $this->frames = [];
+ }
+
+ $frame = $this->compileParams( $env, array_merge( $this->frames, $env->frames ), $args );
+
+ $compile_env = new Less_Environment();
+ $compile_env->frames = array_merge(
+ [ $frame ], // the parameter variables
+ $this->frames, // the parent namespace/mixin frames
+ $env->frames // the current environment frames
+ );
+
+ $compile_env->functions = $env->functions;
+
+ return (bool)$this->condition->compile( $compile_env );
+ }
+
+ public function matchArgs( $args, $env = null ) {
+ $argsLength = count( $args );
+
+ if ( !$this->variadic ) {
+ if ( $argsLength < $this->required ) {
+ return false;
+ }
+ if ( $argsLength > count( $this->params ) ) {
+ return false;
+ }
+ } else {
+ if ( $argsLength < ( $this->required - 1 ) ) {
+ return false;
+ }
+ }
+
+ $len = min( $argsLength, $this->arity );
+
+ for ( $i = 0; $i < $len; $i++ ) {
+ if ( !isset( $this->params[$i]['name'] ) && !isset( $this->params[$i]['variadic'] ) ) {
+ if ( $args[$i]['value']->compile( $env )->toCSS() != $this->params[$i]['value']->compile( $env )->toCSS() ) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/NameValue.php b/vendor/wikimedia/less.php/lib/Less/Tree/NameValue.php
new file mode 100644
index 0000000..88a2296
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/NameValue.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * A simple CSS name-value pair, e.g. `width: 100px;`
+ *
+ * In bootstrap, there are about 600-1000 simple name-value pairs (depending on
+ * how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule).
+ *
+ * Using the name-value object can speed up bootstrap compilation slightly, but
+ * it breaks color keyword interpretation: `color: red` -> `color: #FF0000`.
+ *
+ * @private
+ */
+class Less_Tree_NameValue extends Less_Tree {
+
+ public $name;
+ public $value;
+ public $index;
+ public $currentFileInfo;
+ public $type = 'NameValue';
+ public $important = '';
+
+ public function __construct( $name, $value = null, $index = null, $currentFileInfo = null ) {
+ $this->name = $name;
+ $this->value = $value;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ public function genCSS( $output ) {
+ $output->add(
+ $this->name
+ . Less_Environment::$_outputMap[': ']
+ . $this->value
+ . $this->important
+ . ( ( ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ),
+ $this->currentFileInfo, $this->index );
+ }
+
+ public function compile( $env ) {
+ return $this;
+ }
+
+ public function makeImportant() {
+ $new = new Less_Tree_NameValue( $this->name, $this->value, $this->index, $this->currentFileInfo );
+ $new->important = ' !important';
+ return $new;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Negative.php b/vendor/wikimedia/less.php/lib/Less/Tree/Negative.php
new file mode 100644
index 0000000..516f1ee
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Negative.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Negative extends Less_Tree {
+
+ public $value;
+ public $type = 'Negative';
+
+ public function __construct( $node ) {
+ $this->value = $node;
+ }
+
+ // function accept($visitor) {
+ // $this->value = $visitor->visit($this->value);
+ //}
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( '-' );
+ $this->value->genCSS( $output );
+ }
+
+ public function compile( $env ) {
+ if ( Less_Environment::isMathOn() ) {
+ $ret = new Less_Tree_Operation( '*', [ new Less_Tree_Dimension( -1 ), $this->value ] );
+ return $ret->compile( $env );
+ }
+ return new Less_Tree_Negative( $this->value->compile( $env ) );
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Operation.php b/vendor/wikimedia/less.php/lib/Less/Tree/Operation.php
new file mode 100644
index 0000000..4d79dc0
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Operation.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Operation extends Less_Tree {
+
+ public $op;
+ public $operands;
+ public $isSpaced;
+ public $type = 'Operation';
+
+ /**
+ * @param string $op
+ */
+ public function __construct( $op, $operands, $isSpaced = false ) {
+ $this->op = trim( $op );
+ $this->operands = $operands;
+ $this->isSpaced = $isSpaced;
+ }
+
+ public function accept( $visitor ) {
+ $this->operands = $visitor->visitArray( $this->operands );
+ }
+
+ public function compile( $env ) {
+ $a = $this->operands[0]->compile( $env );
+ $b = $this->operands[1]->compile( $env );
+
+ if ( Less_Environment::isMathOn() ) {
+
+ if ( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ) {
+ $a = $a->toColor();
+
+ } elseif ( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ) {
+ $b = $b->toColor();
+
+ }
+
+ if ( !method_exists( $a, 'operate' ) ) {
+ throw new Less_Exception_Compiler( "Operation on an invalid type" );
+ }
+
+ return $a->operate( $this->op, $b );
+ }
+
+ return new Less_Tree_Operation( $this->op, [ $a, $b ], $this->isSpaced );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $this->operands[0]->genCSS( $output );
+ if ( $this->isSpaced ) {
+ $output->add( " " );
+ }
+ $output->add( $this->op );
+ if ( $this->isSpaced ) {
+ $output->add( ' ' );
+ }
+ $this->operands[1]->genCSS( $output );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Paren.php b/vendor/wikimedia/less.php/lib/Less/Tree/Paren.php
new file mode 100644
index 0000000..d659b84
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Paren.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Paren extends Less_Tree {
+
+ /** @var Less_Tree $value */
+ public $value;
+ public $type = 'Paren';
+
+ /**
+ * @param Less_Tree $value
+ */
+ public function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( '(' );
+ $this->value->genCSS( $output );
+ $output->add( ')' );
+ }
+
+ public function compile( $env ) {
+ return new Less_Tree_Paren( $this->value->compile( $env ) );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Quoted.php b/vendor/wikimedia/less.php/lib/Less/Tree/Quoted.php
new file mode 100644
index 0000000..0c5e190
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Quoted.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Quoted extends Less_Tree {
+ public $escaped;
+ public $value;
+ public $quote;
+ public $index;
+ public $currentFileInfo;
+ public $type = 'Quoted';
+
+ /**
+ * @param string $str
+ */
+ public function __construct( $str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ) {
+ $this->escaped = $escaped;
+ $this->value = $content;
+ if ( $str ) {
+ $this->quote = $str[0];
+ }
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( !$this->escaped ) {
+ $output->add( $this->quote, $this->currentFileInfo, $this->index );
+ }
+ $output->add( $this->value );
+ if ( !$this->escaped ) {
+ $output->add( $this->quote );
+ }
+ }
+
+ public function compile( $env ) {
+ $value = $this->value;
+ if ( preg_match_all( '/`([^`]+)`/', $this->value, $matches ) ) {
+ foreach ( $matches as $i => $match ) {
+ $js = new Less_Tree_JavaScript( $matches[1], $this->index, true );
+ $js = $js->compile( $env )->value;
+ $value = str_replace( $matches[0][$i], $js, $value );
+ }
+ }
+
+ if ( preg_match_all( '/@\{([\w-]+)\}/', $value, $matches ) ) {
+ foreach ( $matches[1] as $i => $match ) {
+ $v = new Less_Tree_Variable( '@' . $match, $this->index, $this->currentFileInfo );
+ $v = $v->compile( $env );
+ $v = ( $v instanceof Less_Tree_Quoted ) ? $v->value : $v->toCSS();
+ $value = str_replace( $matches[0][$i], $v, $value );
+ }
+ }
+
+ return new Less_Tree_Quoted( $this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo );
+ }
+
+ public function compare( $x ) {
+ if ( !Less_Parser::is_method( $x, 'toCSS' ) ) {
+ return -1;
+ }
+
+ $left = $this->toCSS();
+ $right = $x->toCSS();
+
+ if ( $left === $right ) {
+ return 0;
+ }
+
+ return $left < $right ? -1 : 1;
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Rule.php b/vendor/wikimedia/less.php/lib/Less/Tree/Rule.php
new file mode 100644
index 0000000..842ad0c
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Rule.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Rule extends Less_Tree {
+
+ public $name;
+ /** @var Less_Tree $value */
+ public $value;
+ public $important;
+ public $merge;
+ public $index;
+ public $inline;
+ public $variable;
+ public $currentFileInfo;
+ public $type = 'Rule';
+
+ /**
+ * @param string $important
+ */
+ public function __construct( $name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null, $inline = false ) {
+ $this->name = $name;
+ $this->value = ( $value instanceof Less_Tree )
+ ? $value
+ : new Less_Tree_Value( [ $value ] );
+ $this->important = $important ? ' ' . trim( $important ) : '';
+ $this->merge = $merge;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ $this->inline = $inline;
+ $this->variable = ( is_string( $name ) && $name[0] === '@' );
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index );
+ try{
+ $this->value->genCSS( $output );
+
+ }catch ( Less_Exception_Parser $e ) {
+ $e->index = $this->index;
+ $e->currentFile = $this->currentFileInfo;
+ throw $e;
+ }
+ $output->add( $this->important . ( ( $this->inline || ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ), $this->currentFileInfo, $this->index );
+ }
+
+ /**
+ * @param Less_Environment $env
+ * @return Less_Tree_Rule
+ */
+ public function compile( $env ) {
+ $name = $this->name;
+ if ( is_array( $name ) ) {
+ // expand 'primitive' name directly to get
+ // things faster (~10% for benchmark.less):
+ if ( count( $name ) === 1 && $name[0] instanceof Less_Tree_Keyword ) {
+ $name = $name[0]->value;
+ } else {
+ $name = $this->CompileName( $env, $name );
+ }
+ }
+
+ $strictMathBypass = Less_Parser::$options['strictMath'];
+ if ( $name === "font" && !Less_Parser::$options['strictMath'] ) {
+ Less_Parser::$options['strictMath'] = true;
+ }
+
+ try {
+ $evaldValue = $this->value->compile( $env );
+
+ if ( !$this->variable && $evaldValue->type === "DetachedRuleset" ) {
+ throw new Less_Exception_Compiler( "Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo );
+ }
+
+ if ( Less_Environment::$mixin_stack ) {
+ $return = new Less_Tree_Rule( $name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline );
+ } else {
+ $this->name = $name;
+ $this->value = $evaldValue;
+ $return = $this;
+ }
+
+ } catch ( Less_Exception_Parser $e ) {
+ if ( !is_numeric( $e->index ) ) {
+ $e->index = $this->index;
+ $e->currentFile = $this->currentFileInfo;
+ }
+ throw $e;
+ }
+
+ Less_Parser::$options['strictMath'] = $strictMathBypass;
+
+ return $return;
+ }
+
+ public function CompileName( $env, $name ) {
+ $output = new Less_Output();
+ foreach ( $name as $n ) {
+ $n->compile( $env )->genCSS( $output );
+ }
+ return $output->toString();
+ }
+
+ public function makeImportant() {
+ return new Less_Tree_Rule( $this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Ruleset.php b/vendor/wikimedia/less.php/lib/Less/Tree/Ruleset.php
new file mode 100644
index 0000000..7acc6af
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Ruleset.php
@@ -0,0 +1,730 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Ruleset extends Less_Tree {
+
+ protected $lookups;
+ public $_variables;
+ public $_rulesets;
+
+ public $strictImports;
+
+ public $selectors;
+ public $rules;
+ public $root;
+ public $allowImports;
+ public $paths;
+ public $firstRoot;
+ public $type = 'Ruleset';
+ public $multiMedia;
+ public $allExtends;
+
+ public $ruleset_id;
+ public $originalRuleset;
+
+ public $first_oelements;
+
+ public function SetRulesetIndex() {
+ $this->ruleset_id = Less_Parser::$next_id++;
+ $this->originalRuleset = $this->ruleset_id;
+
+ if ( $this->selectors ) {
+ foreach ( $this->selectors as $sel ) {
+ if ( $sel->_oelements ) {
+ $this->first_oelements[$sel->_oelements[0]] = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param null|Less_Tree_Selector[] $selectors
+ * @param Less_Tree[] $rules
+ * @param null|bool $strictImports
+ */
+ public function __construct( $selectors, $rules, $strictImports = null ) {
+ $this->selectors = $selectors;
+ $this->rules = $rules;
+ $this->lookups = [];
+ $this->strictImports = $strictImports;
+ $this->SetRulesetIndex();
+ }
+
+ public function accept( $visitor ) {
+ if ( $this->paths !== null ) {
+ $paths_len = count( $this->paths );
+ for ( $i = 0; $i < $paths_len; $i++ ) {
+ $this->paths[$i] = $visitor->visitArray( $this->paths[$i] );
+ }
+ } elseif ( $this->selectors ) {
+ $this->selectors = $visitor->visitArray( $this->selectors );
+ }
+
+ if ( $this->rules ) {
+ $this->rules = $visitor->visitArray( $this->rules );
+ }
+ }
+
+ /**
+ * @param Less_Environment $env
+ * @return Less_Tree_Ruleset
+ * @see less-2.5.3.js#Ruleset.prototype.eval
+ */
+ public function compile( $env ) {
+ $ruleset = $this->PrepareRuleset( $env );
+
+ // Store the frames around mixin definitions,
+ // so they can be evaluated like closures when the time comes.
+ $rsRuleCnt = count( $ruleset->rules );
+ for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+ // These checks are the equivalent of the rule.evalFirst property in less.js
+ if ( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ) {
+ $ruleset->rules[$i] = $ruleset->rules[$i]->compile( $env );
+ }
+ }
+
+ $mediaBlockCount = count( $env->mediaBlocks );
+
+ // Evaluate mixin calls.
+ $this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
+
+ // Evaluate everything else
+ for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+ if ( !( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ) ) {
+ $ruleset->rules[$i] = $ruleset->rules[$i]->compile( $env );
+ }
+ }
+
+ // Evaluate everything else
+ for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+ $rule = $ruleset->rules[$i];
+
+ // for rulesets, check if it is a css guard and can be removed
+ if ( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count( $rule->selectors ) === 1 ) {
+
+ // check if it can be folded in (e.g. & where)
+ if ( $rule->selectors[0]->isJustParentSelector() ) {
+ array_splice( $ruleset->rules, $i--, 1 );
+ $rsRuleCnt--;
+
+ for ( $j = 0; $j < count( $rule->rules ); $j++ ) {
+ $subRule = $rule->rules[$j];
+ if ( !( $subRule instanceof Less_Tree_Rule ) || !$subRule->variable ) {
+ array_splice( $ruleset->rules, ++$i, 0, [ $subRule ] );
+ $rsRuleCnt++;
+ }
+ }
+
+ }
+ }
+ }
+
+ // Pop the stack
+ $env->shiftFrame();
+
+ if ( $mediaBlockCount ) {
+ $len = count( $env->mediaBlocks );
+ for ( $i = $mediaBlockCount; $i < $len; $i++ ) {
+ $env->mediaBlocks[$i]->bubbleSelectors( $ruleset->selectors );
+ }
+ }
+
+ return $ruleset;
+ }
+
+ /**
+ * Compile Less_Tree_Mixin_Call objects
+ *
+ * @param Less_Tree_Ruleset $ruleset
+ * @param int $rsRuleCnt
+ */
+ private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ) {
+ for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+ $rule = $ruleset->rules[$i];
+
+ if ( $rule instanceof Less_Tree_Mixin_Call ) {
+ $rule = $rule->compile( $env );
+
+ $temp = [];
+ foreach ( $rule as $r ) {
+ if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
+ // do not pollute the scope if the variable is
+ // already there. consider returning false here
+ // but we need a way to "return" variable from mixins
+ if ( !$ruleset->variable( $r->name ) ) {
+ $temp[] = $r;
+ }
+ } else {
+ $temp[] = $r;
+ }
+ }
+ $temp_count = count( $temp ) - 1;
+ array_splice( $ruleset->rules, $i, 1, $temp );
+ $rsRuleCnt += $temp_count;
+ $i += $temp_count;
+ $ruleset->resetCache();
+
+ } elseif ( $rule instanceof Less_Tree_RulesetCall ) {
+
+ $rule = $rule->compile( $env );
+ $rules = [];
+ foreach ( $rule->rules as $r ) {
+ if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
+ continue;
+ }
+ $rules[] = $r;
+ }
+
+ array_splice( $ruleset->rules, $i, 1, $rules );
+ $temp_count = count( $rules );
+ $rsRuleCnt += $temp_count - 1;
+ $i += $temp_count - 1;
+ $ruleset->resetCache();
+ }
+
+ }
+ }
+
+ /**
+ * Compile the selectors and create a new ruleset object for the compile() method
+ *
+ * @param Less_Environment $env
+ * @return Less_Tree_Ruleset
+ */
+ private function PrepareRuleset( $env ) {
+ // NOTE: Preserve distinction between null and empty array when compiling
+ // $this->selectors to $selectors
+ $thisSelectors = $this->selectors;
+ $selectors = null;
+ $hasOnePassingSelector = false;
+
+ if ( $thisSelectors ) {
+ Less_Tree_DefaultFunc::error( "it is currently only allowed in parametric mixin guards," );
+
+ $selectors = [];
+ foreach ( $thisSelectors as $s ) {
+ $selector = $s->compile( $env );
+ $selectors[] = $selector;
+ if ( $selector->evaldCondition ) {
+ $hasOnePassingSelector = true;
+ }
+ }
+
+ Less_Tree_DefaultFunc::reset();
+ } else {
+ $hasOnePassingSelector = true;
+ }
+
+ if ( $this->rules && $hasOnePassingSelector ) {
+ // Copy the array (no need for slice in PHP)
+ $rules = $this->rules;
+ } else {
+ $rules = [];
+ }
+
+ $ruleset = new Less_Tree_Ruleset( $selectors, $rules, $this->strictImports );
+
+ $ruleset->originalRuleset = $this->ruleset_id;
+ $ruleset->root = $this->root;
+ $ruleset->firstRoot = $this->firstRoot;
+ $ruleset->allowImports = $this->allowImports;
+
+ // push the current ruleset to the frames stack
+ $env->unshiftFrame( $ruleset );
+
+ // Evaluate imports
+ if ( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ) {
+ $ruleset->evalImports( $env );
+ }
+
+ return $ruleset;
+ }
+
+ function evalImports( $env ) {
+ $rules_len = count( $this->rules );
+ for ( $i = 0; $i < $rules_len; $i++ ) {
+ $rule = $this->rules[$i];
+
+ if ( $rule instanceof Less_Tree_Import ) {
+ $rules = $rule->compile( $env );
+ if ( is_array( $rules ) ) {
+ array_splice( $this->rules, $i, 1, $rules );
+ $temp_count = count( $rules ) - 1;
+ $i += $temp_count;
+ $rules_len += $temp_count;
+ } else {
+ array_splice( $this->rules, $i, 1, [ $rules ] );
+ }
+
+ $this->resetCache();
+ }
+ }
+ }
+
+ function makeImportant() {
+ $important_rules = [];
+ foreach ( $this->rules as $rule ) {
+ if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset || $rule instanceof Less_Tree_NameValue ) {
+ $important_rules[] = $rule->makeImportant();
+ } else {
+ $important_rules[] = $rule;
+ }
+ }
+
+ return new Less_Tree_Ruleset( $this->selectors, $important_rules, $this->strictImports );
+ }
+
+ public function matchArgs( $args, $env = null ) {
+ return !$args;
+ }
+
+ // lets you call a css selector with a guard
+ public function matchCondition( $args, $env ) {
+ $lastSelector = end( $this->selectors );
+
+ if ( !$lastSelector->evaldCondition ) {
+ return false;
+ }
+ if ( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ) {
+ return false;
+ }
+ return true;
+ }
+
+ function resetCache() {
+ $this->_rulesets = null;
+ $this->_variables = null;
+ $this->lookups = [];
+ }
+
+ public function variables() {
+ $this->_variables = [];
+ foreach ( $this->rules as $r ) {
+ if ( $r instanceof Less_Tree_Rule && $r->variable === true ) {
+ $this->_variables[$r->name] = $r;
+ }
+ }
+ }
+
+ /**
+ * @param string $name
+ * @return Less_Tree_Rule|null
+ */
+ public function variable( $name ) {
+ if ( $this->_variables === null ) {
+ $this->variables();
+ }
+ return $this->_variables[$name] ?? null;
+ }
+
+ public function find( $selector, $self = null ) {
+ $key = implode( ' ', $selector->_oelements );
+
+ if ( !isset( $this->lookups[$key] ) ) {
+
+ if ( !$self ) {
+ $self = $this->ruleset_id;
+ }
+
+ $this->lookups[$key] = [];
+
+ $first_oelement = $selector->_oelements[0];
+
+ foreach ( $this->rules as $rule ) {
+ if ( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ) {
+
+ if ( isset( $rule->first_oelements[$first_oelement] ) ) {
+
+ foreach ( $rule->selectors as $ruleSelector ) {
+ $match = $selector->match( $ruleSelector );
+ if ( $match ) {
+ if ( $selector->elements_len > $match ) {
+ $this->lookups[$key] = array_merge( $this->lookups[$key], $rule->find( new Less_Tree_Selector( array_slice( $selector->elements, $match ) ), $self ) );
+ } else {
+ $this->lookups[$key][] = $rule;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $this->lookups[$key];
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( !$this->root ) {
+ Less_Environment::$tabLevel++;
+ }
+
+ $tabRuleStr = $tabSetStr = '';
+ if ( !Less_Parser::$options['compress'] ) {
+ if ( Less_Environment::$tabLevel ) {
+ $tabRuleStr = "\n" . str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel );
+ $tabSetStr = "\n" . str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel - 1 );
+ } else {
+ $tabSetStr = $tabRuleStr = "\n";
+ }
+ }
+
+ $ruleNodes = [];
+ $rulesetNodes = [];
+ foreach ( $this->rules as $rule ) {
+
+ $class = get_class( $rule );
+ if (
+ ( $class === 'Less_Tree_Media' ) ||
+ ( $class === 'Less_Tree_Directive' ) ||
+ ( $this->root && $class === 'Less_Tree_Comment' ) ||
+ ( $rule instanceof Less_Tree_Ruleset && $rule->rules )
+ ) {
+ $rulesetNodes[] = $rule;
+ } else {
+ $ruleNodes[] = $rule;
+ }
+ }
+
+ // If this is the root node, we don't render
+ // a selector, or {}.
+ if ( !$this->root ) {
+ $paths_len = count( $this->paths );
+ for ( $i = 0; $i < $paths_len; $i++ ) {
+ $path = $this->paths[$i];
+ $firstSelector = true;
+
+ foreach ( $path as $p ) {
+ $p->genCSS( $output, $firstSelector );
+ $firstSelector = false;
+ }
+
+ if ( $i + 1 < $paths_len ) {
+ $output->add( ',' . $tabSetStr );
+ }
+ }
+
+ $output->add( ( Less_Parser::$options['compress'] ? '{' : " {" ) . $tabRuleStr );
+ }
+
+ // Compile rules and rulesets
+ $ruleNodes_len = count( $ruleNodes );
+ $rulesetNodes_len = count( $rulesetNodes );
+ for ( $i = 0; $i < $ruleNodes_len; $i++ ) {
+ $rule = $ruleNodes[$i];
+
+ // @page{ directive ends up with root elements inside it, a mix of rules and rulesets
+ // In this instance we do not know whether it is the last property
+ if ( $i + 1 === $ruleNodes_len && ( !$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ) {
+ Less_Environment::$lastRule = true;
+ }
+
+ $rule->genCSS( $output );
+
+ if ( !Less_Environment::$lastRule ) {
+ $output->add( $tabRuleStr );
+ } else {
+ Less_Environment::$lastRule = false;
+ }
+ }
+
+ if ( !$this->root ) {
+ $output->add( $tabSetStr . '}' );
+ Less_Environment::$tabLevel--;
+ }
+
+ $firstRuleset = true;
+ $space = ( $this->root ? $tabRuleStr : $tabSetStr );
+ for ( $i = 0; $i < $rulesetNodes_len; $i++ ) {
+
+ if ( $ruleNodes_len && $firstRuleset ) {
+ $output->add( $space );
+ } elseif ( !$firstRuleset ) {
+ $output->add( $space );
+ }
+ $firstRuleset = false;
+ $rulesetNodes[$i]->genCSS( $output );
+ }
+
+ if ( !Less_Parser::$options['compress'] && $this->firstRoot ) {
+ $output->add( "\n" );
+ }
+ }
+
+ function markReferenced() {
+ if ( !$this->selectors ) {
+ return;
+ }
+ foreach ( $this->selectors as $selector ) {
+ $selector->markReferenced();
+ }
+ }
+
+ /**
+ * @param Less_Tree_Selector[][] $context
+ * @param Less_Tree_Selector[]|null $selectors
+ * @return Less_Tree_Selector[][]
+ */
+ public function joinSelectors( $context, $selectors ) {
+ $paths = [];
+ if ( $selectors !== null ) {
+ foreach ( $selectors as $selector ) {
+ $this->joinSelector( $paths, $context, $selector );
+ }
+ }
+ return $paths;
+ }
+
+ public function joinSelector( array &$paths, array $context, Less_Tree_Selector $selector ) {
+ $newPaths = [];
+ $hadParentSelector = $this->replaceParentSelector( $newPaths, $context, $selector );
+
+ if ( !$hadParentSelector ) {
+ if ( $context ) {
+ $newPaths = [];
+ foreach ( $context as $path ) {
+ $newPaths[] = array_merge( $path, [ $selector ] );
+ }
+ } else {
+ $newPaths = [ [ $selector ] ];
+ }
+ }
+
+ foreach ( $newPaths as $newPath ) {
+ $paths[] = $newPath;
+ }
+ }
+
+ /**
+ * Replace all parent selectors inside $inSelector with $context.
+ *
+ * @param array &$paths Resulting selectors are appended to $paths.
+ * @param mixed $context
+ * @param Less_Tree_Selector $inSelector Inner selector from Less_Tree_Paren
+ * @return bool True if $inSelector contained at least one parent selector
+ */
+ private function replaceParentSelector( array &$paths, $context, Less_Tree_Selector $inSelector ) {
+ $hadParentSelector = false;
+
+ // The paths are [[Selector]]
+ // The first list is a list of comma separated selectors
+ // The inner list is a list of inheritance separated selectors
+ // e.g.
+ // .a, .b {
+ // .c {
+ // }
+ // }
+ // == [[.a] [.c]] [[.b] [.c]]
+ //
+
+ // the elements from the current selector so far
+ $currentElements = [];
+ // the current list of new selectors to add to the path.
+ // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
+ // by the parents
+ $newSelectors = [
+ []
+ ];
+
+ foreach ( $inSelector->elements as $el ) {
+ // non-parent reference elements just get added
+ if ( $el->value !== '&' ) {
+ $nestedSelector = $this->findNestedSelector( $el );
+ if ( $nestedSelector !== null ) {
+ $this->mergeElementsOnToSelectors( $currentElements, $newSelectors );
+
+ $nestedPaths = [];
+ $replacedNewSelectors = [];
+ $replaced = $this->replaceParentSelector( $nestedPaths, $context, $nestedSelector );
+ $hadParentSelector = $hadParentSelector || $replaced;
+ // $nestedPaths is populated by replaceParentSelector()
+ // $nestedPaths should have exactly one TODO, replaceParentSelector does not multiply selectors
+ foreach ( $nestedPaths as $nestedPath ) {
+ $replacementSelector = $this->createSelector( $nestedPath, $el );
+
+ // join selector path from $newSelectors with every selector path in $addPaths array.
+ // $el contains the element that is being replaced by $addPaths
+ //
+ // @see less-2.5.3.js#Ruleset-addAllReplacementsIntoPath
+ $addPaths = [ $replacementSelector ];
+ foreach ( $newSelectors as $newSelector ) {
+ $replacedNewSelectors[] = $this->addReplacementIntoPath( $newSelector, $addPaths, $el, $inSelector );
+ }
+ }
+ $newSelectors = $replacedNewSelectors;
+ $currentElements = [];
+ } else {
+ $currentElements[] = $el;
+ }
+ } else {
+ $hadParentSelector = true;
+
+ // the new list of selectors to add
+ $selectorsMultiplied = [];
+
+ // merge the current list of non parent selector elements
+ // on to the current list of selectors to add
+ $this->mergeElementsOnToSelectors( $currentElements, $newSelectors );
+
+ foreach ( $newSelectors as $sel ) {
+ // if we don't have any parent paths, the & might be in a mixin so that it can be used
+ // whether there are parents or not
+ if ( !$context ) {
+ // the combinator used on el should now be applied to the next element instead so that
+ // it is not lost
+ if ( $sel ) {
+ $sel[0]->elements[] = new Less_Tree_Element( $el->combinator, '', $el->index, $el->currentFileInfo );
+ }
+ $selectorsMultiplied[] = $sel;
+ } else {
+ // and the parent selectors
+ foreach ( $context as $parentSel ) {
+ // We need to put the current selectors
+ // then join the last selector's elements on to the parents selectors
+ $newSelectorPath = $this->addReplacementIntoPath( $sel, $parentSel, $el, $inSelector );
+ // add that to our new set of selectors
+ $selectorsMultiplied[] = $newSelectorPath;
+ }
+ }
+ }
+
+ // our new selectors has been multiplied, so reset the state
+ $newSelectors = $selectorsMultiplied;
+ $currentElements = [];
+ }
+ }
+
+ // if we have any elements left over (e.g. .a& .b == .b)
+ // add them on to all the current selectors
+ $this->mergeElementsOnToSelectors( $currentElements, $newSelectors );
+
+ foreach ( $newSelectors as &$sel ) {
+ $length = count( $sel );
+ if ( $length ) {
+ $paths[] = $sel;
+ $lastSelector = $sel[$length - 1];
+ $sel[$length - 1] = $lastSelector->createDerived( $lastSelector->elements, $inSelector->extendList );
+ }
+ }
+
+ return $hadParentSelector;
+ }
+
+ /**
+ * @param array $elementsToPak
+ * @param Less_Tree_Element $originalElement
+ * @return Less_Tree_Selector
+ */
+ private function createSelector( array $elementsToPak, $originalElement ) {
+ if ( !$elementsToPak ) {
+ // This is an invalid call. Kept to match less.js. Appears unreachable.
+ // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal
+ $containedElement = new Less_Tree_Paren( null );
+ } else {
+ $insideParent = [];
+ foreach ( $elementsToPak as $elToPak ) {
+ $insideParent[] = new Less_Tree_Element( null, $elToPak, $originalElement->index, $originalElement->currentFileInfo );
+ }
+ $containedElement = new Less_Tree_Paren( new Less_Tree_Selector( $insideParent ) );
+ }
+
+ $element = new Less_Tree_Element( null, $containedElement, $originalElement->index, $originalElement->currentFileInfo );
+ return new Less_Tree_Selector( [ $element ] );
+ }
+
+ /**
+ * @param Less_Tree_Element $element
+ * @return Less_Tree_Selector|null
+ */
+ private function findNestedSelector( $element ) {
+ $maybeParen = $element->value;
+ if ( !( $maybeParen instanceof Less_Tree_Paren ) ) {
+ return null;
+ }
+ $maybeSelector = $maybeParen->value;
+ if ( !( $maybeSelector instanceof Less_Tree_Selector ) ) {
+ return null;
+ }
+ return $maybeSelector;
+ }
+
+ /**
+ * joins selector path from $beginningPath with selector path in $addPath.
+ *
+ * $replacedElement contains the element that is being replaced by $addPath
+ *
+ * @param Less_Tree_Selector[] $beginningPath
+ * @param Less_Tree_Selector[] $addPath
+ * @param Less_Tree_Element $replacedElement
+ * @param Less_Tree_Selector $originalSelector
+ * @return Less_Tree_Selector[] Concatenated path
+ * @see less-2.5.3.js#Ruleset-addReplacementIntoPath
+ */
+ private function addReplacementIntoPath( array $beginningPath, array $addPath, $replacedElement, $originalSelector ) {
+ // our new selector path
+ $newSelectorPath = [];
+
+ // construct the joined selector - if `&` is the first thing this will be empty,
+ // if not newJoinedSelector will be the last set of elements in the selector
+ if ( $beginningPath ) {
+ // NOTE: less.js uses Array slice() to copy. In PHP, arrays are naturally copied by value.
+ $newSelectorPath = $beginningPath;
+ $lastSelector = array_pop( $newSelectorPath );
+ $newJoinedSelector = $originalSelector->createDerived( $lastSelector->elements );
+ } else {
+ $newJoinedSelector = $originalSelector->createDerived( [] );
+ }
+
+ if ( $addPath ) {
+ // if the & does not have a combinator that is "" or " " then
+ // and there is a combinator on the parent, then grab that.
+ // this also allows `+ a { & .b { .a & { ...`
+ $combinator = $replacedElement->combinator;
+ $parentEl = $addPath[0]->elements[0];
+ if ( $replacedElement->combinatorIsEmptyOrWhitespace && !$parentEl->combinatorIsEmptyOrWhitespace ) {
+ $combinator = $parentEl->combinator;
+ }
+ // join the elements so far with the first part of the parent
+ $newJoinedSelector->elements[] = new Less_Tree_Element( $combinator, $parentEl->value, $replacedElement->index, $replacedElement->currentFileInfo );
+ $newJoinedSelector->elements = array_merge(
+ $newJoinedSelector->elements,
+ array_slice( $addPath[0]->elements, 1 )
+ );
+ }
+
+ // now add the joined selector - but only if it is not empty
+ if ( $newJoinedSelector->elements ) {
+ $newSelectorPath[] = $newJoinedSelector;
+ }
+
+ // put together the parent selectors after the join (e.g. the rest of the parent)
+ if ( count( $addPath ) > 1 ) {
+ $newSelectorPath = array_merge( $newSelectorPath, array_slice( $addPath, 1 ) );
+ }
+ return $newSelectorPath;
+ }
+
+ function mergeElementsOnToSelectors( $elements, &$selectors ) {
+ if ( !$elements ) {
+ return;
+ }
+ if ( !$selectors ) {
+ $selectors[] = [ new Less_Tree_Selector( $elements ) ];
+ return;
+ }
+
+ foreach ( $selectors as &$sel ) {
+ // if the previous thing in sel is a parent this needs to join on to it
+ if ( $sel ) {
+ $last = count( $sel ) - 1;
+ $sel[$last] = $sel[$last]->createDerived( array_merge( $sel[$last]->elements, $elements ) );
+ } else {
+ $sel[] = new Less_Tree_Selector( $elements );
+ }
+ }
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/RulesetCall.php b/vendor/wikimedia/less.php/lib/Less/Tree/RulesetCall.php
new file mode 100644
index 0000000..9c162b8
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/RulesetCall.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_RulesetCall extends Less_Tree {
+
+ public $variable;
+ public $type = "RulesetCall";
+
+ /**
+ * @param string $variable
+ */
+ public function __construct( $variable ) {
+ $this->variable = $variable;
+ }
+
+ public function accept( $visitor ) {
+ }
+
+ public function compile( $env ) {
+ $variable = new Less_Tree_Variable( $this->variable );
+ $detachedRuleset = $variable->compile( $env );
+ '@phan-var Less_Tree_DetachedRuleset $detachedRuleset';
+ return $detachedRuleset->callEval( $env );
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Selector.php b/vendor/wikimedia/less.php/lib/Less/Tree/Selector.php
new file mode 100644
index 0000000..71182da
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Selector.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Selector extends Less_Tree {
+
+ public $elements;
+ public $condition;
+ public $extendList = [];
+ public $_css;
+ public $index;
+ public $evaldCondition = false;
+ public $type = 'Selector';
+ public $currentFileInfo = [];
+ public $isReferenced;
+ public $mediaEmpty;
+
+ public $elements_len = 0;
+
+ public $_oelements;
+ public $_oelements_assoc;
+ public $_oelements_len;
+ public $cacheable = true;
+
+ /**
+ * @param bool $isReferenced
+ */
+ public function __construct( $elements, $extendList = [], $condition = null, $index = null, $currentFileInfo = null, $isReferenced = null ) {
+ $this->elements = $elements;
+ $this->elements_len = count( $elements );
+ $this->extendList = $extendList;
+ $this->condition = $condition;
+ if ( $currentFileInfo ) {
+ $this->currentFileInfo = $currentFileInfo;
+ }
+ $this->isReferenced = $isReferenced;
+ if ( !$condition ) {
+ $this->evaldCondition = true;
+ }
+
+ $this->CacheElements();
+ }
+
+ public function accept( $visitor ) {
+ $this->elements = $visitor->visitArray( $this->elements );
+ $this->extendList = $visitor->visitArray( $this->extendList );
+ if ( $this->condition ) {
+ $this->condition = $visitor->visitObj( $this->condition );
+ }
+
+ if ( $visitor instanceof Less_Visitor_extendFinder ) {
+ $this->CacheElements();
+ }
+ }
+
+ public function createDerived( $elements, $extendList = null, $evaldCondition = null ) {
+ $newSelector = new Less_Tree_Selector(
+ $elements,
+ ( $extendList ?: $this->extendList ),
+ null,
+ $this->index,
+ $this->currentFileInfo,
+ $this->isReferenced
+ );
+ $newSelector->evaldCondition = $evaldCondition ?: $this->evaldCondition;
+ $newSelector->mediaEmpty = $this->mediaEmpty;
+ return $newSelector;
+ }
+
+ public function match( $other ) {
+ if ( !$other->_oelements || ( $this->elements_len < $other->_oelements_len ) ) {
+ return 0;
+ }
+
+ for ( $i = 0; $i < $other->_oelements_len; $i++ ) {
+ if ( $this->elements[$i]->value !== $other->_oelements[$i] ) {
+ return 0;
+ }
+ }
+
+ return $other->_oelements_len; // return number of matched elements
+ }
+
+ public function CacheElements() {
+ $this->_oelements = [];
+ $this->_oelements_assoc = [];
+
+ $css = '';
+
+ foreach ( $this->elements as $v ) {
+
+ $css .= $v->combinator;
+ if ( !$v->value_is_object ) {
+ $css .= $v->value;
+ continue;
+ }
+
+ if ( !property_exists( $v->value, 'value' ) || !is_string( $v->value->value ) ) {
+ $this->cacheable = false;
+ return;
+ }
+ $css .= $v->value->value;
+ }
+
+ $this->_oelements_len = preg_match_all( '/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches );
+ if ( $this->_oelements_len ) {
+ $this->_oelements = $matches[0];
+
+ if ( $this->_oelements[0] === '&' ) {
+ array_shift( $this->_oelements );
+ $this->_oelements_len--;
+ }
+
+ $this->_oelements_assoc = array_fill_keys( $this->_oelements, true );
+ }
+ }
+
+ public function isJustParentSelector() {
+ return !$this->mediaEmpty &&
+ count( $this->elements ) === 1 &&
+ $this->elements[0]->value === '&' &&
+ ( $this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === '' );
+ }
+
+ public function compile( $env ) {
+ $elements = [];
+ foreach ( $this->elements as $el ) {
+ $elements[] = $el->compile( $env );
+ }
+
+ $extendList = [];
+ foreach ( $this->extendList as $el ) {
+ $extendList[] = $el->compile( $el );
+ }
+
+ $evaldCondition = false;
+ if ( $this->condition ) {
+ $evaldCondition = $this->condition->compile( $env );
+ }
+
+ return $this->createDerived( $elements, $extendList, $evaldCondition );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output, $firstSelector = true ) {
+ if ( !$firstSelector && $this->elements[0]->combinator === "" ) {
+ $output->add( ' ', $this->currentFileInfo, $this->index );
+ }
+
+ foreach ( $this->elements as $element ) {
+ $element->genCSS( $output );
+ }
+ }
+
+ public function markReferenced() {
+ $this->isReferenced = true;
+ }
+
+ public function getIsReferenced() {
+ return !isset( $this->currentFileInfo['reference'] ) || !$this->currentFileInfo['reference'] || $this->isReferenced;
+ }
+
+ public function getIsOutput() {
+ return $this->evaldCondition;
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/UnicodeDescriptor.php b/vendor/wikimedia/less.php/lib/Less/Tree/UnicodeDescriptor.php
new file mode 100644
index 0000000..304d191
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/UnicodeDescriptor.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_UnicodeDescriptor extends Less_Tree {
+
+ public $value;
+ public $type = 'UnicodeDescriptor';
+
+ public function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->value );
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Unit.php b/vendor/wikimedia/less.php/lib/Less/Tree/Unit.php
new file mode 100644
index 0000000..2fd8927
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Unit.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Unit extends Less_Tree {
+
+ var $numerator = [];
+ var $denominator = [];
+ public $backupUnit;
+ public $type = 'Unit';
+
+ public function __construct( $numerator = [], $denominator = [], $backupUnit = null ) {
+ $this->numerator = $numerator;
+ $this->denominator = $denominator;
+ $this->backupUnit = $backupUnit;
+ }
+
+ public function __clone() {
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( $this->numerator ) {
+ $output->add( $this->numerator[0] );
+ } elseif ( $this->denominator ) {
+ $output->add( $this->denominator[0] );
+ } elseif ( !Less_Parser::$options['strictUnits'] && $this->backupUnit ) {
+ $output->add( $this->backupUnit );
+ return;
+ }
+ }
+
+ public function toString() {
+ $returnStr = implode( '*', $this->numerator );
+ foreach ( $this->denominator as $d ) {
+ $returnStr .= '/' . $d;
+ }
+ return $returnStr;
+ }
+
+ public function __toString() {
+ return $this->toString();
+ }
+
+ /**
+ * @param Less_Tree_Unit $other
+ */
+ public function compare( $other ) {
+ return $this->is( $other->toString() ) ? 0 : -1;
+ }
+
+ public function is( $unitString ) {
+ return $this->toString() === $unitString;
+ }
+
+ public function isLength() {
+ $css = $this->toCSS();
+ return (bool)preg_match( '/px|em|%|in|cm|mm|pc|pt|ex/', $css );
+ }
+
+ public function isAngle() {
+ return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] );
+ }
+
+ public function isEmpty() {
+ return !$this->numerator && !$this->denominator;
+ }
+
+ public function isSingular() {
+ return count( $this->numerator ) <= 1 && !$this->denominator;
+ }
+
+ public function usedUnits() {
+ $result = [];
+
+ foreach ( Less_Tree_UnitConversions::$groups as $groupName ) {
+ $group = Less_Tree_UnitConversions::${$groupName};
+
+ foreach ( $this->numerator as $atomicUnit ) {
+ if ( isset( $group[$atomicUnit] ) && !isset( $result[$groupName] ) ) {
+ $result[$groupName] = $atomicUnit;
+ }
+ }
+
+ foreach ( $this->denominator as $atomicUnit ) {
+ if ( isset( $group[$atomicUnit] ) && !isset( $result[$groupName] ) ) {
+ $result[$groupName] = $atomicUnit;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ public function cancel() {
+ $counter = [];
+ $backup = null;
+
+ foreach ( $this->numerator as $atomicUnit ) {
+ if ( !$backup ) {
+ $backup = $atomicUnit;
+ }
+ $counter[$atomicUnit] = ( $counter[$atomicUnit] ?? 0 ) + 1;
+ }
+
+ foreach ( $this->denominator as $atomicUnit ) {
+ if ( !$backup ) {
+ $backup = $atomicUnit;
+ }
+ $counter[$atomicUnit] = ( $counter[$atomicUnit] ?? 0 ) - 1;
+ }
+
+ $this->numerator = [];
+ $this->denominator = [];
+
+ foreach ( $counter as $atomicUnit => $count ) {
+ if ( $count > 0 ) {
+ for ( $i = 0; $i < $count; $i++ ) {
+ $this->numerator[] = $atomicUnit;
+ }
+ } elseif ( $count < 0 ) {
+ for ( $i = 0; $i < -$count; $i++ ) {
+ $this->denominator[] = $atomicUnit;
+ }
+ }
+ }
+
+ if ( !$this->numerator && !$this->denominator && $backup ) {
+ $this->backupUnit = $backup;
+ }
+
+ sort( $this->numerator );
+ sort( $this->denominator );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/UnitConversions.php b/vendor/wikimedia/less.php/lib/Less/Tree/UnitConversions.php
new file mode 100644
index 0000000..31efe1c
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/UnitConversions.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_UnitConversions {
+
+ public static $groups = [ 'length','duration','angle' ];
+
+ public static $length = [
+ 'm' => 1,
+ 'cm' => 0.01,
+ 'mm' => 0.001,
+ 'in' => 0.0254,
+ 'px' => 0.000264583, // 0.0254 / 96,
+ 'pt' => 0.000352778, // 0.0254 / 72,
+ 'pc' => 0.004233333, // 0.0254 / 72 * 12
+ ];
+
+ public static $duration = [
+ 's' => 1,
+ 'ms' => 0.001
+ ];
+
+ public static $angle = [
+ 'rad' => 0.1591549430919, // 1/(2*M_PI),
+ 'deg' => 0.002777778, // 1/360,
+ 'grad' => 0.0025, // 1/400,
+ 'turn' => 1
+ ];
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Url.php b/vendor/wikimedia/less.php/lib/Less/Tree/Url.php
new file mode 100644
index 0000000..6ae3518
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Url.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Url extends Less_Tree {
+
+ public $attrs;
+ public $value;
+ public $currentFileInfo;
+ public $isEvald;
+ public $type = 'Url';
+
+ /**
+ * @param Less_Tree_Variable|Less_Tree_Quoted|Less_Tree_Anonymous $value
+ * @param array|null $currentFileInfo
+ * @param bool|null $isEvald
+ */
+ public function __construct( $value, $currentFileInfo = null, $isEvald = null ) {
+ $this->value = $value;
+ $this->currentFileInfo = $currentFileInfo;
+ $this->isEvald = $isEvald;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( 'url(' );
+ $this->value->genCSS( $output );
+ $output->add( ')' );
+ }
+
+ /**
+ * @param Less_Environment $ctx
+ */
+ public function compile( $ctx ) {
+ $val = $this->value->compile( $ctx );
+
+ if ( !$this->isEvald ) {
+ // Add the base path if the URL is relative
+ if ( Less_Parser::$options['relativeUrls']
+ && $this->currentFileInfo
+ && is_string( $val->value )
+ && Less_Environment::isPathRelative( $val->value )
+ ) {
+ $rootpath = $this->currentFileInfo['uri_root'];
+ if ( !$val->quote ) {
+ $rootpath = preg_replace( '/[\(\)\'"\s]/', '\\$1', $rootpath );
+ }
+ $val->value = $rootpath . $val->value;
+ }
+
+ $val->value = Less_Environment::normalizePath( $val->value );
+ }
+
+ // Add cache buster if enabled
+ if ( Less_Parser::$options['urlArgs'] ) {
+ if ( !preg_match( '/^\s*data:/', $val->value ) ) {
+ $delimiter = strpos( $val->value, '?' ) === false ? '?' : '&';
+ $urlArgs = $delimiter . Less_Parser::$options['urlArgs'];
+ $hash_pos = strpos( $val->value, '#' );
+ if ( $hash_pos !== false ) {
+ $val->value = substr_replace( $val->value, $urlArgs, $hash_pos, 0 );
+ } else {
+ $val->value .= $urlArgs;
+ }
+ }
+ }
+
+ return new Less_Tree_URL( $val, $this->currentFileInfo, true );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Value.php b/vendor/wikimedia/less.php/lib/Less/Tree/Value.php
new file mode 100644
index 0000000..bf45caf
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Value.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Value extends Less_Tree {
+
+ public $type = 'Value';
+ public $value;
+
+ /**
+ * @param array<Less_Tree> $value
+ */
+ public function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitArray( $this->value );
+ }
+
+ public function compile( $env ) {
+ $ret = [];
+ $i = 0;
+ foreach ( $this->value as $i => $v ) {
+ $ret[] = $v->compile( $env );
+ }
+ if ( $i > 0 ) {
+ return new Less_Tree_Value( $ret );
+ }
+ return $ret[0];
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ function genCSS( $output ) {
+ $len = count( $this->value );
+ for ( $i = 0; $i < $len; $i++ ) {
+ $this->value[$i]->genCSS( $output );
+ if ( $i + 1 < $len ) {
+ $output->add( Less_Environment::$_outputMap[','] );
+ }
+ }
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Tree/Variable.php b/vendor/wikimedia/less.php/lib/Less/Tree/Variable.php
new file mode 100644
index 0000000..dcb1823
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Tree/Variable.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @private
+ */
+class Less_Tree_Variable extends Less_Tree {
+
+ public $name;
+ public $index;
+ public $currentFileInfo;
+ public $evaluating = false;
+ public $type = 'Variable';
+
+ /**
+ * @param string $name
+ */
+ public function __construct( $name, $index = null, $currentFileInfo = null ) {
+ $this->name = $name;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ /**
+ * @param Less_Environment $env
+ * @return Less_Tree
+ * @see less-2.5.3.js#Ruleset.prototype.eval
+ */
+ public function compile( $env ) {
+ if ( $this->name[1] === '@' ) {
+ $v = new Less_Tree_Variable( substr( $this->name, 1 ), $this->index + 1, $this->currentFileInfo );
+ // While some Less_Tree nodes have no 'value', we know these can't ocurr after a variable
+ // assignment (would have been a ParseError).
+ // TODO: Solve better (https://phabricator.wikimedia.org/T327082).
+ // @phan-suppress-next-line PhanUndeclaredProperty
+ $name = '@' . $v->compile( $env )->value;
+ } else {
+ $name = $this->name;
+ }
+
+ if ( $this->evaluating ) {
+ throw new Less_Exception_Compiler( "Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo );
+ }
+
+ $this->evaluating = true;
+
+ foreach ( $env->frames as $frame ) {
+ if ( $v = $frame->variable( $name ) ) {
+ $r = $v->value->compile( $env );
+ $this->evaluating = false;
+ return $r;
+ }
+ }
+
+ throw new Less_Exception_Compiler( "variable " . $name . " is undefined in file " . $this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Version.php b/vendor/wikimedia/less.php/lib/Less/Version.php
new file mode 100644
index 0000000..1c96e8e
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Version.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * Release numbers
+ */
+class Less_Version {
+
+ public const version = '3.2.1'; // The current build number of less.php
+ public const less_version = '2.5.3'; // The less.js version that this build should be compatible with
+ public const cache_version = '253'; // The parser cache version
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Visitor.php b/vendor/wikimedia/less.php/lib/Less/Visitor.php
new file mode 100644
index 0000000..582f323
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Visitor.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @private
+ */
+class Less_Visitor {
+
+ protected $methods = [];
+ protected $_visitFnCache = [];
+
+ public function __construct() {
+ $this->_visitFnCache = get_class_methods( get_class( $this ) );
+ $this->_visitFnCache = array_flip( $this->_visitFnCache );
+ }
+
+ public function visitObj( $node ) {
+ $funcName = 'visit' . $node->type;
+ if ( isset( $this->_visitFnCache[$funcName] ) ) {
+
+ $visitDeeper = true;
+ $this->$funcName( $node, $visitDeeper );
+
+ if ( $visitDeeper ) {
+ $node->accept( $this );
+ }
+
+ $funcName .= "Out";
+ if ( isset( $this->_visitFnCache[$funcName] ) ) {
+ $this->$funcName( $node );
+ }
+
+ } else {
+ $node->accept( $this );
+ }
+
+ return $node;
+ }
+
+ public function visitArray( $nodes ) {
+ foreach ( $nodes as $node ) {
+ $this->visitObj( $node );
+ }
+ return $nodes;
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Visitor/extendFinder.php b/vendor/wikimedia/less.php/lib/Less/Visitor/extendFinder.php
new file mode 100644
index 0000000..8b3238d
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Visitor/extendFinder.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * @private
+ */
+class Less_Visitor_extendFinder extends Less_Visitor {
+
+ public $contexts = [];
+ public $allExtendsStack;
+ public $foundExtends;
+
+ public function __construct() {
+ $this->contexts = [];
+ $this->allExtendsStack = [ [] ];
+ parent::__construct();
+ }
+
+ /**
+ * @param Less_Tree_Ruleset $root
+ */
+ public function run( $root ) {
+ $root = $this->visitObj( $root );
+ $root->allExtends =& $this->allExtendsStack[0];
+ return $root;
+ }
+
+ public function visitRule( $ruleNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ public function visitRuleset( $rulesetNode ) {
+ if ( $rulesetNode->root ) {
+ return;
+ }
+
+ $allSelectorsExtendList = [];
+
+ // get &:extend(.a); rules which apply to all selectors in this ruleset
+ if ( $rulesetNode->rules ) {
+ foreach ( $rulesetNode->rules as $rule ) {
+ if ( $rule instanceof Less_Tree_Extend ) {
+ $allSelectorsExtendList[] = $rule;
+ $rulesetNode->extendOnEveryPath = true;
+ }
+ }
+ }
+
+ // now find every selector and apply the extends that apply to all extends
+ // and the ones which apply to an individual extend
+ foreach ( $rulesetNode->paths as $selectorPath ) {
+ $selector = end( $selectorPath ); // $selectorPath[ count($selectorPath)-1];
+
+ $j = 0;
+ foreach ( $selector->extendList as $extend ) {
+ $this->allExtendsStackPush( $rulesetNode, $selectorPath, $extend, $j );
+ }
+ foreach ( $allSelectorsExtendList as $extend ) {
+ $this->allExtendsStackPush( $rulesetNode, $selectorPath, $extend, $j );
+ }
+ }
+
+ $this->contexts[] = $rulesetNode->selectors;
+ }
+
+ public function allExtendsStackPush( $rulesetNode, $selectorPath, $extend, &$j ) {
+ $this->foundExtends = true;
+ $extend = clone $extend;
+ $extend->findSelfSelectors( $selectorPath );
+ $extend->ruleset = $rulesetNode;
+ if ( $j === 0 ) {
+ $extend->firstExtendOnThisSelectorPath = true;
+ }
+
+ $end_key = count( $this->allExtendsStack ) - 1;
+ $this->allExtendsStack[$end_key][] = $extend;
+ $j++;
+ }
+
+ public function visitRulesetOut( $rulesetNode ) {
+ if ( !is_object( $rulesetNode ) || !$rulesetNode->root ) {
+ array_pop( $this->contexts );
+ }
+ }
+
+ public function visitMedia( $mediaNode ) {
+ $mediaNode->allExtends = [];
+ $this->allExtendsStack[] =& $mediaNode->allExtends;
+ }
+
+ public function visitMediaOut() {
+ array_pop( $this->allExtendsStack );
+ }
+
+ public function visitDirective( $directiveNode ) {
+ $directiveNode->allExtends = [];
+ $this->allExtendsStack[] =& $directiveNode->allExtends;
+ }
+
+ public function visitDirectiveOut() {
+ array_pop( $this->allExtendsStack );
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Visitor/import.php b/vendor/wikimedia/less.php/lib/Less/Visitor/import.php
new file mode 100644
index 0000000..7af96eb
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Visitor/import.php
@@ -0,0 +1,137 @@
+<?php
+
+/*
+class Less_Visitor_import extends Less_VisitorReplacing{
+
+ public $_visitor;
+ public $_importer;
+ public $importCount;
+
+ function __construct( $evalEnv ){
+ $this->env = $evalEnv;
+ $this->importCount = 0;
+ parent::__construct();
+ }
+
+
+ function run( $root ){
+ $root = $this->visitObj($root);
+ $this->isFinished = true;
+
+ //if( $this->importCount === 0) {
+ // $this->_finish();
+ //}
+ }
+
+ function visitImport($importNode, &$visitDeeper ){
+ $importVisitor = $this;
+ $inlineCSS = $importNode->options['inline'];
+
+ if( !$importNode->css || $inlineCSS ){
+ $evaldImportNode = $importNode->compileForImport($this->env);
+
+ if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){
+ $importNode = $evaldImportNode;
+ $this->importCount++;
+ $env = clone $this->env;
+
+ if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){
+ $env->importMultiple = true;
+ }
+
+ //get path & uri
+ $path_and_uri = null;
+ if( is_callable(Less_Parser::$options['import_callback']) ){
+ $path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode);
+ }
+
+ if( !$path_and_uri ){
+ $path_and_uri = $importNode->PathAndUri();
+ }
+
+ if( $path_and_uri ){
+ list($full_path, $uri) = $path_and_uri;
+ }else{
+ $full_path = $uri = $importNode->getPath();
+ }
+
+
+ //import once
+ if( $importNode->skip( $full_path, $env) ){
+ return array();
+ }
+
+ if( $importNode->options['inline'] ){
+ //todo needs to reference css file not import
+ //$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true );
+
+ Less_Parser::AddParsedFile($full_path);
+ $contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
+
+ if( $importNode->features ){
+ return new Less_Tree_Media( array($contents), $importNode->features->value );
+ }
+
+ return array( $contents );
+ }
+
+
+ // css ?
+ if( $importNode->css ){
+ $features = ( $importNode->features ? $importNode->features->compile($env) : null );
+ return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index);
+ }
+
+ return $importNode->ParseImport( $full_path, $uri, $env );
+ }
+
+ }
+
+ $visitDeeper = false;
+ return $importNode;
+ }
+
+
+ function visitRule( $ruleNode, &$visitDeeper ){
+ $visitDeeper = false;
+ return $ruleNode;
+ }
+
+ function visitDirective($directiveNode, $visitArgs){
+ array_unshift($this->env->frames,$directiveNode);
+ return $directiveNode;
+ }
+
+ function visitDirectiveOut($directiveNode) {
+ array_shift($this->env->frames);
+ }
+
+ function visitMixinDefinition($mixinDefinitionNode, $visitArgs) {
+ array_unshift($this->env->frames,$mixinDefinitionNode);
+ return $mixinDefinitionNode;
+ }
+
+ function visitMixinDefinitionOut($mixinDefinitionNode) {
+ array_shift($this->env->frames);
+ }
+
+ function visitRuleset($rulesetNode, $visitArgs) {
+ array_unshift($this->env->frames,$rulesetNode);
+ return $rulesetNode;
+ }
+
+ function visitRulesetOut($rulesetNode) {
+ array_shift($this->env->frames);
+ }
+
+ function visitMedia($mediaNode, $visitArgs) {
+ array_unshift($this->env->frames, $mediaNode->ruleset);
+ return $mediaNode;
+ }
+
+ function visitMediaOut($mediaNode) {
+ array_shift($this->env->frames);
+ }
+
+}
+*/
diff --git a/vendor/wikimedia/less.php/lib/Less/Visitor/joinSelector.php b/vendor/wikimedia/less.php/lib/Less/Visitor/joinSelector.php
new file mode 100644
index 0000000..a106830
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Visitor/joinSelector.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * @private
+ */
+class Less_Visitor_joinSelector extends Less_Visitor {
+
+ public $contexts = [ [] ];
+
+ /**
+ * @param Less_Tree_Ruleset $root
+ */
+ public function run( $root ) {
+ return $this->visitObj( $root );
+ }
+
+ public function visitRule( $ruleNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ public function visitRuleset( $rulesetNode ) {
+ $context = end( $this->contexts );
+ $paths = [];
+
+ if ( !$rulesetNode->root ) {
+ $selectors = $rulesetNode->selectors;
+ if ( $selectors !== null ) {
+ $filtered = [];
+ foreach ( $selectors as $selector ) {
+ if ( $selector->getIsOutput() ) {
+ $filtered[] = $selector;
+ }
+ }
+ $selectors = $rulesetNode->selectors = $filtered ?: null;
+ if ( $selectors ) {
+ $paths = $rulesetNode->joinSelectors( $context, $selectors );
+ }
+ }
+
+ if ( $selectors === null ) {
+ $rulesetNode->rules = null;
+ }
+
+ $rulesetNode->paths = $paths;
+ }
+
+ // NOTE: Assigned here instead of at the start like less.js,
+ // because PHP arrays aren't by-ref
+ $this->contexts[] = $paths;
+ }
+
+ public function visitRulesetOut() {
+ array_pop( $this->contexts );
+ }
+
+ public function visitMedia( $mediaNode ) {
+ $context = end( $this->contexts );
+
+ if ( count( $context ) === 0 || ( is_object( $context[0] ) && $context[0]->multiMedia ) ) {
+ $mediaNode->rules[0]->root = true;
+ }
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Visitor/processExtends.php b/vendor/wikimedia/less.php/lib/Less/Visitor/processExtends.php
new file mode 100644
index 0000000..9491f3c
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Visitor/processExtends.php
@@ -0,0 +1,447 @@
+<?php
+/**
+ * @private
+ */
+class Less_Visitor_processExtends extends Less_Visitor {
+
+ public $allExtendsStack;
+
+ /**
+ * @param Less_Tree_Ruleset $root
+ */
+ public function run( $root ) {
+ $extendFinder = new Less_Visitor_extendFinder();
+ $extendFinder->run( $root );
+ if ( !$extendFinder->foundExtends ) {
+ return $root;
+ }
+
+ $root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends );
+
+ $this->allExtendsStack = [];
+ $this->allExtendsStack[] = &$root->allExtends;
+
+ return $this->visitObj( $root );
+ }
+
+ private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0 ) {
+ //
+ // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
+ // the selector we would do normally, but we are also adding an extend with the same target selector
+ // this means this new extend can then go and alter other extends
+ //
+ // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
+ // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
+ // we look at each selector at a time, as is done in visitRuleset
+
+ $extendsToAdd = [];
+
+ // loop through comparing every extend with every target extend.
+ // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
+ // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
+ // and the second is the target.
+ // the separation into two lists allows us to process a subset of chains with a bigger set, as is the
+ // case when processing media queries
+ for ( $extendIndex = 0, $extendsList_len = count( $extendsList ); $extendIndex < $extendsList_len; $extendIndex++ ) {
+ for ( $targetExtendIndex = 0; $targetExtendIndex < count( $extendsListTarget ); $targetExtendIndex++ ) {
+
+ $extend = $extendsList[$extendIndex];
+ $targetExtend = $extendsListTarget[$targetExtendIndex];
+
+ // Optimisation: Explicit reference, <https://github.com/wikimedia/less.php/pull/14>
+ if ( \array_key_exists( $targetExtend->object_id, $extend->parent_ids ) ) {
+ // ignore circular references
+ continue;
+ }
+
+ // find a match in the target extends self selector (the bit before :extend)
+ $selectorPath = [ $targetExtend->selfSelectors[0] ];
+ $matches = $this->findMatch( $extend, $selectorPath );
+
+ if ( $matches ) {
+
+ // we found a match, so for each self selector..
+ foreach ( $extend->selfSelectors as $selfSelector ) {
+
+ // process the extend as usual
+ $newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector );
+
+ // but now we create a new extend from it
+ $newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0 );
+ $newExtend->selfSelectors = $newSelector;
+
+ // add the extend onto the list of extends for that selector
+ end( $newSelector )->extendList = [ $newExtend ];
+ // $newSelector[ count($newSelector)-1]->extendList = array($newExtend);
+
+ // record that we need to add it.
+ $extendsToAdd[] = $newExtend;
+ $newExtend->ruleset = $targetExtend->ruleset;
+
+ // remember its parents for circular references
+ $newExtend->parent_ids = array_merge( $newExtend->parent_ids, $targetExtend->parent_ids, $extend->parent_ids );
+
+ // only process the selector once.. if we have :extend(.a,.b) then multiple
+ // extends will look at the same selector path, so when extending
+ // we know that any others will be duplicates in terms of what is added to the css
+ if ( $targetExtend->firstExtendOnThisSelectorPath ) {
+ $newExtend->firstExtendOnThisSelectorPath = true;
+ $targetExtend->ruleset->paths[] = $newSelector;
+ }
+ }
+ }
+ }
+ }
+
+ if ( $extendsToAdd ) {
+ // try to detect circular references to stop a stack overflow.
+ // may no longer be needed. $this->extendChainCount++;
+ if ( $iterationCount > 100 ) {
+
+ try{
+ $selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS();
+ $selectorTwo = $extendsToAdd[0]->selector->toCSS();
+ }catch ( Exception $e ) {
+ $selectorOne = "{unable to calculate}";
+ $selectorTwo = "{unable to calculate}";
+ }
+
+ throw new Less_Exception_Parser( "extend circular reference detected. One of the circular extends is currently:" . $selectorOne . ":extend(" . $selectorTwo . ")" );
+ }
+
+ // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
+ $extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount + 1 );
+ }
+
+ return array_merge( $extendsList, $extendsToAdd );
+ }
+
+ protected function visitRule( $ruleNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ protected function visitSelector( $selectorNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ protected function visitRuleset( $rulesetNode ) {
+ if ( $rulesetNode->root ) {
+ return;
+ }
+
+ $allExtends = end( $this->allExtendsStack );
+ $paths_len = count( $rulesetNode->paths );
+
+ // look at each selector path in the ruleset, find any extend matches and then copy, find and replace
+ foreach ( $allExtends as $allExtend ) {
+ for ( $pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ) {
+
+ // extending extends happens initially, before the main pass
+ if ( isset( $rulesetNode->extendOnEveryPath ) && $rulesetNode->extendOnEveryPath ) {
+ continue;
+ }
+
+ $selectorPath = $rulesetNode->paths[$pathIndex];
+
+ if ( end( $selectorPath )->extendList ) {
+ continue;
+ }
+
+ $this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath );
+
+ }
+ }
+ }
+
+ private function ExtendMatch( $rulesetNode, $extend, $selectorPath ) {
+ $matches = $this->findMatch( $extend, $selectorPath );
+
+ if ( $matches ) {
+ foreach ( $extend->selfSelectors as $selfSelector ) {
+ $rulesetNode->paths[] = $this->extendSelector( $matches, $selectorPath, $selfSelector );
+ }
+ }
+ }
+
+ /**
+ * @param Less_Tree_Extend $extend
+ * @param Less_Tree_Selector[] $haystackSelectorPath
+ * @return false|array<array{index:int,initialCombinator:string}>
+ */
+ private function findMatch( $extend, $haystackSelectorPath ) {
+ if ( !$this->HasMatches( $extend, $haystackSelectorPath ) ) {
+ return false;
+ }
+
+ //
+ // look through the haystack selector path to try and find the needle - extend.selector
+ // returns an array of selector matches that can then be replaced
+ //
+ $needleElements = $extend->selector->elements;
+ $potentialMatches = [];
+ $potentialMatches_len = 0;
+ $potentialMatch = null;
+ $matches = [];
+
+ // loop through the haystack elements
+ $haystack_path_len = count( $haystackSelectorPath );
+ for ( $haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ) {
+ $hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex];
+
+ $haystack_elements_len = count( $hackstackSelector->elements );
+ for ( $hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ) {
+
+ $haystackElement = $hackstackSelector->elements[$hackstackElementIndex];
+
+ // if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
+ if ( $extend->allowBefore || ( $haystackSelectorIndex === 0 && $hackstackElementIndex === 0 ) ) {
+ $potentialMatches[] = [ 'pathIndex' => $haystackSelectorIndex, 'index' => $hackstackElementIndex, 'matched' => 0, 'initialCombinator' => $haystackElement->combinator ];
+ $potentialMatches_len++;
+ }
+
+ for ( $i = 0; $i < $potentialMatches_len; $i++ ) {
+
+ $potentialMatch = &$potentialMatches[$i];
+ $potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex );
+
+ // if we are still valid and have finished, test whether we have elements after and whether these are allowed
+ if ( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ) {
+ $potentialMatch['finished'] = true;
+
+ if ( !$extend->allowAfter && ( $hackstackElementIndex + 1 < $haystack_elements_len || $haystackSelectorIndex + 1 < $haystack_path_len ) ) {
+ $potentialMatch = null;
+ }
+ }
+
+ // if null we remove, if not, we are still valid, so either push as a valid match or continue
+ if ( $potentialMatch ) {
+ if ( $potentialMatch['finished'] ) {
+ $potentialMatch['length'] = $extend->selector->elements_len;
+ $potentialMatch['endPathIndex'] = $haystackSelectorIndex;
+ $potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match
+ $potentialMatches = []; // we don't allow matches to overlap, so start matching again
+ $potentialMatches_len = 0;
+ $matches[] = $potentialMatch;
+ }
+ continue;
+ }
+
+ array_splice( $potentialMatches, $i, 1 );
+ $potentialMatches_len--;
+ $i--;
+ }
+ }
+ }
+
+ return $matches;
+ }
+
+ // Before going through all the nested loops, lets check to see if a match is possible
+ // Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s
+ private function HasMatches( $extend, $haystackSelectorPath ) {
+ if ( !$extend->selector->cacheable ) {
+ return true;
+ }
+
+ $first_el = $extend->selector->_oelements[0];
+
+ foreach ( $haystackSelectorPath as $hackstackSelector ) {
+ if ( !$hackstackSelector->cacheable ) {
+ return true;
+ }
+
+ // Optimisation: Explicit reference, <https://github.com/wikimedia/less.php/pull/14>
+ if ( \array_key_exists( $first_el, $hackstackSelector->_oelements_assoc ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param int $hackstackElementIndex
+ */
+ private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ) {
+ if ( $potentialMatch['matched'] > 0 ) {
+
+ // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
+ // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
+ // what the resulting combinator will be
+ $targetCombinator = $haystackElement->combinator;
+ if ( $targetCombinator === '' && $hackstackElementIndex === 0 ) {
+ $targetCombinator = ' ';
+ }
+
+ if ( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ) {
+ return null;
+ }
+ }
+
+ // if we don't match, null our match to indicate failure
+ if ( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value ) ) {
+ return null;
+ }
+
+ $potentialMatch['finished'] = false;
+ $potentialMatch['matched']++;
+
+ return $potentialMatch;
+ }
+
+ /**
+ * @param string|Less_Tree_Attribute|Less_Tree_Dimension|Less_Tree_Keyword $elementValue1
+ * @param string|Less_Tree_Attribute|Less_Tree_Dimension|Less_Tree_Keyword $elementValue2
+ * @return bool
+ */
+ private function isElementValuesEqual( $elementValue1, $elementValue2 ) {
+ if ( $elementValue1 === $elementValue2 ) {
+ return true;
+ }
+
+ if ( is_string( $elementValue1 ) || is_string( $elementValue2 ) ) {
+ return false;
+ }
+
+ if ( $elementValue1 instanceof Less_Tree_Attribute ) {
+ return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 );
+ }
+
+ $elementValue1 = $elementValue1->value;
+ if ( $elementValue1 instanceof Less_Tree_Selector ) {
+ return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 );
+ }
+
+ return false;
+ }
+
+ /**
+ * @param Less_Tree_Selector $elementValue1
+ */
+ private function isSelectorValuesEqual( $elementValue1, $elementValue2 ) {
+ $elementValue2 = $elementValue2->value;
+ if ( !( $elementValue2 instanceof Less_Tree_Selector ) || $elementValue1->elements_len !== $elementValue2->elements_len ) {
+ return false;
+ }
+
+ for ( $i = 0; $i < $elementValue1->elements_len; $i++ ) {
+
+ if ( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ) {
+ if ( $i !== 0 || ( $elementValue1->elements[$i]->combinator || ' ' ) !== ( $elementValue2->elements[$i]->combinator || ' ' ) ) {
+ return false;
+ }
+ }
+
+ if ( !$this->isElementValuesEqual( $elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param Less_Tree_Attribute $elementValue1
+ */
+ private function isAttributeValuesEqual( $elementValue1, $elementValue2 ) {
+ if ( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ) {
+ return false;
+ }
+
+ if ( !$elementValue1->value || !$elementValue2->value ) {
+ if ( $elementValue1->value || $elementValue2->value ) {
+ return false;
+ }
+ return true;
+ }
+
+ $elementValue1 = ( $elementValue1->value->value ?: $elementValue1->value );
+ $elementValue2 = ( $elementValue2->value->value ?: $elementValue2->value );
+
+ return $elementValue1 === $elementValue2;
+ }
+
+ private function extendSelector( $matches, $selectorPath, $replacementSelector ) {
+ // for a set of matches, replace each match with the replacement selector
+
+ $currentSelectorPathIndex = 0;
+ $currentSelectorPathElementIndex = 0;
+ $path = [];
+ $selectorPath_len = count( $selectorPath );
+
+ for ( $matchIndex = 0, $matches_len = count( $matches ); $matchIndex < $matches_len; $matchIndex++ ) {
+
+ $match = $matches[$matchIndex];
+ $selector = $selectorPath[ $match['pathIndex'] ];
+
+ $firstElement = new Less_Tree_Element(
+ $match['initialCombinator'],
+ $replacementSelector->elements[0]->value,
+ $replacementSelector->elements[0]->index,
+ $replacementSelector->elements[0]->currentFileInfo
+ );
+
+ if ( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ) {
+ $last_path = end( $path );
+ $last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) );
+ $currentSelectorPathElementIndex = 0;
+ $currentSelectorPathIndex++;
+ }
+
+ $newElements = array_merge(
+ array_slice( $selector->elements, $currentSelectorPathElementIndex, ( $match['index'] - $currentSelectorPathElementIndex ) ), // last parameter of array_slice is different than the last parameter of javascript's slice
+ [ $firstElement ],
+ array_slice( $replacementSelector->elements, 1 )
+ );
+
+ if ( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ) {
+ $last_key = count( $path ) - 1;
+ $path[$last_key]->elements = array_merge( $path[$last_key]->elements, $newElements );
+ } else {
+ $path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] ) );
+ $path[] = new Less_Tree_Selector( $newElements );
+ }
+
+ $currentSelectorPathIndex = $match['endPathIndex'];
+ $currentSelectorPathElementIndex = $match['endPathElementIndex'];
+ if ( $currentSelectorPathElementIndex >= count( $selectorPath[$currentSelectorPathIndex]->elements ) ) {
+ $currentSelectorPathElementIndex = 0;
+ $currentSelectorPathIndex++;
+ }
+ }
+
+ if ( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ) {
+ $last_path = end( $path );
+ $last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) );
+ $currentSelectorPathIndex++;
+ }
+
+ $slice_len = $selectorPath_len - $currentSelectorPathIndex;
+ $path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $slice_len ) );
+
+ return $path;
+ }
+
+ protected function visitMedia( $mediaNode ) {
+ $newAllExtends = array_merge( $mediaNode->allExtends, end( $this->allExtendsStack ) );
+ $this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $mediaNode->allExtends );
+ }
+
+ protected function visitMediaOut() {
+ array_pop( $this->allExtendsStack );
+ }
+
+ protected function visitDirective( $directiveNode ) {
+ $newAllExtends = array_merge( $directiveNode->allExtends, end( $this->allExtendsStack ) );
+ $this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $directiveNode->allExtends );
+ }
+
+ protected function visitDirectiveOut() {
+ array_pop( $this->allExtendsStack );
+ }
+
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/Visitor/toCSS.php b/vendor/wikimedia/less.php/lib/Less/Visitor/toCSS.php
new file mode 100644
index 0000000..6c476d2
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/Visitor/toCSS.php
@@ -0,0 +1,275 @@
+<?php
+/**
+ * @private
+ */
+class Less_Visitor_toCSS extends Less_VisitorReplacing {
+
+ private $charset;
+
+ public function __construct() {
+ parent::__construct();
+ }
+
+ /**
+ * @param Less_Tree_Ruleset $root
+ */
+ public function run( $root ) {
+ return $this->visitObj( $root );
+ }
+
+ public function visitRule( $ruleNode ) {
+ if ( $ruleNode->variable ) {
+ return [];
+ }
+ return $ruleNode;
+ }
+
+ public function visitMixinDefinition( $mixinNode ) {
+ // mixin definitions do not get eval'd - this means they keep state
+ // so we have to clear that state here so it isn't used if toCSS is called twice
+ $mixinNode->frames = [];
+ return [];
+ }
+
+ public function visitExtend() {
+ return [];
+ }
+
+ public function visitComment( $commentNode ) {
+ if ( $commentNode->isSilent() ) {
+ return [];
+ }
+ return $commentNode;
+ }
+
+ public function visitMedia( $mediaNode, &$visitDeeper ) {
+ $mediaNode->accept( $this );
+ $visitDeeper = false;
+
+ if ( !$mediaNode->rules ) {
+ return [];
+ }
+ return $mediaNode;
+ }
+
+ public function visitDirective( $directiveNode ) {
+ if ( isset( $directiveNode->currentFileInfo['reference'] ) && ( !property_exists( $directiveNode, 'isReferenced' ) || !$directiveNode->isReferenced ) ) {
+ return [];
+ }
+ if ( $directiveNode->name === '@charset' ) {
+ // Only output the debug info together with subsequent @charset definitions
+ // a comment (or @media statement) before the actual @charset directive would
+ // be considered illegal css as it has to be on the first line
+ if ( isset( $this->charset ) && $this->charset ) {
+
+ // if( $directiveNode->debugInfo ){
+ // $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");
+ // $comment->debugInfo = $directiveNode->debugInfo;
+ // return $this->visit($comment);
+ //}
+
+ return [];
+ }
+ $this->charset = true;
+ }
+ return $directiveNode;
+ }
+
+ public function checkPropertiesInRoot( $rulesetNode ) {
+ if ( !$rulesetNode->firstRoot ) {
+ return;
+ }
+
+ foreach ( $rulesetNode->rules as $ruleNode ) {
+ if ( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ) {
+ $msg = "properties must be inside selector blocks, they cannot be in the root. Index " . $ruleNode->index . ( $ruleNode->currentFileInfo ? ( ' Filename: ' . $ruleNode->currentFileInfo['filename'] ) : null );
+ throw new Less_Exception_Compiler( $msg );
+ }
+ }
+ }
+
+ public function visitRuleset( $rulesetNode, &$visitDeeper ) {
+ $visitDeeper = false;
+
+ $this->checkPropertiesInRoot( $rulesetNode );
+
+ if ( $rulesetNode->root ) {
+ return $this->visitRulesetRoot( $rulesetNode );
+ }
+
+ $rulesets = [];
+ $rulesetNode->paths = $this->visitRulesetPaths( $rulesetNode );
+
+ // Compile rules and rulesets
+ $nodeRuleCnt = $rulesetNode->rules ? count( $rulesetNode->rules ) : 0;
+ for ( $i = 0; $i < $nodeRuleCnt; ) {
+ $rule = $rulesetNode->rules[$i];
+
+ if ( property_exists( $rule, 'rules' ) ) {
+ // visit because we are moving them out from being a child
+ $rulesets[] = $this->visitObj( $rule );
+ array_splice( $rulesetNode->rules, $i, 1 );
+ $nodeRuleCnt--;
+ continue;
+ }
+ $i++;
+ }
+
+ // accept the visitor to remove rules and refactor itself
+ // then we can decide now whether we want it or not
+ if ( $nodeRuleCnt > 0 ) {
+ $rulesetNode->accept( $this );
+
+ if ( $rulesetNode->rules ) {
+
+ if ( count( $rulesetNode->rules ) > 1 ) {
+ $this->_mergeRules( $rulesetNode->rules );
+ $this->_removeDuplicateRules( $rulesetNode->rules );
+ }
+
+ // now decide whether we keep the ruleset
+ if ( $rulesetNode->paths ) {
+ // array_unshift($rulesets, $rulesetNode);
+ array_splice( $rulesets, 0, 0, [ $rulesetNode ] );
+ }
+ }
+
+ }
+
+ if ( count( $rulesets ) === 1 ) {
+ return $rulesets[0];
+ }
+ return $rulesets;
+ }
+
+ /**
+ * Helper function for visitiRuleset
+ *
+ * return array|Less_Tree_Ruleset
+ */
+ private function visitRulesetRoot( $rulesetNode ) {
+ $rulesetNode->accept( $this );
+ if ( $rulesetNode->firstRoot || $rulesetNode->rules ) {
+ return $rulesetNode;
+ }
+ return [];
+ }
+
+ /**
+ * Helper function for visitRuleset()
+ *
+ * @return array
+ */
+ private function visitRulesetPaths( $rulesetNode ) {
+ $paths = [];
+ foreach ( $rulesetNode->paths as $p ) {
+ if ( $p[0]->elements[0]->combinator === ' ' ) {
+ $p[0]->elements[0]->combinator = '';
+ }
+
+ foreach ( $p as $pi ) {
+ if ( $pi->getIsReferenced() && $pi->getIsOutput() ) {
+ $paths[] = $p;
+ break;
+ }
+ }
+ }
+
+ return $paths;
+ }
+
+ protected function _removeDuplicateRules( &$rules ) {
+ // remove duplicates
+ $ruleCache = [];
+ for ( $i = count( $rules ) - 1; $i >= 0; $i-- ) {
+ $rule = $rules[$i];
+ if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ) {
+
+ if ( !isset( $ruleCache[$rule->name] ) ) {
+ $ruleCache[$rule->name] = $rule;
+ } else {
+ $ruleList =& $ruleCache[$rule->name];
+
+ if ( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ) {
+ $ruleList = $ruleCache[$rule->name] = [ $ruleCache[$rule->name]->toCSS() ];
+ }
+
+ $ruleCSS = $rule->toCSS();
+ if ( array_search( $ruleCSS, $ruleList ) !== false ) {
+ array_splice( $rules, $i, 1 );
+ } else {
+ $ruleList[] = $ruleCSS;
+ }
+ }
+ }
+ }
+ }
+
+ protected function _mergeRules( &$rules ) {
+ $groups = [];
+
+ // obj($rules);
+
+ $rules_len = count( $rules );
+ for ( $i = 0; $i < $rules_len; $i++ ) {
+ $rule = $rules[$i];
+
+ if ( ( $rule instanceof Less_Tree_Rule ) && $rule->merge ) {
+
+ $key = $rule->name;
+ if ( $rule->important ) {
+ $key .= ',!';
+ }
+
+ if ( !isset( $groups[$key] ) ) {
+ $groups[$key] = [];
+ } else {
+ array_splice( $rules, $i--, 1 );
+ $rules_len--;
+ }
+
+ $groups[$key][] = $rule;
+ }
+ }
+
+ foreach ( $groups as $parts ) {
+
+ if ( count( $parts ) > 1 ) {
+ $rule = $parts[0];
+ $spacedGroups = [];
+ $lastSpacedGroup = [];
+ $parts_mapped = [];
+ foreach ( $parts as $p ) {
+ if ( $p->merge === '+' ) {
+ if ( $lastSpacedGroup ) {
+ $spacedGroups[] = self::toExpression( $lastSpacedGroup );
+ }
+ $lastSpacedGroup = [];
+ }
+ $lastSpacedGroup[] = $p;
+ }
+
+ $spacedGroups[] = self::toExpression( $lastSpacedGroup );
+ $rule->value = self::toValue( $spacedGroups );
+ }
+ }
+ }
+
+ public static function toExpression( $values ) {
+ $mapped = [];
+ foreach ( $values as $p ) {
+ $mapped[] = $p->value;
+ }
+ return new Less_Tree_Expression( $mapped );
+ }
+
+ public static function toValue( $values ) {
+ // return new Less_Tree_Value($values); ??
+
+ $mapped = [];
+ foreach ( $values as $p ) {
+ $mapped[] = $p;
+ }
+ return new Less_Tree_Value( $mapped );
+ }
+}
diff --git a/vendor/wikimedia/less.php/lib/Less/VisitorReplacing.php b/vendor/wikimedia/less.php/lib/Less/VisitorReplacing.php
new file mode 100644
index 0000000..ae8b82e
--- /dev/null
+++ b/vendor/wikimedia/less.php/lib/Less/VisitorReplacing.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @private
+ */
+class Less_VisitorReplacing extends Less_Visitor {
+
+ public function visitObj( $node ) {
+ $funcName = 'visit' . $node->type;
+ if ( isset( $this->_visitFnCache[$funcName] ) ) {
+
+ $visitDeeper = true;
+ $node = $this->$funcName( $node, $visitDeeper );
+
+ if ( $node ) {
+ if ( $visitDeeper && is_object( $node ) ) {
+ $node->accept( $this );
+ }
+
+ $funcName .= "Out";
+ if ( isset( $this->_visitFnCache[$funcName] ) ) {
+ $this->$funcName( $node );
+ }
+ }
+
+ } else {
+ $node->accept( $this );
+ }
+
+ return $node;
+ }
+
+ public function visitArray( $nodes ) {
+ $newNodes = [];
+ foreach ( $nodes as $node ) {
+ $evald = $this->visitObj( $node );
+ if ( $evald ) {
+ if ( is_array( $evald ) ) {
+ self::flatten( $evald, $newNodes );
+ } else {
+ $newNodes[] = $evald;
+ }
+ }
+ }
+ return $newNodes;
+ }
+
+ public function flatten( $arr, &$out ) {
+ foreach ( $arr as $item ) {
+ if ( !is_array( $item ) ) {
+ $out[] = $item;
+ continue;
+ }
+
+ foreach ( $item as $nestedItem ) {
+ if ( is_array( $nestedItem ) ) {
+ self::flatten( $nestedItem, $out );
+ } else {
+ $out[] = $nestedItem;
+ }
+ }
+ }
+
+ return $out;
+ }
+
+}