: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$numCalling = count($callingArgs);
if (empty($block->args)) {
return $block->isVararg || $numCalling == 0;
// try to match by arity or by argument literal
foreach ($block->args as $i => $arg) {
if (empty($callingArgs[$i]) || !$this->eq($arg[1], $callingArgs[$i])) {
// no arg and no default value
if (!isset($callingArgs[$i]) && !isset($arg[2])) {
$i--; // rest can be empty
return true; // not having enough is handled above
// greater than becuase default values always match
return $numMatched >= $numCalling;
protected function patternMatchAll($blocks, $callingArgs) {
foreach ($blocks as $block) {
if ($this->patternMatch($block, $callingArgs)) {
// attempt to find blocks matched by path and args
protected function findBlocks($searchIn, $path, $args, $seen = array()) {
if ($searchIn == null) return null;
if (isset($seen[$searchIn->id])) return null;
$seen[$searchIn->id] = true;
if (isset($searchIn->children[$name])) {
$blocks = $searchIn->children[$name];
$matches = $this->patternMatchAll($blocks, $args);
// This will return all blocks that match in the closest
// scope that has any matching block, like lessjs
foreach ($blocks as $subBlock) {
$subMatches = $this->findBlocks($subBlock, array_slice($path, 1), $args, $seen);
if (!is_null($subMatches)) {
foreach ($subMatches as $sm) {
return count($matches) > 0 ? $matches : null;
if ($searchIn->parent === $searchIn) return null;
return $this->findBlocks($searchIn->parent, $path, $args, $seen);
// sets all argument names in $args to either the default value
// or the one passed in through $values
protected function zipSetArgs($args, $values) {
$assignedValues = array();
if ($i < count($values) && !is_null($values[$i])) {
} elseif (isset($a[2])) {
$value = $this->reduce($value);
$this->set($a[1], $value);
$assignedValues[] = $value;
if (!empty($last) && $last[0] == "rest") {
$rest = array_slice($values, count($args) - 1);
$this->set($last[1], $this->reduce(array(
$this->env->arguments = $assignedValues;
// compile a prop and update $lines or $blocks appropriately
protected function compileProp($prop, $block, $out) {
// set error position context
$this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
list(, $name, $value) = $prop;
if ($name[0] == $this->vPrefix) {
$this->set($name, $value);
$out->lines[] = $this->formatter->property($name, $this->compileValue($this->reduce($value)));
$this->compileBlock($child);
list(, $path, $args, $suffix) = $prop;
$mixins = $this->findBlocks($block, $path, $args);
// fwrite(STDERR,"failed to find block: ".implode(" > ", $path)."\n");
break; // throw error here??
foreach ($mixins as $mixin) {
if (isset($mixin->parent->scope)) {
$mixinParentEnv = $this->pushEnv();
$mixinParentEnv->storeParent = $mixin->parent->scope;
if (isset($mixin->args)) {
$this->zipSetArgs($mixin->args, $args);
$oldParent = $mixin->parent;
if ($mixin != $block) $mixin->parent = $block;
foreach ($this->sortProps($mixin->props) as $subProp) {
if ($suffix !== null && $subProp[0] == "assign" && is_string($subProp[1]) && $subProp[1][0] != $this->vPrefix) {
$this->compileProp($subProp, $mixin, $out);
$mixin->parent = $oldParent;
if ($haveArgs) $this->popEnv();
if ($haveScope) $this->popEnv();
$out->lines[] = $prop[1];
list(, $name, $value) = $prop;
$out->lines[] = "@$name " . $this->compileValue($this->reduce($value)) . ';';
$out->lines[] = $prop[1];
list(, $importPath, $importId) = $prop;
$importPath = $this->reduce($importPath);
if (!isset($this->env->imports)) {
$this->env->imports = array();
$result = $this->tryImport($importPath, $block, $out);
$this->env->imports[$importId] = $result === false ? array(
"@import " . $this->compileValue($importPath) . ";"
list(, $importId) = $prop;
$import = $this->env->imports[$importId];
if ($import[0] === false) {
$out->lines[] = $import[1];
list(, $bottom, $parser, $importDir) = $import;
$this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
$this->throwError("unknown op: {$prop[0]}\n");
* Compiles a primitive value into a CSS property value.
* Values in lessphp are typed by being wrapped in arrays, their format is
* array(type, contents [, additional_contents]*)
* The input is expected to be reduced. This function will not work on
* things like expressions and variables.
protected function compileValue($value) {
return implode($value[1], array_map(array(
if (!empty($this->formatter->compressColors)) {
return $this->compileValue($this->coerceColor($value));
list(, $num, $unit) = $value;
if ($this->numberPrecision !== null) {
$num = round($num, $this->numberPrecision);
// [1] - contents of string (includes quotes)
list(, $delim, $content) = $value;
foreach ($content as &$part) {
$part = $this->compileValue($part);
return $delim . implode($content) . $delim;
// [1] - red component (either number or a %)
// [4] - optional alpha component
list(, $r, $g, $b) = $value;
if (count($value) == 5 && $value[4] != 1) { // rgba
return 'rgba(' . $r . ',' . $g . ',' . $b . ',' . $value[4] . ')';
$h = sprintf("#%02x%02x%02x", $r, $g, $b);
if (!empty($this->formatter->compressColors)) {
// Converting hex color to short notation (e.g. #003399 to #039)
if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
$h = '#' . $h[1] . $h[3] . $h[5];
list(, $name, $args) = $value;
return $name . '(' . $this->compileValue($args) . ')';
default: // assumed to be unit
$this->throwError("unknown value type: $value[0]");
protected function lib_isnumber($value) {
return $this->toBool($value[0] == "number");
protected function lib_isstring($value) {
return $this->toBool($value[0] == "string");
protected function lib_iscolor($value) {
return $this->toBool($this->coerceColor($value));
protected function lib_iskeyword($value) {
return $this->toBool($value[0] == "keyword");
protected function lib_ispixel($value) {
return $this->toBool($value[0] == "number" && $value[2] == "px");
protected function lib_ispercentage($value) {
return $this->toBool($value[0] == "number" && $value[2] == "%");
protected function lib_isem($value) {
return $this->toBool($value[0] == "number" && $value[2] == "em");
protected function lib_isrem($value) {
return $this->toBool($value[0] == "number" && $value[2] == "rem");
protected function lib_rgbahex($color) {
$color = $this->coerceColor($color);
if (is_null($color)) $this->throwError("color expected for rgbahex");
return sprintf("#%02x%02x%02x%02x", isset($color[4]) ? $color[4] * 255 : 255, $color[1], $color[2], $color[3]);
protected function lib_argb($color) {
return $this->lib_rgbahex($color);
// utility func to unquote a string
protected function lib_e($arg) {
return $this->lib_e($items[0]);
return self::$defaultValue;
$this->compileValue($arg)
protected function lib__sprintf($args) {
if ($args[0] != "list") return $args;
$string = array_shift($values);
$template = $this->compileValue($this->lib_e($string));
if (preg_match_all('/%[dsa]/', $template, $m)) {
foreach ($m[0] as $match) {
$val = isset($values[$i]) ? $this->reduce($values[$i]) : array(
// lessjs compat, renders fully expanded color, not raw color
if ($color = $this->coerceColor($val)) {
$rep = $this->compileValue($this->lib_e($val));
$template = preg_replace('/' . self::preg_quote($match) . '/', $rep, $template, 1);
$d = $string[0] == "string" ? $string[1] : '"';
protected function lib_floor($arg) {
$value = $this->assertNumber($arg);
protected function lib_ceil($arg) {
$value = $this->assertNumber($arg);
protected function lib_round($arg) {
$value = $this->assertNumber($arg);
protected function lib_unit($arg) {
list($number, $newUnit) = $arg[2];
$this->assertNumber($number),
$this->compileValue($this->lib_e($newUnit))
$this->assertNumber($arg),
* Helper function to get arguments for color manipulation functions.
* takes a list that contains a color like thing and a percentage
protected function colorArgs($args) {
if ($args[0] != 'list' || count($args[2]) < 2) {