: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
namespace Nextend\Framework\Asset\Css\Less;
use Nextend\Framework\Asset\Css\Less\Formatter\Classic;
use Nextend\Framework\Asset\Css\Less\Formatter\Compressed;
use Nextend\Framework\Asset\Css\Less\Formatter\Debug;
use Nextend\Framework\Filesystem\Filesystem;
* The less compiler and parser.
* Converting LESS to CSS is a three stage process. The incoming file is parsed
* by `lessc_parser` into a syntax tree, then it is compiled into another tree
* representing the CSS structure by `lessc`. The CSS tree is fed into a
* formatter, like `lessc_formatter` which then outputs CSS as a string.
* During the first compile, all values are *reduced*, which means that their
* types are brought to the lowest form before being dump as strings. This
* handles math equations, variable dereferences, and the like.
* The `parse` function of `lessc` is the entry point.
* The `lessc` class creates an intstance of the parser, feeds it LESS code,
* then transforms the resulting tree to a CSS tree. This class also holds the
* evaluation context, such as all available mixins and variables at any given
* The `lessc_parser` class is only concerned with parsing its input.
* The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
* handling things like indentation.
#[\AllowDynamicProperties]
static public $VERSION = "v0.3.8";
static protected $TRUE = array(
static protected $FALSE = array(
protected $libFunctions = array();
protected $registeredVars = array();
protected $preserveComments = false;
public $vPrefix = '@'; // prefix of abstract properties
public $mPrefix = '$'; // prefix of abstract blocks
public $parentSelector = '&';
public $importDisabled = false;
protected $numberPrecision = null;
// set to the parser that generated the current line when compiling
// so we know how to create error messages
public $sourceParser = null;
protected $sourceLoc = null;
static public $defaultValue = array(
static protected $nextImportId = 0; // uniquely identify imports
// attempts to find the path of an import url, returns null for css files
protected function findImport($url) {
foreach ((array)$this->importDir as $dir) {
$full = $dir . (substr($dir, -1) != '/' ? '/' : '') . $url;
if ($this->fileExists($file = $full . '.n2less') || $this->fileExists($file = $full)) {
protected function fileExists($name) {
static public function compressList($items, $delim) {
if (!isset($items[1]) && isset($items[0])) return $items[0]; else return array(
static public function preg_quote($what) {
return preg_quote($what, '/');
protected function tryImport($importPath, $parentBlock, $out) {
if ($importPath[0] == "function" && $importPath[1] == "url") {
$importPath = $this->flattenList($importPath[2]);
$str = $this->coerceString($importPath);
if ($str === null) return false;
$url = $this->compileValue($this->lib_e($str));
if (isset($this->registeredVars[$url])) {
$url = $this->registeredVars[$url];
// don't import if it ends in css
if (strlen($url) >= 4 && substr_compare($url, '.css', -4, 4) === 0) return false;
$realPath = $this->findImport($url);
if ($realPath === null) return false;
if ($this->importDisabled) {
$this->addParsedFile($realPath);
$parser = $this->makeParser($realPath);
$root = $parser->parse(file_get_contents($realPath));
// set the parents of all the block props
foreach ($root->props as $prop) {
if ($prop[0] == "block") {
$prop[1]->parent = $parentBlock;
// copy mixins into scope, set their parents
// bring blocks from import into current block
// TODO: need to mark the source parser these came from this file
foreach ($root->children as $childName => $child) {
if (isset($parentBlock->children[$childName])) {
$parentBlock->children[$childName] = array_merge($parentBlock->children[$childName], $child);
$parentBlock->children[$childName] = $child;
$pi = pathinfo($realPath);
list($top, $bottom) = $this->sortProps($root->props, true);
$this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
$oldSourceParser = $this->sourceParser;
$oldImport = $this->importDir;
// TODO: this is because the importDir api is stupid
$this->importDir = (array)$this->importDir;
array_unshift($this->importDir, $importDir);
foreach ($props as $prop) {
$this->compileProp($prop, $block, $out);
$this->importDir = $oldImport;
$this->sourceParser = $oldSourceParser;
* Recursively compiles a block.
* A block is analogous to a CSS block in most cases. A single LESS document
* is encapsulated in a block when parsed, but it does not have parent tags
* so all of it's children appear on the root level when compiled.
* Blocks are made up of props and children.
* Props are property instructions, array tuples which describe an action
* to be taken, eg. write a property, set a variable, mixin a block.
* The children of a block are just all the blocks that are defined within.
* This is used to look up mixins when performing a mixin.
* Compiling the block involves pushing a fresh environment on the stack,
* and iterating through the props, compiling each one.
* See lessc::compileProp()
protected function compileBlock($block) {
$this->compileRoot($block);
$this->compileCSSBlock($block);
$this->compileMedia($block);
$name = "@" . $block->name;
if (!empty($block->value)) {
$name .= " " . $this->compileValue($this->reduce($block->value));
$this->compileNestedBlock($block, array($name));
$this->throwError("unknown block type: $block->type\n");
protected function compileCSSBlock($block) {
$selectors = $this->compileSelectors($block->tags);
$env->selectors = $this->multiplySelectors($selectors);
$out = $this->makeOutputBlock(null, $env->selectors);
$this->scope->children[] = $out;
$this->compileProps($block, $out);
$block->scope = $env; // mixins carry scope with them!
protected function compileMedia($media) {
$env = $this->pushEnv($media);
$parentScope = $this->mediaParent($this->scope);
$query = $this->compileMediaQuery($this->multiplyMedia($env));
$this->scope = $this->makeOutputBlock($media->type, array($query));
$parentScope->children[] = $this->scope;
$this->compileProps($media, $this->scope);
if (count($this->scope->lines) > 0) {
$orphanSelelectors = $this->findClosestSelectors();
if (!is_null($orphanSelelectors)) {
$orphan = $this->makeOutputBlock(null, $orphanSelelectors);
$orphan->lines = $this->scope->lines;
array_unshift($this->scope->children, $orphan);
$this->scope->lines = array();
$this->scope = $this->scope->parent;
protected function mediaParent($scope) {
while (!empty($scope->parent)) {
if (!empty($scope->type) && $scope->type != "media") {
protected function compileNestedBlock($block, $selectors) {
$this->scope = $this->makeOutputBlock($block->type, $selectors);
$this->scope->parent->children[] = $this->scope;
$this->compileProps($block, $this->scope);
$this->scope = $this->scope->parent;
protected function compileRoot($root) {
$this->scope = $this->makeOutputBlock($root->type);
$this->compileProps($root, $this->scope);
protected function compileProps($block, $out) {
foreach ($this->sortProps($block->props) as $prop) {
$this->compileProp($prop, $block, $out);
protected function sortProps($props, $split = false) {
foreach ($props as $prop) {
if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
$id = self::$nextImportId++;
array_merge($vars, $imports),
return array_merge($vars, $imports, $other);
protected function compileMediaQuery($queries) {
$compiledQueries = array();
foreach ($queries as $query) {
$parts[] = implode(" ", array_slice($q, 1));
$parts[] = "($q[1]: " . $this->compileValue($this->reduce($q[2])) . ")";
$parts[] = $this->compileValue($this->reduce($q));
$compiledQueries[] = implode(" and ", $parts);
$out .= " " . implode($this->formatter->selectorSeparator, $compiledQueries);
protected function multiplyMedia($env, $childQueries = null) {
if (is_null($env) || !empty($env->block->type) && $env->block->type != "media") {
if (empty($env->block->type)) {
return $this->multiplyMedia($env->parent, $childQueries);
$queries = $env->block->queries;
if (is_null($childQueries)) {
foreach ($queries as $parent) {
foreach ($childQueries as $child) {
$out[] = array_merge($parent, $child);
return $this->multiplyMedia($env->parent, $out);
protected function expandParentSelectors(&$tag, $replace) {
$parts = explode("$&$", $tag);
foreach ($parts as &$part) {
$part = str_replace($this->parentSelector, $replace, $part, $c);
$tag = implode($this->parentSelector, $parts);
protected function findClosestSelectors() {
if (isset($env->selectors)) {
$selectors = $env->selectors;
// multiply $selectors against the nearest selectors in env
protected function multiplySelectors($selectors) {
$parentSelectors = $this->findClosestSelectors();
if (is_null($parentSelectors)) {
// kill parent reference in top level selector
foreach ($selectors as &$s) {
$this->expandParentSelectors($s, "");
foreach ($parentSelectors as $parent) {
foreach ($selectors as $child) {
$count = $this->expandParentSelectors($child, $parent);
// don't prepend the parent tag if & was used
$out[] = trim($parent . ' ' . $child);
// reduces selector expressions
protected function compileSelectors($selectors) {
foreach ($selectors as $s) {
$out[] = trim($this->compileValue($this->reduce($value)));
protected function eq($left, $right) {
protected function patternMatch($block, $callingArgs) {
// match the guards if it has them
// any one of the groups must have all its guards pass for a match
if (!empty($block->guards)) {
foreach ($block->guards as $guardGroup) {
foreach ($guardGroup as $guard) {
$this->zipSetArgs($block->args, $callingArgs);
if ($guard[0] == "negate") {
$passed = $this->reduce($guard) == self::$TRUE;
if ($negate) $passed = !$passed;