diff --git a/.gitmodules b/.gitmodules
index 1fb1c44..8b13789 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,7 +1 @@
-[submodule "Core/Frameworks/Formal"]
- path = Core/Frameworks/Formal
- url = https://jeromeschneider@github.com/jeromeschneider/Formal.git
-[submodule "Core/Frameworks/Flake"]
- path = Core/Frameworks/Flake
- url = https://jeromeschneider@github.com/jeromeschneider/Flake.git
diff --git a/Core/Frameworks/Flake b/Core/Frameworks/Flake
deleted file mode 160000
index 3679cb4..0000000
--- a/Core/Frameworks/Flake
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3679cb4a0dbc808f9200f495a2e1f6a3dd0fe4ca
diff --git a/Core/Frameworks/Flake/Controller/Cli.php b/Core/Frameworks/Flake/Controller/Cli.php
new file mode 100644
index 0000000..d967c8e
--- /dev/null
+++ b/Core/Frameworks/Flake/Controller/Cli.php
@@ -0,0 +1,154 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Controller;
+
+class Cli extends \Flake\Core\Render\Container {
+
+ function render() {
+ $this->sys_init();
+ $this->init();
+
+ $this->echoFlush($this->notice("process started @" . strftime("%d/%m/%Y %H:%M:%S")));
+ $this->execute();
+ $this->echoFlush($this->notice("process ended @" . strftime("%d/%m/%Y %H:%M:%S")) . "\n\n");
+ }
+
+ function execute() {
+ reset($this->aSequence);
+ while(list($sKey,) = each($this->aSequence)) {
+ $this->aSequence[$sKey]["block"]->execute();
+ }
+ }
+
+ /**************************************************************************/
+
+ var $sLog = "";
+
+ function sys_init() {
+ $this->rawLine("Command line: " . (implode(" ", $_SERVER["argv"])));
+ $this->initArgs();
+ }
+
+ function init() {
+ }
+
+ function initArgs() {
+ $sShortOpts = "";
+ $sShortOpts .= "h"; // help; pas de valeur
+ $sShortOpts .= "w:"; // author; valeur obligatoire
+
+ $aLongOpts = array(
+ "help", // help; pas de valeur
+ "helloworld", // author; pas de valeur
+ );
+
+ $this->aArgs = getopt($sShortOpts, $aLongOpts);
+ }
+
+ function getScriptPath() {
+ return realpath($_SERVER['argv'][0]);
+ }
+
+ function getSyntax() {
+ return $this->getScriptPath();
+ }
+
+ function syntaxError() {
+ $sStr = $this->rawLine("Syntax error.\nUsage: " . $this->getSyntax());
+ die("\n\n" . $sStr . "\n\n");
+ }
+
+ function log($sStr) {
+ $this->sLog .= $sStr;
+ }
+
+ function header($sMsg) {
+
+ $sStr = "\n" . str_repeat("#", 80);
+ $sStr .= "\n" . "#" . str_repeat(" ", 78) . "#";
+ $sStr .= "\n" . "#" . str_pad(strtoupper($sMsg), 78, " ", STR_PAD_BOTH) . "#";
+ $sStr .= "\n" . "#" . str_repeat(" ", 78) . "#";
+ $sStr .= "\n" . str_repeat("#", 80);
+ $sStr .= "\n";
+
+ $this->log($sStr);
+ return $sStr;
+ }
+
+ function subHeader($sMsg) {
+ $sStr = "\n\n# " . str_pad(strtoupper($sMsg) . " ", 78, "-", STR_PAD_RIGHT) . "\n";
+ $this->log($sStr);
+ return $sStr;
+ }
+
+ function subHeader2($sMsg) {
+ $sStr = "\n# # " . str_pad($sMsg . " ", 76, "-", STR_PAD_RIGHT) . "\n";
+ $this->log($sStr);
+ return $sStr;
+ }
+
+ function textLine($sMsg) {
+ $sStr = ". " . $sMsg . "\n";
+ $this->log($sStr);
+ return $sStr;
+ }
+
+ function rawLine($sMsg) {
+ $sStr = $sMsg . "\n";
+ $this->log($sStr);
+ return $sStr;
+ }
+
+ function notice($sMsg) {
+ $sStr = "\n" . str_pad($sMsg, 80, ".", STR_PAD_BOTH) . "\n";
+ $this->log($sStr);
+ return $sStr;
+ }
+
+ function getLog() {
+ return $this->sLog;
+ }
+
+ function file_writeBin($sPath, $sData, $bUTF8 = TRUE) {
+
+ $rFile = fopen($sPath, "wb");
+
+ if($bUTF8 === TRUE) {
+ fputs($rFile, "\xEF\xBB\xBF" . $sData);
+ } else {
+ fputs($rFile, $sData);
+ }
+
+ fclose($rFile);
+ }
+
+ function echoFlush($sString = "") {
+ echo $sString;
+ ob_flush();
+ flush();
+ }
+}
diff --git a/Core/Frameworks/Flake/Controller/HtmlBlock.php b/Core/Frameworks/Flake/Controller/HtmlBlock.php
new file mode 100644
index 0000000..7b15b50
--- /dev/null
+++ b/Core/Frameworks/Flake/Controller/HtmlBlock.php
@@ -0,0 +1,42 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Controller;
+
+class HtmlBlock extends \Flake\Core\Controller {
+
+ function __construct($sHtml) {
+ $this->sHtml = $sHtml;
+ }
+
+ function execute() {
+
+ }
+
+ function render() {
+ return $this->sHtml;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Controller/HtmlBlockTemplated.php b/Core/Frameworks/Flake/Controller/HtmlBlockTemplated.php
new file mode 100644
index 0000000..d3eac0f
--- /dev/null
+++ b/Core/Frameworks/Flake/Controller/HtmlBlockTemplated.php
@@ -0,0 +1,44 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Controller;
+
+class HtmlBlockTemplated extends \Flake\Core\Controller {
+
+ function __construct($sTemplatePath, $aMarkers = array()) {
+ $this->sTemplatePath = $sTemplatePath;
+ $this->aMarkers = $aMarkers;
+ }
+
+ function render() {
+ $oTemplate = new \Flake\Core\Template($this->sTemplatePath);
+ $sHtml = $oTemplate->parse(
+ $this->aMarkers
+ );
+
+ return $sHtml;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Controller/Page.php b/Core/Frameworks/Flake/Controller/Page.php
new file mode 100644
index 0000000..1e0a41c
--- /dev/null
+++ b/Core/Frameworks/Flake/Controller/Page.php
@@ -0,0 +1,123 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Controller;
+
+class Page extends \Flake\Core\Render\Container {
+
+ protected $sTitle = "";
+ protected $sMetaKeywords = "";
+ protected $sMetaDescription = "";
+ protected $sTemplatePath = "";
+
+ public function __construct($sTemplatePath) {
+ $this->sTemplatePath = $sTemplatePath;
+ }
+
+ public function setTitle($sTitle) {
+ $this->sTitle = $sTitle;
+ }
+
+ public function setMetaKeywords($sKeywords) {
+ $this->sMetaKeywords = $sKeywords;
+ }
+
+ public function setMetaDescription($sDescription) {
+ $this->sMetaDescription = $sDescription;
+ }
+
+ public function getTitle() {
+ return $this->sTitle;
+ }
+
+ public function getMetaKeywords() {
+ $sString = str_replace(array("le", "la", "les", "de", "des", "un", "une"), " ", $this->sMetaKeywords);
+ $sString = \Flake\Util\Tools::stringToUrlToken($sString);
+ return implode(", ", explode("-", $sString));
+ }
+
+ public function getMetaDescription() {
+ return $this->sMetaDescription;
+ }
+
+ public function setBaseUrl($sBaseUrl) {
+ $this->sBaseUrl = $sBaseUrl;
+ }
+
+ public function getBaseUrl() {
+ return $this->sBaseUrl;
+ }
+
+ public function injectHTTPHeaders() {
+ header("Content-Type: text/html; charset=UTF-8");
+ }
+
+ public function render() {
+ $this->execute();
+
+ $aRenderedBlocks = $this->renderBlocks();
+ $aRenderedBlocks["pagetitle"] = $this->getTitle();
+ $aRenderedBlocks["pagemetakeywords"] = $this->getMetaKeywords();
+ $aRenderedBlocks["pagemetadescription"] = $this->getMetaDescription();
+ $aRenderedBlocks["baseurl"] = $this->getBaseUrl();
+
+ $oTemplate = new \Flake\Core\Template($this->sTemplatePath);
+ $sHtml = $oTemplate->parse(
+ $aRenderedBlocks
+ );
+
+ return $sHtml;
+ }
+
+ public function addCss($sCssAbsPath) {
+
+ if(\Flake\Util\Frameworks::enabled("LessPHP")) {
+ $sCompiledPath = PATH_buildcss;
+ $sFileName = basename($sCssAbsPath);
+
+ $sCompiledFilePath = $sCompiledPath . \Flake\Util\Tools::shortMD5($sFileName) . "_" . $sFileName;
+
+ if(substr(strtolower($sCompiledFilePath), -4) !== ".css") {
+ $sCompiledFilePath .= ".css";
+ }
+
+ if(!file_exists($sCompiledPath)) {
+ @mkdir($sCompiledPath);
+ if(!file_exists($sCompiledPath)) {
+ die("Page: Cannot create " . $sCompiledPath);
+ }
+ }
+
+ \Frameworks\LessPHP\Delegate::compileCss($sCssAbsPath, $sCompiledFilePath);
+ $sCssUrl = \Flake\Util\Tools::serverToRelativeWebPath($sCompiledFilePath);
+ } else {
+ $sCssUrl = \Flake\Util\Tools::serverToRelativeWebPath($sCssAbsPath);
+ }
+
+ $sHtml = "";
+ $this->zone("head")->addBlock(new \Flake\Controller\HtmlBlock($sHtml));
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Controller/Rpc.php b/Core/Frameworks/Flake/Controller/Rpc.php
new file mode 100644
index 0000000..3e7fc59
--- /dev/null
+++ b/Core/Frameworks/Flake/Controller/Rpc.php
@@ -0,0 +1,68 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Controller;
+
+class Rpc extends \Flake\Core\Render\Container {
+
+ public function initializeContext() {
+ $this->injectHTTPHeaders();
+ $GLOBALS["POSTCONNECTIONSERVICES"] = array();
+ }
+
+ public function injectHTTPHeaders() {
+ ob_start();
+
+ header("Access-Control-Allow-Origin: *"); # To allow cross domain AJAX response
+ header("Access-Control-Allow-Credentials: true"); # To allow cross domain cookies
+ header("Content-Type: application/json; charset=UTF-8");
+
+ # Needed to cut client off when needed
+ header("Connection: close\r\n");
+ ignore_user_abort(TRUE);
+
+ }
+
+ public function P3PAllowCrossDomainCookies() {
+ # This tells IE6+ to accept passing cookies allong when establishing a XHR connection to read.codr.fr
+ header('P3P: CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
+ }
+
+ public function sendResponseCutClientAndRunPostConnectionTasks() {
+ header("Content-Length: " . ob_get_length());
+ ob_end_flush();
+ flush();
+
+ reset($GLOBALS["POSTCONNECTIONSERVICES"]);
+
+ # If post-connection services are registered, process
+ foreach($GLOBALS["POSTCONNECTIONSERVICES"] as $service) {
+ $service->execute();
+ }
+
+ session_write_close();
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/ClassLoader.php b/Core/Frameworks/Flake/Core/ClassLoader.php
new file mode 100644
index 0000000..a2a1105
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/ClassLoader.php
@@ -0,0 +1,85 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+class ClassLoader {
+
+ public static function register() {
+ return spl_autoload_register(array(__CLASS__, 'loadClass'));
+ }
+
+ public static function loadClass($sFullClassName) {
+
+ $sClassPath = FALSE;
+
+ $aParts = explode("\\", $sFullClassName);
+ if(count($aParts) === 1) {
+ return;
+ }
+
+ # Extracting the Radical
+ $sRadical = $aParts[0];
+
+ if(in_array($sRadical, array("Flake", "Specific", "Frameworks"))) {
+
+ if($sRadical === "Flake") {
+ $sRootPath = FLAKE_PATH_ROOT;
+ } elseif($sRadical === "Specific") {
+ $sRootPath = FLAKE_PATH_SPECIFIC;
+ } else {
+ $sRootPath = PROJECT_PATH_FRAMEWORKS;
+ }
+
+ # Stripping radical
+ array_shift($aParts);
+
+ # Classname is the last part
+ $sClassName = array_pop($aParts);
+
+ # Path to class
+ $sClassPath = $sRootPath . implode("/", $aParts) . "/" . $sClassName . ".php";
+
+ } elseif(count($aParts) > 1) {
+ if($aParts[1] === "Framework") {
+ # It must be a Flake Framework
+ $sClassPath = PROJECT_PATH_FRAMEWORKS . $sRadical . "/Framework.php";
+ }
+ }
+
+ if($sClassPath === FALSE) {
+ return;
+ }
+
+ if(file_exists($sClassPath) && is_readable($sClassPath)) {
+ require_once($sClassPath);
+ } else {
+ echo '
";
+ die();
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Core/Collection.php b/Core/Frameworks/Flake/Core/Collection.php
new file mode 100644
index 0000000..679968b
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Collection.php
@@ -0,0 +1,214 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+class Collection extends \Flake\Core\FLObject implements \Iterator {
+ protected $aCollection = array();
+ protected $aMeta = array();
+
+ public function current() {
+ return current($this->aCollection);
+ }
+
+ public function key() {
+ return key($this->aCollection);
+ }
+
+ public function next() {
+ return next($this->aCollection);
+ }
+
+ public function rewind() {
+ $this->reset();
+ }
+
+ public function valid() {
+ $key = key($this->aCollection);
+ return ($key !== NULL && $key !== FALSE);
+ }
+
+ public function &getForKey($sKey) {
+ $aKeys = $this->keys();
+ if(!in_array($sKey, $aKeys)) {
+ throw new \Exception("\Flake\Core\Collection->getForKey(): key '" . $sKey . "' not found in Collection");
+ }
+
+ $oRes = $this->aCollection[$sKey];
+ return $oRes;
+ }
+
+ public function &each() {
+ list($key, $val) = each($this->aCollection);
+ return $val;
+ }
+
+ public function reset() {
+ reset($this->aCollection);
+ }
+
+ public function prev() {
+ return prev($this->aCollection);
+ }
+
+ public function count() {
+ return count($this->aCollection);
+ }
+
+ public function keys() {
+ return array_keys($this->aCollection);
+ }
+
+ public function isEmpty() {
+ return $this->count() === 0;
+ }
+
+ public function isAtFirst() {
+ return $this->key() === array_shift($this->keys());
+ }
+
+ public function isAtLast() {
+ return $this->key() === array_pop($this->keys());
+ }
+
+ public function push(&$mMixed) {
+ array_push($this->aCollection, $mMixed);
+ }
+
+ public function flush() {
+ unset($this->aCollection);
+ $this->aCollection = array();
+ }
+
+ public function &first() {
+ if(!$this->isEmpty()) {
+ $aKeys = $this->keys();
+ return $this->aCollection[array_shift($aKeys)];
+ }
+
+ $var = null; # two lines instead of one
+ return $var; # as PHP needs a variable to return by ref
+ }
+
+ public function &last() {
+ if(!$this->isEmpty()) {
+ $aKeys = $this->keys();
+ return $this->aCollection[array_pop($aKeys)];
+ }
+
+ $var = null;
+ return $var;
+ }
+
+ public function toArray() {
+ return $this->aCollection;
+ }
+
+ public static function fromArray($aData) {
+ $oColl = new \Flake\Core\Collection();
+ reset($aData);
+ foreach($aData as $mData) {
+ $oColl->push($mData);
+ }
+
+ return $oColl;
+ }
+
+ # Create a new collection like this one
+ # This abstraction is useful because of CollectionTyped
+ protected function newCollectionLikeThisOne() {
+ $oCollection = new \Flake\Core\Collection(); # two lines instead of one
+ return $oCollection; # as PHP needs a variable to return by ref
+ }
+
+ public function map($sFunc) {
+ $aData = $this->toArray();
+ $oNewColl = $this->fromArray(array_map($sFunc, $aData));
+ return $oNewColl;
+ }
+
+ public function walk($sFunc, $aParams=array()) {
+ $aData = $this->toArray();
+ $oNewColl = $this->fromArray(array_walk($aData, $sFunc, $aParams));
+ return $oNewColl;
+ }
+
+ public function remove($sKey) {
+ $aKeys = $this->keys();
+ if(!in_array($sKey, $aKeys)) {
+ throw new \Exception("\Flake\Core\Collection->remove(): key '" . $sKey . "' not found in Collection");
+ }
+
+ unset($this->aCollection[$sKey]);
+ $this->aCollection = array_values($this->aCollection);
+ }
+
+ public function &__call($sName, $aArguments) {
+ if(
+ strlen($sName) > 7 &&
+ $sName{0} === "s" &&
+ $sName{1} === "e" &&
+ $sName{2} === "t" &&
+ $sName{3} === "M" &&
+ $sName{4} === "e" &&
+ $sName{5} === "t" &&
+ $sName{6} === "a"
+ ) {
+ $sKey = strtolower(substr($sName, 7, 1)) . substr($sName, 8);
+ $mValue =& $aArguments[0];
+
+ if(is_null($mValue)) {
+ if(array_key_exists($sKey, $this->aMeta)) {
+ unset($this->aMeta[$sKey]);
+ }
+ } else {
+ $this->aMeta[$sKey] =& $mValue;
+ }
+
+ $res = NULL;
+ return $res; # To avoid 'Notice: Only variable references should be returned by reference'
+
+ } elseif(
+ strlen($sName) > 7 &&
+ $sName{0} === "g" &&
+ $sName{1} === "e" &&
+ $sName{2} === "t" &&
+ $sName{3} === "M" &&
+ $sName{4} === "e" &&
+ $sName{5} === "t" &&
+ $sName{6} === "a"
+ ) {
+ $sKey = strtolower(substr($sName, 7, 1)) . substr($sName, 8);
+ if(array_key_exists($sKey, $this->aMeta)) {
+ return $this->aMeta[$sKey];
+ } else {
+ return null;
+ }
+ } else {
+ throw new \Exception("Method " . $sName . "() not found on " . get_class($this));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/CollectionTyped.php b/Core/Frameworks/Flake/Core/CollectionTyped.php
new file mode 100644
index 0000000..55c0a36
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/CollectionTyped.php
@@ -0,0 +1,51 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+class CollectionTyped extends \Flake\Core\Collection {
+
+ protected $sTypeClassOrProtocol;
+
+ public function __construct($sTypeClassOrProtocol) {
+ $this->sTypeClassOrProtocol = $sTypeClassOrProtocol;
+ $this->setMetaType($this->sTypeClassOrProtocol);
+ }
+
+ public function push(&$mMixed) {
+ if(!\Flake\Util\Tools::is_a($mMixed, $this->sTypeClassOrProtocol)) {
+ throw new \Exception("\Flake\Core\CollectionTyped<" . $this->sTypeClassOrProtocol . ">: Given object is not correctly typed.");
+ }
+
+ parent::push($mMixed);
+ }
+
+ # Create a new collection like this one
+ public function newCollectionLikeThisOne() {
+ $oCollection = new \Flake\Core\CollectionTyped($this->sTypeClassOrProtocol);
+ return $oCollection;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Controller.php b/Core/Frameworks/Flake/Core/Controller.php
new file mode 100644
index 0000000..e0e417e
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Controller.php
@@ -0,0 +1,56 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+abstract class Controller extends \Flake\Core\FLObject {
+
+ protected $aParams = array();
+
+ public function __construct($aParams = array()) {
+ $this->aParams = $aParams;
+ }
+
+ public function getParams() {
+ return $this->aParams;
+ }
+
+ public static function link(/*[$sParam, $sParam2, ...]*/) {
+ return static::buildRoute();
+ }
+
+ public static function buildRoute($aParams = array()) {
+ # TODO: il faut remplacer le mécanisme basé sur un nombre variable de paramètres en un mécanisme basé sur un seul paramètre "tableau"
+ #$aParams = func_get_args();
+ $sController = "\\" . get_called_class();
+ #array_unshift($aParams, $sController); # Injecting current controller as first param
+ #return call_user_func_array($GLOBALS["ROUTER"] . "::buildRouteForController", $aParams);
+ return $GLOBALS["ROUTER"]::buildRouteForController($sController, $aParams);
+ }
+
+ public abstract function execute();
+ public abstract function render();
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/DOM/HTMLElement.php b/Core/Frameworks/Flake/Core/DOM/HTMLElement.php
new file mode 100644
index 0000000..e2b3475
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/DOM/HTMLElement.php
@@ -0,0 +1,130 @@
+
+# All rights reserved
+#
+# http://bootstrap.codr.fr
+#
+# This script is part of the CodrBootstrap project. The CodrBootstrap project
+# is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\DOM;
+
+class HTMLElement extends \DOMElement {
+ public function getInnerText() {
+ return $this->nodeValue;
+ }
+
+ public function getOuterHTML() {
+ return $this->ownerDocument->saveHTML($this);
+ }
+
+ public function getNormalizedInnerText() {
+ return $this->normalizeWhiteSpace($this->getInnerText());
+ }
+
+ public function getNormalizedOuterHTML() {
+ return $this->normalizeWhitespace($this->getOuterHTML());
+ }
+
+ protected function normalizeWhitespace($sText) {
+ $sText = str_replace(array("\t", "\r\n", "\n"), ' ', $sText);
+
+ # using multiple str_replace has proven to be twice as fast that regexp on big strings
+ $iCount = 0;
+ do {
+ $sText = str_replace(' ', ' ', $sText, $iCount);
+ } while($iCount > 0);
+
+ return trim($sText);
+ }
+
+ public function setInnerHTML($sHtml) {
+ // first, empty the element
+ for ($x=$this->childNodes->length-1; $x>=0; $x--) {
+ $this->removeChild($this->childNodes->item($x));
+ }
+ // $value holds our new inner HTML
+ if ($sHtml != '') {
+ $f = $this->ownerDocument->createDocumentFragment();
+ // appendXML() expects well-formed markup (XHTML)
+ $result = @$f->appendXML($sHtml); // @ to suppress PHP warnings
+ if ($result) {
+ if ($f->hasChildNodes()) $this->appendChild($f);
+ } else {
+ // $value is probably ill-formed
+ $f = new \DOMDocument();
+ $sHtml = mb_convert_encoding($sHtml, 'HTML-ENTITIES', 'UTF-8');
+ // Using will generate a warning, but so will bad HTML
+ // (and by this point, bad HTML is what we've got).
+ // We use it (and suppress the warning) because an HTML fragment will
+ // be wrapped around tags which we don't really want to keep.
+ // Note: despite the warning, if loadHTML succeeds it will return true.
+ $result = @$f->loadHTML(''.$sHtml.'');
+ if ($result) {
+ $import = $f->getElementsByTagName('htmlfragment')->item(0);
+ foreach ($import->childNodes as $child) {
+ $importedNode = $this->ownerDocument->importNode($child, true);
+ $this->appendChild($importedNode);
+ }
+ } else {
+ // oh well, we tried, we really did. :(
+ // this element is now empty
+ }
+ }
+ }
+ }
+
+ public function getInnerHTML() {
+ $sHtml = '';
+ $iNodes = $this->childNodes->length;
+ for($i = 0; $i < $iNodes; $i++) {
+ $oItem = $this->childNodes->item($i);
+ $sHtml .= $oItem->ownerDocument->saveHTML($oItem);
+ }
+
+ return $sHtml;
+ }
+
+ public function isDOMText() {
+ return $this->nodeType === XML_TEXT_NODE;
+ }
+
+ public function getSiblingPosition() {
+ $iPos = 0;
+ $oNode = $this;
+
+ while(!is_null($oNode->previousSibling)) {
+ $oNode = $oNode->previousSibling;
+ $iPos++;
+ }
+
+ return $iPos;
+ }
+
+ public function getTreePosition() {
+ # Tree position is number 100^level + sibling offset
+ $iLevel = substr_count($this->getNodePath(), "/") - 2; # -1 to align on 0, and -1 to compensate for /document
+ if($iLevel === 0) {
+ return $this->getSiblingPosition();
+ } else {
+ return pow(10, $iLevel) + $this->getSiblingPosition();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Database.php b/Core/Frameworks/Flake/Core/Database.php
new file mode 100644
index 0000000..5a03571
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Database.php
@@ -0,0 +1,208 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+abstract class Database extends \Flake\Core\FLObject {
+
+ /* common stuff */
+
+ protected function messageAndDie($sMessage) {
+ $sError = "
" . get_class($this) . ": " . $sMessage . "
";
+ die($sError);
+ }
+
+ public function exec_INSERTquery($table,$fields_values,$no_quote_fields=FALSE) {
+ return $this->query($this->INSERTquery($table,$fields_values,$no_quote_fields));
+ }
+
+ public function INSERTquery($table,$fields_values,$no_quote_fields=FALSE) {
+
+ // Table and fieldnames should be "SQL-injection-safe" when supplied to this function (contrary to values in the arrays which may be insecure).
+ if (is_array($fields_values) && count($fields_values)) {
+
+ // quote and escape values
+ $fields_values = $this->fullQuoteArray($fields_values,$table,$no_quote_fields);
+
+ // Build query:
+ $query = 'INSERT INTO '.$table.'
+ (
+ '.implode(',
+ ',array_keys($fields_values)).'
+ ) VALUES (
+ '.implode(',
+ ',$fields_values).'
+ )';
+
+ // Return query:
+ if ($this->debugOutput || $this->store_lastBuiltQuery) $this->debug_lastBuiltQuery = $query;
+ return $query;
+ }
+ }
+
+ public function exec_UPDATEquery($table,$where,$fields_values,$no_quote_fields=FALSE) {
+ return $this->query($this->UPDATEquery($table,$where,$fields_values,$no_quote_fields));
+ }
+
+ public function UPDATEquery($table,$where,$fields_values,$no_quote_fields=FALSE) {
+
+ // Table and fieldnames should be "SQL-injection-safe" when supplied to this function (contrary to values in the arrays which may be insecure).
+ if (is_string($where)) {
+ if (is_array($fields_values) && count($fields_values)) {
+
+ // quote and escape values
+ $nArr = $this->fullQuoteArray($fields_values,$table,$no_quote_fields);
+
+ $fields = array();
+ foreach ($nArr as $k => $v) {
+ $fields[] = $k.'='.$v;
+ }
+
+ // Build query:
+ $query = 'UPDATE '.$table.'
+ SET
+ '.implode(',
+ ',$fields).
+ (strlen($where)>0 ? '
+ WHERE
+ '.$where : '');
+
+ // Return query:
+ if ($this->debugOutput || $this->store_lastBuiltQuery) $this->debug_lastBuiltQuery = $query;
+ return $query;
+ }
+ } else {
+ die('Fatal Error: "Where" clause argument for UPDATE query was not a string in $this->UPDATEquery() !');
+ }
+ }
+
+ public function exec_DELETEquery($table,$where) {
+ return $this->query($this->DELETEquery($table,$where));
+ }
+
+ public function DELETEquery($table,$where) {
+ if (is_string($where)) {
+
+ // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
+ $query = 'DELETE FROM '.$table.
+ (strlen($where)>0 ? '
+ WHERE
+ '.$where : '');
+
+ if ($this->debugOutput || $this->store_lastBuiltQuery) $this->debug_lastBuiltQuery = $query;
+ return $query;
+ } else {
+ die('Fatal Error: "Where" clause argument for DELETE query was not a string in $this->DELETEquery() !');
+ }
+ }
+
+ public function exec_SELECTquery($select_fields,$from_table,$where_clause,$groupBy='',$orderBy='',$limit='') {
+ return $this->query($this->SELECTquery($select_fields,$from_table,$where_clause,$groupBy,$orderBy,$limit));
+ }
+
+ public function SELECTquery($select_fields,$from_table,$where_clause,$groupBy='',$orderBy='',$limit='') {
+
+ // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
+ // Build basic query:
+ $query = 'SELECT '.$select_fields.'
+ FROM '.$from_table.
+ (strlen($where_clause)>0 ? '
+ WHERE
+ '.$where_clause : '');
+
+ // Group by:
+ if (strlen($groupBy)>0) {
+ $query.= '
+ GROUP BY '.$groupBy;
+ }
+ // Order by:
+ if (strlen($orderBy)>0) {
+ $query.= '
+ ORDER BY '.$orderBy;
+ }
+ // Group by:
+ if (strlen($limit)>0) {
+ $query.= '
+ LIMIT '.$limit;
+ }
+
+ // Return query:
+ if ($this->debugOutput || $this->store_lastBuiltQuery) $this->debug_lastBuiltQuery = $query;
+ return $query;
+ }
+
+ public function fullQuote($str, $table) {
+ return '\''.$this->quote($str, $table).'\'';
+ }
+
+ public function fullQuoteArray($arr, $table, $noQuote=FALSE) {
+ if (is_string($noQuote)) {
+ $noQuote = explode(',',$noQuote);
+ } elseif (!is_array($noQuote)) { // sanity check
+ $noQuote = FALSE;
+ }
+
+ foreach($arr as $k => $v) {
+ if ($noQuote===FALSE || !in_array($k,$noQuote)) {
+ $arr[$k] = $this->fullQuote($v, $table);
+ }
+ }
+ return $arr;
+ }
+
+ /* Should be abstract, but we provide a body anyway as PDO abstracts these methods for us */
+
+ public function query($sSql) {
+ if(($stmt = $this->oDb->query($sSql)) === FALSE) {
+ $sMessage = print_r($this->oDb->errorInfo(), TRUE);
+ throw new \Exception("SQL ERROR in: '" . $sSql . "'; Message: " . $sMessage);
+ }
+
+ return new \Flake\Core\Database\Statement($stmt);
+ }
+
+ public function lastInsertId() {
+ return $this->oDb->lastInsertId();
+ }
+
+ public function quote($str) {
+ return substr($this->oDb->quote($str), 1, -1); # stripping first and last quote
+ }
+
+ public function getPDO() {
+ return $this->oDb;
+ }
+
+ public function close() {
+ $this->oDb = null;
+ }
+
+ public function __destruct() {
+ $this->close();
+ }
+
+ public abstract function tables();
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Database/Mysql.php b/Core/Frameworks/Flake/Core/Database/Mysql.php
new file mode 100644
index 0000000..490a80c
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Database/Mysql.php
@@ -0,0 +1,67 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Database;
+
+class Mysql extends \Flake\Core\Database {
+
+ protected $oDb = FALSE; // current DB link
+ protected $debugOutput = FALSE;
+ protected $store_lastBuiltQuery = TRUE;
+ protected $debug_lastBuiltQuery = "";
+ protected $sHost = "";
+ protected $sDbName = "";
+ protected $sUsername = "";
+ protected $sPassword = "";
+
+ public function __construct($sHost, $sDbName, $sUsername, $sPassword) {
+ $this->sHost = $sHost;
+ $this->sDbName = $sDbName;
+ $this->sUsername = $sUsername;
+ $this->sPassword = $sPassword;
+
+ $this->oDb = new \PDO(
+ 'mysql:host=' . $this->sHost . ';dbname=' . $this->sDbName,
+ $this->sUsername,
+ $this->sPassword
+ );
+ }
+
+ public function tables() {
+ $aTables = array();
+
+ $sSql = "SHOW TABLES FROM " . $this->sDbName;
+ $oStmt = $this->query($sSql);
+
+ while(($aRs = $oStmt->fetch()) !== FALSE) {
+ $aTables[] = array_shift($aRs);
+ }
+
+ asort($aTables);
+ reset($aTables);
+ return $aTables;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Database/Sqlite.php b/Core/Frameworks/Flake/Core/Database/Sqlite.php
new file mode 100644
index 0000000..d241855
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Database/Sqlite.php
@@ -0,0 +1,60 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Database;
+
+class Sqlite extends \Flake\Core\Database {
+
+ protected $oDb = FALSE; // current DB link
+ protected $debugOutput = FALSE;
+ protected $store_lastBuiltQuery = TRUE;
+ protected $debug_lastBuiltQuery = "";
+ protected $sDbPath = "";
+
+ public function __construct($sDbPath) {
+ $this->sDbPath = $sDbPath;
+ $this->oDb = new \PDO('sqlite:' . $this->sDbPath);
+ }
+
+ # Taken from http://dev.kohanaframework.org/issues/2985
+ public function tables() {
+ $aTables = array();
+
+ # Find all user level table names
+ $oStmt = $this->query('SELECT name '
+ .'FROM sqlite_master '
+ .'WHERE type=\'table\' AND name NOT LIKE \'sqlite_%\' '
+ .'ORDER BY name');
+
+ while(($aRs = $oStmt->fetch()) !== FALSE) {
+ // Get the table name from the results
+ $aTables[] = array_shift($aRs);
+ }
+
+ reset($aTables);
+ return $aTables;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Database/Statement.php b/Core/Frameworks/Flake/Core/Database/Statement.php
new file mode 100644
index 0000000..71aec56
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Database/Statement.php
@@ -0,0 +1,43 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Database;
+
+class Statement extends \Flake\Core\FLObject {
+ protected $stmt = null;
+
+ public function __construct($stmt) {
+ $this->stmt = $stmt;
+ }
+
+ public function fetch() {
+ if($this->stmt !== FALSE) {
+ return $this->stmt->fetch(\PDO::FETCH_ASSOC, \PDO::FETCH_ORI_FIRST);
+ }
+
+ return FALSE;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Datastructure/Chain.php b/Core/Frameworks/Flake/Core/Datastructure/Chain.php
new file mode 100644
index 0000000..cea6e24
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Datastructure/Chain.php
@@ -0,0 +1,62 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Datastructure;
+
+class Chain extends \SplDoublyLinkedList {
+
+ public function push(\Flake\Core\Datastructure\Chainable $value) {
+ $value->chain($this, $this->count());
+ parent::push($value);
+ }
+
+ public function offsetUnset($offset) {
+ throw new \Exception("Cannot delete Chainable in Chain");
+ }
+
+ public function &first() {
+ $oRes = $this->bottom();
+ return $oRes;
+ }
+
+ public function &last() {
+ $oRes = $this->top();
+ return $oRes;
+ }
+
+ public function reset() {
+ reset($this);
+ }
+
+ public function __toString() {
+ ob_start();
+ var_dump($this);
+ $sDump = ob_get_contents();
+ ob_end_clean();
+
+ return "
" . htmlspecialchars($sDump) . "
";
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Datastructure/ChainLink.php b/Core/Frameworks/Flake/Core/Datastructure/ChainLink.php
new file mode 100644
index 0000000..44ed364
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Datastructure/ChainLink.php
@@ -0,0 +1,110 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Datastructure;
+
+abstract class ChainLink implements \Flake\Core\Datastructure\Chainable {
+ protected $__container = null;
+ protected $__key = null;
+
+ public function chain(Chain $container, $key) {
+ $this->__container = $container;
+ $this->__key = $key;
+ }
+
+ public function offsetSet($offset,$value) {
+ if(is_null($this->__container)) {
+ return;
+ }
+
+ $this->__container->offsetSet($offset, $value);
+ }
+
+ public function offsetExists($offset) {
+ if(is_null($this->__container)) {
+ return FALSE;
+ }
+
+ return $this->__container->offsetExists($offset);
+ }
+
+ public function offsetUnset($offset) {
+ if(is_null($this->__container)) {
+ return;
+ }
+
+ $this->__container->offsetUnset($offset);
+ }
+
+ public function &offsetGet($offset) {
+ if(is_null($this->__container)) {
+ return null;
+ }
+
+ $oRes = $this->__container->offsetGet($offset);
+ return $oRes;
+ }
+
+ public function rewind() {
+ $this->__container->rewind();
+ }
+
+ public function current() {
+ return $this->__container->current();
+ }
+
+ public function key() {
+ return $this->__container->key();
+ }
+
+ public function &next() {
+ $oRes = $this->__container->next();
+ return $oRes;
+ }
+
+ public function &prev() {
+ $oPrev = $this->__container->prev();
+ return $oPrev;
+ }
+
+ public function valid() {
+ return $this->__container->valid();
+ }
+
+ public function count() {
+ return $this->__container->count();
+ }
+
+ public function &first() {
+ $oRes = $this->__container->first();
+ return $oRes;
+ }
+
+ public function &last() {
+ $oRes = $this->__container->last();
+ return $oRes;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Datastructure/Chainable.php b/Core/Frameworks/Flake/Core/Datastructure/Chainable.php
new file mode 100644
index 0000000..e436c3f
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Datastructure/Chainable.php
@@ -0,0 +1,38 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Datastructure;
+
+interface Chainable extends \ArrayAccess, \Iterator, \Countable {
+
+# public function &next(); # This is already specified by interface Iterator
+ public function &prev();
+
+ public function &first();
+ public function &last();
+
+ public function chain(\Flake\Core\Datastructure\Chain $chain, $key);
+}
diff --git a/Core/Frameworks/Flake/Core/FLObject.php b/Core/Frameworks/Flake/Core/FLObject.php
new file mode 100644
index 0000000..e4f0637
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/FLObject.php
@@ -0,0 +1,42 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+class FLObject {
+ public function __toString() {
+ ob_start();
+ var_dump($this);
+ $sDump = ob_get_contents();
+ ob_end_clean();
+
+ return "
" . htmlspecialchars($sDump) . "
";
+ }
+
+ public function isA($sClassOrProtocolName) {
+ return \Flake\Util\Tools::is_a($this, $sClassOrProtocolName);
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Framework.php b/Core/Frameworks/Flake/Core/Framework.php
new file mode 100644
index 0000000..bbec8c2
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Framework.php
@@ -0,0 +1,31 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+class Framework {
+
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Model.php b/Core/Frameworks/Flake/Core/Model.php
new file mode 100644
index 0000000..cae4974
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Model.php
@@ -0,0 +1,106 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+abstract class Model extends \Flake\Core\FLObject {
+ protected $aData = array();
+
+ protected function getData() {
+ reset($this->aData);
+ return $this->aData;
+ }
+
+ public function __get($sPropName) {
+ return $this->get($sPropName);
+ }
+
+ public function __isset($name) {
+ if(array_key_exists($name, $this->aData)) {
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ public function get($sPropName) {
+ if(array_key_exists($sPropName, $this->aData)) {
+ return $this->aData[$sPropName];
+ }
+
+ throw new \Exception("\Flake\Core\Model->get(): property " . htmlspecialchars($sPropName) . " does not exist on " . get_class($this));
+ }
+
+ public function set($sPropName, $sPropValue) {
+ if(array_key_exists($sPropName, $this->aData)) {
+ $this->aData[$sPropName] = $sPropValue;
+ return $this;
+ }
+
+ throw new \Exception("\Flake\Core\Model->set(): property " . htmlspecialchars($sPropName) . " does not exist on " . get_class($this));
+ }
+
+ public function label() {
+ return $this->get($this::LABELFIELD);
+ }
+
+ public static function icon() {
+ return "icon-book";
+ }
+
+ public static function mediumicon() {
+ return "glyph-book";
+ }
+
+ public static function bigicon() {
+ return "glyph2x-book";
+ }
+
+ public static function humanName() {
+ $aRes = explode("\\", get_called_class());
+ return array_pop($aRes);
+ }
+
+ public function floating() {
+ return TRUE;
+ }
+
+ public function formForThisModelInstance($options = array()) {
+ $sClass = get_class($this);
+ $oForm = new \Formal\Form($sClass, $options);
+ $oForm->setModelInstance($this);
+
+ return $oForm;
+ }
+
+ public function formMorphologyForThisModelInstance() {
+ throw new \Exception(get_class($this) . ": No form morphology provided for Model.");
+ }
+
+ public abstract function persist();
+
+ public abstract function destroy();
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Model/Db.php b/Core/Frameworks/Flake/Core/Model/Db.php
new file mode 100644
index 0000000..6695302
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Model/Db.php
@@ -0,0 +1,119 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Model;
+
+abstract class Db extends \Flake\Core\Model {
+
+ protected $bFloating = TRUE;
+
+ public function __construct($sPrimary = FALSE) {
+ if($sPrimary === FALSE) {
+ # Object will be floating
+ $this->initFloating();
+ $this->bFloating = TRUE;
+ } else {
+ $this->initByPrimary($sPrimary);
+ $this->bFloating = FALSE;
+ }
+ }
+
+ public static function &getBaseRequester() {
+ $oRequester = new \Flake\Core\Requester\Sql(get_called_class());
+ $oRequester->setDataTable(self::getDataTable());
+
+ return $oRequester;
+ }
+
+ public static function &getByRequest(\FS\Core\Requester\Sql $oRequester) {
+ // renvoie une collection de la classe du modèle courant (this)
+ return $oRequester->execute();
+ }
+
+ public static function getDataTable() {
+ $sClass = get_called_class();
+ return $sClass::DATATABLE;
+ }
+
+ public static function getPrimaryKey() {
+ $sClass = get_called_class();
+ return $sClass::PRIMARYKEY;
+ }
+
+ public function getPrimary() {
+ return $this->get(self::getPrimaryKey());
+ }
+
+ protected function initByPrimary($sPrimary) {
+
+ $rSql = $GLOBALS["DB"]->exec_SELECTquery(
+ "*",
+ self::getDataTable(),
+ self::getPrimaryKey() . "='" . $GLOBALS["DB"]->quote($sPrimary) . "'"
+ );
+
+ if(($aRs = $rSql->fetch()) === FALSE) {
+ throw new \Exception("\Flake\Core\Model '" . htmlspecialchars($sPrimary) . "' not found for model " . get_class($this));
+ }
+
+ reset($aRs);
+ $this->aData = $aRs;
+ }
+
+ public function persist() {
+ if($this->floating()) {
+ $GLOBALS["DB"]->exec_INSERTquery(
+ self::getDataTable(),
+ $this->getData()
+ );
+
+ $sPrimary = $GLOBALS["DB"]->lastInsertId();
+ $this->initByPrimary($sPrimary);
+ $this->bFloating = FALSE;
+ } else {
+ $GLOBALS["DB"]->exec_UPDATEquery(
+ self::getDataTable(),
+ self::getPrimaryKey() . "='" . $GLOBALS["DB"]->quote($this->getPrimary()) . "'",
+ $this->getData()
+ );
+ }
+ }
+
+ public function destroy() {
+ $GLOBALS["DB"]->exec_DELETEquery(
+ self::getDataTable(),
+ self::getPrimaryKey() . "='" . $GLOBALS["DB"]->quote($this->getPrimary()) . "'"
+ );
+ }
+
+ protected function initFloating() {
+ # nothing; object will be blank
+ }
+
+ public function floating() {
+ return $this->bFloating;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Model/NoDb.php b/Core/Frameworks/Flake/Core/Model/NoDb.php
new file mode 100644
index 0000000..924a982
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Model/NoDb.php
@@ -0,0 +1,36 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Model;
+
+abstract class NoDb extends \Flake\Core\Model {
+
+ public function __construct($aData = FALSE) {
+ if($aData !== FALSE) {
+ $this->aData = $aData;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/PostConnectionService.php b/Core/Frameworks/Flake/Core/PostConnectionService.php
new file mode 100644
index 0000000..50945ee
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/PostConnectionService.php
@@ -0,0 +1,39 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+abstract class PostConnectionService extends \Flake\Core\FLObject {
+
+ public function __construct($aParams = array()) {
+ $this->aParams = $aParams;
+ }
+
+ public function execute() {
+ sleep(10);
+ mail("mail@jeromeschneider.fr", "Hello", "je suis la");
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Render/Container.php b/Core/Frameworks/Flake/Core/Render/Container.php
new file mode 100644
index 0000000..9c38e4c
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Render/Container.php
@@ -0,0 +1,82 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Render;
+
+abstract class Container extends \Flake\Core\Controller {
+
+ var $aSequence = array();
+ var $aBlocks = array();
+ var $aRendu = array();
+ var $aZones = array();
+
+ function addBlock(&$oBlock, $sZone = "_DEFAULT_") {
+ $aTemp = array(
+ "block" => &$oBlock,
+ "rendu" => "",
+ );
+ $this->aSequence[] =& $aTemp;
+ $this->aBlocks[$sZone][] =& $aTemp["rendu"];
+ }
+
+ function &zone($sZone) {
+ if(!array_key_exists($sZone, $this->aZones)) {
+ $this->aZones[$sZone] = new \Flake\Core\Render\Zone($this, $sZone);
+ }
+
+ return $this->aZones[$sZone];
+ }
+
+ public function render() {
+ $this->execute();
+ $aRenderedBlocks = $this->renderBlocks();
+ return implode("", $aRenderedBlocks);
+ }
+
+ public function execute() {
+ reset($this->aSequence);
+ while(list($sKey,) = each($this->aSequence)) {
+ $this->aSequence[$sKey]["block"]->execute();
+ }
+ }
+
+ protected function renderBlocks() {
+ $aHtml = array();
+ reset($this->aSequence);
+ while(list($sKey,) = each($this->aSequence)) {
+ $this->aSequence[$sKey]["rendu"] = $this->aSequence[$sKey]["block"]->render();
+ }
+
+ $aHtml = array();
+ reset($this->aBlocks);
+ while(list($sZone,) = each($this->aBlocks)) {
+ $aHtml[$sZone] = implode("", $this->aBlocks[$sZone]);
+ }
+
+ reset($aHtml);
+ return $aHtml;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Render/Zone.php b/Core/Frameworks/Flake/Core/Render/Zone.php
new file mode 100644
index 0000000..a50a70e
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Render/Zone.php
@@ -0,0 +1,41 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Render;
+
+class Zone extends \Flake\Core\FLObject {
+ function __construct(&$oZonableObject, $sZone) {
+ $this->oZonableObject =& $oZonableObject;
+ $this->sZone = $sZone;
+ }
+
+ function addBlock(&$oBlock) {
+ $this->oZonableObject->addBlock(
+ $oBlock,
+ $this->sZone
+ );
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Requester.php b/Core/Frameworks/Flake/Core/Requester.php
new file mode 100644
index 0000000..f3c799a
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Requester.php
@@ -0,0 +1,65 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+abstract class Requester extends \Flake\Core\FLObject {
+ public function __construct($sModelClass) {
+ $this->sModelClass = $sModelClass;
+ }
+
+ protected function addClause($sField, $sValue) {
+ $this->addClauseEquals($sField, $sValue);
+ return $this;
+ }
+
+ public function limit($iStart, $iNumber = FALSE) {
+ if($iNumber !== FALSE) {
+ return $this->setLimitStart($iStart)->setLimitNumber($iLimitNumber);
+ }
+
+ return $this->setLimitStart($iStart);
+ }
+
+ public function orderBy($sOrderField, $sOrderDirection = "ASC") {
+ $this->sOrderField = $sOrderField;
+ $this->sOrderDirection = $sOrderDirection;
+ return $this;
+ }
+
+ public function setLimitStart($iLimitStart) {
+ $this->iLimitStart = $iLimitStart;
+ return $this;
+ }
+
+ public function setLimitNumber($iLimitNumber) {
+ $this->iLimitNumber = $iLimitNumber;
+ return $this;
+ }
+
+ public abstract function execute();
+ public abstract function count();
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Requester/Sql.php b/Core/Frameworks/Flake/Core/Requester/Sql.php
new file mode 100644
index 0000000..ced3615
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Requester/Sql.php
@@ -0,0 +1,208 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core\Requester;
+
+class Sql extends \Flake\Core\Requester {
+
+ protected $sDataTable = "";
+ protected $aClauses = array();
+ protected $sModelClass = "";
+ protected $sOrderField = "";
+ protected $sOrderDirection = "ASC";
+ protected $iLimitStart = FALSE;
+ protected $iLimitNumber = FALSE;
+ protected $bHasBeenExecuted = FALSE;
+
+ public function setDataTable($sDataTable) {
+ $this->sDataTable = $sDataTable;
+ return $this;
+ }
+
+ public function addClauseEquals($sField, $sValue) {
+ $sWrap = "{field}='{value}'";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ public function addClauseNotEquals($sField, $sValue) {
+ $sWrap = "{field}!='{value}'";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ public function addClauseLike($sField, $sValue) {
+ $sWrap = "{field} LIKE '%{value}%'";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ public function addClauseLikeBeginning($sField, $sValue) {
+ $sWrap = "{field} LIKE '{value}%'";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ public function addClauseLikeEnd($sField, $sValue) {
+ $sWrap = "{field} LIKE '%{value}'";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ public function addClauseNotLike($sField, $sValue) {
+ $sWrap = "{field} NOT LIKE '%{value}%'";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ public function addClauseNotLikeBeginning($sField, $sValue) {
+ $sWrap = "{field} NOT LIKE '{value}%'";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ public function addClauseNotLikeEnd($sField, $sValue) {
+ $sWrap = "{field} NOT LIKE '%{value}'";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ public function addClauseIn($sField, $sValue) {
+ $sWrap = "{field} IN ({value})";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ public function addClauseNotIn($sField, $sValue) {
+ $sWrap = "{field} NOT IN ({value})";
+ $this->addClauseWrapped($sField, $sValue, $sWrap);
+ return $this;
+ }
+
+ protected function addClauseWrapped($sField, $sValue, $sWrap) {
+ $sValue = $this->escapeSqlValue($sValue);
+ $sClause = str_replace(
+ array(
+ "{field}",
+ "{value}",
+ ),
+ array(
+ $sField,
+ $sValue
+ ),
+ $sWrap
+ );
+
+ $this->addClauseLiteral($sClause);
+ return $this;
+ }
+
+ public function addClauseLiteral($sClause) {
+ $this->aClauses[] = $sClause;
+ return $this;
+ }
+
+ protected function escapeSqlValue($sValue) {
+ return $GLOBALS["DB"]->quote(
+ $sValue,
+ $this->sDataTable
+ );
+ }
+
+ protected function &reify($aData) {
+ $sTemp = $this->sModelClass;
+ $res = new $sTemp($aData[$sTemp::getPrimaryKey()]);
+ return $res; # To address 'Notice: Only variable references should be returned by reference'
+ }
+
+ public function hasBeenExecuted() {
+ return $this->bHasBeenExecuted;
+ }
+
+ public function getQuery($sFields = "*") {
+ $sWhere = "1=1";
+ $sOrderBy = "";
+ $sLimit = "";
+
+ if(!empty($this->aClauses)) {
+ $sWhere = implode(" AND ", $this->aClauses);
+ }
+
+ if(trim($this->sOrderField) !== "") {
+ $sOrderBy = $this->sOrderField . " " . $this->sOrderDirection;
+ }
+
+ if($this->iLimitStart !== FALSE) {
+ if($this->iLimitNumber !== FALSE) {
+ $sLimit = $this->iLimitStart . ", " . $this->iLimitNumber;
+ } else {
+ $sLimit = $this->iLimitStart;
+ }
+ } elseif($this->iLimitNumber !== FALSE) {
+ $sLimit = "0, " . $this->iLimitNumber;
+ }
+
+ return $GLOBALS["DB"]->SELECTquery(
+ $sFields,
+ $this->sDataTable,
+ $sWhere,
+ "",
+ $sOrderBy,
+ $sLimit
+ );
+ }
+
+ public function getCountQuery() {
+ return $this->getQuery("count(*) as nbitems");
+ }
+
+ public function execute() {
+ $oCollection = new \Flake\Core\CollectionTyped($this->sModelClass);
+ $sSql = $this->getQuery();
+
+ $rSql = $GLOBALS["DB"]->query($sSql);
+ while(($aRs = $rSql->fetch()) !== FALSE) {
+ $oCollection->push(
+ $this->reify($aRs)
+ );
+ }
+
+ $this->bHasBeenExecuted = TRUE;
+
+ return $oCollection;
+ }
+
+ public function count() {
+ $sSql = $this->getCountQuery();
+
+ $rSql = $GLOBALS["DB"]->query($sSql);
+ if(($aRs = $rSql->fetch()) !== FALSE) {
+ return intval($aRs["nbitems"]);
+ }
+
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Route.php b/Core/Frameworks/Flake/Core/Route.php
new file mode 100644
index 0000000..7f95de2
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Route.php
@@ -0,0 +1,63 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+abstract class Route extends \Flake\Core\FLObject {
+
+ # should be abstract, but is not, due to PHP strict standard
+ public static function layout(\Flake\Core\Render\Container &$oRenderContainer) {
+
+ }
+ public static function parametersMap() {
+ return array();
+ }
+
+ # converts raw url params "a/b/c/d"=[a, b, c, d] in route params [a=>b, c=>d]
+
+ public static function getParams() {
+ $aRouteParams = array();
+
+ $aParametersMap = static::parametersMap(); # static to use method as defined in derived class
+ $aURLParams = $GLOBALS["ROUTER"]::getURLParams();
+
+ reset($aParametersMap);
+ foreach($aParametersMap as $sParam => $aMap) {
+ $sURLToken = $sParam;
+
+ if(array_key_exists("urltoken", $aMap)) {
+ $sURLToken = $aMap["urltoken"];
+ }
+
+ if(($iPos = array_search($sURLToken, $aURLParams)) !== FALSE) {
+ $aRouteParams[$sParam] = $aURLParams[($iPos + 1)]; # the value corresponding to this param is the next one in the URL
+ }
+ }
+
+ reset($aRouteParams);
+ return $aRouteParams;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/Template.php b/Core/Frameworks/Flake/Core/Template.php
new file mode 100644
index 0000000..94390be
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/Template.php
@@ -0,0 +1,51 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+class Template extends \Flake\Core\FLObject {
+
+ private $sAbsPath = "";
+ private $sHtml = "";
+
+ public function __construct($sAbsPath) {
+ $this->sAbsPath = $sAbsPath;
+ $this->sHtml = $this->getTemplateFile(
+ $this->sAbsPath
+ );
+ }
+
+ private function getTemplateFile($sAbsPath) {
+ return file_get_contents($sAbsPath);
+ }
+
+ function parse($aMarkers = array()) {
+ return \Flake\Util\Tools::parseTemplateCode(
+ $this->sHtml,
+ $aMarkers
+ );
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Core/View.php b/Core/Frameworks/Flake/Core/View.php
new file mode 100644
index 0000000..3dd544e
--- /dev/null
+++ b/Core/Frameworks/Flake/Core/View.php
@@ -0,0 +1,59 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Core;
+
+abstract class View extends \Flake\Core\FLObject {
+ protected $aData;
+
+ public function __construct() {
+ $this->aData = array();
+ }
+
+ public function setData($sName, $mData) {
+ $this->aData[$sName] = $mData;
+ }
+
+ public function getData() {
+ return $this->aData;
+ }
+
+ public function get($sWhat) {
+ if(array_key_exists($sWhat, $this->aData)) {
+ return $this->aData[$sWhat];
+ }
+
+ return FALSE;
+ }
+
+ public function render() {
+ $sTemplatePath = $this->templatesPath();
+ $oTemplate = new \Flake\Core\Template($this->templatesPath());
+ return $oTemplate->parse($this->getData());
+ }
+
+ public abstract function templatesPath();
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Framework.php b/Core/Frameworks/Flake/Framework.php
new file mode 100644
index 0000000..8fe87df
--- /dev/null
+++ b/Core/Frameworks/Flake/Framework.php
@@ -0,0 +1,275 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake;
+require_once(PROJECT_PATH_ROOT . "Core/Frameworks/Flake/Core/Framework.php"); # Manual require as Classloader not included yet
+
+class Framework extends \Flake\Core\Framework {
+
+ public static function rmBeginSlash($sString) {
+ if(substr($sString, 0, 1) === "/") {
+ $sString = substr($sString, 1);
+ }
+
+ return $sString;
+ }
+
+ public static function rmEndSlash($sString) {
+ if(substr($sString, -1) === "/") {
+ $sString = substr($sString, 0, -1);
+ }
+
+ return $sString;
+ }
+
+ public static function appendSlash($sString) {
+ if(substr($sString, -1) !== "/") {
+ $sString .= "/";
+ }
+
+ return $sString;
+ }
+
+ public static function prependSlash($sString) {
+ if(substr($sString, 0, 1) !== "/") {
+ $sString = "/" . $sString;
+ }
+
+ return $sString;
+ }
+
+ public static function bootstrap() {
+
+ # Asserting PHP 5.3.0+
+ if(version_compare(PHP_VERSION, '5.3.0', '<')) {
+ die('Flake Fatal Error: Flake requires PHP 5.3.0+ to run properly. Your version is: ' . PHP_VERSION . '.');
+ }
+
+ # Define safehash salt
+ define("PROJECT_SAFEHASH_SALT", "strong-secret-salt");
+
+ # Define absolute server path to Flake Framework
+ define("FLAKE_PATH_ROOT", PROJECT_PATH_ROOT . "Core/Frameworks/Flake/"); # ./
+
+ if(!defined('LF')) {
+ define('LF', chr(10));
+ }
+
+ if(!defined('CR')) {
+ define('CR', chr(13));
+ }
+
+ if(array_key_exists("SERVER_NAME", $_SERVER) && $_SERVER["SERVER_NAME"] === "mongoose") {
+ define("MONGOOSE_SERVER", TRUE);
+ } else {
+ define("MONGOOSE_SERVER", FALSE);
+ }
+
+ # Undo magic_quotes as this cannot be disabled by .htaccess on PHP ran as CGI
+ # Source: http://stackoverflow.com/questions/517008/how-to-turn-off-magic-quotes-on-shared-hosting
+ if(in_array(strtolower(ini_get('magic_quotes_gpc')), array('1', 'on'))) {
+ $_POST = array_map('stripslashes', $_POST);
+ $_GET = array_map('stripslashes', $_GET);
+ $_COOKIE = array_map('stripslashes', $_COOKIE);
+ }
+
+ # Fixing some CGI environments, that prefix HTTP_AUTHORIZATION (forwarded in .htaccess) with "REDIRECT_"
+ if(array_key_exists("REDIRECT_HTTP_AUTHORIZATION", $_SERVER)) {
+ $_SERVER["HTTP_AUTHORIZATION"] = $_SERVER["REDIRECT_HTTP_AUTHORIZATION"];
+ }
+
+ #################################################################################################
+
+ # determine Flake install root path
+ # not using realpath here to avoid symlinks resolution
+
+ define("PROJECT_PATH_CORE", PROJECT_PATH_ROOT . "Core/");
+ define("PROJECT_PATH_CORERESOURCES", PROJECT_PATH_CORE . "Resources/");
+ define("PROJECT_PATH_SPECIFIC", PROJECT_PATH_ROOT . "Specific/");
+ define("PROJECT_PATH_FRAMEWORKS", PROJECT_PATH_CORE . "Frameworks/");
+ define("PROJECT_PATH_WWWROOT", PROJECT_PATH_CORE . "WWWRoot/");
+
+ # Activate Flake class loader
+ require_once(FLAKE_PATH_ROOT . 'Core/ClassLoader.php');
+ \Flake\Core\ClassLoader::register();
+
+ require_once(PROJECT_PATH_CORE . "Distrib.php");
+
+ if(PROJECT_PACKAGE === "regular") {
+ define("PROJECT_PATH_DOCUMENTROOT", PROJECT_PATH_ROOT . "html/");
+ } elseif(PROJECT_PACKAGE === "flat") {
+ define("PROJECT_PATH_DOCUMENTROOT", PROJECT_PATH_ROOT);
+ } else {
+ throw new \Exception("Unrecognized PROJECT_PACKAGE value.");
+ }
+
+ # Determine PROJECT_URI
+ $sScript = substr($_SERVER["SCRIPT_FILENAME"], strlen($_SERVER["DOCUMENT_ROOT"]));
+ $sDirName = str_replace("\\", "/", dirname($sScript)); # fix windows backslashes
+
+ if($sDirName !== ".") {
+ $sDirName = self::appendSlash($sDirName);
+ } else {
+ $sDirName = "/";
+ }
+
+ $sBaseUrl = self::rmBeginSlash(self::appendSlash(substr($sDirName, 0, -1 * strlen(PROJECT_CONTEXT_BASEURI))));
+ define("PROJECT_BASEURI", self::prependSlash($sBaseUrl)); # SabreDAV needs a "/" at the beginning of BASEURL
+
+ $sProtocol = \Flake\Util\Tools::getCurrentProtocol();
+ define("PROJECT_URI", $sProtocol . "://" . self::appendSlash($_SERVER["HTTP_HOST"]) . $sBaseUrl);
+ unset($sScript); unset($sDirName); unset($sBaseUrl); unset($sProtocol);
+
+ #################################################################################################
+
+ require_once(FLAKE_PATH_ROOT . 'Util/Twig/lib/Twig/Autoloader.php');
+ \Twig_Autoloader::register();
+
+ # Include Flake Framework config
+ require_once(FLAKE_PATH_ROOT . "config.php");
+
+ # Determine Router class
+ $GLOBALS["ROUTER"] = \Flake\Util\Tools::router();
+
+ if(!\Flake\Util\Tools::isCliPhp()) {
+ ini_set("html_errors", TRUE);
+ session_start();
+ }
+
+ setlocale(LC_ALL, FLAKE_LOCALE);
+ date_default_timezone_set(FLAKE_TIMEZONE);
+
+ $GLOBALS["TEMPLATESTACK"] = array();
+
+ $aUrlInfo = parse_url(PROJECT_URI);
+ define("FLAKE_DOMAIN", $_SERVER["HTTP_HOST"]);
+ define("FLAKE_URIPATH", \Flake\Util\Tools::stripBeginSlash($aUrlInfo["path"]));
+ unset($aUrlInfo);
+
+
+ # Include Project config
+ # NOTE: DB initialization and App config files inclusion
+ # do not break execution if not properly executed, as
+ # these errors will have to be caught later in the process
+ # notably by the App install tool, if available; breaking right now
+ # would forbid such install tool forwarding, for instance
+
+ $sConfigPath = PROJECT_PATH_SPECIFIC . "config.php";
+ $sConfigSystemPath = PROJECT_PATH_SPECIFIC . "config.system.php";
+
+ if(file_exists($sConfigPath)) {
+ require_once($sConfigPath);
+ }
+
+ if(file_exists($sConfigSystemPath)) {
+ require_once($sConfigSystemPath);
+ }
+
+ self::initDb();
+ }
+
+ protected static function initDb() {
+
+ if(defined("PROJECT_DB_MYSQL") && PROJECT_DB_MYSQL === TRUE) {
+ self::initDbMysql();
+ } else {
+ self::initDbSqlite();
+ }
+ }
+
+ protected static function initDbSqlite() {
+ # Asserting DB filepath is set
+ if(!defined("PROJECT_SQLITE_FILE")) {
+ return FALSE;
+ }
+
+ # Asserting DB file exists
+ if(!file_exists(PROJECT_SQLITE_FILE)) {
+ die("
DB file does not exist. To create it, please copy 'Core/Resources/Db/SQLite/db.sqlite' to '" . PROJECT_SQLITE_FILE . "'
");
+ }
+
+ # Asserting DB file is readable
+ if(!is_readable(PROJECT_SQLITE_FILE)) {
+ die("
DB file is not readable. Please give read permissions on file '" . PROJECT_SQLITE_FILE . "'
");
+ }
+
+ # Asserting DB file is writable
+ if(!is_writable(PROJECT_SQLITE_FILE)) {
+ die("
DB file is not writable. Please give write permissions on file '" . PROJECT_SQLITE_FILE . "'
");
+ }
+
+ # Asserting DB directory is writable
+ if(!is_writable(dirname(PROJECT_SQLITE_FILE))) {
+ die("
The FOLDER containing the DB file is not writable, and it has to. Please give write permissions on folder '" . dirname(PROJECT_SQLITE_FILE) . "'
The constant PROJECT_DB_MYSQL_PASSWORD, containing the MySQL database password, is not set. You should set it in Specific/config.system.php
");
+ }
+
+ try {
+ $GLOBALS["DB"] = new \Flake\Core\Database\Mysql(
+ PROJECT_DB_MYSQL_HOST,
+ PROJECT_DB_MYSQL_DBNAME,
+ PROJECT_DB_MYSQL_USERNAME,
+ PROJECT_DB_MYSQL_PASSWORD
+ );
+
+ # We now setup the connexion to use UTF8
+ $GLOBALS["DB"]->query("SET NAMES UTF8");
+ } catch(\Exception $e) {
+ #die("
Baïkal was not able to establish a connexion to the configured MySQL database (as configured in Specific/config.system.php).
");
+ }
+
+ return TRUE;
+ }
+
+ public static function isDBInitialized() {
+ return isset($GLOBALS["DB"]) && \Flake\Util\Tools::is_a($GLOBALS["DB"], "\Flake\Core\Database");
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Model/IUser.php b/Core/Frameworks/Flake/Model/IUser.php
new file mode 100644
index 0000000..cceffb1
--- /dev/null
+++ b/Core/Frameworks/Flake/Model/IUser.php
@@ -0,0 +1,32 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Model;
+
+interface IUser {
+ public function isAdmin();
+ public function getDisplayName();
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Model/User/Admin.php b/Core/Frameworks/Flake/Model/User/Admin.php
new file mode 100644
index 0000000..89ab052
--- /dev/null
+++ b/Core/Frameworks/Flake/Model/User/Admin.php
@@ -0,0 +1,46 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Model\User;
+
+class Admin extends \Flake\Core\Model\NoDb {
+
+ public function isAdmin() {
+ return TRUE;
+ }
+
+ public function getDisplayName() {
+ return "Admin";
+ }
+
+ public function persist() {
+
+ }
+
+ public function destroy() {
+
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Model/User/Customer.php b/Core/Frameworks/Flake/Model/User/Customer.php
new file mode 100644
index 0000000..c1a2c70
--- /dev/null
+++ b/Core/Frameworks/Flake/Model/User/Customer.php
@@ -0,0 +1,89 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Model\User;
+
+class Customer extends \Flake\Core\Model\Db implements \Flake\Model\IUser {
+
+ const DATATABLE = "user";
+ const PRIMARYKEY = "uid";
+ const LABELFIELD = "username";
+
+ protected $aData = array(
+ "username" => "",
+ "firstname" => "",
+ "lastname" => "",
+ "email" => "",
+ "password" => "",
+ "salt" => "",
+ "crdate" => 0,
+ "enabled" => 0,
+ );
+
+ public function isAdmin() {
+ return FALSE;
+ }
+
+ public function getDisplayName() {
+ return $this->get("firstname") . " " . $this->get("lastname");
+ }
+
+ public function persist() {
+
+ }
+
+ public function destroy() {
+
+ }
+
+ public static function hashPassword($sClearPassword, $sSalt) {
+ return sha1(APP_ENCRYPTION_KEY . ":" . $sClearPassword . ":" . $sSalt);
+ }
+
+ public static function fetchByCredentials($sUsername, $sClearPassword) {
+ # Algorithm:
+ # 1- find the user by username
+ # 2- hash the given password using the salt for this user
+ # 3- compare hashes
+
+ $oUser = self::getBaseRequester()
+ ->addClauseEquals("username", $sUsername)
+ ->addClauseEquals("enabled", 1)
+ ->execute()
+ ->first();
+
+ if(is_null($oUser)) {
+ return FALSE;
+ }
+
+ if($oUser->get("password") !== self::hashPassword($sClearPassword, $oUser->get("salt"))) {
+ return FALSE;
+ }
+
+ return $oUser;
+ }
+
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Util/Frameworks.php b/Core/Frameworks/Flake/Util/Frameworks.php
new file mode 100644
index 0000000..4b18adc
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Frameworks.php
@@ -0,0 +1,55 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Util;
+
+class Frameworks extends \Flake\Core\FLObject {
+ private function __construct() { # private constructor to force static class
+ }
+
+ public function isAFramework($sName) {
+ $sName = trim(\Flake\Util\Tools::trimSlashes($sName));
+ if($sName === "" || $sName === "." || $sName === "..") {
+ return FALSE;
+ }
+
+ $sFrameworkPath = PROJECT_PATH_FRAMEWORKS . $sName;
+ return file_exists($sFrameworkPath) && is_dir($sFrameworkPath);
+ }
+
+ public static function enabled($sFramework) {
+ return FALSE;
+ }
+
+ # TODO: Create a 'Framework' Model
+ public function getPath($sName) {
+ if(self::isAFramework($sName)) {
+ throw new \Flake\Core\Exception(htmlspecialchars($$sName) . " is not a framework.", $sName);
+ }
+
+ return \Flake\Util\Tools::appendSlash(PROJECT_PATH_FRAMEWORKS . $sName);
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Util/Profiler.php b/Core/Frameworks/Flake/Util/Profiler.php
new file mode 100644
index 0000000..2c5b6cb
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Profiler.php
@@ -0,0 +1,66 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Util;
+
+class Profiler extends \Flake\Core\FLObject {
+
+ protected static $TUSAGE;
+ protected static $RUSAGE;
+
+ protected function __construct() {
+ # Static class
+ }
+
+ public static function start() {
+ $dat = getrusage();
+ self::$TUSAGE = microtime(TRUE);
+ self::$RUSAGE = $dat["ru_utime.tv_sec"] * 1e6 + $dat["ru_utime.tv_usec"];
+ }
+
+ public static function cpuUsage() {
+ $dat = getrusage();
+ $tv_usec = (($dat["ru_utime.tv_sec"] * 1e6) + $dat["ru_utime.tv_usec"]) - self::$RUSAGE;
+ $time = (microtime(true) - self::$TUSAGE) * 1e6;
+
+ // cpu per request
+ if($time > 0) {
+ $cpu = number_format(($tv_usec / $time) * 100, 2);
+ } else {
+ $cpu = '0.00';
+ }
+
+ return $cpu;
+ }
+
+ public static function cpuTime() {
+ $dat = getrusage();
+ $tv_usec = (($dat["ru_utime.tv_sec"] * 1e6) + $dat["ru_utime.tv_usec"]) - self::$RUSAGE;
+ $time = (microtime(true) - self::$TUSAGE) * 1e6;
+ $cpuusage = ($tv_usec / $time);
+ return round(($time / 1000) * $cpuusage);
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Util/Router.php b/Core/Frameworks/Flake/Util/Router.php
new file mode 100644
index 0000000..ed8aa28
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Router.php
@@ -0,0 +1,144 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Util;
+
+abstract class Router extends \Flake\Core\FLObject {
+
+ static $sURIPath = "";
+
+ /* ----------------------- COMMON METHODS ------------------------------*/
+
+ private function __construct() {
+ # private constructor for static class
+ }
+
+ public static function getRoutes() {
+ reset($GLOBALS["ROUTES"]);
+ return $GLOBALS["ROUTES"];
+ }
+
+ public static function getControllerForRoute($sRoute) {
+ return str_replace("\\Route", "\\Controller", self::getRouteClassForRoute($sRoute));
+ }
+
+ public static function getRouteClassForRoute($sRoute) {
+ $aRoutes = $GLOBALS["ROUTER"]::getRoutes();
+ return $aRoutes[$sRoute];
+ }
+
+ public static function getRouteForController($sController) {
+
+ if($sController{0} !== "\\") {
+ $sController = "\\" . $sController;
+ }
+
+ $aRoutes = $GLOBALS["ROUTER"]::getRoutes();
+
+ reset($aRoutes);
+ while(list($sRoute,) = each($aRoutes)) {
+ if(str_replace("\\Route", "\\Controller", $aRoutes[$sRoute]) === $sController) {
+ return $sRoute;
+ }
+ }
+
+ return FALSE;
+ }
+
+ public static function route(\Flake\Core\Render\Container &$oRenderContainer) {
+ $sRouteClass = $GLOBALS["ROUTER"]::getRouteClassForRoute(
+ $GLOBALS["ROUTER"]::getCurrentRoute()
+ );
+
+ $sRouteClass::layout($oRenderContainer);
+ }
+
+ public static function buildRouteForController($sController, $aParams = array()) {
+
+ #$aParams = func_get_args();
+ #array_shift($aParams); # stripping $sController
+ if(($sRouteForController = $GLOBALS["ROUTER"]::getRouteForController($sController)) === FALSE) {
+ throw new \Exception("buildRouteForController '" . htmlspecialchars($sController) . "': no route available.");
+ }
+
+ $aRewrittenParams = array();
+
+ $sRouteClass = self::getRouteClassForRoute($sRouteForController);
+ $aParametersMap = $sRouteClass::parametersMap();
+ reset($aParametersMap);
+ foreach($aParametersMap as $sParam => $aMap) {
+ if(!array_key_exists($sParam, $aParams)) {
+ # if parameter not in parameters map, skip !
+ continue;
+ }
+
+ $sUrlToken = $sParam;
+ if(array_key_exists("urltoken", $aMap)) {
+ $sUrlToken = $aMap["urltoken"];
+ }
+
+ $aRewrittenParams[$sUrlToken] = $aParams[$sParam];
+ }
+
+ #array_unshift($aParams, $sRouteForController); # Injecting route as first param
+ #return call_user_func_array($GLOBALS["ROUTER"] . "::buildRoute", $aParams);
+ return $GLOBALS["ROUTER"]::buildRoute($sRouteForController, $aRewrittenParams);
+ }
+
+ public static function buildCurrentRoute(/*[$sParam, $sParam2, ...]*/) {
+ $aParams = func_get_args();
+ $sCurrentRoute = $GLOBALS["ROUTER"]::getCurrentRoute();
+
+ array_unshift($aParams, $sCurrentRoute); # Injecting route as first param
+ return call_user_func_array($GLOBALS["ROUTER"] . "::buildRoute", $aParams);
+ }
+
+ public static function setURIPath($sURIPath) {
+ static::$sURIPath = $sURIPath;
+ }
+
+ public static function getUriPath() {
+ return FLAKE_URIPATH . static::$sURIPath;
+ }
+
+ /* ----------------------- CHANGING METHODS ----------------------------*/
+
+ # this method is likely to change with every Router implementation
+ # should be abstract, but is not, because of PHP's strict standards
+ public static function buildRoute($sRoute, $aParams/* [, $sParam, $sParam2, ...] */) {
+
+ }
+
+ # should be abstract, but is not, because of PHP's strict standards
+ public static function getCurrentRoute() {
+
+ }
+
+ # should be abstract, but is not, because of PHP's strict standards
+ public static function getURLParams() {
+
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Util/Router/QuestionMarkRewrite.php b/Core/Frameworks/Flake/Util/Router/QuestionMarkRewrite.php
new file mode 100644
index 0000000..6c3c343
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Router/QuestionMarkRewrite.php
@@ -0,0 +1,153 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Util\Router;
+
+class QuestionMarkRewrite extends \Flake\Util\Router {
+
+ public static function getCurrentRoute() {
+
+ $aMatches = array();
+ $sRouteTokens = implode("/", self::getRouteTokens());
+
+ $aRoutes = self::getRoutes();
+ reset($aRoutes);
+ foreach($aRoutes as $sDefinedRoute => $sDefinedController) {
+
+ if(strpos($sRouteTokens, $sDefinedRoute) === 0) {
+
+ # found a match
+ $iSlashCount = substr_count($sDefinedRoute, "/");
+ if(!array_key_exists($iSlashCount, $aMatches)) {
+ $aMatches[$iSlashCount] = array();
+ }
+
+ $aMatches[$iSlashCount][] = $sDefinedRoute;
+ }
+ }
+
+ if(empty($aMatches)) {
+ return "default";
+ }
+
+ $aBestMatches = array_pop($aMatches); // obtains the deepest matching route (higher number of slashes)
+ return array_shift($aBestMatches); // first route amongst best matches
+ }
+
+ public static function buildRoute($sRoute, $aParams = array()/* [, $sParam, $sParam2, ...] */) {
+# $aParams = func_get_args();
+# array_shift($aParams); # Stripping $sRoute
+
+# $sParams = implode("/", $aParams);
+
+ $aParamsSegments = array();
+ reset($aParams);
+ foreach($aParams as $sParamName => $sParamValue) {
+ $aParamsSegments[] = rawurlencode($sParamName) . "/" . rawurlencode($sParamValue);
+ }
+
+ $sParams = implode("/", $aParamsSegments);
+
+ if(trim($sParams) !== "") {
+ $sParams .= "/";
+ }
+
+ if($sRoute === "default" && empty($aParams)) {
+ $sUrl = "/";
+ } else {
+ $sUrl = "/" . $sRoute . "/" . $sParams;
+ }
+
+ $sUriPath = self::getUriPath();
+ if($sUriPath === "" || $sUriPath === "/") {
+ if($sUrl !== "/") {
+ $sUrl = "?" . $sUrl;
+ }
+ } else {
+ if($sUrl !== "/") {
+ $sUrl = "/" . self::getUriPath() . "?" . $sUrl;
+ } else {
+ $sUrl = "/" . self::getUriPath();
+ }
+ }
+
+ return $sUrl;
+ }
+
+ protected static function getUrlTokens() {
+ $sQuery = "";
+ $sUrl = \Flake\Util\Tools::stripBeginSlash(\Flake\Util\Tools::getCurrentUrl());
+ $aUrlParts = parse_url($sUrl);
+
+ $aParams = array();
+ if(array_key_exists("query", $aUrlParts)) {
+ $aParams = explode("/", "?" . $aUrlParts["query"]);
+ }
+
+ return $aParams;
+ }
+
+ protected static function getRouteTokens() {
+ $aUrlTokens = self::getUrlTokens();
+
+ if(!empty($aUrlTokens)) {
+ return array_slice($aUrlTokens, 1);
+ }
+
+ return array();
+ }
+
+ public static function getURLParams() {
+ $aTokens = self::getRouteTokens();
+
+ # stripping route
+ if(!empty($aTokens)) {
+
+ $sRouteUrl = implode("/", $aTokens);
+ $sCurrentRoute = $GLOBALS["ROUTER"]::getCurrentRoute();
+
+ if(strpos($sRouteUrl, $sCurrentRoute) === FALSE) {
+ throw new \Exception("Flake\Util\Router\QuestionMarkRewrite::getURLParams(): unrecognized route.");
+ }
+
+ $sParams = \Flake\Util\Tools::trimSlashes(substr($sRouteUrl, strlen($sCurrentRoute)));
+
+ $aParams = array();
+ if($sParams !== "") {
+ $aParams = explode("/", $sParams);
+ }
+
+ reset($aParams);
+ foreach($aParams as $sParam => $sValue) {
+ $aParams[$sParam] = rawurldecode($sValue);
+ }
+
+ return $aParams;
+ }
+
+ return array();
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Util/Tools.php b/Core/Frameworks/Flake/Util/Tools.php
new file mode 100644
index 0000000..81e4a1e
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Tools.php
@@ -0,0 +1,764 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Flake\Util;
+
+class Tools extends \Flake\Core\FLObject {
+
+ private function __construct() { # private constructor to force static class
+ }
+
+ public static function getCurrentUrl() {
+ if(MONGOOSE_SERVER) {
+ $sUrl = $GLOBALS["_SERVER"]["REQUEST_URI"];
+ if(array_key_exists("QUERY_STRING", $GLOBALS["_SERVER"]) && trim($GLOBALS["_SERVER"]["QUERY_STRING"]) !== "") {
+ $sUrl .= "?" . $GLOBALS["_SERVER"]["QUERY_STRING"];
+ }
+ } else {
+ $sUrl = $GLOBALS["_SERVER"]["REQUEST_URI"]; # Would be REDIRECT_URL for ServerRewrite
+ }
+
+ return $sUrl;
+ }
+
+ public static function getCurrentProtocol() {
+ if((!empty($GLOBALS["_SERVER"]["HTTPS"]) && $GLOBALS["_SERVER"]['HTTPS'] !== 'off') || intval($_SERVER['SERVER_PORT']) === 443) {
+ return "https";
+ }
+
+ return "http";
+ }
+
+ public static function deCamelCase($sString, $sGlue=" ") {
+ $sSep = md5(rand());
+ $sRes = preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $sSep . '$0', $sString));
+ if($sGlue !== "" && preg_match('/^[[:upper:]].*/', $sRes)) {
+ $sRes = $sSep . $sRes;
+ }
+
+ return str_replace($sSep, $sGlue, $sRes);
+ }
+
+ public static function serverToRelativeWebPath($sAbsPath) {
+ return "/" . str_replace(PROJECT_PATH_WWWROOT, "", $sAbsPath);
+ }
+
+ public static function view_array($array_in) {
+ if (is_array($array_in)) {
+ $result='
';
+ if (!count($array_in)) {$result.= '
'.htmlspecialchars("EMPTY!").'
';}
+ while (list($key,$val)=each($array_in)) {
+ $result.= '
';
+ }
+ }
+
+ public static function debug_trail() {
+ $trail = debug_backtrace();
+ $trail = array_reverse($trail);
+ array_pop($trail);
+
+ $path = array();
+ foreach($trail as $dat) {
+ $path[] = $dat['class'].$dat['type'].$dat['function'];
+ }
+
+ return implode(' // ',$path);
+ }
+
+ public static function POST($sVar = FALSE) {
+ if($sVar !== FALSE) {
+ $aData = \Flake\Util\Tools::POST();
+ if(array_key_exists($sVar, $aData)) {
+ return $aData[$sVar];
+ }
+
+ return "";
+ }
+
+ return is_array($GLOBALS["_POST"]) ? $GLOBALS["_POST"] : array();
+ }
+
+ public static function GET($sVar = FALSE) {
+ if($sVar !== FALSE) {
+ $aData = \Flake\Util\Tools::GET();
+ if(array_key_exists($sVar, $aData)) {
+ return $aData[$sVar];
+ }
+
+ return "";
+ }
+
+ return is_array($GLOBALS["_GET"]) ? $GLOBALS["_GET"] : array();
+ }
+
+ public static function GP($sVar = FALSE) {
+ if($sVar !== FALSE) {
+ $aData = \Flake\Util\Tools::GP();
+ if(array_key_exists($sVar, $aData)) {
+ return $aData[$sVar];
+ }
+
+ return "";
+ }
+
+ return array_merge(
+ \Flake\Util\Tools::GET(),
+ \Flake\Util\Tools::POST()
+ );
+ }
+
+ public static function safelock($sString) {
+ return substr(md5(PROJECT_SAFEHASH_SALT . ":" . $sString), 0, 5);
+ }
+
+ public static function redirect($sUrl) {
+ header("Location: " . $sUrl);
+ exit(0);
+ }
+
+ public static function redirectUsingMeta($sUrl) {
+ $sDoc = "";
+ echo $sDoc;
+ exit(0);
+ }
+
+ public static function refreshPage() {
+ header("Location: " . \Flake\Util\Tools::getCurrentUrl());
+ exit(0);
+ }
+
+ public static function validEmail($sEmail) {
+ return (filter_var($sEmail, FILTER_VALIDATE_EMAIL) !== FALSE);
+ }
+
+ public static function filterFormInput($sInput) {
+ return strip_tags($sInput);
+ }
+
+ public static function getHumanDate($iStamp) {
+ return ucwords(strftime("%A, %d %B %Y", $iStamp));
+ }
+
+ public static function getHumanTime($iStamp) {
+ return strftime("%Hh%M", $iStamp);
+ }
+
+ public static function trimExplode($string, $delim=",", $removeEmptyValues = false, $limit = 0) {
+ $explodedValues = explode($delim, $string);
+
+ $result = array_map('trim', $explodedValues);
+
+ if ($removeEmptyValues) {
+ $temp = array();
+ foreach($result as $value) {
+ if ($value !== '') {
+ $temp[] = $value;
+ }
+ }
+ $result = $temp;
+ }
+
+ if ($limit != 0) {
+ if ($limit < 0) {
+ $result = array_slice($result, 0, $limit);
+ } elseif (count($result) > $limit) {
+ $lastElements = array_slice($result, $limit - 1);
+ $result = array_slice($result, 0, $limit - 1);
+ $result[] = implode($delim, $lastElements);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Taken from TYPO3
+ * Returns true if the first part of $str matches the string $partStr
+ *
+ * @param string Full string to check
+ * @param string Reference string which must be found as the "first part" of the full string
+ * @return boolean True if $partStr was found to be equal to the first part of $str
+ */
+ public static function isFirstPartOfStr($str,$partStr) {
+ // Returns true, if the first part of a $str equals $partStr and $partStr is not ''
+ $psLen = strlen($partStr);
+ if ($psLen) {
+ return substr($str,0,$psLen)==(string)$partStr;
+ } else return false;
+ }
+
+ /**
+ * Binary-reads a file
+ *
+ * @param string $sPath: absolute server path to file
+ * @return string file contents
+ */
+ public static function file_readBin($sPath) {
+ $sData = "";
+ $rFile = fopen($sPath, "rb");
+ while(!feof($rFile)) {
+ $sData .= fread($rFile, 1024);
+ }
+ fclose($rFile);
+
+ return $sData;
+ }
+
+ /**
+ * Binary-writes a file
+ *
+ * @param string $sPath: absolute server path to file
+ * @param string $sData: file contents
+ * @param boolean $bUTF8: add UTF8-BOM or not ?
+ * @return void
+ */
+ public static function file_writeBin($sPath, $sData) {
+ $rFile=fopen($sPath, "wb");
+ fputs($rFile, $sData);
+ fclose($rFile);
+ }
+
+ public static function sendHtmlMail($sToAddress, $sSubject, $sBody, $sFromName, $sFromAddress, $sReplyToName, $sReplyToAddress) {
+
+ $sMessage = <<
+
+
+ Email
+
+
+ {$sBody}
+
+
+TEST;
+
+ $sHeaders = "From: " . $sFromName . "<" . $sFromAddress . ">" . "\r\n";
+ $sHeaders .= "Reply-To: " . $sReplyToName . "<" . $sReplyToAddress . ">" . "\r\n";
+ $sHeaders .= "Bcc: " . $sReplyToName . "<" . $sReplyToAddress . ">" . "\r\n";
+ $sHeaders .= "Content-Type: text/html" . "\r\n";
+
+ mail($sToAddress, $sSubject, $sMessage, $sHeaders);
+ }
+
+ public static function shortMD5($sValue) {
+ return strtolower(substr(md5($sValue), 0, 5));
+ }
+
+ public static function overrideFirstWithSecond($sFirst, $sSecond) {
+ if(trim($sSecond) !== "") {
+ return $sSecond;
+ }
+
+ return "" . $sFirst;
+ }
+
+ public static function parseTemplateCode($sCode, $aMarkers) {
+
+ $loader = new \Twig_Loader_String();
+ $twig = new \Twig_Environment($loader);
+
+ return $twig->render($sCode, $aMarkers);
+ }
+
+
+ public static function is_a($object, $class) {
+ if(is_object($object)) return $object instanceof $class;
+ if(is_string($object)){
+ if(is_object($class)) $class=get_class($class);
+
+ if(class_exists($class, TRUE)) { # TRUE to autoload class
+ return @is_subclass_of($object, $class) || $object==$class;
+ }
+
+ if(interface_exists($class)) {
+ $reflect = new \ReflectionClass($object);
+ return $reflect->implementsInterface($class);
+ }
+
+ }
+ return false;
+ }
+
+ public static function HTTPStatus($iCode, $sMessage) {
+ header("HTTP/1.1 404 Not Found");
+ header("Status: 404 Not Found");
+ die("
HTTP Status " . $iCode . " : " . $sMessage . "
");
+ }
+
+ public static function number2Rank($a) {
+ $a = intval($a);
+
+ if ($a === 1) {
+ return "premier";
+ } elseif($a === 2) {
+ return "second";
+ }
+
+ $sNumber = self::number2Human($a);
+
+ $sLastLetter = substr($sNumber, -1, 1);
+ if($sLastLetter === "e") {
+ $sNumber = substr($sNumber, 0, -1);
+ } elseif($sLastLetter === "q") {
+ $sNumber = $sNumber . "u";
+ } elseif($sLastLetter === "f") {
+ $sNumber = substr($sNumber, 0, -1) . "v";
+ }
+
+ return $sNumber . "ième";
+ }
+
+ public static function number2Human($a) {
+ $temp = explode('.',$a);
+ if (isset($temp[1]) && $temp[1]!='') {
+ return self::number2Human($temp[0]).' virgule '.self::number2Human($temp[1]) ;
+ }
+
+ if ($a<0) return 'moins '.self::number2Human(-$a);
+
+ if ($a<17) {
+ switch ($a) {
+ case 0: return 'zero';
+ case 1: return 'un';
+ case 2: return 'deux';
+ case 3: return 'trois';
+ case 4: return 'quatre';
+ case 5: return 'cinq';
+ case 6: return 'six';
+ case 7: return 'sept';
+ case 8: return 'huit';
+ case 9: return 'neuf';
+ case 10: return 'dix';
+ case 11: return 'onze';
+ case 12: return 'douze';
+ case 13: return 'treize';
+ case 14: return 'quatorze';
+ case 15: return 'quinze';
+ case 16: return 'seize';
+ }
+ } else if ($a<20) {
+ return 'dix-' . self::number2Human($a-10);
+ } else if ($a<100) {
+ if ($a%10==0) {
+ switch($a) {
+ case 20: return 'vingt';
+ case 30: return 'trente';
+ case 40: return 'quarante';
+ case 50: return 'cinquante';
+ case 60: return 'soixante';
+ case 70: return 'soixante-dix';
+ case 80: return 'quatre-vingt';
+ case 90: return 'quatre-vingt-dix';
+ }
+ } elseif(substr($a, -1) == 1) {
+ if( ((int)($a/10)*10)<70 ) {
+ return self::number2Human((int)($a/10)*10).'-et-un';
+ } elseif ($a==71) {
+ return 'soixante-et-onze';
+ } elseif ($a==81) {
+ return 'quatre-vingt-un';
+ } elseif ($a==91) {
+ return 'quatre-vingt-onze';
+ }
+ } elseif ($a<70) {
+ return self::number2Human($a-$a%10).'-'.self::number2Human($a%10);
+ } elseif ($a<80) {
+ return self::number2Human(60).'-'.self::number2Human($a%20);
+ } else {
+ return self::number2Human(80).'-'.self::number2Human($a%20);
+ }
+ } else if ($a==100) {
+ return 'cent';
+ } else if ($a<200) {
+ return self::number2Human(100).' '.self::number2Human($a%100);
+ } else if ($a<1000) {
+ return self::number2Human((int)($a/100)).' '.self::number2Human(100).' '.self::number2Human($a%100);
+ } else if ($a==1000) {
+ return 'mille';
+ } else if ($a<2000) {
+ return self::number2Human(1000).' '.self::number2Human($a%1000).' ';
+ } else if ($a<1000000) {
+ return self::number2Human((int)($a/1000)).' '.self::number2Human(1000).' '.self::number2Human($a%1000);
+ }
+ }
+
+ public static function stringToUrlToken($sString) {
+
+ # Taken from TYPO3 extension realurl
+
+ $space = "-";
+ $sString = strtr($sString, ' -+_\'', $space . $space . $space . $space . $space); // convert spaces
+
+ if(function_exists("iconv")) {
+ $sString = iconv('UTF-8', 'ASCII//TRANSLIT', $sString);
+ }
+
+ $sString = strtolower($sString);
+
+ $sString = preg_replace('/[^a-zA-Z0-9\\' . $space . ']/', '', $sString);
+ $sString = preg_replace('/\\' . $space . '{2,}/', $space, $sString); // Convert multiple 'spaces' to a single one
+ $sString = trim($sString, $space);
+
+ return $sString;
+ }
+
+ public static function isCliPhp() {
+ return strtolower(php_sapi_name()) === "cli";
+ }
+
+ public static function getIP() {
+
+ $alt_ip = $_SERVER['REMOTE_ADDR'];
+
+ if(isset($_SERVER['HTTP_CLIENT_IP'])) {
+ $alt_ip = $_SERVER['HTTP_CLIENT_IP'];
+ } else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
+
+ // make sure we dont pick up an internal IP defined by RFC1918
+ foreach($matches[0] AS $ip) {
+ if (!preg_match('#^(10|172\.16|192\.168)\.#', $ip)) {
+ $alt_ip = $ip;
+ break;
+ }
+ }
+ } else if (isset($_SERVER['HTTP_FROM'])) {
+ $alt_ip = $_SERVER['HTTP_FROM'];
+ }
+
+ return $alt_ip;
+ }
+
+ public static function getUserAgent() {
+ return $_SERVER['HTTP_USER_AGENT'];
+ }
+
+ ###########
+ public static function appendSlash($sString) {
+ return self::appendString($sString, "/");
+ }
+
+ public static function prependSlash($sString) {
+ return self::prependString($sString, "/");
+ }
+
+ public static function stripBeginSlash($sString) {
+ return self::stripBeginString($sString, "/");
+ }
+
+ public static function stripEndSlash($sString) {
+ return self::stripEndString($sString, "/");
+ }
+
+ public static function trimSlashes($sString) {
+ return self::stripBeginSlash(self::stripEndSlash($sString));
+ }
+
+ ###########
+ public static function appendString($sString, $sAppend) {
+ if(substr($sString, -1 * strlen($sAppend)) !== $sAppend) {
+ $sString .= $sAppend;
+ }
+
+ return $sString;
+ }
+
+ public static function prependString($sString, $sAppend) {
+ if(substr($sString, 0, 1 * strlen($sAppend)) !== $sAppend) {
+ $sString = $sAppend . $sString;
+ }
+
+ return $sString;
+ }
+
+ public static function stripBeginString($sString, $sAppend) {
+ if(substr($sString, 0, 1 * strlen($sAppend)) === $sAppend) {
+ $sString = substr($sString, strlen($sAppend));
+ }
+
+ return $sString;
+ }
+
+ public static function stripEndString($sString, $sAppend) {
+ if(substr($sString, -1 * strlen($sAppend)) === $sAppend) {
+ $sString = substr($sString, 0, -1 * strlen($sAppend));
+ }
+
+ return $sString;
+ }
+
+ public static function trimStrings($sString, $sAppend) {
+ return self::stripBeginString(self::stripEndString($sString, $sAppend), $sAppend);
+ }
+
+ public static function stringEndsWith($sHaystack, $sNeedle) {
+ return substr($sHaystack, strlen($sNeedle) * -1) === $sNeedle;
+ }
+
+ ###########
+
+ public static function router() {
+ return "\Flake\Util\Router\QuestionMarkRewrite";
+ }
+
+ public static function arrayIsAssoc($aArray) {
+ if(!is_array($aArray)) {
+ throw new \Exception("\Flake\Util\Tools::arrayIsAssoc(): parameter has to be an array.");
+ }
+
+ # Taken from http://stackoverflow.com/questions/173400/php-arrays-a-good-way-to-check-if-an-array-is-associative-or-sequential#answer-4254008
+ # count() will return 0 if numeric, and > 0 if assoc, even partially
+ return (bool)count(array_filter(array_keys($aArray), 'is_string'));
+ }
+
+ public static function arrayIsSeq($aArray) {
+ return !self::arrayIsAssoc($aArray);
+ }
+
+ public static function echoAndCutClient($sMessage='') {
+ ignore_user_abort(TRUE);
+# set_time_limit(0);
+
+ header("Connection: close");
+ header("Content-Length: ".strlen($sMessage));
+ echo $sMessage;
+ echo str_repeat("\r\n", 10); // just to be sure
+ flush();
+ }
+
+ public static function milliseconds() {
+ return intval((microtime(TRUE) * 1000));
+ }
+
+ public static function stopWatch($sWhat) {
+# return;
+ $iStop = \Flake\Util\Tools::milliseconds();
+
+ $trail = debug_backtrace();
+ $aLastNode = $trail[0]; // l'appel qui nous intéresse
+ $sFile = basename($aLastNode["file"]);
+ $iLine = intval($aLastNode["line"]);
+
+ if(!array_key_exists("FLAKE_STOPWATCHES", $GLOBALS)) {
+ $GLOBALS["FLAKE_STOPWATCHES"] = array();
+ }
+
+ if(!array_key_exists($sWhat, $GLOBALS["FLAKE_STOPWATCHES"])) {
+ $GLOBALS["FLAKE_STOPWATCHES"][$sWhat] = $iStop;
+ } else {
+ $iTime = $iStop - $GLOBALS["FLAKE_STOPWATCHES"][$sWhat];
+ echo "
@" . $sFile . "+" . $iLine . ":" . $sWhat . ":" . $iTime . " ms";
+ flush();
+ }
+ }
+
+ # Taken from http://www.php.net/manual/en/function.gzdecode.php#82930
+ public static function gzdecode($data, &$filename='', &$error='', $maxlength=null) {
+ $len = strlen($data);
+ if ($len < 18 || strcmp(substr($data,0,2),"\x1f\x8b")) {
+ $error = "Not in GZIP format.";
+ return null; // Not GZIP format (See RFC 1952)
+ }
+ $method = ord(substr($data,2,1)); // Compression method
+ $flags = ord(substr($data,3,1)); // Flags
+ if ($flags & 31 != $flags) {
+ $error = "Reserved bits not allowed.";
+ return null;
+ }
+ // NOTE: $mtime may be negative (PHP integer limitations)
+ $mtime = unpack("V", substr($data,4,4));
+ $mtime = $mtime[1];
+ $xfl = substr($data,8,1);
+ $os = substr($data,8,1);
+ $headerlen = 10;
+ $extralen = 0;
+ $extra = "";
+ if ($flags & 4) {
+ // 2-byte length prefixed EXTRA data in header
+ if ($len - $headerlen - 2 < 8) {
+ return false; // invalid
+ }
+ $extralen = unpack("v",substr($data,8,2));
+ $extralen = $extralen[1];
+ if ($len - $headerlen - 2 - $extralen < 8) {
+ return false; // invalid
+ }
+ $extra = substr($data,10,$extralen);
+ $headerlen += 2 + $extralen;
+ }
+ $filenamelen = 0;
+ $filename = "";
+ if ($flags & 8) {
+ // C-style string
+ if ($len - $headerlen - 1 < 8) {
+ return false; // invalid
+ }
+ $filenamelen = strpos(substr($data,$headerlen),chr(0));
+ if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) {
+ return false; // invalid
+ }
+ $filename = substr($data,$headerlen,$filenamelen);
+ $headerlen += $filenamelen + 1;
+ }
+ $commentlen = 0;
+ $comment = "";
+ if ($flags & 16) {
+ // C-style string COMMENT data in header
+ if ($len - $headerlen - 1 < 8) {
+ return false; // invalid
+ }
+ $commentlen = strpos(substr($data,$headerlen),chr(0));
+ if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) {
+ return false; // Invalid header format
+ }
+ $comment = substr($data,$headerlen,$commentlen);
+ $headerlen += $commentlen + 1;
+ }
+ $headercrc = "";
+ if ($flags & 2) {
+ // 2-bytes (lowest order) of CRC32 on header present
+ if ($len - $headerlen - 2 < 8) {
+ return false; // invalid
+ }
+ $calccrc = crc32(substr($data,0,$headerlen)) & 0xffff;
+ $headercrc = unpack("v", substr($data,$headerlen,2));
+ $headercrc = $headercrc[1];
+ if ($headercrc != $calccrc) {
+ $error = "Header checksum failed.";
+ return false; // Bad header CRC
+ }
+ $headerlen += 2;
+ }
+ // GZIP FOOTER
+ $datacrc = unpack("V",substr($data,-8,4));
+ $datacrc = sprintf('%u',$datacrc[1] & 0xFFFFFFFF);
+ $isize = unpack("V",substr($data,-4));
+ $isize = $isize[1];
+ // decompression:
+ $bodylen = $len-$headerlen-8;
+ if ($bodylen < 1) {
+ // IMPLEMENTATION BUG!
+ return null;
+ }
+ $body = substr($data,$headerlen,$bodylen);
+ $data = "";
+ if ($bodylen > 0) {
+ switch ($method) {
+ case 8:
+ // Currently the only supported compression method:
+ $data = gzinflate($body,$maxlength);
+ break;
+ default:
+ $error = "Unknown compression method.";
+ return false;
+ }
+ } // zero-byte body content is allowed
+ // Verifiy CRC32
+ $crc = sprintf("%u",crc32($data));
+ $crcOK = $crc == $datacrc;
+ $lenOK = $isize == strlen($data);
+ if (!$lenOK || !$crcOK) {
+ $error = ( $lenOK ? '' : 'Length check FAILED. ') . ( $crcOK ? '' : 'Checksum FAILED.');
+ return false;
+ }
+ return $data;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Flake/Util/Twig/AUTHORS b/Core/Frameworks/Flake/Util/Twig/AUTHORS
new file mode 100644
index 0000000..eb5db05
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/AUTHORS
@@ -0,0 +1,9 @@
+Twig is written and maintained by the Twig Team:
+
+Lead Developer:
+
+- Fabien Potencier
+
+Project Founder:
+
+- Armin Ronacher
diff --git a/Core/Frameworks/Flake/Util/Twig/CHANGELOG b/Core/Frameworks/Flake/Util/Twig/CHANGELOG
new file mode 100644
index 0000000..23413de
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/CHANGELOG
@@ -0,0 +1,501 @@
+* 1.8.0 (2012-05-08)
+
+ * enforced interface when adding tests, filters, functions, and node visitors from extensions
+ * fixed a side-effect of the date filter where the timezone might be changed
+ * simplified usage of the autoescape tag; the only (optional) argument is now the escaping strategy or false (with a BC layer)
+ * added a way to dynamically change the auto-escaping strategy according to the template "filename"
+ * changed the autoescape option to also accept a supported escaping strategy (for BC, true is equivalent to html)
+ * added an embed tag
+
+* 1.7.0 (2012-04-24)
+
+ * fixed a PHP warning when using CIFS
+ * fixed template line number in some exceptions
+ * added an iterable test
+ * added an error when defining two blocks with the same name in a template
+ * added the preserves_safety option for filters
+ * fixed a PHP notice when trying to access a key on a non-object/array variable
+ * enhanced error reporting when the template file is an instance of SplFileInfo
+ * added Twig_Environment::mergeGlobals()
+ * added compilation checks to avoid misuses of the sandbox tag
+ * fixed filesystem loader freshness logic for high traffic websites
+ * fixed random function when charset is null
+
+* 1.6.5 (2012-04-11)
+
+ * fixed a regression when a template only extends another one without defining any blocks
+
+* 1.6.4 (2012-04-02)
+
+ * fixed PHP notice in Twig_Error::guessTemplateLine() introduced in 1.6.3
+ * fixed performance when compiling large files
+ * optimized parent template creation when the template does not use dynamic inheritance
+
+* 1.6.3 (2012-03-22)
+
+ * fixed usage of Z_ADDREF_P for PHP 5.2 in the C extension
+ * fixed compilation of numeric values used in templates when using a locale where the decimal separator is not a dot
+ * made the strategy used to guess the real template file name and line number in exception messages much faster and more accurate
+
+* 1.6.2 (2012-03-18)
+
+ * fixed sandbox mode when used with inheritance
+ * added preserveKeys support for the slice filter
+ * fixed the date filter when a DateTime instance is passed with a specific timezone
+ * added a trim filter
+
+* 1.6.1 (2012-02-29)
+
+ * fixed Twig C extension
+ * removed the creation of Twig_Markup instances when not needed
+ * added a way to set the default global timezone for dates
+ * fixed the slice filter on strings when the length is not specified
+ * fixed the creation of the cache directory in case of a race condition
+
+* 1.6.0 (2012-02-04)
+
+ * fixed raw blocks when used with the whitespace trim option
+ * made a speed optimization to macro calls when imported via the "from" tag
+ * fixed globals, parsers, visitors, filters, tests, and functions management in Twig_Environment when a new one or new extension is added
+ * fixed the attribute function when passing arguments
+ * added slice notation support for the [] operator (syntactic sugar for the slice operator)
+ * added a slice filter
+ * added string support for the reverse filter
+ * fixed the empty test and the length filter for Twig_Markup instances
+ * added a date function to ease date comparison
+ * fixed unary operators precedence
+ * added recursive parsing support in the parser
+ * added string and integer handling for the random function
+
+* 1.5.1 (2012-01-05)
+
+ * fixed a regression when parsing strings
+
+* 1.5.0 (2012-01-04)
+
+ * added Traversable objects support for the join filter
+
+* 1.5.0-RC2 (2011-12-30)
+
+ * added a way to set the default global date interval format
+ * fixed the date filter for DateInterval instances (setTimezone() does not exist for them)
+ * refactored Twig_Template::display() to ease its extension
+ * added a number_format filter
+
+* 1.5.0-RC1 (2011-12-26)
+
+ * removed the need to quote hash keys
+ * allowed hash keys to be any expression
+ * added a do tag
+ * added a flush tag
+ * added support for dynamically named filters and functions
+ * added a dump function to help debugging templates
+ * added a nl2br filter
+ * added a random function
+ * added a way to change the default format for the date filter
+ * fixed the lexer when an operator ending with a letter ends a line
+ * added string interpolation support
+ * enhanced exceptions for unknown filters, functions, tests, and tags
+
+* 1.4.0 (2011-12-07)
+
+ * fixed lexer when using big numbers (> PHP_INT_MAX)
+ * added missing preserveKeys argument to the reverse filter
+ * fixed macros containing filter tag calls
+
+* 1.4.0-RC2 (2011-11-27)
+
+ * removed usage of Reflection in Twig_Template::getAttribute()
+ * added a C extension that can optionally replace Twig_Template::getAttribute()
+ * added negative timestamp support to the date filter
+
+* 1.4.0-RC1 (2011-11-20)
+
+ * optimized variable access when using PHP 5.4
+ * changed the precedence of the .. operator to be more consistent with languages that implements such a feature like Ruby
+ * added an Exception to Twig_Loader_Array::isFresh() method when the template does not exist to be consistent with other loaders
+ * added Twig_Function_Node to allow more complex functions to have their own Node class
+ * added Twig_Filter_Node to allow more complex filters to have their own Node class
+ * added Twig_Test_Node to allow more complex tests to have their own Node class
+ * added a better error message when a template is empty but contain a BOM
+ * fixed "in" operator for empty strings
+ * fixed the "defined" test and the "default" filter (now works with more than one call (foo.bar.foo) and for both values of the strict_variables option)
+ * changed the way extensions are loaded (addFilter/addFunction/addGlobal/addTest/addNodeVisitor/addTokenParser/addExtension can now be called in any order)
+ * added Twig_Environment::display()
+ * made the escape filter smarter when the encoding is not supported by PHP
+ * added a convert_encoding filter
+ * moved all node manipulations outside the compile() Node method
+ * made several speed optimizations
+
+* 1.3.0 (2011-10-08)
+
+no changes
+
+* 1.3.0-RC1 (2011-10-04)
+
+ * added an optimization for the parent() function
+ * added cache reloading when auto_reload is true and an extension has been modified
+ * added the possibility to force the escaping of a string already marked as safe (instance of Twig_Markup)
+ * allowed empty templates to be used as traits
+ * added traits support for the "parent" function
+
+* 1.2.0 (2011-09-13)
+
+no changes
+
+* 1.2.0-RC1 (2011-09-10)
+
+ * enhanced the exception when a tag remains unclosed
+ * added support for empty Countable objects for the "empty" test
+ * fixed algorithm that determines if a template using inheritance is valid (no output between block definitions)
+ * added better support for encoding problems when escaping a string (available as of PHP 5.4)
+ * added a way to ignore a missing template when using the "include" tag ({% include "foo" ignore missing %})
+ * added support for an array of templates to the "include" and "extends" tags ({% include ['foo', 'bar'] %})
+ * added support for bitwise operators in expressions
+ * added the "attribute" function to allow getting dynamic attributes on variables
+ * added Twig_Loader_Chain
+ * added Twig_Loader_Array::setTemplate()
+ * added an optimization for the set tag when used to capture a large chunk of static text
+ * changed name regex to match PHP one "[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*" (works for blocks, tags, functions, filters, and macros)
+ * removed the possibility to use the "extends" tag from a block
+ * added "if" modifier support to "for" loops
+
+* 1.1.2 (2011-07-30)
+
+ * fixed json_encode filter on PHP 5.2
+ * fixed regression introduced in 1.1.1 ({{ block(foo|lower) }})
+ * fixed inheritance when using conditional parents
+ * fixed compilation of templates when the body of a child template is not empty
+ * fixed output when a macro throws an exception
+ * fixed a parsing problem when a large chunk of text is enclosed in a comment tag
+ * added PHPDoc for all Token parsers and Core extension functions
+
+* 1.1.1 (2011-07-17)
+
+ * added a performance optimization in the Optimizer (also helps to lower the number of nested level calls)
+ * made some performance improvement for some edge cases
+
+* 1.1.0 (2011-06-28)
+
+ * fixed json_encode filter
+
+* 1.1.0-RC3 (2011-06-24)
+
+ * fixed method case-sensitivity when using the sandbox mode
+ * added timezone support for the date filter
+ * fixed possible security problems with NUL bytes
+
+* 1.1.0-RC2 (2011-06-16)
+
+ * added an exception when the template passed to "use" is not a string
+ * made 'a.b is defined' not throw an exception if a is not defined (in strict mode)
+ * added {% line \d+ %} directive
+
+* 1.1.0-RC1 (2011-05-28)
+
+Flush your cache after upgrading.
+
+ * fixed date filter when using a timestamp
+ * fixed the defined test for some cases
+ * fixed a parsing problem when a large chunk of text is enclosed in a raw tag
+ * added support for horizontal reuse of template blocks (see docs for more information)
+ * added whitespace control modifier to all tags (see docs for more information)
+ * added null as an alias for none (the null test is also an alias for the none test now)
+ * made TRUE, FALSE, NONE equivalent to their lowercase counterparts
+ * wrapped all compilation and runtime exceptions with Twig_Error_Runtime and added logic to guess the template name and line
+ * moved display() method to Twig_Template (generated templates should now use doDisplay() instead)
+
+* 1.0.0 (2011-03-27)
+
+ * fixed output when using mbstring
+ * fixed duplicate call of methods when using the sandbox
+ * made the charset configurable for the escape filter
+
+* 1.0.0-RC2 (2011-02-21)
+
+ * changed the way {% set %} works when capturing (the content is now marked as safe)
+ * added support for macro name in the endmacro tag
+ * make Twig_Error compatible with PHP 5.3.0 >
+ * fixed an infinite loop on some Windows configurations
+ * fixed the "length" filter for numbers
+ * fixed Template::getAttribute() as properties in PHP are case sensitive
+ * removed coupling between Twig_Node and Twig_Template
+ * fixed the ternary operator precedence rule
+
+* 1.0.0-RC1 (2011-01-09)
+
+Backward incompatibilities:
+
+ * the "items" filter, which has been deprecated for quite a long time now, has been removed
+ * the "range" filter has been converted to a function: 0|range(10) -> range(0, 10)
+ * the "constant" filter has been converted to a function: {{ some_date|date('DATE_W3C'|constant) }} -> {{ some_date|date(constant('DATE_W3C')) }}
+ * the "cycle" filter has been converted to a function: {{ ['odd', 'even']|cycle(i) }} -> {{ cycle(['odd', 'even'], i) }}
+ * the "for" tag does not support "joined by" anymore
+ * the "autoescape" first argument is now "true"/"false" (instead of "on"/"off")
+ * the "parent" tag has been replaced by a "parent" function ({{ parent() }} instead of {% parent %})
+ * the "display" tag has been replaced by a "block" function ({{ block('title') }} instead of {% display title %})
+ * removed the grammar and simple token parser (moved to the Twig Extensions repository)
+
+Changes:
+
+ * added "needs_context" option for filters and functions (the context is then passed as a first argument)
+ * added global variables support
+ * made macros return their value instead of echoing directly (fixes calling a macro in sandbox mode)
+ * added the "from" tag to import macros as functions
+ * added support for functions (a function is just syntactic sugar for a getAttribute() call)
+ * made macros callable when sandbox mode is enabled
+ * added an exception when a macro uses a reserved name
+ * the "default" filter now uses the "empty" test instead of just checking for null
+ * added the "empty" test
+
+* 0.9.10 (2010-12-16)
+
+Backward incompatibilities:
+
+ * The Escaper extension is enabled by default, which means that all displayed
+ variables are now automatically escaped. You can revert to the previous
+ behavior by removing the extension via $env->removeExtension('escaper')
+ or just set the 'autoescape' option to 'false'.
+ * removed the "without loop" attribute for the "for" tag (not needed anymore
+ as the Optimizer take care of that for most cases)
+ * arrays and hashes have now a different syntax
+ * arrays keep the same syntax with square brackets: [1, 2]
+ * hashes now use curly braces (["a": "b"] should now be written as {"a": "b"})
+ * support for "arrays with keys" and "hashes without keys" is not supported anymore ([1, "foo": "bar"] or {"foo": "bar", 1})
+ * the i18n extension is now part of the Twig Extensions repository
+
+Changes:
+
+ * added the merge filter
+ * removed 'is_escaper' option for filters (a left over from the previous version) -- you must use 'is_safe' now instead
+ * fixed usage of operators as method names (like is, in, and not)
+ * changed the order of execution for node visitors
+ * fixed default() filter behavior when used with strict_variables set to on
+ * fixed filesystem loader compatibility with PHAR files
+ * enhanced error messages when an unexpected token is parsed in an expression
+ * fixed filename not being added to syntax error messages
+ * added the autoescape option to enable/disable autoescaping
+ * removed the newline after a comment (mimicks PHP behavior)
+ * added a syntax error exception when parent block is used on a template that does not extend another one
+ * made the Escaper extension enabled by default
+ * fixed sandbox extension when used with auto output escaping
+ * fixed escaper when wrapping a Twig_Node_Print (the original class must be preserved)
+ * added an Optimizer extension (enabled by default; optimizes "for" loops and "raw" filters)
+ * added priority to node visitors
+
+* 0.9.9 (2010-11-28)
+
+Backward incompatibilities:
+ * the self special variable has been renamed to _self
+ * the odd and even filters are now tests:
+ {{ foo|odd }} must now be written {{ foo is odd }}
+ * the "safe" filter has been renamed to "raw"
+ * in Node classes,
+ sub-nodes are now accessed via getNode() (instead of property access)
+ attributes via getAttribute() (instead of array access)
+ * the urlencode filter had been renamed to url_encode
+ * the include tag now merges the passed variables with the current context by default
+ (the old behavior is still possible by adding the "only" keyword)
+ * moved Exceptions to Twig_Error_* (Twig_SyntaxError/Twig_RuntimeError are now Twig_Error_Syntax/Twig_Error_Runtime)
+ * removed support for {{ 1 < i < 3 }} (use {{ i > 1 and i < 3 }} instead)
+ * the "in" filter has been removed ({{ a|in(b) }} should now be written {{ a in b }})
+
+Changes:
+ * added file and line to Twig_Error_Runtime exceptions thrown from Twig_Template
+ * changed trans tag to accept any variable for the plural count
+ * fixed sandbox mode (__toString() method check was not enforced if called implicitly from complex statements)
+ * added the ** (power) operator
+ * changed the algorithm used for parsing expressions
+ * added the spaceless tag
+ * removed trim_blocks option
+ * added support for is*() methods for attributes (foo.bar now looks for foo->getBar() or foo->isBar())
+ * changed all exceptions to extend Twig_Error
+ * fixed unary expressions ({{ not(1 or 0) }})
+ * fixed child templates (with an extend tag) that uses one or more imports
+ * added support for {{ 1 not in [2, 3] }} (more readable than the current {{ not (1 in [2, 3]) }})
+ * escaping has been rewritten
+ * the implementation of template inheritance has been rewritten
+ (blocks can now be called individually and still work with inheritance)
+ * fixed error handling for if tag when a syntax error occurs within a subparse process
+ * added a way to implement custom logic for resolving token parsers given a tag name
+ * fixed js escaper to be stricter (now uses a whilelist-based js escaper)
+ * added the following filers: "constant", "trans", "replace", "json_encode"
+ * added a "constant" test
+ * fixed objects with __toString() not being autoescaped
+ * fixed subscript expressions when calling __call() (methods now keep the case)
+ * added "test" feature (accessible via the "is" operator)
+ * removed the debug tag (should be done in an extension)
+ * fixed trans tag when no vars are used in plural form
+ * fixed race condition when writing template cache
+ * added the special _charset variable to reference the current charset
+ * added the special _context variable to reference the current context
+ * renamed self to _self (to avoid conflict)
+ * fixed Twig_Template::getAttribute() for protected properties
+
+* 0.9.8 (2010-06-28)
+
+Backward incompatibilities:
+ * the trans tag plural count is now attached to the plural tag:
+ old: `{% trans count %}...{% plural %}...{% endtrans %}`
+ new: `{% trans %}...{% plural count %}...{% endtrans %}`
+
+ * added a way to translate strings coming from a variable ({% trans var %})
+ * fixed trans tag when used with the Escaper extension
+ * fixed default cache umask
+ * removed Twig_Template instances from the debug tag output
+ * fixed objects with __isset() defined
+ * fixed set tag when used with a capture
+ * fixed type hinting for Twig_Environment::addFilter() method
+
+* 0.9.7 (2010-06-12)
+
+Backward incompatibilities:
+ * changed 'as' to '=' for the set tag ({% set title as "Title" %} must now be {% set title = "Title" %})
+ * removed the sandboxed attribute of the include tag (use the new sandbox tag instead)
+ * refactored the Node system (if you have custom nodes, you will have to update them to use the new API)
+
+ * added self as a special variable that refers to the current template (useful for importing macros from the current template)
+ * added Twig_Template instance support to the include tag
+ * added support for dynamic and conditional inheritance ({% extends some_var %} and {% extends standalone ? "minimum" : "base" %})
+ * added a grammar sub-framework to ease the creation of custom tags
+ * fixed the for tag for large arrays (some loop variables are now only available for arrays and objects that implement the Countable interface)
+ * removed the Twig_Resource::resolveMissingFilter() method
+ * fixed the filter tag which did not apply filtering to included files
+ * added a bunch of unit tests
+ * added a bunch of phpdoc
+ * added a sandbox tag in the sandbox extension
+ * changed the date filter to support any date format supported by DateTime
+ * added strict_variable setting to throw an exception when an invalid variable is used in a template (disabled by default)
+ * added the lexer, parser, and compiler as arguments to the Twig_Environment constructor
+ * changed the cache option to only accepts an explicit path to a cache directory or false
+ * added a way to add token parsers, filters, and visitors without creating an extension
+ * added three interfaces: Twig_NodeInterface, Twig_TokenParserInterface, and Twig_FilterInterface
+ * changed the generated code to match the new coding standards
+ * fixed sandbox mode (__toString() method check was not enforced if called implicitly from a simple statement like {{ article }})
+ * added an exception when a child template has a non-empty body (as it is always ignored when rendering)
+
+* 0.9.6 (2010-05-12)
+
+ * fixed variables defined outside a loop and for which the value changes in a for loop
+ * fixed the test suite for PHP 5.2 and older versions of PHPUnit
+ * added support for __call() in expression resolution
+ * fixed node visiting for macros (macros are now visited by visitors as any other node)
+ * fixed nested block definitions with a parent call (rarely useful but nonetheless supported now)
+ * added the cycle filter
+ * fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII
+ * added a long-syntax for the set tag ({% set foo %}...{% endset %})
+ * unit tests are now powered by PHPUnit
+ * added support for gettext via the `i18n` extension
+ * fixed twig_capitalize_string_filter() and fixed twig_length_filter() when used with UTF-8 values
+ * added a more useful exception if an if tag is not closed properly
+ * added support for escaping strategy in the autoescape tag
+ * fixed lexer when a template has a big chunk of text between/in a block
+
+* 0.9.5 (2010-01-20)
+
+As for any new release, don't forget to remove all cached templates after
+upgrading.
+
+If you have defined custom filters, you MUST upgrade them for this release. To
+upgrade, replace "array" with "new Twig_Filter_Function", and replace the
+environment constant by the "needs_environment" option:
+
+ // before
+ 'even' => array('twig_is_even_filter', false),
+ 'escape' => array('twig_escape_filter', true),
+
+ // after
+ 'even' => new Twig_Filter_Function('twig_is_even_filter'),
+ 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)),
+
+If you have created NodeTransformer classes, you will need to upgrade them to
+the new interface (please note that the interface is not yet considered
+stable).
+
+ * fixed list nodes that did not extend the Twig_NodeListInterface
+ * added the "without loop" option to the for tag (it disables the generation of the loop variable)
+ * refactored node transformers to node visitors
+ * fixed automatic-escaping for blocks
+ * added a way to specify variables to pass to an included template
+ * changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules)
+ * improved the filter system to allow object methods to be used as filters
+ * changed the Array and String loaders to actually make use of the cache mechanism
+ * included the default filter function definitions in the extension class files directly (Core, Escaper)
+ * added the // operator (like the floor() PHP function)
+ * added the .. operator (as a syntactic sugar for the range filter when the step is 1)
+ * added the in operator (as a syntactic sugar for the in filter)
+ * added the following filters in the Core extension: in, range
+ * added support for arrays (same behavior as in PHP, a mix between lists and dictionaries, arrays and hashes)
+ * enhanced some error messages to provide better feedback in case of parsing errors
+
+* 0.9.4 (2009-12-02)
+
+If you have custom loaders, you MUST upgrade them for this release: The
+Twig_Loader base class has been removed, and the Twig_LoaderInterface has also
+been changed (see the source code for more information or the documentation).
+
+ * added support for DateTime instances for the date filter
+ * fixed loop.last when the array only has one item
+ * made it possible to insert newlines in tag and variable blocks
+ * fixed a bug when a literal '\n' were present in a template text
+ * fixed bug when the filename of a template contains */
+ * refactored loaders
+
+* 0.9.3 (2009-11-11)
+
+This release is NOT backward compatible with the previous releases.
+
+ The loaders do not take the cache and autoReload arguments anymore. Instead,
+ the Twig_Environment class has two new options: cache and auto_reload.
+ Upgrading your code means changing this kind of code:
+
+ $loader = new Twig_Loader_Filesystem('/path/to/templates', '/path/to/compilation_cache', true);
+ $twig = new Twig_Environment($loader);
+
+ to something like this:
+
+ $loader = new Twig_Loader_Filesystem('/path/to/templates');
+ $twig = new Twig_Environment($loader, array(
+ 'cache' => '/path/to/compilation_cache',
+ 'auto_reload' => true,
+ ));
+
+ * deprecated the "items" filter as it is not needed anymore
+ * made cache and auto_reload options of Twig_Environment instead of arguments of Twig_Loader
+ * optimized template loading speed
+ * removed output when an error occurs in a template and render() is used
+ * made major speed improvements for loops (up to 300% on even the smallest loops)
+ * added properties as part of the sandbox mode
+ * added public properties support (obj.item can now be the item property on the obj object)
+ * extended set tag to support expression as value ({% set foo as 'foo' ~ 'bar' %} )
+ * fixed bug when \ was used in HTML
+
+* 0.9.2 (2009-10-29)
+
+ * made some speed optimizations
+ * changed the cache extension to .php
+ * added a js escaping strategy
+ * added support for short block tag
+ * changed the filter tag to allow chained filters
+ * made lexer more flexible as you can now change the default delimiters
+ * added set tag
+ * changed default directory permission when cache dir does not exist (more secure)
+ * added macro support
+ * changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance
+ * made Twig_Autoloader::autoload() a static method
+ * avoid writing template file if an error occurs
+ * added $ escaping when outputting raw strings
+ * enhanced some error messages to ease debugging
+ * fixed empty cache files when the template contains an error
+
+* 0.9.1 (2009-10-14)
+
+ * fixed a bug in PHP 5.2.6
+ * fixed numbers with one than one decimal
+ * added support for method calls with arguments ({{ foo.bar('a', 43) }})
+ * made small speed optimizations
+ * made minor tweaks to allow better extensibility and flexibility
+
+* 0.9.0 (2009-10-12)
+
+ * Initial release
diff --git a/Core/Frameworks/Flake/Util/Twig/LICENSE b/Core/Frameworks/Flake/Util/Twig/LICENSE
new file mode 100644
index 0000000..5063d8d
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/LICENSE
@@ -0,0 +1,31 @@
+Copyright (c) 2009 by the Twig Team, see AUTHORS for more details.
+
+Some rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * The names of the contributors may not be used to endorse or
+ promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Core/Frameworks/Flake/Util/Twig/README.markdown b/Core/Frameworks/Flake/Util/Twig/README.markdown
new file mode 100644
index 0000000..88d6fab
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/README.markdown
@@ -0,0 +1,17 @@
+Twig, the flexible, fast, and secure template language for PHP
+==============================================================
+
+[](http://travis-ci.org/fabpot/Twig)
+
+Twig is a template language for PHP, released under the new BSD license (code
+and documentation).
+
+Twig uses a syntax similar to the Django and Jinja template languages which
+inspired the Twig runtime environment.
+
+More Information
+----------------
+
+Read the [documentation][1] for more information.
+
+[1]: http://twig.sensiolabs.org/documentation
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Autoloader.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Autoloader.php
new file mode 100644
index 0000000..a93b8ca
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Autoloader.php
@@ -0,0 +1,46 @@
+
+ */
+class Twig_Autoloader
+{
+ /**
+ * Registers Twig_Autoloader as an SPL autoloader.
+ */
+ static public function register()
+ {
+ ini_set('unserialize_callback_func', 'spl_autoload_call');
+ spl_autoload_register(array(new self, 'autoload'));
+ }
+
+ /**
+ * Handles autoloading of classes.
+ *
+ * @param string $class A class name.
+ *
+ * @return boolean Returns true if the class has been loaded
+ */
+ static public function autoload($class)
+ {
+ if (0 !== strpos($class, 'Twig')) {
+ return;
+ }
+
+ if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) {
+ require $file;
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Compiler.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Compiler.php
new file mode 100644
index 0000000..ae415a2
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Compiler.php
@@ -0,0 +1,242 @@
+
+ */
+class Twig_Compiler implements Twig_CompilerInterface
+{
+ protected $lastLine;
+ protected $source;
+ protected $indentation;
+ protected $env;
+ protected $debugInfo;
+ protected $sourceOffset;
+ protected $sourceLine;
+
+ /**
+ * Constructor.
+ *
+ * @param Twig_Environment $env The twig environment instance
+ */
+ public function __construct(Twig_Environment $env)
+ {
+ $this->env = $env;
+ $this->debugInfo = array();
+ }
+
+ /**
+ * Returns the environment instance related to this compiler.
+ *
+ * @return Twig_Environment The environment instance
+ */
+ public function getEnvironment()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Gets the current PHP code after compilation.
+ *
+ * @return string The PHP code
+ */
+ public function getSource()
+ {
+ return $this->source;
+ }
+
+ /**
+ * Compiles a node.
+ *
+ * @param Twig_NodeInterface $node The node to compile
+ * @param integer $indentation The current indentation
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function compile(Twig_NodeInterface $node, $indentation = 0)
+ {
+ $this->lastLine = null;
+ $this->source = '';
+ $this->sourceOffset = 0;
+ $this->sourceLine = 0;
+ $this->indentation = $indentation;
+
+ $node->compile($this);
+
+ return $this;
+ }
+
+ public function subcompile(Twig_NodeInterface $node, $raw = true)
+ {
+ if (false === $raw) {
+ $this->addIndentation();
+ }
+
+ $node->compile($this);
+
+ return $this;
+ }
+
+ /**
+ * Adds a raw string to the compiled code.
+ *
+ * @param string $string The string
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function raw($string)
+ {
+ $this->source .= $string;
+
+ return $this;
+ }
+
+ /**
+ * Writes a string to the compiled code by adding indentation.
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function write()
+ {
+ $strings = func_get_args();
+ foreach ($strings as $string) {
+ $this->addIndentation();
+ $this->source .= $string;
+ }
+
+ return $this;
+ }
+
+ public function addIndentation()
+ {
+ $this->source .= str_repeat(' ', $this->indentation * 4);
+
+ return $this;
+ }
+
+ /**
+ * Adds a quoted string to the compiled code.
+ *
+ * @param string $value The string
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function string($value)
+ {
+ $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
+
+ return $this;
+ }
+
+ /**
+ * Returns a PHP representation of a given value.
+ *
+ * @param mixed $value The value to convert
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function repr($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ if (false !== $locale = setlocale(LC_NUMERIC, 0)) {
+ setlocale(LC_NUMERIC, 'C');
+ }
+
+ $this->raw($value);
+
+ if (false !== $locale) {
+ setlocale(LC_NUMERIC, $locale);
+ }
+ } elseif (null === $value) {
+ $this->raw('null');
+ } elseif (is_bool($value)) {
+ $this->raw($value ? 'true' : 'false');
+ } elseif (is_array($value)) {
+ $this->raw('array(');
+ $i = 0;
+ foreach ($value as $key => $value) {
+ if ($i++) {
+ $this->raw(', ');
+ }
+ $this->repr($key);
+ $this->raw(' => ');
+ $this->repr($value);
+ }
+ $this->raw(')');
+ } else {
+ $this->string($value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds debugging information.
+ *
+ * @param Twig_NodeInterface $node The related twig node
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function addDebugInfo(Twig_NodeInterface $node)
+ {
+ if ($node->getLine() != $this->lastLine) {
+ $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
+ $this->sourceOffset = strlen($this->source);
+ $this->debugInfo[$this->sourceLine] = $node->getLine();
+
+ $this->lastLine = $node->getLine();
+ $this->write("// line {$node->getLine()}\n");
+ }
+
+ return $this;
+ }
+
+ public function getDebugInfo()
+ {
+ return $this->debugInfo;
+ }
+
+ /**
+ * Indents the generated code.
+ *
+ * @param integer $step The number of indentation to add
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function indent($step = 1)
+ {
+ $this->indentation += $step;
+
+ return $this;
+ }
+
+ /**
+ * Outdents the generated code.
+ *
+ * @param integer $step The number of indentation to remove
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function outdent($step = 1)
+ {
+ $this->indentation -= $step;
+
+ if ($this->indentation < 0) {
+ throw new Twig_Error('Unable to call outdent() as the indentation would become negative');
+ }
+
+ return $this;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/CompilerInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/CompilerInterface.php
new file mode 100644
index 0000000..0a13edf
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/CompilerInterface.php
@@ -0,0 +1,35 @@
+
+ */
+interface Twig_CompilerInterface
+{
+ /**
+ * Compiles a node.
+ *
+ * @param Twig_NodeInterface $node The node to compile
+ *
+ * @return Twig_CompilerInterface The current compiler instance
+ */
+ function compile(Twig_NodeInterface $node);
+
+ /**
+ * Gets the current PHP code after compilation.
+ *
+ * @return string The PHP code
+ */
+ function getSource();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Environment.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Environment.php
new file mode 100644
index 0000000..81c8fe6
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Environment.php
@@ -0,0 +1,1106 @@
+
+ */
+class Twig_Environment
+{
+ const VERSION = '1.8.0';
+
+ protected $charset;
+ protected $loader;
+ protected $debug;
+ protected $autoReload;
+ protected $cache;
+ protected $lexer;
+ protected $parser;
+ protected $compiler;
+ protected $baseTemplateClass;
+ protected $extensions;
+ protected $parsers;
+ protected $visitors;
+ protected $filters;
+ protected $tests;
+ protected $functions;
+ protected $globals;
+ protected $runtimeInitialized;
+ protected $loadedTemplates;
+ protected $strictVariables;
+ protected $unaryOperators;
+ protected $binaryOperators;
+ protected $templateClassPrefix = '__TwigTemplate_';
+ protected $functionCallbacks;
+ protected $filterCallbacks;
+ protected $staging;
+
+ /**
+ * Constructor.
+ *
+ * Available options:
+ *
+ * * debug: When set to `true`, the generated templates have a __toString()
+ * method that you can use to display the generated nodes (default to
+ * false).
+ *
+ * * charset: The charset used by the templates (default to utf-8).
+ *
+ * * base_template_class: The base template class to use for generated
+ * templates (default to Twig_Template).
+ *
+ * * cache: An absolute path where to store the compiled templates, or
+ * false to disable compilation cache (default).
+ *
+ * * auto_reload: Whether to reload the template is the original source changed.
+ * If you don't provide the auto_reload option, it will be
+ * determined automatically base on the debug value.
+ *
+ * * strict_variables: Whether to ignore invalid variables in templates
+ * (default to false).
+ *
+ * * autoescape: Whether to enable auto-escaping (default to html):
+ * * false: disable auto-escaping
+ * * true: equivalent to html
+ * * html, js: set the autoescaping to one of the supported strategies
+ * * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename"
+ *
+ * * optimizations: A flag that indicates which optimizations to apply
+ * (default to -1 which means that all optimizations are enabled;
+ * set it to 0 to disable).
+ *
+ * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
+ * @param array $options An array of options
+ */
+ public function __construct(Twig_LoaderInterface $loader = null, $options = array())
+ {
+ if (null !== $loader) {
+ $this->setLoader($loader);
+ }
+
+ $options = array_merge(array(
+ 'debug' => false,
+ 'charset' => 'UTF-8',
+ 'base_template_class' => 'Twig_Template',
+ 'strict_variables' => false,
+ 'autoescape' => 'html',
+ 'cache' => false,
+ 'auto_reload' => null,
+ 'optimizations' => -1,
+ ), $options);
+
+ $this->debug = (bool) $options['debug'];
+ $this->charset = $options['charset'];
+ $this->baseTemplateClass = $options['base_template_class'];
+ $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
+ $this->extensions = array(
+ 'core' => new Twig_Extension_Core(),
+ 'escaper' => new Twig_Extension_Escaper($options['autoescape']),
+ 'optimizer' => new Twig_Extension_Optimizer($options['optimizations']),
+ );
+ $this->strictVariables = (bool) $options['strict_variables'];
+ $this->runtimeInitialized = false;
+ $this->setCache($options['cache']);
+ $this->functionCallbacks = array();
+ $this->filterCallbacks = array();
+ $this->staging = array(
+ 'functions' => array(),
+ 'filters' => array(),
+ 'tests' => array(),
+ 'token_parsers' => array(),
+ 'visitors' => array(),
+ 'globals' => array(),
+ );
+ }
+
+ /**
+ * Gets the base template class for compiled templates.
+ *
+ * @return string The base template class name
+ */
+ public function getBaseTemplateClass()
+ {
+ return $this->baseTemplateClass;
+ }
+
+ /**
+ * Sets the base template class for compiled templates.
+ *
+ * @param string $class The base template class name
+ */
+ public function setBaseTemplateClass($class)
+ {
+ $this->baseTemplateClass = $class;
+ }
+
+ /**
+ * Enables debugging mode.
+ */
+ public function enableDebug()
+ {
+ $this->debug = true;
+ }
+
+ /**
+ * Disables debugging mode.
+ */
+ public function disableDebug()
+ {
+ $this->debug = false;
+ }
+
+ /**
+ * Checks if debug mode is enabled.
+ *
+ * @return Boolean true if debug mode is enabled, false otherwise
+ */
+ public function isDebug()
+ {
+ return $this->debug;
+ }
+
+ /**
+ * Enables the auto_reload option.
+ */
+ public function enableAutoReload()
+ {
+ $this->autoReload = true;
+ }
+
+ /**
+ * Disables the auto_reload option.
+ */
+ public function disableAutoReload()
+ {
+ $this->autoReload = false;
+ }
+
+ /**
+ * Checks if the auto_reload option is enabled.
+ *
+ * @return Boolean true if auto_reload is enabled, false otherwise
+ */
+ public function isAutoReload()
+ {
+ return $this->autoReload;
+ }
+
+ /**
+ * Enables the strict_variables option.
+ */
+ public function enableStrictVariables()
+ {
+ $this->strictVariables = true;
+ }
+
+ /**
+ * Disables the strict_variables option.
+ */
+ public function disableStrictVariables()
+ {
+ $this->strictVariables = false;
+ }
+
+ /**
+ * Checks if the strict_variables option is enabled.
+ *
+ * @return Boolean true if strict_variables is enabled, false otherwise
+ */
+ public function isStrictVariables()
+ {
+ return $this->strictVariables;
+ }
+
+ /**
+ * Gets the cache directory or false if cache is disabled.
+ *
+ * @return string|false
+ */
+ public function getCache()
+ {
+ return $this->cache;
+ }
+
+ /**
+ * Sets the cache directory or false if cache is disabled.
+ *
+ * @param string|false $cache The absolute path to the compiled templates,
+ * or false to disable cache
+ */
+ public function setCache($cache)
+ {
+ $this->cache = $cache ? $cache : false;
+ }
+
+ /**
+ * Gets the cache filename for a given template.
+ *
+ * @param string $name The template name
+ *
+ * @return string The cache file name
+ */
+ public function getCacheFilename($name)
+ {
+ if (false === $this->cache) {
+ return false;
+ }
+
+ $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix));
+
+ return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php';
+ }
+
+ /**
+ * Gets the template class associated with the given string.
+ *
+ * @param string $name The name for which to calculate the template class name
+ * @param integer $index The index if it is an embedded template
+ *
+ * @return string The template class name
+ */
+ public function getTemplateClass($name, $index = null)
+ {
+ return $this->templateClassPrefix.md5($this->loader->getCacheKey($name)).(null === $index ? '' : '_'.$index);
+ }
+
+ /**
+ * Gets the template class prefix.
+ *
+ * @return string The template class prefix
+ */
+ public function getTemplateClassPrefix()
+ {
+ return $this->templateClassPrefix;
+ }
+
+ /**
+ * Renders a template.
+ *
+ * @param string $name The template name
+ * @param array $context An array of parameters to pass to the template
+ *
+ * @return string The rendered template
+ */
+ public function render($name, array $context = array())
+ {
+ return $this->loadTemplate($name)->render($context);
+ }
+
+ /**
+ * Displays a template.
+ *
+ * @param string $name The template name
+ * @param array $context An array of parameters to pass to the template
+ */
+ public function display($name, array $context = array())
+ {
+ $this->loadTemplate($name)->display($context);
+ }
+
+ /**
+ * Loads a template by name.
+ *
+ * @param string $name The template name
+ * @param integer $index The index if it is an embedded template
+ *
+ * @return Twig_TemplateInterface A template instance representing the given template name
+ */
+ public function loadTemplate($name, $index = null)
+ {
+ $cls = $this->getTemplateClass($name, $index);
+
+ if (isset($this->loadedTemplates[$cls])) {
+ return $this->loadedTemplates[$cls];
+ }
+
+ if (!class_exists($cls, false)) {
+ if (false === $cache = $this->getCacheFilename($name)) {
+ eval('?>'.$this->compileSource($this->loader->getSource($name), $name));
+ } else {
+ if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
+ $this->writeCacheFile($cache, $this->compileSource($this->loader->getSource($name), $name));
+ }
+
+ require_once $cache;
+ }
+ }
+
+ if (!$this->runtimeInitialized) {
+ $this->initRuntime();
+ }
+
+ return $this->loadedTemplates[$cls] = new $cls($this);
+ }
+
+ /**
+ * Returns true if the template is still fresh.
+ *
+ * Besides checking the loader for freshness information,
+ * this method also checks if the enabled extensions have
+ * not changed.
+ *
+ * @param string $name The template name
+ * @param timestamp $time The last modification time of the cached template
+ *
+ * @return Boolean true if the template is fresh, false otherwise
+ */
+ public function isTemplateFresh($name, $time)
+ {
+ foreach ($this->extensions as $extension) {
+ $r = new ReflectionObject($extension);
+ if (filemtime($r->getFileName()) > $time) {
+ return false;
+ }
+ }
+
+ return $this->loader->isFresh($name, $time);
+ }
+
+ public function resolveTemplate($names)
+ {
+ if (!is_array($names)) {
+ $names = array($names);
+ }
+
+ foreach ($names as $name) {
+ if ($name instanceof Twig_Template) {
+ return $name;
+ }
+
+ try {
+ return $this->loadTemplate($name);
+ } catch (Twig_Error_Loader $e) {
+ }
+ }
+
+ if (1 === count($names)) {
+ throw $e;
+ }
+
+ throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
+ }
+
+ /**
+ * Clears the internal template cache.
+ */
+ public function clearTemplateCache()
+ {
+ $this->loadedTemplates = array();
+ }
+
+ /**
+ * Clears the template cache files on the filesystem.
+ */
+ public function clearCacheFiles()
+ {
+ if (false === $this->cache) {
+ return;
+ }
+
+ foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
+ if ($file->isFile()) {
+ @unlink($file->getPathname());
+ }
+ }
+ }
+
+ /**
+ * Gets the Lexer instance.
+ *
+ * @return Twig_LexerInterface A Twig_LexerInterface instance
+ */
+ public function getLexer()
+ {
+ if (null === $this->lexer) {
+ $this->lexer = new Twig_Lexer($this);
+ }
+
+ return $this->lexer;
+ }
+
+ /**
+ * Sets the Lexer instance.
+ *
+ * @param Twig_LexerInterface A Twig_LexerInterface instance
+ */
+ public function setLexer(Twig_LexerInterface $lexer)
+ {
+ $this->lexer = $lexer;
+ }
+
+ /**
+ * Tokenizes a source code.
+ *
+ * @param string $source The template source code
+ * @param string $name The template name
+ *
+ * @return Twig_TokenStream A Twig_TokenStream instance
+ */
+ public function tokenize($source, $name = null)
+ {
+ return $this->getLexer()->tokenize($source, $name);
+ }
+
+ /**
+ * Gets the Parser instance.
+ *
+ * @return Twig_ParserInterface A Twig_ParserInterface instance
+ */
+ public function getParser()
+ {
+ if (null === $this->parser) {
+ $this->parser = new Twig_Parser($this);
+ }
+
+ return $this->parser;
+ }
+
+ /**
+ * Sets the Parser instance.
+ *
+ * @param Twig_ParserInterface A Twig_ParserInterface instance
+ */
+ public function setParser(Twig_ParserInterface $parser)
+ {
+ $this->parser = $parser;
+ }
+
+ /**
+ * Parses a token stream.
+ *
+ * @param Twig_TokenStream $tokens A Twig_TokenStream instance
+ *
+ * @return Twig_Node_Module A Node tree
+ */
+ public function parse(Twig_TokenStream $tokens)
+ {
+ return $this->getParser()->parse($tokens);
+ }
+
+ /**
+ * Gets the Compiler instance.
+ *
+ * @return Twig_CompilerInterface A Twig_CompilerInterface instance
+ */
+ public function getCompiler()
+ {
+ if (null === $this->compiler) {
+ $this->compiler = new Twig_Compiler($this);
+ }
+
+ return $this->compiler;
+ }
+
+ /**
+ * Sets the Compiler instance.
+ *
+ * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance
+ */
+ public function setCompiler(Twig_CompilerInterface $compiler)
+ {
+ $this->compiler = $compiler;
+ }
+
+ /**
+ * Compiles a Node.
+ *
+ * @param Twig_NodeInterface $node A Twig_NodeInterface instance
+ *
+ * @return string The compiled PHP source code
+ */
+ public function compile(Twig_NodeInterface $node)
+ {
+ return $this->getCompiler()->compile($node)->getSource();
+ }
+
+ /**
+ * Compiles a template source code.
+ *
+ * @param string $source The template source code
+ * @param string $name The template name
+ *
+ * @return string The compiled PHP source code
+ */
+ public function compileSource($source, $name = null)
+ {
+ try {
+ return $this->compile($this->parse($this->tokenize($source, $name)));
+ } catch (Twig_Error $e) {
+ $e->setTemplateFile($name);
+ throw $e;
+ } catch (Exception $e) {
+ throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
+ }
+ }
+
+ /**
+ * Sets the Loader instance.
+ *
+ * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
+ */
+ public function setLoader(Twig_LoaderInterface $loader)
+ {
+ $this->loader = $loader;
+ }
+
+ /**
+ * Gets the Loader instance.
+ *
+ * @return Twig_LoaderInterface A Twig_LoaderInterface instance
+ */
+ public function getLoader()
+ {
+ return $this->loader;
+ }
+
+ /**
+ * Sets the default template charset.
+ *
+ * @param string $charset The default charset
+ */
+ public function setCharset($charset)
+ {
+ $this->charset = $charset;
+ }
+
+ /**
+ * Gets the default template charset.
+ *
+ * @return string The default charset
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Initializes the runtime environment.
+ */
+ public function initRuntime()
+ {
+ $this->runtimeInitialized = true;
+
+ foreach ($this->getExtensions() as $extension) {
+ $extension->initRuntime($this);
+ }
+ }
+
+ /**
+ * Returns true if the given extension is registered.
+ *
+ * @param string $name The extension name
+ *
+ * @return Boolean Whether the extension is registered or not
+ */
+ public function hasExtension($name)
+ {
+ return isset($this->extensions[$name]);
+ }
+
+ /**
+ * Gets an extension by name.
+ *
+ * @param string $name The extension name
+ *
+ * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance
+ */
+ public function getExtension($name)
+ {
+ if (!isset($this->extensions[$name])) {
+ throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name));
+ }
+
+ return $this->extensions[$name];
+ }
+
+ /**
+ * Registers an extension.
+ *
+ * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance
+ */
+ public function addExtension(Twig_ExtensionInterface $extension)
+ {
+ $this->extensions[$extension->getName()] = $extension;
+ $this->parsers = null;
+ $this->visitors = null;
+ $this->filters = null;
+ $this->tests = null;
+ $this->functions = null;
+ $this->globals = null;
+ }
+
+ /**
+ * Removes an extension by name.
+ *
+ * @param string $name The extension name
+ */
+ public function removeExtension($name)
+ {
+ unset($this->extensions[$name]);
+ $this->parsers = null;
+ $this->visitors = null;
+ $this->filters = null;
+ $this->tests = null;
+ $this->functions = null;
+ $this->globals = null;
+ }
+
+ /**
+ * Registers an array of extensions.
+ *
+ * @param array $extensions An array of extensions
+ */
+ public function setExtensions(array $extensions)
+ {
+ foreach ($extensions as $extension) {
+ $this->addExtension($extension);
+ }
+ }
+
+ /**
+ * Returns all registered extensions.
+ *
+ * @return array An array of extensions
+ */
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+
+ /**
+ * Registers a Token Parser.
+ *
+ * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
+ */
+ public function addTokenParser(Twig_TokenParserInterface $parser)
+ {
+ $this->staging['token_parsers'][] = $parser;
+ $this->parsers = null;
+ }
+
+ /**
+ * Gets the registered Token Parsers.
+ *
+ * @return Twig_TokenParserBrokerInterface A broker containing token parsers
+ */
+ public function getTokenParsers()
+ {
+ if (null === $this->parsers) {
+ $this->parsers = new Twig_TokenParserBroker();
+
+ if (isset($this->staging['token_parsers'])) {
+ foreach ($this->staging['token_parsers'] as $parser) {
+ $this->parsers->addTokenParser($parser);
+ }
+ }
+
+ foreach ($this->getExtensions() as $extension) {
+ $parsers = $extension->getTokenParsers();
+ foreach($parsers as $parser) {
+ if ($parser instanceof Twig_TokenParserInterface) {
+ $this->parsers->addTokenParser($parser);
+ } elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
+ $this->parsers->addTokenParserBroker($parser);
+ } else {
+ throw new Twig_Error_Runtime('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
+ }
+ }
+ }
+ }
+
+ return $this->parsers;
+ }
+
+ /**
+ * Gets registered tags.
+ *
+ * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
+ *
+ * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances
+ */
+ public function getTags()
+ {
+ $tags = array();
+ foreach ($this->getTokenParsers()->getParsers() as $parser) {
+ if ($parser instanceof Twig_TokenParserInterface) {
+ $tags[$parser->getTag()] = $parser;
+ }
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Registers a Node Visitor.
+ *
+ * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
+ */
+ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
+ {
+ $this->staging['visitors'][] = $visitor;
+ $this->visitors = null;
+ }
+
+ /**
+ * Gets the registered Node Visitors.
+ *
+ * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
+ */
+ public function getNodeVisitors()
+ {
+ if (null === $this->visitors) {
+ foreach ($this->getExtensions() as $extension) {
+ foreach ($extension->getNodeVisitors() as $visitor) {
+ $this->addNodeVisitor($visitor);
+ }
+ }
+
+ $this->visitors = $this->staging['visitors'];
+ }
+
+ return $this->visitors;
+ }
+
+ /**
+ * Registers a Filter.
+ *
+ * @param string $name The filter name
+ * @param Twig_FilterInterface $filter A Twig_FilterInterface instance
+ */
+ public function addFilter($name, Twig_FilterInterface $filter)
+ {
+ $this->staging['filters'][$name] = $filter;
+ $this->filters = null;
+ }
+
+ /**
+ * Get a filter by name.
+ *
+ * Subclasses may override this method and load filters differently;
+ * so no list of filters is available.
+ *
+ * @param string $name The filter name
+ *
+ * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exists
+ */
+ public function getFilter($name)
+ {
+ if (null === $this->filters) {
+ $this->getFilters();
+ }
+
+ if (isset($this->filters[$name])) {
+ return $this->filters[$name];
+ }
+
+ foreach ($this->filters as $pattern => $filter) {
+ $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
+
+ if ($count) {
+ if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
+ array_shift($matches);
+ $filter->setArguments($matches);
+
+ return $filter;
+ }
+ }
+ }
+
+ foreach ($this->filterCallbacks as $callback) {
+ if (false !== $filter = call_user_func($callback, $name)) {
+ return $filter;
+ }
+ }
+
+ return false;
+ }
+
+ public function registerUndefinedFilterCallback($callable)
+ {
+ $this->filterCallbacks[] = $callable;
+ }
+
+ /**
+ * Gets the registered Filters.
+ *
+ * Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback.
+ *
+ * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances
+ *
+ * @see registerUndefinedFilterCallback
+ */
+ public function getFilters()
+ {
+ if (null === $this->filters) {
+ foreach ($this->getExtensions() as $extension) {
+ foreach ($extension->getFilters() as $name => $filter) {
+ $this->addFilter($name, $filter);
+ }
+ }
+
+ $this->filters = $this->staging['filters'];
+ }
+
+ return $this->filters;
+ }
+
+ /**
+ * Registers a Test.
+ *
+ * @param string $name The test name
+ * @param Twig_TestInterface $test A Twig_TestInterface instance
+ */
+ public function addTest($name, Twig_TestInterface $test)
+ {
+ $this->staging['tests'][$name] = $test;
+ $this->tests = null;
+ }
+
+ /**
+ * Gets the registered Tests.
+ *
+ * @return Twig_TestInterface[] An array of Twig_TestInterface instances
+ */
+ public function getTests()
+ {
+ if (null === $this->tests) {
+ foreach ($this->getExtensions() as $extension) {
+ foreach ($extension->getTests() as $name => $test) {
+ $this->addTest($name, $test);
+ }
+ }
+
+ $this->tests = $this->staging['tests'];
+ }
+
+ return $this->tests;
+ }
+
+ /**
+ * Registers a Function.
+ *
+ * @param string $name The function name
+ * @param Twig_FunctionInterface $function A Twig_FunctionInterface instance
+ */
+ public function addFunction($name, Twig_FunctionInterface $function)
+ {
+ $this->staging['functions'][$name] = $function;
+ $this->functions = null;
+ }
+
+ /**
+ * Get a function by name.
+ *
+ * Subclasses may override this method and load functions differently;
+ * so no list of functions is available.
+ *
+ * @param string $name function name
+ *
+ * @return Twig_Function|false A Twig_Function instance or false if the function does not exists
+ */
+ public function getFunction($name)
+ {
+ if (null === $this->functions) {
+ $this->getFunctions();
+ }
+
+ if (isset($this->functions[$name])) {
+ return $this->functions[$name];
+ }
+
+ foreach ($this->functions as $pattern => $function) {
+ $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
+
+ if ($count) {
+ if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
+ array_shift($matches);
+ $function->setArguments($matches);
+
+ return $function;
+ }
+ }
+ }
+
+ foreach ($this->functionCallbacks as $callback) {
+ if (false !== $function = call_user_func($callback, $name)) {
+ return $function;
+ }
+ }
+
+ return false;
+ }
+
+ public function registerUndefinedFunctionCallback($callable)
+ {
+ $this->functionCallbacks[] = $callable;
+ }
+
+ /**
+ * Gets registered functions.
+ *
+ * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
+ *
+ * @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances
+ *
+ * @see registerUndefinedFunctionCallback
+ */
+ public function getFunctions()
+ {
+ if (null === $this->functions) {
+ foreach ($this->getExtensions() as $extension) {
+ foreach ($extension->getFunctions() as $name => $function) {
+ $this->addFunction($name, $function);
+ }
+ }
+
+ $this->functions = $this->staging['functions'];
+ }
+
+ return $this->functions;
+ }
+
+ /**
+ * Registers a Global.
+ *
+ * @param string $name The global name
+ * @param mixed $value The global value
+ */
+ public function addGlobal($name, $value)
+ {
+ $this->staging['globals'][$name] = $value;
+ $this->globals = null;
+ }
+
+ /**
+ * Gets the registered Globals.
+ *
+ * @return array An array of globals
+ */
+ public function getGlobals()
+ {
+ if (null === $this->globals) {
+ $this->globals = isset($this->staging['globals']) ? $this->staging['globals'] : array();
+ foreach ($this->getExtensions() as $extension) {
+ $this->globals = array_merge($this->globals, $extension->getGlobals());
+ }
+ }
+
+ return $this->globals;
+ }
+
+ /**
+ * Merges a context with the defined globals.
+ *
+ * @param array $context An array representing the context
+ *
+ * @return array The context merged with the globals
+ */
+ public function mergeGlobals(array $context)
+ {
+ // we don't use array_merge as the context being generally
+ // bigger than globals, this code is faster.
+ foreach ($this->getGlobals() as $key => $value) {
+ if (!array_key_exists($key, $context)) {
+ $context[$key] = $value;
+ }
+ }
+
+ return $context;
+ }
+
+ /**
+ * Gets the registered unary Operators.
+ *
+ * @return array An array of unary operators
+ */
+ public function getUnaryOperators()
+ {
+ if (null === $this->unaryOperators) {
+ $this->initOperators();
+ }
+
+ return $this->unaryOperators;
+ }
+
+ /**
+ * Gets the registered binary Operators.
+ *
+ * @return array An array of binary operators
+ */
+ public function getBinaryOperators()
+ {
+ if (null === $this->binaryOperators) {
+ $this->initOperators();
+ }
+
+ return $this->binaryOperators;
+ }
+
+ public function computeAlternatives($name, $items)
+ {
+ $alternatives = array();
+ foreach ($items as $item) {
+ $lev = levenshtein($name, $item);
+ if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
+ $alternatives[$item] = $lev;
+ }
+ }
+ asort($alternatives);
+
+ return array_keys($alternatives);
+ }
+
+ protected function initOperators()
+ {
+ $this->unaryOperators = array();
+ $this->binaryOperators = array();
+ foreach ($this->getExtensions() as $extension) {
+ $operators = $extension->getOperators();
+
+ if (!$operators) {
+ continue;
+ }
+
+ if (2 !== count($operators)) {
+ throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension)));
+ }
+
+ $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
+ $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
+ }
+ }
+
+ protected function writeCacheFile($file, $content)
+ {
+ $dir = dirname($file);
+ if (!is_dir($dir)) {
+ if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
+ throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
+ }
+ } elseif (!is_writable($dir)) {
+ throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
+ }
+
+ $tmpFile = tempnam(dirname($file), basename($file));
+ if (false !== @file_put_contents($tmpFile, $content)) {
+ // rename does not work on Win32 before 5.2.6
+ if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {
+ @chmod($file, 0644);
+
+ return;
+ }
+ }
+
+ throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file));
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error.php
new file mode 100644
index 0000000..8ac2a2e
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error.php
@@ -0,0 +1,199 @@
+
+ */
+class Twig_Error extends Exception
+{
+ protected $lineno;
+ protected $filename;
+ protected $rawMessage;
+ protected $previous;
+
+ /**
+ * Constructor.
+ *
+ * @param string $message The error message
+ * @param integer $lineno The template line where the error occurred
+ * @param string $filename The template file name where the error occurred
+ * @param Exception $previous The previous exception
+ */
+ public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
+ {
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+ $this->previous = $previous;
+ parent::__construct('');
+ } else {
+ parent::__construct('', 0, $previous);
+ }
+
+ $this->lineno = $lineno;
+ $this->filename = $filename;
+
+ if (-1 === $this->lineno || null === $this->filename) {
+ $this->guessTemplateInfo();
+ }
+
+ $this->rawMessage = $message;
+
+ $this->updateRepr();
+ }
+
+ /**
+ * Gets the raw message.
+ *
+ * @return string The raw message
+ */
+ public function getRawMessage()
+ {
+ return $this->rawMessage;
+ }
+
+ /**
+ * Gets the filename where the error occurred.
+ *
+ * @return string The filename
+ */
+ public function getTemplateFile()
+ {
+ return $this->filename;
+ }
+
+ /**
+ * Sets the filename where the error occurred.
+ *
+ * @param string $filename The filename
+ */
+ public function setTemplateFile($filename)
+ {
+ $this->filename = $filename;
+
+ $this->updateRepr();
+ }
+
+ /**
+ * Gets the template line where the error occurred.
+ *
+ * @return integer The template line
+ */
+ public function getTemplateLine()
+ {
+ return $this->lineno;
+ }
+
+ /**
+ * Sets the template line where the error occurred.
+ *
+ * @param integer $lineno The template line
+ */
+ public function setTemplateLine($lineno)
+ {
+ $this->lineno = $lineno;
+
+ $this->updateRepr();
+ }
+
+ /**
+ * For PHP < 5.3.0, provides access to the getPrevious() method.
+ *
+ * @param string $method The method name
+ * @param array $arguments The parameters to be passed to the method
+ *
+ * @return Exception The previous exception or null
+ */
+ public function __call($method, $arguments)
+ {
+ if ('getprevious' == strtolower($method)) {
+ return $this->previous;
+ }
+
+ throw new BadMethodCallException(sprintf('Method "Twig_Error::%s()" does not exist.', $method));
+ }
+
+ protected function updateRepr()
+ {
+ $this->message = $this->rawMessage;
+
+ $dot = false;
+ if ('.' === substr($this->message, -1)) {
+ $this->message = substr($this->message, 0, -1);
+ $dot = true;
+ }
+
+ if (null !== $this->filename) {
+ if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) {
+ $filename = sprintf('"%s"', $this->filename);
+ } else {
+ $filename = json_encode($this->filename);
+ }
+ $this->message .= sprintf(' in %s', $filename);
+ }
+
+ if ($this->lineno >= 0) {
+ $this->message .= sprintf(' at line %d', $this->lineno);
+ }
+
+ if ($dot) {
+ $this->message .= '.';
+ }
+ }
+
+ protected function guessTemplateInfo()
+ {
+ $template = null;
+ foreach (debug_backtrace() as $trace) {
+ if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) {
+ $template = $trace['object'];
+
+ // update template filename
+ if (null === $this->filename) {
+ $this->filename = $template->getTemplateName();
+ }
+
+ break;
+ }
+ }
+
+ if (null === $template || $this->lineno > -1) {
+ return;
+ }
+
+ $r = new ReflectionObject($template);
+ $file = $r->getFileName();
+
+ $exceptions = array($e = $this);
+ while (($e instanceof self || method_exists($e, 'getPrevious')) && $e = $e->getPrevious()) {
+ $exceptions[] = $e;
+ }
+
+ while ($e = array_pop($exceptions)) {
+ $traces = $e->getTrace();
+ while ($trace = array_shift($traces)) {
+ if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
+ continue;
+ }
+
+ foreach ($template->getDebugInfo() as $codeLine => $templateLine) {
+ if ($codeLine <= $trace['line']) {
+ // update template line
+ $this->lineno = $templateLine;
+
+ return;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error/Loader.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error/Loader.php
new file mode 100644
index 0000000..418a776
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error/Loader.php
@@ -0,0 +1,20 @@
+
+ */
+class Twig_Error_Loader extends Twig_Error
+{
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error/Runtime.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error/Runtime.php
new file mode 100644
index 0000000..8a387fa
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error/Runtime.php
@@ -0,0 +1,21 @@
+
+ */
+class Twig_Error_Runtime extends Twig_Error
+{
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error/Syntax.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error/Syntax.php
new file mode 100644
index 0000000..a2650c3
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Error/Syntax.php
@@ -0,0 +1,21 @@
+
+ */
+class Twig_Error_Syntax extends Twig_Error
+{
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/ExpressionParser.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/ExpressionParser.php
new file mode 100644
index 0000000..0f35930
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/ExpressionParser.php
@@ -0,0 +1,488 @@
+
+ */
+class Twig_ExpressionParser
+{
+ const OPERATOR_LEFT = 1;
+ const OPERATOR_RIGHT = 2;
+
+ protected $parser;
+ protected $unaryOperators;
+ protected $binaryOperators;
+
+ public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators)
+ {
+ $this->parser = $parser;
+ $this->unaryOperators = $unaryOperators;
+ $this->binaryOperators = $binaryOperators;
+ }
+
+ public function parseExpression($precedence = 0)
+ {
+ $expr = $this->getPrimary();
+ $token = $this->parser->getCurrentToken();
+ while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
+ $op = $this->binaryOperators[$token->getValue()];
+ $this->parser->getStream()->next();
+
+ if (isset($op['callable'])) {
+ $expr = call_user_func($op['callable'], $this->parser, $expr);
+ } else {
+ $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
+ $class = $op['class'];
+ $expr = new $class($expr, $expr1, $token->getLine());
+ }
+
+ $token = $this->parser->getCurrentToken();
+ }
+
+ if (0 === $precedence) {
+ return $this->parseConditionalExpression($expr);
+ }
+
+ return $expr;
+ }
+
+ protected function getPrimary()
+ {
+ $token = $this->parser->getCurrentToken();
+
+ if ($this->isUnary($token)) {
+ $operator = $this->unaryOperators[$token->getValue()];
+ $this->parser->getStream()->next();
+ $expr = $this->parseExpression($operator['precedence']);
+ $class = $operator['class'];
+
+ return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
+ } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+ $this->parser->getStream()->next();
+ $expr = $this->parseExpression();
+ $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
+
+ return $this->parsePostfixExpression($expr);
+ }
+
+ return $this->parsePrimaryExpression();
+ }
+
+ protected function parseConditionalExpression($expr)
+ {
+ while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) {
+ $this->parser->getStream()->next();
+ $expr2 = $this->parseExpression();
+ $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'The ternary operator must have a default value');
+ $expr3 = $this->parseExpression();
+
+ $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
+ }
+
+ return $expr;
+ }
+
+ protected function isUnary(Twig_Token $token)
+ {
+ return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
+ }
+
+ protected function isBinary(Twig_Token $token)
+ {
+ return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
+ }
+
+ public function parsePrimaryExpression()
+ {
+ $token = $this->parser->getCurrentToken();
+ switch ($token->getType()) {
+ case Twig_Token::NAME_TYPE:
+ $this->parser->getStream()->next();
+ switch ($token->getValue()) {
+ case 'true':
+ case 'TRUE':
+ $node = new Twig_Node_Expression_Constant(true, $token->getLine());
+ break;
+
+ case 'false':
+ case 'FALSE':
+ $node = new Twig_Node_Expression_Constant(false, $token->getLine());
+ break;
+
+ case 'none':
+ case 'NONE':
+ case 'null':
+ case 'NULL':
+ $node = new Twig_Node_Expression_Constant(null, $token->getLine());
+ break;
+
+ default:
+ if ('(' === $this->parser->getCurrentToken()->getValue()) {
+ $node = $this->getFunctionNode($token->getValue(), $token->getLine());
+ } else {
+ $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
+ }
+ }
+ break;
+
+ case Twig_Token::NUMBER_TYPE:
+ $this->parser->getStream()->next();
+ $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
+ break;
+
+ case Twig_Token::STRING_TYPE:
+ case Twig_Token::INTERPOLATION_START_TYPE:
+ $node = $this->parseStringExpression();
+ break;
+
+ default:
+ if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
+ $node = $this->parseArrayExpression();
+ } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
+ $node = $this->parseHashExpression();
+ } else {
+ throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine());
+ }
+ }
+
+ return $this->parsePostfixExpression($node);
+ }
+
+ public function parseStringExpression()
+ {
+ $stream = $this->parser->getStream();
+
+ $nodes = array();
+ // a string cannot be followed by another string in a single expression
+ $nextCanBeString = true;
+ while (true) {
+ if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) {
+ $token = $stream->next();
+ $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
+ $nextCanBeString = false;
+ } elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) {
+ $stream->next();
+ $nodes[] = $this->parseExpression();
+ $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
+ $nextCanBeString = true;
+ } else {
+ break;
+ }
+ }
+
+ $expr = array_shift($nodes);
+ foreach ($nodes as $node) {
+ $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine());
+ }
+
+ return $expr;
+ }
+
+ public function parseArrayExpression()
+ {
+ $stream = $this->parser->getStream();
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
+
+ $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
+ $first = true;
+ while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
+ if (!$first) {
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
+
+ // trailing ,?
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
+ break;
+ }
+ }
+ $first = false;
+
+ $node->addElement($this->parseExpression());
+ }
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
+
+ return $node;
+ }
+
+ public function parseHashExpression()
+ {
+ $stream = $this->parser->getStream();
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
+
+ $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
+ $first = true;
+ while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
+ if (!$first) {
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
+
+ // trailing ,?
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
+ break;
+ }
+ }
+ $first = false;
+
+ // a hash key can be:
+ //
+ // * a number -- 12
+ // * a string -- 'a'
+ // * a name, which is equivalent to a string -- a
+ // * an expression, which must be enclosed in parentheses -- (1 + 2)
+ if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) {
+ $token = $stream->next();
+ $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
+ } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+ $key = $this->parseExpression();
+ } else {
+ $current = $stream->getCurrent();
+
+ throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine());
+ }
+
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
+ $value = $this->parseExpression();
+
+ $node->addElement($value, $key);
+ }
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
+
+ return $node;
+ }
+
+ public function parsePostfixExpression($node)
+ {
+ while (true) {
+ $token = $this->parser->getCurrentToken();
+ if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) {
+ if ('.' == $token->getValue() || '[' == $token->getValue()) {
+ $node = $this->parseSubscriptExpression($node);
+ } elseif ('|' == $token->getValue()) {
+ $node = $this->parseFilterExpression($node);
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ return $node;
+ }
+
+ public function getFunctionNode($name, $line)
+ {
+ $args = $this->parseArguments();
+ switch ($name) {
+ case 'parent':
+ if (!count($this->parser->getBlockStack())) {
+ throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line);
+ }
+
+ if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
+ throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line);
+ }
+
+ return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
+ case 'block':
+ return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $line);
+ case 'attribute':
+ if (count($args) < 2) {
+ throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line);
+ }
+
+ return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line);
+ default:
+ if (null !== $alias = $this->parser->getImportedFunction($name)) {
+ $arguments = new Twig_Node_Expression_Array(array(), $line);
+ foreach ($args as $n) {
+ $arguments->addElement($n);
+ }
+
+ $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
+ $node->setAttribute('safe', true);
+
+ return $node;
+ }
+
+ $class = $this->getFunctionNodeClass($name);
+
+ return new $class($name, $args, $line);
+ }
+ }
+
+ public function parseSubscriptExpression($node)
+ {
+ $stream = $this->parser->getStream();
+ $token = $stream->next();
+ $lineno = $token->getLine();
+ $arguments = new Twig_Node_Expression_Array(array(), $lineno);
+ $type = Twig_TemplateInterface::ANY_CALL;
+ if ($token->getValue() == '.') {
+ $token = $stream->next();
+ if (
+ $token->getType() == Twig_Token::NAME_TYPE
+ ||
+ $token->getType() == Twig_Token::NUMBER_TYPE
+ ||
+ ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
+ ) {
+ $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
+
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+ $type = Twig_TemplateInterface::METHOD_CALL;
+ foreach ($this->parseArguments() as $n) {
+ $arguments->addElement($n);
+ }
+ }
+ } else {
+ throw new Twig_Error_Syntax('Expected name or number', $lineno);
+ }
+ } else {
+ $type = Twig_TemplateInterface::ARRAY_CALL;
+
+ $arg = $this->parseExpression();
+
+ // slice?
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
+ $stream->next();
+
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
+ $length = new Twig_Node_Expression_Constant(null, $token->getLine());
+ } else {
+ $length = $this->parseExpression();
+ }
+
+ $class = $this->getFilterNodeClass('slice');
+ $arguments = new Twig_Node(array($arg, $length));
+ $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
+
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
+
+ return $filter;
+ }
+
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
+ }
+
+ return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
+ }
+
+ public function parseFilterExpression($node)
+ {
+ $this->parser->getStream()->next();
+
+ return $this->parseFilterExpressionRaw($node);
+ }
+
+ public function parseFilterExpressionRaw($node, $tag = null)
+ {
+ while (true) {
+ $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
+
+ $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
+ if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+ $arguments = new Twig_Node();
+ } else {
+ $arguments = $this->parseArguments();
+ }
+
+ $class = $this->getFilterNodeClass($name->getAttribute('value'));
+
+ $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
+
+ if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
+ break;
+ }
+
+ $this->parser->getStream()->next();
+ }
+
+ return $node;
+ }
+
+ public function parseArguments()
+ {
+ $args = array();
+ $stream = $this->parser->getStream();
+
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must be opened by a parenthesis');
+ while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
+ if (!empty($args)) {
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
+ }
+ $args[] = $this->parseExpression();
+ }
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
+
+ return new Twig_Node($args);
+ }
+
+ public function parseAssignmentExpression()
+ {
+ $targets = array();
+ while (true) {
+ $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
+ if (in_array($token->getValue(), array('true', 'false', 'none'))) {
+ throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine());
+ }
+ $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
+
+ if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
+ break;
+ }
+ $this->parser->getStream()->next();
+ }
+
+ return new Twig_Node($targets);
+ }
+
+ public function parseMultitargetExpression()
+ {
+ $targets = array();
+ while (true) {
+ $targets[] = $this->parseExpression();
+ if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
+ break;
+ }
+ $this->parser->getStream()->next();
+ }
+
+ return new Twig_Node($targets);
+ }
+
+ protected function getFunctionNodeClass($name)
+ {
+ $functionMap = $this->parser->getEnvironment()->getFunctions();
+ if (isset($functionMap[$name]) && $functionMap[$name] instanceof Twig_Function_Node) {
+ return $functionMap[$name]->getClass();
+ }
+
+ return 'Twig_Node_Expression_Function';
+ }
+
+ protected function getFilterNodeClass($name)
+ {
+ $filterMap = $this->parser->getEnvironment()->getFilters();
+ if (isset($filterMap[$name]) && $filterMap[$name] instanceof Twig_Filter_Node) {
+ return $filterMap[$name]->getClass();
+ }
+
+ return 'Twig_Node_Expression_Filter';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension.php
new file mode 100644
index 0000000..931fc03
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension.php
@@ -0,0 +1,93 @@
+dateFormats[0] = $format;
+ }
+
+ if (null !== $dateIntervalFormat) {
+ $this->dateFormats[1] = $dateIntervalFormat;
+ }
+ }
+
+ /**
+ * Gets the default format to be used by the date filter.
+ *
+ * @return array The default date format string and the default date interval format string
+ */
+ public function getDateFormat()
+ {
+ return $this->dateFormats;
+ }
+
+ /**
+ * Sets the default timezone to be used by the date filter.
+ *
+ * @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object
+ */
+ public function setTimezone($timezone)
+ {
+ $this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
+ }
+
+ /**
+ * Gets the default timezone to be used by the date filter.
+ *
+ * @return DateTimeZone The default timezone currently in use
+ */
+ public function getTimezone()
+ {
+ return $this->timezone;
+ }
+
+ /**
+ * Sets the default format to be used by the number_format filter.
+ *
+ * @param integer $decimal The number of decimal places to use.
+ * @param string $decimalPoint The character(s) to use for the decimal point.
+ * @param string $thousandSep The character(s) to use for the thousands separator.
+ */
+ public function setNumberFormat($decimal, $decimalPoint, $thousandSep)
+ {
+ $this->numberFormat = array($decimal, $decimalPoint, $thousandSep);
+ }
+
+ /**
+ * Get the default format used by the number_format filter.
+ *
+ * @return array The arguments for number_format()
+ */
+ public function getNumberFormat()
+ {
+ return $this->numberFormat;
+ }
+
+ /**
+ * Returns the token parser instance to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParser instances
+ */
+ public function getTokenParsers()
+ {
+ return array(
+ new Twig_TokenParser_For(),
+ new Twig_TokenParser_If(),
+ new Twig_TokenParser_Extends(),
+ new Twig_TokenParser_Include(),
+ new Twig_TokenParser_Block(),
+ new Twig_TokenParser_Use(),
+ new Twig_TokenParser_Filter(),
+ new Twig_TokenParser_Macro(),
+ new Twig_TokenParser_Import(),
+ new Twig_TokenParser_From(),
+ new Twig_TokenParser_Set(),
+ new Twig_TokenParser_Spaceless(),
+ new Twig_TokenParser_Flush(),
+ new Twig_TokenParser_Do(),
+ new Twig_TokenParser_Embed(),
+ );
+ }
+
+ /**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ public function getFilters()
+ {
+ $filters = array(
+ // formatting filters
+ 'date' => new Twig_Filter_Function('twig_date_format_filter', array('needs_environment' => true)),
+ 'format' => new Twig_Filter_Function('sprintf'),
+ 'replace' => new Twig_Filter_Function('strtr'),
+ 'number_format' => new Twig_Filter_Function('twig_number_format_filter', array('needs_environment' => true)),
+
+ // encoding
+ 'url_encode' => new Twig_Filter_Function('twig_urlencode_filter'),
+ 'json_encode' => new Twig_Filter_Function('twig_jsonencode_filter'),
+ 'convert_encoding' => new Twig_Filter_Function('twig_convert_encoding'),
+
+ // string filters
+ 'title' => new Twig_Filter_Function('twig_title_string_filter', array('needs_environment' => true)),
+ 'capitalize' => new Twig_Filter_Function('twig_capitalize_string_filter', array('needs_environment' => true)),
+ 'upper' => new Twig_Filter_Function('strtoupper'),
+ 'lower' => new Twig_Filter_Function('strtolower'),
+ 'striptags' => new Twig_Filter_Function('strip_tags'),
+ 'trim' => new Twig_Filter_Function('trim'),
+ 'nl2br' => new Twig_Filter_Function('nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
+
+ // array helpers
+ 'join' => new Twig_Filter_Function('twig_join_filter'),
+ 'sort' => new Twig_Filter_Function('twig_sort_filter'),
+ 'merge' => new Twig_Filter_Function('twig_array_merge'),
+
+ // string/array filters
+ 'reverse' => new Twig_Filter_Function('twig_reverse_filter', array('needs_environment' => true)),
+ 'length' => new Twig_Filter_Function('twig_length_filter', array('needs_environment' => true)),
+ 'slice' => new Twig_Filter_Function('twig_slice', array('needs_environment' => true)),
+
+ // iteration and runtime
+ 'default' => new Twig_Filter_Node('Twig_Node_Expression_Filter_Default'),
+ '_default' => new Twig_Filter_Function('_twig_default_filter'),
+
+ 'keys' => new Twig_Filter_Function('twig_get_array_keys_filter'),
+
+ // escaping
+ 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
+ 'e' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
+ );
+
+ if (function_exists('mb_get_info')) {
+ $filters['upper'] = new Twig_Filter_Function('twig_upper_filter', array('needs_environment' => true));
+ $filters['lower'] = new Twig_Filter_Function('twig_lower_filter', array('needs_environment' => true));
+ }
+
+ return $filters;
+ }
+
+ /**
+ * Returns a list of global functions to add to the existing list.
+ *
+ * @return array An array of global functions
+ */
+ public function getFunctions()
+ {
+ return array(
+ 'range' => new Twig_Function_Function('range'),
+ 'constant' => new Twig_Function_Function('constant'),
+ 'cycle' => new Twig_Function_Function('twig_cycle'),
+ 'random' => new Twig_Function_Function('twig_random', array('needs_environment' => true)),
+ 'date' => new Twig_Function_Function('twig_date_converter', array('needs_environment' => true)),
+ );
+ }
+
+ /**
+ * Returns a list of tests to add to the existing list.
+ *
+ * @return array An array of tests
+ */
+ public function getTests()
+ {
+ return array(
+ 'even' => new Twig_Test_Node('Twig_Node_Expression_Test_Even'),
+ 'odd' => new Twig_Test_Node('Twig_Node_Expression_Test_Odd'),
+ 'defined' => new Twig_Test_Node('Twig_Node_Expression_Test_Defined'),
+ 'sameas' => new Twig_Test_Node('Twig_Node_Expression_Test_Sameas'),
+ 'none' => new Twig_Test_Node('Twig_Node_Expression_Test_Null'),
+ 'null' => new Twig_Test_Node('Twig_Node_Expression_Test_Null'),
+ 'divisibleby' => new Twig_Test_Node('Twig_Node_Expression_Test_Divisibleby'),
+ 'constant' => new Twig_Test_Node('Twig_Node_Expression_Test_Constant'),
+ 'empty' => new Twig_Test_Function('twig_test_empty'),
+ 'iterable' => new Twig_Test_Function('twig_test_iterable'),
+ );
+ }
+
+ /**
+ * Returns a list of operators to add to the existing list.
+ *
+ * @return array An array of operators
+ */
+ public function getOperators()
+ {
+ return array(
+ array(
+ 'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
+ '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'),
+ '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
+ ),
+ array(
+ 'b-and' => array('precedence' => 5, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ 'b-xor' => array('precedence' => 5, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ 'b-or' => array('precedence' => 5, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
+ '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
+ ),
+ );
+ }
+
+ public function parseNotTestExpression(Twig_Parser $parser, $node)
+ {
+ return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine());
+ }
+
+ public function parseTestExpression(Twig_Parser $parser, $node)
+ {
+ $stream = $parser->getStream();
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
+ $arguments = null;
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
+ $arguments = $parser->getExpressionParser()->parseArguments();
+ }
+
+ $class = $this->getTestNodeClass($parser->getEnvironment(), $name);
+
+ return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine());
+ }
+
+ protected function getTestNodeClass(Twig_Environment $env, $name)
+ {
+ $testMap = $env->getTests();
+ if (isset($testMap[$name]) && $testMap[$name] instanceof Twig_Test_Node) {
+ return $testMap[$name]->getClass();
+ }
+
+ return 'Twig_Node_Expression_Test';
+ }
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName()
+ {
+ return 'core';
+ }
+}
+
+/**
+ * Cycles over a value.
+ *
+ * @param ArrayAccess|array $values An array or an ArrayAccess instance
+ * @param integer $i The cycle value
+ *
+ * @return string The next value in the cycle
+ */
+function twig_cycle($values, $i)
+{
+ if (!is_array($values) && !$values instanceof ArrayAccess) {
+ return $values;
+ }
+
+ return $values[$i % count($values)];
+}
+
+/**
+ * Returns a random value depending on the supplied parameter type:
+ * - a random item from a Traversable or array
+ * - a random character from a string
+ * - a random integer between 0 and the integer parameter
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param Traversable|array|int|string $values The values to pick a random item from
+ *
+ * @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is).
+ *
+ * @return mixed A random value from the given sequence
+ */
+function twig_random(Twig_Environment $env, $values = null)
+{
+ if (null === $values) {
+ return mt_rand();
+ }
+
+ if (is_int($values) || is_float($values)) {
+ return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values);
+ }
+
+ if ($values instanceof Traversable) {
+ $values = iterator_to_array($values);
+ } elseif (is_string($values)) {
+ if ('' === $values) {
+ return '';
+ }
+ if (null !== $charset = $env->getCharset()) {
+ if ('UTF-8' != $charset) {
+ $values = twig_convert_encoding($values, 'UTF-8', $charset);
+ }
+
+ // unicode version of str_split()
+ // split at all positions, but not after the start and not before the end
+ $values = preg_split('/(? $value) {
+ $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8');
+ }
+ }
+ } else {
+ return $values[mt_rand(0, strlen($values) - 1)];
+ }
+ }
+
+ if (!is_array($values)) {
+ return $values;
+ }
+
+ if (0 === count($values)) {
+ throw new Twig_Error_Runtime('The random function cannot pick from an empty array.');
+ }
+
+ return $values[array_rand($values, 1)];
+}
+
+/**
+ * Converts a date to the given format.
+ *
+ *
+ * {{ post.published_at|date("m/d/Y") }}
+ *
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param DateTime|DateInterval|string $date A date
+ * @param string $format A format
+ * @param DateTimeZone|string $timezone A timezone
+ *
+ * @return string The formatter date
+ */
+function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null)
+{
+ if (null === $format) {
+ $formats = $env->getExtension('core')->getDateFormat();
+ $format = $date instanceof DateInterval ? $formats[1] : $formats[0];
+ }
+
+ if ($date instanceof DateInterval || $date instanceof DateTime) {
+ if (null !== $timezone) {
+ $date = clone $date;
+ $date->setTimezone($timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone));
+ }
+
+ return $date->format($format);
+ }
+
+ return twig_date_converter($env, $date, $timezone)->format($format);
+}
+
+/**
+ * Converts an input to a DateTime instance.
+ *
+ *
+ *
+ * @param array $value An array
+ * @param string $glue The separator
+ *
+ * @return string The concatenated string
+ */
+function twig_join_filter($value, $glue = '')
+{
+ if ($value instanceof Traversable) {
+ $value = iterator_to_array($value, false);
+ }
+
+ return implode($glue, (array) $value);
+}
+
+// The '_default' filter is used internally to avoid using the ternary operator
+// which costs a lot for big contexts (before PHP 5.4). So, on average,
+// a function call is cheaper.
+function _twig_default_filter($value, $default = '')
+{
+ if (twig_test_empty($value)) {
+ return $default;
+ }
+
+ return $value;
+}
+
+/**
+ * Returns the keys for the given array.
+ *
+ * It is useful when you want to iterate over the keys of an array:
+ *
+ *
+ * {% for key in array|keys %}
+ * {# ... #}
+ * {% endfor %}
+ *
+ *
+ * @param array $array An array
+ *
+ * @return array The keys
+ */
+function twig_get_array_keys_filter($array)
+{
+ if (is_object($array) && $array instanceof Traversable) {
+ return array_keys(iterator_to_array($array));
+ }
+
+ if (!is_array($array)) {
+ return array();
+ }
+
+ return array_keys($array);
+}
+
+/**
+ * Reverses a variable.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param array|Traversable|string $item An array, a Traversable instance, or a string
+ * @param Boolean $preserveKeys Whether to preserve key or not
+ *
+ * @return mixed The reversed input
+ */
+function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false)
+{
+ if (is_object($item) && $item instanceof Traversable) {
+ return array_reverse(iterator_to_array($item), $preserveKeys);
+ }
+
+ if (is_array($item)) {
+ return array_reverse($item, $preserveKeys);
+ }
+
+ if (null !== $charset = $env->getCharset()) {
+ $string = (string) $item;
+
+ if ('UTF-8' != $charset) {
+ $item = twig_convert_encoding($string, 'UTF-8', $charset);
+ }
+
+ preg_match_all('/./us', $item, $matches);
+
+ $string = implode('', array_reverse($matches[0]));
+
+ if ('UTF-8' != $charset) {
+ $string = twig_convert_encoding($string, $charset, 'UTF-8');
+ }
+
+ return $string;
+ }
+
+ return strrev((string) $item);
+}
+
+/**
+ * Sorts an array.
+ *
+ * @param array $array An array
+ */
+function twig_sort_filter($array)
+{
+ asort($array);
+
+ return $array;
+}
+
+/* used internally */
+function twig_in_filter($value, $compare)
+{
+ if (is_array($compare)) {
+ return in_array($value, $compare);
+ } elseif (is_string($compare)) {
+ if (!strlen((string) $value)) {
+ return empty($compare);
+ }
+
+ return false !== strpos($compare, (string) $value);
+ } elseif (is_object($compare) && $compare instanceof Traversable) {
+ return in_array($value, iterator_to_array($compare, false));
+ }
+
+ return false;
+}
+
+/**
+ * Escapes a string.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param string $string The value to be escaped
+ * @param string $strategy The escaping strategy
+ * @param string $charset The charset
+ * @param Boolean $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
+ */
+function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
+{
+ if ($autoescape && is_object($string) && $string instanceof Twig_Markup) {
+ return $string;
+ }
+
+ if (!is_string($string) && !(is_object($string) && method_exists($string, '__toString'))) {
+ return $string;
+ }
+
+ if (null === $charset) {
+ $charset = $env->getCharset();
+ }
+
+ $string = (string) $string;
+
+ switch ($strategy) {
+ case 'js':
+ // escape all non-alphanumeric characters
+ // into their \xHH or \uHHHH representations
+ if ('UTF-8' != $charset) {
+ $string = twig_convert_encoding($string, 'UTF-8', $charset);
+ }
+
+ if (null === $string = preg_replace_callback('#[^\p{L}\p{N} ]#u', '_twig_escape_js_callback', $string)) {
+ throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
+ }
+
+ if ('UTF-8' != $charset) {
+ $string = twig_convert_encoding($string, $charset, 'UTF-8');
+ }
+
+ return $string;
+
+ case 'html':
+ // see http://php.net/htmlspecialchars
+
+ // Using a static variable to avoid initializing the array
+ // each time the function is called. Moving the declaration on the
+ // top of the function slow downs other escaping strategies.
+ static $htmlspecialcharsCharsets = array(
+ 'iso-8859-1' => true, 'iso8859-1' => true,
+ 'iso-8859-15' => true, 'iso8859-15' => true,
+ 'utf-8' => true,
+ 'cp866' => true, 'ibm866' => true, '866' => true,
+ 'cp1251' => true, 'windows-1251' => true, 'win-1251' => true,
+ '1251' => true,
+ 'cp1252' => true, 'windows-1252' => true, '1252' => true,
+ 'koi8-r' => true, 'koi8-ru' => true, 'koi8r' => true,
+ 'big5' => true, '950' => true,
+ 'gb2312' => true, '936' => true,
+ 'big5-hkscs' => true,
+ 'shift_jis' => true, 'sjis' => true, '932' => true,
+ 'euc-jp' => true, 'eucjp' => true,
+ 'iso8859-5' => true, 'iso-8859-5' => true, 'macroman' => true,
+ );
+
+ if (isset($htmlspecialcharsCharsets[strtolower($charset)])) {
+ return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
+ }
+
+ $string = twig_convert_encoding($string, 'UTF-8', $charset);
+ $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+
+ return twig_convert_encoding($string, $charset, 'UTF-8');
+
+ default:
+ throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: html, js).', $strategy));
+ }
+}
+
+/* used internally */
+function twig_escape_filter_is_safe(Twig_Node $filterArgs)
+{
+ foreach ($filterArgs as $arg) {
+ if ($arg instanceof Twig_Node_Expression_Constant) {
+ return array($arg->getAttribute('value'));
+ }
+
+ return array();
+ }
+
+ return array('html');
+}
+
+if (function_exists('iconv')) {
+ function twig_convert_encoding($string, $to, $from)
+ {
+ return iconv($from, $to, $string);
+ }
+} elseif (function_exists('mb_convert_encoding')) {
+ function twig_convert_encoding($string, $to, $from)
+ {
+ return mb_convert_encoding($string, $to, $from);
+ }
+} else {
+ function twig_convert_encoding($string, $to, $from)
+ {
+ throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
+ }
+}
+
+function _twig_escape_js_callback($matches)
+{
+ $char = $matches[0];
+
+ // \xHH
+ if (!isset($char[1])) {
+ return '\\x'.substr('00'.bin2hex($char), -2);
+ }
+
+ // \uHHHH
+ $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
+
+ return '\\u'.substr('0000'.bin2hex($char), -4);
+}
+
+// add multibyte extensions if possible
+if (function_exists('mb_get_info')) {
+ /**
+ * Returns the length of a variable.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param mixed $thing A variable
+ *
+ * @return integer The length of the value
+ */
+ function twig_length_filter(Twig_Environment $env, $thing)
+ {
+ return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing);
+ }
+
+ /**
+ * Converts a string to uppercase.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param string $string A string
+ *
+ * @return string The uppercased string
+ */
+ function twig_upper_filter(Twig_Environment $env, $string)
+ {
+ if (null !== ($charset = $env->getCharset())) {
+ return mb_strtoupper($string, $charset);
+ }
+
+ return strtoupper($string);
+ }
+
+ /**
+ * Converts a string to lowercase.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param string $string A string
+ *
+ * @return string The lowercased string
+ */
+ function twig_lower_filter(Twig_Environment $env, $string)
+ {
+ if (null !== ($charset = $env->getCharset())) {
+ return mb_strtolower($string, $charset);
+ }
+
+ return strtolower($string);
+ }
+
+ /**
+ * Returns a titlecased string.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param string $string A string
+ *
+ * @return string The titlecased string
+ */
+ function twig_title_string_filter(Twig_Environment $env, $string)
+ {
+ if (null !== ($charset = $env->getCharset())) {
+ return mb_convert_case($string, MB_CASE_TITLE, $charset);
+ }
+
+ return ucwords(strtolower($string));
+ }
+
+ /**
+ * Returns a capitalized string.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param string $string A string
+ *
+ * @return string The capitalized string
+ */
+ function twig_capitalize_string_filter(Twig_Environment $env, $string)
+ {
+ if (null !== ($charset = $env->getCharset())) {
+ return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).
+ mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset);
+ }
+
+ return ucfirst(strtolower($string));
+ }
+}
+// and byte fallback
+else
+{
+ /**
+ * Returns the length of a variable.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param mixed $thing A variable
+ *
+ * @return integer The length of the value
+ */
+ function twig_length_filter(Twig_Environment $env, $thing)
+ {
+ return is_scalar($thing) ? strlen($thing) : count($thing);
+ }
+
+ /**
+ * Returns a titlecased string.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param string $string A string
+ *
+ * @return string The titlecased string
+ */
+ function twig_title_string_filter(Twig_Environment $env, $string)
+ {
+ return ucwords(strtolower($string));
+ }
+
+ /**
+ * Returns a capitalized string.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param string $string A string
+ *
+ * @return string The capitalized string
+ */
+ function twig_capitalize_string_filter(Twig_Environment $env, $string)
+ {
+ return ucfirst(strtolower($string));
+ }
+}
+
+/* used internally */
+function twig_ensure_traversable($seq)
+{
+ if ($seq instanceof Traversable || is_array($seq)) {
+ return $seq;
+ }
+
+ return array();
+}
+
+/**
+ * Checks if a variable is empty.
+ *
+ *
+ * {# evaluates to true if the foo variable is null, false, or the empty string #}
+ * {% if foo is empty %}
+ * {# ... #}
+ * {% endif %}
+ *
+ *
+ * @param mixed $value A variable
+ *
+ * @return Boolean true if the value is empty, false otherwise
+ */
+function twig_test_empty($value)
+{
+ if ($value instanceof Countable) {
+ return 0 == count($value);
+ }
+
+ return false === $value || (empty($value) && '0' != $value);
+}
+
+/**
+ * Checks if a variable is traversable.
+ *
+ *
+ * {# evaluates to true if the foo variable is an array or a traversable object #}
+ * {% if foo is traversable %}
+ * {# ... #}
+ * {% endif %}
+ *
+ *
+ * @param mixed $value A variable
+ *
+ * @return Boolean true if the value is traversable
+ */
+function twig_test_iterable($value)
+{
+ return $value instanceof Traversable || is_array($value);
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Debug.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Debug.php
new file mode 100644
index 0000000..aab7093
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Debug.php
@@ -0,0 +1,64 @@
+ new Twig_Function_Function('twig_var_dump', array('is_safe' => $isDumpOutputHtmlSafe ? array('html') : array(), 'needs_context' => true, 'needs_environment' => true)),
+ );
+ }
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName()
+ {
+ return 'debug';
+ }
+}
+
+function twig_var_dump(Twig_Environment $env, $context)
+{
+ if (!$env->isDebug()) {
+ return;
+ }
+
+ ob_start();
+
+ $count = func_num_args();
+ if (2 === $count) {
+ $vars = array();
+ foreach ($context as $key => $value) {
+ if (!$value instanceof Twig_Template) {
+ $vars[$key] = $value;
+ }
+ }
+
+ var_dump($vars);
+ } else {
+ for ($i = 2; $i < $count; $i++) {
+ var_dump(func_get_arg($i));
+ }
+ }
+
+ return ob_get_clean();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Escaper.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Escaper.php
new file mode 100644
index 0000000..b0aa8b1
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Escaper.php
@@ -0,0 +1,106 @@
+setDefaultStrategy($defaultStrategy);
+ }
+
+ /**
+ * Returns the token parser instances to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
+ */
+ public function getTokenParsers()
+ {
+ return array(new Twig_TokenParser_AutoEscape());
+ }
+
+ /**
+ * Returns the node visitor instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeVisitorInterface instances
+ */
+ public function getNodeVisitors()
+ {
+ return array(new Twig_NodeVisitor_Escaper());
+ }
+
+ /**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ public function getFilters()
+ {
+ return array(
+ 'raw' => new Twig_Filter_Function('twig_raw_filter', array('is_safe' => array('all'))),
+ );
+ }
+
+ /**
+ * Sets the default strategy to use when not defined by the user.
+ *
+ * The strategy can be a valid PHP callback that takes the template
+ * "filename" as an argument and returns the strategy to use.
+ *
+ * @param mixed $defaultStrategy An escaping strategy
+ */
+ public function setDefaultStrategy($defaultStrategy)
+ {
+ // for BC
+ if (true === $defaultStrategy) {
+ $defaultStrategy = 'html';
+ }
+
+ $this->defaultStrategy = $defaultStrategy;
+ }
+
+ /**
+ * Gets the default strategy to use when not defined by the user.
+ *
+ * @param string $filename The template "filename"
+ *
+ * @return string The default strategy to use for the template
+ */
+ public function getDefaultStrategy($filename)
+ {
+ if (is_callable($this->defaultStrategy)) {
+ return call_user_func($this->defaultStrategy, $filename);
+ }
+
+ return $this->defaultStrategy;
+ }
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName()
+ {
+ return 'escaper';
+ }
+}
+
+/**
+ * Marks a variable as being safe.
+ *
+ * @param string $string A PHP variable
+ */
+function twig_raw_filter($string)
+{
+ return $string;
+}
+
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Optimizer.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Optimizer.php
new file mode 100644
index 0000000..013fcb6
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Optimizer.php
@@ -0,0 +1,35 @@
+optimizers = $optimizers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNodeVisitors()
+ {
+ return array(new Twig_NodeVisitor_Optimizer($this->optimizers));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'optimizer';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Sandbox.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Sandbox.php
new file mode 100644
index 0000000..bf76c11
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Extension/Sandbox.php
@@ -0,0 +1,112 @@
+policy = $policy;
+ $this->sandboxedGlobally = $sandboxed;
+ }
+
+ /**
+ * Returns the token parser instances to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
+ */
+ public function getTokenParsers()
+ {
+ return array(new Twig_TokenParser_Sandbox());
+ }
+
+ /**
+ * Returns the node visitor instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeVisitorInterface instances
+ */
+ public function getNodeVisitors()
+ {
+ return array(new Twig_NodeVisitor_Sandbox());
+ }
+
+ public function enableSandbox()
+ {
+ $this->sandboxed = true;
+ }
+
+ public function disableSandbox()
+ {
+ $this->sandboxed = false;
+ }
+
+ public function isSandboxed()
+ {
+ return $this->sandboxedGlobally || $this->sandboxed;
+ }
+
+ public function isSandboxedGlobally()
+ {
+ return $this->sandboxedGlobally;
+ }
+
+ public function setSecurityPolicy(Twig_Sandbox_SecurityPolicyInterface $policy)
+ {
+ $this->policy = $policy;
+ }
+
+ public function getSecurityPolicy()
+ {
+ return $this->policy;
+ }
+
+ public function checkSecurity($tags, $filters, $functions)
+ {
+ if ($this->isSandboxed()) {
+ $this->policy->checkSecurity($tags, $filters, $functions);
+ }
+ }
+
+ public function checkMethodAllowed($obj, $method)
+ {
+ if ($this->isSandboxed()) {
+ $this->policy->checkMethodAllowed($obj, $method);
+ }
+ }
+
+ public function checkPropertyAllowed($obj, $method)
+ {
+ if ($this->isSandboxed()) {
+ $this->policy->checkPropertyAllowed($obj, $method);
+ }
+ }
+
+ public function ensureToStringAllowed($obj)
+ {
+ if (is_object($obj)) {
+ $this->policy->checkMethodAllowed($obj, '__toString');
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName()
+ {
+ return 'sandbox';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/ExtensionInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/ExtensionInterface.php
new file mode 100644
index 0000000..0bfed88
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/ExtensionInterface.php
@@ -0,0 +1,84 @@
+
+ */
+interface Twig_ExtensionInterface
+{
+ /**
+ * Initializes the runtime environment.
+ *
+ * This is where you can load some file that contains filter functions for instance.
+ *
+ * @param Twig_Environment $environment The current Twig_Environment instance
+ */
+ function initRuntime(Twig_Environment $environment);
+
+ /**
+ * Returns the token parser instances to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
+ */
+ function getTokenParsers();
+
+ /**
+ * Returns the node visitor instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeVisitorInterface instances
+ */
+ function getNodeVisitors();
+
+ /**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ function getFilters();
+
+ /**
+ * Returns a list of tests to add to the existing list.
+ *
+ * @return array An array of tests
+ */
+ function getTests();
+
+ /**
+ * Returns a list of functions to add to the existing list.
+ *
+ * @return array An array of functions
+ */
+ function getFunctions();
+
+ /**
+ * Returns a list of operators to add to the existing list.
+ *
+ * @return array An array of operators
+ */
+ function getOperators();
+
+ /**
+ * Returns a list of global variables to add to the existing list.
+ *
+ * @return array An array of global variables
+ */
+ function getGlobals();
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ function getName();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter.php
new file mode 100644
index 0000000..1a4806c
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter.php
@@ -0,0 +1,75 @@
+
+ */
+abstract class Twig_Filter implements Twig_FilterInterface
+{
+ protected $options;
+ protected $arguments = array();
+
+ public function __construct(array $options = array())
+ {
+ $this->options = array_merge(array(
+ 'needs_environment' => false,
+ 'needs_context' => false,
+ 'pre_escape' => null,
+ 'preserves_safety' => null,
+ ), $options);
+ }
+
+ public function setArguments($arguments)
+ {
+ $this->arguments = $arguments;
+ }
+
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ public function needsEnvironment()
+ {
+ return $this->options['needs_environment'];
+ }
+
+ public function needsContext()
+ {
+ return $this->options['needs_context'];
+ }
+
+ public function getSafe(Twig_Node $filterArgs)
+ {
+ if (isset($this->options['is_safe'])) {
+ return $this->options['is_safe'];
+ }
+
+ if (isset($this->options['is_safe_callback'])) {
+ return call_user_func($this->options['is_safe_callback'], $filterArgs);
+ }
+
+ return null;
+ }
+
+ public function getPreservesSafety()
+ {
+ return $this->options['preserves_safety'];
+ }
+
+ public function getPreEscape()
+ {
+ return $this->options['pre_escape'];
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter/Function.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter/Function.php
new file mode 100644
index 0000000..1de078b
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter/Function.php
@@ -0,0 +1,33 @@
+
+ */
+class Twig_Filter_Function extends Twig_Filter
+{
+ protected $function;
+
+ public function __construct($function, array $options = array())
+ {
+ parent::__construct($options);
+
+ $this->function = $function;
+ }
+
+ public function compile()
+ {
+ return $this->function;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter/Method.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter/Method.php
new file mode 100644
index 0000000..d831e0f
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter/Method.php
@@ -0,0 +1,34 @@
+
+ */
+class Twig_Filter_Method extends Twig_Filter
+{
+ protected $extension, $method;
+
+ public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
+ {
+ parent::__construct($options);
+
+ $this->extension = $extension;
+ $this->method = $method;
+ }
+
+ public function compile()
+ {
+ return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter/Node.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter/Node.php
new file mode 100644
index 0000000..7481c05
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Filter/Node.php
@@ -0,0 +1,37 @@
+
+ */
+class Twig_Filter_Node extends Twig_Filter
+{
+ protected $class;
+
+ public function __construct($class, array $options = array())
+ {
+ parent::__construct($options);
+
+ $this->class = $class;
+ }
+
+ public function getClass()
+ {
+ return $this->class;
+ }
+
+ public function compile()
+ {
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/FilterInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/FilterInterface.php
new file mode 100644
index 0000000..f398026
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/FilterInterface.php
@@ -0,0 +1,40 @@
+
+ */
+interface Twig_FilterInterface
+{
+ /**
+ * Compiles a filter.
+ *
+ * @return string The PHP code for the filter
+ */
+ function compile();
+
+ function needsEnvironment();
+
+ function needsContext();
+
+ function getSafe(Twig_Node $filterArgs);
+
+ function getPreservesSafety();
+
+ function getPreEscape();
+
+ function setArguments($arguments);
+
+ function getArguments();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function.php
new file mode 100644
index 0000000..cd7643f
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function.php
@@ -0,0 +1,63 @@
+
+ */
+abstract class Twig_Function implements Twig_FunctionInterface
+{
+ protected $options;
+ protected $arguments = array();
+
+ public function __construct(array $options = array())
+ {
+ $this->options = array_merge(array(
+ 'needs_environment' => false,
+ 'needs_context' => false,
+ ), $options);
+ }
+
+ public function setArguments($arguments)
+ {
+ $this->arguments = $arguments;
+ }
+
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ public function needsEnvironment()
+ {
+ return $this->options['needs_environment'];
+ }
+
+ public function needsContext()
+ {
+ return $this->options['needs_context'];
+ }
+
+ public function getSafe(Twig_Node $functionArgs)
+ {
+ if (isset($this->options['is_safe'])) {
+ return $this->options['is_safe'];
+ }
+
+ if (isset($this->options['is_safe_callback'])) {
+ return call_user_func($this->options['is_safe_callback'], $functionArgs);
+ }
+
+ return array();
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function/Function.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function/Function.php
new file mode 100644
index 0000000..3237d8c
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function/Function.php
@@ -0,0 +1,34 @@
+
+ */
+class Twig_Function_Function extends Twig_Function
+{
+ protected $function;
+
+ public function __construct($function, array $options = array())
+ {
+ parent::__construct($options);
+
+ $this->function = $function;
+ }
+
+ public function compile()
+ {
+ return $this->function;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function/Method.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function/Method.php
new file mode 100644
index 0000000..7328566
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function/Method.php
@@ -0,0 +1,35 @@
+
+ */
+class Twig_Function_Method extends Twig_Function
+{
+ protected $extension, $method;
+
+ public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
+ {
+ parent::__construct($options);
+
+ $this->extension = $extension;
+ $this->method = $method;
+ }
+
+ public function compile()
+ {
+ return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function/Node.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function/Node.php
new file mode 100644
index 0000000..a687a84
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Function/Node.php
@@ -0,0 +1,37 @@
+
+ */
+class Twig_Function_Node extends Twig_Filter
+{
+ protected $class;
+
+ public function __construct($class, array $options = array())
+ {
+ parent::__construct($options);
+
+ $this->class = $class;
+ }
+
+ public function getClass()
+ {
+ return $this->class;
+ }
+
+ public function compile()
+ {
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/FunctionInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/FunctionInterface.php
new file mode 100644
index 0000000..d402d17
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/FunctionInterface.php
@@ -0,0 +1,37 @@
+
+ */
+interface Twig_FunctionInterface
+{
+ /**
+ * Compiles a function.
+ *
+ * @return string The PHP code for the function
+ */
+ function compile();
+
+ function needsEnvironment();
+
+ function needsContext();
+
+ function getSafe(Twig_Node $filterArgs);
+
+ function setArguments($arguments);
+
+ function getArguments();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Lexer.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Lexer.php
new file mode 100644
index 0000000..4d0ba9f
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Lexer.php
@@ -0,0 +1,406 @@
+
+ */
+class Twig_Lexer implements Twig_LexerInterface
+{
+ protected $tokens;
+ protected $code;
+ protected $cursor;
+ protected $lineno;
+ protected $end;
+ protected $state;
+ protected $states;
+ protected $brackets;
+ protected $env;
+ protected $filename;
+ protected $options;
+ protected $regexes;
+
+ const STATE_DATA = 0;
+ const STATE_BLOCK = 1;
+ const STATE_VAR = 2;
+ const STATE_STRING = 3;
+ const STATE_INTERPOLATION = 4;
+
+ const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
+ const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
+ const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
+ const REGEX_DQ_STRING_DELIM = '/"/A';
+ const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
+ const PUNCTUATION = '()[]{}?:.,|';
+
+ public function __construct(Twig_Environment $env, array $options = array())
+ {
+ $this->env = $env;
+
+ $this->options = array_merge(array(
+ 'tag_comment' => array('{#', '#}'),
+ 'tag_block' => array('{%', '%}'),
+ 'tag_variable' => array('{{', '}}'),
+ 'whitespace_trim' => '-',
+ 'interpolation' => array('#{', '}'),
+ ), $options);
+
+ $this->regexes = array(
+ 'lex_var' => '/\s*'.preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A',
+ 'lex_block' => '/\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')\n?/A',
+ 'lex_raw_data' => '/('.preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim'], '/').'|'.preg_quote($this->options['tag_block'][0], '/').')\s*endraw\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/s',
+ 'operator' => $this->getOperatorRegex(),
+ 'lex_comment' => '/(?:'.preg_quote($this->options['whitespace_trim'], '/').preg_quote($this->options['tag_comment'][1], '/').'\s*|'.preg_quote($this->options['tag_comment'][1], '/').')\n?/s',
+ 'lex_block_raw' => '/\s*raw\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/As',
+ 'lex_block_line' => '/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As',
+ 'lex_tokens_start' => '/('.preg_quote($this->options['tag_variable'][0], '/').'|'.preg_quote($this->options['tag_block'][0], '/').'|'.preg_quote($this->options['tag_comment'][0], '/').')('.preg_quote($this->options['whitespace_trim'], '/').')?/s',
+ 'interpolation_start' => '/'.preg_quote($this->options['interpolation'][0], '/').'\s*/A',
+ 'interpolation_end' => '/\s*'.preg_quote($this->options['interpolation'][1], '/').'/A',
+ );
+ }
+
+ /**
+ * Tokenizes a source code.
+ *
+ * @param string $code The source code
+ * @param string $filename A unique identifier for the source code
+ *
+ * @return Twig_TokenStream A token stream instance
+ */
+ public function tokenize($code, $filename = null)
+ {
+ if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
+ $mbEncoding = mb_internal_encoding();
+ mb_internal_encoding('ASCII');
+ }
+
+ $this->code = str_replace(array("\r\n", "\r"), "\n", $code);
+ $this->filename = $filename;
+ $this->cursor = 0;
+ $this->lineno = 1;
+ $this->end = strlen($this->code);
+ $this->tokens = array();
+ $this->state = self::STATE_DATA;
+ $this->states = array();
+ $this->brackets = array();
+ $this->position = -1;
+
+ // find all token starts in one go
+ preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE);
+ $this->positions = $matches;
+
+ while ($this->cursor < $this->end) {
+ // dispatch to the lexing functions depending
+ // on the current state
+ switch ($this->state) {
+ case self::STATE_DATA:
+ $this->lexData();
+ break;
+
+ case self::STATE_BLOCK:
+ $this->lexBlock();
+ break;
+
+ case self::STATE_VAR:
+ $this->lexVar();
+ break;
+
+ case self::STATE_STRING:
+ $this->lexString();
+ break;
+
+ case self::STATE_INTERPOLATION:
+ $this->lexInterpolation();
+ break;
+ }
+ }
+
+ $this->pushToken(Twig_Token::EOF_TYPE);
+
+ if (!empty($this->brackets)) {
+ list($expect, $lineno) = array_pop($this->brackets);
+ throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
+ }
+
+ if (isset($mbEncoding)) {
+ mb_internal_encoding($mbEncoding);
+ }
+
+ return new Twig_TokenStream($this->tokens, $this->filename);
+ }
+
+ protected function lexData()
+ {
+ // if no matches are left we return the rest of the template as simple text token
+ if ($this->position == count($this->positions[0]) - 1) {
+ $this->pushToken(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor));
+ $this->cursor = $this->end;
+
+ return;
+ }
+
+ // Find the first token after the current cursor
+ $position = $this->positions[0][++$this->position];
+ while ($position[1] < $this->cursor) {
+ if ($this->position == count($this->positions[0]) - 1) {
+ return;
+ }
+ $position = $this->positions[0][++$this->position];
+ }
+
+ // push the template text first
+ $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor);
+ if (isset($this->positions[2][$this->position][0])) {
+ $text = rtrim($text);
+ }
+ $this->pushToken(Twig_Token::TEXT_TYPE, $text);
+ $this->moveCursor($textContent.$position[0]);
+
+ switch ($this->positions[1][$this->position][0]) {
+ case $this->options['tag_comment'][0]:
+ $this->lexComment();
+ break;
+
+ case $this->options['tag_block'][0]:
+ // raw data?
+ if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, null, $this->cursor)) {
+ $this->moveCursor($match[0]);
+ $this->lexRawData();
+ // {% line \d+ %}
+ } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, null, $this->cursor)) {
+ $this->moveCursor($match[0]);
+ $this->lineno = (int) $match[1];
+ } else {
+ $this->pushToken(Twig_Token::BLOCK_START_TYPE);
+ $this->pushState(self::STATE_BLOCK);
+ }
+ break;
+
+ case $this->options['tag_variable'][0]:
+ $this->pushToken(Twig_Token::VAR_START_TYPE);
+ $this->pushState(self::STATE_VAR);
+ break;
+ }
+ }
+
+ protected function lexBlock()
+ {
+ if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, null, $this->cursor)) {
+ $this->pushToken(Twig_Token::BLOCK_END_TYPE);
+ $this->moveCursor($match[0]);
+ $this->popState();
+ } else {
+ $this->lexExpression();
+ }
+ }
+
+ protected function lexVar()
+ {
+ if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, null, $this->cursor)) {
+ $this->pushToken(Twig_Token::VAR_END_TYPE);
+ $this->moveCursor($match[0]);
+ $this->popState();
+ } else {
+ $this->lexExpression();
+ }
+ }
+
+ protected function lexExpression()
+ {
+ // whitespace
+ if (preg_match('/\s+/A', $this->code, $match, null, $this->cursor)) {
+ $this->moveCursor($match[0]);
+
+ if ($this->cursor >= $this->end) {
+ throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s"', $this->state === self::STATE_BLOCK ? 'block' : 'variable'), $this->lineno, $this->filename);
+ }
+ }
+
+ // operators
+ if (preg_match($this->regexes['operator'], $this->code, $match, null, $this->cursor)) {
+ $this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]);
+ $this->moveCursor($match[0]);
+ }
+ // names
+ elseif (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor)) {
+ $this->pushToken(Twig_Token::NAME_TYPE, $match[0]);
+ $this->moveCursor($match[0]);
+ }
+ // numbers
+ elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) {
+ $number = (float) $match[0]; // floats
+ if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) {
+ $number = (int) $match[0]; // integers lower than the maximum
+ }
+ $this->pushToken(Twig_Token::NUMBER_TYPE, $number);
+ $this->moveCursor($match[0]);
+ }
+ // punctuation
+ elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
+ // opening bracket
+ if (false !== strpos('([{', $this->code[$this->cursor])) {
+ $this->brackets[] = array($this->code[$this->cursor], $this->lineno);
+ }
+ // closing bracket
+ elseif (false !== strpos(')]}', $this->code[$this->cursor])) {
+ if (empty($this->brackets)) {
+ throw new Twig_Error_Syntax(sprintf('Unexpected "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename);
+ }
+
+ list($expect, $lineno) = array_pop($this->brackets);
+ if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) {
+ throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
+ }
+ }
+
+ $this->pushToken(Twig_Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);
+ ++$this->cursor;
+ }
+ // strings
+ elseif (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) {
+ $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)));
+ $this->moveCursor($match[0]);
+ }
+ // opening double quoted string
+ elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
+ $this->brackets[] = array('"', $this->lineno);
+ $this->pushState(self::STATE_STRING);
+ $this->moveCursor($match[0]);
+ }
+ // unlexable
+ else {
+ throw new Twig_Error_Syntax(sprintf('Unexpected character "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename);
+ }
+ }
+
+ protected function lexRawData()
+ {
+ if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
+ throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "block"'), $this->lineno, $this->filename);
+ }
+
+ $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
+ $this->moveCursor($text.$match[0][0]);
+
+ if (false !== strpos($match[1][0], $this->options['whitespace_trim'])) {
+ $text = rtrim($text);
+ }
+
+ $this->pushToken(Twig_Token::TEXT_TYPE, $text);
+ }
+
+ protected function lexComment()
+ {
+ if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
+ throw new Twig_Error_Syntax('Unclosed comment', $this->lineno, $this->filename);
+ }
+
+ $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]);
+ }
+
+ protected function lexString()
+ {
+ if (preg_match($this->regexes['interpolation_start'], $this->code, $match, null, $this->cursor)) {
+ $this->brackets[] = array($this->options['interpolation'][0], $this->lineno);
+ $this->pushToken(Twig_Token::INTERPOLATION_START_TYPE);
+ $this->moveCursor($match[0]);
+ $this->pushState(self::STATE_INTERPOLATION);
+
+ } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, null, $this->cursor) && strlen($match[0]) > 0) {
+ $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes($match[0]));
+ $this->moveCursor($match[0]);
+
+ } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
+
+ list($expect, $lineno) = array_pop($this->brackets);
+ if ($this->code[$this->cursor] != '"') {
+ throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
+ }
+
+ $this->popState();
+ ++$this->cursor;
+
+ return;
+ }
+ }
+
+ protected function lexInterpolation()
+ {
+ $bracket = end($this->brackets);
+ if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, null, $this->cursor)) {
+ array_pop($this->brackets);
+ $this->pushToken(Twig_Token::INTERPOLATION_END_TYPE);
+ $this->moveCursor($match[0]);
+ $this->popState();
+ } else {
+ $this->lexExpression();
+ }
+ }
+
+ protected function pushToken($type, $value = '')
+ {
+ // do not push empty text tokens
+ if (Twig_Token::TEXT_TYPE === $type && '' === $value) {
+ return;
+ }
+
+ $this->tokens[] = new Twig_Token($type, $value, $this->lineno);
+ }
+
+ protected function moveCursor($text)
+ {
+ $this->cursor += strlen($text);
+ $this->lineno += substr_count($text, "\n");
+ }
+
+ protected function getOperatorRegex()
+ {
+ $operators = array_merge(
+ array('='),
+ array_keys($this->env->getUnaryOperators()),
+ array_keys($this->env->getBinaryOperators())
+ );
+
+ $operators = array_combine($operators, array_map('strlen', $operators));
+ arsort($operators);
+
+ $regex = array();
+ foreach ($operators as $operator => $length) {
+ // an operator that ends with a character must be followed by
+ // a whitespace or a parenthesis
+ if (ctype_alpha($operator[$length - 1])) {
+ $regex[] = preg_quote($operator, '/').'(?=[\s()])';
+ } else {
+ $regex[] = preg_quote($operator, '/');
+ }
+ }
+
+ return '/'.implode('|', $regex).'/A';
+ }
+
+ protected function pushState($state)
+ {
+ $this->states[] = $this->state;
+ $this->state = $state;
+ }
+
+ protected function popState()
+ {
+ if (0 === count($this->states)) {
+ throw new Exception('Cannot pop state without a previous state');
+ }
+
+ $this->state = array_pop($this->states);
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/LexerInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/LexerInterface.php
new file mode 100644
index 0000000..0223384
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/LexerInterface.php
@@ -0,0 +1,29 @@
+
+ */
+interface Twig_LexerInterface
+{
+ /**
+ * Tokenizes a source code.
+ *
+ * @param string $code The source code
+ * @param string $filename A unique identifier for the source code
+ *
+ * @return Twig_TokenStream A token stream instance
+ */
+ function tokenize($code, $filename = null);
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/Array.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/Array.php
new file mode 100644
index 0000000..32bb7e4
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/Array.php
@@ -0,0 +1,102 @@
+
+ */
+class Twig_Loader_Array implements Twig_LoaderInterface
+{
+ protected $templates;
+
+ /**
+ * Constructor.
+ *
+ * @param array $templates An array of templates (keys are the names, and values are the source code)
+ *
+ * @see Twig_Loader
+ */
+ public function __construct(array $templates)
+ {
+ $this->templates = array();
+ foreach ($templates as $name => $template) {
+ $this->templates[$name] = $template;
+ }
+ }
+
+ /**
+ * Adds or overrides a template.
+ *
+ * @param string $name The template name
+ * @param string $template The template source
+ */
+ public function setTemplate($name, $template)
+ {
+ $this->templates[(string) $name] = $template;
+ }
+
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The template source code
+ */
+ public function getSource($name)
+ {
+ $name = (string) $name;
+ if (!isset($this->templates[$name])) {
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
+ }
+
+ return $this->templates[$name];
+ }
+
+ /**
+ * Gets the cache key to use for the cache for a given template name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The cache key
+ */
+ public function getCacheKey($name)
+ {
+ $name = (string) $name;
+ if (!isset($this->templates[$name])) {
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
+ }
+
+ return $this->templates[$name];
+ }
+
+ /**
+ * Returns true if the template is still fresh.
+ *
+ * @param string $name The template name
+ * @param timestamp $time The last modification time of the cached template
+ */
+ public function isFresh($name, $time)
+ {
+ $name = (string) $name;
+ if (!isset($this->templates[$name])) {
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
+ }
+
+ return true;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/Chain.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/Chain.php
new file mode 100644
index 0000000..48dd8b8
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/Chain.php
@@ -0,0 +1,100 @@
+
+ */
+class Twig_Loader_Chain implements Twig_LoaderInterface
+{
+ protected $loaders;
+
+ /**
+ * Constructor.
+ *
+ * @param Twig_LoaderInterface[] $loaders An array of loader instances
+ */
+ public function __construct(array $loaders = array())
+ {
+ $this->loaders = array();
+ foreach ($loaders as $loader) {
+ $this->addLoader($loader);
+ }
+ }
+
+ /**
+ * Adds a loader instance.
+ *
+ * @param Twig_LoaderInterface $loader A Loader instance
+ */
+ public function addLoader(Twig_LoaderInterface $loader)
+ {
+ $this->loaders[] = $loader;
+ }
+
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The template source code
+ */
+ public function getSource($name)
+ {
+ foreach ($this->loaders as $loader) {
+ try {
+ return $loader->getSource($name);
+ } catch (Twig_Error_Loader $e) {
+ }
+ }
+
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
+ }
+
+ /**
+ * Gets the cache key to use for the cache for a given template name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The cache key
+ */
+ public function getCacheKey($name)
+ {
+ foreach ($this->loaders as $loader) {
+ try {
+ return $loader->getCacheKey($name);
+ } catch (Twig_Error_Loader $e) {
+ }
+ }
+
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
+ }
+
+ /**
+ * Returns true if the template is still fresh.
+ *
+ * @param string $name The template name
+ * @param timestamp $time The last modification time of the cached template
+ */
+ public function isFresh($name, $time)
+ {
+ foreach ($this->loaders as $loader) {
+ try {
+ return $loader->isFresh($name, $time);
+ } catch (Twig_Error_Loader $e) {
+ }
+ }
+
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/Filesystem.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/Filesystem.php
new file mode 100644
index 0000000..5cd40f9
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/Filesystem.php
@@ -0,0 +1,152 @@
+
+ */
+class Twig_Loader_Filesystem implements Twig_LoaderInterface
+{
+ protected $paths;
+ protected $cache;
+
+ /**
+ * Constructor.
+ *
+ * @param string|array $paths A path or an array of paths where to look for templates
+ */
+ public function __construct($paths)
+ {
+ $this->setPaths($paths);
+ }
+
+ /**
+ * Returns the paths to the templates.
+ *
+ * @return array The array of paths where to look for templates
+ */
+ public function getPaths()
+ {
+ return $this->paths;
+ }
+
+ /**
+ * Sets the paths where templates are stored.
+ *
+ * @param string|array $paths A path or an array of paths where to look for templates
+ */
+ public function setPaths($paths)
+ {
+ if (!is_array($paths)) {
+ $paths = array($paths);
+ }
+
+ $this->paths = array();
+ foreach ($paths as $path) {
+ $this->addPath($path);
+ }
+ }
+
+ /**
+ * Adds a path where templates are stored.
+ *
+ * @param string $path A path where to look for templates
+ */
+ public function addPath($path)
+ {
+ // invalidate the cache
+ $this->cache = array();
+
+ if (!is_dir($path)) {
+ throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
+ }
+
+ $this->paths[] = $path;
+ }
+
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The template source code
+ */
+ public function getSource($name)
+ {
+ return file_get_contents($this->findTemplate($name));
+ }
+
+ /**
+ * Gets the cache key to use for the cache for a given template name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The cache key
+ */
+ public function getCacheKey($name)
+ {
+ return $this->findTemplate($name);
+ }
+
+ /**
+ * Returns true if the template is still fresh.
+ *
+ * @param string $name The template name
+ * @param timestamp $time The last modification time of the cached template
+ */
+ public function isFresh($name, $time)
+ {
+ return filemtime($this->findTemplate($name)) <= $time;
+ }
+
+ protected function findTemplate($name)
+ {
+ // normalize name
+ $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/'));
+
+ if (isset($this->cache[$name])) {
+ return $this->cache[$name];
+ }
+
+ $this->validateName($name);
+
+ foreach ($this->paths as $path) {
+ if (is_file($path.'/'.$name)) {
+ return $this->cache[$name] = $path.'/'.$name;
+ }
+ }
+
+ throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths)));
+ }
+
+ protected function validateName($name)
+ {
+ if (false !== strpos($name, "\0")) {
+ throw new Twig_Error_Loader('A template name cannot contain NUL bytes.');
+ }
+
+ $parts = explode('/', $name);
+ $level = 0;
+ foreach ($parts as $part) {
+ if ('..' === $part) {
+ --$level;
+ } elseif ('.' !== $part) {
+ ++$level;
+ }
+
+ if ($level < 0) {
+ throw new Twig_Error_Loader(sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
+ }
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/String.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/String.php
new file mode 100644
index 0000000..26eb009
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Loader/String.php
@@ -0,0 +1,59 @@
+
+ */
+class Twig_Loader_String implements Twig_LoaderInterface
+{
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The template source code
+ */
+ public function getSource($name)
+ {
+ return $name;
+ }
+
+ /**
+ * Gets the cache key to use for the cache for a given template name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The cache key
+ */
+ public function getCacheKey($name)
+ {
+ return $name;
+ }
+
+ /**
+ * Returns true if the template is still fresh.
+ *
+ * @param string $name The template name
+ * @param timestamp $time The last modification time of the cached template
+ */
+ public function isFresh($name, $time)
+ {
+ return true;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/LoaderInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/LoaderInterface.php
new file mode 100644
index 0000000..d8ae444
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/LoaderInterface.php
@@ -0,0 +1,53 @@
+
+ */
+interface Twig_LoaderInterface
+{
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The template source code
+ *
+ * @throws Twig_Error_Loader When $name is not found
+ */
+ function getSource($name);
+
+ /**
+ * Gets the cache key to use for the cache for a given template name.
+ *
+ * @param string $name The name of the template to load
+ *
+ * @return string The cache key
+ *
+ * @throws Twig_Error_Loader When $name is not found
+ */
+ function getCacheKey($name);
+
+ /**
+ * Returns true if the template is still fresh.
+ *
+ * @param string $name The template name
+ * @param timestamp $time The last modification time of the cached template
+ *
+ * @return Boolean true if the template is fresh, false otherwise
+ *
+ * @throws Twig_Error_Loader When $name is not found
+ */
+ function isFresh($name, $time);
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Markup.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Markup.php
new file mode 100644
index 0000000..7099b29
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Markup.php
@@ -0,0 +1,38 @@
+
+ */
+class Twig_Markup implements Countable
+{
+ protected $content;
+ protected $charset;
+
+ public function __construct($content, $charset)
+ {
+ $this->content = (string) $content;
+ $this->charset = $charset;
+ }
+
+ public function __toString()
+ {
+ return $this->content;
+ }
+
+ public function count()
+ {
+ return function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : strlen($this->content);
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node.php
new file mode 100644
index 0000000..651ffc4
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node.php
@@ -0,0 +1,227 @@
+
+ */
+class Twig_Node implements Twig_NodeInterface
+{
+ protected $nodes;
+ protected $attributes;
+ protected $lineno;
+ protected $tag;
+
+ /**
+ * Constructor.
+ *
+ * The nodes are automatically made available as properties ($this->node).
+ * The attributes are automatically made available as array items ($this['name']).
+ *
+ * @param array $nodes An array of named nodes
+ * @param array $attributes An array of attributes (should not be nodes)
+ * @param integer $lineno The line number
+ * @param string $tag The tag name associated with the Node
+ */
+ public function __construct(array $nodes = array(), array $attributes = array(), $lineno = 0, $tag = null)
+ {
+ $this->nodes = $nodes;
+ $this->attributes = $attributes;
+ $this->lineno = $lineno;
+ $this->tag = $tag;
+ }
+
+ public function __toString()
+ {
+ $attributes = array();
+ foreach ($this->attributes as $name => $value) {
+ $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
+ }
+
+ $repr = array(get_class($this).'('.implode(', ', $attributes));
+
+ if (count($this->nodes)) {
+ foreach ($this->nodes as $name => $node) {
+ $len = strlen($name) + 4;
+ $noderepr = array();
+ foreach (explode("\n", (string) $node) as $line) {
+ $noderepr[] = str_repeat(' ', $len).$line;
+ }
+
+ $repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr)));
+ }
+
+ $repr[] = ')';
+ } else {
+ $repr[0] .= ')';
+ }
+
+ return implode("\n", $repr);
+ }
+
+ public function toXml($asDom = false)
+ {
+ $dom = new DOMDocument('1.0', 'UTF-8');
+ $dom->formatOutput = true;
+ $dom->appendChild($xml = $dom->createElement('twig'));
+
+ $xml->appendChild($node = $dom->createElement('node'));
+ $node->setAttribute('class', get_class($this));
+
+ foreach ($this->attributes as $name => $value) {
+ $node->appendChild($attribute = $dom->createElement('attribute'));
+ $attribute->setAttribute('name', $name);
+ $attribute->appendChild($dom->createTextNode($value));
+ }
+
+ foreach ($this->nodes as $name => $n) {
+ if (null === $n) {
+ continue;
+ }
+
+ $child = $n->toXml(true)->getElementsByTagName('node')->item(0);
+ $child = $dom->importNode($child, true);
+ $child->setAttribute('name', $name);
+
+ $node->appendChild($child);
+ }
+
+ return $asDom ? $dom : $dom->saveXml();
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ foreach ($this->nodes as $node) {
+ $node->compile($compiler);
+ }
+ }
+
+ public function getLine()
+ {
+ return $this->lineno;
+ }
+
+ public function getNodeTag()
+ {
+ return $this->tag;
+ }
+
+ /**
+ * Returns true if the attribute is defined.
+ *
+ * @param string The attribute name
+ *
+ * @return Boolean true if the attribute is defined, false otherwise
+ */
+ public function hasAttribute($name)
+ {
+ return array_key_exists($name, $this->attributes);
+ }
+
+ /**
+ * Gets an attribute.
+ *
+ * @param string The attribute name
+ *
+ * @return mixed The attribute value
+ */
+ public function getAttribute($name)
+ {
+ if (!array_key_exists($name, $this->attributes)) {
+ throw new Twig_Error_Runtime(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this)));
+ }
+
+ return $this->attributes[$name];
+ }
+
+ /**
+ * Sets an attribute.
+ *
+ * @param string The attribute name
+ * @param mixed The attribute value
+ */
+ public function setAttribute($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ /**
+ * Removes an attribute.
+ *
+ * @param string The attribute name
+ */
+ public function removeAttribute($name)
+ {
+ unset($this->attributes[$name]);
+ }
+
+ /**
+ * Returns true if the node with the given identifier exists.
+ *
+ * @param string The node name
+ *
+ * @return Boolean true if the node with the given name exists, false otherwise
+ */
+ public function hasNode($name)
+ {
+ return array_key_exists($name, $this->nodes);
+ }
+
+ /**
+ * Gets a node by name.
+ *
+ * @param string The node name
+ *
+ * @return Twig_Node A Twig_Node instance
+ */
+ public function getNode($name)
+ {
+ if (!array_key_exists($name, $this->nodes)) {
+ throw new Twig_Error_Runtime(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this)));
+ }
+
+ return $this->nodes[$name];
+ }
+
+ /**
+ * Sets a node.
+ *
+ * @param string The node name
+ * @param Twig_Node A Twig_Node instance
+ */
+ public function setNode($name, $node = null)
+ {
+ $this->nodes[$name] = $node;
+ }
+
+ /**
+ * Removes a node by name.
+ *
+ * @param string The node name
+ */
+ public function removeNode($name)
+ {
+ unset($this->nodes[$name]);
+ }
+
+ public function count()
+ {
+ return count($this->nodes);
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->nodes);
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/AutoEscape.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/AutoEscape.php
new file mode 100644
index 0000000..a0c2ee6
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/AutoEscape.php
@@ -0,0 +1,40 @@
+
+ */
+class Twig_Node_AutoEscape extends Twig_Node
+{
+ public function __construct($value, Twig_NodeInterface $body, $lineno, $tag = 'autoescape')
+ {
+ parent::__construct(array('body' => $body), array('value' => $value), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->subcompile($this->getNode('body'));
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Block.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Block.php
new file mode 100644
index 0000000..5548ad0
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Block.php
@@ -0,0 +1,45 @@
+
+ */
+class Twig_Node_Block extends Twig_Node
+{
+ public function __construct($name, Twig_NodeInterface $body, $lineno, $tag = null)
+ {
+ parent::__construct(array('body' => $body), array('name' => $name), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write(sprintf("public function block_%s(\$context, array \$blocks = array())\n", $this->getAttribute('name')), "{\n")
+ ->indent()
+ ;
+
+ $compiler
+ ->subcompile($this->getNode('body'))
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/BlockReference.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/BlockReference.php
new file mode 100644
index 0000000..53f6287
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/BlockReference.php
@@ -0,0 +1,38 @@
+
+ */
+class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInterface
+{
+ public function __construct($name, $lineno, $tag = null)
+ {
+ parent::__construct(array(), array('name' => $name), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name')))
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Body.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Body.php
new file mode 100644
index 0000000..f72bf50
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Body.php
@@ -0,0 +1,20 @@
+
+ */
+class Twig_Node_Body extends Twig_Node
+{
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Do.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Do.php
new file mode 100644
index 0000000..aa419d9
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Do.php
@@ -0,0 +1,39 @@
+
+ */
+class Twig_Node_Do extends Twig_Node
+{
+ public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null)
+ {
+ parent::__construct(array('expr' => $expr), array(), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('')
+ ->subcompile($this->getNode('expr'))
+ ->raw(";\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Embed.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Embed.php
new file mode 100644
index 0000000..5edb953
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Embed.php
@@ -0,0 +1,39 @@
+
+ */
+class Twig_Node_Embed extends Twig_Node_Include
+{
+ // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module)
+ public function __construct($filename, $index, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
+ {
+ parent::__construct(new Twig_Node_Expression_Constant('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
+
+ $this->setAttribute('filename', $filename);
+ $this->setAttribute('index', $index);
+ }
+
+ protected function addGetTemplate(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->write("\$this->env->loadTemplate(")
+ ->string($this->getAttribute('filename'))
+ ->raw(', ')
+ ->string($this->getAttribute('index'))
+ ->raw(")")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression.php
new file mode 100644
index 0000000..13b170e
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression.php
@@ -0,0 +1,21 @@
+
+ */
+abstract class Twig_Node_Expression extends Twig_Node
+{
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Array.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Array.php
new file mode 100644
index 0000000..1da785f
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Array.php
@@ -0,0 +1,86 @@
+index = -1;
+ foreach ($this->getKeyValuePairs() as $pair) {
+ if ($pair['key'] instanceof Twig_Node_Expression_Constant && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) {
+ $this->index = $pair['key']->getAttribute('value');
+ }
+ }
+ }
+
+ public function getKeyValuePairs()
+ {
+ $pairs = array();
+
+ foreach (array_chunk($this->nodes, 2) as $pair) {
+ $pairs[] = array(
+ 'key' => $pair[0],
+ 'value' => $pair[1],
+ );
+ }
+
+ return $pairs;
+ }
+
+ public function hasElement(Twig_Node_Expression $key)
+ {
+ foreach ($this->getKeyValuePairs() as $pair) {
+ // we compare the string representation of the keys
+ // to avoid comparing the line numbers which are not relevant here.
+ if ((string) $key == (string) $pair['key']) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function addElement(Twig_Node_Expression $value, Twig_Node_Expression $key = null)
+ {
+ if (null === $key) {
+ $key = new Twig_Node_Expression_Constant(++$this->index, $value->getLine());
+ }
+
+ array_push($this->nodes, $key, $value);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->raw('array(');
+ $first = true;
+ foreach ($this->getKeyValuePairs() as $pair) {
+ if (!$first) {
+ $compiler->raw(', ');
+ }
+ $first = false;
+
+ $compiler
+ ->subcompile($pair['key'])
+ ->raw(' => ')
+ ->subcompile($pair['value'])
+ ;
+ }
+ $compiler->raw(')');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/AssignName.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/AssignName.php
new file mode 100644
index 0000000..2ddea78
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/AssignName.php
@@ -0,0 +1,28 @@
+raw('$context[')
+ ->string($this->getAttribute('name'))
+ ->raw(']')
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary.php
new file mode 100644
index 0000000..9dd5de2
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary.php
@@ -0,0 +1,40 @@
+ $left, 'right' => $right), array(), $lineno);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->raw('(')
+ ->subcompile($this->getNode('left'))
+ ->raw(' ')
+ ;
+ $this->operator($compiler);
+ $compiler
+ ->raw(' ')
+ ->subcompile($this->getNode('right'))
+ ->raw(')')
+ ;
+ }
+
+ abstract public function operator(Twig_Compiler $compiler);
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Add.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Add.php
new file mode 100644
index 0000000..0ef8e11
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Add.php
@@ -0,0 +1,18 @@
+raw('+');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/And.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/And.php
new file mode 100644
index 0000000..d5752eb
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/And.php
@@ -0,0 +1,18 @@
+raw('&&');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php
new file mode 100644
index 0000000..9a46d84
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php
@@ -0,0 +1,18 @@
+raw('&');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php
new file mode 100644
index 0000000..058a20b
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php
@@ -0,0 +1,18 @@
+raw('|');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php
new file mode 100644
index 0000000..f4da73d
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php
@@ -0,0 +1,18 @@
+raw('^');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Concat.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Concat.php
new file mode 100644
index 0000000..f9a6462
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Concat.php
@@ -0,0 +1,18 @@
+raw('.');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Div.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Div.php
new file mode 100644
index 0000000..e0797a6
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Div.php
@@ -0,0 +1,18 @@
+raw('/');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Equal.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Equal.php
new file mode 100644
index 0000000..7b1236d
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Equal.php
@@ -0,0 +1,17 @@
+raw('==');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/FloorDiv.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/FloorDiv.php
new file mode 100644
index 0000000..7fbd055
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/FloorDiv.php
@@ -0,0 +1,29 @@
+raw('intval(floor(');
+ parent::compile($compiler);
+ $compiler->raw('))');
+ }
+
+ public function operator(Twig_Compiler $compiler)
+ {
+ return $compiler->raw('/');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Greater.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Greater.php
new file mode 100644
index 0000000..a110bd9
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Greater.php
@@ -0,0 +1,17 @@
+raw('>');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php
new file mode 100644
index 0000000..3754fed
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php
@@ -0,0 +1,17 @@
+raw('>=');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/In.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/In.php
new file mode 100644
index 0000000..788f937
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/In.php
@@ -0,0 +1,33 @@
+raw('twig_in_filter(')
+ ->subcompile($this->getNode('left'))
+ ->raw(', ')
+ ->subcompile($this->getNode('right'))
+ ->raw(')')
+ ;
+ }
+
+ public function operator(Twig_Compiler $compiler)
+ {
+ return $compiler->raw('in');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Less.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Less.php
new file mode 100644
index 0000000..45fd300
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Less.php
@@ -0,0 +1,17 @@
+raw('<');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/LessEqual.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/LessEqual.php
new file mode 100644
index 0000000..e38e257
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/LessEqual.php
@@ -0,0 +1,17 @@
+raw('<=');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Mod.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Mod.php
new file mode 100644
index 0000000..9924114
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Mod.php
@@ -0,0 +1,18 @@
+raw('%');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Mul.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Mul.php
new file mode 100644
index 0000000..c91529c
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Mul.php
@@ -0,0 +1,18 @@
+raw('*');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/NotEqual.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/NotEqual.php
new file mode 100644
index 0000000..26867ba
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/NotEqual.php
@@ -0,0 +1,17 @@
+raw('!=');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/NotIn.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/NotIn.php
new file mode 100644
index 0000000..f347b7b
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/NotIn.php
@@ -0,0 +1,33 @@
+raw('!twig_in_filter(')
+ ->subcompile($this->getNode('left'))
+ ->raw(', ')
+ ->subcompile($this->getNode('right'))
+ ->raw(')')
+ ;
+ }
+
+ public function operator(Twig_Compiler $compiler)
+ {
+ return $compiler->raw('not in');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Or.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Or.php
new file mode 100644
index 0000000..adba49c
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Or.php
@@ -0,0 +1,18 @@
+raw('||');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Power.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Power.php
new file mode 100644
index 0000000..b2c5904
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Power.php
@@ -0,0 +1,33 @@
+raw('pow(')
+ ->subcompile($this->getNode('left'))
+ ->raw(', ')
+ ->subcompile($this->getNode('right'))
+ ->raw(')')
+ ;
+ }
+
+ public function operator(Twig_Compiler $compiler)
+ {
+ return $compiler->raw('**');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Range.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Range.php
new file mode 100644
index 0000000..bea4f2a
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Range.php
@@ -0,0 +1,33 @@
+raw('range(')
+ ->subcompile($this->getNode('left'))
+ ->raw(', ')
+ ->subcompile($this->getNode('right'))
+ ->raw(')')
+ ;
+ }
+
+ public function operator(Twig_Compiler $compiler)
+ {
+ return $compiler->raw('..');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Sub.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Sub.php
new file mode 100644
index 0000000..d446399
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Binary/Sub.php
@@ -0,0 +1,18 @@
+raw('-');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/BlockReference.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/BlockReference.php
new file mode 100644
index 0000000..174d909
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/BlockReference.php
@@ -0,0 +1,52 @@
+
+ */
+class Twig_Node_Expression_BlockReference extends Twig_Node_Expression
+{
+ public function __construct(Twig_NodeInterface $name, $asString = false, $lineno, $tag = null)
+ {
+ parent::__construct(array('name' => $name), array('as_string' => $asString, 'output' => false), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ if ($this->getAttribute('as_string')) {
+ $compiler->raw('(string) ');
+ }
+
+ if ($this->getAttribute('output')) {
+ $compiler
+ ->addDebugInfo($this)
+ ->write("\$this->displayBlock(")
+ ->subcompile($this->getNode('name'))
+ ->raw(", \$context, \$blocks);\n")
+ ;
+ } else {
+ $compiler
+ ->raw("\$this->renderBlock(")
+ ->subcompile($this->getNode('name'))
+ ->raw(", \$context, \$blocks)")
+ ;
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Conditional.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Conditional.php
new file mode 100644
index 0000000..edcb1e2
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Conditional.php
@@ -0,0 +1,31 @@
+ $expr1, 'expr2' => $expr2, 'expr3' => $expr3), array(), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->raw('((')
+ ->subcompile($this->getNode('expr1'))
+ ->raw(') ? (')
+ ->subcompile($this->getNode('expr2'))
+ ->raw(') : (')
+ ->subcompile($this->getNode('expr3'))
+ ->raw('))')
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Constant.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Constant.php
new file mode 100644
index 0000000..a91dc69
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Constant.php
@@ -0,0 +1,23 @@
+ $value), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->repr($this->getAttribute('value'));
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/ExtensionReference.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/ExtensionReference.php
new file mode 100644
index 0000000..cb4efad
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/ExtensionReference.php
@@ -0,0 +1,34 @@
+
+ */
+class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression
+{
+ public function __construct($name, $lineno, $tag = null)
+ {
+ parent::__construct(array(), array('name' => $name), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name')));
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Filter.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Filter.php
new file mode 100644
index 0000000..8a0903a
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Filter.php
@@ -0,0 +1,61 @@
+ $node, 'filter' => $filterName, 'arguments' => $arguments), array(), $lineno, $tag);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $name = $this->getNode('filter')->getAttribute('value');
+
+ if (false === $filter = $compiler->getEnvironment()->getFilter($name)) {
+ $message = sprintf('The filter "%s" does not exist', $name);
+ if ($alternatives = $compiler->getEnvironment()->computeAlternatives($name, array_keys($compiler->getEnvironment()->getFilters()))) {
+ $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
+ }
+
+ throw new Twig_Error_Syntax($message, $this->getLine());
+ }
+
+ $this->compileFilter($compiler, $filter);
+ }
+
+ protected function compileFilter(Twig_Compiler $compiler, Twig_FilterInterface $filter)
+ {
+ $compiler
+ ->raw($filter->compile().'(')
+ ->raw($filter->needsEnvironment() ? '$this->env, ' : '')
+ ->raw($filter->needsContext() ? '$context, ' : '')
+ ;
+
+ foreach ($filter->getArguments() as $argument) {
+ $compiler
+ ->string($argument)
+ ->raw(', ')
+ ;
+ }
+
+ $compiler->subcompile($this->getNode('node'));
+
+ foreach ($this->getNode('arguments') as $node) {
+ $compiler
+ ->raw(', ')
+ ->subcompile($node)
+ ;
+ }
+
+ $compiler->raw(')');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Filter/Default.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Filter/Default.php
new file mode 100644
index 0000000..1cb3342
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Filter/Default.php
@@ -0,0 +1,44 @@
+
+ * {{ var.foo|default('foo item on var is not defined') }}
+ *
+ *
+ * @package twig
+ * @author Fabien Potencier
+ */
+class Twig_Node_Expression_Filter_Default extends Twig_Node_Expression_Filter
+{
+ public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null)
+ {
+ $default = new Twig_Node_Expression_Filter($node, new Twig_Node_Expression_Constant('_default', $node->getLine()), $arguments, $node->getLine());
+
+ if ('default' === $filterName->getAttribute('value') && ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr)) {
+ $test = new Twig_Node_Expression_Test_Defined(clone $node, 'defined', new Twig_Node(), $node->getLine());
+ $false = count($arguments) ? $arguments->getNode(0) : new Twig_Node_Expression_Constant('', $node->getLine());
+
+ $node = new Twig_Node_Expression_Conditional($test, $default, $false, $node->getLine());
+ } else {
+ $node = $default;
+ }
+
+ parent::__construct($node, $filterName, $arguments, $lineno, $tag);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->subcompile($this->getNode('node'));
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Function.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Function.php
new file mode 100644
index 0000000..9342bb1
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Function.php
@@ -0,0 +1,66 @@
+ $arguments), array('name' => $name), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $name = $this->getAttribute('name');
+
+ if (false === $function = $compiler->getEnvironment()->getFunction($name)) {
+ $message = sprintf('The function "%s" does not exist', $name);
+ if ($alternatives = $compiler->getEnvironment()->computeAlternatives($name, array_keys($compiler->getEnvironment()->getFunctions()))) {
+ $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
+ }
+
+ throw new Twig_Error_Syntax($message, $this->getLine());
+ }
+
+ $compiler->raw($function->compile().'(');
+
+ $first = true;
+
+ if ($function->needsEnvironment()) {
+ $compiler->raw('$this->env');
+ $first = false;
+ }
+
+ if ($function->needsContext()) {
+ if (!$first) {
+ $compiler->raw(', ');
+ }
+ $compiler->raw('$context');
+ $first = false;
+ }
+
+ foreach ($function->getArguments() as $argument) {
+ if (!$first) {
+ $compiler->raw(', ');
+ }
+ $compiler->string($argument);
+ $first = false;
+ }
+
+ foreach ($this->getNode('arguments') as $node) {
+ if (!$first) {
+ $compiler->raw(', ');
+ }
+ $compiler->subcompile($node);
+ $first = false;
+ }
+
+ $compiler->raw(')');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/GetAttr.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/GetAttr.php
new file mode 100644
index 0000000..6498444
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/GetAttr.php
@@ -0,0 +1,53 @@
+ $node, 'attribute' => $attribute, 'arguments' => $arguments), array('type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ if (function_exists('twig_template_get_attributes')) {
+ $compiler->raw('twig_template_get_attributes($this, ');
+ } else {
+ $compiler->raw('$this->getAttribute(');
+ }
+
+ if ($this->getAttribute('ignore_strict_check')) {
+ $this->getNode('node')->setAttribute('ignore_strict_check', true);
+ }
+
+ $compiler->subcompile($this->getNode('node'));
+
+ $compiler->raw(', ')->subcompile($this->getNode('attribute'));
+
+ if (count($this->getNode('arguments')) || Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
+ $compiler->raw(', ')->subcompile($this->getNode('arguments'));
+
+ if (Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
+ $compiler->raw(', ')->repr($this->getAttribute('type'));
+ }
+
+ if ($this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
+ $compiler->raw(', '.($this->getAttribute('is_defined_test') ? 'true' : 'false'));
+ }
+
+ if ($this->getAttribute('ignore_strict_check')) {
+ $compiler->raw(', '.($this->getAttribute('ignore_strict_check') ? 'true' : 'false'));
+ }
+ }
+
+ $compiler->raw(')');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/MethodCall.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/MethodCall.php
new file mode 100644
index 0000000..5093808
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/MethodCall.php
@@ -0,0 +1,37 @@
+ $node, 'arguments' => $arguments), array('method' => $method, 'safe' => false), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->subcompile($this->getNode('node'))
+ ->raw('->')
+ ->raw($this->getAttribute('method'))
+ ->raw('(')
+ ;
+ $first = true;
+ foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
+ if (!$first) {
+ $compiler->raw(', ');
+ }
+ $first = false;
+
+ $compiler->subcompile($pair['value']);
+ }
+ $compiler->raw(')');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Name.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Name.php
new file mode 100644
index 0000000..8f5a1ea
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Name.php
@@ -0,0 +1,76 @@
+ '$this',
+ '_context' => '$context',
+ '_charset' => '$this->env->getCharset()',
+ );
+
+ public function __construct($name, $lineno)
+ {
+ parent::__construct(array(), array('name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $name = $this->getAttribute('name');
+
+ if ($this->getAttribute('is_defined_test')) {
+ if ($this->isSpecial()) {
+ $compiler->repr(true);
+ } else {
+ $compiler->raw('array_key_exists(')->repr($name)->raw(', $context)');
+ }
+ } elseif ($this->isSpecial()) {
+ $compiler->raw($this->specialVars[$name]);
+ } else {
+ // remove the non-PHP 5.4 version when PHP 5.3 support is dropped
+ // as the non-optimized version is just a workaround for slow ternary operator
+ // when the context has a lot of variables
+ if (version_compare(phpversion(), '5.4.0RC1', '>=') && ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables())) {
+ // PHP 5.4 ternary operator performance was optimized
+ $compiler
+ ->raw('(isset($context[')
+ ->string($name)
+ ->raw(']) ? $context[')
+ ->string($name)
+ ->raw('] : null)')
+ ;
+ } else {
+ $compiler
+ ->raw('$this->getContext($context, ')
+ ->string($name)
+ ;
+
+ if ($this->getAttribute('ignore_strict_check')) {
+ $compiler->raw(', true');
+ }
+
+ $compiler
+ ->raw(')')
+ ;
+ }
+ }
+ }
+
+ public function isSpecial()
+ {
+ return isset($this->specialVars[$this->getAttribute('name')]);
+ }
+
+ public function isSimple()
+ {
+ return !$this->isSpecial() && !$this->getAttribute('is_defined_test');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Parent.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Parent.php
new file mode 100644
index 0000000..ea97349
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Parent.php
@@ -0,0 +1,48 @@
+
+ */
+class Twig_Node_Expression_Parent extends Twig_Node_Expression
+{
+ public function __construct($name, $lineno, $tag = null)
+ {
+ parent::__construct(array(), array('output' => false, 'name' => $name), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ if ($this->getAttribute('output')) {
+ $compiler
+ ->addDebugInfo($this)
+ ->write("\$this->displayParentBlock(")
+ ->string($this->getAttribute('name'))
+ ->raw(", \$context, \$blocks);\n")
+ ;
+ } else {
+ $compiler
+ ->raw("\$this->renderParentBlock(")
+ ->string($this->getAttribute('name'))
+ ->raw(", \$context, \$blocks)")
+ ;
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/TempName.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/TempName.php
new file mode 100644
index 0000000..eea9d47
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/TempName.php
@@ -0,0 +1,22 @@
+ $name), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->raw('$_')->raw($this->getAttribute('name'))->raw('_');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test.php
new file mode 100644
index 0000000..4e0b25e
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test.php
@@ -0,0 +1,54 @@
+ $node, 'arguments' => $arguments), array('name' => $name), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $name = $this->getAttribute('name');
+ $testMap = $compiler->getEnvironment()->getTests();
+ if (!isset($testMap[$name])) {
+ $message = sprintf('The test "%s" does not exist', $name);
+ if ($alternatives = $compiler->getEnvironment()->computeAlternatives($name, array_keys($compiler->getEnvironment()->getTests()))) {
+ $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
+ }
+
+ throw new Twig_Error_Syntax($message, $this->getLine());
+ }
+
+ $name = $this->getAttribute('name');
+ $node = $this->getNode('node');
+
+ $compiler
+ ->raw($testMap[$name]->compile().'(')
+ ->subcompile($node)
+ ;
+
+ if (null !== $this->getNode('arguments')) {
+ $compiler->raw(', ');
+
+ $max = count($this->getNode('arguments')) - 1;
+ foreach ($this->getNode('arguments') as $i => $arg) {
+ $compiler->subcompile($arg);
+
+ if ($i != $max) {
+ $compiler->raw(', ');
+ }
+ }
+ }
+
+ $compiler->raw(')');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Constant.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Constant.php
new file mode 100644
index 0000000..6e6b6fd
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Constant.php
@@ -0,0 +1,36 @@
+
+ * {% if post.status is constant('Post::PUBLISHED') %}
+ * the status attribute is exactly the same as Post::PUBLISHED
+ * {% endif %}
+ *
+ *
+ * @package twig
+ * @author Fabien Potencier
+ */
+class Twig_Node_Expression_Test_Constant extends Twig_Node_Expression_Test
+{
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->raw('(')
+ ->subcompile($this->getNode('node'))
+ ->raw(' === constant(')
+ ->subcompile($this->getNode('arguments')->getNode(0))
+ ->raw('))')
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Defined.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Defined.php
new file mode 100644
index 0000000..e7c6828
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Defined.php
@@ -0,0 +1,55 @@
+
+ * {# defined works with variable names and variable attributes #}
+ * {% if foo is defined %}
+ * {# ... #}
+ * {% endif %}
+ *
+ *
+ * @package twig
+ * @author Fabien Potencier
+ */
+class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test
+{
+ public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno)
+ {
+ parent::__construct($node, $name, $arguments, $lineno);
+
+ if ($node instanceof Twig_Node_Expression_Name) {
+ $node->setAttribute('is_defined_test', true);
+ } elseif ($node instanceof Twig_Node_Expression_GetAttr) {
+ $node->setAttribute('is_defined_test', true);
+
+ $this->changeIgnoreStrictCheck($node);
+ } else {
+ throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine());
+ }
+ }
+
+ protected function changeIgnoreStrictCheck(Twig_Node_Expression_GetAttr $node)
+ {
+ $node->setAttribute('ignore_strict_check', true);
+
+ if ($node->getNode('node') instanceof Twig_Node_Expression_GetAttr) {
+ $this->changeIgnoreStrictCheck($node->getNode('node'));
+ }
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->subcompile($this->getNode('node'));
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Divisibleby.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Divisibleby.php
new file mode 100644
index 0000000..05563d5
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Divisibleby.php
@@ -0,0 +1,34 @@
+
+ * {% if loop.index is divisibleby(3) %}
+ *
+ *
+ * @package twig
+ * @author Fabien Potencier
+ */
+class Twig_Node_Expression_Test_Divisibleby extends Twig_Node_Expression_Test
+{
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->raw('(0 == ')
+ ->subcompile($this->getNode('node'))
+ ->raw(' % ')
+ ->subcompile($this->getNode('arguments')->getNode(0))
+ ->raw(')')
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Even.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Even.php
new file mode 100644
index 0000000..08e6d82
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Even.php
@@ -0,0 +1,33 @@
+
+ * {{ var is even }}
+ *
+ *
+ * @package twig
+ * @author Fabien Potencier
+ */
+class Twig_Node_Expression_Test_Even extends Twig_Node_Expression_Test
+{
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->raw('(')
+ ->subcompile($this->getNode('node'))
+ ->raw(' % 2 == 0')
+ ->raw(')')
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Null.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Null.php
new file mode 100644
index 0000000..55061db
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Null.php
@@ -0,0 +1,32 @@
+
+ * {{ var is none }}
+ *
+ *
+ * @package twig
+ * @author Fabien Potencier
+ */
+class Twig_Node_Expression_Test_Null extends Twig_Node_Expression_Test
+{
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->raw('(null === ')
+ ->subcompile($this->getNode('node'))
+ ->raw(')')
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Odd.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Odd.php
new file mode 100644
index 0000000..5fecebc
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Odd.php
@@ -0,0 +1,33 @@
+
+ * {{ var is odd }}
+ *
+ *
+ * @package twig
+ * @author Fabien Potencier
+ */
+class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test
+{
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->raw('(')
+ ->subcompile($this->getNode('node'))
+ ->raw(' % 2 == 1')
+ ->raw(')')
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Sameas.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Sameas.php
new file mode 100644
index 0000000..8639b96
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Test/Sameas.php
@@ -0,0 +1,30 @@
+
+ */
+class Twig_Node_Expression_Test_Sameas extends Twig_Node_Expression_Test
+{
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->raw('(')
+ ->subcompile($this->getNode('node'))
+ ->raw(' === ')
+ ->subcompile($this->getNode('arguments')->getNode(0))
+ ->raw(')')
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary.php
new file mode 100644
index 0000000..c514388
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary.php
@@ -0,0 +1,30 @@
+ $node), array(), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->raw('(');
+ $this->operator($compiler);
+ $compiler
+ ->subcompile($this->getNode('node'))
+ ->raw(')')
+ ;
+ }
+
+ abstract public function operator(Twig_Compiler $compiler);
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary/Neg.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary/Neg.php
new file mode 100644
index 0000000..2a3937e
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary/Neg.php
@@ -0,0 +1,18 @@
+raw('-');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary/Not.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary/Not.php
new file mode 100644
index 0000000..f94073c
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary/Not.php
@@ -0,0 +1,18 @@
+raw('!');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary/Pos.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary/Pos.php
new file mode 100644
index 0000000..04edb52
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Expression/Unary/Pos.php
@@ -0,0 +1,18 @@
+raw('+');
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Flush.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Flush.php
new file mode 100644
index 0000000..8f2ab9d
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Flush.php
@@ -0,0 +1,37 @@
+
+ */
+class Twig_Node_Flush extends Twig_Node
+{
+ public function __construct($lineno, $tag)
+ {
+ parent::__construct(array(), array(), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write("flush();\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/For.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/For.php
new file mode 100644
index 0000000..d9d25b3
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/For.php
@@ -0,0 +1,113 @@
+
+ */
+class Twig_Node_For extends Twig_Node
+{
+ protected $loop;
+
+ public function __construct(Twig_Node_Expression_AssignName $keyTarget, Twig_Node_Expression_AssignName $valueTarget, Twig_Node_Expression $seq, Twig_Node_Expression $ifexpr = null, Twig_NodeInterface $body, Twig_NodeInterface $else = null, $lineno, $tag = null)
+ {
+ $body = new Twig_Node(array($body, $this->loop = new Twig_Node_ForLoop($lineno, $tag)));
+
+ if (null !== $ifexpr) {
+ $body = new Twig_Node_If(new Twig_Node(array($ifexpr, $body)), null, $lineno, $tag);
+ }
+
+ parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body, 'else' => $else), array('with_loop' => true, 'ifexpr' => null !== $ifexpr), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ // the (array) cast bypasses a PHP 5.2.6 bug
+ ->write("\$context['_parent'] = (array) \$context;\n")
+ ->write("\$context['_seq'] = twig_ensure_traversable(")
+ ->subcompile($this->getNode('seq'))
+ ->raw(");\n")
+ ;
+
+ if (null !== $this->getNode('else')) {
+ $compiler->write("\$context['_iterated'] = false;\n");
+ }
+
+ if ($this->getAttribute('with_loop')) {
+ $compiler
+ ->write("\$context['loop'] = array(\n")
+ ->write(" 'parent' => \$context['_parent'],\n")
+ ->write(" 'index0' => 0,\n")
+ ->write(" 'index' => 1,\n")
+ ->write(" 'first' => true,\n")
+ ->write(");\n")
+ ;
+
+ if (!$this->getAttribute('ifexpr')) {
+ $compiler
+ ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {\n")
+ ->indent()
+ ->write("\$length = count(\$context['_seq']);\n")
+ ->write("\$context['loop']['revindex0'] = \$length - 1;\n")
+ ->write("\$context['loop']['revindex'] = \$length;\n")
+ ->write("\$context['loop']['length'] = \$length;\n")
+ ->write("\$context['loop']['last'] = 1 === \$length;\n")
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+ }
+
+ $this->loop->setAttribute('else', null !== $this->getNode('else'));
+ $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop'));
+ $this->loop->setAttribute('ifexpr', $this->getAttribute('ifexpr'));
+
+ $compiler
+ ->write("foreach (\$context['_seq'] as ")
+ ->subcompile($this->getNode('key_target'))
+ ->raw(" => ")
+ ->subcompile($this->getNode('value_target'))
+ ->raw(") {\n")
+ ->indent()
+ ->subcompile($this->getNode('body'))
+ ->outdent()
+ ->write("}\n")
+ ;
+
+ if (null !== $this->getNode('else')) {
+ $compiler
+ ->write("if (!\$context['_iterated']) {\n")
+ ->indent()
+ ->subcompile($this->getNode('else'))
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+
+ $compiler->write("\$_parent = \$context['_parent'];\n");
+
+ // remove some "private" loop variables (needed for nested loops)
+ $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n");
+
+ // keep the values set in the inner context for variables defined in the outer context
+ $compiler->write("\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));\n");
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/ForLoop.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/ForLoop.php
new file mode 100644
index 0000000..38f2e85
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/ForLoop.php
@@ -0,0 +1,56 @@
+
+ */
+class Twig_Node_ForLoop extends Twig_Node
+{
+ public function __construct($lineno, $tag = null)
+ {
+ parent::__construct(array(), array('with_loop' => false, 'ifexpr' => false, 'else' => false), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ if ($this->getAttribute('else')) {
+ $compiler->write("\$context['_iterated'] = true;\n");
+ }
+
+ if ($this->getAttribute('with_loop')) {
+ $compiler
+ ->write("++\$context['loop']['index0'];\n")
+ ->write("++\$context['loop']['index'];\n")
+ ->write("\$context['loop']['first'] = false;\n")
+ ;
+
+ if (!$this->getAttribute('ifexpr')) {
+ $compiler
+ ->write("if (isset(\$context['loop']['length'])) {\n")
+ ->indent()
+ ->write("--\$context['loop']['revindex0'];\n")
+ ->write("--\$context['loop']['revindex'];\n")
+ ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n")
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/If.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/If.php
new file mode 100644
index 0000000..aa12efb
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/If.php
@@ -0,0 +1,67 @@
+
+ */
+class Twig_Node_If extends Twig_Node
+{
+ public function __construct(Twig_NodeInterface $tests, Twig_NodeInterface $else = null, $lineno, $tag = null)
+ {
+ parent::__construct(array('tests' => $tests, 'else' => $else), array(), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->addDebugInfo($this);
+ for ($i = 0; $i < count($this->getNode('tests')); $i += 2) {
+ if ($i > 0) {
+ $compiler
+ ->outdent()
+ ->write("} elseif (")
+ ;
+ } else {
+ $compiler
+ ->write('if (')
+ ;
+ }
+
+ $compiler
+ ->subcompile($this->getNode('tests')->getNode($i))
+ ->raw(") {\n")
+ ->indent()
+ ->subcompile($this->getNode('tests')->getNode($i + 1))
+ ;
+ }
+
+ if ($this->hasNode('else') && null !== $this->getNode('else')) {
+ $compiler
+ ->outdent()
+ ->write("} else {\n")
+ ->indent()
+ ->subcompile($this->getNode('else'))
+ ;
+ }
+
+ $compiler
+ ->outdent()
+ ->write("}\n");
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Import.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Import.php
new file mode 100644
index 0000000..a327411
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Import.php
@@ -0,0 +1,51 @@
+
+ */
+class Twig_Node_Import extends Twig_Node
+{
+ public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $var, $lineno, $tag = null)
+ {
+ parent::__construct(array('expr' => $expr, 'var' => $var), array(), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('')
+ ->subcompile($this->getNode('var'))
+ ->raw(' = ')
+ ;
+
+ if ($this->getNode('expr') instanceof Twig_Node_Expression_Name && '_self' === $this->getNode('expr')->getAttribute('name')) {
+ $compiler->raw("\$this");
+ } else {
+ $compiler
+ ->raw('$this->env->loadTemplate(')
+ ->subcompile($this->getNode('expr'))
+ ->raw(")")
+ ;
+ }
+
+ $compiler->raw(";\n");
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Include.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Include.php
new file mode 100644
index 0000000..5b6be7a
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Include.php
@@ -0,0 +1,100 @@
+
+ */
+class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface
+{
+ public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
+ {
+ parent::__construct(array('expr' => $expr, 'variables' => $variables), array('only' => (Boolean) $only, 'ignore_missing' => (Boolean) $ignoreMissing), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->addDebugInfo($this);
+
+ if ($this->getAttribute('ignore_missing')) {
+ $compiler
+ ->write("try {\n")
+ ->indent()
+ ;
+ }
+
+ $this->addGetTemplate($compiler);
+
+ $compiler->raw('->display(');
+
+ $this->addTemplateArguments($compiler);
+
+ $compiler->raw(");\n");
+
+ if ($this->getAttribute('ignore_missing')) {
+ $compiler
+ ->outdent()
+ ->write("} catch (Twig_Error_Loader \$e) {\n")
+ ->indent()
+ ->write("// ignore missing template\n")
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+ }
+
+ protected function addGetTemplate(Twig_Compiler $compiler)
+ {
+ if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) {
+ $compiler
+ ->write("\$this->env->loadTemplate(")
+ ->subcompile($this->getNode('expr'))
+ ->raw(")")
+ ;
+ } else {
+ $compiler
+ ->write("\$template = \$this->env->resolveTemplate(")
+ ->subcompile($this->getNode('expr'))
+ ->raw(");\n")
+ ->write('$template')
+ ;
+ }
+ }
+
+ protected function addTemplateArguments(Twig_Compiler $compiler)
+ {
+ if (false === $this->getAttribute('only')) {
+ if (null === $this->getNode('variables')) {
+ $compiler->raw('$context');
+ } else {
+ $compiler
+ ->raw('array_merge($context, ')
+ ->subcompile($this->getNode('variables'))
+ ->raw(')')
+ ;
+ }
+ } else {
+ if (null === $this->getNode('variables')) {
+ $compiler->raw('array()');
+ } else {
+ $compiler->subcompile($this->getNode('variables'));
+ }
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Macro.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Macro.php
new file mode 100644
index 0000000..e0c3dca
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Macro.php
@@ -0,0 +1,84 @@
+
+ */
+class Twig_Node_Macro extends Twig_Node
+{
+ public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null)
+ {
+ parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $arguments = array();
+ foreach ($this->getNode('arguments') as $argument) {
+ $arguments[] = '$'.$argument->getAttribute('name').' = null';
+ }
+
+ $compiler
+ ->addDebugInfo($this)
+ ->write(sprintf("public function get%s(%s)\n", $this->getAttribute('name'), implode(', ', $arguments)), "{\n")
+ ->indent()
+ ;
+
+ if (!count($this->getNode('arguments'))) {
+ $compiler->write("\$context = \$this->env->getGlobals();\n\n");
+ } else {
+ $compiler
+ ->write("\$context = \$this->env->mergeGlobals(array(\n")
+ ->indent()
+ ;
+
+ foreach ($this->getNode('arguments') as $argument) {
+ $compiler
+ ->write('')
+ ->string($argument->getAttribute('name'))
+ ->raw(' => $'.$argument->getAttribute('name'))
+ ->raw(",\n")
+ ;
+ }
+
+ $compiler
+ ->outdent()
+ ->write("));\n\n")
+ ;
+ }
+
+ $compiler
+ ->write("\$blocks = array();\n\n")
+ ->write("ob_start();\n")
+ ->write("try {\n")
+ ->indent()
+ ->subcompile($this->getNode('body'))
+ ->outdent()
+ ->write("} catch(Exception \$e) {\n")
+ ->indent()
+ ->write("ob_end_clean();\n\n")
+ ->write("throw \$e;\n")
+ ->outdent()
+ ->write("}\n\n")
+ ->write("return ob_get_clean();\n")
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Module.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Module.php
new file mode 100644
index 0000000..4c7a107
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Module.php
@@ -0,0 +1,372 @@
+
+ */
+class Twig_Node_Module extends Twig_Node
+{
+ public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $filename)
+ {
+ // embedded templates are set as attributes so that they are only visited once by the visitors
+ parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename, 'index' => null, 'embedded_templates' => $embeddedTemplates), 1);
+ }
+
+ public function setIndex($index)
+ {
+ $this->setAttribute('index', $index);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $this->compileTemplate($compiler);
+
+ foreach ($this->getAttribute('embedded_templates') as $template) {
+ $compiler->subcompile($template);
+ }
+ }
+
+ protected function compileTemplate(Twig_Compiler $compiler)
+ {
+ if (!$this->getAttribute('index')) {
+ $compiler->write('compileClassHeader($compiler);
+
+ if (count($this->getNode('blocks')) || count($this->getNode('traits')) || null === $this->getNode('parent') || $this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
+ $this->compileConstructor($compiler);
+ }
+
+ $this->compileGetParent($compiler);
+
+ $this->compileDisplayHeader($compiler);
+
+ $this->compileDisplayBody($compiler);
+
+ $this->compileDisplayFooter($compiler);
+
+ $compiler->subcompile($this->getNode('blocks'));
+
+ $this->compileMacros($compiler);
+
+ $this->compileGetTemplateName($compiler);
+
+ $this->compileIsTraitable($compiler);
+
+ $this->compileDebugInfo($compiler);
+
+ $this->compileClassFooter($compiler);
+ }
+
+ protected function compileGetParent(Twig_Compiler $compiler)
+ {
+ if (null === $this->getNode('parent')) {
+ return;
+ }
+
+ $compiler
+ ->write("protected function doGetParent(array \$context)\n", "{\n")
+ ->indent()
+ ->write("return ")
+ ;
+
+ if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
+ $compiler->subcompile($this->getNode('parent'));
+ } else {
+ $compiler
+ ->raw("\$this->env->resolveTemplate(")
+ ->subcompile($this->getNode('parent'))
+ ->raw(")")
+ ;
+ }
+
+ $compiler
+ ->raw(";\n")
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+
+ protected function compileDisplayBody(Twig_Compiler $compiler)
+ {
+ $compiler->subcompile($this->getNode('body'));
+
+ if (null !== $this->getNode('parent')) {
+ if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
+ $compiler->write("\$this->parent");
+ } else {
+ $compiler->write("\$this->getParent(\$context)");
+ }
+ $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
+ }
+ }
+
+ protected function compileClassHeader(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->write("\n\n")
+ // if the filename contains */, add a blank to avoid a PHP parse error
+ ->write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n")
+ ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('index')))
+ ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass()))
+ ->write("{\n")
+ ->indent()
+ ;
+ }
+
+ protected function compileConstructor(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->write("public function __construct(Twig_Environment \$env)\n", "{\n")
+ ->indent()
+ ->write("parent::__construct(\$env);\n\n")
+ ;
+
+ // parent
+ if (null === $this->getNode('parent')) {
+ $compiler->write("\$this->parent = false;\n\n");
+ } elseif ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
+ $compiler
+ ->write("\$this->parent = \$this->env->loadTemplate(")
+ ->subcompile($this->getNode('parent'))
+ ->raw(");\n\n")
+ ;
+ }
+
+ $countTraits = count($this->getNode('traits'));
+ if ($countTraits) {
+ // traits
+ foreach ($this->getNode('traits') as $i => $trait) {
+ $this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i));
+
+ $compiler
+ ->addDebugInfo($trait->getNode('template'))
+ ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i))
+ ->indent()
+ ->write("throw new Twig_Error_Runtime('Template \"'.")
+ ->subcompile($trait->getNode('template'))
+ ->raw(".'\" cannot be used as a trait.');\n")
+ ->outdent()
+ ->write("}\n")
+ ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i))
+ ;
+
+ foreach ($trait->getNode('targets') as $key => $value) {
+ $compiler
+ ->write(sprintf("\$_trait_%s_blocks[", $i))
+ ->subcompile($value)
+ ->raw(sprintf("] = \$_trait_%s_blocks[", $i))
+ ->string($key)
+ ->raw(sprintf("]; unset(\$_trait_%s_blocks[", $i))
+ ->string($key)
+ ->raw("]);\n\n")
+ ;
+ }
+ }
+
+ if ($countTraits > 1) {
+ $compiler
+ ->write("\$this->traits = array_merge(\n")
+ ->indent()
+ ;
+
+ for ($i = 0; $i < $countTraits; $i++) {
+ $compiler
+ ->write(sprintf("\$_trait_%s_blocks".($i == $countTraits - 1 ? '' : ',')."\n", $i))
+ ;
+ }
+
+ $compiler
+ ->outdent()
+ ->write(");\n\n")
+ ;
+ } else {
+ $compiler
+ ->write("\$this->traits = \$_trait_0_blocks;\n\n")
+ ;
+ }
+
+ $compiler
+ ->write("\$this->blocks = array_merge(\n")
+ ->indent()
+ ->write("\$this->traits,\n")
+ ->write("array(\n")
+ ;
+ } else {
+ $compiler
+ ->write("\$this->blocks = array(\n")
+ ;
+ }
+
+ // blocks
+ $compiler
+ ->indent()
+ ;
+
+ foreach ($this->getNode('blocks') as $name => $node) {
+ $compiler
+ ->write(sprintf("'%s' => array(\$this, 'block_%s'),\n", $name, $name))
+ ;
+ }
+
+ if ($countTraits) {
+ $compiler
+ ->outdent()
+ ->write(")\n")
+ ;
+ }
+
+ $compiler
+ ->outdent()
+ ->write(");\n")
+ ->outdent()
+ ->write("}\n\n");
+ ;
+ }
+
+ protected function compileDisplayHeader(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->write("protected function doDisplay(array \$context, array \$blocks = array())\n", "{\n")
+ ->indent()
+ ;
+ }
+
+ protected function compileDisplayFooter(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+
+ protected function compileClassFooter(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+
+ protected function compileMacros(Twig_Compiler $compiler)
+ {
+ $compiler->subcompile($this->getNode('macros'));
+ }
+
+ protected function compileGetTemplateName(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->write("public function getTemplateName()\n", "{\n")
+ ->indent()
+ ->write('return ')
+ ->repr($this->getAttribute('filename'))
+ ->raw(";\n")
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+
+ protected function compileIsTraitable(Twig_Compiler $compiler)
+ {
+ // A template can be used as a trait if:
+ // * it has no parent
+ // * it has no macros
+ // * it has no body
+ //
+ // Put another way, a template can be used as a trait if it
+ // only contains blocks and use statements.
+ $traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros'));
+ if ($traitable) {
+ if ($this->getNode('body') instanceof Twig_Node_Body) {
+ $nodes = $this->getNode('body')->getNode(0);
+ } else {
+ $nodes = $this->getNode('body');
+ }
+
+ if (!count($nodes)) {
+ $nodes = new Twig_Node(array($nodes));
+ }
+
+ foreach ($nodes as $node) {
+ if (!count($node)) {
+ continue;
+ }
+
+ if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) {
+ continue;
+ }
+
+ if ($node instanceof Twig_Node_BlockReference) {
+ continue;
+ }
+
+ $traitable = false;
+ break;
+ }
+ }
+
+ if ($traitable) {
+ return;
+ }
+
+ $compiler
+ ->write("public function isTraitable()\n", "{\n")
+ ->indent()
+ ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+
+ protected function compileDebugInfo(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->write("public function getDebugInfo()\n", "{\n")
+ ->indent()
+ ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+
+ protected function compileLoadTemplate(Twig_Compiler $compiler, $node, $var)
+ {
+ if ($node instanceof Twig_Node_Expression_Constant) {
+ $compiler
+ ->write(sprintf("%s = \$this->env->loadTemplate(", $var))
+ ->subcompile($node)
+ ->raw(");\n")
+ ;
+ } else {
+ $compiler
+ ->write(sprintf("%s = ", $var))
+ ->subcompile($node)
+ ->raw(";\n")
+ ->write(sprintf("if (!%s", $var))
+ ->raw(" instanceof Twig_Template) {\n")
+ ->indent()
+ ->write(sprintf("%s = \$this->env->loadTemplate(%s);\n", $var, $var))
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Print.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Print.php
new file mode 100644
index 0000000..766725f
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Print.php
@@ -0,0 +1,40 @@
+
+ */
+class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface
+{
+ public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null)
+ {
+ parent::__construct(array('expr' => $expr), array(), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('echo ')
+ ->subcompile($this->getNode('expr'))
+ ->raw(";\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Sandbox.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Sandbox.php
new file mode 100644
index 0000000..cbfcb41
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Sandbox.php
@@ -0,0 +1,48 @@
+
+ */
+class Twig_Node_Sandbox extends Twig_Node
+{
+ public function __construct(Twig_NodeInterface $body, $lineno, $tag = null)
+ {
+ parent::__construct(array('body' => $body), array(), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write("\$sandbox = \$this->env->getExtension('sandbox');\n")
+ ->write("if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {\n")
+ ->indent()
+ ->write("\$sandbox->enableSandbox();\n")
+ ->outdent()
+ ->write("}\n")
+ ->subcompile($this->getNode('body'))
+ ->write("if (!\$alreadySandboxed) {\n")
+ ->indent()
+ ->write("\$sandbox->disableSandbox();\n")
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/SandboxedModule.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/SandboxedModule.php
new file mode 100644
index 0000000..6dd63e5
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/SandboxedModule.php
@@ -0,0 +1,61 @@
+
+ */
+class Twig_Node_SandboxedModule extends Twig_Node_Module
+{
+ protected $usedFilters;
+ protected $usedTags;
+ protected $usedFunctions;
+
+ public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags, array $usedFunctions)
+ {
+ parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getNode('traits'), $node->getAttribute('embedded_templates'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag());
+
+ $this->setAttribute('index', $node->getAttribute('index'));
+
+ $this->usedFilters = $usedFilters;
+ $this->usedTags = $usedTags;
+ $this->usedFunctions = $usedFunctions;
+ }
+
+ protected function compileDisplayBody(Twig_Compiler $compiler)
+ {
+ $compiler->write("\$this->checkSecurity();\n");
+
+ parent::compileDisplayBody($compiler);
+ }
+
+ protected function compileDisplayFooter(Twig_Compiler $compiler)
+ {
+ parent::compileDisplayFooter($compiler);
+
+ $compiler
+ ->write("protected function checkSecurity() {\n")
+ ->indent()
+ ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n")
+ ->indent()
+ ->write(!$this->usedTags ? "array(),\n" : "array('".implode('\', \'', $this->usedTags)."'),\n")
+ ->write(!$this->usedFilters ? "array(),\n" : "array('".implode('\', \'', $this->usedFilters)."'),\n")
+ ->write(!$this->usedFunctions ? "array()\n" : "array('".implode('\', \'', $this->usedFunctions)."')\n")
+ ->outdent()
+ ->write(");\n")
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/SandboxedPrint.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/SandboxedPrint.php
new file mode 100644
index 0000000..77730d8
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/SandboxedPrint.php
@@ -0,0 +1,60 @@
+
+ */
+class Twig_Node_SandboxedPrint extends Twig_Node_Print
+{
+ public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null)
+ {
+ parent::__construct($expr, $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('echo $this->env->getExtension(\'sandbox\')->ensureToStringAllowed(')
+ ->subcompile($this->getNode('expr'))
+ ->raw(");\n")
+ ;
+ }
+
+ /**
+ * Removes node filters.
+ *
+ * This is mostly needed when another visitor adds filters (like the escaper one).
+ *
+ * @param Twig_Node $node A Node
+ */
+ protected function removeNodeFilter($node)
+ {
+ if ($node instanceof Twig_Node_Expression_Filter) {
+ return $this->removeNodeFilter($node->getNode('node'));
+ }
+
+ return $node;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Set.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Set.php
new file mode 100644
index 0000000..70bb519
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Set.php
@@ -0,0 +1,102 @@
+
+ */
+class Twig_Node_Set extends Twig_Node
+{
+ public function __construct($capture, Twig_NodeInterface $names, Twig_NodeInterface $values, $lineno, $tag = null)
+ {
+ parent::__construct(array('names' => $names, 'values' => $values), array('capture' => $capture, 'safe' => false), $lineno, $tag);
+
+ /*
+ * Optimizes the node when capture is used for a large block of text.
+ *
+ * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig_Markup("foo");
+ */
+ if ($this->getAttribute('capture')) {
+ $this->setAttribute('safe', true);
+
+ $values = $this->getNode('values');
+ if ($values instanceof Twig_Node_Text) {
+ $this->setNode('values', new Twig_Node_Expression_Constant($values->getAttribute('data'), $values->getLine()));
+ $this->setAttribute('capture', false);
+ }
+ }
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler->addDebugInfo($this);
+
+ if (count($this->getNode('names')) > 1) {
+ $compiler->write('list(');
+ foreach ($this->getNode('names') as $idx => $node) {
+ if ($idx) {
+ $compiler->raw(', ');
+ }
+
+ $compiler->subcompile($node);
+ }
+ $compiler->raw(')');
+ } else {
+ if ($this->getAttribute('capture')) {
+ $compiler
+ ->write("ob_start();\n")
+ ->subcompile($this->getNode('values'))
+ ;
+ }
+
+ $compiler->subcompile($this->getNode('names'), false);
+
+ if ($this->getAttribute('capture')) {
+ $compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())");
+ }
+ }
+
+ if (!$this->getAttribute('capture')) {
+ $compiler->raw(' = ');
+
+ if (count($this->getNode('names')) > 1) {
+ $compiler->write('array(');
+ foreach ($this->getNode('values') as $idx => $value) {
+ if ($idx) {
+ $compiler->raw(', ');
+ }
+
+ $compiler->subcompile($value);
+ }
+ $compiler->raw(')');
+ } else {
+ if ($this->getAttribute('safe')) {
+ $compiler
+ ->raw("('' === \$tmp = ")
+ ->subcompile($this->getNode('values'))
+ ->raw(") ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())")
+ ;
+ } else {
+ $compiler->subcompile($this->getNode('values'));
+ }
+ }
+ }
+
+ $compiler->raw(";\n");
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/SetTemp.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/SetTemp.php
new file mode 100644
index 0000000..3bdd1cb
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/SetTemp.php
@@ -0,0 +1,35 @@
+ $name), $lineno);
+ }
+
+ public function compile(Twig_Compiler $compiler)
+ {
+ $name = $this->getAttribute('name');
+ $compiler
+ ->addDebugInfo($this)
+ ->write('if (isset($context[')
+ ->string($name)
+ ->raw('])) { $_')
+ ->raw($name)
+ ->raw('_ = $context[')
+ ->repr($name)
+ ->raw(']; } else { $_')
+ ->raw($name)
+ ->raw("_ = null; }\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Spaceless.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Spaceless.php
new file mode 100644
index 0000000..4601346
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Spaceless.php
@@ -0,0 +1,41 @@
+
+ */
+class Twig_Node_Spaceless extends Twig_Node
+{
+ public function __construct(Twig_NodeInterface $body, $lineno, $tag = 'spaceless')
+ {
+ parent::__construct(array('body' => $body), array(), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write("ob_start();\n")
+ ->subcompile($this->getNode('body'))
+ ->write("echo trim(preg_replace('/>\s+', '><', ob_get_clean()));\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Text.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Text.php
new file mode 100644
index 0000000..0c1c092
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Node/Text.php
@@ -0,0 +1,40 @@
+
+ */
+class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface
+{
+ public function __construct($data, $lineno)
+ {
+ parent::__construct(array(), array('data' => $data), $lineno);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ public function compile(Twig_Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('echo ')
+ ->string($this->getAttribute('data'))
+ ->raw(";\n")
+ ;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeInterface.php
new file mode 100644
index 0000000..29a84b0
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeInterface.php
@@ -0,0 +1,30 @@
+
+ */
+interface Twig_NodeInterface extends Countable, IteratorAggregate
+{
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Twig_Compiler A Twig_Compiler instance
+ */
+ function compile(Twig_Compiler $compiler);
+
+ function getLine();
+
+ function getNodeTag();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeOutputInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeOutputInterface.php
new file mode 100644
index 0000000..7183956
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeOutputInterface.php
@@ -0,0 +1,20 @@
+
+ */
+interface Twig_NodeOutputInterface
+{
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeTraverser.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeTraverser.php
new file mode 100644
index 0000000..1e82b03
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeTraverser.php
@@ -0,0 +1,89 @@
+
+ */
+class Twig_NodeTraverser
+{
+ protected $env;
+ protected $visitors;
+
+ /**
+ * Constructor.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ * @param array $visitors An array of Twig_NodeVisitorInterface instances
+ */
+ public function __construct(Twig_Environment $env, array $visitors = array())
+ {
+ $this->env = $env;
+ $this->visitors = array();
+ foreach ($visitors as $visitor) {
+ $this->addVisitor($visitor);
+ }
+ }
+
+ /**
+ * Adds a visitor.
+ *
+ * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
+ */
+ public function addVisitor(Twig_NodeVisitorInterface $visitor)
+ {
+ if (!isset($this->visitors[$visitor->getPriority()])) {
+ $this->visitors[$visitor->getPriority()] = array();
+ }
+
+ $this->visitors[$visitor->getPriority()][] = $visitor;
+ }
+
+ /**
+ * Traverses a node and calls the registered visitors.
+ *
+ * @param Twig_NodeInterface $node A Twig_NodeInterface instance
+ */
+ public function traverse(Twig_NodeInterface $node)
+ {
+ ksort($this->visitors);
+ foreach ($this->visitors as $visitors) {
+ foreach ($visitors as $visitor) {
+ $node = $this->traverseForVisitor($visitor, $node);
+ }
+ }
+
+ return $node;
+ }
+
+ protected function traverseForVisitor(Twig_NodeVisitorInterface $visitor, Twig_NodeInterface $node = null)
+ {
+ if (null === $node) {
+ return null;
+ }
+
+ $node = $visitor->enterNode($node, $this->env);
+
+ foreach ($node as $k => $n) {
+ if (false !== $n = $this->traverseForVisitor($visitor, $n)) {
+ $node->setNode($k, $n);
+ } else {
+ $node->removeNode($k);
+ }
+ }
+
+ return $visitor->leaveNode($node, $this->env);
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/Escaper.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/Escaper.php
new file mode 100644
index 0000000..5b1249d
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/Escaper.php
@@ -0,0 +1,164 @@
+
+ */
+class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
+{
+ protected $statusStack = array();
+ protected $blocks = array();
+ protected $safeAnalysis;
+ protected $traverser;
+ protected $defaultStrategy = false;
+
+ public function __construct()
+ {
+ $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis();
+ }
+
+ /**
+ * Called before child nodes are visited.
+ *
+ * @param Twig_NodeInterface $node The node to visit
+ * @param Twig_Environment $env The Twig environment instance
+ *
+ * @return Twig_NodeInterface The modified node
+ */
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+ if ($node instanceof Twig_Node_Module) {
+ if ($env->hasExtension('escaper') && $defaultStrategy = $env->getExtension('escaper')->getDefaultStrategy($node->getAttribute('filename'))) {
+ $this->defaultStrategy = $defaultStrategy;
+ }
+ } elseif ($node instanceof Twig_Node_AutoEscape) {
+ $this->statusStack[] = $node->getAttribute('value');
+ } elseif ($node instanceof Twig_Node_Block) {
+ $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env);
+ }
+
+ return $node;
+ }
+
+ /**
+ * Called after child nodes are visited.
+ *
+ * @param Twig_NodeInterface $node The node to visit
+ * @param Twig_Environment $env The Twig environment instance
+ *
+ * @return Twig_NodeInterface The modified node
+ */
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+ if ($node instanceof Twig_Node_Module) {
+ $this->defaultStrategy = false;
+ } elseif ($node instanceof Twig_Node_Expression_Filter) {
+ return $this->preEscapeFilterNode($node, $env);
+ } elseif ($node instanceof Twig_Node_Print) {
+ return $this->escapePrintNode($node, $env, $this->needEscaping($env));
+ }
+
+ if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) {
+ array_pop($this->statusStack);
+ } elseif ($node instanceof Twig_Node_BlockReference) {
+ $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env);
+ }
+
+ return $node;
+ }
+
+ protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type)
+ {
+ if (false === $type) {
+ return $node;
+ }
+
+ $expression = $node->getNode('expr');
+
+ if ($this->isSafeFor($type, $expression, $env)) {
+ return $node;
+ }
+
+ $class = get_class($node);
+
+ return new $class(
+ $this->getEscaperFilter($type, $expression),
+ $node->getLine()
+ );
+ }
+
+ protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env)
+ {
+ $name = $filter->getNode('filter')->getAttribute('value');
+
+ if (false !== $f = $env->getFilter($name)) {
+ $type = $f->getPreEscape();
+ if (null === $type) {
+ return $filter;
+ }
+
+ $node = $filter->getNode('node');
+ if ($this->isSafeFor($type, $node, $env)) {
+ return $filter;
+ }
+
+ $filter->setNode('node', $this->getEscaperFilter($type, $node));
+
+ return $filter;
+ }
+
+ return $filter;
+ }
+
+ protected function isSafeFor($type, Twig_NodeInterface $expression, $env)
+ {
+ $safe = $this->safeAnalysis->getSafe($expression);
+
+ if (null === $safe) {
+ if (null === $this->traverser) {
+ $this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis));
+ }
+ $this->traverser->traverse($expression);
+ $safe = $this->safeAnalysis->getSafe($expression);
+ }
+
+ return in_array($type, $safe) || in_array('all', $safe);
+ }
+
+ protected function needEscaping(Twig_Environment $env)
+ {
+ if (count($this->statusStack)) {
+ return $this->statusStack[count($this->statusStack) - 1];
+ }
+
+ return $this->defaultStrategy ? $this->defaultStrategy : false;
+ }
+
+ protected function getEscaperFilter($type, Twig_NodeInterface $node)
+ {
+ $line = $node->getLine();
+ $name = new Twig_Node_Expression_Constant('escape', $line);
+ $args = new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line), new Twig_Node_Expression_Constant(null, $line), new Twig_Node_Expression_Constant(true, $line)));
+
+ return new Twig_Node_Expression_Filter($node, $name, $args, $line);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPriority()
+ {
+ return 0;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/Optimizer.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/Optimizer.php
new file mode 100644
index 0000000..cbc61fc
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/Optimizer.php
@@ -0,0 +1,247 @@
+
+ */
+class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
+{
+ const OPTIMIZE_ALL = -1;
+ const OPTIMIZE_NONE = 0;
+ const OPTIMIZE_FOR = 2;
+ const OPTIMIZE_RAW_FILTER = 4;
+ const OPTIMIZE_VAR_ACCESS = 8;
+
+ protected $loops = array();
+ protected $optimizers;
+ protected $prependedNodes = array();
+ protected $inABody = false;
+
+ /**
+ * Constructor.
+ *
+ * @param integer $optimizers The optimizer mode
+ */
+ public function __construct($optimizers = -1)
+ {
+ if (!is_int($optimizers) || $optimizers > 2) {
+ throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
+ }
+
+ $this->optimizers = $optimizers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+ if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
+ $this->enterOptimizeFor($node, $env);
+ }
+
+ if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
+ if ($this->inABody) {
+ if (!$node instanceof Twig_Node_Expression) {
+ if (get_class($node) !== 'Twig_Node') {
+ array_unshift($this->prependedNodes, array());
+ }
+ } else {
+ $node = $this->optimizeVariables($node, $env);
+ }
+ } elseif ($node instanceof Twig_Node_Body) {
+ $this->inABody = true;
+ }
+ }
+
+ return $node;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+ $expression = $node instanceof Twig_Node_Expression;
+
+ if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
+ $this->leaveOptimizeFor($node, $env);
+ }
+
+ if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
+ $node = $this->optimizeRawFilter($node, $env);
+ }
+
+ $node = $this->optimizePrintNode($node, $env);
+
+ if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
+ if ($node instanceof Twig_Node_Body) {
+ $this->inABody = false;
+ } elseif ($this->inABody) {
+ if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) {
+ $nodes = array();
+ foreach (array_unique($prependedNodes) as $name) {
+ $nodes[] = new Twig_Node_SetTemp($name, $node->getLine());
+ }
+
+ $nodes[] = $node;
+ $node = new Twig_Node($nodes);
+ }
+ }
+ }
+
+ return $node;
+ }
+
+ protected function optimizeVariables($node, $env)
+ {
+ if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) {
+ $this->prependedNodes[0][] = $node->getAttribute('name');
+
+ return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine());
+ }
+
+ return $node;
+ }
+
+ /**
+ * Optimizes print nodes.
+ *
+ * It replaces:
+ *
+ * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
+ *
+ * @param Twig_NodeInterface $node A Node
+ * @param Twig_Environment $env The current Twig environment
+ */
+ protected function optimizePrintNode($node, $env)
+ {
+ if (!$node instanceof Twig_Node_Print) {
+ return $node;
+ }
+
+ if (
+ $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference ||
+ $node->getNode('expr') instanceof Twig_Node_Expression_Parent
+ ) {
+ $node->getNode('expr')->setAttribute('output', true);
+
+ return $node->getNode('expr');
+ }
+
+ return $node;
+ }
+
+ /**
+ * Removes "raw" filters.
+ *
+ * @param Twig_NodeInterface $node A Node
+ * @param Twig_Environment $env The current Twig environment
+ */
+ protected function optimizeRawFilter($node, $env)
+ {
+ if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
+ return $node->getNode('node');
+ }
+
+ return $node;
+ }
+
+ /**
+ * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
+ *
+ * @param Twig_NodeInterface $node A Node
+ * @param Twig_Environment $env The current Twig environment
+ */
+ protected function enterOptimizeFor($node, $env)
+ {
+ if ($node instanceof Twig_Node_For) {
+ // disable the loop variable by default
+ $node->setAttribute('with_loop', false);
+ array_unshift($this->loops, $node);
+ } elseif (!$this->loops) {
+ // we are outside a loop
+ return;
+ }
+
+ // when do we need to add the loop variable back?
+
+ // the loop variable is referenced for the current loop
+ elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
+ $this->addLoopToCurrent();
+ }
+
+ // block reference
+ elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
+ $this->addLoopToCurrent();
+ }
+
+ // include without the only attribute
+ elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
+ $this->addLoopToAll();
+ }
+
+ // the loop variable is referenced via an attribute
+ elseif ($node instanceof Twig_Node_Expression_GetAttr
+ && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
+ || 'parent' === $node->getNode('attribute')->getAttribute('value')
+ )
+ && (true === $this->loops[0]->getAttribute('with_loop')
+ || ($node->getNode('node') instanceof Twig_Node_Expression_Name
+ && 'loop' === $node->getNode('node')->getAttribute('name')
+ )
+ )
+ ) {
+ $this->addLoopToAll();
+ }
+ }
+
+ /**
+ * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
+ *
+ * @param Twig_NodeInterface $node A Node
+ * @param Twig_Environment $env The current Twig environment
+ */
+ protected function leaveOptimizeFor($node, $env)
+ {
+ if ($node instanceof Twig_Node_For) {
+ array_shift($this->loops);
+ }
+ }
+
+ protected function addLoopToCurrent()
+ {
+ $this->loops[0]->setAttribute('with_loop', true);
+ }
+
+ protected function addLoopToAll()
+ {
+ foreach ($this->loops as $loop) {
+ $loop->setAttribute('with_loop', true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPriority()
+ {
+ return 255;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/SafeAnalysis.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/SafeAnalysis.php
new file mode 100644
index 0000000..9f511b5
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/SafeAnalysis.php
@@ -0,0 +1,119 @@
+data[$hash])) {
+ foreach($this->data[$hash] as $bucket) {
+ if ($bucket['key'] === $node) {
+ return $bucket['value'];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ protected function setSafe(Twig_NodeInterface $node, array $safe)
+ {
+ $hash = spl_object_hash($node);
+ if (isset($this->data[$hash])) {
+ foreach($this->data[$hash] as &$bucket) {
+ if ($bucket['key'] === $node) {
+ $bucket['value'] = $safe;
+
+ return;
+ }
+ }
+ }
+ $this->data[$hash][] = array(
+ 'key' => $node,
+ 'value' => $safe,
+ );
+ }
+
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+ return $node;
+ }
+
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+ if ($node instanceof Twig_Node_Expression_Constant) {
+ // constants are marked safe for all
+ $this->setSafe($node, array('all'));
+ } elseif ($node instanceof Twig_Node_Expression_BlockReference) {
+ // blocks are safe by definition
+ $this->setSafe($node, array('all'));
+ } elseif ($node instanceof Twig_Node_Expression_Parent) {
+ // parent block is safe by definition
+ $this->setSafe($node, array('all'));
+ } elseif ($node instanceof Twig_Node_Expression_Conditional) {
+ // intersect safeness of both operands
+ $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3')));
+ $this->setSafe($node, $safe);
+ } elseif ($node instanceof Twig_Node_Expression_Filter) {
+ // filter expression is safe when the filter is safe
+ $name = $node->getNode('filter')->getAttribute('value');
+ $args = $node->getNode('arguments');
+ if (false !== $filter = $env->getFilter($name)) {
+ $safe = $filter->getSafe($args);
+ if (null === $safe) {
+ $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety());
+ }
+ $this->setSafe($node, $safe);
+ } else {
+ $this->setSafe($node, array());
+ }
+ } elseif ($node instanceof Twig_Node_Expression_Function) {
+ // function expression is safe when the function is safe
+ $name = $node->getAttribute('name');
+ $args = $node->getNode('arguments');
+ $function = $env->getFunction($name);
+ if (false !== $function) {
+ $this->setSafe($node, $function->getSafe($args));
+ } else {
+ $this->setSafe($node, array());
+ }
+ } elseif ($node instanceof Twig_Node_Expression_MethodCall) {
+ if ($node->getAttribute('safe')) {
+ $this->setSafe($node, array('all'));
+ } else {
+ $this->setSafe($node, array());
+ }
+ } else {
+ $this->setSafe($node, array());
+ }
+
+ return $node;
+ }
+
+ protected function intersectSafe(array $a = null, array $b = null)
+ {
+ if (null === $a || null === $b) {
+ return array();
+ }
+
+ if (in_array('all', $a)) {
+ return $b;
+ }
+
+ if (in_array('all', $b)) {
+ return $a;
+ }
+
+ return array_intersect($a, $b);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPriority()
+ {
+ return 0;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/Sandbox.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/Sandbox.php
new file mode 100644
index 0000000..61ef0c6
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitor/Sandbox.php
@@ -0,0 +1,106 @@
+
+ */
+class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface
+{
+ protected $inAModule = false;
+ protected $tags;
+ protected $filters;
+ protected $functions;
+
+ /**
+ * Called before child nodes are visited.
+ *
+ * @param Twig_NodeInterface $node The node to visit
+ * @param Twig_Environment $env The Twig environment instance
+ *
+ * @return Twig_NodeInterface The modified node
+ */
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+ // in a sandbox tag, only include tags are allowed
+ if ($node instanceof Twig_Node_Sandbox && !$node->getNode('body') instanceof Twig_Node_Include) {
+ foreach ($node->getNode('body') as $n) {
+ if ($n instanceof Twig_Node_Text && ctype_space($n->getAttribute('data'))) {
+ continue;
+ }
+
+ if (!$n instanceof Twig_Node_Include) {
+ throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section', $n->getLine());
+ }
+ }
+ }
+
+ if ($node instanceof Twig_Node_Module) {
+ $this->inAModule = true;
+ $this->tags = array();
+ $this->filters = array();
+ $this->functions = array();
+
+ return $node;
+ } elseif ($this->inAModule) {
+ // look for tags
+ if ($node->getNodeTag()) {
+ $this->tags[] = $node->getNodeTag();
+ }
+
+ // look for filters
+ if ($node instanceof Twig_Node_Expression_Filter) {
+ $this->filters[] = $node->getNode('filter')->getAttribute('value');
+ }
+
+ // look for functions
+ if ($node instanceof Twig_Node_Expression_Function) {
+ $this->functions[] = $node->getAttribute('name');
+ }
+
+ // wrap print to check __toString() calls
+ if ($node instanceof Twig_Node_Print) {
+ return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getLine(), $node->getNodeTag());
+ }
+ }
+
+ return $node;
+ }
+
+ /**
+ * Called after child nodes are visited.
+ *
+ * @param Twig_NodeInterface $node The node to visit
+ * @param Twig_Environment $env The Twig environment instance
+ *
+ * @return Twig_NodeInterface The modified node
+ */
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
+ {
+ if ($node instanceof Twig_Node_Module) {
+ $this->inAModule = false;
+
+ return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags), array_unique($this->functions));
+ }
+
+ return $node;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPriority()
+ {
+ return 0;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitorInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitorInterface.php
new file mode 100644
index 0000000..e0123b5
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/NodeVisitorInterface.php
@@ -0,0 +1,48 @@
+
+ */
+interface Twig_NodeVisitorInterface
+{
+ /**
+ * Called before child nodes are visited.
+ *
+ * @param Twig_NodeInterface $node The node to visit
+ * @param Twig_Environment $env The Twig environment instance
+ *
+ * @return Twig_NodeInterface The modified node
+ */
+ function enterNode(Twig_NodeInterface $node, Twig_Environment $env);
+
+ /**
+ * Called after child nodes are visited.
+ *
+ * @param Twig_NodeInterface $node The node to visit
+ * @param Twig_Environment $env The Twig environment instance
+ *
+ * @return Twig_NodeInterface The modified node
+ */
+ function leaveNode(Twig_NodeInterface $node, Twig_Environment $env);
+
+ /**
+ * Returns the priority for this visitor.
+ *
+ * Priority should be between -10 and 10 (0 is the default).
+ *
+ * @return integer The priority level
+ */
+ function getPriority();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Parser.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Parser.php
new file mode 100644
index 0000000..0bfcec7
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Parser.php
@@ -0,0 +1,384 @@
+
+ */
+class Twig_Parser implements Twig_ParserInterface
+{
+ protected $stack = array();
+ protected $stream;
+ protected $parent;
+ protected $handlers;
+ protected $visitors;
+ protected $expressionParser;
+ protected $blocks;
+ protected $blockStack;
+ protected $macros;
+ protected $env;
+ protected $reservedMacroNames;
+ protected $importedFunctions;
+ protected $tmpVarCount;
+ protected $traits;
+ protected $embeddedTemplates = array();
+
+ /**
+ * Constructor.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ */
+ public function __construct(Twig_Environment $env)
+ {
+ $this->env = $env;
+ }
+
+ public function getEnvironment()
+ {
+ return $this->env;
+ }
+
+ public function getVarName()
+ {
+ return sprintf('__internal_%s_%d', substr($this->env->getTemplateClass($this->stream->getFilename()), strlen($this->env->getTemplateClassPrefix())), ++$this->tmpVarCount);
+ }
+
+ /**
+ * Converts a token stream to a node tree.
+ *
+ * @param Twig_TokenStream $stream A token stream instance
+ *
+ * @return Twig_Node_Module A node tree
+ */
+ public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false)
+ {
+ // push all variables into the stack to keep the current state of the parser
+ $vars = get_object_vars($this);
+ unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser']);
+ $this->stack[] = $vars;
+
+ $this->tmpVarCount = 0;
+
+ // tag handlers
+ if (null === $this->handlers) {
+ $this->handlers = $this->env->getTokenParsers();
+ $this->handlers->setParser($this);
+ }
+
+ // node visitors
+ if (null === $this->visitors) {
+ $this->visitors = $this->env->getNodeVisitors();
+ }
+
+ if (null === $this->expressionParser) {
+ $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators());
+ }
+
+ $this->stream = $stream;
+ $this->parent = null;
+ $this->blocks = array();
+ $this->macros = array();
+ $this->traits = array();
+ $this->blockStack = array();
+ $this->importedFunctions = array(array());
+ $this->embeddedTemplates = array();
+
+ try {
+ $body = $this->subparse($test, $dropNeedle);
+
+ if (null !== $this->parent) {
+ if (null === $body = $this->filterBodyNodes($body)) {
+ $body = new Twig_Node();
+ }
+ }
+ } catch (Twig_Error_Syntax $e) {
+ if (null === $e->getTemplateFile()) {
+ $e->setTemplateFile($this->stream->getFilename());
+ }
+
+ throw $e;
+ }
+
+ $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $this->stream->getFilename());
+
+ $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
+
+ $node = $traverser->traverse($node);
+
+ // restore previous stack so previous parse() call can resume working
+ foreach (array_pop($this->stack) as $key => $val) {
+ $this->$key = $val;
+ }
+
+ return $node;
+ }
+
+ public function subparse($test, $dropNeedle = false)
+ {
+ $lineno = $this->getCurrentToken()->getLine();
+ $rv = array();
+ while (!$this->stream->isEOF()) {
+ switch ($this->getCurrentToken()->getType()) {
+ case Twig_Token::TEXT_TYPE:
+ $token = $this->stream->next();
+ $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine());
+ break;
+
+ case Twig_Token::VAR_START_TYPE:
+ $token = $this->stream->next();
+ $expr = $this->expressionParser->parseExpression();
+ $this->stream->expect(Twig_Token::VAR_END_TYPE);
+ $rv[] = new Twig_Node_Print($expr, $token->getLine());
+ break;
+
+ case Twig_Token::BLOCK_START_TYPE:
+ $this->stream->next();
+ $token = $this->getCurrentToken();
+
+ if ($token->getType() !== Twig_Token::NAME_TYPE) {
+ throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->stream->getFilename());
+ }
+
+ if (null !== $test && call_user_func($test, $token)) {
+ if ($dropNeedle) {
+ $this->stream->next();
+ }
+
+ if (1 === count($rv)) {
+ return $rv[0];
+ }
+
+ return new Twig_Node($rv, array(), $lineno);
+ }
+
+ $subparser = $this->handlers->getTokenParser($token->getValue());
+ if (null === $subparser) {
+ if (null !== $test) {
+ throw new Twig_Error_Syntax(sprintf('Unexpected tag name "%s" (expecting closing tag for the "%s" tag defined near line %s)', $token->getValue(), $test[0]->getTag(), $lineno), $token->getLine(), $this->stream->getFilename());
+ }
+
+ $message = sprintf('Unknown tag name "%s"', $token->getValue());
+ if ($alternatives = $this->env->computeAlternatives($token->getValue(), array_keys($this->env->getTags()))) {
+ $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
+ }
+
+ throw new Twig_Error_Syntax($message, $token->getLine(), $this->stream->getFilename());
+ }
+
+ $this->stream->next();
+
+ $node = $subparser->parse($token);
+ if (null !== $node) {
+ $rv[] = $node;
+ }
+ break;
+
+ default:
+ throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', -1, $this->stream->getFilename());
+ }
+ }
+
+ if (1 === count($rv)) {
+ return $rv[0];
+ }
+
+ return new Twig_Node($rv, array(), $lineno);
+ }
+
+ public function addHandler($name, $class)
+ {
+ $this->handlers[$name] = $class;
+ }
+
+ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
+ {
+ $this->visitors[] = $visitor;
+ }
+
+ public function getBlockStack()
+ {
+ return $this->blockStack;
+ }
+
+ public function peekBlockStack()
+ {
+ return $this->blockStack[count($this->blockStack) - 1];
+ }
+
+ public function popBlockStack()
+ {
+ array_pop($this->blockStack);
+ }
+
+ public function pushBlockStack($name)
+ {
+ $this->blockStack[] = $name;
+ }
+
+ public function hasBlock($name)
+ {
+ return isset($this->blocks[$name]);
+ }
+
+ public function getBlock($name)
+ {
+ return $this->blocks[$name];
+ }
+
+ public function setBlock($name, $value)
+ {
+ $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine());
+ }
+
+ public function hasMacro($name)
+ {
+ return isset($this->macros[$name]);
+ }
+
+ public function setMacro($name, Twig_Node_Macro $node)
+ {
+ if (null === $this->reservedMacroNames) {
+ $this->reservedMacroNames = array();
+ $r = new ReflectionClass($this->env->getBaseTemplateClass());
+ foreach ($r->getMethods() as $method) {
+ $this->reservedMacroNames[] = $method->getName();
+ }
+ }
+
+ if (in_array($name, $this->reservedMacroNames)) {
+ throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine());
+ }
+
+ $this->macros[$name] = $node;
+ }
+
+ public function addTrait($trait)
+ {
+ $this->traits[] = $trait;
+ }
+
+ public function hasTraits()
+ {
+ return count($this->traits) > 0;
+ }
+
+ public function embedTemplate(Twig_Node_Module $template)
+ {
+ $template->setIndex(count($this->embeddedTemplates) + 1);
+
+ $this->embeddedTemplates[] = $template;
+ }
+
+ public function addImportedFunction($alias, $name, Twig_Node_Expression $node)
+ {
+ $this->importedFunctions[0][$alias] = array('name' => $name, 'node' => $node);
+ }
+
+ public function getImportedFunction($alias)
+ {
+ foreach ($this->importedFunctions as $functions) {
+ if (isset($functions[$alias])) {
+ return $functions[$alias];
+ }
+ }
+ }
+
+ public function isMainScope()
+ {
+ return 1 === count($this->importedFunctions);
+ }
+
+ public function pushLocalScope()
+ {
+ array_unshift($this->importedFunctions, array());
+ }
+
+ public function popLocalScope()
+ {
+ array_shift($this->importedFunctions);
+ }
+
+ /**
+ * Gets the expression parser.
+ *
+ * @return Twig_ExpressionParser The expression parser
+ */
+ public function getExpressionParser()
+ {
+ return $this->expressionParser;
+ }
+
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ public function setParent($parent)
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Gets the token stream.
+ *
+ * @return Twig_TokenStream The token stream
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+
+ /**
+ * Gets the current token.
+ *
+ * @return Twig_Token The current token
+ */
+ public function getCurrentToken()
+ {
+ return $this->stream->getCurrent();
+ }
+
+ protected function filterBodyNodes(Twig_NodeInterface $node)
+ {
+ // check that the body does not contain non-empty output nodes
+ if (
+ ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data')))
+ ||
+ (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
+ ) {
+ if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) {
+ throw new Twig_Error_Syntax('A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.', $node->getLine(), $this->stream->getFilename());
+ }
+
+ throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->stream->getFilename());
+ }
+
+ // bypass "set" nodes as they "capture" the output
+ if ($node instanceof Twig_Node_Set) {
+ return $node;
+ }
+
+ if ($node instanceof Twig_NodeOutputInterface) {
+ return;
+ }
+
+ foreach ($node as $k => $n) {
+ if (null !== $n && null === $n = $this->filterBodyNodes($n)) {
+ $node->removeNode($k);
+ }
+ }
+
+ return $node;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/ParserInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/ParserInterface.php
new file mode 100644
index 0000000..4cb6d28
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/ParserInterface.php
@@ -0,0 +1,28 @@
+
+ */
+interface Twig_ParserInterface
+{
+ /**
+ * Converts a token stream to a node tree.
+ *
+ * @param Twig_TokenStream $stream A token stream instance
+ *
+ * @return Twig_Node_Module A node tree
+ */
+ function parse(Twig_TokenStream $stream);
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Sandbox/SecurityError.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Sandbox/SecurityError.php
new file mode 100644
index 0000000..debabb7
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Sandbox/SecurityError.php
@@ -0,0 +1,20 @@
+
+ */
+class Twig_Sandbox_SecurityError extends Twig_Error
+{
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Sandbox/SecurityPolicy.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Sandbox/SecurityPolicy.php
new file mode 100644
index 0000000..ba912ef
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Sandbox/SecurityPolicy.php
@@ -0,0 +1,120 @@
+
+ */
+class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterface
+{
+ protected $allowedTags;
+ protected $allowedFilters;
+ protected $allowedMethods;
+ protected $allowedProperties;
+ protected $allowedFunctions;
+
+ public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array(), array $allowedProperties = array(), array $allowedFunctions = array())
+ {
+ $this->allowedTags = $allowedTags;
+ $this->allowedFilters = $allowedFilters;
+ $this->setAllowedMethods($allowedMethods);
+ $this->allowedProperties = $allowedProperties;
+ $this->allowedFunctions = $allowedFunctions;
+ }
+
+ public function setAllowedTags(array $tags)
+ {
+ $this->allowedTags = $tags;
+ }
+
+ public function setAllowedFilters(array $filters)
+ {
+ $this->allowedFilters = $filters;
+ }
+
+ public function setAllowedMethods(array $methods)
+ {
+ $this->allowedMethods = array();
+ foreach ($methods as $class => $m) {
+ $this->allowedMethods[$class] = array_map('strtolower', is_array($m) ? $m : array($m));
+ }
+ }
+
+ public function setAllowedProperties(array $properties)
+ {
+ $this->allowedProperties = $properties;
+ }
+
+ public function setAllowedFunctions(array $functions)
+ {
+ $this->allowedFunctions = $functions;
+ }
+
+ public function checkSecurity($tags, $filters, $functions)
+ {
+ foreach ($tags as $tag) {
+ if (!in_array($tag, $this->allowedTags)) {
+ throw new Twig_Sandbox_SecurityError(sprintf('Tag "%s" is not allowed.', $tag));
+ }
+ }
+
+ foreach ($filters as $filter) {
+ if (!in_array($filter, $this->allowedFilters)) {
+ throw new Twig_Sandbox_SecurityError(sprintf('Filter "%s" is not allowed.', $filter));
+ }
+ }
+
+ foreach ($functions as $function) {
+ if (!in_array($function, $this->allowedFunctions)) {
+ throw new Twig_Sandbox_SecurityError(sprintf('Function "%s" is not allowed.', $function));
+ }
+ }
+ }
+
+ public function checkMethodAllowed($obj, $method)
+ {
+ if ($obj instanceof Twig_TemplateInterface || $obj instanceof Twig_Markup) {
+ return true;
+ }
+
+ $allowed = false;
+ $method = strtolower($method);
+ foreach ($this->allowedMethods as $class => $methods) {
+ if ($obj instanceof $class) {
+ $allowed = in_array($method, $methods);
+
+ break;
+ }
+ }
+
+ if (!$allowed) {
+ throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj)));
+ }
+ }
+
+ public function checkPropertyAllowed($obj, $property)
+ {
+ $allowed = false;
+ foreach ($this->allowedProperties as $class => $properties) {
+ if ($obj instanceof $class) {
+ $allowed = in_array($property, is_array($properties) ? $properties : array($properties));
+
+ break;
+ }
+ }
+
+ if (!$allowed) {
+ throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, get_class($obj)));
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Sandbox/SecurityPolicyInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Sandbox/SecurityPolicyInterface.php
new file mode 100644
index 0000000..d5015af
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Sandbox/SecurityPolicyInterface.php
@@ -0,0 +1,25 @@
+
+ */
+interface Twig_Sandbox_SecurityPolicyInterface
+{
+ function checkSecurity($tags, $filters, $functions);
+
+ function checkMethodAllowed($obj, $method);
+
+ function checkPropertyAllowed($obj, $method);
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Template.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Template.php
new file mode 100644
index 0000000..9357781
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Template.php
@@ -0,0 +1,450 @@
+
+ */
+abstract class Twig_Template implements Twig_TemplateInterface
+{
+ static protected $cache = array();
+
+ protected $parent;
+ protected $parents;
+ protected $env;
+ protected $blocks;
+ protected $traits;
+
+ /**
+ * Constructor.
+ *
+ * @param Twig_Environment $env A Twig_Environment instance
+ */
+ public function __construct(Twig_Environment $env)
+ {
+ $this->env = $env;
+ $this->blocks = array();
+ $this->traits = array();
+ }
+
+ /**
+ * Returns the template name.
+ *
+ * @return string The template name
+ */
+ abstract public function getTemplateName();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getEnvironment()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Returns the parent template.
+ *
+ * This method is for internal use only and should never be called
+ * directly.
+ *
+ * @return Twig_TemplateInterface|false The parent template or false if there is no parent
+ */
+ public function getParent(array $context)
+ {
+ if (null !== $this->parent) {
+ return $this->parent;
+ }
+
+ $parent = $this->doGetParent($context);
+ if (false === $parent) {
+ return false;
+ } elseif ($parent instanceof Twig_Template) {
+ $name = $parent->getTemplateName();
+ $this->parents[$name] = $parent;
+ $parent = $name;
+ } elseif (!isset($this->parents[$parent])) {
+ $this->parents[$parent] = $this->env->loadTemplate($parent);
+ }
+
+ return $this->parents[$parent];
+ }
+
+ protected function doGetParent(array $context)
+ {
+ return false;
+ }
+
+ public function isTraitable()
+ {
+ return true;
+ }
+
+ /**
+ * Displays a parent block.
+ *
+ * This method is for internal use only and should never be called
+ * directly.
+ *
+ * @param string $name The block name to display from the parent
+ * @param array $context The context
+ * @param array $blocks The current set of blocks
+ */
+ public function displayParentBlock($name, array $context, array $blocks = array())
+ {
+ $name = (string) $name;
+
+ if (isset($this->traits[$name])) {
+ $this->traits[$name][0]->displayBlock($name, $context, $blocks);
+ } elseif (false !== $parent = $this->getParent($context)) {
+ $parent->displayBlock($name, $context, $blocks);
+ } else {
+ throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block', $name), -1, $this->getTemplateName());
+ }
+ }
+
+ /**
+ * Displays a block.
+ *
+ * This method is for internal use only and should never be called
+ * directly.
+ *
+ * @param string $name The block name to display
+ * @param array $context The context
+ * @param array $blocks The current set of blocks
+ */
+ public function displayBlock($name, array $context, array $blocks = array())
+ {
+ $name = (string) $name;
+
+ if (isset($blocks[$name])) {
+ $b = $blocks;
+ unset($b[$name]);
+ call_user_func($blocks[$name], $context, $b);
+ } elseif (isset($this->blocks[$name])) {
+ call_user_func($this->blocks[$name], $context, $blocks);
+ } elseif (false !== $parent = $this->getParent($context)) {
+ $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks));
+ }
+ }
+
+ /**
+ * Renders a parent block.
+ *
+ * This method is for internal use only and should never be called
+ * directly.
+ *
+ * @param string $name The block name to render from the parent
+ * @param array $context The context
+ * @param array $blocks The current set of blocks
+ *
+ * @return string The rendered block
+ */
+ public function renderParentBlock($name, array $context, array $blocks = array())
+ {
+ ob_start();
+ $this->displayParentBlock($name, $context, $blocks);
+
+ return ob_get_clean();
+ }
+
+ /**
+ * Renders a block.
+ *
+ * This method is for internal use only and should never be called
+ * directly.
+ *
+ * @param string $name The block name to render
+ * @param array $context The context
+ * @param array $blocks The current set of blocks
+ *
+ * @return string The rendered block
+ */
+ public function renderBlock($name, array $context, array $blocks = array())
+ {
+ ob_start();
+ $this->displayBlock($name, $context, $blocks);
+
+ return ob_get_clean();
+ }
+
+ /**
+ * Returns whether a block exists or not.
+ *
+ * This method is for internal use only and should never be called
+ * directly.
+ *
+ * This method does only return blocks defined in the current template
+ * or defined in "used" traits.
+ *
+ * It does not return blocks from parent templates as the parent
+ * template name can be dynamic, which is only known based on the
+ * current context.
+ *
+ * @param string $name The block name
+ *
+ * @return Boolean true if the block exists, false otherwise
+ */
+ public function hasBlock($name)
+ {
+ return isset($this->blocks[(string) $name]);
+ }
+
+ /**
+ * Returns all block names.
+ *
+ * This method is for internal use only and should never be called
+ * directly.
+ *
+ * @return array An array of block names
+ *
+ * @see hasBlock
+ */
+ public function getBlockNames()
+ {
+ return array_keys($this->blocks);
+ }
+
+ /**
+ * Returns all blocks.
+ *
+ * This method is for internal use only and should never be called
+ * directly.
+ *
+ * @return array An array of blocks
+ *
+ * @see hasBlock
+ */
+ public function getBlocks()
+ {
+ return $this->blocks;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function display(array $context, array $blocks = array())
+ {
+ $this->displayWithErrorHandling($this->env->mergeGlobals($context), $blocks);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render(array $context)
+ {
+ $level = ob_get_level();
+ ob_start();
+ try {
+ $this->display($context);
+ } catch (Exception $e) {
+ while (ob_get_level() > $level) {
+ ob_end_clean();
+ }
+
+ throw $e;
+ }
+
+ return ob_get_clean();
+ }
+
+ protected function displayWithErrorHandling(array $context, array $blocks = array())
+ {
+ try {
+ $this->doDisplay($context, $blocks);
+ } catch (Twig_Error $e) {
+ throw $e;
+ } catch (Exception $e) {
+ throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e);
+ }
+ }
+
+ /**
+ * Auto-generated method to display the template with the given context.
+ *
+ * @param array $context An array of parameters to pass to the template
+ * @param array $blocks An array of blocks to pass to the template
+ */
+ abstract protected function doDisplay(array $context, array $blocks = array());
+
+ /**
+ * Returns a variable from the context.
+ *
+ * This method is for internal use only and should never be called
+ * directly.
+ *
+ * This method should not be overriden in a sub-class as this is an
+ * implementation detail that has been introduced to optimize variable
+ * access for versions of PHP before 5.4. This is not a way to override
+ * the way to get a variable value.
+ *
+ * @param array $context The context
+ * @param string $item The variable to return from the context
+ * @param Boolean $ignoreStrictCheck Whether to ignore the strict variable check or not
+ *
+ * @return The content of the context variable
+ *
+ * @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode
+ */
+ final protected function getContext($context, $item, $ignoreStrictCheck = false)
+ {
+ if (!array_key_exists($item, $context)) {
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
+ return null;
+ }
+
+ throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item));
+ }
+
+ return $context[$item];
+ }
+
+ /**
+ * Returns the attribute value for a given array/object.
+ *
+ * @param mixed $object The object or array from where to get the item
+ * @param mixed $item The item to get from the array or object
+ * @param array $arguments An array of arguments to pass if the item is an object method
+ * @param string $type The type of attribute (@see Twig_TemplateInterface)
+ * @param Boolean $isDefinedTest Whether this is only a defined check
+ * @param Boolean $ignoreStrictCheck Whether to ignore the strict attribute check or not
+ *
+ * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
+ *
+ * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
+ */
+ protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
+ {
+ $item = (string) $item;
+
+ // array
+ if (Twig_TemplateInterface::METHOD_CALL !== $type) {
+ if ((is_array($object) && array_key_exists($item, $object))
+ || ($object instanceof ArrayAccess && isset($object[$item]))
+ ) {
+ if ($isDefinedTest) {
+ return true;
+ }
+
+ return $object[$item];
+ }
+
+ if (Twig_TemplateInterface::ARRAY_CALL === $type) {
+ if ($isDefinedTest) {
+ return false;
+ }
+
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
+ return null;
+ }
+
+ if (is_object($object)) {
+ throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)));
+ } elseif (is_array($object)) {
+ throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))));
+ } else {
+ throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)));
+ }
+ }
+ }
+
+ if (!is_object($object)) {
+ if ($isDefinedTest) {
+ return false;
+ }
+
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
+ return null;
+ }
+
+ throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object));
+ }
+
+ $class = get_class($object);
+
+ // object property
+ if (Twig_TemplateInterface::METHOD_CALL !== $type) {
+ /* apparently, this is not needed as this is already covered by the array_key_exists() call below
+ if (!isset(self::$cache[$class]['properties'])) {
+ foreach (get_object_vars($object) as $k => $v) {
+ self::$cache[$class]['properties'][$k] = true;
+ }
+ }
+ */
+
+ if (isset($object->$item) || array_key_exists($item, $object)) {
+ if ($isDefinedTest) {
+ return true;
+ }
+
+ if ($this->env->hasExtension('sandbox')) {
+ $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
+ }
+
+ return $object->$item;
+ }
+ }
+
+ // object method
+ if (!isset(self::$cache[$class]['methods'])) {
+ self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
+ }
+
+ $lcItem = strtolower($item);
+ if (isset(self::$cache[$class]['methods'][$lcItem])) {
+ $method = $item;
+ } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
+ $method = 'get'.$item;
+ } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
+ $method = 'is'.$item;
+ } elseif (isset(self::$cache[$class]['methods']['__call'])) {
+ $method = $item;
+ } else {
+ if ($isDefinedTest) {
+ return false;
+ }
+
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
+ return null;
+ }
+
+ throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)));
+ }
+
+ if ($isDefinedTest) {
+ return true;
+ }
+
+ if ($this->env->hasExtension('sandbox')) {
+ $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
+ }
+
+ $ret = call_user_func_array(array($object, $method), $arguments);
+
+ // hack to be removed when macro calls are refactored
+ if ($object instanceof Twig_TemplateInterface) {
+ return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset());
+ }
+
+ return $ret;
+ }
+
+ /**
+ * This method is only useful when testing Twig. Do not use it.
+ */
+ static public function clearCache()
+ {
+ self::$cache = array();
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TemplateInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TemplateInterface.php
new file mode 100644
index 0000000..08da116
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TemplateInterface.php
@@ -0,0 +1,47 @@
+
+ */
+interface Twig_TemplateInterface
+{
+ const ANY_CALL = 'any';
+ const ARRAY_CALL = 'array';
+ const METHOD_CALL = 'method';
+
+ /**
+ * Renders the template with the given context and returns it as string.
+ *
+ * @param array $context An array of parameters to pass to the template
+ *
+ * @return string The rendered template
+ */
+ function render(array $context);
+
+ /**
+ * Displays the template with the given context.
+ *
+ * @param array $context An array of parameters to pass to the template
+ * @param array $blocks An array of blocks to pass to the template
+ */
+ function display(array $context, array $blocks = array());
+
+ /**
+ * Returns the bound environment for this template.
+ *
+ * @return Twig_Environment The current environment
+ */
+ function getEnvironment();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Test/Function.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Test/Function.php
new file mode 100644
index 0000000..1240a0f
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Test/Function.php
@@ -0,0 +1,31 @@
+
+ */
+class Twig_Test_Function implements Twig_TestInterface
+{
+ protected $function;
+
+ public function __construct($function)
+ {
+ $this->function = $function;
+ }
+
+ public function compile()
+ {
+ return $this->function;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Test/Method.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Test/Method.php
new file mode 100644
index 0000000..a3b3948
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Test/Method.php
@@ -0,0 +1,32 @@
+
+ */
+class Twig_Test_Method implements Twig_TestInterface
+{
+ protected $extension, $method;
+
+ public function __construct(Twig_ExtensionInterface $extension, $method)
+ {
+ $this->extension = $extension;
+ $this->method = $method;
+ }
+
+ public function compile()
+ {
+ return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Test/Node.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Test/Node.php
new file mode 100644
index 0000000..47a978e
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Test/Node.php
@@ -0,0 +1,35 @@
+
+ */
+class Twig_Test_Node implements Twig_TestInterface
+{
+ protected $class;
+
+ public function __construct($class)
+ {
+ $this->class = $class;
+ }
+
+ public function getClass()
+ {
+ return $this->class;
+ }
+
+ public function compile()
+ {
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TestInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TestInterface.php
new file mode 100644
index 0000000..c2ff725
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TestInterface.php
@@ -0,0 +1,26 @@
+
+ */
+interface Twig_TestInterface
+{
+ /**
+ * Compiles a test.
+ *
+ * @return string The PHP code for the test
+ */
+ function compile();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/Token.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Token.php
new file mode 100644
index 0000000..918bb91
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/Token.php
@@ -0,0 +1,219 @@
+
+ */
+class Twig_Token
+{
+ protected $value;
+ protected $type;
+ protected $lineno;
+
+ const EOF_TYPE = -1;
+ const TEXT_TYPE = 0;
+ const BLOCK_START_TYPE = 1;
+ const VAR_START_TYPE = 2;
+ const BLOCK_END_TYPE = 3;
+ const VAR_END_TYPE = 4;
+ const NAME_TYPE = 5;
+ const NUMBER_TYPE = 6;
+ const STRING_TYPE = 7;
+ const OPERATOR_TYPE = 8;
+ const PUNCTUATION_TYPE = 9;
+ const INTERPOLATION_START_TYPE = 10;
+ const INTERPOLATION_END_TYPE = 11;
+
+ /**
+ * Constructor.
+ *
+ * @param integer $type The type of the token
+ * @param string $value The token value
+ * @param integer $lineno The line position in the source
+ */
+ public function __construct($type, $value, $lineno)
+ {
+ $this->type = $type;
+ $this->value = $value;
+ $this->lineno = $lineno;
+ }
+
+ /**
+ * Returns a string representation of the token.
+ *
+ * @return string A string representation of the token
+ */
+ public function __toString()
+ {
+ return sprintf('%s(%s)', self::typeToString($this->type, true, $this->lineno), $this->value);
+ }
+
+ /**
+ * Tests the current token for a type and/or a value.
+ *
+ * Parameters may be:
+ * * just type
+ * * type and value (or array of possible values)
+ * * just value (or array of possible values) (NAME_TYPE is used as type)
+ *
+ * @param array|integer $type The type to test
+ * @param array|string|null $values The token value
+ *
+ * @return Boolean
+ */
+ public function test($type, $values = null)
+ {
+ if (null === $values && !is_int($type)) {
+ $values = $type;
+ $type = self::NAME_TYPE;
+ }
+
+ return ($this->type === $type) && (
+ null === $values ||
+ (is_array($values) && in_array($this->value, $values)) ||
+ $this->value == $values
+ );
+ }
+
+ /**
+ * Gets the line.
+ *
+ * @return integer The source line
+ */
+ public function getLine()
+ {
+ return $this->lineno;
+ }
+
+ /**
+ * Gets the token type.
+ *
+ * @return integer The token type
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Gets the token value.
+ *
+ * @return string The token value
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Returns the constant representation (internal) of a given type.
+ *
+ * @param integer $type The type as an integer
+ * @param Boolean $short Whether to return a short representation or not
+ * @param integer $line The code line
+ *
+ * @return string The string representation
+ */
+ static public function typeToString($type, $short = false, $line = -1)
+ {
+ switch ($type) {
+ case self::EOF_TYPE:
+ $name = 'EOF_TYPE';
+ break;
+ case self::TEXT_TYPE:
+ $name = 'TEXT_TYPE';
+ break;
+ case self::BLOCK_START_TYPE:
+ $name = 'BLOCK_START_TYPE';
+ break;
+ case self::VAR_START_TYPE:
+ $name = 'VAR_START_TYPE';
+ break;
+ case self::BLOCK_END_TYPE:
+ $name = 'BLOCK_END_TYPE';
+ break;
+ case self::VAR_END_TYPE:
+ $name = 'VAR_END_TYPE';
+ break;
+ case self::NAME_TYPE:
+ $name = 'NAME_TYPE';
+ break;
+ case self::NUMBER_TYPE:
+ $name = 'NUMBER_TYPE';
+ break;
+ case self::STRING_TYPE:
+ $name = 'STRING_TYPE';
+ break;
+ case self::OPERATOR_TYPE:
+ $name = 'OPERATOR_TYPE';
+ break;
+ case self::PUNCTUATION_TYPE:
+ $name = 'PUNCTUATION_TYPE';
+ break;
+ case self::INTERPOLATION_START_TYPE:
+ $name = 'INTERPOLATION_START_TYPE';
+ break;
+ case self::INTERPOLATION_END_TYPE:
+ $name = 'INTERPOLATION_END_TYPE';
+ break;
+ default:
+ throw new Twig_Error_Syntax(sprintf('Token of type "%s" does not exist.', $type), $line);
+ }
+
+ return $short ? $name : 'Twig_Token::'.$name;
+ }
+
+ /**
+ * Returns the english representation of a given type.
+ *
+ * @param integer $type The type as an integer
+ * @param integer $line The code line
+ *
+ * @return string The string representation
+ */
+ static public function typeToEnglish($type, $line = -1)
+ {
+ switch ($type) {
+ case self::EOF_TYPE:
+ return 'end of template';
+ case self::TEXT_TYPE:
+ return 'text';
+ case self::BLOCK_START_TYPE:
+ return 'begin of statement block';
+ case self::VAR_START_TYPE:
+ return 'begin of print statement';
+ case self::BLOCK_END_TYPE:
+ return 'end of statement block';
+ case self::VAR_END_TYPE:
+ return 'end of print statement';
+ case self::NAME_TYPE:
+ return 'name';
+ case self::NUMBER_TYPE:
+ return 'number';
+ case self::STRING_TYPE:
+ return 'string';
+ case self::OPERATOR_TYPE:
+ return 'operator';
+ case self::PUNCTUATION_TYPE:
+ return 'punctuation';
+ case self::INTERPOLATION_START_TYPE:
+ return 'begin of string interpolation';
+ case self::INTERPOLATION_END_TYPE:
+ return 'end of string interpolation';
+ default:
+ throw new Twig_Error_Syntax(sprintf('Token of type "%s" does not exist.', $type), $line);
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser.php
new file mode 100644
index 0000000..ab18bfa
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser.php
@@ -0,0 +1,34 @@
+
+ */
+abstract class Twig_TokenParser implements Twig_TokenParserInterface
+{
+ /**
+ * @var Twig_Parser
+ */
+ protected $parser;
+
+ /**
+ * Sets the parser associated with this token parser
+ *
+ * @param $parser A Twig_Parser instance
+ */
+ public function setParser(Twig_Parser $parser)
+ {
+ $this->parser = $parser;
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/AutoEscape.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/AutoEscape.php
new file mode 100644
index 0000000..0040845
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/AutoEscape.php
@@ -0,0 +1,88 @@
+
+ * {% autoescape true %}
+ * Everything will be automatically escaped in this block
+ * {% endautoescape %}
+ *
+ * {% autoescape false %}
+ * Everything will be outputed as is in this block
+ * {% endautoescape %}
+ *
+ * {% autoescape true js %}
+ * Everything will be automatically escaped in this block
+ * using the js escaping strategy
+ * {% endautoescape %}
+ *
+ */
+class Twig_TokenParser_AutoEscape extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+
+ if ($this->parser->getStream()->test(Twig_Token::BLOCK_END_TYPE)) {
+ $value = 'html';
+ } else {
+ $expr = $this->parser->getExpressionParser()->parseExpression();
+ if (!$expr instanceof Twig_Node_Expression_Constant) {
+ throw new Twig_Error_Syntax('An escaping strategy must be a string or a Boolean.', $lineno);
+ }
+ $value = $expr->getAttribute('value');
+
+ $compat = true === $value || false === $value;
+
+ if (true === $value) {
+ $value = 'html';
+ }
+
+ if ($compat && $this->parser->getStream()->test(Twig_Token::NAME_TYPE)) {
+ if (false === $value) {
+ throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $lineno);
+ }
+
+ $value = $this->parser->getStream()->next()->getValue();
+ }
+ }
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_AutoEscape($value, $body, $lineno, $this->getTag());
+ }
+
+ public function decideBlockEnd(Twig_Token $token)
+ {
+ return $token->test('endautoescape');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'autoescape';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Block.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Block.php
new file mode 100644
index 0000000..994078e
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Block.php
@@ -0,0 +1,83 @@
+
+ * {% block head %}
+ *
+ * {% block title %}{% endblock %} - My Webpage
+ * {% endblock %}
+ *
+ */
+class Twig_TokenParser_Block extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $stream = $this->parser->getStream();
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
+ if ($this->parser->hasBlock($name)) {
+ throw new Twig_Error_Syntax(sprintf("The block '$name' has already been defined line %d", $this->parser->getBlock($name)->getLine()), $lineno);
+ }
+ $this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno));
+ $this->parser->pushLocalScope();
+ $this->parser->pushBlockStack($name);
+
+ if ($stream->test(Twig_Token::BLOCK_END_TYPE)) {
+ $stream->next();
+
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ if ($stream->test(Twig_Token::NAME_TYPE)) {
+ $value = $stream->next()->getValue();
+
+ if ($value != $name) {
+ throw new Twig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)", $value), $lineno);
+ }
+ }
+ } else {
+ $body = new Twig_Node(array(
+ new Twig_Node_Print($this->parser->getExpressionParser()->parseExpression(), $lineno),
+ ));
+ }
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
+
+ $block->setNode('body', $body);
+ $this->parser->popBlockStack();
+ $this->parser->popLocalScope();
+
+ return new Twig_Node_BlockReference($name, $lineno, $this->getTag());
+ }
+
+ public function decideBlockEnd(Twig_Token $token)
+ {
+ return $token->test('endblock');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'block';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Do.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Do.php
new file mode 100644
index 0000000..593d1c6
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Do.php
@@ -0,0 +1,42 @@
+parser->getExpressionParser()->parseExpression();
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Do($expr, $token->getLine(), $this->getTag());
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'do';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Embed.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Embed.php
new file mode 100644
index 0000000..69cb5f3
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Embed.php
@@ -0,0 +1,66 @@
+parser->getStream();
+
+ $parent = $this->parser->getExpressionParser()->parseExpression();
+
+ list($variables, $only, $ignoreMissing) = $this->parseArguments();
+
+ // inject a fake parent to make the parent() function work
+ $stream->injectTokens(array(
+ new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', $token->getLine()),
+ new Twig_Token(Twig_Token::NAME_TYPE, 'extends', $token->getLine()),
+ new Twig_Token(Twig_Token::STRING_TYPE, '__parent__', $token->getLine()),
+ new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', $token->getLine()),
+ ));
+
+ $module = $this->parser->parse($stream, array($this, 'decideBlockEnd'), true);
+
+ // override the parent with the correct one
+ $module->setNode('parent', $parent);
+
+ $this->parser->embedTemplate($module);
+
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Embed($module->getAttribute('filename'), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
+ }
+
+ public function decideBlockEnd(Twig_Token $token)
+ {
+ return $token->test('endembed');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'embed';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Extends.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Extends.php
new file mode 100644
index 0000000..54f49ad
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Extends.php
@@ -0,0 +1,54 @@
+
+ * {% extends "base.html" %}
+ *
+ */
+class Twig_TokenParser_Extends extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ if (!$this->parser->isMainScope()) {
+ throw new Twig_Error_Syntax('Cannot extend from a block', $token->getLine());
+ }
+
+ if (null !== $this->parser->getParent()) {
+ throw new Twig_Error_Syntax('Multiple extends tags are forbidden', $token->getLine());
+ }
+ $this->parser->setParent($this->parser->getExpressionParser()->parseExpression());
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return null;
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'extends';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Filter.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Filter.php
new file mode 100644
index 0000000..2b97475
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Filter.php
@@ -0,0 +1,61 @@
+
+ * {% filter upper %}
+ * This text becomes uppercase
+ * {% endfilter %}
+ *
+ */
+class Twig_TokenParser_Filter extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $name = $this->parser->getVarName();
+ $ref = new Twig_Node_Expression_BlockReference(new Twig_Node_Expression_Constant($name, $token->getLine()), true, $token->getLine(), $this->getTag());
+
+ $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag());
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ $block = new Twig_Node_Block($name, $body, $token->getLine());
+ $this->parser->setBlock($name, $block);
+
+ return new Twig_Node_Print($filter, $token->getLine(), $this->getTag());
+ }
+
+ public function decideBlockEnd(Twig_Token $token)
+ {
+ return $token->test('endfilter');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'filter';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Flush.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Flush.php
new file mode 100644
index 0000000..4e15e78
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Flush.php
@@ -0,0 +1,42 @@
+parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Flush($token->getLine(), $this->getTag());
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'flush';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/For.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/For.php
new file mode 100644
index 0000000..2cbb580
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/For.php
@@ -0,0 +1,89 @@
+
+ *
+ * {% for user in users %}
+ *
{{ user.username|e }}
+ * {% endfor %}
+ *
+ *
+ */
+class Twig_TokenParser_For extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $targets = $this->parser->getExpressionParser()->parseAssignmentExpression();
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, 'in');
+ $seq = $this->parser->getExpressionParser()->parseExpression();
+
+ $ifexpr = null;
+ if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'if')) {
+ $this->parser->getStream()->next();
+ $ifexpr = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideForFork'));
+ if ($this->parser->getStream()->next()->getValue() == 'else') {
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $else = $this->parser->subparse(array($this, 'decideForEnd'), true);
+ } else {
+ $else = null;
+ }
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ if (count($targets) > 1) {
+ $keyTarget = $targets->getNode(0);
+ $keyTarget = new Twig_Node_Expression_AssignName($keyTarget->getAttribute('name'), $keyTarget->getLine());
+ $valueTarget = $targets->getNode(1);
+ $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine());
+ } else {
+ $keyTarget = new Twig_Node_Expression_AssignName('_key', $lineno);
+ $valueTarget = $targets->getNode(0);
+ $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine());
+ }
+
+ return new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag());
+ }
+
+ public function decideForFork(Twig_Token $token)
+ {
+ return $token->test(array('else', 'endfor'));
+ }
+
+ public function decideForEnd(Twig_Token $token)
+ {
+ return $token->test('endfor');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'for';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/From.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/From.php
new file mode 100644
index 0000000..4e20f5c
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/From.php
@@ -0,0 +1,74 @@
+
+ * {% from 'forms.html' import forms %}
+ *
+ */
+class Twig_TokenParser_From extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $macro = $this->parser->getExpressionParser()->parseExpression();
+ $stream = $this->parser->getStream();
+ $stream->expect('import');
+
+ $targets = array();
+ do {
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
+
+ $alias = $name;
+ if ($stream->test('as')) {
+ $stream->next();
+
+ $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
+ }
+
+ $targets[$name] = $alias;
+
+ if (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
+ break;
+ }
+
+ $stream->next();
+ } while (true);
+
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
+
+ $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag());
+
+ foreach($targets as $name => $alias) {
+ $this->parser->addImportedFunction($alias, 'get'.$name, $node->getNode('var'));
+ }
+
+ return $node;
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'from';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/If.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/If.php
new file mode 100644
index 0000000..1a694af
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/If.php
@@ -0,0 +1,93 @@
+
+ * {% if users %}
+ *
+ * {% for user in users %}
+ *
{{ user.username|e }}
+ * {% endfor %}
+ *
+ * {% endif %}
+ *
+ */
+class Twig_TokenParser_If extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $expr = $this->parser->getExpressionParser()->parseExpression();
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideIfFork'));
+ $tests = array($expr, $body);
+ $else = null;
+
+ $end = false;
+ while (!$end) {
+ switch ($this->parser->getStream()->next()->getValue()) {
+ case 'else':
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $else = $this->parser->subparse(array($this, 'decideIfEnd'));
+ break;
+
+ case 'elseif':
+ $expr = $this->parser->getExpressionParser()->parseExpression();
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideIfFork'));
+ $tests[] = $expr;
+ $tests[] = $body;
+ break;
+
+ case 'endif':
+ $end = true;
+ break;
+
+ default:
+ throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d)', $lineno), -1);
+ }
+ }
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_If(new Twig_Node($tests), $else, $lineno, $this->getTag());
+ }
+
+ public function decideIfFork(Twig_Token $token)
+ {
+ return $token->test(array('elseif', 'else', 'endif'));
+ }
+
+ public function decideIfEnd(Twig_Token $token)
+ {
+ return $token->test(array('endif'));
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'if';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Import.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Import.php
new file mode 100644
index 0000000..5219289
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Import.php
@@ -0,0 +1,47 @@
+
+ * {% import 'forms.html' as forms %}
+ *
+ */
+class Twig_TokenParser_Import extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $macro = $this->parser->getExpressionParser()->parseExpression();
+ $this->parser->getStream()->expect('as');
+ $var = new Twig_Node_Expression_AssignName($this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(), $token->getLine());
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Import($macro, $var, $token->getLine(), $this->getTag());
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'import';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Include.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Include.php
new file mode 100644
index 0000000..4a31786
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Include.php
@@ -0,0 +1,80 @@
+
+ * {% include 'header.html' %}
+ * Body
+ * {% include 'footer.html' %}
+ *
+ */
+class Twig_TokenParser_Include extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $expr = $this->parser->getExpressionParser()->parseExpression();
+
+ list($variables, $only, $ignoreMissing) = $this->parseArguments();
+
+ return new Twig_Node_Include($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
+ }
+
+ protected function parseArguments()
+ {
+ $stream = $this->parser->getStream();
+
+ $ignoreMissing = false;
+ if ($stream->test(Twig_Token::NAME_TYPE, 'ignore')) {
+ $stream->next();
+ $stream->expect(Twig_Token::NAME_TYPE, 'missing');
+
+ $ignoreMissing = true;
+ }
+
+ $variables = null;
+ if ($stream->test(Twig_Token::NAME_TYPE, 'with')) {
+ $stream->next();
+
+ $variables = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $only = false;
+ if ($stream->test(Twig_Token::NAME_TYPE, 'only')) {
+ $stream->next();
+
+ $only = true;
+ }
+
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return array($variables, $only, $ignoreMissing);
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'include';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Macro.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Macro.php
new file mode 100644
index 0000000..ffd5848
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Macro.php
@@ -0,0 +1,69 @@
+
+ * {% macro input(name, value, type, size) %}
+ *
+ * {% endmacro %}
+ *
+ */
+class Twig_TokenParser_Macro extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+
+ $arguments = $this->parser->getExpressionParser()->parseArguments();
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $this->parser->pushLocalScope();
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE)) {
+ $value = $this->parser->getStream()->next()->getValue();
+
+ if ($value != $name) {
+ throw new Twig_Error_Syntax(sprintf("Expected endmacro for macro '$name' (but %s given)", $value), $lineno);
+ }
+ }
+ $this->parser->popLocalScope();
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ $this->parser->setMacro($name, new Twig_Node_Macro($name, new Twig_Node_Body(array($body)), $arguments, $lineno, $this->getTag()));
+
+ return null;
+ }
+
+ public function decideBlockEnd(Twig_Token $token)
+ {
+ return $token->test('endmacro');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'macro';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Sandbox.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Sandbox.php
new file mode 100644
index 0000000..0277c70
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Sandbox.php
@@ -0,0 +1,55 @@
+
+ * {% sandbox %}
+ * {% include 'user.html' %}
+ * {% endsandbox %}
+ *
+ *
+ * @see http://www.twig-project.org/doc/api.html#sandbox-extension for details
+ */
+class Twig_TokenParser_Sandbox extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Sandbox($body, $token->getLine(), $this->getTag());
+ }
+
+ public function decideBlockEnd(Twig_Token $token)
+ {
+ return $token->test('endsandbox');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'sandbox';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Set.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Set.php
new file mode 100644
index 0000000..3b4479c
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Set.php
@@ -0,0 +1,84 @@
+
+ * {% set foo = 'foo' %}
+ *
+ * {% set foo = [1, 2] %}
+ *
+ * {% set foo = {'foo': 'bar'} %}
+ *
+ * {% set foo = 'foo' ~ 'bar' %}
+ *
+ * {% set foo, bar = 'foo', 'bar' %}
+ *
+ * {% set foo %}Some content{% endset %}
+ *
+ */
+class Twig_TokenParser_Set extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $stream = $this->parser->getStream();
+ $names = $this->parser->getExpressionParser()->parseAssignmentExpression();
+
+ $capture = false;
+ if ($stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
+ $stream->next();
+ $values = $this->parser->getExpressionParser()->parseMultitargetExpression();
+
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
+
+ if (count($names) !== count($values)) {
+ throw new Twig_Error_Syntax("When using set, you must have the same number of variables and assignements.", $lineno);
+ }
+ } else {
+ $capture = true;
+
+ if (count($names) > 1) {
+ throw new Twig_Error_Syntax("When using set with a block, you cannot have a multi-target.", $lineno);
+ }
+
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
+
+ $values = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
+ }
+
+ return new Twig_Node_Set($capture, $names, $values, $lineno, $this->getTag());
+ }
+
+ public function decideBlockEnd(Twig_Token $token)
+ {
+ return $token->test('endset');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'set';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Spaceless.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Spaceless.php
new file mode 100644
index 0000000..1e3fa8f
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Spaceless.php
@@ -0,0 +1,59 @@
+
+ * {% spaceless %}
+ *
+ * foo
+ *
+ * {% endspaceless %}
+ *
+ * {# output will be
foo
#}
+ *
+ */
+class Twig_TokenParser_Spaceless extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideSpacelessEnd'), true);
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Spaceless($body, $lineno, $this->getTag());
+ }
+
+ public function decideSpacelessEnd(Twig_Token $token)
+ {
+ return $token->test('endspaceless');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'spaceless';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Use.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Use.php
new file mode 100644
index 0000000..beafc80
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParser/Use.php
@@ -0,0 +1,85 @@
+
+ * {% extends "base.html" %}
+ *
+ * {% use "blocks.html" %}
+ *
+ * {% block title %}{% endblock %}
+ * {% block content %}{% endblock %}
+ *
+ *
+ * @see http://www.twig-project.org/doc/templates.html#horizontal-reuse for details.
+ */
+class Twig_TokenParser_Use extends Twig_TokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ public function parse(Twig_Token $token)
+ {
+ $template = $this->parser->getExpressionParser()->parseExpression();
+
+ if (!$template instanceof Twig_Node_Expression_Constant) {
+ throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $token->getLine());
+ }
+
+ $stream = $this->parser->getStream();
+
+ $targets = array();
+ if ($stream->test('with')) {
+ $stream->next();
+
+ do {
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
+
+ $alias = $name;
+ if ($stream->test('as')) {
+ $stream->next();
+
+ $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
+ }
+
+ $targets[$name] = new Twig_Node_Expression_Constant($alias, -1);
+
+ if (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
+ break;
+ }
+
+ $stream->next();
+ } while (true);
+ }
+
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
+
+ $this->parser->addTrait(new Twig_Node(array('template' => $template, 'targets' => new Twig_Node($targets))));
+
+ return null;
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'use';
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParserBroker.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParserBroker.php
new file mode 100644
index 0000000..b214e99
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParserBroker.php
@@ -0,0 +1,113 @@
+
+ */
+class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface
+{
+ protected $parser;
+ protected $parsers = array();
+ protected $brokers = array();
+
+ /**
+ * Constructor.
+ *
+ * @param array|Traversable $parsers A Traversable of Twig_TokenParserInterface instances
+ * @param array|Traversable $brokers A Traversable of Twig_TokenParserBrokerInterface instances
+ */
+ public function __construct($parsers = array(), $brokers = array())
+ {
+ foreach ($parsers as $parser) {
+ if (!$parser instanceof Twig_TokenParserInterface) {
+ throw new Twig_Error('$parsers must a an array of Twig_TokenParserInterface');
+ }
+ $this->parsers[$parser->getTag()] = $parser;
+ }
+ foreach ($brokers as $broker) {
+ if (!$broker instanceof Twig_TokenParserBrokerInterface) {
+ throw new Twig_Error('$brokers must a an array of Twig_TokenParserBrokerInterface');
+ }
+ $this->brokers[] = $broker;
+ }
+ }
+
+ /**
+ * Adds a TokenParser.
+ *
+ * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
+ */
+ public function addTokenParser(Twig_TokenParserInterface $parser)
+ {
+ $this->parsers[$parser->getTag()] = $parser;
+ }
+
+ /**
+ * Adds a TokenParserBroker.
+ *
+ * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance
+ */
+ public function addTokenParserBroker(Twig_TokenParserBroker $broker)
+ {
+ $this->brokers[] = $broker;
+ }
+
+ /**
+ * Gets a suitable TokenParser for a tag.
+ *
+ * First looks in parsers, then in brokers.
+ *
+ * @param string $tag A tag name
+ *
+ * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found
+ */
+ public function getTokenParser($tag)
+ {
+ if (isset($this->parsers[$tag])) {
+ return $this->parsers[$tag];
+ }
+ $broker = end($this->brokers);
+ while (false !== $broker) {
+ $parser = $broker->getTokenParser($tag);
+ if (null !== $parser) {
+ return $parser;
+ }
+ $broker = prev($this->brokers);
+ }
+
+ return null;
+ }
+
+ public function getParsers()
+ {
+ return $this->parsers;
+ }
+
+ public function getParser()
+ {
+ return $this->parser;
+ }
+
+ public function setParser(Twig_ParserInterface $parser)
+ {
+ $this->parser = $parser;
+ foreach ($this->parsers as $tokenParser) {
+ $tokenParser->setParser($parser);
+ }
+ foreach ($this->brokers as $broker) {
+ $broker->setParser($parser);
+ }
+ }
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParserBrokerInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParserBrokerInterface.php
new file mode 100644
index 0000000..3ce8ca2
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParserBrokerInterface.php
@@ -0,0 +1,45 @@
+
+ */
+interface Twig_TokenParserBrokerInterface
+{
+ /**
+ * Gets a TokenParser suitable for a tag.
+ *
+ * @param string $tag A tag name
+ *
+ * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found
+ */
+ function getTokenParser($tag);
+
+ /**
+ * Calls Twig_TokenParserInterface::setParser on all parsers the implementation knows of.
+ *
+ * @param Twig_ParserInterface $parser A Twig_ParserInterface interface
+ */
+ function setParser(Twig_ParserInterface $parser);
+
+ /**
+ * Gets the Twig_ParserInterface.
+ *
+ * @return null|Twig_ParserInterface A Twig_ParserInterface instance of null
+ */
+ function getParser();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParserInterface.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParserInterface.php
new file mode 100644
index 0000000..192c018
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenParserInterface.php
@@ -0,0 +1,42 @@
+
+ */
+interface Twig_TokenParserInterface
+{
+ /**
+ * Sets the parser associated with this token parser
+ *
+ * @param $parser A Twig_Parser instance
+ */
+ function setParser(Twig_Parser $parser);
+
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Twig_Token $token A Twig_Token instance
+ *
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
+ */
+ function parse(Twig_Token $token);
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ function getTag();
+}
diff --git a/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenStream.php b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenStream.php
new file mode 100644
index 0000000..5708091
--- /dev/null
+++ b/Core/Frameworks/Flake/Util/Twig/lib/Twig/TokenStream.php
@@ -0,0 +1,145 @@
+
+ */
+class Twig_TokenStream
+{
+ protected $tokens;
+ protected $current;
+ protected $filename;
+
+ /**
+ * Constructor.
+ *
+ * @param array $tokens An array of tokens
+ * @param string $filename The name of the filename which tokens are associated with
+ */
+ public function __construct(array $tokens, $filename = null)
+ {
+ $this->tokens = $tokens;
+ $this->current = 0;
+ $this->filename = $filename;
+ }
+
+ /**
+ * Returns a string representation of the token stream.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return implode("\n", $this->tokens);
+ }
+
+ public function injectTokens(array $tokens)
+ {
+ $this->tokens = array_merge(array_slice($this->tokens, 0, $this->current), $tokens, array_slice($this->tokens, $this->current));
+ }
+
+ /**
+ * Sets the pointer to the next token and returns the old one.
+ *
+ * @return Twig_Token
+ */
+ public function next()
+ {
+ if (!isset($this->tokens[++$this->current])) {
+ throw new Twig_Error_Syntax('Unexpected end of template', -1, $this->filename);
+ }
+
+ return $this->tokens[$this->current - 1];
+ }
+
+ /**
+ * Tests a token and returns it or throws a syntax error.
+ *
+ * @return Twig_Token
+ */
+ public function expect($type, $value = null, $message = null)
+ {
+ $token = $this->tokens[$this->current];
+ if (!$token->test($type, $value)) {
+ $line = $token->getLine();
+ throw new Twig_Error_Syntax(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)',
+ $message ? $message.'. ' : '',
+ Twig_Token::typeToEnglish($token->getType(), $line), $token->getValue(),
+ Twig_Token::typeToEnglish($type, $line), $value ? sprintf(' with value "%s"', $value) : ''),
+ $line,
+ $this->filename
+ );
+ }
+ $this->next();
+
+ return $token;
+ }
+
+ /**
+ * Looks at the next token.
+ *
+ * @param integer $number
+ *
+ * @return Twig_Token
+ */
+ public function look($number = 1)
+ {
+ if (!isset($this->tokens[$this->current + $number])) {
+ throw new Twig_Error_Syntax('Unexpected end of template', -1, $this->filename);
+ }
+
+ return $this->tokens[$this->current + $number];
+ }
+
+ /**
+ * Tests the current token
+ *
+ * @return bool
+ */
+ public function test($primary, $secondary = null)
+ {
+ return $this->tokens[$this->current]->test($primary, $secondary);
+ }
+
+ /**
+ * Checks if end of stream was reached
+ *
+ * @return bool
+ */
+ public function isEOF()
+ {
+ return $this->tokens[$this->current]->getType() === Twig_Token::EOF_TYPE;
+ }
+
+ /**
+ * Gets the current token
+ *
+ * @return Twig_Token
+ */
+ public function getCurrent()
+ {
+ return $this->tokens[$this->current];
+ }
+
+ /**
+ * Gets the filename associated with this stream
+ *
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->filename;
+ }
+}
diff --git a/Core/Frameworks/Flake/config.php b/Core/Frameworks/Flake/config.php
new file mode 100644
index 0000000..f229c05
--- /dev/null
+++ b/Core/Frameworks/Flake/config.php
@@ -0,0 +1,37 @@
+
+# All rights reserved
+#
+# http://flake.codr.fr
+#
+# This script is part of the Flake project. The Flake
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+if(defined("PROJECT_LOCALE")) {
+ define("FLAKE_LOCALE", PROJECT_LOCALE);
+} else {
+ define("FLAKE_LOCALE", "fr_FR.UTF-8");
+}
+
+if(defined("PROJECT_TIMEZONE")) {
+ define("FLAKE_TIMEZONE", PROJECT_TIMEZONE);
+} else {
+ define("FLAKE_TIMEZONE", "Europe/Paris");
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Formal b/Core/Frameworks/Formal
deleted file mode 160000
index 1663200..0000000
--- a/Core/Frameworks/Formal
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 1663200604730cd7bf27795f3d215b226e050431
diff --git a/Core/Frameworks/Formal/Core/ClassLoader.php b/Core/Frameworks/Formal/Core/ClassLoader.php
new file mode 100644
index 0000000..55bb79b
--- /dev/null
+++ b/Core/Frameworks/Formal/Core/ClassLoader.php
@@ -0,0 +1,65 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal\Core;
+
+class ClassLoader {
+
+ public static function register() {
+ return spl_autoload_register(array(__CLASS__, 'loadClass'));
+ }
+
+ public static function loadClass($sFullClassName) {
+
+ $aParts = explode("\\", $sFullClassName);
+ if(count($aParts) === 1) {
+ return;
+ }
+
+ if($aParts[0] !== "Formal") {
+ return;
+ }
+
+ // ejecting the Radical
+ $sRadical = array_shift($aParts);
+
+ if($sRadical === "Formal") {
+ $sRootPath = FORMAL_PATH_ROOT;
+ }
+
+ $sClassName = array_pop($aParts);
+ $sBasePath = $sRootPath . implode("/", $aParts) . "/";
+ $sClassPath = $sBasePath . $sClassName . ".php";
+
+ if(file_exists($sClassPath) && is_readable($sClassPath)) {
+ require_once($sClassPath);
+ } else {
+ echo '
";
+ die();
+ }
+ }
+}
diff --git a/Core/Frameworks/Formal/Core/Message.php b/Core/Frameworks/Formal/Core/Message.php
new file mode 100644
index 0000000..56e004a
--- /dev/null
+++ b/Core/Frameworks/Formal/Core/Message.php
@@ -0,0 +1,81 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal\Core;
+
+class Message {
+ private function __construct() {
+ }
+
+ public static function error($sMessage, $sTitle = "") {
+ if($sTitle !== "") {
+ $sTitle = '
+
+HTML;
+ return $sHtml;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Formal/Element.php b/Core/Frameworks/Formal/Element.php
new file mode 100644
index 0000000..e12fd40
--- /dev/null
+++ b/Core/Frameworks/Formal/Element.php
@@ -0,0 +1,100 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal;
+
+abstract class Element {
+
+ protected $aOptions = array(
+ "class" => "",
+ "inputclass" => "input-xlarge",
+ "readonly" => FALSE,
+ "validation" => "",
+ "error" => FALSE,
+ "placeholder" => "",
+ "help" => "",
+ "popover" => "",
+ "refreshonchange" => FALSE,
+ );
+
+ protected $sValue = "";
+
+ public function __construct($aOptions) {
+ $this->aOptions = array_merge($this->aOptions, $aOptions);
+ }
+
+ public function option($sName) {
+ if(array_key_exists($sName, $this->aOptions)) {
+ return $this->aOptions[$sName];
+ }
+
+ throw new \Exception("\Formal\Element->option(): Option '" . htmlspecialchars($sName) . "' not found.");
+ }
+
+ public function optionArray($sOptionName) {
+ $sOption = trim($this->option($sOptionName));
+ if($sOption !== "") {
+ $aOptions = explode(",", $sOption);
+ } else {
+ $aOptions = array();
+ }
+
+ reset($aOptions);
+ return $aOptions;
+ }
+
+ public function setOption($sOptionName, $sOptionValue) {
+ $this->aOptions[$sOptionName] = $sOptionValue;
+ }
+
+ public function value() {
+ return $this->sValue;
+ }
+
+ public function setValue($sValue) {
+ $this->sValue = $sValue;
+ }
+
+ public function __toString() {
+ return get_class($this) . "<" . $this->option("label") . ">";
+ }
+
+ public function renderWitness() {
+ return '';
+ }
+
+ public function posted() {
+ $aPost = \Flake\Util\Tools::POST("witness");
+ if(is_array($aPost)) {
+ $sProp = $this->option("prop");
+ return (array_key_exists($sProp, $aPost)) && (intval($aPost[$sProp]) === 1);
+ }
+
+ return FALSE;
+ }
+
+ public abstract function render();
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Formal/Element/Checkbox.php b/Core/Frameworks/Formal/Element/Checkbox.php
new file mode 100644
index 0000000..698932e
--- /dev/null
+++ b/Core/Frameworks/Formal/Element/Checkbox.php
@@ -0,0 +1,84 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal\Element;
+
+class Checkbox extends \Formal\Element {
+
+ public function setValue($sValue) {
+ # Boolean
+ $this->sValue = ((intval($sValue) === 1));
+ }
+
+ public function render() {
+ $disabled = "";
+ $inputclass = "";
+ $groupclass = "";
+ $onchange = "";
+ $helpblock = "";
+ $popover = "";
+
+ $value = $this->value();
+
+ $checked = ($value === TRUE ? " checked=\"checked\" " : "");
+ $label = $this->option("label");
+ $prop = $this->option("prop");
+
+ if($this->option("readonly") === TRUE) {
+ $inputclass .= " disabled";
+ $disabled = " disabled";
+ }
+
+ if($this->option("error") === TRUE) {
+ $groupclass .= " error";
+ }
+
+ if(($sHelp = trim($this->option("help"))) !== "") {
+ $helpblock = "
+
+HTML;
+ return $sHtml . $this->renderWitness();
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Formal/Element/Listbox.php b/Core/Frameworks/Formal/Element/Listbox.php
new file mode 100644
index 0000000..27cf2c6
--- /dev/null
+++ b/Core/Frameworks/Formal/Element/Listbox.php
@@ -0,0 +1,104 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal\Element;
+
+class Listbox extends \Formal\Element {
+ public function render() {
+
+ $disabled = "";
+ $inputclass = "";
+ $groupclass = "";
+ $placeholder = "";
+
+ $value = $this->value();
+ $label = $this->option("label");
+ $prop = $this->option("prop");
+ $helpblock = "";
+ $popover = "";
+
+ if($this->option("readonly") === TRUE) {
+ $inputclass .= " disabled";
+ $disabled = " disabled";
+ }
+
+ if($this->option("error") === TRUE) {
+ $groupclass .= " error";
+ }
+
+ $aOptions = $this->option("options");
+ if(!is_array($aOptions)) {
+ throw new \Exception("\Formal\Element\Listbox->render(): 'options' has to be an array.");
+ }
+
+ if(($sHelp = trim($this->option("help"))) !== "") {
+ $helpblock = "
+
+HTML;
+ return $sHtml . $this->renderWitness();
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Formal/Element/Password.php b/Core/Frameworks/Formal/Element/Password.php
new file mode 100644
index 0000000..1c22236
--- /dev/null
+++ b/Core/Frameworks/Formal/Element/Password.php
@@ -0,0 +1,34 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal\Element;
+
+class Password extends \Formal\Element\Text {
+
+ protected function inputtype() {
+ return "password";
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Formal/Element/Text.php b/Core/Frameworks/Formal/Element/Text.php
new file mode 100644
index 0000000..2c1b19c
--- /dev/null
+++ b/Core/Frameworks/Formal/Element/Text.php
@@ -0,0 +1,101 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal\Element;
+
+class Text extends \Formal\Element {
+
+ protected function inputtype() {
+ return "text";
+ }
+
+ public function render() {
+ $disabled = "";
+ $inputclass = "";
+ $groupclass = "";
+ $placeholder = "";
+
+ $value = $this->value();
+ $label = $this->option("label");
+ $prop = $this->option("prop");
+ $placeholder = "";
+ $helpblock = "";
+ $popover = "";
+
+ if($this->option("readonly") === TRUE) {
+ $inputclass .= " disabled";
+ $disabled = " disabled";
+ }
+
+ if($this->option("error") === TRUE) {
+ $groupclass .= " error";
+ }
+
+ if(trim($this->option("class")) !== "") {
+ $groupclass .= " " . $this->option("class");
+ }
+
+ if(trim($this->option("inputclass")) !== "") {
+ $inputclass = $this->option("inputclass");
+ }
+
+ if(($sPlaceHolder = trim($this->option("placeholder"))) !== "") {
+ $placeholder = " placeholder=\"" . htmlspecialchars($sPlaceHolder) . "\" ";
+ }
+
+ $clientvalue = htmlspecialchars($value);
+
+ $sInputType = $this->inputtype();
+
+ if(($sHelp = trim($this->option("help"))) !== "") {
+ $helpblock = "
+
+HTML;
+ return $sHtml . $this->renderWitness();
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Formal/Form.php b/Core/Frameworks/Formal/Form.php
new file mode 100644
index 0000000..d438ac5
--- /dev/null
+++ b/Core/Frameworks/Formal/Form.php
@@ -0,0 +1,420 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal;
+
+class Form {
+
+ protected $sModelClass = "";
+ protected $aOptions = array(
+ "action" => "",
+ "close" => TRUE,
+ "closeurl" => "",
+ "hook.validation" => FALSE,
+ "hook.morphology" => FALSE,
+ );
+ protected $oModelInstance = null;
+ protected $oElements = null;
+ protected $aErrors = array();
+ protected $bPersisted = null; # TRUE when form has persisted; available only after execute
+
+ protected $sDisplayTitle = ""; # Displayed form title; generated in setModelInstance()
+ protected $sDisplayMessage = ""; # Displayed confirm message; generated in execute()
+
+ protected $oMorpho = null;
+
+ public function __construct($sModelClass, $aOptions = array()) {
+ $this->sModelClass = $sModelClass;
+ $this->aOptions = array_merge($this->aOptions, $aOptions);
+ $this->oElements = new \Flake\Core\CollectionTyped("\Formal\Element");
+ }
+
+ public function option($sName) {
+ if(array_key_exists($sName, $this->aOptions)) {
+ return $this->aOptions[$sName];
+ }
+
+ throw new \Exception("\Formal\Form->option(): Option '" . htmlspecialchars($sName) . "' not found.");
+ }
+
+ public function setOption($sName, $sValue) {
+ $this->aOptions[$sName] = $sValue;
+ return $this;
+ }
+
+ public function options() {
+ $aOptions = $this->aOptions;
+ return $aOptions;
+ }
+
+ public function getMorpho() {
+ if(!is_null($this->oMorpho)) {
+ return $this->oMorpho;
+ }
+
+ $this->oMorpho = $this->modelInstance()->formMorphologyForThisModelInstance();
+
+ # Calling validation hook if defined
+ if(($aHook = $this->option("hook.morphology")) !== FALSE) {
+ call_user_func($aHook, $this, $this->oMorpho);
+ }
+
+ return $this->oMorpho;
+ }
+
+ public function setModelInstance($oModelInstance) {
+ if(!\Flake\Util\Tools::is_a($oModelInstance, $this->sModelClass)) {
+ throw new \Exception("\Formal\Core->setModelInstance(): Given instance is not of class '" . $this->sModelClass . "'");
+ }
+
+ $this->oModelInstance = $oModelInstance;
+
+ $this->oElements->reset();
+ foreach($this->oElements as $oElement) {
+ $oElement->setValue(
+ $this->modelInstance()->get(
+ $oElement->option("prop")
+ )
+ );
+ }
+
+ # Displayed form title is generated depending on modelInstance floatingness
+
+ if($this->floatingModelInstance()) {
+ $this->sDisplayTitle = "Creating new" . $this->modelInstance()->humanName() . "";
+ } else {
+ # This is changed if form is persisted, after persistance, to reflect possible change in model instance label
+ $this->sDisplayTitle = "Editing " . $this->modelInstance()->humanName() . "" . $this->modelInstance()->label() . "";
+ }
+
+ return $this;
+ }
+
+ public function modelInstance() {
+ return $this->oModelInstance;
+ }
+
+ public function floatingModelInstance() {
+ return $this->modelInstance()->floating();
+ }
+
+ public function execute() {
+ # Obtaining morphology from model object
+ $oMorpho = $this->getMorpho();
+
+ $this->aErrors = array();
+ $oMorpho->elements()->reset();
+ foreach($oMorpho->elements() as $oElement) {
+ # If element is readonly, skip process
+ if($oElement->option("readonly")) {
+ continue;
+ }
+
+ $sPropName = $oElement->option("prop");
+
+ # posted value is fetched, then passes to element before persistance
+ if($oElement->posted()) {
+
+ $sPostValue = $this->postValue($sPropName);
+ $oElement->setValue($sPostValue);
+
+ $sValue = $oElement->value();
+
+ $this->modelInstance()->set(
+ $sPropName,
+ $sValue
+ );
+ } else {
+ $oElement->setValue(
+ $this->modelInstance()->get(
+ $sPropName
+ )
+ );
+ }
+ }
+
+ $oMorpho->elements()->reset();
+ foreach($oMorpho->elements() as $oElement) {
+ $aValidation = $oElement->optionArray("validation");
+ if(empty($aValidation)) {
+ continue;
+ }
+
+ $sValue = $oElement->value();
+
+ foreach($aValidation as $sValidation) {
+
+ # If element is readonly, skip process
+ if($oElement->option("readonly")) {
+ continue;
+ }
+
+ $sParam = FALSE;
+ if(strpos($sValidation, ":") !== FALSE) {
+ $sValidation = strtok($sValidation, ":");
+ $sParam = strtok(":");
+ }
+
+ $sMethod = "validate" . ucfirst(strtolower($sValidation));
+ if(!method_exists($this, $sMethod)) {
+ throw new \Exception("\Formal\Form::execute(): no validation method for '" . htmlspecialchars($sValidation) . "'");
+ }
+
+ if($sParam === FALSE) {
+ $mValid = $this->$sMethod($sValue, $oMorpho, $oElement);
+ } else {
+ $mValid = $this->$sMethod($sValue, $oMorpho, $oElement, $sParam);
+ }
+
+ if($mValid !== TRUE) {
+ $this->declareError($oElement, $mValid);
+ break; # one error per element per submit
+ }
+ }
+ }
+
+ # Calling validation hook if defined
+ if(($aHook = $this->option("hook.validation")) !== FALSE) {
+ call_user_func($aHook, $this, $oMorpho);
+ }
+
+ if(!$this->refreshed() && empty($this->aErrors)) {
+
+ # Model object is persisted
+ # Last chance to generate a confirm message corresponding to what *was* submitted ("Creating", instead of "Editing")
+
+ if($this->floatingModelInstance()) {
+ $this->sDisplayMessage = \Formal\Core\Message::notice(
+ $this->modelInstance()->humanName() . " " . $this->modelInstance()->label() . " has been created.",
+ "",
+ FALSE
+ );
+ $bWasFloating = TRUE;
+ } else {
+ $bWasFloating = FALSE;
+ $this->sDisplayMessage = \Formal\Core\Message::notice(
+ "Changes on " . $this->modelInstance()->label() . " have been saved.",
+ FALSE, # No title
+ FALSE # No close button
+ );
+ }
+
+ $this->modelInstance()->persist();
+ if($bWasFloating === FALSE) {
+ # Title is generated now, as submitted data might have changed the model instance label
+ $this->sDisplayTitle = "Editing " . $this->modelInstance()->humanName() . "" . $this->modelInstance()->label() . "";
+ }
+ $this->bPersisted = TRUE;
+ } else {
+ $this->bPersisted = FALSE;
+ }
+ }
+
+ # public, as it may be called from a hook
+ public function declareError(\Formal\Element $oElement, $sMessage = "") {
+ $this->aErrors[] = array(
+ "element" => $oElement,
+ "message" => $sMessage,
+ );
+
+ $oElement->setOption("error", TRUE);
+ }
+
+ public function persisted() {
+ if($this->submitted()) {
+ if(is_null($this->bPersisted)) {
+ throw new \Exception("\Formal\Form->persisted(): information is not available yet. This method may only be called after execute()");
+ }
+
+ return $this->bPersisted;
+ }
+
+ return FALSE;
+ }
+
+ public function validateRequired($sValue, \Formal\Form\Morphology $oMorpho, \Formal\Element $oElement) {
+ if(trim($sValue) !== "") {
+ return TRUE;
+ }
+
+ return "" . $oElement->option("label") . " is required.";
+ }
+
+ public function validateEmail($sValue, \Formal\Form\Morphology $oMorpho, \Formal\Element $oElement) {
+ if(\Flake\Util\Tools::validEmail($sValue)) {
+ return TRUE;
+ }
+
+ return "" . $oElement->option("label") . " should be an email.";
+ }
+
+ public function validateSameas($sValue, \Formal\Form\Morphology $oMorpho, \Formal\Element $oElement, $sReferencePropName) {
+ $sReferenceValue = $oMorpho->element($sReferencePropName)->value();
+ if($sValue === $sReferenceValue) {
+ return TRUE;
+ }
+
+ return "" . $oElement->option("label") . " does not match " . $oMorpho->element($sReferencePropName)->option("label") . ".";
+ }
+
+ public function validateUnique($sValue, \Formal\Form\Morphology $oMorpho, \Formal\Element $oElement) {
+ $oModelInstance = $this->modelInstance();
+
+ $oRequest = $oModelInstance->getBaseRequester()->addClauseEquals(
+ $oElement->option("prop"),
+ $sValue
+ );
+
+ if(!$oModelInstance->floating()) {
+ # checking id only if model instance is not floating
+ $oRequest->addClauseNotEquals(
+ $oModelInstance::PRIMARYKEY,
+ $oModelInstance->get(
+ $oModelInstance::PRIMARYKEY
+ )
+ );
+ }
+
+ $oColl = $oRequest->execute();
+
+ if($oColl->count() > 0) {
+ return "" . $oElement->option("label") . " has to be unique. Given value is not available.";
+ }
+
+ return TRUE;
+ }
+
+ public function validateTokenid($sValue, \Formal\Form\Morphology $oMorpho, \Formal\Element $oElement) {
+ if(!preg_match("/^[a-z0-9\-]+$/", $sValue)) {
+ return "" . $oElement->option("label") . " is not valid. Allowed characters are digits, lowercase letters and the dash symbol '-'.";
+ }
+
+ return TRUE;
+ }
+
+ public function postValue($sPropName) {
+ $aData = \Flake\Util\Tools::POST("data");
+
+ if(is_array($aData) && array_key_exists($sPropName, $aData)) {
+ return $aData[$sPropName];
+ }
+
+ return "";
+ }
+
+ public function render() {
+ $aHtml = array();
+
+ $oMorpho = $this->getMorpho();
+
+ $oMorpho->elements()->reset();
+ foreach($oMorpho->elements() as $oElement) {
+
+ # Setting current prop value for element
+ # Set on empty (just created) FormMorphology
+ # And obtained from Model instance
+
+ $oElement->setValue(
+ $this->modelInstance()->get(
+ $oElement->option("prop")
+ )
+ );
+
+ $aHtml[] = $oElement->render();
+ }
+
+ $elements = implode("\n", $aHtml);
+ $sModelClass = $this->sModelClass;
+
+ ######################################################
+ # Displaying messages
+ ######################################################
+
+ if($this->submitted()) {
+
+ # There were errors detected during execute()
+ # Error messages are displayed
+
+ if(!empty($this->aErrors)) {
+ $this->sDisplayMessage = "";
+ $aMessages = array();
+ reset($this->aErrors);
+ foreach($this->aErrors as $aError) {
+ if(trim($aError["message"]) === "") {
+ continue;
+ }
+
+ $aMessages[] = $aError["message"];
+ }
+
+ $this->sDisplayMessage = \Formal\Core\Message::error(
+ implode(" ", $aMessages),
+ "Validation error"
+ );
+ }
+ }
+
+ $sSubmittedFlagName = $this->submitSignatureName();
+ if($this->option("close") === TRUE) {
+ $sCloseUrl = $this->option("closeurl");
+ $sCloseButton = 'Close';
+ } else {
+ $sCloseButton = "";
+ }
+
+ $sActionUrl = $this->option("action");
+
+ $sHtml =<<
+
+
+
+
+HTML;
+
+ return $sHtml;
+ }
+
+ protected function submitSignatureName() {
+ return str_replace('\\', '_', $this->sModelClass . "::submitted");
+ }
+
+ public function submitted() {
+ return intval(\Flake\Util\Tools::POST($this->submitSignatureName())) === 1;
+ }
+
+ public function refreshed() {
+ return intval(\Flake\Util\Tools::POST("refreshed")) === 1;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Formal/Form/Morphology.php b/Core/Frameworks/Formal/Form/Morphology.php
new file mode 100644
index 0000000..b16dc06
--- /dev/null
+++ b/Core/Frameworks/Formal/Form/Morphology.php
@@ -0,0 +1,75 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal\Form;
+
+class Morphology {
+
+ protected $oElements = null;
+
+ public function __construct() {
+ $this->oElements = new \Flake\Core\CollectionTyped("\Formal\Element");
+ }
+
+ public function add(\Formal\Element $oElement) {
+ $this->oElements->push($oElement);
+ }
+
+ protected function keyForPropName($sPropName) {
+ $aKeys = $this->oElements->keys();
+ reset($aKeys);
+ foreach($aKeys as $sKey) {
+ $oElement = $this->oElements->getForKey($sKey);
+
+ if($oElement->option("prop") === $sPropName) {
+ return $sKey;
+ }
+ }
+
+ return FALSE;
+ }
+
+ public function &element($sPropName) {
+ if(($sKey = $this->keyForPropName($sPropName)) === FALSE) {
+ throw new \Exception("\Formal\Form\Morphology->element(): Element prop='" . $sPropName . "' not found");
+ }
+
+ $oElement = $this->oElements->getForKey($sKey);
+ return $oElement;
+ }
+
+ public function remove($sPropName) {
+ if(($sKey = $this->keyForPropName($sPropName)) === FALSE) {
+ throw new \Exception("\Formal\Form\Morphology->element(): Element prop='" . $sPropName . "' not found");
+ }
+
+ $this->oElements->remove($sKey);
+ }
+
+ public function elements() {
+ return $this->oElements;
+ }
+}
\ No newline at end of file
diff --git a/Core/Frameworks/Formal/Framework.php b/Core/Frameworks/Formal/Framework.php
new file mode 100644
index 0000000..fded59b
--- /dev/null
+++ b/Core/Frameworks/Formal/Framework.php
@@ -0,0 +1,37 @@
+
+# All rights reserved
+#
+# http://formal.codr.fr
+#
+# This script is part of the Formal project. The Formal
+# project is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+# This script is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# This copyright notice MUST APPEAR in all copies of the script!
+#################################################################
+
+namespace Formal;
+
+class Framework extends \Flake\Core\Framework {
+
+ public static function bootstrap() {
+ define("FORMAL_PATH_ROOT", PROJECT_PATH_ROOT . "Core/Frameworks/Formal/");
+
+ require_once(FORMAL_PATH_ROOT . 'Core/ClassLoader.php');
+ \Formal\Core\ClassLoader::register();
+ }
+}
\ No newline at end of file