summaryrefslogtreecommitdiffstats
path: root/vendor/wikimedia/less.php/lib/Less/Visitor
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/wikimedia/less.php/lib/Less/Visitor')
-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
5 files changed, 1031 insertions, 0 deletions
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 );
+ }
+}