Browse Source

Initial commit

Miraty 4 years ago
commit
f6cce25a8b
100 changed files with 12792 additions and 0 deletions
  1. 22 0
      auth/acc.csv
  2. 14 0
      auth/index.php
  3. 53 0
      auth/login-csv.php
  4. 43 0
      auth/login.php
  5. 8 0
      auth/logout.php
  6. 32 0
      auth/register-csv.php
  7. 48 0
      auth/register.php
  8. 13 0
      bottom.inc.php
  9. 0 0
      css/lessphp_257q7e5wsg000wskskosko8s4gggg00.lesscache
  10. 0 0
      css/lessphp_78brb0gq7eccocco800ksc04sc44cg0.lesscache
  11. 1 0
      css/lessphp_875433pmt6w44wwgwk4g4w8s8okw08c.lesscache
  12. 87 0
      css/lessphp_a3957886f597d2e20a01c1e18c0588cfbb3aef14.css
  13. 4 0
      css/lessphp_d8ddf76d7bce5fedd7293eaa54b692f7.list
  14. 1 0
      css/lessphpvars_81cf9c18478094febee0a4aa4ca97d59e14197e1.less
  15. 8 0
      db.inc.php
  16. BIN
      db/auth.db
  17. 22 0
      dom.php
  18. 33 0
      ht/domain.php
  19. 23 0
      ht/index.php
  20. 34 0
      ht/le.php
  21. 27 0
      ht/mkdir.php
  22. 52 0
      ht/onion.php
  23. 27 0
      ht/setup.php
  24. 21 0
      index.php
  25. 13 0
      less/buttons.less
  26. 56 0
      less/form.less
  27. 42 0
      less/main.less
  28. 70 0
      lessphp/CHANGES.md
  29. 178 0
      lessphp/LICENSE
  30. 315 0
      lessphp/README.md
  31. 191 0
      lessphp/bin/lessc
  32. 49 0
      lessphp/composer.json
  33. 274 0
      lessphp/lessc.inc.php
  34. 2 0
      lessphp/lib/Less/.easymin/ignore_prefixes
  35. 77 0
      lessphp/lib/Less/Autoloader.php
  36. 293 0
      lessphp/lib/Less/Cache.php
  37. 169 0
      lessphp/lib/Less/Colors.php
  38. 66 0
      lessphp/lib/Less/Configurable.php
  39. 157 0
      lessphp/lib/Less/Environment.php
  40. 203 0
      lessphp/lib/Less/Exception/Chunk.php
  41. 11 0
      lessphp/lib/Less/Exception/Compiler.php
  42. 116 0
      lessphp/lib/Less/Exception/Parser.php
  43. 1186 0
      lessphp/lib/Less/Functions.php
  44. 17 0
      lessphp/lib/Less/Less.php.combine
  45. 41 0
      lessphp/lib/Less/Mime.php
  46. 48 0
      lessphp/lib/Less/Output.php
  47. 119 0
      lessphp/lib/Less/Output/Mapped.php
  48. 2691 0
      lessphp/lib/Less/Parser.php
  49. 187 0
      lessphp/lib/Less/SourceMap/Base64VLQ.php
  50. 354 0
      lessphp/lib/Less/SourceMap/Generator.php
  51. 84 0
      lessphp/lib/Less/Tree.php
  52. 48 0
      lessphp/lib/Less/Tree/Alpha.php
  53. 58 0
      lessphp/lib/Less/Tree/Anonymous.php
  54. 39 0
      lessphp/lib/Less/Tree/Assignment.php
  55. 53 0
      lessphp/lib/Less/Tree/Attribute.php
  56. 117 0
      lessphp/lib/Less/Tree/Call.php
  57. 230 0
      lessphp/lib/Less/Tree/Color.php
  58. 51 0
      lessphp/lib/Less/Tree/Comment.php
  59. 72 0
      lessphp/lib/Less/Tree/Condition.php
  60. 34 0
      lessphp/lib/Less/Tree/DefaultFunc.php
  61. 39 0
      lessphp/lib/Less/Tree/DetachedRuleset.php
  62. 198 0
      lessphp/lib/Less/Tree/Dimension.php
  63. 96 0
      lessphp/lib/Less/Tree/Directive.php
  64. 70 0
      lessphp/lib/Less/Tree/Element.php
  65. 95 0
      lessphp/lib/Less/Tree/Expression.php
  66. 80 0
      lessphp/lib/Less/Tree/Extend.php
  67. 291 0
      lessphp/lib/Less/Tree/Import.php
  68. 30 0
      lessphp/lib/Less/Tree/Javascript.php
  69. 43 0
      lessphp/lib/Less/Tree/Keyword.php
  70. 173 0
      lessphp/lib/Less/Tree/Media.php
  71. 193 0
      lessphp/lib/Less/Tree/Mixin/Call.php
  72. 233 0
      lessphp/lib/Less/Tree/Mixin/Definition.php
  73. 49 0
      lessphp/lib/Less/Tree/NameValue.php
  74. 37 0
      lessphp/lib/Less/Tree/Negative.php
  75. 68 0
      lessphp/lib/Less/Tree/Operation.php
  76. 35 0
      lessphp/lib/Less/Tree/Paren.php
  77. 79 0
      lessphp/lib/Less/Tree/Quoted.php
  78. 112 0
      lessphp/lib/Less/Tree/Rule.php
  79. 621 0
      lessphp/lib/Less/Tree/Ruleset.php
  80. 26 0
      lessphp/lib/Less/Tree/RulesetCall.php
  81. 165 0
      lessphp/lib/Less/Tree/Selector.php
  82. 28 0
      lessphp/lib/Less/Tree/UnicodeDescriptor.php
  83. 142 0
      lessphp/lib/Less/Tree/Unit.php
  84. 35 0
      lessphp/lib/Less/Tree/UnitConversions.php
  85. 76 0
      lessphp/lib/Less/Tree/Url.php
  86. 47 0
      lessphp/lib/Less/Tree/Value.php
  87. 51 0
      lessphp/lib/Less/Tree/Variable.php
  88. 15 0
      lessphp/lib/Less/Version.php
  89. 46 0
      lessphp/lib/Less/Visitor.php
  90. 109 0
      lessphp/lib/Less/Visitor/extendFinder.php
  91. 137 0
      lessphp/lib/Less/Visitor/import.php
  92. 68 0
      lessphp/lib/Less/Visitor/joinSelector.php
  93. 441 0
      lessphp/lib/Less/Visitor/processExtends.php
  94. 280 0
      lessphp/lib/Less/Visitor/toCSS.php
  95. 70 0
      lessphp/lib/Less/VisitorReplacing.php
  96. 82 0
      nic/ds.php
  97. 38 0
      nic/glue.php
  98. 11 0
      nic/index.php
  99. 43 0
      nic/ns.php
  100. 56 0
      nic/register.php

+ 22 - 0
auth/acc.csv

@@ -0,0 +1,22 @@
+a,b
+c,d
+e,f
+40,41
+40,41
+40,41
+40,41
+40,41
+a,b
+a,b
+a,b
+a,b
+a,b
+a,b
+gergez,$2y$10$eozUhORVi31KoYcAfeV6R.dcT79GbJF4pWSFC1SPrUSuTmFbFOSXq
+hrteheryt,$2y$10$b1KPV/WdDxLxgBhfyYZaGOOcxeZ.a7kO0oogjIwIxxLL3XbGIDrx6
+loiloiulilui,$2y$10$nmsmh1hqTKDSVKQyIBw34eLHiU7qGADQk8BMY6SYW7mydtmTVEST.
+luiluylitlyuk,$2y$10$AD2X7Pd0KPrVHAxX3Xij.O5JwNnfOHKWx02yxqeZfFxqWOmsCOsea
+grgreg,$2y$10$PKUsl.OvHQjc2BbDdjoJMea8/nW28EipJhrOaOx4XytIuJcpLQFkW
+,$2y$10$xO..USSsDDfJm8El13gu/esh1tV/3NJG4kOypfKn48iQyYXkmXJvW
+ab,$2y$10$/I1TZ2D59588b5j1xx6qZ.gZtOhUQ9L5/vgOfL5ovJtUDARf3LUu2
+azerty,$2y$10$fSTFx1sZEgzN2DcD9lWbKO8ee/QBRR3Ph83vRvZD8169/z/hvhk3S

+ 14 - 0
auth/index.php

@@ -0,0 +1,14 @@
+<?php require "../top.inc.php"; ?>
+<?php if (isset($_SESSION['username'])) { ?>
+<a href="logout.php">Se déconnecter</a>
+
+<?php } else { ?>
+  Vous devez être authentifié·e pour utiliser Niver
+  <br>
+  <a href="register.php">Créer un compte</a>
+  <br>
+  <a href="login.php">Se connecter</a>
+<?php } ?>
+
+
+<?php require "../bottom.inc.php"; ?>

+ 53 - 0
auth/login-csv.php

@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Connexion au compte Hypertopie</title>
+  </head>
+  <body>
+    <header>
+      <h1>Serveur de nom ns1.atope.art</h1>
+    </header>
+    <p>
+      Connexion à votre compte Hypertopie
+    </p>
+
+    <form action="login-csv.php" method="post">
+      <label for="username">Identifiant :</label><br>
+      <input id="username" name="username" type="text"/><br>
+
+      <label for="password">Mot de passe :</label><br>
+      <input id="password" name="password" type="password"/><br>
+
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_POST['password'])) {
+
+      if (($file = fopen("acc.csv", "r")) !== FALSE) {
+        while (($ligne = fgetcsv($file, 1000, ",")) !== FALSE) {
+          if ($_POST['username'] == $ligne[0]) {
+            if (password_verify($_POST['password'], $ligne[1])) {
+              echo "Connexion réussie";
+            } else {
+              echo "Connexion échouée";
+            }
+
+            exit();
+          }
+        }
+        fclose($file);
+      }
+
+      echo "Formulaire traité !!";
+    } else {
+      echo "Rien n'a été reçu lors du dernière chargement";
+    }
+
+    ?>
+
+  </body>
+</html>
+
+<?php

+ 43 - 0
auth/login.php

@@ -0,0 +1,43 @@
+<?php require "../top.inc.php"; ?>
+
+    <form action="login.php" method="post">
+      <label for="username">Identifiant</label><br>
+      <input id="username" name="username" type="text"/><br>
+
+      <label for="password">Mot de passe</label><br>
+      <input id="password" name="password" type="password"/><br>
+
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_POST['username']) AND isset($_POST['password'])) {
+
+      $username[0] = $_POST['username'];
+
+      $db = new PDO('sqlite:' . $dbPath);
+
+      $req = $db->prepare('SELECT username, password FROM users WHERE username = ?');
+      $req->execute($username);
+
+      $goodPassword = $req->fetch()['password'];
+
+      if (password_verify($_POST['password'], $goodPassword)) {
+        $_SESSION['username'] = $_POST['username'];
+        header('Location: ../index.php');
+      } else {
+        echo "<br>Connexion impossible : paire identifiant/mot de passe invalide";
+      }
+
+
+
+
+      echo "<br>Formulaire traité !";
+    } else {
+      echo "<br>Rien n'a été reçu lors du dernier chargement";
+    }
+
+    ?>
+
+<?php require "../bottom.inc.php"; ?>

+ 8 - 0
auth/logout.php

@@ -0,0 +1,8 @@
+<?php require "../top.inc.php"; ?>
+
+<?php
+session_destroy();
+header('Location: ' . $prefixURL . '/auth/');
+exit;
+?>
+<?php require "../bottom.inc.php"; ?>

+ 32 - 0
auth/register-csv.php

@@ -0,0 +1,32 @@
+<?php include "../top.inc.php"; ?>
+    <p>
+      Ici vous pouvez ajouter ou enlever des IPv4 dans une zone déjà enregistrée sur le serveur ns1.atope.art
+    </p>
+
+    <form action="register-csv.php" method="post">
+      <label for="username">Identifiant :</label><br>
+      <input id="username" name="username" type="text"/><br>
+
+      <label for="password">Mot de passe</label><br>
+      <input id="password" name="password" type="password"/><br>
+
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_POST['password'])) {
+
+      // Register the user to the CSV file
+      file_put_contents("acc.csv", $_POST['username'] . "," . password_hash($_POST['password'], PASSWORD_DEFAULT) . "\n", FILE_APPEND);
+
+      // Adds user to the system
+
+
+      echo "Formulaire traité !!";
+    } else {
+      echo "Rien n'a été reçu lors du dernière chargement";
+    }
+
+    ?>
+<?php include "../bottom.inc.php"; ?>

+ 48 - 0
auth/register.php

@@ -0,0 +1,48 @@
+<?php require "../top.inc.php"; ?>
+
+    <form action="register.php" method="post">
+      <label for="username">Identifiant</label><br>
+      <input id="username" name="username" type="text"/><br>
+
+      <label for="password">Mot de passe</label><br>
+      <input id="password" name="password" type="password"/><br>
+
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_POST['username']) AND isset($_POST['password'])) {
+      $username = $_POST['username'];
+      $password = password_hash($_POST['password'], PASSWORD_DEFAULT);
+
+      $db = new PDO('sqlite:' . $dbPath);
+
+      $stmt = $db->prepare("INSERT INTO users(username, password) VALUES(:username, :password)");
+
+      $stmt->bindParam(':username', $username);
+      $stmt->bindParam(':password', $password);
+
+      $stmt->execute();
+
+      // Setup SFTP access for Hypertext
+      exec("sudo /root/maniver/target/debug/maniver setup-user " . $_POST['username'] . " " . $_POST['password']);
+
+    }
+
+    echo "Liste des utilisateurices<br>";
+
+    $db = new PDO('sqlite:' . $dbPath);
+
+    $result = $db->query('SELECT * FROM users');
+
+    foreach ($result as $result) {
+        print $result['id'];
+        echo " - ";
+        print $result['username'];
+        echo "<br>";
+    }
+
+?>
+
+<?php require "../bottom.inc.php"; ?>

+ 13 - 0
bottom.inc.php

@@ -0,0 +1,13 @@
+    <br>
+    <br>
+    <footer>
+      <small>
+      <?php if (isset($_SESSION['username'])) {
+        echo "Connecté·e en tant que " . $_SESSION['username'] . "<br><a href='" . $prefixURL . "/auth/logout.php'>Se déconnecter</a>";
+      } else { ?>
+        Vous n'êtes pas connecté·e à un compte Niver
+      <?php } ?>
+      </small>
+    </footer>
+  </body>
+</html>

File diff suppressed because it is too large
+ 0 - 0
css/lessphp_257q7e5wsg000wskskosko8s4gggg00.lesscache


File diff suppressed because it is too large
+ 0 - 0
css/lessphp_78brb0gq7eccocco800ksc04sc44cg0.lesscache


+ 1 - 0
css/lessphp_875433pmt6w44wwgwk4g4w8s8okw08c.lesscache

@@ -0,0 +1 @@
+<?php return  Array(0 =>  new Less_Tree_Rule('@mainColor', new Less_Tree_Value( Array(0 =>  new Less_Tree_Expression( Array(0 =>  new Less_Tree_Color('00FF00'),)),)),NULL,false,0, Array('entryPath' => '/var/www/niver/capuche/less/','entryUri' => '','rootpath' => '/var/www/niver/capuche/less/','currentDirectory' => '/var/www/niver/capuche/css/','currentUri' => '/lessphpvars_81cf9c18478094febee0a4aa4ca97d59e14197e1.less','filename' => '/var/www/niver/capuche/css/lessphpvars_81cf9c18478094febee0a4aa4ca97d59e14197e1.less','uri_root' => '/',)),1 =>  new Less_Tree_Rule('@color1', new Less_Tree_Value( Array(0 =>  new Less_Tree_Expression( Array(0 =>  new Less_Tree_Color('ffffff'),)),)),NULL,false,20, Array('entryPath' => '/var/www/niver/capuche/less/','entryUri' => '','rootpath' => '/var/www/niver/capuche/less/','currentDirectory' => '/var/www/niver/capuche/css/','currentUri' => '/lessphpvars_81cf9c18478094febee0a4aa4ca97d59e14197e1.less','filename' => '/var/www/niver/capuche/css/lessphpvars_81cf9c18478094febee0a4aa4ca97d59e14197e1.less','uri_root' => '/',)),2 =>  new Less_Tree_Rule('@color2', new Less_Tree_Value( Array(0 =>  new Less_Tree_Expression( Array(0 =>  new Less_Tree_Color('2a2a2a'),)),)),NULL,false,35, Array('entryPath' => '/var/www/niver/capuche/less/','entryUri' => '','rootpath' => '/var/www/niver/capuche/less/','currentDirectory' => '/var/www/niver/capuche/css/','currentUri' => '/lessphpvars_81cf9c18478094febee0a4aa4ca97d59e14197e1.less','filename' => '/var/www/niver/capuche/css/lessphpvars_81cf9c18478094febee0a4aa4ca97d59e14197e1.less','uri_root' => '/',)),); ?>

+ 87 - 0
css/lessphp_a3957886f597d2e20a01c1e18c0588cfbb3aef14.css

@@ -0,0 +1,87 @@
+html {
+  font-family: system-ui;
+  margin-left: 35%;
+  margin-right: 35%;
+  font-size: 26px;
+}
+@media (max-width: 500px) {
+  html {
+    margin-left: 20px;
+    margin-right: 20px;
+  }
+}
+header {
+  text-align: center;
+}
+a {
+  color: #00ff00;
+}
+.button {
+  border: 2px red solid;
+  text-decoration: none;
+  border-radius: 10px;
+  padding: 8px;
+}
+@media (prefers-color-scheme: light) {
+  html {
+    background-color: #ffffff;
+    color: #2a2a2a;
+  }
+}
+@media (prefers-color-scheme: dark) {
+  html {
+    background-color: #2a2a2a;
+    color: #ffffff;
+  }
+}
+form {
+  margin-left: 50px;
+  margin-right: 50px;
+  text-align: center;
+}
+@media (max-width: 500px) {
+  form {
+    margin-left: 0px;
+    margin-right: 0px;
+  }
+}
+input {
+  border-radius: 12px;
+  height: 30px;
+  font-size: 26px;
+  font-family: monospace;
+  margin: 5px;
+  height: 100%;
+  padding: 10px;
+}
+input[type=submit] {
+  margin: 22px;
+}
+@media (prefers-color-scheme: light) {
+  input {
+    background-color: #ffffff;
+    color: #2a2a2a;
+    border: 2px solid #2a2a2a;
+  }
+  input:hover {
+    box-shadow: 0px 0px 10px #2a2a2a;
+  }
+  input:focus {
+    box-shadow: 0px 0px 15px #00ff00;
+    border: 2px #00ff00 solid;
+  }
+}
+@media (prefers-color-scheme: dark) {
+  input {
+    background-color: #2a2a2a;
+    color: #ffffff;
+    border: 2px solid #ffffff;
+  }
+  input:hover {
+    box-shadow: 0px 0px 10px #ffffff;
+  }
+  input:focus {
+    box-shadow: 0px 0px 15px #00ff00;
+    border: 2px #00ff00 solid;
+  }
+}

+ 4 - 0
css/lessphp_d8ddf76d7bce5fedd7293eaa54b692f7.list

@@ -0,0 +1,4 @@
+/var/www/niver/capuche/less/main.less
+/var/www/niver/capuche/less/form.less
+/var/www/niver/capuche/css/lessphpvars_81cf9c18478094febee0a4aa4ca97d59e14197e1.less
+lessphp_a3957886f597d2e20a01c1e18c0588cfbb3aef14.css

+ 1 - 0
css/lessphpvars_81cf9c18478094febee0a4aa4ca97d59e14197e1.less

@@ -0,0 +1 @@
+@mainColor: #00FF00;@color1: white;@color2: #2a2a2a;

+ 8 - 0
db.inc.php

@@ -0,0 +1,8 @@
+<?php
+function setupDB() {
+  try {
+    $db = new PDO('sqlite:/var/www/nic/db/auth.db');
+  } catch(Exception $e) {
+    die('Erreur SQLite : ' . $e->getMessage());
+  }
+}

BIN
db/auth.db


+ 22 - 0
dom.php

@@ -0,0 +1,22 @@
+
+    <form action="dom.php" method="post">
+      <label for="domain">Domaine</label><br>
+      <input placeholder="niver.atope.art" id="domain" name="domain" type="text"/><br>
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_POST['domain'])) {
+      if (filter_var($_POST['domain'], FILTER_VALIDATE_DOMAIN)) {
+        echo htmlspecialchars(filter_var($_POST['domain'], FILTER_VALIDATE_DOMAIN));
+      } else {
+        echo "<br>Pas un domaine !!<br>";
+      }
+
+      echo "<br>Formulaire traité !!";
+    } else {
+      echo "<br>Rien n'a été reçu lors du dernière chargement";
+    }
+
+    ?>

+ 33 - 0
ht/domain.php

@@ -0,0 +1,33 @@
+<?php require "../top.inc.php"; ?>
+    <p>
+      Ajouter un domaine sur un dossier de site
+    </p>
+
+    <form action="domain.php" method="post">
+      <label for="domain">Domaine sur lequel répondre :</label><br>
+      <input id="domain" name="domain" type="text"/><br>
+      <label for="dir">Dossier ciblé :</label><br>
+      <input id="dir" name="dir" type="text"/><br>
+
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_POST['domain']) AND isset($_POST['dir']) AND isset($_SESSION['username'])) {
+
+      $conf = file_get_contents("/etc/nginx/hyper.d/dns.template");
+      $conf = preg_replace("#DOMAIN#", $_POST['domain'], $conf);
+      $conf = preg_replace("#DIR#", $_POST['dir'], $conf);
+      $conf = preg_replace("#USER#", $_SESSION['username'], $conf);
+      file_put_contents("/etc/nginx/hyper.d/" . $_POST['domain'] . ".conf", $conf);
+      exec("sudo /root/maniver/target/debug/maniver reload-nginx");
+      //certbot certonly --nginx -d testcrabe.atope.art
+      echo "Formulaire traité !!";
+    } else {
+      echo "Rien n'a été reçu lors du dernier chargement";
+    }
+
+    ?>
+
+<?php require "../bottom.inc.php"; ?>

+ 23 - 0
ht/index.php

@@ -0,0 +1,23 @@
+<?php require "../top.inc.php"; ?>
+
+    Vous pouvez vous connecter avec <code>sftp <?= $_SESSION['username'] ?>@serveur</code> et téléverser votre site dans <code>/hyper/&nbsp;nom de votre site&nbsp;</code>.
+    <br><br>
+    Voici les SHA256 des clés publiques su serveur :
+
+    <br><br>Ed25519 :
+    <br><code>MHwU49oafgq4jY6whUy2INWHMrs+uz4A0j+gsQEgho8</code>
+    <br><br>RSA :
+    <br><code>6wWSPLxqns4ZKtnqzv7ch3k/R2ztPgDiCr4c0B/I/mw</code>
+    <br><br>ECDSA :
+    <br><code>XMwGgdngT+MZPlndX7rB9CchjPRiJD3SPHKj18qYcPA</code>
+
+    <br><br>N'acceptez pas la connexion si elles ne correspondent pas !
+
+    <br><br>
+    <a href="onion.php">Ajouter un accès en .onion sur un dossier</a>
+    <br>
+    <a href="domain.php">Ajouter un accès par domaine sur un dossier</a>
+    <br>
+    <a href="le.php">Installer un certificat Let's Encrypt sur un domaine</a>
+
+<?php require "../bottom.inc.php"; ?>

+ 34 - 0
ht/le.php

@@ -0,0 +1,34 @@
+<?php require "../top.inc.php"; ?>
+    <p>
+      Installer un certificat Let's Encrypt
+    </p>
+
+    <form action="le.php" method="post">
+      <label for="domain">Domaine sur lequel installer le certificat :</label><br>
+      <input id="domain" name="domain" type="text"/><br>
+
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_POST['domain'])) {
+
+      exec("sudo /root/maniver/target/debug/maniver le-install " . $_POST['domain'], $output);
+      echo "<pre>";
+      print_r($output);
+      echo "</pre>";
+      $conf = file_get_contents("/etc/nginx/hyper.d/" . $_POST['domain'] . ".conf");
+      $conf = preg_replace("#host\.atope\.art#", $_POST['domain'], $conf);
+      file_put_contents("/etc/nginx/hyper.d/" . $_POST['domain'] . ".conf", $conf);
+
+      exec("sudo /root/maniver/target/debug/maniver reload-nginx");
+
+      echo "Formulaire traité !!";
+    } else {
+      echo "Rien n'a été reçu lors du dernier chargement";
+    }
+
+    ?>
+
+<?php require "../bottom.inc.php"; ?>

+ 27 - 0
ht/mkdir.php

@@ -0,0 +1,27 @@
+<?php require "../top.inc.php"; ?>
+    <p>
+      Ici vous pouvez héberger des contenus hypertexte
+    </p>
+
+    <form action="mkdir.php" method="post">
+      <label for="dir">Nom du dossier à créer :</label><br>
+      <input id="dir" name="dir" type="text"/><br>
+
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_SESSION['username']) AND isset($_POST['dir'])) {
+
+      // Setup SFTP access
+      exec("mkdir /srv/hyper/" . $_SESSION['username'] . "/" . $_POST['dir']);
+
+      echo "Formulaire traité !!";
+    } else {
+      echo "Rien n'a été reçu lors du dernier chargement";
+    }
+
+    ?>
+
+<?php require "../bottom.inc.php"; ?>

+ 52 - 0
ht/onion.php

@@ -0,0 +1,52 @@
+<?php require "../top.inc.php"; ?>
+    <p>
+      Ajouter un accès en .onion sur un dossier
+    </p>
+
+    <form action="onion.php" method="post">
+      <label for="dir">Dossier ciblé</label><br>
+      <input id="dir" name="dir" type="text"/><br>
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_POST['dir']) AND isset($_SESSION['username'])) {
+
+      // Generate a .onion address
+      $torConf = file_get_contents("/etc/tor/torrc");
+      $torConf = $torConf . "\nHiddenServiceDir /var/lib/tor/niver/" . $_POST['dir'] . "/\nHiddenServicePort 80 [::1]:80";
+      file_put_contents("/etc/tor/torrc", $torConf);
+
+      exec("sudo -u root /root/maniver/target/debug/maniver reload-tor", $output1);
+      echo "<pre>";
+      print_r($output1);
+      echo "</pre>";
+      sleep(3);
+
+      exec("sudo -u root /root/maniver/target/debug/maniver export-tor " . $_SESSION['username'] . " " . $_POST['dir'], $output2);
+      echo "<pre>";
+      print_r($output2);
+      echo "</pre>";
+      sleep(3);
+
+      // Add this address to Nginx
+      $onion = file_get_contents("/srv/hyper/" . $_SESSION['username'] . "/hyper/" . $_POST['dir'] . "/hostname");
+      $onion = str_replace(array("\r","\n"), "", $onion);
+      echo "START" . $onion . "STOP";
+      $nginxConf = file_get_contents("/etc/nginx/hyper.d/onion.template");
+      $nginxConf = preg_replace("#DOMAIN#", $onion, $nginxConf);
+      $nginxConf = preg_replace("#DIR#", $_POST['dir'], $nginxConf);
+      $nginxConf = preg_replace("#USER#", $_SESSION['username'], $nginxConf);
+      file_put_contents("/etc/nginx/hyper.d/" . $_POST['dir'] . ".conf", $nginxConf);
+
+      exec("sudo /root/maniver/target/debug/maniver reload-nginx");
+
+      echo "Formulaire traité !!";
+    } else {
+      echo "Rien n'a été reçu lors du dernier chargement";
+    }
+
+    ?>
+
+<?php require "../bottom.inc.php"; ?>

+ 27 - 0
ht/setup.php

@@ -0,0 +1,27 @@
+<?php require "../top.inc.php"; ?>
+    <p>
+    </p>
+
+    <form action="setup.php" method="post">
+
+      <label for="password">Créer ce mot de passe :</label><br>
+      <input id="password" name="password" type="password"/><br>
+
+      <input type="submit"/>
+    </form>
+
+    <?php
+
+    if (isset($_SESSION['username']) AND isset($_POST['password'])) {
+
+      // Setup SFTP access
+      exec("sudo /root/maniver/target/debug/maniver setup-user " . $_SESSION['username'] . " " . $_POST['password']);
+
+      echo "Formulaire traité !!";
+    } else {
+      echo "Rien n'a été reçu lors du dernier chargement";
+    }
+
+    ?>
+
+<?php require "../bottom.inc.php"; ?>

+ 21 - 0
index.php

@@ -0,0 +1,21 @@
+<?php require "top.inc.php"; ?>
+
+<h2><a style="color: <?= $purple ?>" href="nic">Registre atope.art</a></h2>
+
+Demander l'attribution d'un sous-domaine d'atope.art
+<br>
+<a style="color: <?= $purple ?>" class="button" href="nic">&gt; Enregistrer un domaine</a>
+
+<h2><a style="color: <?= $cyan ?>" href="ns">Serveurs de noms</a></h2>
+
+Utiliser les serveurs ns*.atope.art pour héberger ses zones de domaines
+
+<h2><a style="color: <?= $red ?>" href="ht">Hypertexte</a></h2>
+
+Mettre en ligne son site statique sur un espace SFTP, et le faire répondre sur des domaines ou par Tor
+
+<h2><a style="color: <?= $green ?>" href="auth">Authentification</a></h2>
+
+Gérer son compte Niver
+
+<?php require "bottom.inc.php"; ?>

+ 13 - 0
less/buttons.less

@@ -0,0 +1,13 @@
+
+.button {
+  border-width: 2px;
+  border-style: solid;
+  text-decoration: none;
+  border-radius: 10px;
+  padding: 8px;
+}
+
+.htButton {
+  .button();
+  border-color: red;
+}

+ 56 - 0
less/form.less

@@ -0,0 +1,56 @@
+
+form {
+  margin-left: 50px;
+  margin-right: 50px;
+  text-align: center;
+  @media (max-width: 500px) {
+    margin-left: 0px;
+    margin-right: 0px;
+  }
+}
+
+input {
+  border-radius: 12px;
+  height: 30px;
+  font-size: @fontSize;
+  font-family: monospace;
+  margin: 5px;
+  height: 100%;
+  padding: 10px;
+
+}
+
+input[type=submit] {
+  margin: 22px;
+}
+
+@media (prefers-color-scheme: light) {
+  input {
+    background-color: @color1;
+    color: @color2;
+    border: 2px solid @color2;
+    &:hover {
+      box-shadow: 0px 0px 10px @color2;
+    }
+    &:focus {
+      box-shadow: 0px 0px 15px @mainColor;
+      border: 2px @mainColor solid;
+    }
+  }
+}
+
+@media (prefers-color-scheme: dark) {
+
+  input {
+    background-color: @color2;
+    color: @color1;
+    border: 2px solid @color1;
+    &:hover {
+      box-shadow: 0px 0px 10px @color1;
+    }
+    &:focus {
+      box-shadow: 0px 0px 15px @mainColor;
+      border: 2px @mainColor solid;
+    }
+  }
+}

+ 42 - 0
less/main.less

@@ -0,0 +1,42 @@
+@fontSize: 26px;
+
+html {
+  font-family: system-ui;
+  margin-left: 35%;
+  margin-right: 35%;
+  font-size: @fontSize;
+  @media (max-width: 500px) {
+    margin-left: 20px;
+    margin-right: 20px;
+  }
+}
+
+
+header {
+  text-align: center;
+}
+
+a {
+  color: @mainColor;
+}
+
+.button {
+  border: 2px red solid;
+  text-decoration: none;
+  border-radius: 10px;
+  padding: 8px;
+}
+
+@media (prefers-color-scheme: light) {
+  html {
+    background-color: @color1;
+    color: @color2;
+  }
+}
+
+@media (prefers-color-scheme: dark) {
+  html {
+    background-color: @color2;
+    color: @color1;
+  }
+}

+ 70 - 0
lessphp/CHANGES.md

@@ -0,0 +1,70 @@
+# 3.1.0
+- [All Changes](https://github.com/wikimedia/less.php/compare/v3.0.0...v3.1.0)
+* PHP 8.0 support: Drop use of curly braces for sub-string eval (James D. Forrester)
+* Make `Directive::__construct` $rules arg optional (fix PHP 7.4 warning) (Sam Reed)
+* ProcessExtends: Improve performance by using a map for selectors and parents (Andrey Legayev)
+* build: Run CI tests on PHP 8.0 too (James D. Forrester)
+* code: Fix PSR12.Properties.ConstantVisibility.NotFound (Sam Reed)
+
+# 3.0.0
+- [All Changes](https://github.com/wikimedia/less.php/compare/v2.0.0...v3.0.0)
+- Raise PHP requirement from 7.1 to 7.2.9 (James Forrester)
+- build: Upgrade phpunit to ^8.5 and make pass  (James Forrester)
+- build: Install php-parallel-lint  (James Forrester)
+- build: Install minus-x and make pass  (James Forrester)
+
+# 2.0.0
+- [All Changes](https://github.com/wikimedia/less.php/compare/1.8.2...v2.0.0)
+- Relax PHP requirement down to 7.1, from 7.2.9 (Franz Liedke)
+- Reflect recent breaking changes properly with the semantic versioning (James Forrester)
+
+# 1.8.2
+- [All Changes](https://github.com/wikimedia/less.php/compare/1.8.1...1.8.2)
+- Require PHP 7.2.9+, up from 5.3+ (James Forrester)
+- Release: Update Version.php with the current release ID (COBadger)
+- Fix access array offset on value of type null (Michele Locati)
+- Fixed test suite on PHP 7.4 (Sergei Morozov)
+- docs: Fix 1.8.1 "All changes" link (Timo Tijhof)
+
+# 1.8.1
+- [All Changes](https://github.com/wikimedia/less.php/compare/v1.8.0...1.8.1)
+- Another PHP 7.3 compatibility tweak
+
+# 1.8.0
+- [All Changes](https://github.com/Asenar/less.php/compare/v1.7.0.13...v1.8.0)
+- Wikimedia fork
+- Supports up to PHP 7.3
+- No longer tested against PHP 5, though it's still remains allowed in `composer.json` for HHVM compatibility
+- Switched to [semantic versioning](https://semver.org/), hence version numbers now use 3 digits
+
+# 1.7.0.13
+ - [All Changes](https://github.com/Asenar/less.php/compare/v1.7.0.12...v1.7.0.13)
+ - Fix composer.json (PSR-4 was invalid)
+
+# 1.7.0.12
+ - [All Changes](https://github.com/Asenar/less.php/compare/v1.7.0.11...v1.7.0.12)
+ - set bin/lessc bit executable
+ - Add 'gettingVariables' method in Less_Parser
+
+# 1.7.0.11
+ - [All Changes](https://github.com/Asenar/less.php/compare/v1.7.0.10...v1.7.0.11)
+ - Fix realpath issue (windows)
+ - Set Less_Tree_Call property back to public ( Fix 258 266 267 issues from oyejorge/less.php)
+
+# 1.7.0.10
+
+ - [All Changes](https://github.com/oyejorge/less.php/compare/v1.7.0.9...v1.7.10)
+ - Add indentation option
+ - Add 'optional' modifier for @import
+ - fix $color in Exception messages
+ - don't use set_time_limit when running cli
+ - take relative-url into account when building the cache filename
+ - urlArgs should be string no array()
+ - add bug-report fixtures [#6dc898f](https://github.com/oyejorge/less.php/commit/6dc898f5d75b447464906bdf19d79c2e19d95e33)
+ - fix #269, missing on NameValue type [#a8dac63](https://github.com/oyejorge/less.php/commit/a8dac63d93fb941c54fb78b12588abf635747c1b)
+
+# 1.7.0.9
+
+ - [All Changes](https://github.com/oyejorge/less.php/compare/v1.7.0.8...v1.7.0.9)
+ - Remove space at beginning of Version.php
+ - Revert require() paths in test interface

+ 178 - 0
lessphp/LICENSE

@@ -0,0 +1,178 @@
+
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+

+ 315 - 0
lessphp/README.md

@@ -0,0 +1,315 @@
+[![Continuous Integration](https://github.com/wikimedia/less.php/workflows/PHP%20Test/badge.svg)](https://github.com/wikimedia/less.php/actions)
+
+[Less.php](http://lessphp.typesettercms.com)
+========
+
+This is the Wikimedia fork of a PHP port of the official LESS processor <http://lesscss.org>.
+
+* [About](#about)
+* [Installation](#installation)
+* [Basic Use](#basic-use)
+* [Caching](#caching)
+* [Source Maps](#source-maps)
+* [Command Line](#command-line)
+* [Integration with other projects](#integration-with-other-projects)
+* [Transitioning from Leafo/lessphp](#transitioning-from-leafolessphp)
+* [Credits](#credits)
+
+
+
+About
+---
+The code structure of less.php mirrors that of the official processor which helps us ensure compatibility and allows for easy maintenance.
+
+Please note, there are a few unsupported LESS features:
+
+- Evaluation of JavaScript expressions within back-ticks (for obvious reasons).
+- Definition of custom functions.
+
+
+Installation
+---
+
+You can install the library with Composer or manually.
+
+#### Composer
+
+1. [Install Composer](https://getcomposer.org/download/)
+2. Run `composer require wikimedia/less.php`
+
+#### Manually From Release
+
+Step 1. [Download a release](https://github.com/wikimedia/less.php/releases) and upload the PHP files to your server.
+
+Step 2. Include the library:
+
+```php
+require_once '[path to less.php]/lib/Less/Autoloader.php';
+Less_Autoloader::register();
+```
+
+Basic Use
+---
+
+#### Parsing Strings
+
+```php
+$parser = new Less_Parser();
+$parser->parse( '@color: #4D926F; #header { color: @color; } h2 { color: @color; }' );
+$css = $parser->getCss();
+```
+
+
+#### Parsing LESS Files
+The parseFile() function takes two arguments:
+
+1. The absolute path of the .less file to be parsed
+2. The url root to prepend to any relative image or @import urls in the .less file.
+
+```php
+$parser = new Less_Parser();
+$parser->parseFile( '/var/www/mysite/bootstrap.less', 'http://example.com/mysite/' );
+$css = $parser->getCss();
+```
+
+
+#### Handling Invalid LESS
+An exception will be thrown if the compiler encounters invalid LESS.
+
+```php
+try{
+	$parser = new Less_Parser();
+	$parser->parseFile( '/var/www/mysite/bootstrap.less', 'http://example.com/mysite/' );
+	$css = $parser->getCss();
+}catch(Exception $e){
+	$error_message = $e->getMessage();
+}
+```
+
+
+#### Parsing Multiple Sources
+less.php can parse multiple sources to generate a single CSS file.
+
+```php
+$parser = new Less_Parser();
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$parser->parse( '@color: #4D926F; #header { color: @color; } h2 { color: @color; }' );
+$css = $parser->getCss();
+```
+
+#### Getting Info About The Parsed Files
+less.php can tell you which .less files were imported and parsed.
+
+```php
+$parser = new Less_Parser();
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+$imported_files = $parser->allParsedFiles();
+```
+
+
+#### Compressing Output
+You can tell less.php to remove comments and whitespace to generate minimized CSS files.
+
+```php
+$options = array( 'compress'=>true );
+$parser = new Less_Parser( $options );
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+#### Getting Variables
+You can use the getVariables() method to get an all variables defined and
+their value in a php associative array. Note that LESS has to be previously
+compiled.
+```php
+$parser = new Less_Parser;
+$parser->parseFile( '/var/www/mysite/bootstrap.less');
+$css = $parser->getCss();
+$variables = $parser->getVariables();
+
+```
+
+
+
+#### Setting Variables
+You can use the ModifyVars() method to customize your CSS if you have variables stored in PHP associative arrays.
+
+```php
+$parser = new Less_Parser();
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$parser->ModifyVars( array('font-size-base'=>'16px') );
+$css = $parser->getCss();
+```
+
+
+#### Import Directories
+By default, less.php will look for @imports in the directory of the file passed to parseFile().
+If you're using parse() or if @imports reside in different directories, you can tell less.php where to look.
+
+```php
+$directories = array( '/var/www/mysite/bootstrap/' => '/mysite/bootstrap/' );
+$parser = new Less_Parser();
+$parser->SetImportDirs( $directories );
+$parser->parseFile( '/var/www/mysite/theme.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+
+Caching
+---
+Compiling LESS code into CSS is a time consuming process, caching your results is highly recommended.
+
+
+#### Caching CSS
+Use the Less_Cache class to save and reuse the results of compiled LESS files.
+This method will check the modified time and size of each LESS file (including imported files) and regenerate a new CSS file when changes are found.
+Note: When changes are found, this method will return a different file name for the new cached content.
+
+```php
+$less_files = array( '/var/www/mysite/bootstrap.less' => '/mysite/' );
+$options = array( 'cache_dir' => '/var/www/writable_folder' );
+$css_file_name = Less_Cache::Get( $less_files, $options );
+$compiled = file_get_contents( '/var/www/writable_folder/'.$css_file_name );
+```
+
+#### Caching CSS With Variables
+Passing options to Less_Cache::Get()
+
+```php
+$less_files = array( '/var/www/mysite/bootstrap.less' => '/mysite/' );
+$options = array( 'cache_dir' => '/var/www/writable_folder' );
+$variables = array( 'width' => '100px' );
+$css_file_name = Less_Cache::Get( $less_files, $options, $variables );
+$compiled = file_get_contents( '/var/www/writable_folder/'.$css_file_name );
+```
+
+
+#### Parser Caching
+less.php will save serialized parser data for each .less file if a writable folder is passed to the SetCacheDir() method.
+Note: This feature only caches intermediate parsing results to improve the performance of repeated CSS generation.
+Your application should cache any CSS generated by less.php.
+
+```php
+$options = array('cache_dir'=>'/var/www/writable_folder');
+$parser = new Less_Parser( $options );
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+You can specify the caching technique used by changing the ```cache_method``` option. Supported methods are:
+* ```php```: Creates valid PHP files which can be included without any changes (default method).
+* ```var_export```: Like "php", but using PHP's ```var_export()``` function without any optimizations.
+  It's recommended to use "php" instead.
+* ```serialize```: Faster, but pretty memory-intense.
+* ```callback```: Use custom callback functions to implement your own caching method. Give the "cache_callback_get" and
+  "cache_callback_set" options with callables (see PHP's ```call_user_func()``` and ```is_callable()``` functions). less.php
+  will pass the parser object (class ```Less_Parser```), the path to the parsed .less file ("/some/path/to/file.less") and
+  an identifier that will change every time the .less file is modified. The ```get``` callback must return the ruleset
+  (an array with ```Less_Tree``` objects) provided as fourth parameter of the ```set``` callback. If something goes wrong,
+  return ```NULL``` (cache doesn't exist) or ```FALSE```.
+
+
+
+Source Maps
+---
+Less.php supports v3 sourcemaps
+
+#### Inline
+The sourcemap will be appended to the generated CSS file.
+
+```php
+$options = array( 'sourceMap' => true );
+$parser = new Less_Parser($options);
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+#### Saving to Map File
+
+```php
+$options = array(
+	'sourceMap'			=> true,
+	'sourceMapWriteTo'	=> '/var/www/mysite/writable_folder/filename.map',
+	'sourceMapURL'		=> '/mysite/writable_folder/filename.map',
+	);
+$parser = new Less_Parser($options);
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+
+Command line
+---
+An additional script has been included to use the compiler from the command line.
+In the simplest invocation, you specify an input file and the compiled CSS is written to standard out:
+
+```
+$ lessc input.less > output.css
+```
+
+By using the -w flag you can watch a specified input file and have it compile as needed to the output file:
+
+```
+$ lessc -w input.less output.css
+```
+
+Errors from watch mode are written to standard out.
+
+For more help, run `lessc --help`
+
+
+Integration with other projects
+---
+
+#### Drupal 7
+
+This library can be used as drop-in replacement of lessphp to work with [Drupal 7 less module](https://drupal.org/project/less).
+
+How to install:
+
+1. [Download the less.php source code](https://github.com/wikimedia/less.php/archive/master.zip) and unzip it so that 'lessc.inc.php' is located at 'sites/all/libraries/lessphp/lessc.inc.php'.
+2. Download and install [Drupal 7 less module](https://drupal.org/project/less) as usual.
+3. That's it :)
+
+#### JBST WordPress theme
+
+JBST has a built-in LESS compiler based on lessphp. Customize your WordPress theme with LESS.
+
+How to use / install:
+
+1. [Download the latest release](https://github.com/bassjobsen/jamedo-bootstrap-start-theme) copy the files to your {wordpress/}wp-content/themes folder and activate it.
+2. Find the compiler under Appearance > LESS Compiler in your WordPress dashboard
+3. Enter your LESS code in the text area and press (re)compile
+
+Use the built-in compiler to:
+- set any [Bootstrap](http://getbootstrap.com/customize/) variable or use Bootstrap's mixins:
+	-`@navbar-default-color: blue;`
+        - create a custom button: `.btn-custom {
+  .button-variant(white; red; blue);
+}`
+- set any built-in LESS variable: for example `@footer_bg_color: black;` sets the background color of the footer to black
+- use built-in mixins: - add a custom font: `.include-custom-font(@family: arial,@font-path, @path: @custom-font-dir, @weight: normal, @style: normal);`
+
+The compiler can also be downloaded as [plugin](http://wordpress.org/plugins/wp-less-to-css/)
+
+#### WordPress
+
+This simple plugin will simply make the library available to other plugins and themes and can be used as a dependency using the [TGM Library](http://tgmpluginactivation.com/)
+
+How to install:
+
+1. Install the plugin from your WordPress Dashboard: http://wordpress.org/plugins/lessphp/
+2. That's it :)
+
+
+Transitioning from Leafo/lessphp
+---
+Projects looking for an easy transition from leafo/lessphp can use the lessc.inc.php adapter. To use, [Download the less.php source code](https://github.com/wikimedia/less.php/archive/master.zip) and unzip the files into your project so that the new 'lessc.inc.php' replaces the existing 'lessc.inc.php'.
+
+Note, the 'setPreserveComments' will no longer have any effect on the compiled LESS.
+
+Credits
+---
+less.php was originally ported to PHP by [Matt Agar](https://github.com/agar) and then updated by [Martin Jantošovič](https://github.com/Mordred). This Wikimedia-maintained fork was split off from [Josh Schmidt's version](https://github.com/oyejorge/less.php).
+

+ 191 - 0
lessphp/bin/lessc

@@ -0,0 +1,191 @@
+#!/usr/bin/env php
+<?php
+
+require_once dirname(__FILE__) . '/../lib/Less/Autoloader.php';
+Less_Autoloader::register();
+
+// Create our environment
+$env		= array('compress' => false, 'relativeUrls' => false);
+$silent		= false;
+$watch		= false;
+$rootpath	= '';
+
+// Check for arguments
+array_shift($argv);
+if (!count($argv)) {
+	$argv[] = '-h';
+}
+
+// parse arguments
+foreach ($argv as $key => $arg) {
+	if (preg_match('/^--?([a-z][0-9a-z-]*)(?:=([^\s]+))?$/i', $arg, $matches)) {
+		$option = $matches[1];
+		$value = isset($matches[2]) ? $matches[2] : false;
+		unset($argv[$key]);
+
+		switch ($option) {
+			case 'h':
+			case 'help':
+				echo <<<EOD
+Usage: lessc [options] sources [destination]
+
+ -h, --help            Print help (this message) and exit.
+ -s, --silent          Suppress output of error messages.
+ -v, --version         Print version number and exit.
+ -x, --compress        Compress output by removing some whitespaces.
+ --include-path=PATHS  Set include paths. Separated by `:'. Use `;' on Windows.
+ --strict-imports      Force evaluation of imports.
+ -sm=on|off            Turn on or off strict math, where in strict mode, math
+ --strict-math=on|off  requires brackets. This option may default to on and then
+                       be removed in the future.
+ -su=on|off            Allow mixed units, e.g. 1px+1em or 1px*1px which have units
+ --strict-units=on|off that cannot be represented.
+ -ru, --relative-urls  re-write relative urls to the base less file.
+ -rp, --rootpath=URL   Set rootpath for url rewriting in relative imports and urls.
+                       Works with or without the relative-urls option.
+ -w, --watch           Watch input files for changes.
+
+
+EOD;
+				exit;
+			case 's':
+			case 'silent':
+				$silent = true;
+				break;
+
+			case 'w':
+			case 'watch':
+				$watch = true;
+				break;
+
+			case 'v':
+			case 'version':
+				echo "lessc " . Less_Version::version . " (less.php)\n\n";
+				exit;
+
+			case 'rp':
+			case 'rootpath':
+				$rootpath = $value;
+				break;
+
+
+			//parser options
+			case 'compress':
+				$env['compress'] = true;
+				break;
+
+			case 'ru':
+			case 'relative-urls':
+				$env['relativeUrls'] = true;
+				break;
+
+			case 'su':
+			case 'strict-units':
+				$env['strictUnits'] = ($value === 'on');
+				break;
+
+			case 'sm':
+			case 'strict-math':
+				$env['strictMath'] = ($value === 'on');
+				break;
+
+			case 'x':
+			case 'include-path':
+				$env['import_dirs'] = preg_split('#;|\:#', $value);
+				break;
+
+		}
+	}
+}
+
+if (count($argv) > 1) {
+	$output = array_pop($argv);
+	$inputs = $argv;
+}
+else {
+	$inputs = $argv;
+	$output = false;
+}
+
+if (!count($inputs)) {
+	echo("lessc: no input files\n");
+	exit;
+}
+
+if ($watch) {
+	if (!$output) {
+		echo("lessc: you must specify the output file if --watch is given\n");
+		exit;
+	}
+
+	$lastAction = 0;
+
+	echo("lessc: watching input files\n");
+
+	while (1) {
+		clearstatcache();
+
+		$updated = false;
+		foreach ($inputs as $input) {
+			if ($input == '-') {
+				if (count($inputs) == 1) {
+					echo("lessc: during watching files is not possible to watch stdin\n");
+					exit;
+				}
+				else {
+					continue;
+				}
+			}
+
+			if (filemtime($input) > $lastAction) {
+				$updated = true;
+				break;
+			}
+		}
+
+		if ($updated) {
+			$lastAction = time();
+			$parser = new Less_Parser($env);
+			foreach ($inputs as $input) {
+				try {
+					$parser->parseFile($input, $rootpath);
+				}
+				catch (Exception $e) {
+					echo("lessc: " . $e->getMessage() . " \n");
+					continue; // Invalid processing
+				}
+			}
+
+			file_put_contents($output, $parser->getCss());
+			echo("lessc: output file recompiled\n");
+		}
+
+		sleep(1);
+	}
+}
+else {
+	$parser = new Less_Parser($env);
+	foreach ($inputs as $input) {
+		if ($input == '-') {
+			$content = file_get_contents('php://stdin');
+			$parser->parse($content);
+		}
+		else {
+			try {
+				$parser->parseFile($input);
+			}
+			catch (Exception $e) {
+				if (!$silent) {
+					echo("lessc: " . ((string)$e) . " \n");
+				}
+			}
+		}
+	}
+
+	if ($output) {
+		file_put_contents($output, $parser->getCss());
+	}
+	else {
+		echo $parser->getCss();
+	}
+}

+ 49 - 0
lessphp/composer.json

@@ -0,0 +1,49 @@
+{
+	"name": "wikimedia/less.php",
+	"description": "PHP port of the Javascript version of LESS http://lesscss.org (Originally maintained by Josh Schmidt)",
+	"keywords": [ "less", "css", "php", "stylesheet", "less.js", "lesscss" ],
+	"license": "Apache-2.0",
+	"authors": [
+		{
+			"name": "Josh Schmidt",
+			"homepage": "https://github.com/oyejorge"
+		},
+		{
+			"name": "Matt Agar",
+			"homepage": "https://github.com/agar"
+		},
+		{
+			"name": "Martin Jantošovič",
+			"homepage": "https://github.com/Mordred"
+		}
+	],
+	"require": {
+		"php": ">=7.2.9"
+	},
+	"require-dev": {
+		"mediawiki/mediawiki-codesniffer": "34.0.0",
+		"mediawiki/minus-x": "1.0.0",
+		"php-parallel-lint/php-console-highlighter": "0.5.0",
+		"php-parallel-lint/php-parallel-lint": "1.2.0",
+		"phpunit/phpunit": "^8.5"
+	},
+	"scripts": {
+		"test": [
+			"parallel-lint . --exclude vendor",
+			"phpcs -sp",
+			"phpunit",
+			"minus-x check ."
+		],
+		"fix": [
+			"minus-x fix .",
+			"phpcbf"
+		]
+	},
+	"autoload": {
+		"psr-0": { "Less": "lib/" },
+		"classmap": ["lessc.inc.php"]
+	},
+	"bin": [
+		"bin/lessc"
+	]
+}

+ 274 - 0
lessphp/lessc.inc.php

@@ -0,0 +1,274 @@
+<?php
+/**
+ * This file provides the part of lessphp API (https://github.com/leafo/lessphp)
+ * to be a drop-in replacement for following products:
+ *  - Drupal 7, by the less module v3.0+ (https://drupal.org/project/less)
+ *  - Symfony 2
+ */
+
+// Register autoloader for non-composer installations
+if ( !class_exists( 'Less_Parser' ) ) {
+	require_once __DIR__ . '/lib/Less/Autoloader.php';
+	Less_Autoloader::register();
+}
+
+class lessc {
+
+	static public $VERSION = Less_Version::less_version;
+
+	public $importDir = '';
+	protected $allParsedFiles = array();
+	protected $libFunctions = array();
+	protected $registeredVars = array();
+	private $formatterName;
+	private $options = array();
+
+	public function __construct( $lessc = null, $sourceName = null ) {
+	}
+
+	public function setImportDir( $dirs ) {
+		$this->importDir = (array)$dirs;
+	}
+
+	public function addImportDir( $dir ) {
+		$this->importDir = (array)$this->importDir;
+		$this->importDir[] = $dir;
+	}
+
+	public function setFormatter( $name ) {
+		$this->formatterName = $name;
+	}
+
+	public function setPreserveComments( $preserve ) {
+	}
+
+	public function registerFunction( $name, $func ) {
+		$this->libFunctions[$name] = $func;
+	}
+
+	public function unregisterFunction( $name ) {
+		unset( $this->libFunctions[$name] );
+	}
+
+	public function setVariables( $variables ) {
+		foreach ( $variables as $name => $value ) {
+			$this->setVariable( $name, $value );
+		}
+	}
+
+	public function setVariable( $name, $value ) {
+		$this->registeredVars[$name] = $value;
+	}
+
+	public function unsetVariable( $name ) {
+		unset( $this->registeredVars[$name] );
+	}
+
+	public function setOptions( $options ) {
+		foreach ( $options as $name => $value ) {
+			$this->setOption( $name, $value );
+		}
+	}
+
+	public function setOption( $name, $value ) {
+		$this->options[$name] = $value;
+	}
+
+	public function parse( $buffer, $presets = array() ) {
+		$this->setVariables( $presets );
+
+		$parser = new Less_Parser( $this->getOptions() );
+		$parser->setImportDirs( $this->getImportDirs() );
+		foreach ( $this->libFunctions as $name => $func ) {
+			$parser->registerFunction( $name, $func );
+		}
+		$parser->parse( $buffer );
+		if ( count( $this->registeredVars ) ) {
+			$parser->ModifyVars( $this->registeredVars );
+		}
+
+		return $parser->getCss();
+	}
+
+	protected function getOptions() {
+		$options = array( 'relativeUrls' => false );
+		switch ( $this->formatterName ) {
+			case 'compressed':
+				$options['compress'] = true;
+				break;
+		}
+		if ( is_array( $this->options ) ) {
+			$options = array_merge( $options, $this->options );
+		}
+		return $options;
+	}
+
+	protected function getImportDirs() {
+		$dirs_ = (array)$this->importDir;
+		$dirs = array();
+		foreach ( $dirs_ as $dir ) {
+			$dirs[$dir] = '';
+		}
+		return $dirs;
+	}
+
+	public function compile( $string, $name = null ) {
+		$oldImport = $this->importDir;
+		$this->importDir = (array)$this->importDir;
+
+		$this->allParsedFiles = array();
+
+		$parser = new Less_Parser( $this->getOptions() );
+		$parser->SetImportDirs( $this->getImportDirs() );
+		if ( count( $this->registeredVars ) ) {
+			$parser->ModifyVars( $this->registeredVars );
+		}
+		foreach ( $this->libFunctions as $name => $func ) {
+			$parser->registerFunction( $name, $func );
+		}
+		$parser->parse( $string );
+		$out = $parser->getCss();
+
+		$parsed = Less_Parser::AllParsedFiles();
+		foreach ( $parsed as $file ) {
+			$this->addParsedFile( $file );
+		}
+
+		$this->importDir = $oldImport;
+
+		return $out;
+	}
+
+	public function compileFile( $fname, $outFname = null ) {
+		if ( !is_readable( $fname ) ) {
+			throw new Exception( 'load error: failed to find '.$fname );
+		}
+
+		$pi = pathinfo( $fname );
+
+		$oldImport = $this->importDir;
+
+		$this->importDir = (array)$this->importDir;
+		$this->importDir[] = Less_Parser::AbsPath( $pi['dirname'] ).'/';
+
+		$this->allParsedFiles = array();
+		$this->addParsedFile( $fname );
+
+		$parser = new Less_Parser( $this->getOptions() );
+		$parser->SetImportDirs( $this->getImportDirs() );
+		if ( count( $this->registeredVars ) ) {
+			$parser->ModifyVars( $this->registeredVars );
+		}
+		foreach ( $this->libFunctions as $name => $func ) {
+			$parser->registerFunction( $name, $func );
+		}
+		$parser->parseFile( $fname );
+		$out = $parser->getCss();
+
+		$parsed = Less_Parser::AllParsedFiles();
+		foreach ( $parsed as $file ) {
+			$this->addParsedFile( $file );
+		}
+
+		$this->importDir = $oldImport;
+
+		if ( $outFname !== null ) {
+			return file_put_contents( $outFname, $out );
+		}
+
+		return $out;
+	}
+
+	public function checkedCompile( $in, $out ) {
+		if ( !is_file( $out ) || filemtime( $in ) > filemtime( $out ) ) {
+			$this->compileFile( $in, $out );
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Execute lessphp on a .less file or a lessphp cache structure
+	 *
+	 * The lessphp cache structure contains information about a specific
+	 * less file having been parsed. It can be used as a hint for future
+	 * calls to determine whether or not a rebuild is required.
+	 *
+	 * The cache structure contains two important keys that may be used
+	 * externally:
+	 *
+	 * compiled: The final compiled CSS
+	 * updated: The time (in seconds) the CSS was last compiled
+	 *
+	 * The cache structure is a plain-ol' PHP associative array and can
+	 * be serialized and unserialized without a hitch.
+	 *
+	 * @param mixed $in Input
+	 * @param bool $force Force rebuild?
+	 * @return array lessphp cache structure
+	 */
+	public function cachedCompile( $in, $force = false ) {
+		// assume no root
+		$root = null;
+
+		if ( is_string( $in ) ) {
+			$root = $in;
+		} elseif ( is_array( $in ) and isset( $in['root'] ) ) {
+			if ( $force or !isset( $in['files'] ) ) {
+				// If we are forcing a recompile or if for some reason the
+				// structure does not contain any file information we should
+				// specify the root to trigger a rebuild.
+				$root = $in['root'];
+			} elseif ( isset( $in['files'] ) and is_array( $in['files'] ) ) {
+				foreach ( $in['files'] as $fname => $ftime ) {
+					if ( !file_exists( $fname ) or filemtime( $fname ) > $ftime ) {
+						// One of the files we knew about previously has changed
+						// so we should look at our incoming root again.
+						$root = $in['root'];
+						break;
+					}
+				}
+			}
+		} else {
+			// TODO: Throw an exception? We got neither a string nor something
+			// that looks like a compatible lessphp cache structure.
+			return null;
+		}
+
+		if ( $root !== null ) {
+			// If we have a root value which means we should rebuild.
+			$out = array();
+			$out['root'] = $root;
+			$out['compiled'] = $this->compileFile( $root );
+			$out['files'] = $this->allParsedFiles();
+			$out['updated'] = time();
+			return $out;
+		} else {
+			// No changes, pass back the structure
+			// we were given initially.
+			return $in;
+		}
+	}
+
+	public function ccompile( $in, $out, $less = null ) {
+		if ( $less === null ) {
+			$less = new self;
+		}
+		return $less->checkedCompile( $in, $out );
+	}
+
+	public static function cexecute( $in, $force = false, $less = null ) {
+		if ( $less === null ) {
+			$less = new self;
+		}
+		return $less->cachedCompile( $in, $force );
+	}
+
+	public function allParsedFiles() {
+		return $this->allParsedFiles;
+	}
+
+	protected function addParsedFile( $file ) {
+		$this->allParsedFiles[Less_Parser::AbsPath( $file )] = filemtime( $file );
+	}
+}

+ 2 - 0
lessphp/lib/Less/.easymin/ignore_prefixes

@@ -0,0 +1,2 @@
+.easymin
+Autoloader.php

+ 77 - 0
lessphp/lib/Less/Autoloader.php

@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Autoloader
+ *
+ * @package Less
+ * @subpackage autoload
+ */
+class Less_Autoloader {
+
+	/**
+	 * Registered flag
+	 *
+	 * @var boolean
+	 */
+	protected static $registered = false;
+
+	/**
+	 * Library directory
+	 *
+	 * @var string
+	 */
+	protected static $libDir;
+
+	/**
+	 * Register the autoloader in the spl autoloader
+	 *
+	 * @return void
+	 * @throws Exception If there was an error in registration
+	 */
+	public static function register() {
+		if ( self::$registered ) {
+			return;
+		}
+
+		self::$libDir = dirname( __FILE__ );
+
+		if ( false === spl_autoload_register( array( 'Less_Autoloader', 'loadClass' ) ) ) {
+			throw new Exception( 'Unable to register Less_Autoloader::loadClass as an autoloading method.' );
+		}
+
+		self::$registered = true;
+	}
+
+	/**
+	 * Unregisters the autoloader
+	 *
+	 * @return void
+	 */
+	public static function unregister() {
+		spl_autoload_unregister( array( 'Less_Autoloader', 'loadClass' ) );
+		self::$registered = false;
+	}
+
+	/**
+	 * Loads the class
+	 *
+	 * @param string $className The class to load
+	 */
+	public static function loadClass( $className ) {
+		// handle only package classes
+		if ( strpos( $className, 'Less_' ) !== 0 ) {
+			return;
+		}
+
+		$className = substr( $className, 5 );
+		$fileName = self::$libDir . DIRECTORY_SEPARATOR . str_replace( '_', DIRECTORY_SEPARATOR, $className ) . '.php';
+
+		if ( file_exists( $fileName ) ) {
+			require $fileName;
+			return true;
+		} else {
+			throw new Exception( 'file not loadable '.$fileName );
+		}
+	}
+
+}

+ 293 - 0
lessphp/lib/Less/Cache.php

@@ -0,0 +1,293 @@
+<?php
+
+require_once dirname( __FILE__ ).'/Version.php';
+
+/**
+ * Utility for handling the generation and caching of css files
+ *
+ * @package Less
+ * @subpackage cache
+ *
+ */
+class Less_Cache {
+
+	// directory less.php can use for storing data
+	public static $cache_dir	= false;
+
+	// prefix for the storing data
+	public static $prefix		= 'lessphp_';
+
+	// prefix for the storing vars
+	public static $prefix_vars	= 'lessphpvars_';
+
+	// specifies the number of seconds after which data created by less.php will be seen as 'garbage' and potentially cleaned up
+	public static $gc_lifetime	= 604800;
+
+	/**
+	 * Save and reuse the results of compiled less files.
+	 * The first call to Get() will generate css and save it.
+	 * Subsequent calls to Get() with the same arguments will return the same css filename
+	 *
+	 * @param array $less_files Array of .less files to compile
+	 * @param array $parser_options Array of compiler options
+	 * @param array $modify_vars Array of variables
+	 * @return string Name of the css file
+	 */
+	public static function Get( $less_files, $parser_options = array(), $modify_vars = array() ) {
+		// check $cache_dir
+		if ( isset( $parser_options['cache_dir'] ) ) {
+			Less_Cache::$cache_dir = $parser_options['cache_dir'];
+		}
+
+		if ( empty( Less_Cache::$cache_dir ) ) {
+			throw new Exception( 'cache_dir not set' );
+		}
+
+		if ( isset( $parser_options['prefix'] ) ) {
+			Less_Cache::$prefix = $parser_options['prefix'];
+		}
+
+		if ( empty( Less_Cache::$prefix ) ) {
+			throw new Exception( 'prefix not set' );
+		}
+
+		if ( isset( $parser_options['prefix_vars'] ) ) {
+			Less_Cache::$prefix_vars = $parser_options['prefix_vars'];
+		}
+
+		if ( empty( Less_Cache::$prefix_vars ) ) {
+			throw new Exception( 'prefix_vars not set' );
+		}
+
+		self::CheckCacheDir();
+		$less_files = (array)$less_files;
+
+		// create a file for variables
+		if ( !empty( $modify_vars ) ) {
+			$lessvars = Less_Parser::serializeVars( $modify_vars );
+			$vars_file = Less_Cache::$cache_dir . Less_Cache::$prefix_vars . sha1( $lessvars ) . '.less';
+
+			if ( !file_exists( $vars_file ) ) {
+				file_put_contents( $vars_file, $lessvars );
+			}
+
+			$less_files += array( $vars_file => '/' );
+		}
+
+		// generate name for compiled css file
+		$hash = md5( json_encode( $less_files ) );
+		$list_file = Less_Cache::$cache_dir . Less_Cache::$prefix . $hash . '.list';
+
+		// check cached content
+		if ( !isset( $parser_options['use_cache'] ) || $parser_options['use_cache'] === true ) {
+			if ( file_exists( $list_file ) ) {
+
+				self::ListFiles( $list_file, $list, $cached_name );
+				$compiled_name = self::CompiledName( $list, $hash );
+
+				// if $cached_name is the same as the $compiled name, don't regenerate
+				if ( !$cached_name || $cached_name === $compiled_name ) {
+
+					$output_file = self::OutputFile( $compiled_name, $parser_options );
+
+					if ( $output_file && file_exists( $output_file ) ) {
+						@touch( $list_file );
+						return basename( $output_file ); // for backwards compatibility, we just return the name of the file
+					}
+				}
+			}
+		}
+
+		$compiled = self::Cache( $less_files, $parser_options );
+		if ( !$compiled ) {
+			return false;
+		}
+
+		$compiled_name = self::CompiledName( $less_files, $hash );
+		$output_file = self::OutputFile( $compiled_name, $parser_options );
+
+		// save the file list
+		$list = $less_files;
+		$list[] = $compiled_name;
+		$cache = implode( "\n", $list );
+		file_put_contents( $list_file, $cache );
+
+		// save the css
+		file_put_contents( $output_file, $compiled );
+
+		// clean up
+		self::CleanCache();
+
+		return basename( $output_file );
+	}
+
+	/**
+	 * Force the compiler to regenerate the cached css file
+	 *
+	 * @param array $less_files Array of .less files to compile
+	 * @param array $parser_options Array of compiler options
+	 * @param array $modify_vars Array of variables
+	 * @return string Name of the css file
+	 */
+	public static function Regen( $less_files, $parser_options = array(), $modify_vars = array() ) {
+		$parser_options['use_cache'] = false;
+		return self::Get( $less_files, $parser_options, $modify_vars );
+	}
+
+	public static function Cache( &$less_files, $parser_options = array() ) {
+		// get less.php if it exists
+		$file = dirname( __FILE__ ) . '/Less.php';
+		if ( file_exists( $file ) && !class_exists( 'Less_Parser' ) ) {
+			require_once $file;
+		}
+
+		$parser_options['cache_dir'] = Less_Cache::$cache_dir;
+		$parser = new Less_Parser( $parser_options );
+
+		// combine files
+		foreach ( $less_files as $file_path => $uri_or_less ) {
+
+			// treat as less markup if there are newline characters
+			if ( strpos( $uri_or_less, "\n" ) !== false ) {
+				$parser->Parse( $uri_or_less );
+				continue;
+			}
+
+			$parser->ParseFile( $file_path, $uri_or_less );
+		}
+
+		$compiled = $parser->getCss();
+
+		$less_files = $parser->allParsedFiles();
+
+		return $compiled;
+	}
+
+	private static function OutputFile( $compiled_name, $parser_options ) {
+		// custom output file
+		if ( !empty( $parser_options['output'] ) ) {
+
+			// relative to cache directory?
+			if ( preg_match( '#[\\\\/]#', $parser_options['output'] ) ) {
+				return $parser_options['output'];
+			}
+
+			return Less_Cache::$cache_dir.$parser_options['output'];
+		}
+
+		return Less_Cache::$cache_dir.$compiled_name;
+	}
+
+	private static function CompiledName( $files, $extrahash ) {
+		// save the file list
+		$temp = array( Less_Version::cache_version );
+		foreach ( $files as $file ) {
+			$temp[] = filemtime( $file )."\t".filesize( $file )."\t".$file;
+		}
+
+		return Less_Cache::$prefix.sha1( json_encode( $temp ).$extrahash ).'.css';
+	}
+
+	public static function SetCacheDir( $dir ) {
+		Less_Cache::$cache_dir = $dir;
+		self::CheckCacheDir();
+	}
+
+	public static function CheckCacheDir() {
+		Less_Cache::$cache_dir = str_replace( '\\', '/', Less_Cache::$cache_dir );
+		Less_Cache::$cache_dir = rtrim( Less_Cache::$cache_dir, '/' ).'/';
+
+		if ( !file_exists( Less_Cache::$cache_dir ) ) {
+			if ( !mkdir( Less_Cache::$cache_dir ) ) {
+				throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: '.Less_Cache::$cache_dir );
+			}
+
+		} elseif ( !is_dir( Less_Cache::$cache_dir ) ) {
+			throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: '.Less_Cache::$cache_dir );
+
+		} elseif ( !is_writable( Less_Cache::$cache_dir ) ) {
+			throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: '.Less_Cache::$cache_dir );
+
+		}
+
+	}
+
+	/**
+	 * Delete unused less.php files
+	 *
+	 */
+	public static function CleanCache() {
+		static $clean = false;
+
+		if ( $clean || empty( Less_Cache::$cache_dir ) ) {
+			return;
+		}
+
+		$clean = true;
+
+		// only remove files with extensions created by less.php
+		// css files removed based on the list files
+		$remove_types = array( 'lesscache' => 1,'list' => 1,'less' => 1,'map' => 1 );
+
+		$files = scandir( Less_Cache::$cache_dir );
+		if ( !$files ) {
+			return;
+		}
+
+		$check_time = time() - self::$gc_lifetime;
+		foreach ( $files as $file ) {
+
+			// don't delete if the file wasn't created with less.php
+			if ( strpos( $file, Less_Cache::$prefix ) !== 0 ) {
+				continue;
+			}
+
+			$parts = explode( '.', $file );
+			$type = array_pop( $parts );
+
+			if ( !isset( $remove_types[$type] ) ) {
+				continue;
+			}
+
+			$full_path = Less_Cache::$cache_dir . $file;
+			$mtime = filemtime( $full_path );
+
+			// don't delete if it's a relatively new file
+			if ( $mtime > $check_time ) {
+				continue;
+			}
+
+			// delete the list file and associated css file
+			if ( $type === 'list' ) {
+				self::ListFiles( $full_path, $list, $css_file_name );
+				if ( $css_file_name ) {
+					$css_file = Less_Cache::$cache_dir . $css_file_name;
+					if ( file_exists( $css_file ) ) {
+						unlink( $css_file );
+					}
+				}
+			}
+
+			unlink( $full_path );
+		}
+
+	}
+
+	/**
+	 * Get the list of less files and generated css file from a list file
+	 *
+	 */
+	static function ListFiles( $list_file, &$list, &$css_file_name ) {
+		$list = explode( "\n", file_get_contents( $list_file ) );
+
+		// pop the cached name that should match $compiled_name
+		$css_file_name = array_pop( $list );
+
+		if ( !preg_match( '/^' . Less_Cache::$prefix . '[a-f0-9]+\.css$/', $css_file_name ) ) {
+			$list[] = $css_file_name;
+			$css_file_name = false;
+		}
+
+	}
+
+}

+ 169 - 0
lessphp/lib/Less/Colors.php

@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * Utility for css colors
+ *
+ * @package Less
+ * @subpackage color
+ */
+class Less_Colors {
+
+	public static $colors = array(
+			'aliceblue' => '#f0f8ff',
+			'antiquewhite' => '#faebd7',
+			'aqua' => '#00ffff',
+			'aquamarine' => '#7fffd4',
+			'azure' => '#f0ffff',
+			'beige' => '#f5f5dc',
+			'bisque' => '#ffe4c4',
+			'black' => '#000000',
+			'blanchedalmond' => '#ffebcd',
+			'blue' => '#0000ff',
+			'blueviolet' => '#8a2be2',
+			'brown' => '#a52a2a',
+			'burlywood' => '#deb887',
+			'cadetblue' => '#5f9ea0',
+			'chartreuse' => '#7fff00',
+			'chocolate' => '#d2691e',
+			'coral' => '#ff7f50',
+			'cornflowerblue' => '#6495ed',
+			'cornsilk' => '#fff8dc',
+			'crimson' => '#dc143c',
+			'cyan' => '#00ffff',
+			'darkblue' => '#00008b',
+			'darkcyan' => '#008b8b',
+			'darkgoldenrod' => '#b8860b',
+			'darkgray' => '#a9a9a9',
+			'darkgrey' => '#a9a9a9',
+			'darkgreen' => '#006400',
+			'darkkhaki' => '#bdb76b',
+			'darkmagenta' => '#8b008b',
+			'darkolivegreen' => '#556b2f',
+			'darkorange' => '#ff8c00',
+			'darkorchid' => '#9932cc',
+			'darkred' => '#8b0000',
+			'darksalmon' => '#e9967a',
+			'darkseagreen' => '#8fbc8f',
+			'darkslateblue' => '#483d8b',
+			'darkslategray' => '#2f4f4f',
+			'darkslategrey' => '#2f4f4f',
+			'darkturquoise' => '#00ced1',
+			'darkviolet' => '#9400d3',
+			'deeppink' => '#ff1493',
+			'deepskyblue' => '#00bfff',
+			'dimgray' => '#696969',
+			'dimgrey' => '#696969',
+			'dodgerblue' => '#1e90ff',
+			'firebrick' => '#b22222',
+			'floralwhite' => '#fffaf0',
+			'forestgreen' => '#228b22',
+			'fuchsia' => '#ff00ff',
+			'gainsboro' => '#dcdcdc',
+			'ghostwhite' => '#f8f8ff',
+			'gold' => '#ffd700',
+			'goldenrod' => '#daa520',
+			'gray' => '#808080',
+			'grey' => '#808080',
+			'green' => '#008000',
+			'greenyellow' => '#adff2f',
+			'honeydew' => '#f0fff0',
+			'hotpink' => '#ff69b4',
+			'indianred' => '#cd5c5c',
+			'indigo' => '#4b0082',
+			'ivory' => '#fffff0',
+			'khaki' => '#f0e68c',
+			'lavender' => '#e6e6fa',
+			'lavenderblush' => '#fff0f5',
+			'lawngreen' => '#7cfc00',
+			'lemonchiffon' => '#fffacd',
+			'lightblue' => '#add8e6',
+			'lightcoral' => '#f08080',
+			'lightcyan' => '#e0ffff',
+			'lightgoldenrodyellow' => '#fafad2',
+			'lightgray' => '#d3d3d3',
+			'lightgrey' => '#d3d3d3',
+			'lightgreen' => '#90ee90',
+			'lightpink' => '#ffb6c1',
+			'lightsalmon' => '#ffa07a',
+			'lightseagreen' => '#20b2aa',
+			'lightskyblue' => '#87cefa',
+			'lightslategray' => '#778899',
+			'lightslategrey' => '#778899',
+			'lightsteelblue' => '#b0c4de',
+			'lightyellow' => '#ffffe0',
+			'lime' => '#00ff00',
+			'limegreen' => '#32cd32',
+			'linen' => '#faf0e6',
+			'magenta' => '#ff00ff',
+			'maroon' => '#800000',
+			'mediumaquamarine' => '#66cdaa',
+			'mediumblue' => '#0000cd',
+			'mediumorchid' => '#ba55d3',
+			'mediumpurple' => '#9370d8',
+			'mediumseagreen' => '#3cb371',
+			'mediumslateblue' => '#7b68ee',
+			'mediumspringgreen' => '#00fa9a',
+			'mediumturquoise' => '#48d1cc',
+			'mediumvioletred' => '#c71585',
+			'midnightblue' => '#191970',
+			'mintcream' => '#f5fffa',
+			'mistyrose' => '#ffe4e1',
+			'moccasin' => '#ffe4b5',
+			'navajowhite' => '#ffdead',
+			'navy' => '#000080',
+			'oldlace' => '#fdf5e6',
+			'olive' => '#808000',
+			'olivedrab' => '#6b8e23',
+			'orange' => '#ffa500',
+			'orangered' => '#ff4500',
+			'orchid' => '#da70d6',
+			'palegoldenrod' => '#eee8aa',
+			'palegreen' => '#98fb98',
+			'paleturquoise' => '#afeeee',
+			'palevioletred' => '#d87093',
+			'papayawhip' => '#ffefd5',
+			'peachpuff' => '#ffdab9',
+			'peru' => '#cd853f',
+			'pink' => '#ffc0cb',
+			'plum' => '#dda0dd',
+			'powderblue' => '#b0e0e6',
+			'purple' => '#800080',
+			'red' => '#ff0000',
+			'rosybrown' => '#bc8f8f',
+			'royalblue' => '#4169e1',
+			'saddlebrown' => '#8b4513',
+			'salmon' => '#fa8072',
+			'sandybrown' => '#f4a460',
+			'seagreen' => '#2e8b57',
+			'seashell' => '#fff5ee',
+			'sienna' => '#a0522d',
+			'silver' => '#c0c0c0',
+			'skyblue' => '#87ceeb',
+			'slateblue' => '#6a5acd',
+			'slategray' => '#708090',
+			'slategrey' => '#708090',
+			'snow' => '#fffafa',
+			'springgreen' => '#00ff7f',
+			'steelblue' => '#4682b4',
+			'tan' => '#d2b48c',
+			'teal' => '#008080',
+			'thistle' => '#d8bfd8',
+			'tomato' => '#ff6347',
+			'turquoise' => '#40e0d0',
+			'violet' => '#ee82ee',
+			'wheat' => '#f5deb3',
+			'white' => '#ffffff',
+			'whitesmoke' => '#f5f5f5',
+			'yellow' => '#ffff00',
+			'yellowgreen' => '#9acd32'
+		);
+
+	public static function hasOwnProperty( $color ) {
+		return isset( self::$colors[$color] );
+	}
+
+	public static function color( $color ) {
+		return self::$colors[$color];
+	}
+
+}

+ 66 - 0
lessphp/lib/Less/Configurable.php

@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * Configurable
+ *
+ * @package Less
+ * @subpackage Core
+ */
+abstract class Less_Configurable {
+
+	/**
+	 * Array of options
+	 *
+	 * @var array
+	 */
+	protected $options = array();
+
+	/**
+	 * Array of default options
+	 *
+	 * @var array
+	 */
+	protected $defaultOptions = array();
+
+	/**
+	 * Set options
+	 *
+	 * If $options is an object it will be converted into an array by called
+	 * it's toArray method.
+	 *
+	 * @throws Exception
+	 * @param array|object $options
+	 *
+	 */
+	public function setOptions( $options ) {
+		$options = array_intersect_key( $options, $this->defaultOptions );
+		$this->options = array_merge( $this->defaultOptions, $this->options, $options );
+	}
+
+	/**
+	 * Get an option value by name
+	 *
+	 * If the option is empty or not set a NULL value will be returned.
+	 *
+	 * @param string $name
+	 * @param mixed $default Default value if confiuration of $name is not present
+	 * @return mixed
+	 */
+	public function getOption( $name, $default = null ) {
+		if ( isset( $this->options[$name] ) ) {
+			return $this->options[$name];
+		}
+		return $default;
+	}
+
+	/**
+	 * Set an option
+	 *
+	 * @param string $name
+	 * @param mixed $value
+	 */
+	public function setOption( $name, $value ) {
+		$this->options[$name] = $value;
+	}
+
+}

+ 157 - 0
lessphp/lib/Less/Environment.php

@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * Environment
+ *
+ * @package Less
+ * @subpackage environment
+ */
+class Less_Environment {
+
+	// public $paths = array();              // option - unmodified - paths to search for imports on
+	//public static $files = array();		// list of files that have been imported, used for import-once
+	//public $rootpath;						// option - rootpath to append to URL's
+	//public static $strictImports = null;	// option -
+	//public $insecure;						// option - whether to allow imports from insecure ssl hosts
+	//public $processImports;				// option - whether to process imports. if false then imports will not be imported
+	//public $javascriptEnabled;			// option - whether JavaScript is enabled. if undefined, defaults to true
+	//public $useFileCache;					// browser only - whether to use the per file session cache
+	public $currentFileInfo;				// information about the current file - for error reporting and importing and making urls relative etc.
+
+	public $importMultiple = false; 		// whether we are currently importing multiple copies
+
+	/**
+	 * @var array
+	 */
+	public $frames = array();
+
+	/**
+	 * @var array
+	 */
+	public $mediaBlocks = array();
+
+	/**
+	 * @var array
+	 */
+	public $mediaPath = array();
+
+	public static $parensStack = 0;
+
+	public static $tabLevel = 0;
+
+	public static $lastRule = false;
+
+	public static $_outputMap;
+
+	public static $mixin_stack = 0;
+
+	/**
+	 * @var array
+	 */
+	public $functions = array();
+
+	public function Init() {
+		self::$parensStack = 0;
+		self::$tabLevel = 0;
+		self::$lastRule = false;
+		self::$mixin_stack = 0;
+
+		if ( Less_Parser::$options['compress'] ) {
+
+			Less_Environment::$_outputMap = array(
+				','	=> ',',
+				': ' => ':',
+				''  => '',
+				' ' => ' ',
+				':' => ' :',
+				'+' => '+',
+				'~' => '~',
+				'>' => '>',
+				'|' => '|',
+				'^' => '^',
+				'^^' => '^^'
+			);
+
+		} else {
+
+			Less_Environment::$_outputMap = array(
+				','	=> ', ',
+				': ' => ': ',
+				''  => '',
+				' ' => ' ',
+				':' => ' :',
+				'+' => ' + ',
+				'~' => ' ~ ',
+				'>' => ' > ',
+				'|' => '|',
+				'^' => ' ^ ',
+				'^^' => ' ^^ '
+			);
+
+		}
+	}
+
+	public function copyEvalEnv( $frames = array() ) {
+		$new_env = new Less_Environment();
+		$new_env->frames = $frames;
+		return $new_env;
+	}
+
+	public static function isMathOn() {
+		return !Less_Parser::$options['strictMath'] || Less_Environment::$parensStack;
+	}
+
+	public static function isPathRelative( $path ) {
+		return !preg_match( '/^(?:[a-z-]+:|\/)/', $path );
+	}
+
+	/**
+	 * Canonicalize a path by resolving references to '/./', '/../'
+	 * Does not remove leading "../"
+	 * @param string path or url
+	 * @return string Canonicalized path
+	 *
+	 */
+	public static function normalizePath( $path ) {
+		$segments = explode( '/', $path );
+		$segments = array_reverse( $segments );
+
+		$path = array();
+		$path_len = 0;
+
+		while ( $segments ) {
+			$segment = array_pop( $segments );
+			switch ( $segment ) {
+
+				case '.':
+				break;
+
+				case '..':
+					if ( !$path_len || ( $path[$path_len - 1] === '..' ) ) {
+						$path[] = $segment;
+						$path_len++;
+					} else {
+						array_pop( $path );
+						$path_len--;
+					}
+				break;
+
+				default:
+					$path[] = $segment;
+					$path_len++;
+				break;
+			}
+		}
+
+		return implode( '/', $path );
+	}
+
+	public function unshiftFrame( $frame ) {
+		array_unshift( $this->frames, $frame );
+	}
+
+	public function shiftFrame() {
+		return array_shift( $this->frames );
+	}
+
+}

+ 203 - 0
lessphp/lib/Less/Exception/Chunk.php

@@ -0,0 +1,203 @@
+<?php
+
+/**
+ * Chunk Exception
+ *
+ * @package Less
+ * @subpackage exception
+ */
+class Less_Exception_Chunk extends Less_Exception_Parser {
+
+	protected $parserCurrentIndex = 0;
+
+	protected $emitFrom = 0;
+
+	protected $input_len;
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $input
+	 * @param Exception $previous Previous exception
+	 * @param integer $index The current parser index
+	 * @param Less_FileInfo|string $currentFile The file
+	 * @param integer $code The exception code
+	 */
+	public function __construct( $input, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
+		$this->message = 'ParseError: Unexpected input'; // default message
+
+		$this->index = $index;
+
+		$this->currentFile = $currentFile;
+
+		$this->input = $input;
+		$this->input_len = strlen( $input );
+
+		$this->Chunks();
+		$this->genMessage();
+	}
+
+	/**
+	 * See less.js chunks()
+	 * We don't actually need the chunks
+	 *
+	 */
+	protected function Chunks() {
+		$level = 0;
+		$parenLevel = 0;
+		$lastMultiCommentEndBrace = null;
+		$lastOpening = null;
+		$lastMultiComment = null;
+		$lastParen = null;
+
+		for ( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
+			$cc = $this->CharCode( $this->parserCurrentIndex );
+			if ( ( ( $cc >= 97 ) && ( $cc <= 122 ) ) || ( $cc < 34 ) ) {
+				// a-z or whitespace
+				continue;
+			}
+
+			switch ( $cc ) {
+
+				// (
+				case 40:
+					$parenLevel++;
+					$lastParen = $this->parserCurrentIndex;
+					break;
+
+				// )
+				case 41:
+					$parenLevel--;
+					if ( $parenLevel < 0 ) {
+						return $this->fail( "missing opening `(`" );
+					}
+					break;
+
+				// ;
+				case 59:
+					// if (!$parenLevel) { $this->emitChunk();   }
+					break;
+
+				// {
+				case 123:
+					$level++;
+					$lastOpening = $this->parserCurrentIndex;
+					break;
+
+				// }
+				case 125:
+					$level--;
+					if ( $level < 0 ) {
+						return $this->fail( "missing opening `{`" );
+
+					}
+					// if (!$level && !$parenLevel) { $this->emitChunk(); }
+					break;
+				// \
+				case 92:
+					if ( $this->parserCurrentIndex < $this->input_len - 1 ) { $this->parserCurrentIndex++; break;
+					}
+					return $this->fail( "unescaped `\\`" );
+
+				// ", ' and `
+				case 34:
+				case 39:
+				case 96:
+					$matched = 0;
+					$currentChunkStartIndex = $this->parserCurrentIndex;
+					for ( $this->parserCurrentIndex = $this->parserCurrentIndex + 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
+						$cc2 = $this->CharCode( $this->parserCurrentIndex );
+						if ( $cc2 > 96 ) { continue;
+						}
+						if ( $cc2 == $cc ) { $matched = 1; break;
+						}
+						if ( $cc2 == 92 ) {        // \
+							if ( $this->parserCurrentIndex == $this->input_len - 1 ) {
+								return $this->fail( "unescaped `\\`" );
+							}
+							$this->parserCurrentIndex++;
+						}
+					}
+					if ( $matched ) { break;
+					}
+					return $this->fail( "unmatched `" . chr( $cc ) . "`", $currentChunkStartIndex );
+
+				// /, check for comment
+				case 47:
+					if ( $parenLevel || ( $this->parserCurrentIndex == $this->input_len - 1 ) ) { break;
+					}
+					$cc2 = $this->CharCode( $this->parserCurrentIndex + 1 );
+					if ( $cc2 == 47 ) {
+						// //, find lnfeed
+						for ( $this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
+							$cc2 = $this->CharCode( $this->parserCurrentIndex );
+							if ( ( $cc2 <= 13 ) && ( ( $cc2 == 10 ) || ( $cc2 == 13 ) ) ) { break;
+							}
+						}
+					} else if ( $cc2 == 42 ) {
+						// /*, find */
+						$lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex;
+						for ( $this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++ ) {
+							$cc2 = $this->CharCode( $this->parserCurrentIndex );
+							if ( $cc2 == 125 ) { $lastMultiCommentEndBrace = $this->parserCurrentIndex;
+							}
+							if ( $cc2 != 42 ) { continue;
+							}
+							if ( $this->CharCode( $this->parserCurrentIndex + 1 ) == 47 ) { break;
+							}
+						}
+						if ( $this->parserCurrentIndex == $this->input_len - 1 ) {
+							return $this->fail( "missing closing `*/`", $currentChunkStartIndex );
+						}
+					}
+					break;
+
+				// *, check for unmatched */
+				case 42:
+					if ( ( $this->parserCurrentIndex < $this->input_len - 1 ) && ( $this->CharCode( $this->parserCurrentIndex + 1 ) == 47 ) ) {
+						return $this->fail( "unmatched `/*`" );
+					}
+					break;
+			}
+		}
+
+		if ( $level !== 0 ) {
+			if ( ( $lastMultiComment > $lastOpening ) && ( $lastMultiCommentEndBrace > $lastMultiComment ) ) {
+				return $this->fail( "missing closing `}` or `*/`", $lastOpening );
+			} else {
+				return $this->fail( "missing closing `}`", $lastOpening );
+			}
+		} else if ( $parenLevel !== 0 ) {
+			return $this->fail( "missing closing `)`", $lastParen );
+		}
+
+		// chunk didn't fail
+
+		//$this->emitChunk(true);
+	}
+
+	public function CharCode( $pos ) {
+		return ord( $this->input[$pos] );
+	}
+
+	public function fail( $msg, $index = null ) {
+		if ( !$index ) {
+			$this->index = $this->parserCurrentIndex;
+		} else {
+			$this->index = $index;
+		}
+		$this->message = 'ParseError: '.$msg;
+	}
+
+	/*
+	function emitChunk( $force = false ){
+		$len = $this->parserCurrentIndex - $this->emitFrom;
+		if ((($len < 512) && !$force) || !$len) {
+			return;
+		}
+		$chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom );
+		$this->emitFrom = $this->parserCurrentIndex + 1;
+	}
+	*/
+
+}

+ 11 - 0
lessphp/lib/Less/Exception/Compiler.php

@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * Compiler Exception
+ *
+ * @package Less
+ * @subpackage exception
+ */
+class Less_Exception_Compiler extends Less_Exception_Parser {
+
+}

+ 116 - 0
lessphp/lib/Less/Exception/Parser.php

@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * Parser Exception
+ *
+ * @package Less
+ * @subpackage exception
+ */
+class Less_Exception_Parser extends Exception {
+
+	/**
+	 * The current file
+	 *
+	 * @var Less_ImportedFile
+	 */
+	public $currentFile;
+
+	/**
+	 * The current parser index
+	 *
+	 * @var integer
+	 */
+	public $index;
+
+	protected $input;
+
+	protected $details = array();
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $message
+	 * @param Exception $previous Previous exception
+	 * @param integer $index The current parser index
+	 * @param Less_FileInfo|string $currentFile The file
+	 * @param integer $code The exception code
+	 */
+	public function __construct( $message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
+		if ( PHP_VERSION_ID < 50300 ) {
+			$this->previous = $previous;
+			parent::__construct( $message, $code );
+		} else {
+			parent::__construct( $message, $code, $previous );
+		}
+
+		$this->currentFile = $currentFile;
+		$this->index = $index;
+
+		$this->genMessage();
+	}
+
+	protected function getInput() {
+		if ( !$this->input && $this->currentFile && $this->currentFile['filename'] && file_exists( $this->currentFile['filename'] ) ) {
+			$this->input = file_get_contents( $this->currentFile['filename'] );
+		}
+	}
+
+	/**
+	 * Converts the exception to string
+	 *
+	 * @return string
+	 */
+	public function genMessage() {
+		if ( $this->currentFile && $this->currentFile['filename'] ) {
+			$this->message .= ' in '.basename( $this->currentFile['filename'] );
+		}
+
+		if ( $this->index !== null ) {
+			$this->getInput();
+			if ( $this->input ) {
+				$line = self::getLineNumber();
+				$this->message .= ' on line '.$line.', column '.self::getColumn();
+
+				$lines = explode( "\n", $this->input );
+
+				$count = count( $lines );
+				$start_line = max( 0, $line - 3 );
+				$last_line = min( $count, $start_line + 6 );
+				$num_len = strlen( $last_line );
+				for ( $i = $start_line; $i < $last_line; $i++ ) {
+					$this->message .= "\n".str_pad( $i + 1, $num_len, '0', STR_PAD_LEFT ).'| '.$lines[$i];
+				}
+			}
+		}
+
+	}
+
+	/**
+	 * Returns the line number the error was encountered
+	 *
+	 * @return integer
+	 */
+	public function getLineNumber() {
+		if ( $this->index ) {
+			// https://bugs.php.net/bug.php?id=49790
+			if ( ini_get( "mbstring.func_overload" ) ) {
+				return substr_count( substr( $this->input, 0, $this->index ), "\n" ) + 1;
+			} else {
+				return substr_count( $this->input, "\n", 0, $this->index ) + 1;
+			}
+		}
+		return 1;
+	}
+
+	/**
+	 * Returns the column the error was encountered
+	 *
+	 * @return integer
+	 */
+	public function getColumn() {
+		$part = substr( $this->input, 0, $this->index );
+		$pos = strrpos( $part, "\n" );
+		return $this->index - $pos;
+	}
+
+}

+ 1186 - 0
lessphp/lib/Less/Functions.php

@@ -0,0 +1,1186 @@
+<?php
+
+/**
+ * Builtin functions
+ *
+ * @package Less
+ * @subpackage function
+ * @see http://lesscss.org/functions/
+ */
+class Less_Functions {
+
+	public $env;
+	public $currentFileInfo;
+
+	function __construct( $env, $currentFileInfo = null ) {
+		$this->env = $env;
+		$this->currentFileInfo = $currentFileInfo;
+	}
+
+	/**
+	 * @param string $op
+	 */
+	public static function operate( $op, $a, $b ) {
+		switch ( $op ) {
+			case '+':
+return $a + $b;
+			case '-':
+return $a - $b;
+			case '*':
+return $a * $b;
+			case '/':
+return $a / $b;
+		}
+	}
+
+	public static function clamp( $val, $max = 1 ) {
+		return min( max( $val, 0 ), $max );
+	}
+
+	public static function fround( $value ) {
+		if ( $value === 0 ) {
+			return $value;
+		}
+
+		if ( Less_Parser::$options['numPrecision'] ) {
+			$p = pow( 10, Less_Parser::$options['numPrecision'] );
+			return round( $value * $p ) / $p;
+		}
+		return $value;
+	}
+
+	public static function number( $n ) {
+		if ( $n instanceof Less_Tree_Dimension ) {
+			return floatval( $n->unit->is( '%' ) ? $n->value / 100 : $n->value );
+		} else if ( is_numeric( $n ) ) {
+			return $n;
+		} else {
+			throw new Less_Exception_Compiler( "color functions take numbers as parameters" );
+		}
+	}
+
+	public static function scaled( $n, $size = 255 ) {
+		if ( $n instanceof Less_Tree_Dimension && $n->unit->is( '%' ) ) {
+			return (float)$n->value * $size / 100;
+		} else {
+			return Less_Functions::number( $n );
+		}
+	}
+
+	public function rgb( $r = null, $g = null, $b = null ) {
+		if ( is_null( $r ) || is_null( $g ) || is_null( $b ) ) {
+			throw new Less_Exception_Compiler( "rgb expects three parameters" );
+		}
+		return $this->rgba( $r, $g, $b, 1.0 );
+	}
+
+	public function rgba( $r = null, $g = null, $b = null, $a = null ) {
+		$rgb = array( $r, $g, $b );
+		$rgb = array_map( array( 'Less_Functions','scaled' ), $rgb );
+
+		$a = self::number( $a );
+		return new Less_Tree_Color( $rgb, $a );
+	}
+
+	public function hsl( $h, $s, $l ) {
+		return $this->hsla( $h, $s, $l, 1.0 );
+	}
+
+	public function hsla( $h, $s, $l, $a ) {
+		$h = fmod( self::number( $h ), 360 ) / 360; // Classic % operator will change float to int
+		$s = self::clamp( self::number( $s ) );
+		$l = self::clamp( self::number( $l ) );
+		$a = self::clamp( self::number( $a ) );
+
+		$m2 = $l <= 0.5 ? $l * ( $s + 1 ) : $l + $s - $l * $s;
+
+		$m1 = $l * 2 - $m2;
+
+		return $this->rgba( self::hsla_hue( $h + 1 / 3, $m1, $m2 ) * 255,
+							self::hsla_hue( $h, $m1, $m2 ) * 255,
+							self::hsla_hue( $h - 1 / 3, $m1, $m2 ) * 255,
+							$a );
+	}
+
+	/**
+	 * @param double $h
+	 */
+	public function hsla_hue( $h, $m1, $m2 ) {
+		$h = $h < 0 ? $h + 1 : ( $h > 1 ? $h - 1 : $h );
+		if ( $h * 6 < 1 ) return $m1 + ( $m2 - $m1 ) * $h * 6; else if ( $h * 2 < 1 ) return $m2; else if ( $h * 3 < 2 ) return $m1 + ( $m2 - $m1 ) * ( 2 / 3 - $h ) * 6; else return $m1;
+	}
+
+	public function hsv( $h, $s, $v ) {
+		return $this->hsva( $h, $s, $v, 1.0 );
+	}
+
+	/**
+	 * @param double $a
+	 */
+	public function hsva( $h, $s, $v, $a ) {
+		$h = ( ( Less_Functions::number( $h ) % 360 ) / 360 ) * 360;
+		$s = Less_Functions::number( $s );
+		$v = Less_Functions::number( $v );
+		$a = Less_Functions::number( $a );
+
+		$i = floor( ( $h / 60 ) % 6 );
+		$f = ( $h / 60 ) - $i;
+
+		$vs = array( $v,
+				  $v * ( 1 - $s ),
+				  $v * ( 1 - $f * $s ),
+				  $v * ( 1 - ( 1 - $f ) * $s ) );
+
+		$perm = array( array( 0, 3, 1 ),
+					array( 2, 0, 1 ),
+					array( 1, 0, 3 ),
+					array( 1, 2, 0 ),
+					array( 3, 1, 0 ),
+					array( 0, 1, 2 ) );
+
+		return $this->rgba( $vs[$perm[$i][0]] * 255,
+						 $vs[$perm[$i][1]] * 255,
+						 $vs[$perm[$i][2]] * 255,
+						 $a );
+	}
+
+	public function hue( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to hue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$c = $color->toHSL();
+		return new Less_Tree_Dimension( Less_Parser::round( $c['h'] ) );
+	}
+
+	public function saturation( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to saturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$c = $color->toHSL();
+		return new Less_Tree_Dimension( Less_Parser::round( $c['s'] * 100 ), '%' );
+	}
+
+	public function lightness( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to lightness must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$c = $color->toHSL();
+		return new Less_Tree_Dimension( Less_Parser::round( $c['l'] * 100 ), '%' );
+	}
+
+	public function hsvhue( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to hsvhue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsv = $color->toHSV();
+		return new Less_Tree_Dimension( Less_Parser::round( $hsv['h'] ) );
+	}
+
+	public function hsvsaturation( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to hsvsaturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsv = $color->toHSV();
+		return new Less_Tree_Dimension( Less_Parser::round( $hsv['s'] * 100 ), '%' );
+	}
+
+	public function hsvvalue( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to hsvvalue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsv = $color->toHSV();
+		return new Less_Tree_Dimension( Less_Parser::round( $hsv['v'] * 100 ), '%' );
+	}
+
+	public function red( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to red must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return new Less_Tree_Dimension( $color->rgb[0] );
+	}
+
+	public function green( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to green must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return new Less_Tree_Dimension( $color->rgb[1] );
+	}
+
+	public function blue( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to blue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return new Less_Tree_Dimension( $color->rgb[2] );
+	}
+
+	public function alpha( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to alpha must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$c = $color->toHSL();
+		return new Less_Tree_Dimension( $c['a'] );
+	}
+
+	public function luma( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to luma must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return new Less_Tree_Dimension( Less_Parser::round( $color->luma() * $color->alpha * 100 ), '%' );
+	}
+
+	public function luminance( $color = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to luminance must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$luminance =
+			( 0.2126 * $color->rgb[0] / 255 )
+		  + ( 0.7152 * $color->rgb[1] / 255 )
+		  + ( 0.0722 * $color->rgb[2] / 255 );
+
+		return new Less_Tree_Dimension( Less_Parser::round( $luminance * $color->alpha * 100 ), '%' );
+	}
+
+	public function saturate( $color = null, $amount = null ) {
+		// filter: saturate(3.2);
+		// should be kept as is, so check for color
+		if ( $color instanceof Less_Tree_Dimension ) {
+			return null;
+		}
+
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to saturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$amount instanceof Less_Tree_Dimension ) {
+			throw new Less_Exception_Compiler( 'The second argument to saturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsl = $color->toHSL();
+
+		$hsl['s'] += $amount->value / 100;
+		$hsl['s'] = self::clamp( $hsl['s'] );
+
+		return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+	}
+
+	/**
+	 * @param Less_Tree_Dimension $amount
+	 */
+	public function desaturate( $color = null, $amount = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to desaturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$amount instanceof Less_Tree_Dimension ) {
+			throw new Less_Exception_Compiler( 'The second argument to desaturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsl = $color->toHSL();
+
+		$hsl['s'] -= $amount->value / 100;
+		$hsl['s'] = self::clamp( $hsl['s'] );
+
+		return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+	}
+
+	public function lighten( $color = null, $amount = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to lighten must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$amount instanceof Less_Tree_Dimension ) {
+			throw new Less_Exception_Compiler( 'The second argument to lighten must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsl = $color->toHSL();
+
+		$hsl['l'] += $amount->value / 100;
+		$hsl['l'] = self::clamp( $hsl['l'] );
+
+		return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+	}
+
+	public function darken( $color = null, $amount = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to darken must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$amount instanceof Less_Tree_Dimension ) {
+			throw new Less_Exception_Compiler( 'The second argument to darken must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsl = $color->toHSL();
+		$hsl['l'] -= $amount->value / 100;
+		$hsl['l'] = self::clamp( $hsl['l'] );
+
+		return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+	}
+
+	public function fadein( $color = null, $amount = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to fadein must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$amount instanceof Less_Tree_Dimension ) {
+			throw new Less_Exception_Compiler( 'The second argument to fadein must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsl = $color->toHSL();
+		$hsl['a'] += $amount->value / 100;
+		$hsl['a'] = self::clamp( $hsl['a'] );
+		return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+	}
+
+	public function fadeout( $color = null, $amount = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to fadeout must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$amount instanceof Less_Tree_Dimension ) {
+			throw new Less_Exception_Compiler( 'The second argument to fadeout must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsl = $color->toHSL();
+		$hsl['a'] -= $amount->value / 100;
+		$hsl['a'] = self::clamp( $hsl['a'] );
+		return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+	}
+
+	public function fade( $color = null, $amount = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to fade must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$amount instanceof Less_Tree_Dimension ) {
+			throw new Less_Exception_Compiler( 'The second argument to fade must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsl = $color->toHSL();
+
+		$hsl['a'] = $amount->value / 100;
+		$hsl['a'] = self::clamp( $hsl['a'] );
+		return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+	}
+
+	public function spin( $color = null, $amount = null ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to spin must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$amount instanceof Less_Tree_Dimension ) {
+			throw new Less_Exception_Compiler( 'The second argument to spin must be a number' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$hsl = $color->toHSL();
+		$hue = fmod( $hsl['h'] + $amount->value, 360 );
+
+		$hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
+
+		return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+	}
+
+	//
+	// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
+	// http://sass-lang.com
+	//
+
+	/**
+	 * @param Less_Tree_Color $color1
+	 */
+	public function mix( $color1 = null, $color2 = null, $weight = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to mix must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to mix must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$weight ) {
+			$weight = new Less_Tree_Dimension( '50', '%' );
+		}
+		if ( !$weight instanceof Less_Tree_Dimension ) {
+			throw new Less_Exception_Compiler( 'The third argument to contrast must be a percentage' . ( $weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		$p = $weight->value / 100.0;
+		$w = $p * 2 - 1;
+		$hsl1 = $color1->toHSL();
+		$hsl2 = $color2->toHSL();
+		$a = $hsl1['a'] - $hsl2['a'];
+
+		$w1 = ( ( ( ( $w * $a ) == -1 ) ? $w : ( $w + $a ) / ( 1 + $w * $a ) ) + 1 ) / 2;
+		$w2 = 1 - $w1;
+
+		$rgb = array( $color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
+					 $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
+					 $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2 );
+
+		$alpha = $color1->alpha * $p + $color2->alpha * ( 1 - $p );
+
+		return new Less_Tree_Color( $rgb, $alpha );
+	}
+
+	public function greyscale( $color ) {
+		return $this->desaturate( $color, new Less_Tree_Dimension( 100, '%' ) );
+	}
+
+	public function contrast( $color, $dark = null, $light = null, $threshold = null ) {
+		// filter: contrast(3.2);
+		// should be kept as is, so check for color
+		if ( !$color instanceof Less_Tree_Color ) {
+			return null;
+		}
+		if ( !$light ) {
+			$light = $this->rgba( 255, 255, 255, 1.0 );
+		}
+		if ( !$dark ) {
+			$dark = $this->rgba( 0, 0, 0, 1.0 );
+		}
+
+		if ( !$dark instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to contrast must be a color' . ( $dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$light instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The third argument to contrast must be a color' . ( $light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		// Figure out which is actually light and dark!
+		if ( $dark->luma() > $light->luma() ) {
+			$t = $light;
+			$light = $dark;
+			$dark = $t;
+		}
+		if ( !$threshold ) {
+			$threshold = 0.43;
+		} else {
+			$threshold = Less_Functions::number( $threshold );
+		}
+
+		if ( $color->luma() < $threshold ) {
+			return $light;
+		} else {
+			return $dark;
+		}
+	}
+
+	public function e( $str ) {
+		if ( is_string( $str ) ) {
+			return new Less_Tree_Anonymous( $str );
+		}
+		return new Less_Tree_Anonymous( $str instanceof Less_Tree_JavaScript ? $str->expression : $str->value );
+	}
+
+	public function escape( $str ) {
+		$revert = array( '%21' => '!', '%2A' => '*', '%27' => "'",'%3F' => '?','%26' => '&','%2C' => ',','%2F' => '/','%40' => '@','%2B' => '+','%24' => '$' );
+
+		return new Less_Tree_Anonymous( strtr( rawurlencode( $str->value ), $revert ) );
+	}
+
+	/**
+	 * todo: This function will need some additional work to make it work the same as less.js
+	 *
+	 */
+	public function replace( $string, $pattern, $replacement, $flags = null ) {
+		$result = $string->value;
+
+		$expr = '/'.str_replace( '/', '\\/', $pattern->value ).'/';
+		if ( $flags && $flags->value ) {
+			$expr .= self::replace_flags( $flags->value );
+		}
+
+		$result = preg_replace( $expr, $replacement->value, $result );
+
+		if ( property_exists( $string, 'quote' ) ) {
+			return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
+		}
+		return new Less_Tree_Quoted( '', $result );
+	}
+
+	public static function replace_flags( $flags ) {
+		$flags = str_split( $flags, 1 );
+		$new_flags = '';
+
+		foreach ( $flags as $flag ) {
+			switch ( $flag ) {
+				case 'e':
+				case 'g':
+				break;
+
+				default:
+				$new_flags .= $flag;
+				break;
+			}
+		}
+
+		return $new_flags;
+	}
+
+	public function _percent() {
+		$string = func_get_arg( 0 );
+
+		$args = func_get_args();
+		array_shift( $args );
+		$result = $string->value;
+
+		foreach ( $args as $arg ) {
+			if ( preg_match( '/%[sda]/i', $result, $token ) ) {
+				$token = $token[0];
+				$value = stristr( $token, 's' ) ? $arg->value : $arg->toCSS();
+				$value = preg_match( '/[A-Z]$/', $token ) ? urlencode( $value ) : $value;
+				$result = preg_replace( '/%[sda]/i', $value, $result, 1 );
+			}
+		}
+		$result = str_replace( '%%', '%', $result );
+
+		return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
+	}
+
+	public function unit( $val, $unit = null ) {
+		if ( !( $val instanceof Less_Tree_Dimension ) ) {
+			throw new Less_Exception_Compiler( 'The first argument to unit must be a number' . ( $val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.' ) );
+		}
+
+		if ( $unit ) {
+			if ( $unit instanceof Less_Tree_Keyword ) {
+				$unit = $unit->value;
+			} else {
+				$unit = $unit->toCSS();
+			}
+		} else {
+			$unit = "";
+		}
+		return new Less_Tree_Dimension( $val->value, $unit );
+	}
+
+	public function convert( $val, $unit ) {
+		return $val->convertTo( $unit->value );
+	}
+
+	public function round( $n, $f = false ) {
+		$fraction = 0;
+		if ( $f !== false ) {
+			$fraction = $f->value;
+		}
+
+		return $this->_math( 'Less_Parser::round', null, $n, $fraction );
+	}
+
+	public function pi() {
+		return new Less_Tree_Dimension( M_PI );
+	}
+
+	public function mod( $a, $b ) {
+		return new Less_Tree_Dimension( $a->value % $b->value, $a->unit );
+	}
+
+	public function pow( $x, $y ) {
+		if ( is_numeric( $x ) && is_numeric( $y ) ) {
+			$x = new Less_Tree_Dimension( $x );
+			$y = new Less_Tree_Dimension( $y );
+		} elseif ( !( $x instanceof Less_Tree_Dimension ) || !( $y instanceof Less_Tree_Dimension ) ) {
+			throw new Less_Exception_Compiler( 'Arguments must be numbers' );
+		}
+
+		return new Less_Tree_Dimension( pow( $x->value, $y->value ), $x->unit );
+	}
+
+	// var mathFunctions = [{name:"ce ...
+	public function ceil( $n ) {
+		return $this->_math( 'ceil', null, $n );
+	}
+
+	public function floor( $n ) {
+	return $this->_math( 'floor', null, $n );
+	}
+
+	public function sqrt( $n ) {
+		return $this->_math( 'sqrt', null, $n );
+	}
+
+	public function abs( $n ) {
+		return $this->_math( 'abs', null, $n );
+	}
+
+	public function tan( $n ) {
+		return $this->_math( 'tan', '', $n );
+	}
+
+	public function sin( $n ) {
+		return $this->_math( 'sin', '', $n );
+	}
+
+	public function cos( $n ) {
+		return $this->_math( 'cos', '', $n );
+	}
+
+	public function atan( $n ) {
+		return $this->_math( 'atan', 'rad', $n );
+	}
+
+	public function asin( $n ) {
+		return $this->_math( 'asin', 'rad', $n );
+	}
+
+	public function acos( $n ) {
+		return $this->_math( 'acos', 'rad', $n );
+	}
+
+	private function _math() {
+		$args = func_get_args();
+		$fn = array_shift( $args );
+		$unit = array_shift( $args );
+
+		if ( $args[0] instanceof Less_Tree_Dimension ) {
+
+			if ( $unit === null ) {
+				$unit = $args[0]->unit;
+			} else {
+				$args[0] = $args[0]->unify();
+			}
+			$args[0] = (float)$args[0]->value;
+			return new Less_Tree_Dimension( call_user_func_array( $fn, $args ), $unit );
+		} else if ( is_numeric( $args[0] ) ) {
+			return call_user_func_array( $fn, $args );
+		} else {
+			throw new Less_Exception_Compiler( "math functions take numbers as parameters" );
+		}
+	}
+
+	/**
+	 * @param boolean $isMin
+	 */
+	private function _minmax( $isMin, $args ) {
+		$arg_count = count( $args );
+
+		if ( $arg_count < 1 ) {
+			throw new Less_Exception_Compiler( 'one or more arguments required' );
+		}
+
+		$j = null;
+		$unitClone = null;
+		$unitStatic = null;
+
+		$order = array();	// elems only contains original argument values.
+		$values = array();	// key is the unit.toString() for unified tree.Dimension values,
+							// value is the index into the order array.
+
+		for ( $i = 0; $i < $arg_count; $i++ ) {
+			$current = $args[$i];
+			if ( !( $current instanceof Less_Tree_Dimension ) ) {
+				if ( is_array( $args[$i]->value ) ) {
+					$args[] = $args[$i]->value;
+				}
+				continue;
+			}
+
+			if ( $current->unit->toString() === '' && !$unitClone ) {
+				$temp = new Less_Tree_Dimension( $current->value, $unitClone );
+				$currentUnified = $temp->unify();
+			} else {
+				$currentUnified = $current->unify();
+			}
+
+			if ( $currentUnified->unit->toString() === "" && !$unitStatic ) {
+				$unit = $unitStatic;
+			} else {
+				$unit = $currentUnified->unit->toString();
+			}
+
+			if ( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ) {
+				$unitStatic = $unit;
+			}
+
+			if ( $unit != '' && !$unitClone ) {
+				$unitClone = $current->unit->toString();
+			}
+
+			if ( isset( $values[''] ) && $unit !== '' && $unit === $unitStatic ) {
+				$j = $values[''];
+			} elseif ( isset( $values[$unit] ) ) {
+				$j = $values[$unit];
+			} else {
+
+				if ( $unitStatic && $unit !== $unitStatic ) {
+					throw new Less_Exception_Compiler( 'incompatible types' );
+				}
+				$values[$unit] = count( $order );
+				$order[] = $current;
+				continue;
+			}
+
+			if ( $order[$j]->unit->toString() === "" && $unitClone ) {
+				$temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone );
+				$referenceUnified = $temp->unify();
+			} else {
+				$referenceUnified = $order[$j]->unify();
+			}
+			if ( ( $isMin && $currentUnified->value < $referenceUnified->value ) || ( !$isMin && $currentUnified->value > $referenceUnified->value ) ) {
+				$order[$j] = $current;
+			}
+		}
+
+		if ( count( $order ) == 1 ) {
+			return $order[0];
+		}
+		$args = array();
+		foreach ( $order as $a ) {
+			$args[] = $a->toCSS( $this->env );
+		}
+		return new Less_Tree_Anonymous( ( $isMin ? 'min(' : 'max(' ) . implode( Less_Environment::$_outputMap[','], $args ).')' );
+	}
+
+	public function min() {
+		$args = func_get_args();
+		return $this->_minmax( true, $args );
+	}
+
+	public function max() {
+		$args = func_get_args();
+		return $this->_minmax( false, $args );
+	}
+
+	public function getunit( $n ) {
+		return new Less_Tree_Anonymous( $n->unit );
+	}
+
+	public function argb( $color ) {
+		if ( !$color instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to argb must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return new Less_Tree_Anonymous( $color->toARGB() );
+	}
+
+	public function percentage( $n ) {
+		return new Less_Tree_Dimension( $n->value * 100, '%' );
+	}
+
+	public function color( $n ) {
+		if ( $n instanceof Less_Tree_Quoted ) {
+			$colorCandidate = $n->value;
+			$returnColor = Less_Tree_Color::fromKeyword( $colorCandidate );
+			if ( $returnColor ) {
+				return $returnColor;
+			}
+			if ( preg_match( '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/', $colorCandidate ) ) {
+				return new Less_Tree_Color( substr( $colorCandidate, 1 ) );
+			}
+			throw new Less_Exception_Compiler( "argument must be a color keyword or 3/6 digit hex e.g. #FFF" );
+		} else {
+			throw new Less_Exception_Compiler( "argument must be a string" );
+		}
+	}
+
+	public function iscolor( $n ) {
+		return $this->_isa( $n, 'Less_Tree_Color' );
+	}
+
+	public function isnumber( $n ) {
+		return $this->_isa( $n, 'Less_Tree_Dimension' );
+	}
+
+	public function isstring( $n ) {
+		return $this->_isa( $n, 'Less_Tree_Quoted' );
+	}
+
+	public function iskeyword( $n ) {
+		return $this->_isa( $n, 'Less_Tree_Keyword' );
+	}
+
+	public function isurl( $n ) {
+		return $this->_isa( $n, 'Less_Tree_Url' );
+	}
+
+	public function ispixel( $n ) {
+		return $this->isunit( $n, 'px' );
+	}
+
+	public function ispercentage( $n ) {
+		return $this->isunit( $n, '%' );
+	}
+
+	public function isem( $n ) {
+		return $this->isunit( $n, 'em' );
+	}
+
+	/**
+	 * @param string $unit
+	 */
+	public function isunit( $n, $unit ) {
+		if ( is_object( $unit ) && property_exists( $unit, 'value' ) ) {
+			$unit = $unit->value;
+		}
+
+		return ( $n instanceof Less_Tree_Dimension ) && $n->unit->is( $unit ) ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
+	}
+
+	/**
+	 * @param string $type
+	 */
+	private function _isa( $n, $type ) {
+		return is_a( $n, $type ) ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
+	}
+
+	public function tint( $color, $amount = null ) {
+		return $this->mix( $this->rgb( 255, 255, 255 ), $color, $amount );
+	}
+
+	public function shade( $color, $amount = null ) {
+		return $this->mix( $this->rgb( 0, 0, 0 ), $color, $amount );
+	}
+
+	public function extract( $values, $index ) {
+		$index = (int)$index->value - 1; // (1-based index)
+		// handle non-array values as an array of length 1
+		// return 'undefined' if index is invalid
+		if ( property_exists( $values, 'value' ) && is_array( $values->value ) ) {
+			if ( isset( $values->value[$index] ) ) {
+				return $values->value[$index];
+			}
+			return null;
+
+		} elseif ( (int)$index === 0 ) {
+			return $values;
+		}
+
+		return null;
+	}
+
+	public function length( $values ) {
+		$n = ( property_exists( $values, 'value' ) && is_array( $values->value ) ) ? count( $values->value ) : 1;
+		return new Less_Tree_Dimension( $n );
+	}
+
+	public function datauri( $mimetypeNode, $filePathNode = null ) {
+		$filePath = ( $filePathNode ? $filePathNode->value : null );
+		$mimetype = $mimetypeNode->value;
+
+		$args = 2;
+		if ( !$filePath ) {
+			$filePath = $mimetype;
+			$args = 1;
+		}
+
+		$filePath = str_replace( '\\', '/', $filePath );
+		if ( Less_Environment::isPathRelative( $filePath ) ) {
+
+			if ( Less_Parser::$options['relativeUrls'] ) {
+				$temp = $this->currentFileInfo['currentDirectory'];
+			} else {
+				$temp = $this->currentFileInfo['entryPath'];
+			}
+
+			if ( !empty( $temp ) ) {
+				$filePath = Less_Environment::normalizePath( rtrim( $temp, '/' ).'/'.$filePath );
+			}
+
+		}
+
+		// detect the mimetype if not given
+		if ( $args < 2 ) {
+
+			/* incomplete
+			$mime = require('mime');
+			mimetype = mime.lookup(path);
+
+			// use base 64 unless it's an ASCII or UTF-8 format
+			var charset = mime.charsets.lookup(mimetype);
+			useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
+			if (useBase64) mimetype += ';base64';
+			*/
+
+			$mimetype = Less_Mime::lookup( $filePath );
+
+			$charset = Less_Mime::charsets_lookup( $mimetype );
+			$useBase64 = !in_array( $charset, array( 'US-ASCII', 'UTF-8' ) );
+			if ( $useBase64 ) { $mimetype .= ';base64';
+			}
+
+		} else {
+			$useBase64 = preg_match( '/;base64$/', $mimetype );
+		}
+
+		if ( file_exists( $filePath ) ) {
+			$buf = @file_get_contents( $filePath );
+		} else {
+			$buf = false;
+		}
+
+		// IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
+		// and the --ieCompat flag is enabled, return a normal url() instead.
+		$DATA_URI_MAX_KB = 32;
+		$fileSizeInKB = round( strlen( $buf ) / 1024 );
+		if ( $fileSizeInKB >= $DATA_URI_MAX_KB ) {
+			$url = new Less_Tree_Url( ( $filePathNode ? $filePathNode : $mimetypeNode ), $this->currentFileInfo );
+			return $url->compile( $this );
+		}
+
+		if ( $buf ) {
+			$buf = $useBase64 ? base64_encode( $buf ) : rawurlencode( $buf );
+			$filePath = '"data:' . $mimetype . ',' . $buf . '"';
+		}
+
+		return new Less_Tree_Url( new Less_Tree_Anonymous( $filePath ) );
+	}
+
+	// svg-gradient
+	public function svggradient( $direction ) {
+		$throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
+		$arguments = func_get_args();
+
+		if ( count( $arguments ) < 3 ) {
+			throw new Less_Exception_Compiler( $throw_message );
+		}
+
+		$stops = array_slice( $arguments, 1 );
+		$gradientType = 'linear';
+		$rectangleDimension = 'x="0" y="0" width="1" height="1"';
+		$useBase64 = true;
+		$directionValue = $direction->toCSS();
+
+		switch ( $directionValue ) {
+			case "to bottom":
+				$gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
+				break;
+			case "to right":
+				$gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
+				break;
+			case "to bottom right":
+				$gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
+				break;
+			case "to top right":
+				$gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
+				break;
+			case "ellipse":
+			case "ellipse at center":
+				$gradientType = "radial";
+				$gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
+				$rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
+				break;
+			default:
+				throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
+		}
+
+		$returner = '<?xml version="1.0" ?>' .
+			'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
+			'<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
+
+		for ( $i = 0; $i < count( $stops ); $i++ ) {
+			if ( is_object( $stops[$i] ) && property_exists( $stops[$i], 'value' ) ) {
+				$color = $stops[$i]->value[0];
+				$position = $stops[$i]->value[1];
+			} else {
+				$color = $stops[$i];
+				$position = null;
+			}
+
+			if ( !( $color instanceof Less_Tree_Color ) || ( !( ( $i === 0 || $i + 1 === count( $stops ) ) && $position === null ) && !( $position instanceof Less_Tree_Dimension ) ) ) {
+				throw new Less_Exception_Compiler( $throw_message );
+			}
+			if ( $position ) {
+				$positionValue = $position->toCSS();
+			} elseif ( $i === 0 ) {
+				$positionValue = '0%';
+			} else {
+				$positionValue = '100%';
+			}
+			$alpha = $color->alpha;
+			$returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ( $alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '' ) . '/>';
+		}
+
+		$returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
+
+		if ( $useBase64 ) {
+			$returner = "'data:image/svg+xml;base64,".base64_encode( $returner )."'";
+		} else {
+			$returner = "'data:image/svg+xml,".$returner."'";
+		}
+
+		return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) );
+	}
+
+	/**
+	 * Php version of javascript's `encodeURIComponent` function
+	 *
+	 * @param string $string The string to encode
+	 * @return string The encoded string
+	 */
+	public static function encodeURIComponent( $string ) {
+		$revert = array( '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' );
+		return strtr( rawurlencode( $string ), $revert );
+	}
+
+	// Color Blending
+	// ref: http://www.w3.org/TR/compositing-1
+
+	public function colorBlend( $mode, $color1, $color2 ) {
+		$ab = $color1->alpha;	// backdrop
+		$as = $color2->alpha;	// source
+		$r = array();			// result
+
+		$ar = $as + $ab * ( 1 - $as );
+		for ( $i = 0; $i < 3; $i++ ) {
+			$cb = $color1->rgb[$i] / 255;
+			$cs = $color2->rgb[$i] / 255;
+			$cr = call_user_func( $mode, $cb, $cs );
+			if ( $ar ) {
+				$cr = ( $as * $cs + $ab * ( $cb - $as * ( $cb + $cs - $cr ) ) ) / $ar;
+			}
+			$r[$i] = $cr * 255;
+		}
+
+		return new Less_Tree_Color( $r, $ar );
+	}
+
+	public function multiply( $color1 = null, $color2 = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to multiply must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to multiply must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return $this->colorBlend( array( $this,'colorBlendMultiply' ),  $color1, $color2 );
+	}
+
+	private function colorBlendMultiply( $cb, $cs ) {
+		return $cb * $cs;
+	}
+
+	public function screen( $color1 = null, $color2 = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to screen must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to screen must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return $this->colorBlend( array( $this,'colorBlendScreen' ),  $color1, $color2 );
+	}
+
+	private function colorBlendScreen( $cb, $cs ) {
+		return $cb + $cs - $cb * $cs;
+	}
+
+	public function overlay( $color1 = null, $color2 = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to overlay must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to overlay must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return $this->colorBlend( array( $this,'colorBlendOverlay' ),  $color1, $color2 );
+	}
+
+	private function colorBlendOverlay( $cb, $cs ) {
+		$cb *= 2;
+		return ( $cb <= 1 )
+			? $this->colorBlendMultiply( $cb, $cs )
+			: $this->colorBlendScreen( $cb - 1, $cs );
+	}
+
+	public function softlight( $color1 = null, $color2 = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to softlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to softlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return $this->colorBlend( array( $this,'colorBlendSoftlight' ),  $color1, $color2 );
+	}
+
+	private function colorBlendSoftlight( $cb, $cs ) {
+		$d = 1;
+		$e = $cb;
+		if ( $cs > 0.5 ) {
+			$e = 1;
+			$d = ( $cb > 0.25 ) ? sqrt( $cb )
+				: ( ( 16 * $cb - 12 ) * $cb + 4 ) * $cb;
+		}
+		return $cb - ( 1 - 2 * $cs ) * $e * ( $d - $cb );
+	}
+
+	public function hardlight( $color1 = null, $color2 = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to hardlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to hardlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return $this->colorBlend( array( $this,'colorBlendHardlight' ),  $color1, $color2 );
+	}
+
+	private function colorBlendHardlight( $cb, $cs ) {
+		return $this->colorBlendOverlay( $cs, $cb );
+	}
+
+	public function difference( $color1 = null, $color2 = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to difference must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to difference must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return $this->colorBlend( array( $this,'colorBlendDifference' ),  $color1, $color2 );
+	}
+
+	private function colorBlendDifference( $cb, $cs ) {
+		return abs( $cb - $cs );
+	}
+
+	public function exclusion( $color1 = null, $color2 = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to exclusion must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to exclusion must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return $this->colorBlend( array( $this,'colorBlendExclusion' ),  $color1, $color2 );
+	}
+
+	private function colorBlendExclusion( $cb, $cs ) {
+		return $cb + $cs - 2 * $cb * $cs;
+	}
+
+	public function average( $color1 = null, $color2 = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to average must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to average must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return $this->colorBlend( array( $this,'colorBlendAverage' ),  $color1, $color2 );
+	}
+
+	// non-w3c functions:
+	public function colorBlendAverage( $cb, $cs ) {
+		return ( $cb + $cs ) / 2;
+	}
+
+	public function negation( $color1 = null, $color2 = null ) {
+		if ( !$color1 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The first argument to negation must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+		if ( !$color2 instanceof Less_Tree_Color ) {
+			throw new Less_Exception_Compiler( 'The second argument to negation must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+		}
+
+		return $this->colorBlend( array( $this,'colorBlendNegation' ),  $color1, $color2 );
+	}
+
+	public function colorBlendNegation( $cb, $cs ) {
+		return 1 - abs( $cb + $cs - 1 );
+	}
+
+	// ~ End of Color Blending
+
+}

+ 17 - 0
lessphp/lib/Less/Less.php.combine

@@ -0,0 +1,17 @@
+
+./Parser.php
+./Colors.php
+./Environment.php
+./Functions.php
+./Mime.php
+./Tree.php
+./Output.php
+./Visitor.php
+./VisitorReplacing.php
+./Configurable.php
+./Tree
+./Visitor
+./Exception/Parser.php
+./Exception/
+./Output
+./SourceMap

+ 41 - 0
lessphp/lib/Less/Mime.php

@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * Mime lookup
+ *
+ * @package Less
+ * @subpackage node
+ */
+class Less_Mime {
+
+	// this map is intentionally incomplete
+	// if you want more, install 'mime' dep
+	static $_types = array(
+			'.htm' => 'text/html',
+			'.html' => 'text/html',
+			'.gif' => 'image/gif',
+			'.jpg' => 'image/jpeg',
+			'.jpeg' => 'image/jpeg',
+			'.png' => 'image/png',
+			'.ttf' => 'application/x-font-ttf',
+			'.otf' => 'application/x-font-otf',
+			'.eot' => 'application/vnd.ms-fontobject',
+			'.woff' => 'application/x-font-woff',
+			'.svg' => 'image/svg+xml',
+			);
+
+	public static function lookup( $filepath ) {
+		$parts = explode( '.', $filepath );
+		$ext = '.'.strtolower( array_pop( $parts ) );
+
+		if ( !isset( self::$_types[$ext] ) ) {
+			return null;
+		}
+		return self::$_types[$ext];
+	}
+
+	public static function charsets_lookup( $type = null ) {
+		// assumes all text types are UTF-8
+		return $type && preg_match( '/^text\//', $type ) ? 'UTF-8' : '';
+	}
+}

+ 48 - 0
lessphp/lib/Less/Output.php

@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Parser output
+ *
+ * @package Less
+ * @subpackage output
+ */
+class Less_Output {
+
+	/**
+	 * Output holder
+	 *
+	 * @var string
+	 */
+	protected $strs = array();
+
+	/**
+	 * Adds a chunk to the stack
+	 *
+	 * @param string $chunk The chunk to output
+	 * @param Less_FileInfo $fileInfo The file information
+	 * @param integer $index The index
+	 * @param mixed $mapLines
+	 */
+	public function add( $chunk, $fileInfo = null, $index = 0, $mapLines = null ) {
+		$this->strs[] = $chunk;
+	}
+
+	/**
+	 * Is the output empty?
+	 *
+	 * @return boolean
+	 */
+	public function isEmpty() {
+		return count( $this->strs ) === 0;
+	}
+
+	/**
+	 * Converts the output to string
+	 *
+	 * @return string
+	 */
+	public function toString() {
+		return implode( '', $this->strs );
+	}
+
+}

+ 119 - 0
lessphp/lib/Less/Output/Mapped.php

@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * Parser output with source map
+ *
+ * @package Less
+ * @subpackage Output
+ */
+class Less_Output_Mapped extends Less_Output {
+
+	/**
+	 * The source map generator
+	 *
+	 * @var Less_SourceMap_Generator
+	 */
+	protected $generator;
+
+	/**
+	 * Current line
+	 *
+	 * @var integer
+	 */
+	protected $lineNumber = 0;
+
+	/**
+	 * Current column
+	 *
+	 * @var integer
+	 */
+	protected $column = 0;
+
+	/**
+	 * Array of contents map (file and its content)
+	 *
+	 * @var array
+	 */
+	protected $contentsMap = array();
+
+	/**
+	 * Constructor
+	 *
+	 * @param array $contentsMap Array of filename to contents map
+	 * @param Less_SourceMap_Generator $generator
+	 */
+	public function __construct( array $contentsMap, $generator ) {
+		$this->contentsMap = $contentsMap;
+		$this->generator = $generator;
+	}
+
+	/**
+	 * Adds a chunk to the stack
+	 * The $index for less.php may be different from less.js since less.php does not chunkify inputs
+	 *
+	 * @param string $chunk
+	 * @param string $fileInfo
+	 * @param integer $index
+	 * @param mixed $mapLines
+	 */
+	public function add( $chunk, $fileInfo = null, $index = 0, $mapLines = null ) {
+		// ignore adding empty strings
+		if ( $chunk === '' ) {
+			return;
+		}
+
+		$sourceLines = array();
+		$sourceColumns = ' ';
+
+		if ( $fileInfo ) {
+
+			$url = $fileInfo['currentUri'];
+
+			if ( isset( $this->contentsMap[$url] ) ) {
+				$inputSource = substr( $this->contentsMap[$url], 0, $index );
+				$sourceLines = explode( "\n", $inputSource );
+				$sourceColumns = end( $sourceLines );
+			} else {
+				throw new Exception( 'Filename '.$url.' not in contentsMap' );
+			}
+
+		}
+
+		$lines = explode( "\n", $chunk );
+		$columns = end( $lines );
+
+		if ( $fileInfo ) {
+
+			if ( !$mapLines ) {
+				$this->generator->addMapping(
+						$this->lineNumber + 1,					// generated_line
+						$this->column,							// generated_column
+						count( $sourceLines ),					// original_line
+						strlen( $sourceColumns ),					// original_column
+						$fileInfo
+				);
+			} else {
+				for ( $i = 0, $count = count( $lines ); $i < $count; $i++ ) {
+					$this->generator->addMapping(
+						$this->lineNumber + $i + 1,				// generated_line
+						$i === 0 ? $this->column : 0,			// generated_column
+						count( $sourceLines ) + $i,				// original_line
+						$i === 0 ? strlen( $sourceColumns ) : 0, 	// original_column
+						$fileInfo
+					);
+				}
+			}
+		}
+
+		if ( count( $lines ) === 1 ) {
+			$this->column += strlen( $columns );
+		} else {
+			$this->lineNumber += count( $lines ) - 1;
+			$this->column = strlen( $columns );
+		}
+
+		// add only chunk
+		parent::add( $chunk );
+	}
+
+}

+ 2691 - 0
lessphp/lib/Less/Parser.php

@@ -0,0 +1,2691 @@
+<?php
+
+require_once dirname( __FILE__ ).'/Cache.php';
+
+/**
+ * Class for parsing and compiling less files into css
+ *
+ * @package Less
+ * @subpackage parser
+ *
+ */
+class Less_Parser {
+
+	/**
+	 * Default parser options
+	 */
+	public static $default_options = array(
+		'compress'				=> false,			// option - whether to compress
+		'strictUnits'			=> false,			// whether units need to evaluate correctly
+		'strictMath'			=> false,			// whether math has to be within parenthesis
+		'relativeUrls'			=> true,			// option - whether to adjust URL's to be relative
+		'urlArgs'				=> '',				// whether to add args into url tokens
+		'numPrecision'			=> 8,
+
+		'import_dirs'			=> array(),
+		'import_callback'		=> null,
+		'cache_dir'				=> null,
+		'cache_method'			=> 'php', 			// false, 'serialize', 'php', 'var_export', 'callback';
+		'cache_callback_get'	=> null,
+		'cache_callback_set'	=> null,
+
+		'sourceMap'				=> false,			// whether to output a source map
+		'sourceMapBasepath'		=> null,
+		'sourceMapWriteTo'		=> null,
+		'sourceMapURL'			=> null,
+
+		'indentation' 			=> '  ',
+
+		'plugins'				=> array(),
+
+	);
+
+	public static $options = array();
+
+	private $input;					// Less input string
+	private $input_len;				// input string length
+	private $pos;					// current index in `input`
+	private $saveStack = array();	// holds state for backtracking
+	private $furthest;
+	private $mb_internal_encoding = ''; // for remember exists value of mbstring.internal_encoding
+
+	/**
+	 * @var Less_Environment
+	 */
+	private $env;
+
+	protected $rules = array();
+
+	private static $imports = array();
+
+	public static $has_extends = false;
+
+	public static $next_id = 0;
+
+	/**
+	 * Filename to contents of all parsed the files
+	 *
+	 * @var array
+	 */
+	public static $contentsMap = array();
+
+	/**
+	 * @param Less_Environment|array|null $env
+	 */
+	public function __construct( $env = null ) {
+		// Top parser on an import tree must be sure there is one "env"
+		// which will then be passed around by reference.
+		if ( $env instanceof Less_Environment ) {
+			$this->env = $env;
+		} else {
+			$this->SetOptions( Less_Parser::$default_options );
+			$this->Reset( $env );
+		}
+
+		// mbstring.func_overload > 1 bugfix
+		// The encoding value must be set for each source file,
+		// therefore, to conserve resources and improve the speed of this design is taken here
+		if ( ini_get( 'mbstring.func_overload' ) ) {
+			$this->mb_internal_encoding = ini_get( 'mbstring.internal_encoding' );
+			@ini_set( 'mbstring.internal_encoding', 'ascii' );
+		}
+
+	}
+
+	/**
+	 * Reset the parser state completely
+	 *
+	 */
+	public function Reset( $options = null ) {
+		$this->rules = array();
+		self::$imports = array();
+		self::$has_extends = false;
+		self::$imports = array();
+		self::$contentsMap = array();
+
+		$this->env = new Less_Environment( $options );
+
+		// set new options
+		if ( is_array( $options ) ) {
+			$this->SetOptions( Less_Parser::$default_options );
+			$this->SetOptions( $options );
+		}
+
+		$this->env->Init();
+	}
+
+	/**
+	 * Set one or more compiler options
+	 *  options: import_dirs, cache_dir, cache_method
+	 *
+	 */
+	public function SetOptions( $options ) {
+		foreach ( $options as $option => $value ) {
+			$this->SetOption( $option, $value );
+		}
+	}
+
+	/**
+	 * Set one compiler option
+	 *
+	 */
+	public function SetOption( $option, $value ) {
+		switch ( $option ) {
+
+			case 'import_dirs':
+				$this->SetImportDirs( $value );
+			return;
+
+			case 'cache_dir':
+				if ( is_string( $value ) ) {
+					Less_Cache::SetCacheDir( $value );
+					Less_Cache::CheckCacheDir();
+				}
+			return;
+		}
+
+		Less_Parser::$options[$option] = $value;
+	}
+
+	/**
+	 * Registers a new custom function
+	 *
+	 * @param string $name function name
+	 * @param callable $callback callback
+	 */
+	public function registerFunction( $name, $callback ) {
+		$this->env->functions[$name] = $callback;
+	}
+
+	/**
+	 * Removed an already registered function
+	 *
+	 * @param string $name function name
+	 */
+	public function unregisterFunction( $name ) {
+		if ( isset( $this->env->functions[$name] ) )
+			unset( $this->env->functions[$name] );
+	}
+
+	/**
+	 * Get the current css buffer
+	 *
+	 * @return string
+	 */
+	public function getCss() {
+		$precision = ini_get( 'precision' );
+		@ini_set( 'precision', 16 );
+		$locale = setlocale( LC_NUMERIC, 0 );
+		setlocale( LC_NUMERIC, "C" );
+
+		try {
+
+			$root = new Less_Tree_Ruleset( array(), $this->rules );
+			$root->root = true;
+			$root->firstRoot = true;
+
+			$this->PreVisitors( $root );
+
+			self::$has_extends = false;
+			$evaldRoot = $root->compile( $this->env );
+
+			$this->PostVisitors( $evaldRoot );
+
+			if ( Less_Parser::$options['sourceMap'] ) {
+				$generator = new Less_SourceMap_Generator( $evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options );
+				// will also save file
+				// FIXME: should happen somewhere else?
+				$css = $generator->generateCSS();
+			} else {
+				$css = $evaldRoot->toCSS();
+			}
+
+			if ( Less_Parser::$options['compress'] ) {
+				$css = preg_replace( '/(^(\s)+)|((\s)+$)/', '', $css );
+			}
+
+		} catch ( Exception $exc ) {
+			// Intentional fall-through so we can reset environment
+		}
+
+		// reset php settings
+		@ini_set( 'precision', $precision );
+		setlocale( LC_NUMERIC, $locale );
+
+		// If you previously defined $this->mb_internal_encoding
+		// is required to return the encoding as it was before
+		if ( $this->mb_internal_encoding != '' ) {
+			@ini_set( "mbstring.internal_encoding", $this->mb_internal_encoding );
+			$this->mb_internal_encoding = '';
+		}
+
+		// Rethrow exception after we handled resetting the environment
+		if ( !empty( $exc ) ) {
+			throw $exc;
+		}
+
+		return $css;
+	}
+
+	public function findValueOf( $varName ) {
+		foreach ( $this->rules as $rule ) {
+			if ( isset( $rule->variable ) && ( $rule->variable == true ) && ( str_replace( "@", "", $rule->name ) == $varName ) ) {
+				return $this->getVariableValue( $rule );
+			}
+		}
+		return null;
+	}
+
+	/**
+	 *
+	 * this function gets the private rules variable and returns an array of the found variables
+	 * it uses a helper method getVariableValue() that contains the logic ot fetch the value from the rule object
+	 *
+	 * @return array
+	 */
+	public function getVariables() {
+		$variables = array();
+
+		$not_variable_type = array(
+			'Comment',   // this include less comments ( // ) and css comments (/* */)
+			'Import',    // do not search variables in included files @import
+			'Ruleset',   // selectors (.someclass, #someid, …)
+			'Operation', //
+		);
+
+		// @TODO run compilation if not runned yet
+		foreach ( $this->rules as $key => $rule ) {
+			if ( in_array( $rule->type, $not_variable_type ) ) {
+				continue;
+			}
+
+			// Note: it seems rule->type is always Rule when variable = true
+			if ( $rule->type == 'Rule' && $rule->variable ) {
+				$variables[$rule->name] = $this->getVariableValue( $rule );
+			} else {
+				if ( $rule->type == 'Comment' ) {
+					$variables[] = $this->getVariableValue( $rule );
+				}
+			}
+		}
+		return $variables;
+	}
+
+	public function findVarByName( $var_name ) {
+		foreach ( $this->rules as $rule ) {
+			if ( isset( $rule->variable ) && ( $rule->variable == true ) ) {
+				if ( $rule->name == $var_name ) {
+					return $this->getVariableValue( $rule );
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 *
+	 * This method gets the value of the less variable from the rules object.
+	 * Since the objects vary here we add the logic for extracting the css/less value.
+	 *
+	 * @param $var
+	 *
+	 * @return bool|string
+	 */
+	private function getVariableValue( $var ) {
+		if ( !is_a( $var, 'Less_Tree' ) ) {
+			throw new Exception( 'var is not a Less_Tree object' );
+		}
+
+		switch ( $var->type ) {
+			case 'Color':
+				return $this->rgb2html( $var->rgb );
+			case 'Unit':
+				return $var->value. $var->unit->numerator[0];
+			case 'Variable':
+				return $this->findVarByName( $var->name );
+			case 'Keyword':
+				return $var->value;
+			case 'Rule':
+				return $this->getVariableValue( $var->value );
+			case 'Value':
+				$value = '';
+				foreach ( $var->value as $sub_value ) {
+					$value .= $this->getVariableValue( $sub_value ).' ';
+				}
+				return $value;
+			case 'Quoted':
+				return $var->quote.$var->value.$var->quote;
+			case 'Dimension':
+				$value = $var->value;
+				if ( $var->unit && $var->unit->numerator ) {
+					$value .= $var->unit->numerator[0];
+				}
+				return $value;
+			case 'Expression':
+				$value = "";
+				foreach ( $var->value as $item ) {
+					$value .= $this->getVariableValue( $item )." ";
+				}
+				return $value;
+			case 'Operation':
+				throw new Exception( 'getVariables() require Less to be compiled. please use $parser->getCss() before calling getVariables()' );
+			case 'Comment':
+			case 'Import':
+			case 'Ruleset':
+			default:
+				throw new Exception( "type missing in switch/case getVariableValue for ".$var->type );
+		}
+		return false;
+	}
+
+	private function rgb2html( $r, $g = -1, $b = -1 ) {
+		if ( is_array( $r ) && sizeof( $r ) == 3 )
+			list( $r, $g, $b ) = $r;
+
+		$r = intval( $r ); $g = intval( $g );
+		$b = intval( $b );
+
+		$r = dechex( $r < 0 ? 0 : ( $r > 255 ? 255 : $r ) );
+		$g = dechex( $g < 0 ? 0 : ( $g > 255 ? 255 : $g ) );
+		$b = dechex( $b < 0 ? 0 : ( $b > 255 ? 255 : $b ) );
+
+		$color = ( strlen( $r ) < 2 ? '0' : '' ).$r;
+		$color .= ( strlen( $g ) < 2 ? '0' : '' ).$g;
+		$color .= ( strlen( $b ) < 2 ? '0' : '' ).$b;
+		return '#'.$color;
+	}
+
+	/**
+	 * Run pre-compile visitors
+	 *
+	 */
+	private function PreVisitors( $root ) {
+		if ( Less_Parser::$options['plugins'] ) {
+			foreach ( Less_Parser::$options['plugins'] as $plugin ) {
+				if ( !empty( $plugin->isPreEvalVisitor ) ) {
+					$plugin->run( $root );
+				}
+			}
+		}
+	}
+
+	/**
+	 * Run post-compile visitors
+	 *
+	 */
+	private function PostVisitors( $evaldRoot ) {
+		$visitors = array();
+		$visitors[] = new Less_Visitor_joinSelector();
+		if ( self::$has_extends ) {
+			$visitors[] = new Less_Visitor_processExtends();
+		}
+		$visitors[] = new Less_Visitor_toCSS();
+
+		if ( Less_Parser::$options['plugins'] ) {
+			foreach ( Less_Parser::$options['plugins'] as $plugin ) {
+				if ( property_exists( $plugin, 'isPreEvalVisitor' ) && $plugin->isPreEvalVisitor ) {
+					continue;
+				}
+
+				if ( property_exists( $plugin, 'isPreVisitor' ) && $plugin->isPreVisitor ) {
+					array_unshift( $visitors, $plugin );
+				} else {
+					$visitors[] = $plugin;
+				}
+			}
+		}
+
+		for ( $i = 0; $i < count( $visitors ); $i++ ) {
+			$visitors[$i]->run( $evaldRoot );
+		}
+
+	}
+
+	/**
+	 * Parse a Less string into css
+	 *
+	 * @param string $str The string to convert
+	 * @param string $uri_root The url of the file
+	 * @return Less_Tree_Ruleset|Less_Parser
+	 */
+	public function parse( $str, $file_uri = null ) {
+		if ( !$file_uri ) {
+			$uri_root = '';
+			$filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less';
+		} else {
+			$file_uri = self::WinPath( $file_uri );
+			$filename = $file_uri;
+			$uri_root = dirname( $file_uri );
+		}
+
+		$previousFileInfo = $this->env->currentFileInfo;
+		$uri_root = self::WinPath( $uri_root );
+		$this->SetFileInfo( $filename, $uri_root );
+
+		$this->input = $str;
+		$this->_parse();
+
+		if ( $previousFileInfo ) {
+			$this->env->currentFileInfo = $previousFileInfo;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Parse a Less string from a given file
+	 *
+	 * @throws Less_Exception_Parser
+	 * @param string $filename The file to parse
+	 * @param string $uri_root The url of the file
+	 * @param bool $returnRoot Indicates whether the return value should be a css string a root node
+	 * @return Less_Tree_Ruleset|Less_Parser
+	 */
+	public function parseFile( $filename, $uri_root = '', $returnRoot = false ) {
+		if ( !file_exists( $filename ) ) {
+			$this->Error( sprintf( 'File `%s` not found.', $filename ) );
+		}
+
+		// fix uri_root?
+		// Instead of The mixture of file path for the first argument and directory path for the second argument has bee
+		if ( !$returnRoot && !empty( $uri_root ) && basename( $uri_root ) == basename( $filename ) ) {
+			$uri_root = dirname( $uri_root );
+		}
+
+		$previousFileInfo = $this->env->currentFileInfo;
+
+		if ( $filename ) {
+			$filename = self::AbsPath( $filename, true );
+		}
+		$uri_root = self::WinPath( $uri_root );
+
+		$this->SetFileInfo( $filename, $uri_root );
+
+		self::AddParsedFile( $filename );
+
+		if ( $returnRoot ) {
+			$rules = $this->GetRules( $filename );
+			$return = new Less_Tree_Ruleset( array(), $rules );
+		} else {
+			$this->_parse( $filename );
+			$return = $this;
+		}
+
+		if ( $previousFileInfo ) {
+			$this->env->currentFileInfo = $previousFileInfo;
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Allows a user to set variables values
+	 * @param array $vars
+	 * @return Less_Parser
+	 */
+	public function ModifyVars( $vars ) {
+		$this->input = Less_Parser::serializeVars( $vars );
+		$this->_parse();
+
+		return $this;
+	}
+
+	/**
+	 * @param string $filename
+	 */
+	public function SetFileInfo( $filename, $uri_root = '' ) {
+		$filename = Less_Environment::normalizePath( $filename );
+		$dirname = preg_replace( '/[^\/\\\\]*$/', '', $filename );
+
+		if ( !empty( $uri_root ) ) {
+			$uri_root = rtrim( $uri_root, '/' ).'/';
+		}
+
+		$currentFileInfo = array();
+
+		// entry info
+		if ( isset( $this->env->currentFileInfo ) ) {
+			$currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
+			$currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
+			$currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];
+
+		} else {
+			$currentFileInfo['entryPath'] = $dirname;
+			$currentFileInfo['entryUri'] = $uri_root;
+			$currentFileInfo['rootpath'] = $dirname;
+		}
+
+		$currentFileInfo['currentDirectory'] = $dirname;
+		$currentFileInfo['currentUri'] = $uri_root.basename( $filename );
+		$currentFileInfo['filename'] = $filename;
+		$currentFileInfo['uri_root'] = $uri_root;
+
+		// inherit reference
+		if ( isset( $this->env->currentFileInfo['reference'] ) && $this->env->currentFileInfo['reference'] ) {
+			$currentFileInfo['reference'] = true;
+		}
+
+		$this->env->currentFileInfo = $currentFileInfo;
+	}
+
+	/**
+	 * @deprecated 1.5.1.2
+	 *
+	 */
+	public function SetCacheDir( $dir ) {
+		if ( !file_exists( $dir ) ) {
+			if ( mkdir( $dir ) ) {
+				return true;
+			}
+			throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: '.$dir );
+
+		} elseif ( !is_dir( $dir ) ) {
+			throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: '.$dir );
+
+		} elseif ( !is_writable( $dir ) ) {
+			throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: '.$dir );
+
+		} else {
+			$dir = self::WinPath( $dir );
+			Less_Cache::$cache_dir = rtrim( $dir, '/' ).'/';
+			return true;
+		}
+	}
+
+	/**
+	 * Set a list of directories or callbacks the parser should use for determining import paths
+	 *
+	 * @param array $dirs
+	 */
+	public function SetImportDirs( $dirs ) {
+		Less_Parser::$options['import_dirs'] = array();
+
+		foreach ( $dirs as $path => $uri_root ) {
+
+			$path = self::WinPath( $path );
+			if ( !empty( $path ) ) {
+				$path = rtrim( $path, '/' ).'/';
+			}
+
+			if ( !is_callable( $uri_root ) ) {
+				$uri_root = self::WinPath( $uri_root );
+				if ( !empty( $uri_root ) ) {
+					$uri_root = rtrim( $uri_root, '/' ).'/';
+				}
+			}
+
+			Less_Parser::$options['import_dirs'][$path] = $uri_root;
+		}
+	}
+
+	/**
+	 * @param string $file_path
+	 */
+	private function _parse( $file_path = null ) {
+		$this->rules = array_merge( $this->rules, $this->GetRules( $file_path ) );
+	}
+
+	/**
+	 * Return the results of parsePrimary for $file_path
+	 * Use cache and save cached results if possible
+	 *
+	 * @param string|null $file_path
+	 */
+	private function GetRules( $file_path ) {
+		$this->SetInput( $file_path );
+
+		$cache_file = $this->CacheFile( $file_path );
+		if ( $cache_file ) {
+			if ( Less_Parser::$options['cache_method'] == 'callback' ) {
+				if ( is_callable( Less_Parser::$options['cache_callback_get'] ) ) {
+					$cache = call_user_func_array(
+						Less_Parser::$options['cache_callback_get'],
+						array( $this, $file_path, $cache_file )
+					);
+
+					if ( $cache ) {
+						$this->UnsetInput();
+						return $cache;
+					}
+				}
+
+			} elseif ( file_exists( $cache_file ) ) {
+				switch ( Less_Parser::$options['cache_method'] ) {
+
+					// Using serialize
+					// Faster but uses more memory
+					case 'serialize':
+						$cache = unserialize( file_get_contents( $cache_file ) );
+						if ( $cache ) {
+							touch( $cache_file );
+							$this->UnsetInput();
+							return $cache;
+						}
+						break;
+
+						// Using generated php code
+					case 'var_export':
+					case 'php':
+						$this->UnsetInput();
+						return include $cache_file;
+				}
+			}
+		}
+
+		$rules = $this->parsePrimary();
+
+		if ( $this->pos < $this->input_len ) {
+			throw new Less_Exception_Chunk( $this->input, null, $this->furthest, $this->env->currentFileInfo );
+		}
+
+		$this->UnsetInput();
+
+		// save the cache
+		if ( $cache_file ) {
+			if ( Less_Parser::$options['cache_method'] == 'callback' ) {
+				if ( is_callable( Less_Parser::$options['cache_callback_set'] ) ) {
+					call_user_func_array(
+						Less_Parser::$options['cache_callback_set'],
+						array( $this, $file_path, $cache_file, $rules )
+					);
+				}
+
+			} else {
+				// msg('write cache file');
+				switch ( Less_Parser::$options['cache_method'] ) {
+					case 'serialize':
+						file_put_contents( $cache_file, serialize( $rules ) );
+						break;
+					case 'php':
+						file_put_contents( $cache_file, '<?php return '.self::ArgString( $rules ).'; ?>' );
+						break;
+					case 'var_export':
+						// Requires __set_state()
+						file_put_contents( $cache_file, '<?php return '.var_export( $rules, true ).'; ?>' );
+						break;
+				}
+
+				Less_Cache::CleanCache();
+			}
+		}
+
+		return $rules;
+	}
+
+	/**
+	 * Set up the input buffer
+	 *
+	 */
+	public function SetInput( $file_path ) {
+		if ( $file_path ) {
+			$this->input = file_get_contents( $file_path );
+		}
+
+		$this->pos = $this->furthest = 0;
+
+		// Remove potential UTF Byte Order Mark
+		$this->input = preg_replace( '/\\G\xEF\xBB\xBF/', '', $this->input );
+		$this->input_len = strlen( $this->input );
+
+		if ( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ) {
+			$uri = $this->env->currentFileInfo['currentUri'];
+			Less_Parser::$contentsMap[$uri] = $this->input;
+		}
+
+	}
+
+	/**
+	 * Free up some memory
+	 *
+	 */
+	public function UnsetInput() {
+		unset( $this->input, $this->pos, $this->input_len, $this->furthest );
+		$this->saveStack = array();
+	}
+
+	public function CacheFile( $file_path ) {
+		if ( $file_path && $this->CacheEnabled() ) {
+
+			$env = get_object_vars( $this->env );
+			unset( $env['frames'] );
+
+			$parts = array();
+			$parts[] = $file_path;
+			$parts[] = filesize( $file_path );
+			$parts[] = filemtime( $file_path );
+			$parts[] = $env;
+			$parts[] = Less_Version::cache_version;
+			$parts[] = Less_Parser::$options['cache_method'];
+			return Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1( json_encode( $parts ) ), 16, 36 ) . '.lesscache';
+		}
+	}
+
+	static function AddParsedFile( $file ) {
+		self::$imports[] = $file;
+	}
+
+	static function AllParsedFiles() {
+		return self::$imports;
+	}
+
+	/**
+	 * @param string $file
+	 */
+	static function FileParsed( $file ) {
+		return in_array( $file, self::$imports );
+	}
+
+	function save() {
+		$this->saveStack[] = $this->pos;
+	}
+
+	private function restore() {
+		$this->pos = array_pop( $this->saveStack );
+	}
+
+	private function forget() {
+		array_pop( $this->saveStack );
+	}
+
+	/**
+	 * Determine if the character at the specified offset from the current position is a white space.
+	 *
+	 * @param int $offset
+	 *
+	 * @return bool
+	 */
+	private function isWhitespace( $offset = 0 ) {
+		return strpos( " \t\n\r\v\f", $this->input[$this->pos + $offset] ) !== false;
+	}
+
+	/**
+	 * Parse from a token, regexp or string, and move forward if match
+	 *
+	 * @param array $toks
+	 * @return array
+	 */
+	private function match( $toks ) {
+		// The match is confirmed, add the match length to `this::pos`,
+		// and consume any extra white-space characters (' ' || '\n')
+		// which come after that. The reason for this is that LeSS's
+		// grammar is mostly white-space insensitive.
+		//
+
+		foreach ( $toks as $tok ) {
+
+			$char = $tok[0];
+
+			if ( $char === '/' ) {
+				$match = $this->MatchReg( $tok );
+
+				if ( $match ) {
+					return count( $match ) === 1 ? $match[0] : $match;
+				}
+
+			} elseif ( $char === '#' ) {
+				$match = $this->MatchChar( $tok[1] );
+
+			} else {
+				// Non-terminal, match using a function call
+				$match = $this->$tok();
+
+			}
+
+			if ( $match ) {
+				return $match;
+			}
+		}
+	}
+
+	/**
+	 * @param string[] $toks
+	 *
+	 * @return string
+	 */
+	private function MatchFuncs( $toks ) {
+		if ( $this->pos < $this->input_len ) {
+			foreach ( $toks as $tok ) {
+				$match = $this->$tok();
+				if ( $match ) {
+					return $match;
+				}
+			}
+		}
+
+	}
+
+	// Match a single character in the input,
+	private function MatchChar( $tok ) {
+		if ( ( $this->pos < $this->input_len ) && ( $this->input[$this->pos] === $tok ) ) {
+			$this->skipWhitespace( 1 );
+			return $tok;
+		}
+	}
+
+	// Match a regexp from the current start point
+	private function MatchReg( $tok ) {
+		if ( preg_match( $tok, $this->input, $match, 0, $this->pos ) ) {
+			$this->skipWhitespace( strlen( $match[0] ) );
+			return $match;
+		}
+	}
+
+	/**
+	 * Same as match(), but don't change the state of the parser,
+	 * just return the match.
+	 *
+	 * @param string $tok
+	 * @return integer
+	 */
+	public function PeekReg( $tok ) {
+		return preg_match( $tok, $this->input, $match, 0, $this->pos );
+	}
+
+	/**
+	 * @param string $tok
+	 */
+	public function PeekChar( $tok ) {
+		// return ($this->input[$this->pos] === $tok );
+		return ( $this->pos < $this->input_len ) && ( $this->input[$this->pos] === $tok );
+	}
+
+	/**
+	 * @param integer $length
+	 */
+	public function skipWhitespace( $length ) {
+		$this->pos += $length;
+
+		for ( ; $this->pos < $this->input_len; $this->pos++ ) {
+			$c = $this->input[$this->pos];
+
+			if ( ( $c !== "\n" ) && ( $c !== "\r" ) && ( $c !== "\t" ) && ( $c !== ' ' ) ) {
+				break;
+			}
+		}
+	}
+
+	/**
+	 * @param string $tok
+	 * @param string|null $msg
+	 */
+	public function expect( $tok, $msg = NULL ) {
+		$result = $this->match( array( $tok ) );
+		if ( !$result ) {
+			$this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
+		} else {
+			return $result;
+		}
+	}
+
+	/**
+	 * @param string $tok
+	 */
+	public function expectChar( $tok, $msg = null ) {
+		$result = $this->MatchChar( $tok );
+		if ( !$result ) {
+			$msg = $msg ? $msg : "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'";
+			$this->Error( $msg );
+		} else {
+			return $result;
+		}
+	}
+
+	//
+	// Here in, the parsing rules/functions
+	//
+	// The basic structure of the syntax tree generated is as follows:
+	//
+	//   Ruleset ->  Rule -> Value -> Expression -> Entity
+	//
+	// Here's some LESS code:
+	//
+	//	.class {
+	//	  color: #fff;
+	//	  border: 1px solid #000;
+	//	  width: @w + 4px;
+	//	  > .child {...}
+	//	}
+	//
+	// And here's what the parse tree might look like:
+	//
+	//	 Ruleset (Selector '.class', [
+	//		 Rule ("color",  Value ([Expression [Color #fff]]))
+	//		 Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
+	//		 Rule ("width",  Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
+	//		 Ruleset (Selector [Element '>', '.child'], [...])
+	//	 ])
+	//
+	//  In general, most rules will try to parse a token with the `$()` function, and if the return
+	//  value is truly, will return a new node, of the relevant type. Sometimes, we need to check
+	//  first, before parsing, that's when we use `peek()`.
+	//
+
+	//
+	// The `primary` rule is the *entry* and *exit* point of the parser.
+	// The rules here can appear at any level of the parse tree.
+	//
+	// The recursive nature of the grammar is an interplay between the `block`
+	// rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
+	// as represented by this simplified grammar:
+	//
+	//	 primary  →  (ruleset | rule)+
+	//	 ruleset  →  selector+ block
+	//	 block	→  '{' primary '}'
+	//
+	// Only at one point is the primary rule not called from the
+	// block rule: at the root level.
+	//
+	private function parsePrimary() {
+		$root = array();
+
+		while ( true ) {
+
+			if ( $this->pos >= $this->input_len ) {
+				break;
+			}
+
+			$node = $this->parseExtend( true );
+			if ( $node ) {
+				$root = array_merge( $root, $node );
+				continue;
+			}
+
+			// $node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective'));
+			$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective' ) );
+
+			if ( $node ) {
+				$root[] = $node;
+			} elseif ( !$this->MatchReg( '/\\G[\s\n;]+/' ) ) {
+				break;
+			}
+
+			if ( $this->PeekChar( '}' ) ) {
+				break;
+			}
+		}
+
+		return $root;
+	}
+
+	// We create a Comment node for CSS comments `/* */`,
+	// but keep the LeSS comments `//` silent, by just skipping
+	// over them.
+	private function parseComment() {
+		if ( $this->input[$this->pos] !== '/' ) {
+			return;
+		}
+
+		if ( $this->input[$this->pos + 1] === '/' ) {
+			$match = $this->MatchReg( '/\\G\/\/.*/' );
+			return $this->NewObj4( 'Less_Tree_Comment', array( $match[0], true, $this->pos, $this->env->currentFileInfo ) );
+		}
+
+		// $comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
+		$comment = $this->MatchReg( '/\\G\/\*(?s).*?\*+\/\n?/' );// not the same as less.js to prevent fatal errors
+		if ( $comment ) {
+			return $this->NewObj4( 'Less_Tree_Comment', array( $comment[0], false, $this->pos, $this->env->currentFileInfo ) );
+		}
+	}
+
+	private function parseComments() {
+		$comments = array();
+
+		while ( $this->pos < $this->input_len ) {
+			$comment = $this->parseComment();
+			if ( !$comment ) {
+				break;
+			}
+
+			$comments[] = $comment;
+		}
+
+		return $comments;
+	}
+
+	//
+	// A string, which supports escaping " and '
+	//
+	//	 "milky way" 'he\'s the one!'
+	//
+	private function parseEntitiesQuoted() {
+		$j = $this->pos;
+		$e = false;
+		$index = $this->pos;
+
+		if ( $this->input[$this->pos] === '~' ) {
+			$j++;
+			$e = true; // Escaped strings
+		}
+
+		$char = $this->input[$j];
+		if ( $char !== '"' && $char !== "'" ) {
+			return;
+		}
+
+		if ( $e ) {
+			$this->MatchChar( '~' );
+		}
+
+		$matched = $this->MatchQuoted( $char, $j + 1 );
+		if ( $matched === false ) {
+			return;
+		}
+
+		$quoted = $char.$matched.$char;
+		return $this->NewObj5( 'Less_Tree_Quoted', array( $quoted, $matched, $e, $index, $this->env->currentFileInfo ) );
+	}
+
+	/**
+	 * When PCRE JIT is enabled in php, regular expressions don't work for matching quoted strings
+	 *
+	 *	$regex	= '/\\G\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/';
+	 *	$regex	= '/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"/';
+	 *
+	 */
+	private function MatchQuoted( $quote_char, $i ) {
+		$matched = '';
+		while ( $i < $this->input_len ) {
+			$c = $this->input[$i];
+
+			// escaped character
+			if ( $c === '\\' ) {
+				$matched .= $c . $this->input[$i + 1];
+				$i += 2;
+				continue;
+			}
+
+			if ( $c === $quote_char ) {
+				$this->pos = $i + 1;
+				$this->skipWhitespace( 0 );
+				return $matched;
+			}
+
+			if ( $c === "\r" || $c === "\n" ) {
+				return false;
+			}
+
+			$i++;
+			$matched .= $c;
+		}
+
+		return false;
+	}
+
+	//
+	// A catch-all word, such as:
+	//
+	//	 black border-collapse
+	//
+	private function parseEntitiesKeyword() {
+		// $k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
+		$k = $this->MatchReg( '/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/' );
+		if ( $k ) {
+			$k = $k[0];
+			$color = $this->fromKeyword( $k );
+			if ( $color ) {
+				return $color;
+			}
+			return $this->NewObj1( 'Less_Tree_Keyword', $k );
+		}
+	}
+
+	// duplicate of Less_Tree_Color::FromKeyword
+	private function FromKeyword( $keyword ) {
+		$keyword = strtolower( $keyword );
+
+		if ( Less_Colors::hasOwnProperty( $keyword ) ) {
+			// detect named color
+			return $this->NewObj1( 'Less_Tree_Color', substr( Less_Colors::color( $keyword ), 1 ) );
+		}
+
+		if ( $keyword === 'transparent' ) {
+			return $this->NewObj3( 'Less_Tree_Color', array( array( 0, 0, 0 ), 0, true ) );
+		}
+	}
+
+	//
+	// A function call
+	//
+	//	 rgb(255, 0, 255)
+	//
+	// We also try to catch IE's `alpha()`, but let the `alpha` parser
+	// deal with the details.
+	//
+	// The arguments are parsed with the `entities.arguments` parser.
+	//
+	private function parseEntitiesCall() {
+		$index = $this->pos;
+
+		if ( !preg_match( '/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name, 0, $this->pos ) ) {
+			return;
+		}
+		$name = $name[1];
+		$nameLC = strtolower( $name );
+
+		if ( $nameLC === 'url' ) {
+			return null;
+		}
+
+		$this->pos += strlen( $name );
+
+		if ( $nameLC === 'alpha' ) {
+			$alpha_ret = $this->parseAlpha();
+			if ( $alpha_ret ) {
+				return $alpha_ret;
+			}
+		}
+
+		$this->MatchChar( '(' ); // Parse the '(' and consume whitespace.
+
+		$args = $this->parseEntitiesArguments();
+
+		if ( !$this->MatchChar( ')' ) ) {
+			return;
+		}
+
+		if ( $name ) {
+			return $this->NewObj4( 'Less_Tree_Call', array( $name, $args, $index, $this->env->currentFileInfo ) );
+		}
+	}
+
+	/**
+	 * Parse a list of arguments
+	 *
+	 * @return array
+	 */
+	private function parseEntitiesArguments() {
+		$args = array();
+		while ( true ) {
+			$arg = $this->MatchFuncs( array( 'parseEntitiesAssignment','parseExpression' ) );
+			if ( !$arg ) {
+				break;
+			}
+
+			$args[] = $arg;
+			if ( !$this->MatchChar( ',' ) ) {
+				break;
+			}
+		}
+		return $args;
+	}
+
+	private function parseEntitiesLiteral() {
+		return $this->MatchFuncs( array( 'parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor' ) );
+	}
+
+	// Assignments are argument entities for calls.
+	// They are present in ie filter properties as shown below.
+	//
+	//	 filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
+	//
+	private function parseEntitiesAssignment() {
+		$key = $this->MatchReg( '/\\G\w+(?=\s?=)/' );
+		if ( !$key ) {
+			return;
+		}
+
+		if ( !$this->MatchChar( '=' ) ) {
+			return;
+		}
+
+		$value = $this->parseEntity();
+		if ( $value ) {
+			return $this->NewObj2( 'Less_Tree_Assignment', array( $key[0], $value ) );
+		}
+	}
+
+	//
+	// Parse url() tokens
+	//
+	// We use a specific rule for urls, because they don't really behave like
+	// standard function calls. The difference is that the argument doesn't have
+	// to be enclosed within a string, so it can't be parsed as an Expression.
+	//
+	private function parseEntitiesUrl() {
+		if ( $this->input[$this->pos] !== 'u' || !$this->matchReg( '/\\Gurl\(/' ) ) {
+			return;
+		}
+
+		$value = $this->match( array( 'parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/' ) );
+		if ( !$value ) {
+			$value = '';
+		}
+
+		$this->expectChar( ')' );
+
+		if ( isset( $value->value ) || $value instanceof Less_Tree_Variable ) {
+			return $this->NewObj2( 'Less_Tree_Url', array( $value, $this->env->currentFileInfo ) );
+		}
+
+		return $this->NewObj2( 'Less_Tree_Url', array( $this->NewObj1( 'Less_Tree_Anonymous', $value ), $this->env->currentFileInfo ) );
+	}
+
+	//
+	// A Variable entity, such as `@fink`, in
+	//
+	//	 width: @fink + 2px
+	//
+	// We use a different parser for variable definitions,
+	// see `parsers.variable`.
+	//
+	private function parseEntitiesVariable() {
+		$index = $this->pos;
+		if ( $this->PeekChar( '@' ) && ( $name = $this->MatchReg( '/\\G@@?[\w-]+/' ) ) ) {
+			return $this->NewObj3( 'Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo ) );
+		}
+	}
+
+	// A variable entity using the protective {} e.g. @{var}
+	private function parseEntitiesVariableCurly() {
+		$index = $this->pos;
+
+		if ( $this->input_len > ( $this->pos + 1 ) && $this->input[$this->pos] === '@' && ( $curly = $this->MatchReg( '/\\G@\{([\w-]+)\}/' ) ) ) {
+			return $this->NewObj3( 'Less_Tree_Variable', array( '@'.$curly[1], $index, $this->env->currentFileInfo ) );
+		}
+	}
+
+	//
+	// A Hexadecimal color
+	//
+	//	 #4F3C2F
+	//
+	// `rgb` and `hsl` colors are parsed through the `entities.call` parser.
+	//
+	private function parseEntitiesColor() {
+		if ( $this->PeekChar( '#' ) && ( $rgb = $this->MatchReg( '/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/' ) ) ) {
+			return $this->NewObj1( 'Less_Tree_Color', $rgb[1] );
+		}
+	}
+
+	//
+	// A Dimension, that is, a number and a unit
+	//
+	//	 0.5em 95%
+	//
+	private function parseEntitiesDimension() {
+		$c = @ord( $this->input[$this->pos] );
+
+		// Is the first char of the dimension 0-9, '.', '+' or '-'
+		if ( ( $c > 57 || $c < 43 ) || $c === 47 || $c == 44 ) {
+			return;
+		}
+
+		$value = $this->MatchReg( '/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/' );
+		if ( $value ) {
+
+			if ( isset( $value[2] ) ) {
+				return $this->NewObj2( 'Less_Tree_Dimension', array( $value[1],$value[2] ) );
+			}
+			return $this->NewObj1( 'Less_Tree_Dimension', $value[1] );
+		}
+	}
+
+	//
+	// A unicode descriptor, as is used in unicode-range
+	//
+	// U+0?? or U+00A1-00A9
+	//
+	function parseUnicodeDescriptor() {
+		$ud = $this->MatchReg( '/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/' );
+		if ( $ud ) {
+			return $this->NewObj1( 'Less_Tree_UnicodeDescriptor', $ud[0] );
+		}
+	}
+
+	//
+	// JavaScript code to be evaluated
+	//
+	//	 `window.location.href`
+	//
+	private function parseEntitiesJavascript() {
+		$e = false;
+		$j = $this->pos;
+		if ( $this->input[$j] === '~' ) {
+			$j++;
+			$e = true;
+		}
+		if ( $this->input[$j] !== '`' ) {
+			return;
+		}
+		if ( $e ) {
+			$this->MatchChar( '~' );
+		}
+		$str = $this->MatchReg( '/\\G`([^`]*)`/' );
+		if ( $str ) {
+			return $this->NewObj3( 'Less_Tree_Javascript', array( $str[1], $this->pos, $e ) );
+		}
+	}
+
+	//
+	// The variable part of a variable definition. Used in the `rule` parser
+	//
+	//	 @fink:
+	//
+	private function parseVariable() {
+		if ( $this->PeekChar( '@' ) && ( $name = $this->MatchReg( '/\\G(@[\w-]+)\s*:/' ) ) ) {
+			return $name[1];
+		}
+	}
+
+	//
+	// The variable part of a variable definition. Used in the `rule` parser
+	//
+	// @fink();
+	//
+	private function parseRulesetCall() {
+		if ( $this->input[$this->pos] === '@' && ( $name = $this->MatchReg( '/\\G(@[\w-]+)\s*\(\s*\)\s*;/' ) ) ) {
+			return $this->NewObj1( 'Less_Tree_RulesetCall', $name[1] );
+		}
+	}
+
+	//
+	// extend syntax - used to extend selectors
+	//
+	function parseExtend( $isRule = false ) {
+		$index = $this->pos;
+		$extendList = array();
+
+		if ( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ) { return;
+		}
+
+		do{
+			$option = null;
+			$elements = array();
+			while ( true ) {
+				$option = $this->MatchReg( '/\\G(all)(?=\s*(\)|,))/' );
+				if ( $option ) { break;
+				}
+				$e = $this->parseElement();
+				if ( !$e ) { break;
+				}
+				$elements[] = $e;
+			}
+
+			if ( $option ) {
+				$option = $option[1];
+			}
+
+			$extendList[] = $this->NewObj3( 'Less_Tree_Extend', array( $this->NewObj1( 'Less_Tree_Selector', $elements ), $option, $index ) );
+
+		}while ( $this->MatchChar( "," ) );
+
+		$this->expect( '/\\G\)/' );
+
+		if ( $isRule ) {
+			$this->expect( '/\\G;/' );
+		}
+
+		return $extendList;
+	}
+
+	//
+	// A Mixin call, with an optional argument list
+	//
+	//	 #mixins > .square(#fff);
+	//	 .rounded(4px, black);
+	//	 .button;
+	//
+	// The `while` loop is there because mixins can be
+	// namespaced, but we only support the child and descendant
+	// selector for now.
+	//
+	private function parseMixinCall() {
+		$char = $this->input[$this->pos];
+		if ( $char !== '.' && $char !== '#' ) {
+			return;
+		}
+
+		$index = $this->pos;
+		$this->save(); // stop us absorbing part of an invalid selector
+
+		$elements = $this->parseMixinCallElements();
+
+		if ( $elements ) {
+
+			if ( $this->MatchChar( '(' ) ) {
+				$returned = $this->parseMixinArgs( true );
+				$args = $returned['args'];
+				$this->expectChar( ')' );
+			} else {
+				$args = array();
+			}
+
+			$important = $this->parseImportant();
+
+			if ( $this->parseEnd() ) {
+				$this->forget();
+				return $this->NewObj5( 'Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important ) );
+			}
+		}
+
+		$this->restore();
+	}
+
+	private function parseMixinCallElements() {
+		$elements = array();
+		$c = null;
+
+		while ( true ) {
+			$elemIndex = $this->pos;
+			$e = $this->MatchReg( '/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/' );
+			if ( !$e ) {
+				break;
+			}
+			$elements[] = $this->NewObj4( 'Less_Tree_Element', array( $c, $e[0], $elemIndex, $this->env->currentFileInfo ) );
+			$c = $this->MatchChar( '>' );
+		}
+
+		return $elements;
+	}
+
+	/**
+	 * @param boolean $isCall
+	 */
+	private function parseMixinArgs( $isCall ) {
+		$expressions = array();
+		$argsSemiColon = array();
+		$isSemiColonSeperated = null;
+		$argsComma = array();
+		$expressionContainsNamed = null;
+		$name = null;
+		$returner = array( 'args' => array(), 'variadic' => false );
+
+		$this->save();
+
+		while ( true ) {
+			if ( $isCall ) {
+				$arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) );
+			} else {
+				$this->parseComments();
+				if ( $this->input[ $this->pos ] === '.' && $this->MatchReg( '/\\G\.{3}/' ) ) {
+					$returner['variadic'] = true;
+					if ( $this->MatchChar( ";" ) && !$isSemiColonSeperated ) {
+						$isSemiColonSeperated = true;
+					}
+
+					if ( $isSemiColonSeperated ) {
+						$argsSemiColon[] = array( 'variadic' => true );
+					} else {
+						$argsComma[] = array( 'variadic' => true );
+					}
+					break;
+				}
+				$arg = $this->MatchFuncs( array( 'parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword' ) );
+			}
+
+			if ( !$arg ) {
+				break;
+			}
+
+			$nameLoop = null;
+			if ( $arg instanceof Less_Tree_Expression ) {
+				$arg->throwAwayComments();
+			}
+			$value = $arg;
+			$val = null;
+
+			if ( $isCall ) {
+				// Variable
+				if ( property_exists( $arg, 'value' ) && count( $arg->value ) == 1 ) {
+					$val = $arg->value[0];
+				}
+			} else {
+				$val = $arg;
+			}
+
+			if ( $val instanceof Less_Tree_Variable ) {
+
+				if ( $this->MatchChar( ':' ) ) {
+					if ( $expressions ) {
+						if ( $isSemiColonSeperated ) {
+							$this->Error( 'Cannot mix ; and , as delimiter types' );
+						}
+						$expressionContainsNamed = true;
+					}
+
+					// we do not support setting a ruleset as a default variable - it doesn't make sense
+					// However if we do want to add it, there is nothing blocking it, just don't error
+					// and remove isCall dependency below
+					$value = null;
+					if ( $isCall ) {
+						$value = $this->parseDetachedRuleset();
+					}
+					if ( !$value ) {
+						$value = $this->parseExpression();
+					}
+
+					if ( !$value ) {
+						if ( $isCall ) {
+							$this->Error( 'could not understand value for named argument' );
+						} else {
+							$this->restore();
+							$returner['args'] = array();
+							return $returner;
+						}
+					}
+
+					$nameLoop = ( $name = $val->name );
+				} elseif ( !$isCall && $this->MatchReg( '/\\G\.{3}/' ) ) {
+					$returner['variadic'] = true;
+					if ( $this->MatchChar( ";" ) && !$isSemiColonSeperated ) {
+						$isSemiColonSeperated = true;
+					}
+					if ( $isSemiColonSeperated ) {
+						$argsSemiColon[] = array( 'name' => $arg->name, 'variadic' => true );
+					} else {
+						$argsComma[] = array( 'name' => $arg->name, 'variadic' => true );
+					}
+					break;
+				} elseif ( !$isCall ) {
+					$name = $nameLoop = $val->name;
+					$value = null;
+				}
+			}
+
+			if ( $value ) {
+				$expressions[] = $value;
+			}
+
+			$argsComma[] = array( 'name' => $nameLoop, 'value' => $value );
+
+			if ( $this->MatchChar( ',' ) ) {
+				continue;
+			}
+
+			if ( $this->MatchChar( ';' ) || $isSemiColonSeperated ) {
+
+				if ( $expressionContainsNamed ) {
+					$this->Error( 'Cannot mix ; and , as delimiter types' );
+				}
+
+				$isSemiColonSeperated = true;
+
+				if ( count( $expressions ) > 1 ) {
+					$value = $this->NewObj1( 'Less_Tree_Value', $expressions );
+				}
+				$argsSemiColon[] = array( 'name' => $name, 'value' => $value );
+
+				$name = null;
+				$expressions = array();
+				$expressionContainsNamed = false;
+			}
+		}
+
+		$this->forget();
+		$returner['args'] = ( $isSemiColonSeperated ? $argsSemiColon : $argsComma );
+		return $returner;
+	}
+
+	//
+	// A Mixin definition, with a list of parameters
+	//
+	//	 .rounded (@radius: 2px, @color) {
+	//		...
+	//	 }
+	//
+	// Until we have a finer grained state-machine, we have to
+	// do a look-ahead, to make sure we don't have a mixin call.
+	// See the `rule` function for more information.
+	//
+	// We start by matching `.rounded (`, and then proceed on to
+	// the argument list, which has optional default values.
+	// We store the parameters in `params`, with a `value` key,
+	// if there is a value, such as in the case of `@radius`.
+	//
+	// Once we've got our params list, and a closing `)`, we parse
+	// the `{...}` block.
+	//
+	private function parseMixinDefinition() {
+		$cond = null;
+
+		$char = $this->input[$this->pos];
+		if ( ( $char !== '.' && $char !== '#' ) || ( $char === '{' && $this->PeekReg( '/\\G[^{]*\}/' ) ) ) {
+			return;
+		}
+
+		$this->save();
+
+		$match = $this->MatchReg( '/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/' );
+		if ( $match ) {
+			$name = $match[1];
+
+			$argInfo = $this->parseMixinArgs( false );
+			$params = $argInfo['args'];
+			$variadic = $argInfo['variadic'];
+
+			// .mixincall("@{a}");
+			// looks a bit like a mixin definition..
+			// also
+			// .mixincall(@a: {rule: set;});
+			// so we have to be nice and restore
+			if ( !$this->MatchChar( ')' ) ) {
+				$this->furthest = $this->pos;
+				$this->restore();
+				return;
+			}
+
+			$this->parseComments();
+
+			if ( $this->MatchReg( '/\\Gwhen/' ) ) { // Guard
+				$cond = $this->expect( 'parseConditions', 'Expected conditions' );
+			}
+
+			$ruleset = $this->parseBlock();
+
+			if ( is_array( $ruleset ) ) {
+				$this->forget();
+				return $this->NewObj5( 'Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic ) );
+			}
+
+			$this->restore();
+		} else {
+			$this->forget();
+		}
+	}
+
+	//
+	// Entities are the smallest recognized token,
+	// and can be found inside a rule's value.
+	//
+	private function parseEntity() {
+		return $this->MatchFuncs( array( 'parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment' ) );
+	}
+
+	//
+	// A Rule terminator. Note that we use `peek()` to check for '}',
+	// because the `block` rule will be expecting it, but we still need to make sure
+	// it's there, if ';' was omitted.
+	//
+	private function parseEnd() {
+		return $this->MatchChar( ';' ) || $this->PeekChar( '}' );
+	}
+
+	//
+	// IE's alpha function
+	//
+	//	 alpha(opacity=88)
+	//
+	private function parseAlpha() {
+		if ( !$this->MatchReg( '/\\G\(opacity=/i' ) ) {
+			return;
+		}
+
+		$value = $this->MatchReg( '/\\G[0-9]+/' );
+		if ( $value ) {
+			$value = $value[0];
+		} else {
+			$value = $this->parseEntitiesVariable();
+			if ( !$value ) {
+				return;
+			}
+		}
+
+		$this->expectChar( ')' );
+		return $this->NewObj1( 'Less_Tree_Alpha', $value );
+	}
+
+	//
+	// A Selector Element
+	//
+	//	 div
+	//	 + h1
+	//	 #socks
+	//	 input[type="text"]
+	//
+	// Elements are the building blocks for Selectors,
+	// they are made out of a `Combinator` (see combinator rule),
+	// and an element name, such as a tag a class, or `*`.
+	//
+	private function parseElement() {
+		$c = $this->parseCombinator();
+		$index = $this->pos;
+
+		$e = $this->match( array( '/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
+			'#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly' ) );
+
+		if ( is_null( $e ) ) {
+			$this->save();
+			if ( $this->MatchChar( '(' ) ) {
+				if ( ( $v = $this->parseSelector() ) && $this->MatchChar( ')' ) ) {
+					$e = $this->NewObj1( 'Less_Tree_Paren', $v );
+					$this->forget();
+				} else {
+					$this->restore();
+				}
+			} else {
+				$this->forget();
+			}
+		}
+
+		if ( !is_null( $e ) ) {
+			return $this->NewObj4( 'Less_Tree_Element', array( $c, $e, $index, $this->env->currentFileInfo ) );
+		}
+	}
+
+	//
+	// Combinators combine elements together, in a Selector.
+	//
+	// Because our parser isn't white-space sensitive, special care
+	// has to be taken, when parsing the descendant combinator, ` `,
+	// as it's an empty space. We have to check the previous character
+	// in the input, to see if it's a ` ` character.
+	//
+	private function parseCombinator() {
+		if ( $this->pos < $this->input_len ) {
+			$c = $this->input[$this->pos];
+			if ( $c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ) {
+
+				$this->pos++;
+				if ( $this->input[$this->pos] === '^' ) {
+					$c = '^^';
+					$this->pos++;
+				}
+
+				$this->skipWhitespace( 0 );
+
+				return $c;
+			}
+
+			if ( $this->pos > 0 && $this->isWhitespace( -1 ) ) {
+				return ' ';
+			}
+		}
+	}
+
+	//
+	// A CSS selector (see selector below)
+	// with less extensions e.g. the ability to extend and guard
+	//
+	private function parseLessSelector() {
+		return $this->parseSelector( true );
+	}
+
+	//
+	// A CSS Selector
+	//
+	//	 .class > div + h1
+	//	 li a:hover
+	//
+	// Selectors are made out of one or more Elements, see above.
+	//
+	private function parseSelector( $isLess = false ) {
+		$elements = array();
+		$extendList = array();
+		$condition = null;
+		$when = false;
+		$extend = false;
+		$e = null;
+		$c = null;
+		$index = $this->pos;
+
+		while ( ( $isLess && ( $extend = $this->parseExtend() ) ) || ( $isLess && ( $when = $this->MatchReg( '/\\Gwhen/' ) ) ) || ( $e = $this->parseElement() ) ) {
+			if ( $when ) {
+				$condition = $this->expect( 'parseConditions', 'expected condition' );
+			} elseif ( $condition ) {
+				// error("CSS guard can only be used at the end of selector");
+			} elseif ( $extend ) {
+				$extendList = array_merge( $extendList, $extend );
+			} else {
+				// if( count($extendList) ){
+				//error("Extend can only be used at the end of selector");
+				//}
+				if ( $this->pos < $this->input_len ) {
+					$c = $this->input[ $this->pos ];
+				}
+				$elements[] = $e;
+				$e = null;
+			}
+
+			if ( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')' ) { break;
+			}
+		}
+
+		if ( $elements ) {
+			return $this->NewObj5( 'Less_Tree_Selector', array( $elements, $extendList, $condition, $index, $this->env->currentFileInfo ) );
+		}
+		if ( $extendList ) {
+			$this->Error( 'Extend must be used to extend a selector, it cannot be used on its own' );
+		}
+	}
+
+	private function parseTag() {
+		return ( $tag = $this->MatchReg( '/\\G[A-Za-z][A-Za-z-]*[0-9]?/' ) ) ? $tag : $this->MatchChar( '*' );
+	}
+
+	private function parseAttribute() {
+		$val = null;
+
+		if ( !$this->MatchChar( '[' ) ) {
+			return;
+		}
+
+		$key = $this->parseEntitiesVariableCurly();
+		if ( !$key ) {
+			$key = $this->expect( '/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/' );
+		}
+
+		$op = $this->MatchReg( '/\\G[|~*$^]?=/' );
+		if ( $op ) {
+			$val = $this->match( array( 'parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly' ) );
+		}
+
+		$this->expectChar( ']' );
+
+		return $this->NewObj3( 'Less_Tree_Attribute', array( $key, $op === null ? null : $op[0], $val ) );
+	}
+
+	//
+	// The `block` rule is used by `ruleset` and `mixin.definition`.
+	// It's a wrapper around the `primary` rule, with added `{}`.
+	//
+	private function parseBlock() {
+		if ( $this->MatchChar( '{' ) ) {
+			$content = $this->parsePrimary();
+			if ( $this->MatchChar( '}' ) ) {
+				return $content;
+			}
+		}
+	}
+
+	private function parseBlockRuleset() {
+		$block = $this->parseBlock();
+
+		if ( $block ) {
+			$block = $this->NewObj2( 'Less_Tree_Ruleset', array( null, $block ) );
+		}
+
+		return $block;
+	}
+
+	private function parseDetachedRuleset() {
+		$blockRuleset = $this->parseBlockRuleset();
+		if ( $blockRuleset ) {
+			return $this->NewObj1( 'Less_Tree_DetachedRuleset', $blockRuleset );
+		}
+	}
+
+	//
+	// div, .class, body > p {...}
+	//
+	private function parseRuleset() {
+		$selectors = array();
+
+		$this->save();
+
+		while ( true ) {
+			$s = $this->parseLessSelector();
+			if ( !$s ) {
+				break;
+			}
+			$selectors[] = $s;
+			$this->parseComments();
+
+			if ( $s->condition && count( $selectors ) > 1 ) {
+				$this->Error( 'Guards are only currently allowed on a single selector.' );
+			}
+
+			if ( !$this->MatchChar( ',' ) ) {
+				break;
+			}
+			if ( $s->condition ) {
+				$this->Error( 'Guards are only currently allowed on a single selector.' );
+			}
+			$this->parseComments();
+		}
+
+		if ( $selectors ) {
+			$rules = $this->parseBlock();
+			if ( is_array( $rules ) ) {
+				$this->forget();
+				return $this->NewObj2( 'Less_Tree_Ruleset', array( $selectors, $rules ) ); // Less_Environment::$strictImports
+			}
+		}
+
+		// Backtrack
+		$this->furthest = $this->pos;
+		$this->restore();
+	}
+
+	/**
+	 * Custom less.php parse function for finding simple name-value css pairs
+	 * ex: width:100px;
+	 *
+	 */
+	private function parseNameValue() {
+		$index = $this->pos;
+		$this->save();
+
+		// $match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/');
+		$match = $this->MatchReg( '/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/' );
+		if ( $match ) {
+
+			if ( $match[4] == '}' ) {
+				$this->pos = $index + strlen( $match[0] ) - 1;
+			}
+
+			if ( $match[3] ) {
+				$match[2] .= ' !important';
+			}
+
+			return $this->NewObj4( 'Less_Tree_NameValue', array( $match[1], $match[2], $index, $this->env->currentFileInfo ) );
+		}
+
+		$this->restore();
+	}
+
+	private function parseRule( $tryAnonymous = null ) {
+		$merge = false;
+		$startOfRule = $this->pos;
+
+		$c = $this->input[$this->pos];
+		if ( $c === '.' || $c === '#' || $c === '&' ) {
+			return;
+		}
+
+		$this->save();
+		$name = $this->MatchFuncs( array( 'parseVariable','parseRuleProperty' ) );
+
+		if ( $name ) {
+
+			$isVariable = is_string( $name );
+
+			$value = null;
+			if ( $isVariable ) {
+				$value = $this->parseDetachedRuleset();
+			}
+
+			$important = null;
+			if ( !$value ) {
+
+				// prefer to try to parse first if its a variable or we are compressing
+				// but always fallback on the other one
+				//if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){
+				if ( !$tryAnonymous && ( Less_Parser::$options['compress'] || $isVariable ) ) {
+					$value = $this->MatchFuncs( array( 'parseValue','parseAnonymousValue' ) );
+				} else {
+					$value = $this->MatchFuncs( array( 'parseAnonymousValue','parseValue' ) );
+				}
+
+				$important = $this->parseImportant();
+
+				// a name returned by this.ruleProperty() is always an array of the form:
+				// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
+				// where each item is a tree.Keyword or tree.Variable
+				if ( !$isVariable && is_array( $name ) ) {
+					$nm = array_pop( $name );
+					if ( $nm->value ) {
+						$merge = $nm->value;
+					}
+				}
+			}
+
+			if ( $value && $this->parseEnd() ) {
+				$this->forget();
+				return $this->NewObj6( 'Less_Tree_Rule', array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo ) );
+			} else {
+				$this->furthest = $this->pos;
+				$this->restore();
+				if ( $value && !$tryAnonymous ) {
+					return $this->parseRule( true );
+				}
+			}
+		} else {
+			$this->forget();
+		}
+	}
+
+	function parseAnonymousValue() {
+		if ( preg_match( '/\\G([^@+\/\'"*`(;{}-]*);/', $this->input, $match, 0, $this->pos ) ) {
+			$this->pos += strlen( $match[1] );
+			return $this->NewObj1( 'Less_Tree_Anonymous', $match[1] );
+		}
+	}
+
+	//
+	// An @import directive
+	//
+	//	 @import "lib";
+	//
+	// Depending on our environment, importing is done differently:
+	// In the browser, it's an XHR request, in Node, it would be a
+	// file-system operation. The function used for importing is
+	// stored in `import`, which we pass to the Import constructor.
+	//
+	private function parseImport() {
+		$this->save();
+
+		$dir = $this->MatchReg( '/\\G@import?\s+/' );
+
+		if ( $dir ) {
+			$options = $this->parseImportOptions();
+			$path = $this->MatchFuncs( array( 'parseEntitiesQuoted','parseEntitiesUrl' ) );
+
+			if ( $path ) {
+				$features = $this->parseMediaFeatures();
+				if ( $this->MatchChar( ';' ) ) {
+					if ( $features ) {
+						$features = $this->NewObj1( 'Less_Tree_Value', $features );
+					}
+
+					$this->forget();
+					return $this->NewObj5( 'Less_Tree_Import', array( $path, $features, $options, $this->pos, $this->env->currentFileInfo ) );
+				}
+			}
+		}
+
+		$this->restore();
+	}
+
+	private function parseImportOptions() {
+		$options = array();
+
+		// list of options, surrounded by parens
+		if ( !$this->MatchChar( '(' ) ) {
+			return $options;
+		}
+		do{
+			$optionName = $this->parseImportOption();
+			if ( $optionName ) {
+				$value = true;
+				switch ( $optionName ) {
+					case "css":
+						$optionName = "less";
+						$value = false;
+						break;
+					case "once":
+						$optionName = "multiple";
+						$value = false;
+						break;
+				}
+				$options[$optionName] = $value;
+				if ( !$this->MatchChar( ',' ) ) { break;
+				}
+			}
+		}while ( $optionName );
+		$this->expectChar( ')' );
+		return $options;
+	}
+
+	private function parseImportOption() {
+		$opt = $this->MatchReg( '/\\G(less|css|multiple|once|inline|reference|optional)/' );
+		if ( $opt ) {
+			return $opt[1];
+		}
+	}
+
+	private function parseMediaFeature() {
+		$nodes = array();
+
+		do{
+			$e = $this->MatchFuncs( array( 'parseEntitiesKeyword','parseEntitiesVariable' ) );
+			if ( $e ) {
+				$nodes[] = $e;
+			} elseif ( $this->MatchChar( '(' ) ) {
+				$p = $this->parseProperty();
+				$e = $this->parseValue();
+				if ( $this->MatchChar( ')' ) ) {
+					if ( $p && $e ) {
+						$r = $this->NewObj7( 'Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true ) );
+						$nodes[] = $this->NewObj1( 'Less_Tree_Paren', $r );
+					} elseif ( $e ) {
+						$nodes[] = $this->NewObj1( 'Less_Tree_Paren', $e );
+					} else {
+						return null;
+					}
+				} else return null;
+			}
+		} while ( $e );
+
+		if ( $nodes ) {
+			return $this->NewObj1( 'Less_Tree_Expression', $nodes );
+		}
+	}
+
+	private function parseMediaFeatures() {
+		$features = array();
+
+		do{
+			$e = $this->parseMediaFeature();
+			if ( $e ) {
+				$features[] = $e;
+				if ( !$this->MatchChar( ',' ) ) break;
+			} else {
+				$e = $this->parseEntitiesVariable();
+				if ( $e ) {
+					$features[] = $e;
+					if ( !$this->MatchChar( ',' ) ) break;
+				}
+			}
+		} while ( $e );
+
+		return $features ? $features : null;
+	}
+
+	private function parseMedia() {
+		if ( $this->MatchReg( '/\\G@media/' ) ) {
+			$features = $this->parseMediaFeatures();
+			$rules = $this->parseBlock();
+
+			if ( is_array( $rules ) ) {
+				return $this->NewObj4( 'Less_Tree_Media', array( $rules, $features, $this->pos, $this->env->currentFileInfo ) );
+			}
+		}
+	}
+
+	//
+	// A CSS Directive
+	//
+	// @charset "utf-8";
+	//
+	private function parseDirective() {
+		if ( !$this->PeekChar( '@' ) ) {
+			return;
+		}
+
+		$rules = null;
+		$index = $this->pos;
+		$hasBlock = true;
+		$hasIdentifier = false;
+		$hasExpression = false;
+		$hasUnknown = false;
+
+		$value = $this->MatchFuncs( array( 'parseImport','parseMedia' ) );
+		if ( $value ) {
+			return $value;
+		}
+
+		$this->save();
+
+		$name = $this->MatchReg( '/\\G@[a-z-]+/' );
+
+		if ( !$name ) return;
+		$name = $name[0];
+
+		$nonVendorSpecificName = $name;
+		$pos = strpos( $name, '-', 2 );
+		if ( $name[1] == '-' && $pos > 0 ) {
+			$nonVendorSpecificName = "@" . substr( $name, $pos + 1 );
+		}
+
+		switch ( $nonVendorSpecificName ) {
+			/*
+			case "@font-face":
+			case "@viewport":
+			case "@top-left":
+			case "@top-left-corner":
+			case "@top-center":
+			case "@top-right":
+			case "@top-right-corner":
+			case "@bottom-left":
+			case "@bottom-left-corner":
+			case "@bottom-center":
+			case "@bottom-right":
+			case "@bottom-right-corner":
+			case "@left-top":
+			case "@left-middle":
+			case "@left-bottom":
+			case "@right-top":
+			case "@right-middle":
+			case "@right-bottom":
+			hasBlock = true;
+			break;
+			*/
+			case "@charset":
+				$hasIdentifier = true;
+				$hasBlock = false;
+				break;
+			case "@namespace":
+				$hasExpression = true;
+				$hasBlock = false;
+				break;
+			case "@keyframes":
+				$hasIdentifier = true;
+				break;
+			case "@host":
+			case "@page":
+			case "@document":
+			case "@supports":
+				$hasUnknown = true;
+				break;
+		}
+
+		if ( $hasIdentifier ) {
+			$value = $this->parseEntity();
+			if ( !$value ) {
+				$this->error( "expected " . $name . " identifier" );
+			}
+		} else if ( $hasExpression ) {
+			$value = $this->parseExpression();
+			if ( !$value ) {
+				$this->error( "expected " . $name. " expression" );
+			}
+		} else if ( $hasUnknown ) {
+
+			$value = $this->MatchReg( '/\\G[^{;]+/' );
+			if ( $value ) {
+				$value = $this->NewObj1( 'Less_Tree_Anonymous', trim( $value[0] ) );
+			}
+		}
+
+		if ( $hasBlock ) {
+			$rules = $this->parseBlockRuleset();
+		}
+
+		if ( $rules || ( !$hasBlock && $value && $this->MatchChar( ';' ) ) ) {
+			$this->forget();
+			return $this->NewObj5( 'Less_Tree_Directive', array( $name, $value, $rules, $index, $this->env->currentFileInfo ) );
+		}
+
+		$this->restore();
+	}
+
+	//
+	// A Value is a comma-delimited list of Expressions
+	//
+	//	 font-family: Baskerville, Georgia, serif;
+	//
+	// In a Rule, a Value represents everything after the `:`,
+	// and before the `;`.
+	//
+	private function parseValue() {
+		$expressions = array();
+
+		do{
+			$e = $this->parseExpression();
+			if ( $e ) {
+				$expressions[] = $e;
+				if ( !$this->MatchChar( ',' ) ) {
+					break;
+				}
+			}
+		}while ( $e );
+
+		if ( $expressions ) {
+			return $this->NewObj1( 'Less_Tree_Value', $expressions );
+		}
+	}
+
+	private function parseImportant() {
+		if ( $this->PeekChar( '!' ) && $this->MatchReg( '/\\G! *important/' ) ) {
+			return ' !important';
+		}
+	}
+
+	private function parseSub() {
+		if ( $this->MatchChar( '(' ) ) {
+			$a = $this->parseAddition();
+			if ( $a ) {
+				$this->expectChar( ')' );
+				return $this->NewObj2( 'Less_Tree_Expression', array( array( $a ), true ) ); // instead of $e->parens = true so the value is cached
+			}
+		}
+	}
+
+	/**
+	 * Parses multiplication operation
+	 *
+	 * @return Less_Tree_Operation|null
+	 */
+	function parseMultiplication() {
+		$return = $m = $this->parseOperand();
+		if ( $return ) {
+			while ( true ) {
+
+				$isSpaced = $this->isWhitespace( -1 );
+
+				if ( $this->PeekReg( '/\\G\/[*\/]/' ) ) {
+					break;
+				}
+
+				$op = $this->MatchChar( '/' );
+				if ( !$op ) {
+					$op = $this->MatchChar( '*' );
+					if ( !$op ) {
+						break;
+					}
+				}
+
+				$a = $this->parseOperand();
+
+				if ( !$a ) { break;
+				}
+
+				$m->parensInOp = true;
+				$a->parensInOp = true;
+				$return = $this->NewObj3( 'Less_Tree_Operation', array( $op, array( $return, $a ), $isSpaced ) );
+			}
+		}
+		return $return;
+
+	}
+
+	/**
+	 * Parses an addition operation
+	 *
+	 * @return Less_Tree_Operation|null
+	 */
+	private function parseAddition() {
+		$return = $m = $this->parseMultiplication();
+		if ( $return ) {
+			while ( true ) {
+
+				$isSpaced = $this->isWhitespace( -1 );
+
+				$op = $this->MatchReg( '/\\G[-+]\s+/' );
+				if ( $op ) {
+					$op = $op[0];
+				} else {
+					if ( !$isSpaced ) {
+						$op = $this->match( array( '#+','#-' ) );
+					}
+					if ( !$op ) {
+						break;
+					}
+				}
+
+				$a = $this->parseMultiplication();
+				if ( !$a ) {
+					break;
+				}
+
+				$m->parensInOp = true;
+				$a->parensInOp = true;
+				$return = $this->NewObj3( 'Less_Tree_Operation', array( $op, array( $return, $a ), $isSpaced ) );
+			}
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Parses the conditions
+	 *
+	 * @return Less_Tree_Condition|null
+	 */
+	private function parseConditions() {
+		$index = $this->pos;
+		$return = $a = $this->parseCondition();
+		if ( $a ) {
+			while ( true ) {
+				if ( !$this->PeekReg( '/\\G,\s*(not\s*)?\(/' ) || !$this->MatchChar( ',' ) ) {
+					break;
+				}
+				$b = $this->parseCondition();
+				if ( !$b ) {
+					break;
+				}
+
+				$return = $this->NewObj4( 'Less_Tree_Condition', array( 'or', $return, $b, $index ) );
+			}
+			return $return;
+		}
+	}
+
+	private function parseCondition() {
+		$index = $this->pos;
+		$negate = false;
+		$c = null;
+
+		if ( $this->MatchReg( '/\\Gnot/' ) ) $negate = true;
+		$this->expectChar( '(' );
+		$a = $this->MatchFuncs( array( 'parseAddition','parseEntitiesKeyword','parseEntitiesQuoted' ) );
+
+		if ( $a ) {
+			$op = $this->MatchReg( '/\\G(?:>=|<=|=<|[<=>])/' );
+			if ( $op ) {
+				$b = $this->MatchFuncs( array( 'parseAddition','parseEntitiesKeyword','parseEntitiesQuoted' ) );
+				if ( $b ) {
+					$c = $this->NewObj5( 'Less_Tree_Condition', array( $op[0], $a, $b, $index, $negate ) );
+				} else {
+					$this->Error( 'Unexpected expression' );
+				}
+			} else {
+				$k = $this->NewObj1( 'Less_Tree_Keyword', 'true' );
+				$c = $this->NewObj5( 'Less_Tree_Condition', array( '=', $a, $k, $index, $negate ) );
+			}
+			$this->expectChar( ')' );
+			return $this->MatchReg( '/\\Gand/' ) ? $this->NewObj3( 'Less_Tree_Condition', array( 'and', $c, $this->parseCondition() ) ) : $c;
+		}
+	}
+
+	/**
+	 * An operand is anything that can be part of an operation,
+	 * such as a Color, or a Variable
+	 *
+	 */
+	private function parseOperand() {
+		$negate = false;
+		$offset = $this->pos + 1;
+		if ( $offset >= $this->input_len ) {
+			return;
+		}
+		$char = $this->input[$offset];
+		if ( $char === '@' || $char === '(' ) {
+			$negate = $this->MatchChar( '-' );
+		}
+
+		$o = $this->MatchFuncs( array( 'parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall' ) );
+
+		if ( $negate ) {
+			$o->parensInOp = true;
+			$o = $this->NewObj1( 'Less_Tree_Negative', $o );
+		}
+
+		return $o;
+	}
+
+	/**
+	 * Expressions either represent mathematical operations,
+	 * or white-space delimited Entities.
+	 *
+	 *	 1px solid black
+	 * @var * 2
+	 *
+	 * @return Less_Tree_Expression|null
+	 */
+	private function parseExpression() {
+		$entities = array();
+
+		do{
+			$e = $this->MatchFuncs( array( 'parseAddition','parseEntity' ) );
+			if ( $e ) {
+				$entities[] = $e;
+				// operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
+				if ( !$this->PeekReg( '/\\G\/[\/*]/' ) ) {
+					$delim = $this->MatchChar( '/' );
+					if ( $delim ) {
+						$entities[] = $this->NewObj1( 'Less_Tree_Anonymous', $delim );
+					}
+				}
+			}
+		}while ( $e );
+
+		if ( $entities ) {
+			return $this->NewObj1( 'Less_Tree_Expression', $entities );
+		}
+	}
+
+	/**
+	 * Parse a property
+	 * eg: 'min-width', 'orientation', etc
+	 *
+	 * @return string
+	 */
+	private function parseProperty() {
+		$name = $this->MatchReg( '/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/' );
+		if ( $name ) {
+			return $name[1];
+		}
+	}
+
+	/**
+	 * Parse a rule property
+	 * eg: 'color', 'width', 'height', etc
+	 *
+	 * @return string
+	 */
+	private function parseRuleProperty() {
+		$offset = $this->pos;
+		$name = array();
+		$index = array();
+		$length = 0;
+
+		$this->rulePropertyMatch( '/\\G(\*?)/', $offset, $length, $index, $name );
+		while ( $this->rulePropertyMatch( '/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name ) ); // !
+
+		if ( ( count( $name ) > 1 ) && $this->rulePropertyMatch( '/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name ) ) {
+			// at last, we have the complete match now. move forward,
+			// convert name particles to tree objects and return:
+			$this->skipWhitespace( $length );
+
+			if ( $name[0] === '' ) {
+				array_shift( $name );
+				array_shift( $index );
+			}
+			foreach ( $name as $k => $s ) {
+				if ( !$s || $s[0] !== '@' ) {
+					$name[$k] = $this->NewObj1( 'Less_Tree_Keyword', $s );
+				} else {
+					$name[$k] = $this->NewObj3( 'Less_Tree_Variable', array( '@' . substr( $s, 2, -1 ), $index[$k], $this->env->currentFileInfo ) );
+				}
+			}
+			return $name;
+		}
+
+	}
+
+	private function rulePropertyMatch( $re, &$offset, &$length, &$index, &$name ) {
+		preg_match( $re, $this->input, $a, 0, $offset );
+		if ( $a ) {
+			$index[] = $this->pos + $length;
+			$length += strlen( $a[0] );
+			$offset += strlen( $a[0] );
+			$name[] = $a[1];
+			return true;
+		}
+	}
+
+	public static function serializeVars( $vars ) {
+		$s = '';
+
+		foreach ( $vars as $name => $value ) {
+			$s .= ( ( $name[0] === '@' ) ? '' : '@' ) . $name .': '. $value . ( ( substr( $value, -1 ) === ';' ) ? '' : ';' );
+		}
+
+		return $s;
+	}
+
+	/**
+	 * Some versions of php have trouble with method_exists($a,$b) if $a is not an object
+	 *
+	 * @param string $b
+	 */
+	public static function is_method( $a, $b ) {
+		return is_object( $a ) && method_exists( $a, $b );
+	}
+
+	/**
+	 * Round numbers similarly to javascript
+	 * eg: 1.499999 to 1 instead of 2
+	 *
+	 */
+	public static function round( $i, $precision = 0 ) {
+		$precision = pow( 10, $precision );
+		$i = $i * $precision;
+
+		$ceil = ceil( $i );
+		$floor = floor( $i );
+		if ( ( $ceil - $i ) <= ( $i - $floor ) ) {
+			return $ceil / $precision;
+		} else {
+			return $floor / $precision;
+		}
+	}
+
+	/**
+	 * Create Less_Tree_* objects and optionally generate a cache string
+	 *
+	 * @return mixed
+	 */
+	public function NewObj0( $class ) {
+		$obj = new $class();
+		if ( $this->CacheEnabled() ) {
+			$obj->cache_string = ' new '.$class.'()';
+		}
+		return $obj;
+	}
+
+	public function NewObj1( $class, $arg ) {
+		$obj = new $class( $arg );
+		if ( $this->CacheEnabled() ) {
+			$obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString( $arg ).')';
+		}
+		return $obj;
+	}
+
+	public function NewObj2( $class, $args ) {
+		$obj = new $class( $args[0], $args[1] );
+		if ( $this->CacheEnabled() ) {
+			$this->ObjCache( $obj, $class, $args );
+		}
+		return $obj;
+	}
+
+	public function NewObj3( $class, $args ) {
+		$obj = new $class( $args[0], $args[1], $args[2] );
+		if ( $this->CacheEnabled() ) {
+			$this->ObjCache( $obj, $class, $args );
+		}
+		return $obj;
+	}
+
+	public function NewObj4( $class, $args ) {
+		$obj = new $class( $args[0], $args[1], $args[2], $args[3] );
+		if ( $this->CacheEnabled() ) {
+			$this->ObjCache( $obj, $class, $args );
+		}
+		return $obj;
+	}
+
+	public function NewObj5( $class, $args ) {
+		$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] );
+		if ( $this->CacheEnabled() ) {
+			$this->ObjCache( $obj, $class, $args );
+		}
+		return $obj;
+	}
+
+	public function NewObj6( $class, $args ) {
+		$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
+		if ( $this->CacheEnabled() ) {
+			$this->ObjCache( $obj, $class, $args );
+		}
+		return $obj;
+	}
+
+	public function NewObj7( $class, $args ) {
+		$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] );
+		if ( $this->CacheEnabled() ) {
+			$this->ObjCache( $obj, $class, $args );
+		}
+		return $obj;
+	}
+
+	// caching
+	public function ObjCache( $obj, $class, $args = array() ) {
+		$obj->cache_string = ' new '.$class.'('. self::ArgCache( $args ).')';
+	}
+
+	public function ArgCache( $args ) {
+		return implode( ',', array_map( array( 'Less_Parser','ArgString' ), $args ) );
+	}
+
+	/**
+	 * Convert an argument to a string for use in the parser cache
+	 *
+	 * @return string
+	 */
+	public static function ArgString( $arg ) {
+		$type = gettype( $arg );
+
+		if ( $type === 'object' ) {
+			$string = $arg->cache_string;
+			unset( $arg->cache_string );
+			return $string;
+
+		} elseif ( $type === 'array' ) {
+			$string = ' Array(';
+			foreach ( $arg as $k => $a ) {
+				$string .= var_export( $k, true ).' => '.self::ArgString( $a ).',';
+			}
+			return $string . ')';
+		}
+
+		return var_export( $arg, true );
+	}
+
+	public function Error( $msg ) {
+		throw new Less_Exception_Parser( $msg, null, $this->furthest, $this->env->currentFileInfo );
+	}
+
+	public static function WinPath( $path ) {
+		return str_replace( '\\', '/', $path );
+	}
+
+	public static function AbsPath( $path, $winPath = false ) {
+		if ( strpos( $path, '//' ) !== false && preg_match( '_^(https?:)?//\\w+(\\.\\w+)+/\\w+_i', $path ) ) {
+			return $winPath ? '' : false;
+		} else {
+			$path = realpath( $path );
+			if ( $winPath ) {
+				$path = self::WinPath( $path );
+			}
+			return $path;
+		}
+	}
+
+	public function CacheEnabled() {
+		return ( Less_Parser::$options['cache_method'] && ( Less_Cache::$cache_dir || ( Less_Parser::$options['cache_method'] == 'callback' ) ) );
+	}
+
+}

+ 187 - 0
lessphp/lib/Less/SourceMap/Base64VLQ.php

@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * Encode / Decode Base64 VLQ.
+ *
+ * @package Less
+ * @subpackage SourceMap
+ */
+class Less_SourceMap_Base64VLQ {
+
+	/**
+	 * Shift
+	 *
+	 * @var integer
+	 */
+	private $shift = 5;
+
+	/**
+	 * Mask
+	 *
+	 * @var integer
+	 */
+	private $mask = 0x1F; // == (1 << shift) == 0b00011111
+
+	/**
+	 * Continuation bit
+	 *
+	 * @var integer
+	 */
+	private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
+
+	/**
+	 * Char to integer map
+	 *
+	 * @var array
+	 */
+	private $charToIntMap = array(
+		'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 = array(
+		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 string $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 integer $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 string $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 integer 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 integer $a number to shift
+	 * @param integer $b number of bits to shift
+	 * @return integer
+	 */
+	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 integer $number
+	 * @return string
+	 * @throws Exception If the number is invalid
+	 */
+	public function base64Encode( $number ) {
+		if ( $number < 0 || $number > 63 ) {
+			throw new Exception( sprintf( 'Invalid number "%s" given. Must be between 0 and 63.', $number ) );
+		}
+		return $this->intToCharMap[$number];
+	}
+
+	/**
+	 * Decode single 6-bit digit from base64
+	 *
+	 * @param string $char
+	 * @return number
+	 * @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];
+	}
+
+}

+ 354 - 0
lessphp/lib/Less/SourceMap/Generator.php

@@ -0,0 +1,354 @@
+<?php
+
+/**
+ * Source map generator
+ *
+ * @package Less
+ * @subpackage Output
+ */
+class Less_SourceMap_Generator extends Less_Configurable {
+
+	/**
+	 * What version of source map does the generator generate?
+	 */
+	private const VERSION = 3;
+
+	/**
+	 * Array of default options
+	 *
+	 * @var array
+	 */
+	protected $defaultOptions = array(
+			// an optional source root, useful for relocating source files
+			// on a server or removing repeated values in the 'sources' entry.
+			// This value is prepended to the individual entries in the 'source' field.
+			'sourceRoot'			=> '',
+
+			// an optional name of the generated code that this source map is associated with.
+			'sourceMapFilename'		=> null,
+
+			// url of the map
+			'sourceMapURL'			=> null,
+
+			// absolute path to a file to write the map to
+			'sourceMapWriteTo'		=> null,
+
+			// output source contents?
+			'outputSourceFiles'		=> false,
+
+			// base path for filename normalization
+			'sourceMapRootpath'		=> '',
+
+			// base path for filename normalization
+			'sourceMapBasepath'   => ''
+	);
+
+	/**
+	 * The base64 VLQ encoder
+	 *
+	 * @var Less_SourceMap_Base64VLQ
+	 */
+	protected $encoder;
+
+	/**
+	 * Array of mappings
+	 *
+	 * @var array
+	 */
+	protected $mappings = array();
+
+	/**
+	 * The root node
+	 *
+	 * @var Less_Tree_Ruleset
+	 */
+	protected $root;
+
+	/**
+	 * Array of contents map
+	 *
+	 * @var array
+	 */
+	protected $contentsMap = array();
+
+	/**
+	 * File to content map
+	 *
+	 * @var array
+	 */
+	protected $sources = array();
+	protected $source_keys = array();
+
+	/**
+	 * Constructor
+	 *
+	 * @param Less_Tree_Ruleset $root The root node
+	 * @param array $options Array of options
+	 */
+	public function __construct( Less_Tree_Ruleset $root, $contentsMap, $options = array() ) {
+		$this->root = $root;
+		$this->contentsMap = $contentsMap;
+		$this->encoder = new Less_SourceMap_Base64VLQ();
+
+		$this->SetOptions( $options );
+
+		$this->options['sourceMapRootpath'] = $this->fixWindowsPath( $this->options['sourceMapRootpath'], true );
+		$this->options['sourceMapBasepath'] = $this->fixWindowsPath( $this->options['sourceMapBasepath'], true );
+	}
+
+	/**
+	 * Generates the CSS
+	 *
+	 * @return string
+	 */
+	public function generateCSS() {
+		$output = new Less_Output_Mapped( $this->contentsMap, $this );
+
+		// catch the output
+		$this->root->genCSS( $output );
+
+		$sourceMapUrl				= $this->getOption( 'sourceMapURL' );
+		$sourceMapFilename			= $this->getOption( 'sourceMapFilename' );
+		$sourceMapContent			= $this->generateJson();
+		$sourceMapWriteTo			= $this->getOption( 'sourceMapWriteTo' );
+
+		if ( !$sourceMapUrl && $sourceMapFilename ) {
+			$sourceMapUrl = $this->normalizeFilename( $sourceMapFilename );
+		}
+
+		// write map to a file
+		if ( $sourceMapWriteTo ) {
+			$this->saveMap( $sourceMapWriteTo, $sourceMapContent );
+		}
+
+		// inline the map
+		if ( !$sourceMapUrl ) {
+			$sourceMapUrl = sprintf( 'data:application/json,%s', Less_Functions::encodeURIComponent( $sourceMapContent ) );
+		}
+
+		if ( $sourceMapUrl ) {
+			$output->add( sprintf( '/*# sourceMappingURL=%s */', $sourceMapUrl ) );
+		}
+
+		return $output->toString();
+	}
+
+	/**
+	 * Saves the source map to a file
+	 *
+	 * @param string $file The absolute path to a file
+	 * @param string $content The content to write
+	 * @throws Exception If the file could not be saved
+	 */
+	protected function saveMap( $file, $content ) {
+		$dir = dirname( $file );
+		// directory does not exist
+		if ( !is_dir( $dir ) ) {
+			// FIXME: create the dir automatically?
+			throw new Exception( sprintf( 'The directory "%s" does not exist. Cannot save the source map.', $dir ) );
+		}
+		// FIXME: proper saving, with dir write check!
+		if ( file_put_contents( $file, $content ) === false ) {
+			throw new Exception( sprintf( 'Cannot save the source map to "%s"', $file ) );
+		}
+		return true;
+	}
+
+	/**
+	 * Normalizes the filename
+	 *
+	 * @param string $filename
+	 * @return string
+	 */
+	protected function normalizeFilename( $filename ) {
+		$filename = $this->fixWindowsPath( $filename );
+
+		$rootpath = $this->getOption( 'sourceMapRootpath' );
+		$basePath = $this->getOption( 'sourceMapBasepath' );
+
+		// "Trim" the 'sourceMapBasepath' from the output filename.
+		if ( is_string( $basePath ) && strpos( $filename, $basePath ) === 0 ) {
+			$filename = substr( $filename, strlen( $basePath ) );
+		}
+
+		// Remove extra leading path separators.
+		if ( strpos( $filename, '\\' ) === 0 || strpos( $filename, '/' ) === 0 ) {
+			$filename = substr( $filename, 1 );
+		}
+
+		return $rootpath . $filename;
+	}
+
+	/**
+	 * Adds a mapping
+	 *
+	 * @param integer $generatedLine The line number in generated file
+	 * @param integer $generatedColumn The column number in generated file
+	 * @param integer $originalLine The line number in original file
+	 * @param integer $originalColumn The column number in original file
+	 * @param string $sourceFile The original source file
+	 */
+	public function addMapping( $generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ) {
+		$this->mappings[] = array(
+			'generated_line' => $generatedLine,
+			'generated_column' => $generatedColumn,
+			'original_line' => $originalLine,
+			'original_column' => $originalColumn,
+			'source_file' => $fileInfo['currentUri']
+		);
+
+		$this->sources[$fileInfo['currentUri']] = $fileInfo['filename'];
+	}
+
+	/**
+	 * Generates the JSON source map
+	 *
+	 * @return string
+	 * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
+	 */
+	protected function generateJson() {
+		$sourceMap = array();
+		$mappings = $this->generateMappings();
+
+		// File version (always the first entry in the object) and must be a positive integer.
+		$sourceMap['version'] = self::VERSION;
+
+		// An optional name of the generated code that this source map is associated with.
+		$file = $this->getOption( 'sourceMapFilename' );
+		if ( $file ) {
+			$sourceMap['file'] = $file;
+		}
+
+		// An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry.	This value is prepended to the individual entries in the 'source' field.
+		$root = $this->getOption( 'sourceRoot' );
+		if ( $root ) {
+			$sourceMap['sourceRoot'] = $root;
+		}
+
+		// A list of original sources used by the 'mappings' entry.
+		$sourceMap['sources'] = array();
+		foreach ( $this->sources as $source_uri => $source_filename ) {
+			$sourceMap['sources'][] = $this->normalizeFilename( $source_filename );
+		}
+
+		// A list of symbol names used by the 'mappings' entry.
+		$sourceMap['names'] = array();
+
+		// A string with the encoded mapping data.
+		$sourceMap['mappings'] = $mappings;
+
+		if ( $this->getOption( 'outputSourceFiles' ) ) {
+			// An optional list of source content, useful when the 'source' can't be hosted.
+			// The contents are listed in the same order as the sources above.
+			// 'null' may be used if some original sources should be retrieved by name.
+			$sourceMap['sourcesContent'] = $this->getSourcesContent();
+		}
+
+		// less.js compat fixes
+		if ( count( $sourceMap['sources'] ) && empty( $sourceMap['sourceRoot'] ) ) {
+			unset( $sourceMap['sourceRoot'] );
+		}
+
+		return json_encode( $sourceMap );
+	}
+
+	/**
+	 * Returns the sources contents
+	 *
+	 * @return array|null
+	 */
+	protected function getSourcesContent() {
+		if ( empty( $this->sources ) ) {
+			return;
+		}
+		$content = array();
+		foreach ( $this->sources as $sourceFile ) {
+			$content[] = file_get_contents( $sourceFile );
+		}
+		return $content;
+	}
+
+	/**
+	 * Generates the mappings string
+	 *
+	 * @return string
+	 */
+	public function generateMappings() {
+		if ( !count( $this->mappings ) ) {
+			return '';
+		}
+
+		$this->source_keys = array_flip( array_keys( $this->sources ) );
+
+		// group mappings by generated line number.
+		$groupedMap = $groupedMapEncoded = array();
+		foreach ( $this->mappings as $m ) {
+			$groupedMap[$m['generated_line']][] = $m;
+		}
+		ksort( $groupedMap );
+
+		$lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
+
+		foreach ( $groupedMap as $lineNumber => $line_map ) {
+			while ( ++$lastGeneratedLine < $lineNumber ) {
+				$groupedMapEncoded[] = ';';
+			}
+
+			$lineMapEncoded = array();
+			$lastGeneratedColumn = 0;
+
+			foreach ( $line_map as $m ) {
+				$mapEncoded = $this->encoder->encode( $m['generated_column'] - $lastGeneratedColumn );
+				$lastGeneratedColumn = $m['generated_column'];
+
+				// find the index
+				if ( $m['source_file'] ) {
+					$index = $this->findFileIndex( $m['source_file'] );
+					if ( $index !== false ) {
+						$mapEncoded .= $this->encoder->encode( $index - $lastOriginalIndex );
+						$lastOriginalIndex = $index;
+
+						// lines are stored 0-based in SourceMap spec version 3
+						$mapEncoded .= $this->encoder->encode( $m['original_line'] - 1 - $lastOriginalLine );
+						$lastOriginalLine = $m['original_line'] - 1;
+
+						$mapEncoded .= $this->encoder->encode( $m['original_column'] - $lastOriginalColumn );
+						$lastOriginalColumn = $m['original_column'];
+					}
+				}
+
+				$lineMapEncoded[] = $mapEncoded;
+			}
+
+			$groupedMapEncoded[] = implode( ',', $lineMapEncoded ) . ';';
+		}
+
+		return rtrim( implode( $groupedMapEncoded ), ';' );
+	}
+
+	/**
+	 * Finds the index for the filename
+	 *
+	 * @param string $filename
+	 * @return integer|false
+	 */
+	protected function findFileIndex( $filename ) {
+		return $this->source_keys[$filename];
+	}
+
+	/**
+	 * fix windows paths
+	 * @param string $path
+	 * @return string
+	 */
+	public function fixWindowsPath( $path, $addEndSlash = false ) {
+		$slash = ( $addEndSlash ) ? '/' : '';
+		if ( !empty( $path ) ) {
+			$path = str_replace( '\\', '/', $path );
+			$path = rtrim( $path, '/' ) . $slash;
+		}
+
+		return $path;
+	}
+
+}

+ 84 - 0
lessphp/lib/Less/Tree.php

@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * Tree
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree {
+
+	public $cache_string;
+
+	public function toCSS() {
+		$output = new Less_Output();
+		$this->genCSS( $output );
+		return $output->toString();
+	}
+
+	/**
+	 * Generate CSS by adding it to the output object
+	 *
+	 * @param Less_Output $output The output
+	 * @return void
+	 */
+	public function genCSS( $output ) {
+	}
+
+	/**
+	 * @param Less_Tree_Ruleset[] $rules
+	 */
+	public static function outputRuleset( $output, $rules ) {
+		$ruleCnt = count( $rules );
+		Less_Environment::$tabLevel++;
+
+		// Compressed
+		if ( Less_Parser::$options['compress'] ) {
+			$output->add( '{' );
+			for ( $i = 0; $i < $ruleCnt; $i++ ) {
+				$rules[$i]->genCSS( $output );
+			}
+
+			$output->add( '}' );
+			Less_Environment::$tabLevel--;
+			return;
+		}
+
+		// Non-compressed
+		$tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel - 1 );
+		$tabRuleStr = $tabSetStr.Less_Parser::$options['indentation'];
+
+		$output->add( " {" );
+		for ( $i = 0; $i < $ruleCnt; $i++ ) {
+			$output->add( $tabRuleStr );
+			$rules[$i]->genCSS( $output );
+		}
+		Less_Environment::$tabLevel--;
+		$output->add( $tabSetStr.'}' );
+
+	}
+
+	public function accept( $visitor ) {
+	}
+
+	public static function ReferencedArray( $rules ) {
+		foreach ( $rules as $rule ) {
+			if ( method_exists( $rule, 'markReferenced' ) ) {
+				$rule->markReferenced();
+			}
+		}
+	}
+
+	/**
+	 * Requires php 5.3+
+	 */
+	public static function __set_state( $args ) {
+		$class = get_called_class();
+		$obj = new $class( null, null, null, null );
+		foreach ( $args as $key => $val ) {
+			$obj->$key = $val;
+		}
+		return $obj;
+	}
+
+}

+ 48 - 0
lessphp/lib/Less/Tree/Alpha.php

@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Alpha
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Alpha extends Less_Tree {
+	public $value;
+	public $type = 'Alpha';
+
+	public function __construct( $val ) {
+		$this->value = $val;
+	}
+
+	// function accept( $visitor ){
+	//	$this->value = $visitor->visit( $this->value );
+	//}
+
+	public function compile( $env ) {
+		if ( is_object( $this->value ) ) {
+			$this->value = $this->value->compile( $env );
+		}
+
+		return $this;
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( "alpha(opacity=" );
+
+		if ( is_string( $this->value ) ) {
+			$output->add( $this->value );
+		} else {
+			$this->value->genCSS( $output );
+		}
+
+		$output->add( ')' );
+	}
+
+	public function toCSS() {
+		return "alpha(opacity=" . ( is_string( $this->value ) ? $this->value : $this->value->toCSS() ) . ")";
+	}
+
+}

+ 58 - 0
lessphp/lib/Less/Tree/Anonymous.php

@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Anonymous
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Anonymous extends Less_Tree {
+	public $value;
+	public $quote;
+	public $index;
+	public $mapLines;
+	public $currentFileInfo;
+	public $type = 'Anonymous';
+
+	/**
+	 * @param integer $index
+	 * @param boolean $mapLines
+	 */
+	public function __construct( $value, $index = null, $currentFileInfo = null, $mapLines = null ) {
+		$this->value = $value;
+		$this->index = $index;
+		$this->mapLines = $mapLines;
+		$this->currentFileInfo = $currentFileInfo;
+	}
+
+	public function compile() {
+		return new Less_Tree_Anonymous( $this->value, $this->index, $this->currentFileInfo, $this->mapLines );
+	}
+
+	public function compare( $x ) {
+		if ( !is_object( $x ) ) {
+			return -1;
+		}
+
+		$left = $this->toCSS();
+		$right = $x->toCSS();
+
+		if ( $left === $right ) {
+			return 0;
+		}
+
+		return $left < $right ? -1 : 1;
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
+	}
+
+	public function toCSS() {
+		return $this->value;
+	}
+
+}

+ 39 - 0
lessphp/lib/Less/Tree/Assignment.php

@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Assignment
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Assignment extends Less_Tree {
+
+	public $key;
+	public $value;
+	public $type = 'Assignment';
+
+	public function __construct( $key, $val ) {
+		$this->key = $key;
+		$this->value = $val;
+	}
+
+	public function accept( $visitor ) {
+		$this->value = $visitor->visitObj( $this->value );
+	}
+
+	public function compile( $env ) {
+		return new Less_Tree_Assignment( $this->key, $this->value->compile( $env ) );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( $this->key . '=' );
+		$this->value->genCSS( $output );
+	}
+
+	public function toCss() {
+		return $this->key . '=' . $this->value->toCSS();
+	}
+}

+ 53 - 0
lessphp/lib/Less/Tree/Attribute.php

@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * Attribute
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Attribute extends Less_Tree {
+
+	public $key;
+	public $op;
+	public $value;
+	public $type = 'Attribute';
+
+	public function __construct( $key, $op, $value ) {
+		$this->key = $key;
+		$this->op = $op;
+		$this->value = $value;
+	}
+
+	public function compile( $env ) {
+		$key_obj = is_object( $this->key );
+		$val_obj = is_object( $this->value );
+
+		if ( !$key_obj && !$val_obj ) {
+			return $this;
+		}
+
+		return new Less_Tree_Attribute(
+			$key_obj ? $this->key->compile( $env ) : $this->key,
+			$this->op,
+			$val_obj ? $this->value->compile( $env ) : $this->value );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( $this->toCSS() );
+	}
+
+	public function toCSS() {
+		$value = $this->key;
+
+		if ( $this->op ) {
+			$value .= $this->op;
+			$value .= ( is_object( $this->value ) ? $this->value->toCSS() : $this->value );
+		}
+
+		return '[' . $value . ']';
+	}
+}

+ 117 - 0
lessphp/lib/Less/Tree/Call.php

@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * Call
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Call extends Less_Tree {
+	public $value;
+
+	public $name;
+	public $args;
+	public $index;
+	public $currentFileInfo;
+	public $type = 'Call';
+
+	public function __construct( $name, $args, $index, $currentFileInfo = null ) {
+		$this->name = $name;
+		$this->args = $args;
+		$this->index = $index;
+		$this->currentFileInfo = $currentFileInfo;
+	}
+
+	public function accept( $visitor ) {
+		$this->args = $visitor->visitArray( $this->args );
+	}
+
+	//
+	// When evaluating a function call,
+	// we either find the function in `tree.functions` [1],
+	// in which case we call it, passing the  evaluated arguments,
+	// or we simply print it out as it appeared originally [2].
+	//
+	// The *functions.js* file contains the built-in functions.
+	//
+	// The reason why we evaluate the arguments, is in the case where
+	// we try to pass a variable to a function, like: `saturate(@color)`.
+	// The function should receive the value, not the variable.
+	//
+	public function compile( $env = null ) {
+		$args = array();
+		foreach ( $this->args as $a ) {
+			$args[] = $a->compile( $env );
+		}
+
+		$nameLC = strtolower( $this->name );
+		switch ( $nameLC ) {
+			case '%':
+			$nameLC = '_percent';
+			break;
+
+			case 'get-unit':
+			$nameLC = 'getunit';
+			break;
+
+			case 'data-uri':
+			$nameLC = 'datauri';
+			break;
+
+			case 'svg-gradient':
+			$nameLC = 'svggradient';
+			break;
+		}
+
+		$result = null;
+		if ( $nameLC === 'default' ) {
+			$result = Less_Tree_DefaultFunc::compile();
+
+		} else {
+
+			if ( method_exists( 'Less_Functions', $nameLC ) ) { // 1.
+				try {
+
+					$func = new Less_Functions( $env, $this->currentFileInfo );
+					$result = call_user_func_array( array( $func,$nameLC ), $args );
+
+				} catch ( Exception $e ) {
+					throw new Less_Exception_Compiler( 'error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index );
+				}
+			} elseif ( isset( $env->functions[$nameLC] ) && is_callable( $env->functions[$nameLC] ) ) {
+				try {
+					$result = call_user_func_array( $env->functions[$nameLC], $args );
+				} catch ( Exception $e ) {
+					throw new Less_Exception_Compiler( 'error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index );
+				}
+			}
+		}
+
+		if ( $result !== null ) {
+			return $result;
+		}
+
+		return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( $this->name . '(', $this->currentFileInfo, $this->index );
+		$args_len = count( $this->args );
+		for ( $i = 0; $i < $args_len; $i++ ) {
+			$this->args[$i]->genCSS( $output );
+			if ( $i + 1 < $args_len ) {
+				$output->add( ', ' );
+			}
+		}
+
+		$output->add( ')' );
+	}
+
+	// public function toCSS(){
+	//    return $this->compile()->toCSS();
+	//}
+
+}

+ 230 - 0
lessphp/lib/Less/Tree/Color.php

@@ -0,0 +1,230 @@
+<?php
+
+/**
+ * Color
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Color extends Less_Tree {
+	public $rgb;
+	public $alpha;
+	public $isTransparentKeyword;
+	public $type = 'Color';
+
+	public function __construct( $rgb, $a = 1, $isTransparentKeyword = null ) {
+		if ( $isTransparentKeyword ) {
+			$this->rgb = $rgb;
+			$this->alpha = $a;
+			$this->isTransparentKeyword = true;
+			return;
+		}
+
+		$this->rgb = array();
+		if ( is_array( $rgb ) ) {
+			$this->rgb = $rgb;
+		} else if ( strlen( $rgb ) == 6 ) {
+			foreach ( str_split( $rgb, 2 ) as $c ) {
+				$this->rgb[] = hexdec( $c );
+			}
+		} else {
+			foreach ( str_split( $rgb, 1 ) as $c ) {
+				$this->rgb[] = hexdec( $c.$c );
+			}
+		}
+		$this->alpha = is_numeric( $a ) ? $a : 1;
+	}
+
+	public function compile() {
+		return $this;
+	}
+
+	public function luma() {
+		$r = $this->rgb[0] / 255;
+		$g = $this->rgb[1] / 255;
+		$b = $this->rgb[2] / 255;
+
+		$r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( ( $r + 0.055 ) / 1.055 ), 2.4 );
+		$g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( ( $g + 0.055 ) / 1.055 ), 2.4 );
+		$b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( ( $b + 0.055 ) / 1.055 ), 2.4 );
+
+		return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( $this->toCSS() );
+	}
+
+	public function toCSS( $doNotCompress = false ) {
+		$compress = Less_Parser::$options['compress'] && !$doNotCompress;
+		$alpha = Less_Functions::fround( $this->alpha );
+
+		//
+		// If we have some transparency, the only way to represent it
+		// is via `rgba`. Otherwise, we use the hex representation,
+		// which has better compatibility with older browsers.
+		// Values are capped between `0` and `255`, rounded and zero-padded.
+		//
+		if ( $alpha < 1 ) {
+			if ( ( $alpha === 0 || $alpha === 0.0 ) && isset( $this->isTransparentKeyword ) && $this->isTransparentKeyword ) {
+				return 'transparent';
+			}
+
+			$values = array();
+			foreach ( $this->rgb as $c ) {
+				$values[] = Less_Functions::clamp( round( $c ), 255 );
+			}
+			$values[] = $alpha;
+
+			$glue = ( $compress ? ',' : ', ' );
+			return "rgba(" . implode( $glue, $values ) . ")";
+		} else {
+
+			$color = $this->toRGB();
+
+			if ( $compress ) {
+
+				// Convert color to short format
+				if ( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6] ) {
+					$color = '#'.$color[1] . $color[3] . $color[5];
+				}
+			}
+
+			return $color;
+		}
+	}
+
+	//
+	// Operations have to be done per-channel, if not,
+	// channels will spill onto each other. Once we have
+	// our result, in the form of an integer triplet,
+	// we create a new Color node to hold the result.
+	//
+
+	/**
+	 * @param string $op
+	 */
+	public function operate( $op, $other ) {
+		$rgb = array();
+		$alpha = $this->alpha * ( 1 - $other->alpha ) + $other->alpha;
+		for ( $c = 0; $c < 3; $c++ ) {
+			$rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c] );
+		}
+		return new Less_Tree_Color( $rgb, $alpha );
+	}
+
+	public function toRGB() {
+		return $this->toHex( $this->rgb );
+	}
+
+	public function toHSL() {
+		$r = $this->rgb[0] / 255;
+		$g = $this->rgb[1] / 255;
+		$b = $this->rgb[2] / 255;
+		$a = $this->alpha;
+
+		$max = max( $r, $g, $b );
+		$min = min( $r, $g, $b );
+		$l = ( $max + $min ) / 2;
+		$d = $max - $min;
+
+		$h = $s = 0;
+		if ( $max !== $min ) {
+			$s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
+
+			switch ( $max ) {
+				case $r: $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
+break;
+				case $g: $h = ( $b - $r ) / $d + 2;
+break;
+				case $b: $h = ( $r - $g ) / $d + 4;
+break;
+			}
+			$h /= 6;
+		}
+		return array( 'h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a );
+	}
+
+	// Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
+	public function toHSV() {
+		$r = $this->rgb[0] / 255;
+		$g = $this->rgb[1] / 255;
+		$b = $this->rgb[2] / 255;
+		$a = $this->alpha;
+
+		$max = max( $r, $g, $b );
+		$min = min( $r, $g, $b );
+
+		$v = $max;
+
+		$d = $max - $min;
+		if ( $max === 0 ) {
+			$s = 0;
+		} else {
+			$s = $d / $max;
+		}
+
+		$h = 0;
+		if ( $max !== $min ) {
+			switch ( $max ) {
+				case $r: $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
+break;
+				case $g: $h = ( $b - $r ) / $d + 2;
+break;
+				case $b: $h = ( $r - $g ) / $d + 4;
+break;
+			}
+			$h /= 6;
+		}
+		return array( 'h' => $h * 360, 's' => $s, 'v' => $v, 'a' => $a );
+	}
+
+	public function toARGB() {
+		$argb = array_merge( (array)Less_Parser::round( $this->alpha * 255 ), $this->rgb );
+		return $this->toHex( $argb );
+	}
+
+	public function compare( $x ) {
+		if ( !property_exists( $x, 'rgb' ) ) {
+			return -1;
+		}
+
+		return ( $x->rgb[0] === $this->rgb[0] &&
+			$x->rgb[1] === $this->rgb[1] &&
+			$x->rgb[2] === $this->rgb[2] &&
+			$x->alpha === $this->alpha ) ? 0 : -1;
+	}
+
+	public function toHex( $v ) {
+		$ret = '#';
+		foreach ( $v as $c ) {
+			$c = Less_Functions::clamp( Less_Parser::round( $c ), 255 );
+			if ( $c < 16 ) {
+				$ret .= '0';
+			}
+			$ret .= dechex( $c );
+		}
+
+		return $ret;
+	}
+
+	/**
+	 * @param string $keyword
+	 */
+	public static function fromKeyword( $keyword ) {
+		$keyword = strtolower( $keyword );
+
+		if ( Less_Colors::hasOwnProperty( $keyword ) ) {
+			// detect named color
+			return new Less_Tree_Color( substr( Less_Colors::color( $keyword ), 1 ) );
+		}
+
+		if ( $keyword === 'transparent' ) {
+			return new Less_Tree_Color( array( 0, 0, 0 ), 0, true );
+		}
+	}
+
+}

+ 51 - 0
lessphp/lib/Less/Tree/Comment.php

@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Comment
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Comment extends Less_Tree {
+
+	public $value;
+	public $silent;
+	public $isReferenced;
+	public $currentFileInfo;
+	public $type = 'Comment';
+
+	public function __construct( $value, $silent, $index = null, $currentFileInfo = null ) {
+		$this->value = $value;
+		$this->silent = !!$silent;
+		$this->currentFileInfo = $currentFileInfo;
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		// if( $this->debugInfo ){
+			//$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index);
+		//}
+		$output->add( trim( $this->value ) );// TODO shouldn't need to trim, we shouldn't grab the \n
+	}
+
+	public function toCSS() {
+		return Less_Parser::$options['compress'] ? '' : $this->value;
+	}
+
+	public function isSilent() {
+		$isReference = ( $this->currentFileInfo && isset( $this->currentFileInfo['reference'] ) && ( !isset( $this->isReferenced ) || !$this->isReferenced ) );
+		$isCompressed = Less_Parser::$options['compress'] && !preg_match( '/^\/\*!/', $this->value );
+		return $this->silent || $isReference || $isCompressed;
+	}
+
+	public function compile() {
+		return $this;
+	}
+
+	public function markReferenced() {
+		$this->isReferenced = true;
+	}
+
+}

+ 72 - 0
lessphp/lib/Less/Tree/Condition.php

@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Condition
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Condition extends Less_Tree {
+
+	public $op;
+	public $lvalue;
+	public $rvalue;
+	public $index;
+	public $negate;
+	public $type = 'Condition';
+
+	public function __construct( $op, $l, $r, $i = 0, $negate = false ) {
+		$this->op = trim( $op );
+		$this->lvalue = $l;
+		$this->rvalue = $r;
+		$this->index = $i;
+		$this->negate = $negate;
+	}
+
+	public function accept( $visitor ) {
+		$this->lvalue = $visitor->visitObj( $this->lvalue );
+		$this->rvalue = $visitor->visitObj( $this->rvalue );
+	}
+
+	public function compile( $env ) {
+		$a = $this->lvalue->compile( $env );
+		$b = $this->rvalue->compile( $env );
+
+		switch ( $this->op ) {
+			case 'and':
+				$result = $a && $b;
+			break;
+
+			case 'or':
+				$result = $a || $b;
+			break;
+
+			default:
+				if ( Less_Parser::is_method( $a, 'compare' ) ) {
+					$result = $a->compare( $b );
+				} elseif ( Less_Parser::is_method( $b, 'compare' ) ) {
+					$result = $b->compare( $a );
+				} else {
+					throw new Less_Exception_Compiler( 'Unable to perform comparison', null, $this->index );
+				}
+
+				switch ( $result ) {
+					case -1:
+					$result = $this->op === '<' || $this->op === '=<' || $this->op === '<=';
+					break;
+
+					case  0:
+					$result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<=';
+					break;
+
+					case  1:
+					$result = $this->op === '>' || $this->op === '>=';
+					break;
+				}
+			break;
+		}
+
+		return $this->negate ? !$result : $result;
+	}
+
+}

+ 34 - 0
lessphp/lib/Less/Tree/DefaultFunc.php

@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * DefaultFunc
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_DefaultFunc {
+
+	static $error_;
+	static $value_;
+
+	public static function compile() {
+		if ( self::$error_ ) {
+			throw new Exception( self::$error_ );
+		}
+		if ( self::$value_ !== null ) {
+			return self::$value_ ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
+		}
+	}
+
+	public static function value( $v ) {
+		self::$value_ = $v;
+	}
+
+	public static function error( $e ) {
+		self::$error_ = $e;
+	}
+
+	public static function reset() {
+		self::$value_ = self::$error_ = null;
+	}
+}

+ 39 - 0
lessphp/lib/Less/Tree/DetachedRuleset.php

@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * DetachedRuleset
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_DetachedRuleset extends Less_Tree {
+
+	public $ruleset;
+	public $frames;
+	public $type = 'DetachedRuleset';
+
+	public function __construct( $ruleset, $frames = null ) {
+		$this->ruleset = $ruleset;
+		$this->frames = $frames;
+	}
+
+	public function accept( $visitor ) {
+		$this->ruleset = $visitor->visitObj( $this->ruleset );
+	}
+
+	public function compile( $env ) {
+		if ( $this->frames ) {
+			$frames = $this->frames;
+		} else {
+			$frames = $env->frames;
+		}
+		return new Less_Tree_DetachedRuleset( $this->ruleset, $frames );
+	}
+
+	public function callEval( $env ) {
+		if ( $this->frames ) {
+			return $this->ruleset->compile( $env->copyEvalEnv( array_merge( $this->frames, $env->frames ) ) );
+		}
+		return $this->ruleset->compile( $env );
+	}
+}

+ 198 - 0
lessphp/lib/Less/Tree/Dimension.php

@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * Dimension
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Dimension extends Less_Tree {
+
+	public $value;
+	public $unit;
+	public $type = 'Dimension';
+
+	public function __construct( $value, $unit = null ) {
+		$this->value = floatval( $value );
+
+		if ( $unit && ( $unit instanceof Less_Tree_Unit ) ) {
+			$this->unit = $unit;
+		} elseif ( $unit ) {
+			$this->unit = new Less_Tree_Unit( array( $unit ) );
+		} else {
+			$this->unit = new Less_Tree_Unit();
+		}
+	}
+
+	public function accept( $visitor ) {
+		$this->unit = $visitor->visitObj( $this->unit );
+	}
+
+	public function compile() {
+		return $this;
+	}
+
+	public function toColor() {
+		return new Less_Tree_Color( array( $this->value, $this->value, $this->value ) );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		if ( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ) {
+			throw new Less_Exception_Compiler( "Multiple units in dimension. Correct the units or use the unit function. Bad unit: ".$this->unit->toString() );
+		}
+
+		$value = Less_Functions::fround( $this->value );
+		$strValue = (string)$value;
+
+		if ( $value !== 0 && $value < 0.000001 && $value > -0.000001 ) {
+			// would be output 1e-6 etc.
+			$strValue = number_format( $strValue, 10 );
+			$strValue = preg_replace( '/\.?0+$/', '', $strValue );
+		}
+
+		if ( Less_Parser::$options['compress'] ) {
+			// Zero values doesn't need a unit
+			if ( $value === 0 && $this->unit->isLength() ) {
+				$output->add( $strValue );
+				return $strValue;
+			}
+
+			// Float values doesn't need a leading zero
+			if ( $value > 0 && $value < 1 && $strValue[0] === '0' ) {
+				$strValue = substr( $strValue, 1 );
+			}
+		}
+
+		$output->add( $strValue );
+		$this->unit->genCSS( $output );
+	}
+
+	public function __toString() {
+		return $this->toCSS();
+	}
+
+	// In an operation between two Dimensions,
+	// we default to the first Dimension's unit,
+	// so `1px + 2em` will yield `3px`.
+
+	/**
+	 * @param string $op
+	 */
+	public function operate( $op, $other ) {
+		$value = Less_Functions::operate( $op, $this->value, $other->value );
+		$unit = clone $this->unit;
+
+		if ( $op === '+' || $op === '-' ) {
+
+			if ( !$unit->numerator && !$unit->denominator ) {
+				$unit->numerator = $other->unit->numerator;
+				$unit->denominator = $other->unit->denominator;
+			} elseif ( !$other->unit->numerator && !$other->unit->denominator ) {
+				// do nothing
+			} else {
+				$other = $other->convertTo( $this->unit->usedUnits() );
+
+				if ( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ) {
+					throw new Less_Exception_Compiler( "Incompatible units. Change the units or use the unit function. Bad units: '" . $unit->toString() . "' and " . $other->unit->toString() . "'." );
+				}
+
+				$value = Less_Functions::operate( $op, $this->value, $other->value );
+			}
+		} elseif ( $op === '*' ) {
+			$unit->numerator = array_merge( $unit->numerator, $other->unit->numerator );
+			$unit->denominator = array_merge( $unit->denominator, $other->unit->denominator );
+			sort( $unit->numerator );
+			sort( $unit->denominator );
+			$unit->cancel();
+		} elseif ( $op === '/' ) {
+			$unit->numerator = array_merge( $unit->numerator, $other->unit->denominator );
+			$unit->denominator = array_merge( $unit->denominator, $other->unit->numerator );
+			sort( $unit->numerator );
+			sort( $unit->denominator );
+			$unit->cancel();
+		}
+		return new Less_Tree_Dimension( $value, $unit );
+	}
+
+	public function compare( $other ) {
+		if ( $other instanceof Less_Tree_Dimension ) {
+
+			if ( $this->unit->isEmpty() || $other->unit->isEmpty() ) {
+				$a = $this;
+				$b = $other;
+			} else {
+				$a = $this->unify();
+				$b = $other->unify();
+				if ( $a->unit->compare( $b->unit ) !== 0 ) {
+					return -1;
+				}
+			}
+			$aValue = $a->value;
+			$bValue = $b->value;
+
+			if ( $bValue > $aValue ) {
+				return -1;
+			} elseif ( $bValue < $aValue ) {
+				return 1;
+			} else {
+				return 0;
+			}
+		} else {
+			return -1;
+		}
+	}
+
+	public function unify() {
+		return $this->convertTo( array( 'length' => 'px', 'duration' => 's', 'angle' => 'rad' ) );
+	}
+
+	public function convertTo( $conversions ) {
+		$value = $this->value;
+		$unit = clone $this->unit;
+
+		if ( is_string( $conversions ) ) {
+			$derivedConversions = array();
+			foreach ( Less_Tree_UnitConversions::$groups as $i ) {
+				if ( isset( Less_Tree_UnitConversions::${$i}[$conversions] ) ) {
+					$derivedConversions = array( $i => $conversions );
+				}
+			}
+			$conversions = $derivedConversions;
+		}
+
+		foreach ( $conversions as $groupName => $targetUnit ) {
+			$group = Less_Tree_UnitConversions::${$groupName};
+
+			// numerator
+			foreach ( $unit->numerator as $i => $atomicUnit ) {
+				$atomicUnit = $unit->numerator[$i];
+				if ( !isset( $group[$atomicUnit] ) ) {
+					continue;
+				}
+
+				$value = $value * ( $group[$atomicUnit] / $group[$targetUnit] );
+
+				$unit->numerator[$i] = $targetUnit;
+			}
+
+			// denominator
+			foreach ( $unit->denominator as $i => $atomicUnit ) {
+				$atomicUnit = $unit->denominator[$i];
+				if ( !isset( $group[$atomicUnit] ) ) {
+					continue;
+				}
+
+				$value = $value / ( $group[$atomicUnit] / $group[$targetUnit] );
+
+				$unit->denominator[$i] = $targetUnit;
+			}
+		}
+
+		$unit->cancel();
+
+		return new Less_Tree_Dimension( $value, $unit );
+	}
+}

+ 96 - 0
lessphp/lib/Less/Tree/Directive.php

@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * Directive
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Directive extends Less_Tree {
+
+	public $name;
+	public $value;
+	public $rules;
+	public $index;
+	public $isReferenced;
+	public $currentFileInfo;
+	public $debugInfo;
+	public $type = 'Directive';
+
+	public function __construct( $name, $value = null, $rules = null, $index = null, $currentFileInfo = null, $debugInfo = null ) {
+		$this->name = $name;
+		$this->value = $value;
+		if ( $rules ) {
+			$this->rules = $rules;
+			$this->rules->allowImports = true;
+		}
+
+		$this->index = $index;
+		$this->currentFileInfo = $currentFileInfo;
+		$this->debugInfo = $debugInfo;
+	}
+
+	public function accept( $visitor ) {
+		if ( $this->rules ) {
+			$this->rules = $visitor->visitObj( $this->rules );
+		}
+		if ( $this->value ) {
+			$this->value = $visitor->visitObj( $this->value );
+		}
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$value = $this->value;
+		$rules = $this->rules;
+		$output->add( $this->name, $this->currentFileInfo, $this->index );
+		if ( $this->value ) {
+			$output->add( ' ' );
+			$this->value->genCSS( $output );
+		}
+		if ( $this->rules ) {
+			Less_Tree::outputRuleset( $output, array( $this->rules ) );
+		} else {
+			$output->add( ';' );
+		}
+	}
+
+	public function compile( $env ) {
+		$value = $this->value;
+		$rules = $this->rules;
+		if ( $value ) {
+			$value = $value->compile( $env );
+		}
+
+		if ( $rules ) {
+			$rules = $rules->compile( $env );
+			$rules->root = true;
+		}
+
+		return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo );
+	}
+
+	public function variable( $name ) {
+		if ( $this->rules ) {
+			return $this->rules->variable( $name );
+		}
+	}
+
+	public function find( $selector ) {
+		if ( $this->rules ) {
+			return $this->rules->find( $selector, $this );
+		}
+	}
+
+	// rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); },
+
+	public function markReferenced() {
+		$this->isReferenced = true;
+		if ( $this->rules ) {
+			Less_Tree::ReferencedArray( $this->rules->rules );
+		}
+	}
+
+}

+ 70 - 0
lessphp/lib/Less/Tree/Element.php

@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Element
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Element extends Less_Tree {
+
+	public $combinator = '';
+	public $value = '';
+	public $index;
+	public $currentFileInfo;
+	public $type = 'Element';
+
+	public $value_is_object = false;
+
+	public function __construct( $combinator, $value, $index = null, $currentFileInfo = null ) {
+		$this->value = $value;
+		$this->value_is_object = is_object( $value );
+
+		if ( $combinator ) {
+			$this->combinator = $combinator;
+		}
+
+		$this->index = $index;
+		$this->currentFileInfo = $currentFileInfo;
+	}
+
+	public function accept( $visitor ) {
+		if ( $this->value_is_object ) { // object or string
+			$this->value = $visitor->visitObj( $this->value );
+		}
+	}
+
+	public function compile( $env ) {
+		if ( Less_Environment::$mixin_stack ) {
+			return new Less_Tree_Element( $this->combinator, ( $this->value_is_object ? $this->value->compile( $env ) : $this->value ), $this->index, $this->currentFileInfo );
+		}
+
+		if ( $this->value_is_object ) {
+			$this->value = $this->value->compile( $env );
+		}
+
+		return $this;
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( $this->toCSS(), $this->currentFileInfo, $this->index );
+	}
+
+	public function toCSS() {
+		if ( $this->value_is_object ) {
+			$value = $this->value->toCSS();
+		} else {
+			$value = $this->value;
+		}
+
+		if ( $value === '' && $this->combinator && $this->combinator === '&' ) {
+			return '';
+		}
+
+		return Less_Environment::$_outputMap[$this->combinator] . $value;
+	}
+
+}

+ 95 - 0
lessphp/lib/Less/Tree/Expression.php

@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * Expression
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Expression extends Less_Tree {
+
+	public $value = array();
+	public $parens = false;
+	public $parensInOp = false;
+	public $type = 'Expression';
+
+	public function __construct( $value, $parens = null ) {
+		$this->value = $value;
+		$this->parens = $parens;
+	}
+
+	public function accept( $visitor ) {
+		$this->value = $visitor->visitArray( $this->value );
+	}
+
+	public function compile( $env ) {
+		$doubleParen = false;
+
+		if ( $this->parens && !$this->parensInOp ) {
+			Less_Environment::$parensStack++;
+		}
+
+		$returnValue = null;
+		if ( $this->value ) {
+
+			$count = count( $this->value );
+
+			if ( $count > 1 ) {
+
+				$ret = array();
+				foreach ( $this->value as $e ) {
+					$ret[] = $e->compile( $env );
+				}
+				$returnValue = new Less_Tree_Expression( $ret );
+
+			} else {
+
+				if ( ( $this->value[0] instanceof Less_Tree_Expression ) && $this->value[0]->parens && !$this->value[0]->parensInOp ) {
+					$doubleParen = true;
+				}
+
+				$returnValue = $this->value[0]->compile( $env );
+			}
+
+		} else {
+			$returnValue = $this;
+		}
+
+		if ( $this->parens ) {
+			if ( !$this->parensInOp ) {
+				Less_Environment::$parensStack--;
+
+			} elseif ( !Less_Environment::isMathOn() && !$doubleParen ) {
+				$returnValue = new Less_Tree_Paren( $returnValue );
+
+			}
+		}
+		return $returnValue;
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$val_len = count( $this->value );
+		for ( $i = 0; $i < $val_len; $i++ ) {
+			$this->value[$i]->genCSS( $output );
+			if ( $i + 1 < $val_len ) {
+				$output->add( ' ' );
+			}
+		}
+	}
+
+	public function throwAwayComments() {
+		if ( is_array( $this->value ) ) {
+			$new_value = array();
+			foreach ( $this->value as $v ) {
+				if ( $v instanceof Less_Tree_Comment ) {
+					continue;
+				}
+				$new_value[] = $v;
+			}
+			$this->value = $new_value;
+		}
+	}
+}

+ 80 - 0
lessphp/lib/Less/Tree/Extend.php

@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * Extend
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Extend extends Less_Tree {
+
+	public $selector;
+	public $option;
+	public $index;
+	public $selfSelectors = array();
+	public $allowBefore;
+	public $allowAfter;
+	public $firstExtendOnThisSelectorPath;
+	public $type = 'Extend';
+	public $ruleset;
+
+	public $object_id;
+	public $parent_ids = array();
+
+	/**
+	 * @param integer $index
+	 */
+	public function __construct( $selector, $option, $index ) {
+		static $i = 0;
+		$this->selector = $selector;
+		$this->option = $option;
+		$this->index = $index;
+
+		switch ( $option ) {
+			case "all":
+				$this->allowBefore = true;
+				$this->allowAfter = true;
+			break;
+			default:
+				$this->allowBefore = false;
+				$this->allowAfter = false;
+			break;
+		}
+
+		// This must use a string (instead of int) so that array_merge()
+		// preserves keys on arrays that use IDs in their keys.
+		$this->object_id = 'id_' . $i++;
+
+		$this->parent_ids = array(
+			$this->object_id => true
+		);
+	}
+
+	public function accept( $visitor ) {
+		$this->selector = $visitor->visitObj( $this->selector );
+	}
+
+	public function compile( $env ) {
+		Less_Parser::$has_extends = true;
+		$this->selector = $this->selector->compile( $env );
+		return $this;
+		// return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index);
+	}
+
+	public function findSelfSelectors( $selectors ) {
+		$selfElements = array();
+
+		for ( $i = 0, $selectors_len = count( $selectors ); $i < $selectors_len; $i++ ) {
+			$selectorElements = $selectors[$i]->elements;
+			// duplicate the logic in genCSS function inside the selector node.
+			// future TODO - move both logics into the selector joiner visitor
+			if ( $i && $selectorElements && $selectorElements[0]->combinator === "" ) {
+				$selectorElements[0]->combinator = ' ';
+			}
+			$selfElements = array_merge( $selfElements, $selectors[$i]->elements );
+		}
+
+		$this->selfSelectors = array( new Less_Tree_Selector( $selfElements ) );
+	}
+
+}

+ 291 - 0
lessphp/lib/Less/Tree/Import.php

@@ -0,0 +1,291 @@
+<?php
+
+/**
+ * CSS @import node
+ *
+ * The general strategy here is that we don't want to wait
+ * for the parsing to be completed, before we start importing
+ * the file. That's because in the context of a browser,
+ * most of the time will be spent waiting for the server to respond.
+ *
+ * On creation, we push the import path to our import queue, though
+ * `import,push`, we also pass it a callback, which it'll call once
+ * the file has been fetched, and parsed.
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Import extends Less_Tree {
+
+	public $options;
+	public $index;
+	public $path;
+	public $features;
+	public $currentFileInfo;
+	public $css;
+	public $skip;
+	public $root;
+	public $type = 'Import';
+
+	public function __construct( $path, $features, $options, $index, $currentFileInfo = null ) {
+		$this->options = $options;
+		$this->index = $index;
+		$this->path = $path;
+		$this->features = $features;
+		$this->currentFileInfo = $currentFileInfo;
+
+		if ( is_array( $options ) ) {
+			$this->options += array( 'inline' => false );
+
+			if ( isset( $this->options['less'] ) || $this->options['inline'] ) {
+				$this->css = !isset( $this->options['less'] ) || !$this->options['less'] || $this->options['inline'];
+			} else {
+				$pathValue = $this->getPath();
+				if ( $pathValue && preg_match( '/css([\?;].*)?$/', $pathValue ) ) {
+					$this->css = true;
+				}
+			}
+		}
+	}
+
+//
+// The actual import node doesn't return anything, when converted to CSS.
+// The reason is that it's used at the evaluation stage, so that the rules
+// it imports can be treated like any other rules.
+//
+// In `eval`, we make sure all Import nodes get evaluated, recursively, so
+// we end up with a flat structure, which can easily be imported in the parent
+// ruleset.
+//
+
+	public function accept( $visitor ) {
+		if ( $this->features ) {
+			$this->features = $visitor->visitObj( $this->features );
+		}
+		$this->path = $visitor->visitObj( $this->path );
+
+		if ( !$this->options['inline'] && $this->root ) {
+			$this->root = $visitor->visit( $this->root );
+		}
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		if ( $this->css ) {
+
+			$output->add( '@import ', $this->currentFileInfo, $this->index );
+
+			$this->path->genCSS( $output );
+			if ( $this->features ) {
+				$output->add( ' ' );
+				$this->features->genCSS( $output );
+			}
+			$output->add( ';' );
+		}
+	}
+
+	public function toCSS() {
+		$features = $this->features ? ' ' . $this->features->toCSS() : '';
+
+		if ( $this->css ) {
+			return "@import " . $this->path->toCSS() . $features . ";\n";
+		} else {
+			return "";
+		}
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getPath() {
+		if ( $this->path instanceof Less_Tree_Quoted ) {
+			$path = $this->path->value;
+			$path = ( isset( $this->css ) || preg_match( '/(\.[a-z]*$)|([\?;].*)$/', $path ) ) ? $path : $path . '.less';
+		} else if ( $this->path instanceof Less_Tree_URL ) {
+			$path = $this->path->value->value;
+		} else {
+			return null;
+		}
+
+		// remove query string and fragment
+		return preg_replace( '/[\?#][^\?]*$/', '', $path );
+	}
+
+	public function compileForImport( $env ) {
+		return new Less_Tree_Import( $this->path->compile( $env ), $this->features, $this->options, $this->index, $this->currentFileInfo );
+	}
+
+	public function compilePath( $env ) {
+		$path = $this->path->compile( $env );
+		$rootpath = '';
+		if ( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ) {
+			$rootpath = $this->currentFileInfo['rootpath'];
+		}
+
+		if ( !( $path instanceof Less_Tree_URL ) ) {
+			if ( $rootpath ) {
+				$pathValue = $path->value;
+				// Add the base path if the import is relative
+				if ( $pathValue && Less_Environment::isPathRelative( $pathValue ) ) {
+					$path->value = $this->currentFileInfo['uri_root'].$pathValue;
+				}
+			}
+			$path->value = Less_Environment::normalizePath( $path->value );
+		}
+
+		return $path;
+	}
+
+	public function compile( $env ) {
+		$evald = $this->compileForImport( $env );
+
+		// get path & uri
+		$path_and_uri = null;
+		if ( is_callable( Less_Parser::$options['import_callback'] ) ) {
+			$path_and_uri = call_user_func( Less_Parser::$options['import_callback'], $evald );
+		}
+
+		if ( !$path_and_uri ) {
+			$path_and_uri = $evald->PathAndUri();
+		}
+
+		if ( $path_and_uri ) {
+			list( $full_path, $uri ) = $path_and_uri;
+		} else {
+			$full_path = $uri = $evald->getPath();
+		}
+
+		// import once
+		if ( $evald->skip( $full_path, $env ) ) {
+			return array();
+		}
+
+		if ( $this->options['inline'] ) {
+			// todo needs to reference css file not import
+			//$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true );
+
+			Less_Parser::AddParsedFile( $full_path );
+			$contents = new Less_Tree_Anonymous( file_get_contents( $full_path ), 0, array(), true );
+
+			if ( $this->features ) {
+				return new Less_Tree_Media( array( $contents ), $this->features->value );
+			}
+
+			return array( $contents );
+		}
+
+		// optional (need to be before "CSS" to support optional CSS imports. CSS should be checked only if empty($this->currentFileInfo))
+		if ( isset( $this->options['optional'] ) && $this->options['optional'] && !file_exists( $full_path ) && ( !$evald->css || !empty( $this->currentFileInfo ) ) ) {
+			return array();
+		}
+
+		// css ?
+		if ( $evald->css ) {
+			$features = ( $evald->features ? $evald->features->compile( $env ) : null );
+			return new Less_Tree_Import( $this->compilePath( $env ), $features, $this->options, $this->index );
+		}
+
+		return $this->ParseImport( $full_path, $uri, $env );
+	}
+
+	/**
+	 * Using the import directories, get the full absolute path and uri of the import
+	 *
+	 * @param Less_Tree_Import $evald
+	 */
+	public function PathAndUri() {
+		$evald_path = $this->getPath();
+
+		if ( $evald_path ) {
+
+			$import_dirs = array();
+
+			if ( Less_Environment::isPathRelative( $evald_path ) ) {
+				// if the path is relative, the file should be in the current directory
+				if ( $this->currentFileInfo ) {
+					$import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root'];
+				}
+
+			} else {
+				// otherwise, the file should be relative to the server root
+				if ( $this->currentFileInfo ) {
+					$import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri'];
+				}
+				// if the user supplied entryPath isn't the actual root
+				$import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = '';
+
+			}
+
+			// always look in user supplied import directories
+			$import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] );
+
+			foreach ( $import_dirs as $rootpath => $rooturi ) {
+				if ( is_callable( $rooturi ) ) {
+					list( $path, $uri ) = call_user_func( $rooturi, $evald_path );
+					if ( is_string( $path ) ) {
+						$full_path = $path;
+						return array( $full_path, $uri );
+					}
+				} elseif ( !empty( $rootpath ) ) {
+
+					$path = rtrim( $rootpath, '/\\' ).'/'.ltrim( $evald_path, '/\\' );
+
+					if ( file_exists( $path ) ) {
+						$full_path = Less_Environment::normalizePath( $path );
+						$uri = Less_Environment::normalizePath( dirname( $rooturi.$evald_path ) );
+						return array( $full_path, $uri );
+					} elseif ( file_exists( $path.'.less' ) ) {
+						$full_path = Less_Environment::normalizePath( $path.'.less' );
+						$uri = Less_Environment::normalizePath( dirname( $rooturi.$evald_path.'.less' ) );
+						return array( $full_path, $uri );
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Parse the import url and return the rules
+	 *
+	 * @return Less_Tree_Media|array
+	 */
+	public function ParseImport( $full_path, $uri, $env ) {
+		$import_env = clone $env;
+		if ( ( isset( $this->options['reference'] ) && $this->options['reference'] ) || isset( $this->currentFileInfo['reference'] ) ) {
+			$import_env->currentFileInfo['reference'] = true;
+		}
+
+		if ( ( isset( $this->options['multiple'] ) && $this->options['multiple'] ) ) {
+			$import_env->importMultiple = true;
+		}
+
+		$parser = new Less_Parser( $import_env );
+		$root = $parser->parseFile( $full_path, $uri, true );
+
+		$ruleset = new Less_Tree_Ruleset( array(), $root->rules );
+		$ruleset->evalImports( $import_env );
+
+		return $this->features ? new Less_Tree_Media( $ruleset->rules, $this->features->value ) : $ruleset->rules;
+	}
+
+	/**
+	 * Should the import be skipped?
+	 *
+	 * @return boolean|null
+	 */
+	private function Skip( $path, $env ) {
+		$path = Less_Parser::AbsPath( $path, true );
+
+		if ( $path && Less_Parser::FileParsed( $path ) ) {
+
+			if ( isset( $this->currentFileInfo['reference'] ) ) {
+				return true;
+			}
+
+			return !isset( $this->options['multiple'] ) && !$env->importMultiple;
+		}
+
+	}
+}

+ 30 - 0
lessphp/lib/Less/Tree/Javascript.php

@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Javascript
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Javascript extends Less_Tree {
+
+	public $type = 'Javascript';
+	public $escaped;
+	public $expression;
+	public $index;
+
+	/**
+	 * @param boolean $index
+	 * @param boolean $escaped
+	 */
+	public function __construct( $string, $index, $escaped ) {
+		$this->escaped = $escaped;
+		$this->expression = $string;
+		$this->index = $index;
+	}
+
+	public function compile() {
+		return new Less_Tree_Anonymous( '/* Sorry, can not do JavaScript evaluation in PHP... :( */' );
+	}
+
+}

+ 43 - 0
lessphp/lib/Less/Tree/Keyword.php

@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Keyword
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Keyword extends Less_Tree {
+
+	public $value;
+	public $type = 'Keyword';
+
+	/**
+	 * @param string $value
+	 */
+	public function __construct( $value ) {
+		$this->value = $value;
+	}
+
+	public function compile() {
+		return $this;
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		if ( $this->value === '%' ) {
+			throw new Less_Exception_Compiler( "Invalid % without number" );
+		}
+
+		$output->add( $this->value );
+	}
+
+	public function compare( $other ) {
+		if ( $other instanceof Less_Tree_Keyword ) {
+			return $other->value === $this->value ? 0 : 1;
+		} else {
+			return -1;
+		}
+	}
+}

+ 173 - 0
lessphp/lib/Less/Tree/Media.php

@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * Media
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Media extends Less_Tree {
+
+	public $features;
+	public $rules;
+	public $index;
+	public $currentFileInfo;
+	public $isReferenced;
+	public $type = 'Media';
+
+	public function __construct( $value = array(), $features = array(), $index = null, $currentFileInfo = null ) {
+		$this->index = $index;
+		$this->currentFileInfo = $currentFileInfo;
+
+		$selectors = $this->emptySelectors();
+
+		$this->features = new Less_Tree_Value( $features );
+
+		$this->rules = array( new Less_Tree_Ruleset( $selectors, $value ) );
+		$this->rules[0]->allowImports = true;
+	}
+
+	public function accept( $visitor ) {
+		$this->features = $visitor->visitObj( $this->features );
+		$this->rules = $visitor->visitArray( $this->rules );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( '@media ', $this->currentFileInfo, $this->index );
+		$this->features->genCSS( $output );
+		Less_Tree::outputRuleset( $output, $this->rules );
+
+	}
+
+	public function compile( $env ) {
+		$media = new Less_Tree_Media( array(), array(), $this->index, $this->currentFileInfo );
+
+		$strictMathBypass = false;
+		if ( Less_Parser::$options['strictMath'] === false ) {
+			$strictMathBypass = true;
+			Less_Parser::$options['strictMath'] = true;
+		}
+
+		$media->features = $this->features->compile( $env );
+
+		if ( $strictMathBypass ) {
+			Less_Parser::$options['strictMath'] = false;
+		}
+
+		$env->mediaPath[] = $media;
+		$env->mediaBlocks[] = $media;
+
+		array_unshift( $env->frames, $this->rules[0] );
+		$media->rules = array( $this->rules[0]->compile( $env ) );
+		array_shift( $env->frames );
+
+		array_pop( $env->mediaPath );
+
+		return !$env->mediaPath ? $media->compileTop( $env ) : $media->compileNested( $env );
+	}
+
+	public function variable( $name ) {
+		return $this->rules[0]->variable( $name );
+	}
+
+	public function find( $selector ) {
+		return $this->rules[0]->find( $selector, $this );
+	}
+
+	public function emptySelectors() {
+		$el = new Less_Tree_Element( '', '&', $this->index, $this->currentFileInfo );
+		$sels = array( new Less_Tree_Selector( array( $el ), array(), null, $this->index, $this->currentFileInfo ) );
+		$sels[0]->mediaEmpty = true;
+		return $sels;
+	}
+
+	public function markReferenced() {
+		$this->rules[0]->markReferenced();
+		$this->isReferenced = true;
+		Less_Tree::ReferencedArray( $this->rules[0]->rules );
+	}
+
+	// evaltop
+	public function compileTop( $env ) {
+		$result = $this;
+
+		if ( count( $env->mediaBlocks ) > 1 ) {
+			$selectors = $this->emptySelectors();
+			$result = new Less_Tree_Ruleset( $selectors, $env->mediaBlocks );
+			$result->multiMedia = true;
+		}
+
+		$env->mediaBlocks = array();
+		$env->mediaPath = array();
+
+		return $result;
+	}
+
+	public function compileNested( $env ) {
+		$path = array_merge( $env->mediaPath, array( $this ) );
+
+		// Extract the media-query conditions separated with `,` (OR).
+		foreach ( $path as $key => $p ) {
+			$value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features;
+			$path[$key] = is_array( $value ) ? $value : array( $value );
+		}
+
+		// Trace all permutations to generate the resulting media-query.
+		//
+		// (a, b and c) with nested (d, e) ->
+		//	a and d
+		//	a and e
+		//	b and c and d
+		//	b and c and e
+
+		$permuted = $this->permute( $path );
+		$expressions = array();
+		foreach ( $permuted as $path ) {
+
+			for ( $i = 0, $len = count( $path ); $i < $len; $i++ ) {
+				$path[$i] = Less_Parser::is_method( $path[$i], 'toCSS' ) ? $path[$i] : new Less_Tree_Anonymous( $path[$i] );
+			}
+
+			for ( $i = count( $path ) - 1; $i > 0; $i-- ) {
+				array_splice( $path, $i, 0, array( new Less_Tree_Anonymous( 'and' ) ) );
+			}
+
+			$expressions[] = new Less_Tree_Expression( $path );
+		}
+		$this->features = new Less_Tree_Value( $expressions );
+
+		// Fake a tree-node that doesn't output anything.
+		return new Less_Tree_Ruleset( array(), array() );
+	}
+
+	public function permute( $arr ) {
+		if ( !$arr )
+			return array();
+
+		if ( count( $arr ) == 1 )
+			return $arr[0];
+
+		$result = array();
+		$rest = $this->permute( array_slice( $arr, 1 ) );
+		foreach ( $rest as $r ) {
+			foreach ( $arr[0] as $a ) {
+				$result[] = array_merge(
+					is_array( $a ) ? $a : array( $a ),
+					is_array( $r ) ? $r : array( $r )
+				);
+			}
+		}
+
+		return $result;
+	}
+
+	public function bubbleSelectors( $selectors ) {
+		if ( !$selectors ) return;
+
+		$this->rules = array( new Less_Tree_Ruleset( $selectors, array( $this->rules[0] ) ) );
+	}
+
+}

+ 193 - 0
lessphp/lib/Less/Tree/Mixin/Call.php

@@ -0,0 +1,193 @@
+<?php
+
+class Less_Tree_Mixin_Call extends Less_Tree {
+
+	public $selector;
+	public $arguments;
+	public $index;
+	public $currentFileInfo;
+
+	public $important;
+	public $type = 'MixinCall';
+
+	/**
+	 * less.js: tree.mixin.Call
+	 *
+	 */
+	public function __construct( $elements, $args, $index, $currentFileInfo, $important = false ) {
+		$this->selector = new Less_Tree_Selector( $elements );
+		$this->arguments = $args;
+		$this->index = $index;
+		$this->currentFileInfo = $currentFileInfo;
+		$this->important = $important;
+	}
+
+	// function accept($visitor){
+	//	$this->selector = $visitor->visit($this->selector);
+	//	$this->arguments = $visitor->visit($this->arguments);
+	//}
+
+	public function compile( $env ) {
+		$rules = array();
+		$match = false;
+		$isOneFound = false;
+		$candidates = array();
+		$defaultUsed = false;
+		$conditionResult = array();
+
+		$args = array();
+		foreach ( $this->arguments as $a ) {
+			$args[] = array( 'name' => $a['name'], 'value' => $a['value']->compile( $env ) );
+		}
+
+		foreach ( $env->frames as $frame ) {
+
+			$mixins = $frame->find( $this->selector );
+
+			if ( !$mixins ) {
+				continue;
+			}
+
+			$isOneFound = true;
+			$defNone = 0;
+			$defTrue = 1;
+			$defFalse = 2;
+
+			// To make `default()` function independent of definition order we have two "subpasses" here.
+			// At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
+			// and build candidate list with corresponding flags. Then, when we know all possible matches,
+			// we make a final decision.
+
+			$mixins_len = count( $mixins );
+			for ( $m = 0; $m < $mixins_len; $m++ ) {
+				$mixin = $mixins[$m];
+
+				if ( $this->IsRecursive( $env, $mixin ) ) {
+					continue;
+				}
+
+				if ( $mixin->matchArgs( $args, $env ) ) {
+
+					$candidate = array( 'mixin' => $mixin, 'group' => $defNone );
+
+					if ( $mixin instanceof Less_Tree_Ruleset ) {
+
+						for ( $f = 0; $f < 2; $f++ ) {
+							Less_Tree_DefaultFunc::value( $f );
+							$conditionResult[$f] = $mixin->matchCondition( $args, $env );
+						}
+						if ( $conditionResult[0] || $conditionResult[1] ) {
+							if ( $conditionResult[0] != $conditionResult[1] ) {
+								$candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse;
+							}
+
+							$candidates[] = $candidate;
+						}
+					} else {
+						$candidates[] = $candidate;
+					}
+
+					$match = true;
+				}
+			}
+
+			Less_Tree_DefaultFunc::reset();
+
+			$count = array( 0, 0, 0 );
+			for ( $m = 0; $m < count( $candidates ); $m++ ) {
+				$count[ $candidates[$m]['group'] ]++;
+			}
+
+			if ( $count[$defNone] > 0 ) {
+				$defaultResult = $defFalse;
+			} else {
+				$defaultResult = $defTrue;
+				if ( ( $count[$defTrue] + $count[$defFalse] ) > 1 ) {
+					throw new Exception( 'Ambiguous use of `default()` found when matching for `' . $this->format( $args ) . '`' );
+				}
+			}
+
+			$candidates_length = count( $candidates );
+			$length_1 = ( $candidates_length == 1 );
+
+			for ( $m = 0; $m < $candidates_length; $m++ ) {
+				$candidate = $candidates[$m]['group'];
+				if ( ( $candidate === $defNone ) || ( $candidate === $defaultResult ) ) {
+					try{
+						$mixin = $candidates[$m]['mixin'];
+						if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
+							$mixin = new Less_Tree_Mixin_Definition( '', array(), $mixin->rules, null, false );
+							$mixin->originalRuleset = $mixins[$m]->originalRuleset;
+						}
+						$rules = array_merge( $rules, $mixin->evalCall( $env, $args, $this->important )->rules );
+					} catch ( Exception $e ) {
+						// throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']);
+						throw new Less_Exception_Compiler( $e->getMessage(), null, null, $this->currentFileInfo );
+					}
+				}
+			}
+
+			if ( $match ) {
+				if ( !$this->currentFileInfo || !isset( $this->currentFileInfo['reference'] ) || !$this->currentFileInfo['reference'] ) {
+					Less_Tree::ReferencedArray( $rules );
+				}
+
+				return $rules;
+			}
+		}
+
+		if ( $isOneFound ) {
+			throw new Less_Exception_Compiler( 'No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo );
+
+		} else {
+			throw new Less_Exception_Compiler( trim( $this->selector->toCSS() ) . " is undefined in ".$this->currentFileInfo['filename'], null, $this->index );
+		}
+
+	}
+
+	/**
+	 * Format the args for use in exception messages
+	 *
+	 */
+	private function Format( $args ) {
+		$message = array();
+		if ( $args ) {
+			foreach ( $args as $a ) {
+				$argValue = '';
+				if ( $a['name'] ) {
+					$argValue .= $a['name'] . ':';
+				}
+				if ( is_object( $a['value'] ) ) {
+					$argValue .= $a['value']->toCSS();
+				} else {
+					$argValue .= '???';
+				}
+				$message[] = $argValue;
+			}
+		}
+		return implode( ', ', $message );
+	}
+
+	/**
+	 * Are we in a recursive mixin call?
+	 *
+	 * @return bool
+	 */
+	private function IsRecursive( $env, $mixin ) {
+		foreach ( $env->frames as $recur_frame ) {
+			if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
+
+				if ( $mixin === $recur_frame ) {
+					return true;
+				}
+
+				if ( isset( $recur_frame->originalRuleset ) && $mixin->ruleset_id === $recur_frame->originalRuleset ) {
+					return true;
+				}
+			}
+		}
+
+		return false;
+	}
+
+}

+ 233 - 0
lessphp/lib/Less/Tree/Mixin/Definition.php

@@ -0,0 +1,233 @@
+<?php
+
+class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
+	public $name;
+	public $selectors;
+	public $params;
+	public $arity		= 0;
+	public $rules;
+	public $lookups		= array();
+	public $required	= 0;
+	public $frames		= array();
+	public $condition;
+	public $variadic;
+	public $type		= 'MixinDefinition';
+
+	// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition
+	public function __construct( $name, $params, $rules, $condition, $variadic = false, $frames = array() ) {
+		$this->name = $name;
+		$this->selectors = array( new Less_Tree_Selector( array( new Less_Tree_Element( null, $name ) ) ) );
+
+		$this->params = $params;
+		$this->condition = $condition;
+		$this->variadic = $variadic;
+		$this->rules = $rules;
+
+		if ( $params ) {
+			$this->arity = count( $params );
+			foreach ( $params as $p ) {
+				if ( !isset( $p['name'] ) || ( $p['name'] && !isset( $p['value'] ) ) ) {
+					$this->required++;
+				}
+			}
+		}
+
+		$this->frames = $frames;
+		$this->SetRulesetIndex();
+	}
+
+	// function accept( $visitor ){
+	//	$this->params = $visitor->visit($this->params);
+	//	$this->rules = $visitor->visit($this->rules);
+	//	$this->condition = $visitor->visit($this->condition);
+	//}
+
+	public function toCSS() {
+		return '';
+	}
+
+	// less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams
+	public function compileParams( $env, $mixinFrames, $args = array(), &$evaldArguments = array() ) {
+		$frame = new Less_Tree_Ruleset( null, array() );
+		$params = $this->params;
+		$mixinEnv = null;
+		$argsLength = 0;
+
+		if ( $args ) {
+			$argsLength = count( $args );
+			for ( $i = 0; $i < $argsLength; $i++ ) {
+				$arg = $args[$i];
+
+				if ( $arg && $arg['name'] ) {
+					$isNamedFound = false;
+
+					foreach ( $params as $j => $param ) {
+						if ( !isset( $evaldArguments[$j] ) && $arg['name'] === $params[$j]['name'] ) {
+							$evaldArguments[$j] = $arg['value']->compile( $env );
+							array_unshift( $frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile( $env ) ) );
+							$isNamedFound = true;
+							break;
+						}
+					}
+					if ( $isNamedFound ) {
+						array_splice( $args, $i, 1 );
+						$i--;
+						$argsLength--;
+						continue;
+					} else {
+						throw new Less_Exception_Compiler( "Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found' );
+					}
+				}
+			}
+		}
+
+		$argIndex = 0;
+		foreach ( $params as $i => $param ) {
+
+			if ( isset( $evaldArguments[$i] ) ) { continue;
+			}
+
+			$arg = null;
+			if ( isset( $args[$argIndex] ) ) {
+				$arg = $args[$argIndex];
+			}
+
+			if ( isset( $param['name'] ) && $param['name'] ) {
+
+				if ( isset( $param['variadic'] ) ) {
+					$varargs = array();
+					for ( $j = $argIndex; $j < $argsLength; $j++ ) {
+						$varargs[] = $args[$j]['value']->compile( $env );
+					}
+					$expression = new Less_Tree_Expression( $varargs );
+					array_unshift( $frame->rules, new Less_Tree_Rule( $param['name'], $expression->compile( $env ) ) );
+				} else {
+					$val = ( $arg && $arg['value'] ) ? $arg['value'] : false;
+
+					if ( $val ) {
+						$val = $val->compile( $env );
+					} else if ( isset( $param['value'] ) ) {
+
+						if ( !$mixinEnv ) {
+							$mixinEnv = new Less_Environment();
+							$mixinEnv->frames = array_merge( array( $frame ), $mixinFrames );
+						}
+
+						$val = $param['value']->compile( $mixinEnv );
+						$frame->resetCache();
+					} else {
+						throw new Less_Exception_Compiler( "Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")" );
+					}
+
+					array_unshift( $frame->rules, new Less_Tree_Rule( $param['name'], $val ) );
+					$evaldArguments[$i] = $val;
+				}
+			}
+
+			if ( isset( $param['variadic'] ) && $args ) {
+				for ( $j = $argIndex; $j < $argsLength; $j++ ) {
+					$evaldArguments[$j] = $args[$j]['value']->compile( $env );
+				}
+			}
+			$argIndex++;
+		}
+
+		ksort( $evaldArguments );
+		$evaldArguments = array_values( $evaldArguments );
+
+		return $frame;
+	}
+
+	public function compile( $env ) {
+		if ( $this->frames ) {
+			return new Less_Tree_Mixin_Definition( $this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames );
+		}
+		return new Less_Tree_Mixin_Definition( $this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames );
+	}
+
+	public function evalCall( $env, $args = NULL, $important = NULL ) {
+		Less_Environment::$mixin_stack++;
+
+		$_arguments = array();
+
+		if ( $this->frames ) {
+			$mixinFrames = array_merge( $this->frames, $env->frames );
+		} else {
+			$mixinFrames = $env->frames;
+		}
+
+		$frame = $this->compileParams( $env, $mixinFrames, $args, $_arguments );
+
+		$ex = new Less_Tree_Expression( $_arguments );
+		array_unshift( $frame->rules, new Less_Tree_Rule( '@arguments', $ex->compile( $env ) ) );
+
+		$ruleset = new Less_Tree_Ruleset( null, $this->rules );
+		$ruleset->originalRuleset = $this->ruleset_id;
+
+		$ruleSetEnv = new Less_Environment();
+		$ruleSetEnv->frames = array_merge( array( $this, $frame ), $mixinFrames );
+		$ruleset = $ruleset->compile( $ruleSetEnv );
+
+		if ( $important ) {
+			$ruleset = $ruleset->makeImportant();
+		}
+
+		Less_Environment::$mixin_stack--;
+
+		return $ruleset;
+	}
+
+	public function matchCondition( $args, $env ) {
+		if ( !$this->condition ) {
+			return true;
+		}
+
+		// set array to prevent error on array_merge
+		if ( !is_array( $this->frames ) ) {
+			 $this->frames = array();
+		}
+
+		$frame = $this->compileParams( $env, array_merge( $this->frames, $env->frames ), $args );
+
+		$compile_env = new Less_Environment();
+		$compile_env->frames = array_merge(
+				array( $frame ),		// the parameter variables
+				 $this->frames,		// the parent namespace/mixin frames
+				 $env->frames		// the current environment frames
+			);
+
+		$compile_env->functions = $env->functions;
+
+		return (bool)$this->condition->compile( $compile_env );
+	}
+
+	public function matchArgs( $args, $env = NULL ) {
+		$argsLength = count( $args );
+
+		if ( !$this->variadic ) {
+			if ( $argsLength < $this->required ) {
+				return false;
+			}
+			if ( $argsLength > count( $this->params ) ) {
+				return false;
+			}
+		} else {
+			if ( $argsLength < ( $this->required - 1 ) ) {
+				return false;
+			}
+		}
+
+		$len = min( $argsLength, $this->arity );
+
+		for ( $i = 0; $i < $len; $i++ ) {
+			if ( !isset( $this->params[$i]['name'] ) && !isset( $this->params[$i]['variadic'] ) ) {
+				if ( $args[$i]['value']->compile( $env )->toCSS() != $this->params[$i]['value']->compile( $env )->toCSS() ) {
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+
+}

+ 49 - 0
lessphp/lib/Less/Tree/NameValue.php

@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * A simple css name-value pair
+ * ex: width:100px;
+ *
+ * In bootstrap, there are about 600-1,000 simple name-value pairs (depending on how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule)
+ * Using the name-value object can speed up bootstrap compilation slightly, but it breaks color keyword interpretation: color:red -> color:#FF0000;
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_NameValue extends Less_Tree {
+
+	public $name;
+	public $value;
+	public $index;
+	public $currentFileInfo;
+	public $type = 'NameValue';
+	public $important = '';
+
+	public function __construct( $name, $value = null, $index = null, $currentFileInfo = null ) {
+		$this->name = $name;
+		$this->value = $value;
+		$this->index = $index;
+		$this->currentFileInfo = $currentFileInfo;
+	}
+
+	public function genCSS( $output ) {
+		$output->add(
+			$this->name
+			. Less_Environment::$_outputMap[': ']
+			. $this->value
+			. $this->important
+			. ( ( ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ),
+			 $this->currentFileInfo, $this->index );
+	}
+
+	public function compile( $env ) {
+		return $this;
+	}
+
+	public function makeImportant() {
+		$new = new Less_Tree_NameValue( $this->name, $this->value, $this->index, $this->currentFileInfo );
+		$new->important = ' !important';
+		return $new;
+	}
+
+}

+ 37 - 0
lessphp/lib/Less/Tree/Negative.php

@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Negative
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Negative extends Less_Tree {
+
+	public $value;
+	public $type = 'Negative';
+
+	public function __construct( $node ) {
+		$this->value = $node;
+	}
+
+	// function accept($visitor) {
+	//	$this->value = $visitor->visit($this->value);
+	//}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( '-' );
+		$this->value->genCSS( $output );
+	}
+
+	public function compile( $env ) {
+		if ( Less_Environment::isMathOn() ) {
+			$ret = new Less_Tree_Operation( '*', array( new Less_Tree_Dimension( -1 ), $this->value ) );
+			return $ret->compile( $env );
+		}
+		return new Less_Tree_Negative( $this->value->compile( $env ) );
+	}
+}

+ 68 - 0
lessphp/lib/Less/Tree/Operation.php

@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Operation
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Operation extends Less_Tree {
+
+	public $op;
+	public $operands;
+	public $isSpaced;
+	public $type = 'Operation';
+
+	/**
+	 * @param string $op
+	 */
+	public function __construct( $op, $operands, $isSpaced = false ) {
+		$this->op = trim( $op );
+		$this->operands = $operands;
+		$this->isSpaced = $isSpaced;
+	}
+
+	public function accept( $visitor ) {
+		$this->operands = $visitor->visitArray( $this->operands );
+	}
+
+	public function compile( $env ) {
+		$a = $this->operands[0]->compile( $env );
+		$b = $this->operands[1]->compile( $env );
+
+		if ( Less_Environment::isMathOn() ) {
+
+			if ( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ) {
+				$a = $a->toColor();
+
+			} elseif ( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ) {
+				$b = $b->toColor();
+
+			}
+
+			if ( !method_exists( $a, 'operate' ) ) {
+				throw new Less_Exception_Compiler( "Operation on an invalid type" );
+			}
+
+			return $a->operate( $this->op, $b );
+		}
+
+		return new Less_Tree_Operation( $this->op, array( $a, $b ), $this->isSpaced );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$this->operands[0]->genCSS( $output );
+		if ( $this->isSpaced ) {
+			$output->add( " " );
+		}
+		$output->add( $this->op );
+		if ( $this->isSpaced ) {
+			$output->add( ' ' );
+		}
+		$this->operands[1]->genCSS( $output );
+	}
+
+}

+ 35 - 0
lessphp/lib/Less/Tree/Paren.php

@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * Paren
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Paren extends Less_Tree {
+
+	public $value;
+	public $type = 'Paren';
+
+	public function __construct( $value ) {
+		$this->value = $value;
+	}
+
+	public function accept( $visitor ) {
+		$this->value = $visitor->visitObj( $this->value );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( '(' );
+		$this->value->genCSS( $output );
+		$output->add( ')' );
+	}
+
+	public function compile( $env ) {
+		return new Less_Tree_Paren( $this->value->compile( $env ) );
+	}
+
+}

+ 79 - 0
lessphp/lib/Less/Tree/Quoted.php

@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * Quoted
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Quoted extends Less_Tree {
+	public $escaped;
+	public $value;
+	public $quote;
+	public $index;
+	public $currentFileInfo;
+	public $type = 'Quoted';
+
+	/**
+	 * @param string $str
+	 */
+	public function __construct( $str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ) {
+		$this->escaped = $escaped;
+		$this->value = $content;
+		if ( $str ) {
+			$this->quote = $str[0];
+		}
+		$this->index = $index;
+		$this->currentFileInfo = $currentFileInfo;
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		if ( !$this->escaped ) {
+			$output->add( $this->quote, $this->currentFileInfo, $this->index );
+		}
+		$output->add( $this->value );
+		if ( !$this->escaped ) {
+			$output->add( $this->quote );
+		}
+	}
+
+	public function compile( $env ) {
+		$value = $this->value;
+		if ( preg_match_all( '/`([^`]+)`/', $this->value, $matches ) ) {
+			foreach ( $matches as $i => $match ) {
+				$js = new Less_Tree_JavaScript( $matches[1], $this->index, true );
+				$js = $js->compile()->value;
+				$value = str_replace( $matches[0][$i], $js, $value );
+			}
+		}
+
+		if ( preg_match_all( '/@\{([\w-]+)\}/', $value, $matches ) ) {
+			foreach ( $matches[1] as $i => $match ) {
+				$v = new Less_Tree_Variable( '@' . $match, $this->index, $this->currentFileInfo );
+				$v = $v->compile( $env );
+				$v = ( $v instanceof Less_Tree_Quoted ) ? $v->value : $v->toCSS();
+				$value = str_replace( $matches[0][$i], $v, $value );
+			}
+		}
+
+		return new Less_Tree_Quoted( $this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo );
+	}
+
+	public function compare( $x ) {
+		if ( !Less_Parser::is_method( $x, 'toCSS' ) ) {
+			return -1;
+		}
+
+		$left = $this->toCSS();
+		$right = $x->toCSS();
+
+		if ( $left === $right ) {
+			return 0;
+		}
+
+		return $left < $right ? -1 : 1;
+	}
+}

+ 112 - 0
lessphp/lib/Less/Tree/Rule.php

@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * Rule
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Rule extends Less_Tree {
+
+	public $name;
+	public $value;
+	public $important;
+	public $merge;
+	public $index;
+	public $inline;
+	public $variable;
+	public $currentFileInfo;
+	public $type = 'Rule';
+
+	/**
+	 * @param string $important
+	 */
+	public function __construct( $name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null, $inline = false ) {
+		$this->name = $name;
+		$this->value = ( $value instanceof Less_Tree_Value || $value instanceof Less_Tree_Ruleset ) ? $value : new Less_Tree_Value( array( $value ) );
+		$this->important = $important ? ' ' . trim( $important ) : '';
+		$this->merge = $merge;
+		$this->index = $index;
+		$this->currentFileInfo = $currentFileInfo;
+		$this->inline = $inline;
+		$this->variable = ( is_string( $name ) && $name[0] === '@' );
+	}
+
+	public function accept( $visitor ) {
+		$this->value = $visitor->visitObj( $this->value );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index );
+		try{
+			$this->value->genCSS( $output );
+
+		}catch ( Less_Exception_Parser $e ) {
+			$e->index = $this->index;
+			$e->currentFile = $this->currentFileInfo;
+			throw $e;
+		}
+		$output->add( $this->important . ( ( $this->inline || ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ), $this->currentFileInfo, $this->index );
+	}
+
+	public function compile( $env ) {
+		$name = $this->name;
+		if ( is_array( $name ) ) {
+			// expand 'primitive' name directly to get
+			// things faster (~10% for benchmark.less):
+			if ( count( $name ) === 1 && $name[0] instanceof Less_Tree_Keyword ) {
+				$name = $name[0]->value;
+			} else {
+				$name = $this->CompileName( $env, $name );
+			}
+		}
+
+		$strictMathBypass = Less_Parser::$options['strictMath'];
+		if ( $name === "font" && !Less_Parser::$options['strictMath'] ) {
+			Less_Parser::$options['strictMath'] = true;
+		}
+
+		try {
+			$evaldValue = $this->value->compile( $env );
+
+			if ( !$this->variable && $evaldValue->type === "DetachedRuleset" ) {
+				throw new Less_Exception_Compiler( "Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo );
+			}
+
+			if ( Less_Environment::$mixin_stack ) {
+				$return = new Less_Tree_Rule( $name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline );
+			} else {
+				$this->name = $name;
+				$this->value = $evaldValue;
+				$return = $this;
+			}
+
+		}catch ( Less_Exception_Parser $e ) {
+			if ( !is_numeric( $e->index ) ) {
+				$e->index = $this->index;
+				$e->currentFile = $this->currentFileInfo;
+			}
+			throw $e;
+		}
+
+		Less_Parser::$options['strictMath'] = $strictMathBypass;
+
+		return $return;
+	}
+
+	public function CompileName( $env, $name ) {
+		$output = new Less_Output();
+		foreach ( $name as $n ) {
+			$n->compile( $env )->genCSS( $output );
+		}
+		return $output->toString();
+	}
+
+	public function makeImportant() {
+		return new Less_Tree_Rule( $this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline );
+	}
+
+}

+ 621 - 0
lessphp/lib/Less/Tree/Ruleset.php

@@ -0,0 +1,621 @@
+<?php
+
+/**
+ * Ruleset
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Ruleset extends Less_Tree {
+
+	protected $lookups;
+	public $_variables;
+	public $_rulesets;
+
+	public $strictImports;
+
+	public $selectors;
+	public $rules;
+	public $root;
+	public $allowImports;
+	public $paths;
+	public $firstRoot;
+	public $type = 'Ruleset';
+	public $multiMedia;
+	public $allExtends;
+
+	public $ruleset_id;
+	public $originalRuleset;
+
+	public $first_oelements;
+
+	public function SetRulesetIndex() {
+		$this->ruleset_id = Less_Parser::$next_id++;
+		$this->originalRuleset = $this->ruleset_id;
+
+		if ( $this->selectors ) {
+			foreach ( $this->selectors as $sel ) {
+				if ( $sel->_oelements ) {
+					$this->first_oelements[$sel->_oelements[0]] = true;
+				}
+			}
+		}
+	}
+
+	public function __construct( $selectors, $rules, $strictImports = null ) {
+		$this->selectors = $selectors;
+		$this->rules = $rules;
+		$this->lookups = array();
+		$this->strictImports = $strictImports;
+		$this->SetRulesetIndex();
+	}
+
+	public function accept( $visitor ) {
+		if ( $this->paths ) {
+			$paths_len = count( $this->paths );
+			for ( $i = 0,$paths_len; $i < $paths_len; $i++ ) {
+				$this->paths[$i] = $visitor->visitArray( $this->paths[$i] );
+			}
+		} elseif ( $this->selectors ) {
+			$this->selectors = $visitor->visitArray( $this->selectors );
+		}
+
+		if ( $this->rules ) {
+			$this->rules = $visitor->visitArray( $this->rules );
+		}
+	}
+
+	public function compile( $env ) {
+		$ruleset = $this->PrepareRuleset( $env );
+
+		// Store the frames around mixin definitions,
+		// so they can be evaluated like closures when the time comes.
+		$rsRuleCnt = count( $ruleset->rules );
+		for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+			if ( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ) {
+				$ruleset->rules[$i] = $ruleset->rules[$i]->compile( $env );
+			}
+		}
+
+		$mediaBlockCount = 0;
+		if ( $env instanceof Less_Environment ) {
+			$mediaBlockCount = count( $env->mediaBlocks );
+		}
+
+		// Evaluate mixin calls.
+		$this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
+
+		// Evaluate everything else
+		for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+			if ( !( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ) ) {
+				$ruleset->rules[$i] = $ruleset->rules[$i]->compile( $env );
+			}
+		}
+
+		// Evaluate everything else
+		for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+			$rule = $ruleset->rules[$i];
+
+			// for rulesets, check if it is a css guard and can be removed
+			if ( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count( $rule->selectors ) === 1 ) {
+
+				// check if it can be folded in (e.g. & where)
+				if ( $rule->selectors[0]->isJustParentSelector() ) {
+					array_splice( $ruleset->rules, $i--, 1 );
+					$rsRuleCnt--;
+
+					for ( $j = 0; $j < count( $rule->rules ); $j++ ) {
+						$subRule = $rule->rules[$j];
+						if ( !( $subRule instanceof Less_Tree_Rule ) || !$subRule->variable ) {
+							array_splice( $ruleset->rules, ++$i, 0, array( $subRule ) );
+							$rsRuleCnt++;
+						}
+					}
+
+				}
+			}
+		}
+
+		// Pop the stack
+		$env->shiftFrame();
+
+		if ( $mediaBlockCount ) {
+			$len = count( $env->mediaBlocks );
+			for ( $i = $mediaBlockCount; $i < $len; $i++ ) {
+				$env->mediaBlocks[$i]->bubbleSelectors( $ruleset->selectors );
+			}
+		}
+
+		return $ruleset;
+	}
+
+	/**
+	 * Compile Less_Tree_Mixin_Call objects
+	 *
+	 * @param Less_Tree_Ruleset $ruleset
+	 * @param integer $rsRuleCnt
+	 */
+	private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ) {
+		for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+			$rule = $ruleset->rules[$i];
+
+			if ( $rule instanceof Less_Tree_Mixin_Call ) {
+				$rule = $rule->compile( $env );
+
+				$temp = array();
+				foreach ( $rule as $r ) {
+					if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
+						// do not pollute the scope if the variable is
+						// already there. consider returning false here
+						// but we need a way to "return" variable from mixins
+						if ( !$ruleset->variable( $r->name ) ) {
+							$temp[] = $r;
+						}
+					} else {
+						$temp[] = $r;
+					}
+				}
+				$temp_count = count( $temp ) - 1;
+				array_splice( $ruleset->rules, $i, 1, $temp );
+				$rsRuleCnt += $temp_count;
+				$i += $temp_count;
+				$ruleset->resetCache();
+
+			} elseif ( $rule instanceof Less_Tree_RulesetCall ) {
+
+				$rule = $rule->compile( $env );
+				$rules = array();
+				foreach ( $rule->rules as $r ) {
+					if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
+						continue;
+					}
+					$rules[] = $r;
+				}
+
+				array_splice( $ruleset->rules, $i, 1, $rules );
+				$temp_count = count( $rules );
+				$rsRuleCnt += $temp_count - 1;
+				$i += $temp_count - 1;
+				$ruleset->resetCache();
+			}
+
+		}
+	}
+
+	/**
+	 * Compile the selectors and create a new ruleset object for the compile() method
+	 *
+	 */
+	private function PrepareRuleset( $env ) {
+		$hasOnePassingSelector = false;
+		$selectors = array();
+		if ( $this->selectors ) {
+			Less_Tree_DefaultFunc::error( "it is currently only allowed in parametric mixin guards," );
+
+			foreach ( $this->selectors as $s ) {
+				$selector = $s->compile( $env );
+				$selectors[] = $selector;
+				if ( $selector->evaldCondition ) {
+					$hasOnePassingSelector = true;
+				}
+			}
+
+			Less_Tree_DefaultFunc::reset();
+		} else {
+			$hasOnePassingSelector = true;
+		}
+
+		if ( $this->rules && $hasOnePassingSelector ) {
+			$rules = $this->rules;
+		} else {
+			$rules = array();
+		}
+
+		$ruleset = new Less_Tree_Ruleset( $selectors, $rules, $this->strictImports );
+
+		$ruleset->originalRuleset = $this->ruleset_id;
+
+		$ruleset->root = $this->root;
+		$ruleset->firstRoot = $this->firstRoot;
+		$ruleset->allowImports = $this->allowImports;
+
+		// push the current ruleset to the frames stack
+		$env->unshiftFrame( $ruleset );
+
+		// Evaluate imports
+		if ( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ) {
+			$ruleset->evalImports( $env );
+		}
+
+		return $ruleset;
+	}
+
+	function evalImports( $env ) {
+		$rules_len = count( $this->rules );
+		for ( $i = 0; $i < $rules_len; $i++ ) {
+			$rule = $this->rules[$i];
+
+			if ( $rule instanceof Less_Tree_Import ) {
+				$rules = $rule->compile( $env );
+				if ( is_array( $rules ) ) {
+					array_splice( $this->rules, $i, 1, $rules );
+					$temp_count = count( $rules ) - 1;
+					$i += $temp_count;
+					$rules_len += $temp_count;
+				} else {
+					array_splice( $this->rules, $i, 1, array( $rules ) );
+				}
+
+				$this->resetCache();
+			}
+		}
+	}
+
+	function makeImportant() {
+		$important_rules = array();
+		foreach ( $this->rules as $rule ) {
+			if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset || $rule instanceof Less_Tree_NameValue ) {
+				$important_rules[] = $rule->makeImportant();
+			} else {
+				$important_rules[] = $rule;
+			}
+		}
+
+		return new Less_Tree_Ruleset( $this->selectors, $important_rules, $this->strictImports );
+	}
+
+	public function matchArgs( $args ) {
+		return !$args;
+	}
+
+	// lets you call a css selector with a guard
+	public function matchCondition( $args, $env ) {
+		$lastSelector = end( $this->selectors );
+
+		if ( !$lastSelector->evaldCondition ) {
+			return false;
+		}
+		if ( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ) {
+			return false;
+		}
+		return true;
+	}
+
+	function resetCache() {
+		$this->_rulesets = null;
+		$this->_variables = null;
+		$this->lookups = array();
+	}
+
+	public function variables() {
+		$this->_variables = array();
+		foreach ( $this->rules as $r ) {
+			if ( $r instanceof Less_Tree_Rule && $r->variable === true ) {
+				$this->_variables[$r->name] = $r;
+			}
+		}
+	}
+
+	public function variable( $name ) {
+		if ( is_null( $this->_variables ) ) {
+			$this->variables();
+		}
+		return isset( $this->_variables[$name] ) ? $this->_variables[$name] : null;
+	}
+
+	public function find( $selector, $self = null ) {
+		$key = implode( ' ', $selector->_oelements );
+
+		if ( !isset( $this->lookups[$key] ) ) {
+
+			if ( !$self ) {
+				$self = $this->ruleset_id;
+			}
+
+			$this->lookups[$key] = array();
+
+			$first_oelement = $selector->_oelements[0];
+
+			foreach ( $this->rules as $rule ) {
+				if ( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ) {
+
+					if ( isset( $rule->first_oelements[$first_oelement] ) ) {
+
+						foreach ( $rule->selectors as $ruleSelector ) {
+							$match = $selector->match( $ruleSelector );
+							if ( $match ) {
+								if ( $selector->elements_len > $match ) {
+									$this->lookups[$key] = array_merge( $this->lookups[$key], $rule->find( new Less_Tree_Selector( array_slice( $selector->elements, $match ) ), $self ) );
+								} else {
+									$this->lookups[$key][] = $rule;
+								}
+								break;
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return $this->lookups[$key];
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		if ( !$this->root ) {
+			Less_Environment::$tabLevel++;
+		}
+
+		$tabRuleStr = $tabSetStr = '';
+		if ( !Less_Parser::$options['compress'] ) {
+			if ( Less_Environment::$tabLevel ) {
+				$tabRuleStr = "\n".str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel );
+				$tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel - 1 );
+			} else {
+				$tabSetStr = $tabRuleStr = "\n";
+			}
+		}
+
+		$ruleNodes = array();
+		$rulesetNodes = array();
+		foreach ( $this->rules as $rule ) {
+
+			$class = get_class( $rule );
+			if ( ( $class === 'Less_Tree_Media' ) || ( $class === 'Less_Tree_Directive' ) || ( $this->root && $class === 'Less_Tree_Comment' ) || ( $class === 'Less_Tree_Ruleset' && $rule->rules ) ) {
+				$rulesetNodes[] = $rule;
+			} else {
+				$ruleNodes[] = $rule;
+			}
+		}
+
+		// If this is the root node, we don't render
+		// a selector, or {}.
+		if ( !$this->root ) {
+
+			/*
+			debugInfo = tree.debugInfo(env, this, tabSetStr);
+
+			if (debugInfo) {
+				output.add(debugInfo);
+				output.add(tabSetStr);
+			}
+			*/
+
+			$paths_len = count( $this->paths );
+			for ( $i = 0; $i < $paths_len; $i++ ) {
+				$path = $this->paths[$i];
+				$firstSelector = true;
+
+				foreach ( $path as $p ) {
+					$p->genCSS( $output, $firstSelector );
+					$firstSelector = false;
+				}
+
+				if ( $i + 1 < $paths_len ) {
+					$output->add( ',' . $tabSetStr );
+				}
+			}
+
+			$output->add( ( Less_Parser::$options['compress'] ? '{' : " {" ) . $tabRuleStr );
+		}
+
+		// Compile rules and rulesets
+		$ruleNodes_len = count( $ruleNodes );
+		$rulesetNodes_len = count( $rulesetNodes );
+		for ( $i = 0; $i < $ruleNodes_len; $i++ ) {
+			$rule = $ruleNodes[$i];
+
+			// @page{ directive ends up with root elements inside it, a mix of rules and rulesets
+			// In this instance we do not know whether it is the last property
+			if ( $i + 1 === $ruleNodes_len && ( !$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ) {
+				Less_Environment::$lastRule = true;
+			}
+
+			$rule->genCSS( $output );
+
+			if ( !Less_Environment::$lastRule ) {
+				$output->add( $tabRuleStr );
+			} else {
+				Less_Environment::$lastRule = false;
+			}
+		}
+
+		if ( !$this->root ) {
+			$output->add( $tabSetStr . '}' );
+			Less_Environment::$tabLevel--;
+		}
+
+		$firstRuleset = true;
+		$space = ( $this->root ? $tabRuleStr : $tabSetStr );
+		for ( $i = 0; $i < $rulesetNodes_len; $i++ ) {
+
+			if ( $ruleNodes_len && $firstRuleset ) {
+				$output->add( $space );
+			} elseif ( !$firstRuleset ) {
+				$output->add( $space );
+			}
+			$firstRuleset = false;
+			$rulesetNodes[$i]->genCSS( $output );
+		}
+
+		if ( !Less_Parser::$options['compress'] && $this->firstRoot ) {
+			$output->add( "\n" );
+		}
+
+	}
+
+	function markReferenced() {
+		if ( !$this->selectors ) {
+			return;
+		}
+		foreach ( $this->selectors as $selector ) {
+			$selector->markReferenced();
+		}
+	}
+
+	public function joinSelectors( $context, $selectors ) {
+		$paths = array();
+		if ( is_array( $selectors ) ) {
+			foreach ( $selectors as $selector ) {
+				$this->joinSelector( $paths, $context, $selector );
+			}
+		}
+		return $paths;
+	}
+
+	public function joinSelector( &$paths, $context, $selector ) {
+		$hasParentSelector = false;
+
+		foreach ( $selector->elements as $el ) {
+			if ( $el->value === '&' ) {
+				$hasParentSelector = true;
+			}
+		}
+
+		if ( !$hasParentSelector ) {
+			if ( $context ) {
+				foreach ( $context as $context_el ) {
+					$paths[] = array_merge( $context_el, array( $selector ) );
+				}
+			} else {
+				$paths[] = array( $selector );
+			}
+			return;
+		}
+
+		// The paths are [[Selector]]
+		// The first list is a list of comma separated selectors
+		// The inner list is a list of inheritance separated selectors
+		// e.g.
+		// .a, .b {
+		//   .c {
+		//   }
+		// }
+		// == [[.a] [.c]] [[.b] [.c]]
+		//
+
+		// the elements from the current selector so far
+		$currentElements = array();
+		// the current list of new selectors to add to the path.
+		// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
+		// by the parents
+		$newSelectors = array( array() );
+
+		foreach ( $selector->elements as $el ) {
+
+			// non parent reference elements just get added
+			if ( $el->value !== '&' ) {
+				$currentElements[] = $el;
+			} else {
+				// the new list of selectors to add
+				$selectorsMultiplied = array();
+
+				// merge the current list of non parent selector elements
+				// on to the current list of selectors to add
+				if ( $currentElements ) {
+					$this->mergeElementsOnToSelectors( $currentElements, $newSelectors );
+				}
+
+				// loop through our current selectors
+				foreach ( $newSelectors as $sel ) {
+
+					// if we don't have any parent paths, the & might be in a mixin so that it can be used
+					// whether there are parents or not
+					if ( !$context ) {
+						// the combinator used on el should now be applied to the next element instead so that
+						// it is not lost
+						if ( $sel ) {
+							$sel[0]->elements = array_slice( $sel[0]->elements, 0 );
+							$sel[0]->elements[] = new Less_Tree_Element( $el->combinator, '', $el->index, $el->currentFileInfo );
+						}
+						$selectorsMultiplied[] = $sel;
+					} else {
+
+						// and the parent selectors
+						foreach ( $context as $parentSel ) {
+							// We need to put the current selectors
+							// then join the last selector's elements on to the parents selectors
+
+							// our new selector path
+							$newSelectorPath = array();
+							// selectors from the parent after the join
+							$afterParentJoin = array();
+							$newJoinedSelectorEmpty = true;
+
+							// construct the joined selector - if & is the first thing this will be empty,
+							// if not newJoinedSelector will be the last set of elements in the selector
+							if ( $sel ) {
+								$newSelectorPath = $sel;
+								$lastSelector = array_pop( $newSelectorPath );
+								$newJoinedSelector = $selector->createDerived( array_slice( $lastSelector->elements, 0 ) );
+								$newJoinedSelectorEmpty = false;
+							} else {
+								$newJoinedSelector = $selector->createDerived( array() );
+							}
+
+							// put together the parent selectors after the join
+							if ( count( $parentSel ) > 1 ) {
+								$afterParentJoin = array_merge( $afterParentJoin, array_slice( $parentSel, 1 ) );
+							}
+
+							if ( $parentSel ) {
+								$newJoinedSelectorEmpty = false;
+
+								// join the elements so far with the first part of the parent
+								$newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo );
+
+								$newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice( $parentSel[0]->elements, 1 ) );
+							}
+
+							if ( !$newJoinedSelectorEmpty ) {
+								// now add the joined selector
+								$newSelectorPath[] = $newJoinedSelector;
+							}
+
+							// and the rest of the parent
+							$newSelectorPath = array_merge( $newSelectorPath, $afterParentJoin );
+
+							// add that to our new set of selectors
+							$selectorsMultiplied[] = $newSelectorPath;
+						}
+					}
+				}
+
+				// our new selectors has been multiplied, so reset the state
+				$newSelectors = $selectorsMultiplied;
+				$currentElements = array();
+			}
+		}
+
+		// if we have any elements left over (e.g. .a& .b == .b)
+		// add them on to all the current selectors
+		if ( $currentElements ) {
+			$this->mergeElementsOnToSelectors( $currentElements, $newSelectors );
+		}
+		foreach ( $newSelectors as $new_sel ) {
+			if ( $new_sel ) {
+				$paths[] = $new_sel;
+			}
+		}
+	}
+
+	function mergeElementsOnToSelectors( $elements, &$selectors ) {
+		if ( !$selectors ) {
+			$selectors[] = array( new Less_Tree_Selector( $elements ) );
+			return;
+		}
+
+		foreach ( $selectors as &$sel ) {
+
+			// if the previous thing in sel is a parent this needs to join on to it
+			if ( $sel ) {
+				$last = count( $sel ) - 1;
+				$sel[$last] = $sel[$last]->createDerived( array_merge( $sel[$last]->elements, $elements ) );
+			} else {
+				$sel[] = new Less_Tree_Selector( $elements );
+			}
+		}
+	}
+}

+ 26 - 0
lessphp/lib/Less/Tree/RulesetCall.php

@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * RulesetCall
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_RulesetCall extends Less_Tree {
+
+	public $variable;
+	public $type = "RulesetCall";
+
+	public function __construct( $variable ) {
+		$this->variable = $variable;
+	}
+
+	public function accept( $visitor ) {
+	}
+
+	public function compile( $env ) {
+		$variable = new Less_Tree_Variable( $this->variable );
+		$detachedRuleset = $variable->compile( $env );
+		return $detachedRuleset->callEval( $env );
+	}
+}

+ 165 - 0
lessphp/lib/Less/Tree/Selector.php

@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * Selector
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Selector extends Less_Tree {
+
+	public $elements;
+	public $condition;
+	public $extendList = array();
+	public $_css;
+	public $index;
+	public $evaldCondition = false;
+	public $type = 'Selector';
+	public $currentFileInfo = array();
+	public $isReferenced;
+	public $mediaEmpty;
+
+	public $elements_len = 0;
+
+	public $_oelements;
+	public $_oelements_assoc;
+	public $_oelements_len;
+	public $cacheable = true;
+
+	/**
+	 * @param boolean $isReferenced
+	 */
+	public function __construct( $elements, $extendList = array(), $condition = null, $index = null, $currentFileInfo = null, $isReferenced = null ) {
+		$this->elements = $elements;
+		$this->elements_len = count( $elements );
+		$this->extendList = $extendList;
+		$this->condition = $condition;
+		if ( $currentFileInfo ) {
+			$this->currentFileInfo = $currentFileInfo;
+		}
+		$this->isReferenced = $isReferenced;
+		if ( !$condition ) {
+			$this->evaldCondition = true;
+		}
+
+		$this->CacheElements();
+	}
+
+	public function accept( $visitor ) {
+		$this->elements = $visitor->visitArray( $this->elements );
+		$this->extendList = $visitor->visitArray( $this->extendList );
+		if ( $this->condition ) {
+			$this->condition = $visitor->visitObj( $this->condition );
+		}
+
+		if ( $visitor instanceof Less_Visitor_extendFinder ) {
+			$this->CacheElements();
+		}
+	}
+
+	public function createDerived( $elements, $extendList = null, $evaldCondition = null ) {
+		$newSelector = new Less_Tree_Selector( $elements, ( $extendList ? $extendList : $this->extendList ), null, $this->index, $this->currentFileInfo, $this->isReferenced );
+		$newSelector->evaldCondition = $evaldCondition ? $evaldCondition : $this->evaldCondition;
+		return $newSelector;
+	}
+
+	public function match( $other ) {
+		if ( !$other->_oelements || ( $this->elements_len < $other->_oelements_len ) ) {
+			return 0;
+		}
+
+		for ( $i = 0; $i < $other->_oelements_len; $i++ ) {
+			if ( $this->elements[$i]->value !== $other->_oelements[$i] ) {
+				return 0;
+			}
+		}
+
+		return $other->_oelements_len; // return number of matched elements
+	}
+
+	public function CacheElements() {
+		$this->_oelements = array();
+		$this->_oelements_assoc = array();
+
+		$css = '';
+
+		foreach ( $this->elements as $v ) {
+
+			$css .= $v->combinator;
+			if ( !$v->value_is_object ) {
+				$css .= $v->value;
+				continue;
+			}
+
+			if ( !property_exists( $v->value, 'value' ) || !is_string( $v->value->value ) ) {
+				$this->cacheable = false;
+				return;
+			}
+			$css .= $v->value->value;
+		}
+
+		$this->_oelements_len = preg_match_all( '/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches );
+		if ( $this->_oelements_len ) {
+			$this->_oelements = $matches[0];
+
+			if ( $this->_oelements[0] === '&' ) {
+				array_shift( $this->_oelements );
+				$this->_oelements_len--;
+			}
+
+			$this->_oelements_assoc = array_fill_keys( $this->_oelements, true );
+		}
+	}
+
+	public function isJustParentSelector() {
+		return !$this->mediaEmpty &&
+			count( $this->elements ) === 1 &&
+			$this->elements[0]->value === '&' &&
+			( $this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === '' );
+	}
+
+	public function compile( $env ) {
+		$elements = array();
+		foreach ( $this->elements as $el ) {
+			$elements[] = $el->compile( $env );
+		}
+
+		$extendList = array();
+		foreach ( $this->extendList as $el ) {
+			$extendList[] = $el->compile( $el );
+		}
+
+		$evaldCondition = false;
+		if ( $this->condition ) {
+			$evaldCondition = $this->condition->compile( $env );
+		}
+
+		return $this->createDerived( $elements, $extendList, $evaldCondition );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output, $firstSelector = true ) {
+		if ( !$firstSelector && $this->elements[0]->combinator === "" ) {
+			$output->add( ' ', $this->currentFileInfo, $this->index );
+		}
+
+		foreach ( $this->elements as $element ) {
+			$element->genCSS( $output );
+		}
+	}
+
+	public function markReferenced() {
+		$this->isReferenced = true;
+	}
+
+	public function getIsReferenced() {
+		return !isset( $this->currentFileInfo['reference'] ) || !$this->currentFileInfo['reference'] || $this->isReferenced;
+	}
+
+	public function getIsOutput() {
+		return $this->evaldCondition;
+	}
+
+}

+ 28 - 0
lessphp/lib/Less/Tree/UnicodeDescriptor.php

@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * UnicodeDescriptor
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_UnicodeDescriptor extends Less_Tree {
+
+	public $value;
+	public $type = 'UnicodeDescriptor';
+
+	public function __construct( $value ) {
+		$this->value = $value;
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( $this->value );
+	}
+
+	public function compile() {
+		return $this;
+	}
+}

+ 142 - 0
lessphp/lib/Less/Tree/Unit.php

@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * Unit
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Unit extends Less_Tree {
+
+	var $numerator = array();
+	var $denominator = array();
+	public $backupUnit;
+	public $type = 'Unit';
+
+	public function __construct( $numerator = array(), $denominator = array(), $backupUnit = null ) {
+		$this->numerator = $numerator;
+		$this->denominator = $denominator;
+		$this->backupUnit = $backupUnit;
+	}
+
+	public function __clone() {
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		if ( $this->numerator ) {
+			$output->add( $this->numerator[0] );
+		} elseif ( $this->denominator ) {
+			$output->add( $this->denominator[0] );
+		} elseif ( !Less_Parser::$options['strictUnits'] && $this->backupUnit ) {
+			$output->add( $this->backupUnit );
+			return;
+		}
+	}
+
+	public function toString() {
+		$returnStr = implode( '*', $this->numerator );
+		foreach ( $this->denominator as $d ) {
+			$returnStr .= '/'.$d;
+		}
+		return $returnStr;
+	}
+
+	public function __toString() {
+		return $this->toString();
+	}
+
+	/**
+	 * @param Less_Tree_Unit $other
+	 */
+	public function compare( $other ) {
+		return $this->is( $other->toString() ) ? 0 : -1;
+	}
+
+	public function is( $unitString ) {
+		return $this->toString() === $unitString;
+	}
+
+	public function isLength() {
+		$css = $this->toCSS();
+		return !!preg_match( '/px|em|%|in|cm|mm|pc|pt|ex/', $css );
+	}
+
+	public function isAngle() {
+		return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] );
+	}
+
+	public function isEmpty() {
+		return !$this->numerator && !$this->denominator;
+	}
+
+	public function isSingular() {
+		return count( $this->numerator ) <= 1 && !$this->denominator;
+	}
+
+	public function usedUnits() {
+		$result = array();
+
+		foreach ( Less_Tree_UnitConversions::$groups as $groupName ) {
+			$group = Less_Tree_UnitConversions::${$groupName};
+
+			foreach ( $this->numerator as $atomicUnit ) {
+				if ( isset( $group[$atomicUnit] ) && !isset( $result[$groupName] ) ) {
+					$result[$groupName] = $atomicUnit;
+				}
+			}
+
+			foreach ( $this->denominator as $atomicUnit ) {
+				if ( isset( $group[$atomicUnit] ) && !isset( $result[$groupName] ) ) {
+					$result[$groupName] = $atomicUnit;
+				}
+			}
+		}
+
+		return $result;
+	}
+
+	public function cancel() {
+		$counter = array();
+		$backup = null;
+
+		foreach ( $this->numerator as $atomicUnit ) {
+			if ( !$backup ) {
+				$backup = $atomicUnit;
+			}
+			$counter[$atomicUnit] = ( isset( $counter[$atomicUnit] ) ? $counter[$atomicUnit] : 0 ) + 1;
+		}
+
+		foreach ( $this->denominator as $atomicUnit ) {
+			if ( !$backup ) {
+				$backup = $atomicUnit;
+			}
+			$counter[$atomicUnit] = ( isset( $counter[$atomicUnit] ) ? $counter[$atomicUnit] : 0 ) - 1;
+		}
+
+		$this->numerator = array();
+		$this->denominator = array();
+
+		foreach ( $counter as $atomicUnit => $count ) {
+			if ( $count > 0 ) {
+				for ( $i = 0; $i < $count; $i++ ) {
+					$this->numerator[] = $atomicUnit;
+				}
+			} elseif ( $count < 0 ) {
+				for ( $i = 0; $i < -$count; $i++ ) {
+					$this->denominator[] = $atomicUnit;
+				}
+			}
+		}
+
+		if ( !$this->numerator && !$this->denominator && $backup ) {
+			$this->backupUnit = $backup;
+		}
+
+		sort( $this->numerator );
+		sort( $this->denominator );
+	}
+
+}

+ 35 - 0
lessphp/lib/Less/Tree/UnitConversions.php

@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * UnitConversions
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_UnitConversions {
+
+	public static $groups = array( 'length','duration','angle' );
+
+	public static $length = array(
+		'm' => 1,
+		'cm' => 0.01,
+		'mm' => 0.001,
+		'in' => 0.0254,
+		'px' => 0.000264583, // 0.0254 / 96,
+		'pt' => 0.000352778, // 0.0254 / 72,
+		'pc' => 0.004233333, // 0.0254 / 72 * 12
+		);
+
+	public static $duration = array(
+		's' => 1,
+		'ms' => 0.001
+		);
+
+	public static $angle = array(
+		'rad' => 0.1591549430919,	// 1/(2*M_PI),
+		'deg' => 0.002777778, 		// 1/360,
+		'grad' => 0.0025,			// 1/400,
+		'turn' => 1
+		);
+
+}

+ 76 - 0
lessphp/lib/Less/Tree/Url.php

@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * Url
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Url extends Less_Tree {
+
+	public $attrs;
+	public $value;
+	public $currentFileInfo;
+	public $isEvald;
+	public $type = 'Url';
+
+	public function __construct( $value, $currentFileInfo = null, $isEvald = null ) {
+		$this->value = $value;
+		$this->currentFileInfo = $currentFileInfo;
+		$this->isEvald = $isEvald;
+	}
+
+	public function accept( $visitor ) {
+		$this->value = $visitor->visitObj( $this->value );
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	public function genCSS( $output ) {
+		$output->add( 'url(' );
+		$this->value->genCSS( $output );
+		$output->add( ')' );
+	}
+
+	/**
+	 * @param Less_Functions $ctx
+	 */
+	public function compile( $ctx ) {
+		$val = $this->value->compile( $ctx );
+
+		if ( !$this->isEvald ) {
+			// Add the base path if the URL is relative
+			if ( Less_Parser::$options['relativeUrls']
+				&& $this->currentFileInfo
+				&& is_string( $val->value )
+				&& Less_Environment::isPathRelative( $val->value )
+			) {
+				$rootpath = $this->currentFileInfo['uri_root'];
+				if ( !$val->quote ) {
+					$rootpath = preg_replace( '/[\(\)\'"\s]/', '\\$1', $rootpath );
+				}
+				$val->value = $rootpath . $val->value;
+			}
+
+			$val->value = Less_Environment::normalizePath( $val->value );
+		}
+
+		// Add cache buster if enabled
+		if ( Less_Parser::$options['urlArgs'] ) {
+			if ( !preg_match( '/^\s*data:/', $val->value ) ) {
+				$delimiter = strpos( $val->value, '?' ) === false ? '?' : '&';
+				$urlArgs = $delimiter . Less_Parser::$options['urlArgs'];
+				$hash_pos = strpos( $val->value, '#' );
+				if ( $hash_pos !== false ) {
+					$val->value = substr_replace( $val->value, $urlArgs, $hash_pos, 0 );
+				} else {
+					$val->value .= $urlArgs;
+				}
+			}
+		}
+
+		return new Less_Tree_URL( $val, $this->currentFileInfo, true );
+	}
+
+}

+ 47 - 0
lessphp/lib/Less/Tree/Value.php

@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Value
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Value extends Less_Tree {
+
+	public $type = 'Value';
+	public $value;
+
+	public function __construct( $value ) {
+		$this->value = $value;
+	}
+
+	public function accept( $visitor ) {
+		$this->value = $visitor->visitArray( $this->value );
+	}
+
+	public function compile( $env ) {
+		$ret = array();
+		$i = 0;
+		foreach ( $this->value as $i => $v ) {
+			$ret[] = $v->compile( $env );
+		}
+		if ( $i > 0 ) {
+			return new Less_Tree_Value( $ret );
+		}
+		return $ret[0];
+	}
+
+	/**
+	 * @see Less_Tree::genCSS
+	 */
+	function genCSS( $output ) {
+		$len = count( $this->value );
+		for ( $i = 0; $i < $len; $i++ ) {
+			$this->value[$i]->genCSS( $output );
+			if ( $i + 1 < $len ) {
+				$output->add( Less_Environment::$_outputMap[','] );
+			}
+		}
+	}
+
+}

+ 51 - 0
lessphp/lib/Less/Tree/Variable.php

@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Variable
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Variable extends Less_Tree {
+
+	public $name;
+	public $index;
+	public $currentFileInfo;
+	public $evaluating = false;
+	public $type = 'Variable';
+
+	/**
+	 * @param string $name
+	 */
+	public function __construct( $name, $index = null, $currentFileInfo = null ) {
+		$this->name = $name;
+		$this->index = $index;
+		$this->currentFileInfo = $currentFileInfo;
+	}
+
+	public function compile( $env ) {
+		if ( $this->name[1] === '@' ) {
+			$v = new Less_Tree_Variable( substr( $this->name, 1 ), $this->index + 1, $this->currentFileInfo );
+			$name = '@' . $v->compile( $env )->value;
+		} else {
+			$name = $this->name;
+		}
+
+		if ( $this->evaluating ) {
+			throw new Less_Exception_Compiler( "Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo );
+		}
+
+		$this->evaluating = true;
+
+		foreach ( $env->frames as $frame ) {
+			if ( $v = $frame->variable( $name ) ) {
+				$r = $v->value->compile( $env );
+				$this->evaluating = false;
+				return $r;
+			}
+		}
+
+		throw new Less_Exception_Compiler( "variable " . $name . " is undefined in file ".$this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo );
+	}
+
+}

+ 15 - 0
lessphp/lib/Less/Version.php

@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Release numbers
+ *
+ * @package Less
+ * @subpackage version
+ */
+class Less_Version {
+
+	public const version = '3.1.0';			// The current build number of less.php
+	public const less_version = '2.5.3';		// The less.js version that this build should be compatible with
+	public const cache_version = '253';		// The parser cache version
+
+}

+ 46 - 0
lessphp/lib/Less/Visitor.php

@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_Visitor {
+
+	protected $methods = array();
+	protected $_visitFnCache = array();
+
+	public function __construct() {
+		$this->_visitFnCache = get_class_methods( get_class( $this ) );
+		$this->_visitFnCache = array_flip( $this->_visitFnCache );
+	}
+
+	public function visitObj( $node ) {
+		$funcName = 'visit'.$node->type;
+		if ( isset( $this->_visitFnCache[$funcName] ) ) {
+
+			$visitDeeper = true;
+			$this->$funcName( $node, $visitDeeper );
+
+			if ( $visitDeeper ) {
+				$node->accept( $this );
+			}
+
+			$funcName = $funcName . "Out";
+			if ( isset( $this->_visitFnCache[$funcName] ) ) {
+				$this->$funcName( $node );
+			}
+
+		} else {
+			$node->accept( $this );
+		}
+
+		return $node;
+	}
+
+	public function visitArray( $nodes ) {
+		array_map( array( $this,'visitObj' ), $nodes );
+		return $nodes;
+	}
+}

+ 109 - 0
lessphp/lib/Less/Visitor/extendFinder.php

@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * Extend Finder Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_Visitor_extendFinder extends Less_Visitor {
+
+	public $contexts = array();
+	public $allExtendsStack;
+	public $foundExtends;
+
+	public function __construct() {
+		$this->contexts = array();
+		$this->allExtendsStack = array( array() );
+		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 = array();
+
+		// 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 = array();
+		$this->allExtendsStack[] =& $mediaNode->allExtends;
+	}
+
+	public function visitMediaOut() {
+		array_pop( $this->allExtendsStack );
+	}
+
+	public function visitDirective( $directiveNode ) {
+		$directiveNode->allExtends = array();
+		$this->allExtendsStack[] =& $directiveNode->allExtends;
+	}
+
+	public function visitDirectiveOut() {
+		array_pop( $this->allExtendsStack );
+	}
+}

+ 137 - 0
lessphp/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);
+	}
+
+}
+*/

+ 68 - 0
lessphp/lib/Less/Visitor/joinSelector.php

@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Join Selector Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_Visitor_joinSelector extends Less_Visitor {
+
+	public $contexts = array( array() );
+
+	/**
+	 * @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 ) {
+		$paths = array();
+
+		if ( !$rulesetNode->root ) {
+			$selectors = array();
+
+			if ( $rulesetNode->selectors && $rulesetNode->selectors ) {
+				foreach ( $rulesetNode->selectors as $selector ) {
+					if ( $selector->getIsOutput() ) {
+						$selectors[] = $selector;
+					}
+				}
+			}
+
+			if ( !$selectors ) {
+				$rulesetNode->selectors = null;
+				$rulesetNode->rules = null;
+			} else {
+				$context = end( $this->contexts ); // $context = $this->contexts[ count($this->contexts) - 1];
+				$paths = $rulesetNode->joinSelectors( $context, $selectors );
+			}
+
+			$rulesetNode->paths = $paths;
+		}
+
+		$this->contexts[] = $paths; // different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths
+	}
+
+	public function visitRulesetOut() {
+		array_pop( $this->contexts );
+	}
+
+	public function visitMedia( $mediaNode ) {
+		$context = end( $this->contexts ); // $context = $this->contexts[ count($this->contexts) - 1];
+
+		if ( !count( $context ) || ( is_object( $context[0] ) && $context[0]->multiMedia ) ) {
+			$mediaNode->rules[0]->root = true;
+		}
+	}
+
+}

+ 441 - 0
lessphp/lib/Less/Visitor/processExtends.php

@@ -0,0 +1,441 @@
+<?php
+
+/**
+ * Process Extends Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+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 = array();
+		$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 = array();
+
+		// 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 = array( $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 = array( $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 );
+			}
+		}
+	}
+
+	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 = array();
+		$potentialMatches_len = 0;
+		$potentialMatch = null;
+		$matches = array();
+
+		// 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[] = array( '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 = array(); // 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 integer $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;
+	}
+
+	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->value : $elementValue1->value );
+		$elementValue2 = ( $elementValue2->value->value ? $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 = array();
+		$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
+				 array( $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 );
+	}
+
+}

+ 280 - 0
lessphp/lib/Less/Visitor/toCSS.php

@@ -0,0 +1,280 @@
+<?php
+
+/**
+ * toCSS Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+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 array();
+		}
+		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 = array();
+		return array();
+	}
+
+	public function visitExtend() {
+		return array();
+	}
+
+	public function visitComment( $commentNode ) {
+		if ( $commentNode->isSilent() ) {
+			return array();
+		}
+		return $commentNode;
+	}
+
+	public function visitMedia( $mediaNode, &$visitDeeper ) {
+		$mediaNode->accept( $this );
+		$visitDeeper = false;
+
+		if ( !$mediaNode->rules ) {
+			return array();
+		}
+		return $mediaNode;
+	}
+
+	public function visitDirective( $directiveNode ) {
+		if ( isset( $directiveNode->currentFileInfo['reference'] ) && ( !property_exists( $directiveNode, 'isReferenced' ) || !$directiveNode->isReferenced ) ) {
+			return array();
+		}
+		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 array();
+			}
+			$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 = array();
+		$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, array( $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 array();
+	}
+
+	/**
+	 * Helper function for visitRuleset()
+	 *
+	 * @return array
+	 */
+	private function visitRulesetPaths( $rulesetNode ) {
+		$paths = array();
+		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 = array();
+		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] = array( $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 = array();
+
+		// 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] = array();
+				} else {
+					array_splice( $rules, $i--, 1 );
+					$rules_len--;
+				}
+
+				$groups[$key][] = $rule;
+			}
+		}
+
+		foreach ( $groups as $parts ) {
+
+			if ( count( $parts ) > 1 ) {
+				$rule = $parts[0];
+				$spacedGroups = array();
+				$lastSpacedGroup = array();
+				$parts_mapped = array();
+				foreach ( $parts as $p ) {
+					if ( $p->merge === '+' ) {
+						if ( $lastSpacedGroup ) {
+							$spacedGroups[] = self::toExpression( $lastSpacedGroup );
+						}
+						$lastSpacedGroup = array();
+					}
+					$lastSpacedGroup[] = $p;
+				}
+
+				$spacedGroups[] = self::toExpression( $lastSpacedGroup );
+				$rule->value = self::toValue( $spacedGroups );
+			}
+		}
+
+	}
+
+	public static function toExpression( $values ) {
+		$mapped = array();
+		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 = array();
+		foreach ( $values as $p ) {
+			$mapped[] = $p;
+		}
+		return new Less_Tree_Value( $mapped );
+	}
+}

+ 70 - 0
lessphp/lib/Less/VisitorReplacing.php

@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Replacing Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_VisitorReplacing extends Less_Visitor {
+
+	public function visitObj( $node ) {
+		$funcName = 'visit'.$node->type;
+		if ( isset( $this->_visitFnCache[$funcName] ) ) {
+
+			$visitDeeper = true;
+			$node = $this->$funcName( $node, $visitDeeper );
+
+			if ( $node ) {
+				if ( $visitDeeper && is_object( $node ) ) {
+					$node->accept( $this );
+				}
+
+				$funcName = $funcName . "Out";
+				if ( isset( $this->_visitFnCache[$funcName] ) ) {
+					$this->$funcName( $node );
+				}
+			}
+
+		} else {
+			$node->accept( $this );
+		}
+
+		return $node;
+	}
+
+	public function visitArray( $nodes ) {
+		$newNodes = array();
+		foreach ( $nodes as $node ) {
+			$evald = $this->visitObj( $node );
+			if ( $evald ) {
+				if ( is_array( $evald ) ) {
+					self::flatten( $evald, $newNodes );
+				} else {
+					$newNodes[] = $evald;
+				}
+			}
+		}
+		return $newNodes;
+	}
+
+	public function flatten( $arr, &$out ) {
+		foreach ( $arr as $item ) {
+			if ( !is_array( $item ) ) {
+				$out[] = $item;
+				continue;
+			}
+
+			foreach ( $item as $nestedItem ) {
+				if ( is_array( $nestedItem ) ) {
+					self::flatten( $nestedItem, $out );
+				} else {
+					$out[] = $nestedItem;
+				}
+			}
+		}
+
+		return $out;
+	}
+
+}

+ 82 - 0
nic/ds.php

@@ -0,0 +1,82 @@
+<?php include "../top.inc.php"; ?>
+
+    <form method="POST" action="ns.php">
+      <label for="subdomain">Domaine :</label>
+      <br>
+      <input id="subdomain" placeholder="nic" name="subdomain" type="text">.atope.art
+      <br>
+      <label for="tag">Tag de la clé :</label>
+      <br>
+      <input id="tag" placeholder="12345" min="1" max="100000" name="tag" type="number">
+      <br>
+      <label for="algo">Algorithme :</label>
+      <br>
+      <select name="algo" id="algo">
+        <!-- RFC 8624 : Algorithm Implementation Requirements and Usage Guidance for DNSSEC > Algorithm Selection > DNSKEY Algorithms -->
+        <!-- https://tools.ietf.org/html/rfc8624.html#section-3.1 -->
+        <option value="1" disabled="">1 (RSAMD5)</option>
+        <option value="3" disabled="">3 (DSA)</option>
+        <option value="5" disabled="">5 (RSASHA1)</option>
+        <option value="6" disabled="">6 (DSA-NSEC3-SHA1)</option>
+        <option value="7" disabled="">7 (RSASHA1-NSEC3-SHA1)</option>
+        <option value="8">8 (RSASHA256)</option>
+        <option value="10" disabled="">10 (RSASHA512)</option>
+        <option value="12" disabled="">12 (ECC-GOST)</option>
+        <option value="13" selected="">13 (ECDSAP256SHA256)</option>
+        <option value="14">14 (ECDSAP384SHA384)</option>
+        <option value="15">15 (ED25519)</option>
+        <option value="16">16 (ED448)</option>
+      </select>
+      <br>
+      <label for="dt">Digest Type :</label>
+      <br>
+      <select name="dt" id="dt">
+        <!-- RFC 8624 : Algorithm Implementation Requirements and Usage Guidance for DNSSEC > Algorithm Selection > DS and CDS Algorithms -->
+        <!-- https://tools.ietf.org/html/rfc8624.html#section-3.3 -->
+        <option value="1" disabled="">1 (SHA-1)</option>
+        <option value="2" selected="">2 (SHA-256)</option>
+        <option value="3" disabled="">3 (GOST R 34.11-94)</option>
+        <option value="4">4 (SHA-384)</option>
+      </select>
+      <br>
+      <label for="ds">Serveur de nom :</label>
+      <br>
+      <input id="ds" placeholder="niver.atope.art. 86400 DS 12345 13 4 018F25E4A022463478C9E30136EC53771A1704A0F0B3CE5B883AC9C8A6A55D16B638B4DE70662ACA5295D3669E7CADD9" name="ns" type="text">
+      <br>
+      <input type="submit">
+    </form>
+
+    <?php
+
+    if (isset($_POST['subdomain']) AND isset($_POST['ds']) AND isset($_SESSION['username'])) {
+
+      if (isset($_POST['algo'])
+        AND (
+          $_POST['algo'] == "8"
+          OR $_POST['algo'] == "14"
+          OR $_POST['algo'] == "15"
+          OR $_POST['algo'] == "16"
+        )) {
+
+      }
+
+      /*
+      moomin3.atope.art.      0       DS      11168 13 1 D39D6B1ED58ECE9FA8AB6B7DB53E78338D45E2FF
+      moomin3.atope.art.      0       DS      11168 13 2 DB7C3B76CF40C1F7C0BF278AB46284BFEA5E7D44B382992E32BB5B3DC50BA7AB
+      moomin3.atope.art.      0       DS      11168 13 4 018F25E4A022463478C9E30136EC53771A1704A0F0B3CE5B883AC9C8A6A55D16B638B4DE70662ACA5295D3669E7CADD9
+      from rfc : secure.example.   DS      tag=12345 alg=3 digest_type=1 <foofoo>
+      */
+
+      exec("knotc zone-begin atope.art");
+      exec("knotc zone-set atope.art " . $_POST['subdomain'] . ".atope.art. DS " . $_POST['ns'] . ".");
+      exec("knotc zone-commit atope.art");
+      echo "Modifications appliquées";
+    } else {
+      echo "Rien n'a été appliqué lors du dernier chargement.";
+    }
+
+
+    ?>
+
+    <br><a href="glue.php">Glue Record</a>
+<?php include "../bottom.inc.php"; ?>

+ 38 - 0
nic/glue.php

@@ -0,0 +1,38 @@
+<?php require "../top.inc.php"; ?>
+
+    <form method="POST" action="glue.php">
+
+      <br>
+      <label for="ns">Serveur de nom</label>
+      <br>
+      <input id="ns" placeholder="ns1.atope.art" name="ns" type="text">
+      <br>
+      <label for="ipv4">IPv4</label>
+      <br>
+      <input id="ipv4" placeholder="127.0.0.1" name="ipv4" type="text">
+      <br>
+      <label for="ipv6">IPv6</label>
+      <br>
+      <input id="ipv6" placeholder="::1" name="ipv6" type="text">
+      <br>
+      <input type="submit">
+    </form>
+
+    <?php
+
+    if (isset($_POST['ipv4']) AND isset($_POST['ns'])) {
+      exec("knotc zone-begin atope.art");
+      exec("knotc zone-set atope.art " . $_POST['ns'] . ". 200 IN A " . $_POST['ipv4']);
+      //exec("knotc zone-set atope.art " . $_POST['ns'] . ". 200 IN AAAA " . $_POST['ipv6'] . ".");
+      exec("knotc zone-commit atope.art");
+      echo "Modifications appliquées";
+    } else {
+      echo "Rien n'a été appliqué lors du dernier chargement.";
+    }
+
+
+    ?>
+
+    <br><a href="glue.php">Glue Record</a>
+
+<?php require "../bottom.inc.php"; ?>

+ 11 - 0
nic/index.php

@@ -0,0 +1,11 @@
+<?php include "../top.inc.php"; ?>
+    <p>
+      Ce site a pour but de permettre la création de sous-domaines d'atope.art par n'importe qui.
+      <br>
+      <a href="ns.php">NS (Name Server)</a>
+      <br>
+      <a href="glue.php">Glue Record</a>
+      <br>
+      <a href="ds.php">DS (Delegation Signer)</a>
+    </p>
+<?php include "../bottom.inc.php"; ?>

+ 43 - 0
nic/ns.php

@@ -0,0 +1,43 @@
+<?php include "../top.inc.php"; ?>
+
+    <form method="POST" action="ns.php">
+      <label for="subdomain">Domaine :</label>
+      <br>
+      <input id="subdomain" placeholder="nic" name="subdomain" type="text">.atope.art
+      <br>
+      <label for="ns">Serveur de nom :</label>
+      <br>
+      <input id="ns" placeholder="ns1.atope.art" name="ns" type="text">
+      <br>
+      <input type="submit">
+    </form>
+
+    <?php
+
+    if (isset($_POST['subdomain']) AND isset($_POST['ns']) AND isset($_SESSION['username'])) {
+
+      $db = new PDO('sqlite:' . $dbPath);
+      $stmt = $db->prepare("INSERT INTO registry(domain, username, last_renewal) VALUES(:domain, :username, :last_renewal)");
+      $username = $_SESSION['username'];
+      $domain = $_POST['subdomain'] . ".atope.art.";
+      $time = time();
+      // Bind parameters to statement variables
+      $stmt->bindParam(':domain', $domain);
+      $stmt->bindParam(':username', $username);
+      $stmt->bindParam(':last_renewal', $time);
+
+      $stmt->execute();
+
+      exec("knotc zone-begin atope.art");
+      exec("knotc zone-set atope.art " . $_POST['subdomain'] . ".atope.art. 200 IN NS " . $_POST['ns'] . ".");
+      exec("knotc zone-commit atope.art");
+      echo "Modifications appliquées";
+    } else {
+      echo "Rien n'a été appliqué lors du dernier chargement.";
+    }
+
+
+    ?>
+
+    <br><a href="glue.php">Glue Record</a>
+<?php include "../bottom.inc.php"; ?>

+ 56 - 0
nic/register.php

@@ -0,0 +1,56 @@
+<?php include "../top.inc.php"; ?>
+
+    Enregistrer un nouveau domaine
+
+    <form method="post">
+      <label for="subdomain">Domaine</label>
+      <br>
+      <input id="subdomain" required="" placeholder="nic" name="subdomain" type="text">.atope.art
+
+      <input type="submit">
+    </form>
+
+    <?php
+
+    if (isset($_POST['subdomain']) AND isset($_SESSION['username'])) {
+      $domain = $_POST['subdomain'] . ".atope.art.";
+      if (filter_var($domain, FILTER_VALIDATE_DOMAIN)) {
+
+        $domainArray[0] = $domain;
+
+        $db = new PDO('sqlite:' . $dbPath);
+
+        $req = $db->prepare('SELECT domain FROM registry WHERE domain = ?');
+        $req->execute($domainArray);
+
+        $domainFound = $req->fetch()['domain'];
+
+        if (isset($domainFound)) {
+          echo "Le domaine " . $domainFound . " est déjà utilisé.";
+        } else {
+          $db = new PDO('sqlite:' . $dbPath);
+          $stmt = $db->prepare("INSERT INTO registry(domain, username, last_renewal) VALUES(:domain, :username, :last_renewal)");
+          $username = $_SESSION['username'];
+
+          $time = time();
+
+          $stmt->bindParam(':domain', $domain);
+          $stmt->bindParam(':username', $username);
+          $stmt->bindParam(':last_renewal', $time);
+
+          $stmt->execute();
+
+          echo "Nouveau domaine enregistré";
+        }
+
+      } else {
+        echo "Erreur : Nom de domaine invalide";
+      }
+    } else {
+      echo "Rien n'a été appliqué lors du dernier chargement.";
+    }
+
+
+    ?>
+
+<?php include "../bottom.inc.php"; ?>

Some files were not shown because too many files changed in this diff