Файловый менеджер - Редактировать - /var/www/html/less.php.zip
Ðазад
PK ! =��5 5 README.mdnu �Iw�� [](https://packagist.org/packages/wikimedia/less.php) Less.php ======== This is a PHP port of the [official LESS processor](https://lesscss.org). ## About The code structure of Less.php mirrors that of upstream Less.js to ensure compatibility and help reduce maintenance. The port aims to be compatible with Less.js 3.13.1. Please note that "inline JavaScript expressions" (via eval or backticks) are not supported. * [API § Caching](./API.md#caching), Less.php includes a file-based cache. * [API § Source maps](./API.md#source-maps), Less.php supports v3 sourcemaps. * [API § Command line](./API.md#command-line), the `lessc` command includes a watch mode. ## Installation You can install the library with Composer or standalone. If you have [Composer](https://getcomposer.org/download/) installed: 1. Run `composer require wikimedia/less.php` 2. Use `Less_Parser` in your code. Or standalone: 1. [Download Less.php](https://gerrit.wikimedia.org/g/mediawiki/libs/less.php/+archive/HEAD.tar.gz) and upload the PHP files to your server. 2. Include the library: ```php require_once '[path to]/less.php/lib/Less/Autoloader.php'; Less_Autoloader::register(); ``` 3. Use `Less_Parser` in your code. ## Security The LESS processor language is powerful and includes features that may read or embed arbitrary files that the web server has access to, and features that may be computationally exensive if misused. In general you should treat LESS files as being in the same trust domain as other server-side executables, such as PHP code. In particular, it is not recommended to allow people that use your web service to provide arbitrary LESS code for server-side processing. _See also [SECURITY](./SECURITY.md)._ ## Who uses Less.php? * **[Wikipedia](https://en.wikipedia.org/wiki/MediaWiki)** and the MediaWiki platform ([docs](https://www.mediawiki.org/wiki/ResourceLoader/Architecture#Resource:_Styles)). * **[Matomo](https://en.wikipedia.org/wiki/Matomo_(software))** ([docs](https://developer.matomo.org/guides/asset-pipeline#vanilla-javascript-css-and-less-files)). * **[Magento](https://en.wikipedia.org/wiki/Magento)** as part of Adobe Commerce ([docs](https://developer.adobe.com/commerce/frontend-core/guide/css/preprocess/)). * **[Icinga](https://en.wikipedia.org/wiki/Icinga)** in Icinga Web ([docs](https://github.com/Icinga/icingaweb2)). * **[Shopware](https://de.wikipedia.org/wiki/Shopware)** ([docs](https://developers.shopware.com/designers-guide/less/)). * **[Winter CMS](https://wintercms.com/)** ([docs](https://wintercms.com/docs/v1.2/docs/themes/development)) ## Integrations Less.php has been integrated with various other projects. #### Transitioning from Leafo/lessphp If you're looking to transition from the [Leafo/lessphp](https://github.com/leafo/lessphp) library, use the `lessc.inc.php` adapter file that comes with Less.php. This allows Less.php to be a drop-in replacement for Leafo/lessphp. [Download Less.php](https://gerrit.wikimedia.org/g/mediawiki/libs/less.php/+archive/HEAD.tar.gz), unzip the files into your project, and include its `lessc.inc.php` instead. Note: The `setPreserveComments` option is ignored. Less.php already preserves CSS block comments by default, and removes LESS inline comments. #### Drupal Less.php can be used with [Drupal's less module](https://drupal.org/project/less) via the `lessc.inc.php` adapter. [Download Less.php](https://gerrit.wikimedia.org/g/mediawiki/libs/less.php/+archive/HEAD.tar.gz) and unzip it so that `lessc.inc.php` is located at `sites/all/libraries/lessphp/lessc.inc.php`, then install the Drupal less module as usual. #### WordPress * [wp_enqueue_less](https://github.com/Ed-ITSolutions/wp_enqueue_less) is a Composer package for use in WordPress themes and plugins. It provides a `wp_enqueue_less()` function to automatically manage caching and compilation on-demand, and loads the compressed CSS on the page. * [JBST framework](https://github.com/bassjobsen/jamedo-bootstrap-start-theme) bundles a copy of Less.php. * The [lessphp plugin](https://wordpress.org/plugins/lessphp/) bundles a copy of Less.php for use in other plugins or themes. This dependency can also be combined with the [TGM Library](http://tgmpluginactivation.com/). ## Credits Less.php was originally ported to PHP in 2011 by [Matt Agar](https://github.com/agar) and then updated by [Martin Jantošovič](https://github.com/Mordred) in 2012. From 2013 to 2017, [Josh Schmidt](https://github.com/oyejorge) lead development of the library. Since 2019, the library is maintained by Wikimedia Foundation. ## Contribute * Issue tracker: https://phabricator.wikimedia.org/tag/less.php/ * Source code: https://gerrit.wikimedia.org/g/mediawiki/libs/less.php ([Get started with Gerrit](https://www.mediawiki.org/wiki/Gerrit/Tutorial/tl;dr)) PK ! ��� CONTRIBUTING.mdnu �Iw�� # Maintainers guide ## Release process 1. **Changelog.** Add a new section to the top of `CHANGES.md` with the output from `composer changelog`. Edit your new section by following the [Keep a changelog](https://keepachangelog.com/en/1.0.0/) conventions, where by bullet points are under one of the "Added", "Changed", "Fixed", "Deprecated", or "Removed" labels. Review each point and make sure it is phrased in a way that explains the impact on end-users of the library. If the change does not affect the public API or CSS output, remove the bullet point. 2. **Version bump.** Update `/lib/Less/Version.php` and set `version` to the version that you're about to release. Also increase `cache_version` to increment the last number. 3. **Commit.** Stage and commit your changes with the message `Tag vX.Y.Z`, and then push the commit for review. 4. **Tag.** After the above release commit is merged, checkout the master branch and pull down the latest changes. Then create a `vX.Y.Z` tag and push the tag. Remember to, after the commit is merged, first checkout the master branch and pull down the latest changes. This is to make sure you have the merged version and not the draft commit that you pushed for review. ## Internal overview This is an overview of the high-level steps during the transformation from Less to CSS, and how they compare between Less.js and Less.php. Less.js: * `less.render(input, { paths: … })` * `Parser.parse` normalizes input * `Parser.parse` parses input into rules via `parsers.primary` * `Parser.parse` creates the "root" ruleset object * `Parser.parse` applies ImportVisitor * `ImportVisitor` applies these steps to each `Import` node: * `ImportVisitor#processImportNode` * `Import#evalForImport` * `ImportVisitor` ends with `ImporVisitor#tryRun` loop (async, after last call to `ImportVisitor#onImported`. * `less.render` callback * `ParseTree.prototype.toCSS` * `transformTree` applies pre-visitors, compiles all rules, and applies post-visitors. * `ParseTree.prototype.toCSS` runs toCSS transform on the "root" ruleset. * CSS result ready! Less.php * `Less_Parser->parseFile` * `Less_Parser->_parse` * `Less_Parser->GetRules` normalizes input (via `Less_Parser->SetInput`) * `Less_Parser->GetRules` parses input into rules via `Less_Parser->parsePrimary` * `Less_Parser->getCss` * `Less_Parser->getCss` creates the "root" ruleset object * `Less_Parser->getCss` applies Less_ImportVisitor * `Less_ImportVisitor` applies these steps to each `Import` node: * `ImportVisitor->processImportNode` * `Less_Tree_Import->compileForImport` * `ImportVisitor` ends with `ImporVisitor#tryRun` loop (all sync, no async needed). * `Less_Parser->getCss` applies pre-visitors, compiles all rules, and applies post-visitors. * `Less_Parser->getCss` runs toCSS transform on the "root" ruleset. * CSS result ready! ## Compatibility The `wikimedia/less.php` package inherits a long history of loosely compatible and interchangable Less compilers written in PHP. Starting with less.php v3.2.1 (released in 2023), the public API is more clearly documented, and internal code is now consistently marked `@private`. The public API includes the `Less_Parser` class and several of its public methods. For legacy reasons, some of its internal methods remain public. Maintainers must take care to search the following downstream applications when changing or removing public methods. If a method has one or more references in the below codebases, treat it as a breaking change and document a migration path in the commit message (and later in CHANGES.md), even if the method was undocumented or feels like it is for internal use only. * [MediaWiki (source code)](https://codesearch.wmcloud.org/core/?q=Less_Parser&files=php%24) * [Matomo (source code)](https://github.com/matomo-org/matomo/blob/5.0.2/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php) * [Adobe Magento (source code)](https://github.com/magento/magento2/blob/2.4.6/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php) * [Shopware 5 (source code)](https://github.com/shopware5/shopware/blob/5.7/engine/Shopware/Components/Theme/LessCompiler/Oyejorge.php) * [Winter CMS Assetic (source code)](https://github.com/assetic-php/assetic/tree/v3.1.0/src/Assetic/Filter) PK ! Fh�c lib/Less/Configurable.phpnu �Iw�� <?php /** * @private */ abstract class Less_Configurable { /** * Array of options * * @var array */ protected $options = []; /** * Array of default options * * @var array */ protected $defaultOptions = []; /** * @param array $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; } } PK ! �0��CA CA # lib/Less/Visitor/processExtends.phpnu �Iw�� <?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 visitDeclaration( $declNode, &$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 array $potentialMatch * @param Less_Tree_Element[] $needleElements * @param Less_Tree_Element $haystackElement * @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; if ( $elementValue1 instanceof Less_Tree_Quoted ) { $elementValue1 = $elementValue1->value; } $elementValue2 = $elementValue2->value; if ( $elementValue2 instanceof Less_Tree_Quoted ) { $elementValue2 = $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, // last parameter of array_slice is different than the last parameter of javascript's slice $match['index'] - $currentSelectorPathElementIndex ), [ $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 visitAtRule( $atRuleNode ) { $newAllExtends = array_merge( $atRuleNode->allExtends, end( $this->allExtendsStack ) ); $this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $atRuleNode->allExtends ); } protected function visitAtRuleOut() { array_pop( $this->allExtendsStack ); } } PK ! 5U�� � lib/Less/Visitor/toCSS.phpnu �Iw�� <?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 visitDeclaration( $declNode ) { if ( $declNode->variable ) { return []; } return $declNode; } 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 visitAtRule( $atRuleNode, &$visitDeeper ) { if ( $atRuleNode->name === '@charset' ) { if ( !$atRuleNode->getIsReferenced() ) { return; } if ( isset( $this->charset ) && $this->charset ) { // NOTE: Skip debugInfo handling (not implemented) return; } $this->charset = true; } if ( $atRuleNode->rules ) { self::_mergeRules( $atRuleNode->rules[0]->rules ); // process childs $atRuleNode->accept( $this ); $visitDeeper = false; // the directive was directly referenced and therefore needs to be shown in the output if ( $atRuleNode->getIsReferenced() ) { return $atRuleNode; } if ( !$atRuleNode->rules ) { return; } if ( $this->hasVisibleChild( $atRuleNode ) ) { // marking as referenced in case the directive is stored inside another directive $atRuleNode->markReferenced(); return $atRuleNode; } // The directive was not directly referenced and does not contain anything that //was referenced. Therefore it must not be shown in output. return; } else { if ( !$atRuleNode->getIsReferenced() ) { return; } } return $atRuleNode; } public function checkPropertiesInRoot( $rulesetNode ) { if ( !$rulesetNode->firstRoot ) { return; } foreach ( $rulesetNode->rules as $ruleNode ) { if ( $ruleNode instanceof Less_Tree_Declaration && !$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 ) { self::_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; } public function visitAnonymous( $anonymousNode ) { if ( !$anonymousNode->getIsReferenced() ) { return; } $anonymousNode->accept( $this ); return $anonymousNode; } public function visitImport( $importNode ) { if ( isset( $importNode->path->currentFileInfo["reference"] ) && $importNode->css ) { return; } return $importNode; } /** * 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_Declaration || $rule instanceof Less_Tree_NameValue ) { if ( !isset( $ruleCache[$rule->name] ) ) { $ruleCache[$rule->name] = $rule; } else { $ruleList =& $ruleCache[$rule->name]; if ( $ruleList instanceof Less_Tree_Declaration || $ruleList instanceof Less_Tree_NameValue ) { $ruleList = $ruleCache[$rule->name] = [ $ruleCache[$rule->name]->toCSS() ]; } $ruleCSS = $rule->toCSS(); if ( in_array( $ruleCSS, $ruleList ) ) { array_splice( $rules, $i, 1 ); } else { $ruleList[] = $ruleCSS; } } } } } public static 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_Declaration ) && $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 ); } public function hasVisibleChild( $atRuleNode ) { // prepare list of childs $rule = $bodyRules = $atRuleNode->rules; // if there is only one nested ruleset and that one has no path, then it is //just fake ruleset that got not replaced and we need to look inside it to //get real childs if ( count( $bodyRules ) === 1 && ( !$bodyRules[0]->paths || count( $bodyRules[0]->paths ) === 0 ) ) { $bodyRules = $bodyRules[0]->rules; } foreach ( $bodyRules as $rule ) { if ( method_exists( $rule, 'getIsReferenced' ) && $rule->getIsReferenced() ) { // the directive contains something that was referenced (likely by extend) //therefore it needs to be shown in output too return true; } } return false; } } PK ! W�5 � � ! lib/Less/Visitor/joinSelector.phpnu �Iw�� <?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 visitDeclration( $declNode, &$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; } } public function visitAtRule( $atRuleNode ) { $context = end( $this->contexts ); if ( $atRuleNode->rules && count( $atRuleNode->rules ) > 0 ) { $atRuleNode->rules[0]->root = $atRuleNode->isRooted || count( $context ) === 0; } } } PK ! ��ݫ� � ! lib/Less/Visitor/extendFinder.phpnu �Iw�� <?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 visitDeclaration( $declNode, &$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, Less_Tree_Extend $extend, &$j ) { $this->foundExtends = true; $extend = $extend->clone(); $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 visitAtRule( $atRuleNode ) { $atRuleNode->allExtends = []; $this->allExtendsStack[] =& $atRuleNode->allExtends; } public function visitAtRuleOut() { array_pop( $this->allExtendsStack ); } } PK ! vK}� � lib/Less/Visitor.phpnu �Iw�� <?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 ) { if ( !$node || !is_object( $node ) ) { return $node; } $funcName = 'visit' . str_replace( [ 'Less_Tree_', '_' ], '', get_class( $node ) ); if ( isset( $this->_visitFnCache[$funcName] ) ) { $visitDeeper = true; $newNode = $this->$funcName( $node, $visitDeeper ); if ( $this instanceof Less_VisitorReplacing ) { $node = $newNode; } 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 ) { // NOTE: The use of by-ref in a normal (non-replacing) Visitor may be surprising, // but upstream relies on this for Less_ImportVisitor, which modifies values of // `$importParent->rules` yet is not a replacing visitor. foreach ( $nodes as &$node ) { $this->visitObj( $node ); } return $nodes; } } PK ! |�v� � lib/Less/SourceMap/Base64VLQ.phpnu �Iw�� <?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( "Invalid number \"$number\" given. Must be between 0 and 63." ); } 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]; } } PK ! �ڌ+�&