Source for file Compiler.php
Documentation is available at Compiler.php
include dirname(__FILE__
) .
'/Compilation/Exception.php';
* default dwoo compiler class, compiles dwoo templates into php
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from the use of this software.
* @author Jordi Boggiano <j.boggiano@seld.be>
* @copyright Copyright (c) 2008, Jordi Boggiano
* @license http://dwoo.org/LICENSE Modified BSD License
* constant that represents a php opening tag
* use it in case it needs to be adjusted
const PHP_OPEN =
"<?php ";
* constant that represents a php closing tag
* use it in case it needs to be adjusted
* boolean flag to enable or disable debugging output
* left script delimiter with escaped regex meta characters
* right script delimiter with escaped regex meta characters
* defines whether the nested comments should be parsed as nested or not
* defaults to false (classic block comment parsing as in all languages)
* defines whether opening and closing tags can contain spaces before valid data or not
* turn to true if you want to be sloppy with the syntax, but when set to false it allows
* to skip javascript and css tags as long as they are in the form "{ something", which is
* nice. default is false.
* defines whether the compiler will automatically html-escape variables or not
* @var Dwoo_Security_Policy
* stores the custom plugins registered with this compiler
* stores the template plugins registered with this compiler
* stores the pre- and post-processors callbacks
protected $processors =
array('pre'=>
array(), 'post'=>
array());
* stores a list of plugins that are used in the currently compiled
* template, and that are not compilable. these plugins will be loaded
* during the template's runtime if required.
* it is a 1D array formatted as key:pluginName value:pluginType
* stores the template undergoing compilation
* stores the current pointer position inside the template
* stores the current line count inside the template for debugging purposes
* stores the current template source while compiling it
* stores the data within which the scope moves
* variable scope of the compiler, set to null if
* it can not be resolved to a static string (i.e. if some
* plugin defines a new scope based on a variable array key)
* variable scope tree, that allows to rebuild the current
* scope if required, i.e. when going to a parent level
* block plugins stack, accessible through some methods
* current block at the top of the block plugins stack,
* accessible through getCurrentBlock
* current dwoo object that uses this compiler, or null
* holds an instance of this class, used by getInstance when you don't
* provide a custom compiler in order to save resources
protected static $instance;
* sets the delimiters to use in the templates
* delimiters can be multi-character strings but should not be one of those as they will
* make it very hard to work with templates or might even break the compiler entirely : "\", "$", "|", ":" and finally "#" only if you intend to use config-vars with the #var# syntax.
* @param string $left left delimiter
* @param string $right right delimiter
* returns the left and right template delimiters
* @return array containing the left and the right delimiters
return array($this->ld, $this->rd);
* sets the way to handle nested comments, if set to true
* {* foo {* some other *} comment *} will be stripped correctly.
* if false it will remove {* foo {* some other *} and leave "comment *}" alone,
* this is the default behavior
* @param bool $allow allow nested comments or not, defaults to true (but the default internal value is false)
* returns the nested comments handling setting
* @see setNestedCommentsHandling
* @return bool true if nested comments are allowed
* sets the tag openings handling strictness, if set to true, template tags can
* contain spaces before the first function/string/variable such as { $foo} is valid.
* if set to false (default setting), { $foo} is invalid but that is however a good thing
* as it allows css (i.e. #foo { color:red; }) to be parsed silently without triggering
* an error, same goes for javascript.
* @param bool $allow true to allow loose handling, false to restore default setting
* returns the tag openings handling strictness setting
* @see setLooseOpeningHandling
* @return bool true if loose tags are allowed
* changes the auto escape setting
* if enabled, the compiler will automatically html-escape variables,
* unless they are passed through the safe function such as {$var|safe}
* default setting is disabled/false
* @param bool $enabled set to true to enable, false to disable
* returns the auto escape setting
* default setting is disabled/false
* adds a preprocessor to the compiler, it will be called
* before the template is compiled
* @param mixed $callback either a valid callback to the preprocessor or a simple name if the autoload is set to true
* @param bool $autoload if set to true, the preprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
$class =
'Dwoo_Processor_'.
$name;
$callback =
array(new $class($this), 'process');
$callback =
array('autoload'=>
true, 'class'=>
$class, 'name'=>
$name);
* removes a preprocessor from the compiler
* @param mixed $callback either a valid callback to the preprocessor or a simple name if it was autoloaded
$class =
'Dwoo_Processor_' .
str_replace('Dwoo_Processor_', '', $callback);
foreach ($this->processors['pre'] as $index=>
$proc) {
if (is_array($proc) &&
($proc[0] instanceof
$class) ||
(isset
($proc['class']) &&
$proc['class'] ==
$class)) {
* adds a postprocessor to the compiler, it will be called
* before the template is compiled
* @param mixed $callback either a valid callback to the postprocessor or a simple name if the autoload is set to true
* @param bool $autoload if set to true, the postprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
$class =
'Dwoo_Processor_'.
$name;
$callback =
array(new $class($this), 'process');
$callback =
array('autoload'=>
true, 'class'=>
$class, 'name'=>
$name);
* removes a postprocessor from the compiler
* @param mixed $callback either a valid callback to the postprocessor or a simple name if it was autoloaded
$class =
'Dwoo_Processor_' .
str_replace('Dwoo_Processor_', '', $callback);
foreach ($this->processors['post'] as $index=>
$proc) {
if (is_array($proc) &&
($proc[0] instanceof
$class) ||
(isset
($proc['class']) &&
$proc['class'] ==
$class)) {
* internal function to autoload processors at runtime if required
* @param string $class the class/function name
* @param string $name the plugin name (without Dwoo_Plugin_ prefix)
throw
new Dwoo_Exception('Processor '.
$name.
' could not be found in your plugin directories, please ensure it is in a file named '.
$name.
'.php in the plugin directory');
return array(new $class($this), 'process');
throw
new Dwoo_Exception('Wrong processor name, when using autoload the processor must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Processor_name"');
* adds a template plugin, this is reserved for use by the {function} plugin
* this is required because the template functions are not declared yet
* during compilation, so we must have a way of validating their argument
* signature without using the reflection api
* @param string $name function name
* @param array $params parameter array to help validate the function call
* @param string $uuid unique id of the function
* @param string $body function php code
$this->templatePlugins[$name] =
array('params'=>
$params, 'body' =>
$body, 'uuid' =>
$uuid);
* returns all the parsed sub-templates
* @return array the parsed sub-templates
public function getTemplatePlugins()
* marks a template plugin as being called, which means its source must be included in the compiled template
* @param string $name function name
* adds the custom plugins loaded into Dwoo to the compiler so it can load them
* @param array $customPlugins an array of custom plugins
$this->customPlugins =
$customPlugins;
* sets the security policy object to enforce some php security settings
* use this if untrusted persons can modify templates,
* set it on the Dwoo object as it will be passed onto the compiler automatically
* @param Dwoo_Security_Policy $policy the security policy object
public function setSecurityPolicy(Dwoo_Security_Policy $policy =
null)
* returns the current security policy object or null by default
* @return Dwoo_Security_Policy|nullthe security policy object if any
* sets the pointer position
* @param int $position the new pointer position
* @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
public function setPointer($position, $isOffset =
false)
* returns the current pointer position, only available during compilation of a template
* @param int $number the new line number
* @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
public function setLine($number, $isOffset =
false)
* returns the current line number, only available during compilation of a template
* returns the dwoo object that initiated this template compilation, only available during compilation of a template
* overwrites the template that is being compiled
* @param string $newSource the template source that must replace the current one
* @param bool $fromPointer if set to true, only the source from the current pointer position is replaced
* @return string the template or partial template
if ($fromPointer ===
true) {
* returns the template that is being compiled
* @param mixed $fromPointer if set to true, only the source from the current pointer
* position is returned, if a number is given it overrides the current pointer
* @return string the template or partial template
if ($fromPointer ===
true) {
* resets the compilation pointer, effectively restarting the compilation process
* this is useful if a plugin modifies the template source since it might need to be recompiled
* compiles the provided string down to php code
* @param string $tpl the template to compile
* @return string a compiled php string
public function compile(Dwoo $dwoo, Dwoo_ITemplate $template)
$tpl =
$template->getSource();
// if pointer is at the beginning, reset everything, that allows a plugin to externally reset the compiler if everything must be reparsed
$compiled =
$this->addBlock('topLevelBlock', array(), 0);
$this->stack[0]['buffer'] =
'';
if ($this->debug) echo
'COMPILER INIT<br />';
if (is_array($preProc) && isset
($preProc['autoload'])) {
$preProc =
$this->loadProcessor($preProc['class'], $preProc['name']);
// show template source if debug
// strips php tags if required by the security policy
$search =
array('{<\?php.*?\?>}');
$search =
array('{<\?.*?\?>}', '{<%.*?%>}');
} elseif (substr($tpl, $pos-
1, 1) ===
'\\' &&
substr($tpl, $pos-
2, 1) !==
'\\') {
$endpos =
$litClose[0][1];
$ptr =
$endpos+
strlen($litClose[0][0]);
if (substr($tpl, $pos-
2, 1) ===
'\\' &&
substr($tpl, $pos-
1, 1) ===
'\\') {
while (substr($tpl, $pos, 1) ===
' ') {
if (substr($tpl, $pos, 1) ===
' ' ||
substr($tpl, $pos, 1) ===
"\r" ||
substr($tpl, $pos, 1) ===
"\n" ||
substr($tpl, $pos, 1) ===
"\t") {
// check that there is an end tag present
if (strpos($tpl, $this->rd, $pos) ===
false) {
$parsed =
$this->parse($tpl, $subptr, null, false, 'root', $subptr);
// reload loop if the compiler was reset
// adds additional line breaks between php closing and opening tags because the php parser removes those if there is just a single line break
$this->push($m[1].
$m[1], 1);
$this->push($m[1].
"\n", 2);
if ($this->debug) echo
'PROCESSING POSTPROCESSORS<br>';
foreach ($this->processors['post'] as $postProc) {
if (is_array($postProc) && isset
($postProc['autoload'])) {
$postProc =
$this->loadProcessor($postProc['class'], $postProc['name']);
// build plugin preloader
if ($type & Dwoo::CUSTOM_PLUGIN) {
$output .=
"if (class_exists('Dwoo_Plugin_$plugin', false)===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
$output .=
"if (function_exists('Dwoo_Plugin_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
case Dwoo::SMARTY_MODIFIER:
$output .=
"if (function_exists('smarty_modifier_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
case Dwoo::SMARTY_FUNCTION:
$output .=
"if (function_exists('smarty_function_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
$output .=
"if (function_exists('smarty_block_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
$output .=
$this->getDwoo()->getPluginProxy()->getPreloader($plugin);
if (isset
($attr['called']) &&
$attr['called'] ===
true &&
!isset
($attr['checked'])) {
if (isset
($function['called']) &&
$function['called'] ===
true) {
$output .=
$function['body'].
PHP_EOL;
$output .=
$compiled.
"\n?>";
$output =
str_replace(self::PHP_CLOSE .
self::PHP_OPEN, "\n", $output);
// handle <?xml tag at the beginning
$output =
preg_replace('#(/\* template body \*/ \?>\s*)<\?xml#is', '$1<?php echo \'<?xml\'; ?>', $output);
foreach ($lines as $i=>
$line) {
echo
($i+
1).
'. '.
$line.
"\r\n";
if ($this->debug) echo
'<hr></pre></pre>';
* checks what sub-templates are used in every sub-template so that we're sure they are all compiled
* @param string $function the sub-template name
if ($func !==
$function &&
!isset
($attr['called']) &&
strpos($body, 'Dwoo_Plugin_'.
$func) !==
false) {
* adds compiled content to the current block
* @param string $content the content to push
* @param int $lineCount newlines count in content, optional
public function push($content, $lineCount =
null)
if ($lineCount ===
null) {
// buffer is not initialized yet (the block has just been created)
if (!isset
($this->curBlock['buffer'])) {
throw
new Dwoo_Compilation_Exception($this, 'The template has been closed too early, you probably have an extra block-closing tag somewhere');
// append current content to current block's buffer
$this->curBlock['buffer'] .= (string)
$content;
$this->line +=
$lineCount;
* set to null if the scope becomes "unstable" (i.e. too variable or unknown) so that
* variables are compiled in a more evaluative way than just $this->scope['key']
* @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
* @param bool $absolute if true, the scope is set from the top level scope and not from the current scope
* @return array the current scope tree
public function setScope($scope, $absolute =
false)
if ($bit ===
'_parent' ||
$bit ===
'_') {
} elseif ($bit ===
'_root' ||
$bit ===
'__') {
} elseif (isset
($this->scope[$bit])) {
$this->scope[$bit] =
array();
* adds a block to the top of the block stack
* @param string $type block type (name)
* @param array $params the parameters array
* @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
* @return string the preProcessing() method's output
public function addBlock($type, array $params, $paramtype)
$class =
'Dwoo_Plugin_'.
$type;
if (class_exists($class, false) ===
false) {
$params =
$this->mapParams($params, array($class, 'init'), $paramtype);
$this->stack[] =
array('type' =>
$type, 'params' =>
$params, 'custom' =>
false, 'class' =>
$class, 'buffer' =>
null);
$this->curBlock =
& $this->stack[count($this->stack)-
1];
return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
* adds a custom block to the top of the block stack
* @param string $type block type (name)
* @param array $params the parameters array
* @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
* @return string the preProcessing() method's output
$callback =
$this->customPlugins[$type]['callback'];
if (is_array($callback)) {
$params =
$this->mapParams($params, array($class, 'init'), $paramtype);
$this->stack[] =
array('type' =>
$type, 'params' =>
$params, 'custom' =>
true, 'class' =>
$class, 'buffer' =>
null);
return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
* injects a block at the top of the plugin stack without calling its preProcessing method
* used by {else} blocks to re-add themselves after having closed everything up to their parent
* @param string $type block type (name)
* @param array $params parameters array
$class =
'Dwoo_Plugin_'.
$type;
if (class_exists($class, false) ===
false) {
$this->stack[] =
array('type' =>
$type, 'params' =>
$params, 'custom' =>
false, 'class' =>
$class, 'buffer' =>
null);
* removes the closest-to-top block of the given type and all other
* blocks encountered while going down the block stack
* @param string $type block type (name)
* @return string the output of all postProcessing() method's return values of the closed blocks
if ($pluginType & Dwoo::SMARTY_BLOCK) {
$type =
'smartyinterface';
$class =
'Dwoo_Plugin_'.
$top['type'];
$this->push(call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']), 0);
$output =
call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']);
if ($top['type'] ===
$type) {
* returns a reference to the first block of the given type encountered and
* optionally closes all blocks until it finds it
* this is mainly used by {else} plugins to close everything that was opened
* between their parent and themselves
* @param string $type the block type (name)
* @param bool $closeAlong whether to close all blocks encountered while going down the block stack or not
* @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
* 'custom'=>bool defining whether it's a custom plugin or not, for internal use)
public function &findBlock($type, $closeAlong =
false)
if ($closeAlong===
true) {
if ($b['type']===
$type) {
if ($b['type']===
$type) {
* returns a reference to the current block array
* @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
* 'custom'=>bool defining whether it's a custom plugin or not, for internal use)
* removes the block at the top of the stack and calls its postProcessing() method
* @return string the postProcessing() method's output
$class =
'Dwoo_Plugin_'.
$o['type'];
return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']);
* returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out of the given parameter array
* @param array $params parameter array
* @return array filtered parameters
foreach ($params as $k=>
$p) {
* returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given parameter array
* @param array $params parameter array
* @return array filtered parameters
foreach ($params as $k=>
$p) {
* entry point of the parser, it redirects calls to other parse* functions
* @param string $in the string within which we must parse something
* @param int $from the starting offset of the parsed area
* @param int $to the ending offset of the parsed area
* @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
* @param string $curBlock the current parser-block being processed
* @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
* @return string parsed values
protected function parse($in, $from, $to, $parsingParams =
false, $curBlock=
'', &$pointer =
null)
$first =
substr($in, $from, 1);
while ($first===
" " ||
$first===
"\n" ||
$first===
"\t" ||
$first===
"\r") {
if ($curBlock ===
'root' &&
substr($in, $from, strlen($this->rd)) ===
$this->rd) {
if ($this->debug) echo
'TEMPLATE PARSING ENDED<br />';
$substr =
substr($in, $from, $to-
$from);
if ($this->debug) echo
'<br />PARSE CALL : PARSING "<b>'.
htmlentities(substr($in, $from, min($to-
$from, 50))).
(($to-
$from) >
50 ?
'...':
'').
'</b>" @ '.
$from.
':'.
$to.
' in '.
$curBlock.
' : pointer='.
$pointer.
'<br/>';
if ($curBlock ===
'root' &&
$first ===
'*') {
$char =
substr($src, --
$startpos, 1);
} while ($startpos >
0 &&
($char ==
' ' ||
$char ==
"\t"));
if (!isset
($whitespaceStart)) {
$comOpen =
$this->ld.
'*';
$comClose =
'*'.
$this->rd;
while ($level >
0 &&
$ptr <
strlen($src)) {
$open =
strpos($src, $comOpen, $ptr);
$close =
strpos($src, $comClose, $ptr);
if ($open !==
false &&
$close !==
false) {
$ptr =
$open +
strlen($comOpen);
$ptr =
$close +
strlen($comClose);
} elseif ($open !==
false) {
$ptr =
$open +
strlen($comOpen);
} elseif ($close !==
false) {
$ptr =
$close +
strlen($comClose);
$endpos =
strpos($src, '*'.
$this->rd, $startpos);
$pointer +=
$endpos -
$startpos +
strlen('*'.
$this->rd);
$out =
$this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer);
} elseif ($first===
'%' &&
preg_match('#^%[a-z]#i', $substr)) {
$out =
$this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer);
} elseif ($first===
'"' ||
$first===
"'") {
$out =
$this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer);
} elseif (preg_match('/^[a-z][a-z0-9_]*(?:::[a-z][a-z0-9_]*)?('.
(is_array($parsingParams)||
$curBlock!=
'root'?
'':
'\s+[^(]|').
'\s*\(|\s*'.
$this->rdr.
'|\s*;)/i', $substr)) {
$out =
$this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer);
} elseif ($first ===
';') {
if ($this->debug) echo
'END OF INSTRUCTION<br />';
return $this->parse($in, $from+
1, $to, false, 'root', $pointer);
} elseif ($curBlock ===
'root' &&
preg_match('#^/([a-z][a-z0-9_]*)?#i', $substr, $match)) {
if (!empty($match[1]) &&
$match[1] ==
'else') {
throw
new Dwoo_Compilation_Exception($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed');
if (!empty($match[1]) &&
$match[1] ==
'elseif') {
throw
new Dwoo_Compilation_Exception($this, 'Elseif blocks must not be closed explicitly, they are automatically closed when their parent block is closed or a new else/elseif block is declared after them');
$pointer +=
strlen($match[0]);
$pointer -=
strlen($match[0]);
if ($this->debug) echo
'TOP BLOCK CLOSED<br />';
if ($this->debug) echo
'BLOCK OF TYPE '.
$match[1].
' CLOSED<br />';
} elseif ($curBlock ===
'root' &&
substr($substr, 0, strlen($this->rd)) ===
$this->rd) {
if ($this->debug) echo
'TAG PARSING ENDED<br />';
} elseif (is_array($parsingParams) &&
preg_match('#^([a-z0-9_]+\s*=)(?:\s+|[^=]).*#i', $substr, $match)) {
if ($this->debug) echo
'NAMED PARAM FOUND<br />';
while (substr($in, $from+
$len, 1)===
' ') {
$output =
array(trim(substr(trim($match[1]), 0, -
1)), $this->parse($in, $from+
$len, $to, false, 'namedparam', $pointer));
$parsingParams[] =
$output;
} elseif (preg_match('#^([a-z0-9_]+::\$[a-z0-9_]+)#i', $substr, $match)) {
$parsingParams[] =
array($match[1], $match[1]);
$pointer +=
strlen($match[1]);
} elseif ($substr!==
'' &&
(is_array($parsingParams) ||
$curBlock ===
'namedparam' ||
$curBlock ===
'condition' ||
$curBlock ===
'expression')) {
// unquoted string, bool or number
$out =
$this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
$substr =
substr($in, $pointer, $to-
$pointer);
// var parsed, check if any var-extension applies
if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) {
if($this->debug) echo
'PARSING POST-VAR EXPRESSION '.
$substr.
'<br />';
$pointer +=
strlen($match[0]) -
1;
$expr =
$this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer);
$expr =
$this->parse($in, $pointer, $to, array(), 'expression', $pointer);
$out[count($out)-
1][0] .=
$match[1] .
$expr[0][0];
$out[count($out)-
1][1] .=
$match[1] .
$expr[0][1];
$expr =
$this->parseVar($in, $pointer, $to, false, $curBlock, $pointer);
$expr =
$this->parse($in, $pointer, $to, false, 'expression', $pointer);
$out[0] .=
$match[1] .
$expr[0];
$out[1] .=
$match[1] .
$expr[1];
$out[0] .=
$match[1] .
$expr;
$out[1] .=
$match[1] .
$expr;
$out .=
$match[1] .
$expr[0];
$out .=
$match[1] .
$expr;
} else if ($curBlock ===
'root' &&
preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) {
if($this->debug) echo
'PARSING POST-VAR ASSIGNMENT '.
$substr.
'<br />';
$operator =
trim($match[1]);
if (substr($value, 0, 1) ==
'=') {
$pointer +=
strlen($match[1]);
if ($operator !==
'++' &&
$operator !==
'--') {
$parts =
$this->parse($value, 0, strlen($value), $parts, 'condition', $ptr);
$parts =
$this->mapParams($parts, array('Dwoo_Plugin_if', 'init'), 1);
$out =
preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out);
if ($curBlock !==
'modifier' &&
($parsed ===
'func' ||
$parsed ===
'var') &&
preg_match('#^\|@?[a-z0-9_]+(:.*)?#i', $substr, $match)) {
// parse modifier on funcs or vars
$out[count($out)-
1][0] =
$tmp;
$out[count($out)-
1][1] .=
substr($substr, $srcPointer, $srcPointer -
$pointer);
$out =
$this->replaceModifiers(array(null, null, $out, $match[0]), 'var', $pointer);
// func parsed, check if any func-extension applies
if ($parsed===
'func' &&
preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z].*)?#is', $substr, $match)) {
// parse method call or property read
$out[count($out)-
1][1] .=
$output;
if ($curBlock ===
'root' &&
substr($out, 0, strlen(self::PHP_OPEN)) !==
self::PHP_OPEN) {
return self::PHP_OPEN .
'echo '.
$out.
';'.
self::PHP_CLOSE;
* @param string $in the string within which we must parse something
* @param int $from the starting offset of the parsed area
* @param int $to the ending offset of the parsed area
* @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
* @param string $curBlock the current parser-block being processed
* @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
* @return string parsed values
protected function parseFunction($in, $from, $to, $parsingParams =
false, $curBlock=
'', &$pointer =
null)
$cmdstr =
substr($in, $from, $to-
$from);
preg_match('/^([a-z][a-z0-9_]*(?:::[a-z][a-z0-9_]*)?)(\s*'.
$this->rdr.
'|\s*;)?/i', $cmdstr, $match);
if ($this->debug) echo
'FUNC FOUND ('.
$func.
')<br />';
if (is_array($parsingParams) ||
$curBlock !=
'root') {
$paramspos =
strpos($cmdstr, '(');
} elseif(preg_match_all('#[a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) {
$paramspos =
$match[1][0][1];
$paramsep =
substr($match[1][0][0], -
1) ===
'(' ?
')':
'';
$paramspos +=
strlen($match[1][0][0]) -
1;
if(substr($cmdstr, 0, 2) ===
'if' ||
substr($cmdstr, 0, 6) ===
'elseif') {
if(strlen($match[1][0][0]) >
1) {
if ($paramspos ===
false) {
if ($curBlock !==
'root') {
return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
if ($curBlock ===
'condition') {
return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
$paramstr =
substr($cmdstr, $paramspos+
1);
if (substr($paramstr, -
1, 1) ===
$paramsep) {
$paramstr =
substr($paramstr, 0, -
1);
$params =
$this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr);
while ($ptr <
strlen($paramstr)) {
if ($ptr >=
strlen($paramstr)) {
if ($func !==
'if' &&
$func !==
'elseif' &&
$paramstr[$ptr] ===
')') {
if ($this->debug) echo
'PARAM PARSING ENDED, ")" FOUND, POINTER AT '.
$ptr.
'<br/>';
} elseif ($paramstr[$ptr] ===
';') {
if ($this->debug) echo
'PARAM PARSING ENDED, ";" FOUND, POINTER AT '.
$ptr.
'<br/>';
} elseif ($func !==
'if' &&
$func !==
'elseif' &&
$paramstr[$ptr] ===
'/') {
if ($this->debug) echo
'PARAM PARSING ENDED, "/" FOUND, POINTER AT '.
$ptr.
'<br/>';
if ($this->debug) echo
'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT '.
$ptr.
'<br/>';
if ($paramstr[$ptr] ===
' ' ||
$paramstr[$ptr] ===
',' ||
$paramstr[$ptr] ===
"\r" ||
$paramstr[$ptr] ===
"\n" ||
$paramstr[$ptr] ===
"\t") {
if ($this->debug) echo
'FUNC START PARAM PARSING WITH POINTER AT '.
$ptr.
'<br/>';
if ($func ===
'if' ||
$func ===
'elseif' ||
$func ===
'tif') {
$params =
$this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr);
$params =
$this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr);
if ($this->debug) echo
'PARAM PARSED, POINTER AT '.
$ptr.
' ('.
substr($paramstr, $ptr-
1, 3).
')<br/>';
$paramstr =
substr($paramstr, 0, $ptr);
foreach ($params as $k=>
$p) {
if (($state & 2) &&
preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
$params[$k] =
array($m[2], array('true', 'true'));
$pointer +=
(isset
($paramstr) ?
strlen($paramstr) :
0) +
(')' ===
$paramsep ?
2 :
($paramspos ===
false ?
0 :
1)) +
strlen($func) +
(isset
($whitespace) ?
$whitespace :
0);
if ($this->debug) echo
'FUNC ADDS '.
((isset
($paramstr) ?
strlen($paramstr) :
0) +
(')' ===
$paramsep ?
2 :
($paramspos ===
false ?
0 :
1)) +
strlen($func)).
' TO POINTER<br/>';
if ($curBlock ===
'method' ||
$func ===
'do' ||
strstr($func, '::') !==
false) {
$pluginType =
Dwoo::NATIVE_PLUGIN;
if ($pluginType & Dwoo::BLOCK_PLUGIN) {
if ($curBlock !==
'root' ||
is_array($parsingParams)) {
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
return $this->addBlock($func, $params, $state);
} elseif ($pluginType & Dwoo::SMARTY_BLOCK) {
if ($curBlock !==
'root' ||
is_array($parsingParams)) {
array_unshift($params, array('__functype', array($pluginType, $pluginType)));
array_unshift($params, array('__funcname', array($func, $func)));
return $this->addBlock('smartyinterface', $params, $state);
if ($pluginType & Dwoo::NATIVE_PLUGIN ||
$pluginType & Dwoo::SMARTY_FUNCTION ||
$pluginType & Dwoo::SMARTY_BLOCK) {
$params =
$this->mapParams($params, null, $state);
} elseif ($pluginType & Dwoo::CLASS_PLUGIN) {
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
$params =
$this->mapParams($params, array('Dwoo_Plugin_'.
$func, ($pluginType & Dwoo::COMPILABLE_PLUGIN) ?
'compile' :
'process'), $state);
} elseif ($pluginType & Dwoo::FUNC_PLUGIN) {
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
$params =
$this->mapParams($params, 'Dwoo_Plugin_'.
$func.
(($pluginType & Dwoo::COMPILABLE_PLUGIN) ?
'_compile' :
''), $state);
} elseif ($pluginType & Dwoo::SMARTY_MODIFIER) {
$output =
'smarty_modifier_'.
$func.
'('.
implode(', ', $params).
')';
} elseif ($pluginType & Dwoo::PROXY_PLUGIN) {
$params =
$this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
} elseif ($pluginType & Dwoo::TEMPLATE_PLUGIN) {
// transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
$hasDefault =
$defValue !==
null;
if ($defValue ===
'null') {
} elseif ($defValue ===
'false') {
} elseif ($defValue ===
'true') {
} elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) {
$defValue =
substr($defValue, 1, -
1);
$map[] =
array($param, $hasDefault, $defValue);
$params =
$this->mapParams($params, null, $state, $map);
// only keep php-syntax-safe values for non-block plugins
foreach ($params as &$p) {
if ($pluginType & Dwoo::NATIVE_PLUGIN) {
if (isset
($params['*'])) {
$output =
implode(';', $params['*']).
';';
if (is_array($parsingParams) ||
$curBlock !==
'root') {
return self::PHP_OPEN.
$output.
self::PHP_CLOSE;
if (isset
($params['*'])) {
$output =
$func.
'('.
implode(', ', $params['*']).
')';
} elseif ($pluginType & Dwoo::FUNC_PLUGIN) {
if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
$funcCompiler =
'Dwoo_Plugin_'.
$func.
'_compile';
$params =
self::implode_r($params);
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
$output =
'call_user_func(\''.
$callback.
'\', '.
$params.
')';
$output =
'Dwoo_Plugin_'.
$func.
'('.
$params.
')';
} elseif ($pluginType & Dwoo::CLASS_PLUGIN) {
if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
throw
new Dwoo_Exception('Custom plugin '.
$func.
' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use');
if (($ref =
new ReflectionMethod($callback, 'compile')) &&
$ref->isStatic()) {
$funcCompiler =
array($callback, 'compile');
$funcCompiler =
array(new $callback, 'compile');
$funcCompiler =
$callback;
$funcCompiler =
array('Dwoo_Plugin_'.
$func, 'compile');
$params =
self::implode_r($params);
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
throw
new Dwoo_Exception('Custom plugin '.
$func.
' must implement the "process" method to be usable, or you should provide a full callback to the method to use');
if (($ref =
new ReflectionMethod($callback, 'process')) &&
$ref->isStatic()) {
$output =
'call_user_func(array(\''.
$callback.
'\', \'process\'), '.
$params.
')';
$output =
'call_user_func(array($this->getObjectPlugin(\''.
$callback.
'\'), \'process\'), '.
$params.
')';
$output =
'call_user_func(array($this->plugins[\''.
$func.
'\'][\'callback\'][0], \''.
$callback[1].
'\'), '.
$params.
')';
} elseif (($ref =
new ReflectionMethod($callback[0], $callback[1])) &&
$ref->isStatic()) {
$output =
'call_user_func(array(\''.
$callback[0].
'\', \''.
$callback[1].
'\'), '.
$params.
')';
$output =
'call_user_func(array($this->getObjectPlugin(\''.
$callback[0].
'\'), \''.
$callback[1].
'\'), '.
$params.
')';
$output =
substr($output, 0, -
3).
')';
$output =
'$this->classCall(\''.
$func.
'\', array('.
$params.
'))';
} elseif ($pluginType & Dwoo::PROXY_PLUGIN) {
$output =
call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
} elseif ($pluginType & Dwoo::SMARTY_FUNCTION) {
if (isset
($params['*'])) {
$params =
self::implode_r($params['*'], true);
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
$output =
'call_user_func_array(array($this->plugins[\''.
$func.
'\'][\'callback\'][0], \''.
$callback[1].
'\'), array(array('.
$params.
'), $this))';
$output =
'call_user_func_array(array(\''.
$callback[0].
'\', \''.
$callback[1].
'\'), array(array('.
$params.
'), $this))';
$output =
$callback.
'(array('.
$params.
'), $this)';
$output =
'smarty_function_'.
$func.
'(array('.
$params.
'), $this)';
} elseif ($pluginType & Dwoo::TEMPLATE_PLUGIN) {
$params =
self::implode_r($params);
$output =
'Dwoo_Plugin_'.
$func.
'_'.
$this->templatePlugins[$func]['uuid'].
'('.
$params.
')';
$parsingParams[] =
array($output, $output);
} elseif ($curBlock ===
'namedparam') {
return array($output, $output);
* @param string $in the string within which we must parse something
* @param int $from the starting offset of the parsed area
* @param int $to the ending offset of the parsed area
* @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
* @param string $curBlock the current parser-block being processed
* @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
* @return string parsed values
protected function parseString($in, $from, $to, $parsingParams =
false, $curBlock=
'', &$pointer =
null)
$substr =
substr($in, $from, $to-
$from);
if ($this->debug) echo
'STRING FOUND (in '.
htmlentities(substr($in, $from, min($to-
$from, 50))).
(($to-
$from) >
50 ?
'...':
'').
')<br />';
while ($strend ===
false) {
$strend =
strpos($in, $first, $o);
if (substr($in, $strend-
1, 1) ===
'\\') {
if ($this->debug) echo
'STRING DELIMITED: '.
substr($in, $from, $strend+
1-
$from).
'<br/>';
$srcOutput =
substr($in, $from, $strend+
1-
$from);
$pointer +=
strlen($srcOutput);
if ($curBlock !==
'modifier' &&
preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend+
1-
$from), $match)) {
if ($curBlock ===
'root' &&
substr($modstr, -
1) ===
'}') {
$modstr =
substr($modstr, 0, -
1);
$output =
$this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr);
$srcOutput .=
substr($substr, $strend+
1-
$from, $ptr);
$parsingParams[] =
array($output, substr($srcOutput, 1, -
1));
} elseif ($curBlock ===
'namedparam') {
return array($output, substr($srcOutput, 1, -
1));
* @param string $in the string within which we must parse something
* @param int $from the starting offset of the parsed area
* @param int $to the ending offset of the parsed area
* @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
* @param string $curBlock the current parser-block being processed
* @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
* @return string parsed values
protected function parseConst($in, $from, $to, $parsingParams =
false, $curBlock=
'', &$pointer =
null)
$substr =
substr($in, $from, $to-
$from);
echo
'CONST FOUND : '.
$substr.
'<br />';
if (!preg_match('#^%([a-z0-9_:]+)#i', $substr, $m)) {
$parsingParams[] =
array($output, $m[1]);
} elseif ($curBlock ===
'namedparam') {
return array($output, $m[1]);
* @param string $key the constant to parse
* @param string $curBlock the current parser-block being processed
* @return string parsed constant
if ($curBlock !==
'root') {
$output =
'(defined("'.
$key.
'") ? '.
$key.
' : null)';
* @param string $in the string within which we must parse something
* @param int $from the starting offset of the parsed area
* @param int $to the ending offset of the parsed area
* @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
* @param string $curBlock the current parser-block being processed
* @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
* @return string parsed values
protected function parseVar($in, $from, $to, $parsingParams =
false, $curBlock=
'', &$pointer =
null)
$substr =
substr($in, $from, $to-
$from);
if (preg_match('#(\$?\.?[a-z0-9_:]*(?:(?:(?:\.|->)(?:[a-z0-9_:]+|(?R))|\[(?:[a-z0-9_:]+|(?R)|(["\'])[^\2]*?\2)\]))*)' .
// var key
($curBlock===
'root' ||
$curBlock===
'function' ||
$curBlock===
'namedparam' ||
$curBlock===
'condition' ||
$curBlock===
'variable' ||
$curBlock===
'expression' ?
'(\(.*)?' :
'()') .
// method call
($curBlock===
'root' ||
$curBlock===
'function' ||
$curBlock===
'namedparam' ||
$curBlock===
'condition' ||
$curBlock===
'variable' ||
$curBlock===
'delimited_string' ?
'((?:(?:[+/*%=-])(?:(?<!=)=?-?[$%][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9.,]*|[+-]))*)':
'()') .
// simple math expressions
($curBlock!==
'modifier' ?
'((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?':
'(())') .
// modifiers
'#i', $substr, $match)) {
$matchedLength =
strlen($match[0]);
$hasModifiers =
!empty($match[5]);
$hasExpression =
!empty($match[4]);
$hasMethodCall =
!empty($match[3]);
if (substr($key, -
1) ==
".") {
$methodCall =
substr($match[1], strrpos($match[1], '->')) .
$match[3];
$matchedLength -=
strlen($match[5]);
$pointer +=
$matchedLength;
// replace useless brackets by dot accessed vars
// prevent $foo->$bar calls because it doesn't seem worth the trouble
if (strpos($key, '->$') !==
false) {
echo
'METHOD CALL FOUND : $'.
$key.
substr($methodCall, 0, 30).
'<br />';
echo
'VAR FOUND : $'.
$key.
'<br />';
$parsed =
array($uid =>
'');
$curTxt =
& $parsed[$uid++
];
$current[$uid] =
array($uid+
1 =>
'');
$current =
& $current[$uid++
];
$curTxt =
& $current[$uid++
];
} elseif ($char ===
']') {
$current =
& $tree[count($tree)-
1];
$curTxt =
& $current[$uid++
];
} elseif ($char ===
'$') {
$curTxt =
& $current[$uid++
];
} elseif (($char ===
'.' ||
$char ===
'-') &&
count($tree) ==
0 &&
$inSplittedVar) {
$curTxt =
& $current[$uid++
];
unset
($uid, $current, $curTxt, $tree, $chars);
if ($this->debug) echo
'RECURSIVE VAR REPLACEMENT : '.
$key.
'<br>';
if ($this->debug) echo
'RECURSIVE VAR REPLACEMENT DONE : '.
$key.
'<br>';
$output =
preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("'.
$key.
'")');
$output =
$this->parseVarKey($key, $hasModifiers ?
'modifier' :
$curBlock);
preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch);
foreach ($expMatch[1] as $k=>
$operator) {
if (substr($expMatch[2][$k], 0, 1)===
'=') {
if ($curBlock !==
'root') {
throw
new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.
$substr.
'</em>, assignments can only be used in top level expressions like {$foo+=3} or {$foo="bar"}');
$expMatch[2][$k] =
substr($expMatch[2][$k], 1);
if (substr($expMatch[2][$k], 0, 1)===
'-' &&
strlen($expMatch[2][$k]) >
1) {
$expMatch[2][$k] =
substr($expMatch[2][$k], 1);
if (($operator===
'+'||
$operator===
'-') &&
$expMatch[2][$k]===
$operator) {
$output =
'('.
$output.
$operator.
$operator.
')';
} elseif (substr($expMatch[2][$k], 0, 1) ===
'$') {
$output =
'('.
$output.
' '.
$operator.
' '.
$this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').
')';
} elseif (substr($expMatch[2][$k], 0, 1) ===
'%') {
$output =
'('.
$output.
' '.
$operator.
' '.
$this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').
')';
} elseif (!empty($expMatch[2][$k])) {
$output =
'('.
$output.
' '.
$operator.
' '.
str_replace(',', '.', $expMatch[2][$k]).
')';
throw
new Dwoo_Compilation_Exception($this, 'Unfinished expression <em>'.
$substr.
'</em>, missing var or number after math operator');
$output =
'(is_string($tmp='.
$output.
') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)';
if ($curBlock !==
'modifier' &&
$hasModifiers) {
$output =
$this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr);
$parsingParams[] =
array($output, $key);
} elseif ($curBlock ===
'namedparam') {
return array($output, $key);
} elseif ($curBlock ===
'string' ||
$curBlock ===
'delimited_string') {
return array($matchedLength, $output);
} elseif ($curBlock ===
'expression' ||
$curBlock ===
'variable') {
} elseif (isset
($assign)) {
return self::PHP_OPEN.
$output.
';'.
self::PHP_CLOSE;
if ($curBlock ===
'string' ||
$curBlock ===
'delimited_string') {
* parses any number of chained method calls/property reads
* @param string $output the variable or whatever upon which the method are called
* @param string $methodCall method call source, starting at "->"
* @param string $curBlock the current parser-block being processed
* @param int $pointer a reference to a pointer that will be increased by the amount of characters parsed
* @return string parsed call(s)/read(s)
protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer)
if (strpos($methodCall, '->', $ptr) ===
$ptr) {
if (in_array($methodCall[$ptr], array(';', '/', ' ', "\t", "\r", "\n", ')', '+', '*', '%', '=', '-', '|')) ||
substr($methodCall, $ptr, strlen($this->rd)) ===
$this->rd) {
if(!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) {
if (empty($methMatch[2])) {
if ($curBlock ===
'root') {
$output .=
'->'.
$methMatch[1];
$output =
'(($tmp = '.
$output.
') ? $tmp->'.
$methMatch[1].
' : null)';
$ptr +=
strlen($methMatch[1]);
if (substr($methMatch[2], 0, 2) ===
'()') {
$parsedCall =
'->'.
$methMatch[1].
'()';
$ptr +=
strlen($methMatch[1]) +
2;
$parsedCall =
'->'.
$this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr);
if ($curBlock ===
'root') {
$output =
'(($tmp = '.
$output.
') ? $tmp'.
$parsedCall.
' : null)';
* parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save runtime processing time
* @param string $key the variable to parse
* @param string $curBlock the current parser-block being processed
* @return string parsed variable
if (substr($key, 0, 1) ===
'.') {
if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) {
if ($global ===
'COOKIES') {
if ($curBlock ===
'root') {
$output =
'(isset('.
$key.
')?'.
$key.
':null)';
} elseif (preg_match('#dwoo\.const\.([a-z0-9_:]+)#i', $key, $m)) {
} elseif ($this->scope !==
null) {
if (strstr($key, '.') ===
false &&
strstr($key, '[') ===
false &&
strstr($key, '->') ===
false) {
$output =
'$this->globals';
} elseif ($key ===
'_root' ||
$key ===
'__') {
} elseif ($key ===
'_parent' ||
$key ===
'_') {
$output =
'$this->readParentVar(1)';
} elseif ($key ===
'_key') {
if ($curBlock ===
'root') {
$output =
'$this->scope["'.
$key.
'"]';
$output =
'(isset($this->scope["'.
$key.
'"]) ? $this->scope["'.
$key.
'"] : null)';
preg_match_all('#(\[|->|\.)?([a-z0-9_]+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m);
if ($i ===
'_parent' ||
$i ===
'_') {
if (current($m[2]) ===
'_parent') {
$output =
'$this->readParentVar('.
$parentCnt.
')';
$output =
'$this->globals';
} elseif ($i ===
'_root' ||
$i ===
'__') {
} elseif ($i ===
'_key') {
$output =
'$this->scope';
while (count($m[1]) &&
$m[1][0] !==
'->') {
$m[2][0] =
preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]);
if(substr($m[2][0], 0, 1) ==
'"' ||
substr($m[2][0], 0, 1) ==
"'") {
$output .=
'['.
$m[2][0].
']';
$output .=
'["'.
$m[2][0].
'"]';
if ($curBlock !==
'root') {
$output =
'(isset('.
$output.
') ? '.
$output.
':null)';
$output =
'$this->readVarInto('.
str_replace("\n", '', var_export($m, true)).
', '.
$output.
', '.
($curBlock ==
'root' ?
'false':
'true').
')';
* flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz,
* it computes the contents of the brackets first and works out from there
* @param array $tree the variable tree parsed by he parseVar() method that must be flattened
* @param bool $recursed leave that to false by default, it is only for internal use
* @return string flattened tree
$out =
$recursed ?
'".$this->readVarInto(' :
'';
foreach ($tree as $bit) {
if (substr($key, 0, 1)===
'$') {
$out .=
'".'.
$this->parseVar($key, 0, strlen($key), false, 'variable').
'."';
if ($this->debug) echo
'PARSING SUBVARS IN : '.
$key.
'<br>';
preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*'.
'((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch);
'#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)'.
'((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i',
array($this, 'replaceVarKeyHelper'), substr($key, $last, $len)
if ($this->debug) echo
'RECURSIVE VAR REPLACEMENT DONE : '.
$key.
'<br>';
$out .=
$recursed ?
', true)."' :
'';
* helper function that parses a variable
* @param array $match the matched variable, array(1=>"string match")
* @return string parsed variable
return '".'.
$this->parseVar($match[0], 0, strlen($match[0]), false, 'variable').
'."';
* parses various constants, operators or non-quoted strings
* @param string $in the string within which we must parse something
* @param int $from the starting offset of the parsed area
* @param int $to the ending offset of the parsed area
* @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
* @param string $curBlock the current parser-block being processed
* @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
* @return string parsed values
protected function parseOthers($in, $from, $to, $parsingParams =
false, $curBlock=
'', &$pointer =
null)
$substr =
substr($in, $from, $to-
$from);
if ($curBlock ===
'condition') {
$breakChars =
array('(', ')', ' ', '||', '&&', '|', '&', '>=', '<=', '===', '==', '=', '!==', '!=', '<<', '<', '>>', '>', '^', '~', ',', '+', '-', '*', '/', '%', '!', '?', ':', $this->rd, ';');
} elseif ($curBlock ===
'modifier') {
$breakChars =
array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ";", $this->rd);
} elseif ($curBlock ===
'expression') {
$breakChars =
array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
$breakChars =
array(' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
while (list
($k,$char) =
each($breakChars)) {
$test =
strpos($substr, $char);
if ($test !==
false &&
$test <
$end) {
if ($curBlock ===
'condition') {
if ($end ===
0 &&
$breaker !==
false) {
$end =
strlen($breakChars[$breaker]);
$substr =
substr($substr, 0, $end);
if ($this->debug) echo
'BOOLEAN(FALSE) PARSED<br />';
if ($this->debug) echo
'BOOLEAN(TRUE) PARSED<br />';
} elseif ($substr ===
'null' ||
$substr ===
'NULL') {
if ($this->debug) echo
'NULL PARSED<br />';
$substr = (float)
$substr;
if ((int)
$substr ==
$substr) {
if ($this->debug) echo
'NUMBER ('.
$substr.
') PARSED<br />';
} elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) {
if ($this->debug) echo
'SIMPLE MATH PARSED<br />';
$substr =
'('.
$substr.
')';
} elseif ($curBlock ===
'condition' &&
array_search($substr, $breakChars, true) !==
false) {
if ($this->debug) echo
'BREAKCHAR ('.
$substr.
') PARSED<br />';
//$substr = '"'.$substr.'"';
if ($this->debug) echo
'BLABBER ('.
$substr.
') CASTED AS STRING<br />';
$parsingParams[] =
array($substr, $src);
} elseif ($curBlock ===
'namedparam') {
return array($substr, $src);
} elseif ($curBlock ===
'expression') {
throw
new Exception('Something went wrong');
* replaces variables within a parsed string
* @param string $string the parsed string
* @param string $first the first character parsed in the string, which is the string delimiter (' or ")
* @param string $curBlock the current parser-block being processed
* @return string the original string with variables replaced
if ($this->debug) echo
'STRING VAR REPLACEMENT : '.
$string.
'<br>';
while (($pos =
strpos($string, '$', $pos)) !==
false) {
$prev =
substr($string, $pos-
1, 1);
$var =
$this->parse($string, $pos, null, false, ($curBlock ===
'modifier' ?
'modifier' :
($prev ===
'`' ?
'delimited_string':
'string')));
$var =
$this->parse(str_replace('\\'.
$first, $first, $string), $pos, null, false, ($curBlock ===
'modifier' ?
'modifier' :
($prev ===
'`' ?
'delimited_string':
'string')));
if ($prev ===
'`' &&
substr($string, $pos+
$len, 1) ===
'`') {
$string =
substr_replace($string, $first.
'.'.
$var[1].
'.'.
$first, $pos-
1, $len+
2);
$string =
substr_replace($string, $first.
'.'.
$var[1].
'.'.
$first, $pos, $len);
if ($this->debug) echo
'STRING VAR REPLACEMENT DONE : '.
$string.
'<br>';
$string =
preg_replace_callback('#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array($this, 'replaceModifiers'), $string);
// replace escaped dollar operators by unescaped ones if required
* replaces the modifiers applied to a string or a variable
* @param array $m the regex matches that must be array(1=>"double or single quotes enclosing a string, when applicable", 2=>"the string or var", 3=>"the modifiers matched")
* @param string $curBlock the current parser-block being processed
* @return string the input enclosed with various function calls according to the modifiers found
protected function replaceModifiers(array $m, $curBlock =
null, &$pointer =
null)
if ($this->debug) echo
'PARSING MODIFIERS : '.
$m[3].
'<br />';
$cmdstrsrc =
substr($m[3], 1);
// remove last quote if present
if (substr($cmdstrsrc, -
1, 1) ===
$m[1]) {
$cmdstrsrc =
substr($cmdstrsrc, 0, -
1);
while (strlen($cmdstrsrc) >
0 &&
$continue) {
if ($cmdstrsrc[0] ===
'|') {
$cmdstrsrc =
substr($cmdstrsrc, 1);
if ($cmdstrsrc[0] ===
' ' ||
$cmdstrsrc[0] ===
';' ||
substr($cmdstrsrc, 0, strlen($this->rd)) ===
$this->rd) {
if ($this->debug) echo
'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND<br/>';
$pointer -=
strlen($cmdstrsrc);
if (!preg_match('/^(@{0,2}[a-z][a-z0-9_]*)(:)?/i', $cmdstr, $match)) {
$paramspos =
!empty($match[2]) ?
strlen($match[1]) :
false;
if ($paramspos ===
false) {
if ($this->debug) echo
'MODIFIER ('.
$func.
') CALLED WITH NO PARAMS<br/>';
$paramstr =
substr($cmdstr, $paramspos+
1);
if (substr($paramstr, -
1, 1) ===
$paramsep) {
$paramstr =
substr($paramstr, 0, -
1);
while ($ptr <
strlen($paramstr)) {
if ($this->debug) echo
'MODIFIER ('.
$func.
') START PARAM PARSING WITH POINTER AT '.
$ptr.
'<br/>';
if ($this->debug) echo
$paramstr.
'--'.
$ptr.
'--'.
strlen($paramstr).
'--modifier<br/>';
$params =
$this->parse($paramstr, $ptr, strlen($paramstr), $params, 'modifier', $ptr);
if ($this->debug) echo
'PARAM PARSED, POINTER AT '.
$ptr.
'<br/>';
if ($ptr >=
strlen($paramstr)) {
if ($this->debug) echo
'PARAM PARSING ENDED, PARAM STRING CONSUMED<br/>';
if ($paramstr[$ptr] ===
' ' ||
$paramstr[$ptr] ===
'|' ||
$paramstr[$ptr] ===
';' ||
substr($paramstr, $ptr, strlen($this->rd)) ===
$this->rd) {
if ($this->debug) echo
'PARAM PARSING ENDED, " ", "|", RIGHT DELIMITER or ";" FOUND, POINTER AT '.
$ptr.
'<br/>';
if ($paramstr[$ptr] !==
'|') {
$pointer -=
strlen($paramstr) -
$ptr;
if ($ptr <
strlen($paramstr) &&
$paramstr[$ptr] ===
':') {
$paramstr =
substr($paramstr, 0, $ptr);
foreach ($params as $k=>
$p) {
if (($state & 2) &&
preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
$params[$k] =
array($m[2], array('true', 'true'));
// check if we must use array_map with this plugin or not
if (substr($func, 0, 1) ===
'@') {
if ($pluginType & Dwoo::NATIVE_PLUGIN) {
$params =
$this->mapParams($params, null, $state);
$params =
$params['*'][0];
$params =
self::implode_r($params);
$output =
'$this->arrayMap(\''.
$func.
'\', array('.
$params.
'))';
$output =
$func.
'('.
$params.
')';
} elseif ($pluginType & Dwoo::PROXY_PLUGIN) {
$params =
$this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
$output =
call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
} elseif ($pluginType & Dwoo::SMARTY_MODIFIER) {
$params =
$this->mapParams($params, null, $state);
$params =
$params['*'][0];
$params =
self::implode_r($params);
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
$output =
($mapped ?
'$this->arrayMap' :
'call_user_func_array').
'(array($this->plugins[\''.
$func.
'\'][\'callback\'][0], \''.
$callback[1].
'\'), array('.
$params.
'))';
$output =
($mapped ?
'$this->arrayMap' :
'call_user_func_array').
'(array(\''.
$callback[0].
'\', \''.
$callback[1].
'\'), array('.
$params.
'))';
$output =
'$this->arrayMap(\''.
$callback.
'\', array('.
$params.
'))';
$output =
$callback.
'('.
$params.
')';
$output =
'$this->arrayMap(\'smarty_modifier_'.
$func.
'\', array('.
$params.
'))';
$output =
'smarty_modifier_'.
$func.
'('.
$params.
')';
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
$pluginName =
'Dwoo_Plugin_'.
$func;
if ($pluginType & Dwoo::CLASS_PLUGIN) {
$callback =
array($pluginName, ($pluginType & Dwoo::COMPILABLE_PLUGIN) ?
'compile' :
'process');
$callback =
$pluginName .
(($pluginType & Dwoo::COMPILABLE_PLUGIN) ?
'_compile' :
'');
$params =
$this->mapParams($params, $callback, $state);
if ($pluginType & Dwoo::FUNC_PLUGIN) {
if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
$funcCompiler =
'Dwoo_Plugin_'.
$func.
'_compile';
$params =
self::implode_r($params);
$output =
'$this->arrayMap(\''.
$pluginName.
'\', array('.
$params.
'))';
$output =
$pluginName.
'('.
$params.
')';
if ($pluginType & Dwoo::COMPILABLE_PLUGIN) {
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
throw
new Dwoo_Exception('Custom plugin '.
$func.
' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use');
if (($ref =
new ReflectionMethod($callback, 'compile')) &&
$ref->isStatic()) {
$funcCompiler =
array($callback, 'compile');
$funcCompiler =
array(new $callback, 'compile');
$funcCompiler =
$callback;
$funcCompiler =
array('Dwoo_Plugin_'.
$func, 'compile');
$params =
self::implode_r($params);
if ($pluginType & Dwoo::CUSTOM_PLUGIN) {
$output =
($mapped ?
'$this->arrayMap' :
'call_user_func_array').
'(array($this->plugins[\''.
$func.
'\'][\'callback\'][0], \''.
$callback[1].
'\'), array('.
$params.
'))';
$output =
($mapped ?
'$this->arrayMap' :
'call_user_func_array').
'(array(\''.
$callback[0].
'\', \''.
$callback[1].
'\'), array('.
$params.
'))';
$output =
'$this->arrayMap(array($this->getObjectPlugin(\'Dwoo_Plugin_'.
$func.
'\'), \'process\'), array('.
$params.
'))';
$output =
'$this->classCall(\''.
$func.
'\', array('.
$params.
'))';
if ($curBlock ===
'var' ||
$m[1] ===
null) {
} elseif ($curBlock ===
'string' ||
$curBlock ===
'root') {
return $m[1].
'.'.
$output.
'.'.
$m[1].
(isset
($add)?
$add:
null);
* recursively implodes an array in a similar manner as var_export() does but with some tweaks
* to handle pre-compiled values and the fact that we do not need to enclose everything with
* "array" and do not require top-level keys to be displayed
* @param array $params the array to implode
* @param bool $recursiveCall if set to true, the function outputs key names for the top level
* @return string the imploded array
public static function implode_r(array $params, $recursiveCall =
false)
foreach ($params as $k=>
$p) {
$out2 .=
var_export($k2, true).
' => '.
(is_array($v) ?
'array('.
self::implode_r($v, true).
')' :
$v).
', ';
$p =
rtrim($out2, ', ').
')';
return rtrim($out, ', ');
* returns the plugin type of a plugin and adds it to the used plugins array if required
* @param string $name plugin name, as found in the template
* @return int type as a multi bit flag composed of the Dwoo plugin types constants
while ($pluginType <=
0) {
$pluginType =
Dwoo::TEMPLATE_PLUGIN |
Dwoo::COMPILABLE_PLUGIN;
} elseif (class_exists('Dwoo_Plugin_'.
$name, false) !==
false) {
$pluginType =
Dwoo::BLOCK_PLUGIN;
$pluginType =
Dwoo::CLASS_PLUGIN;
if (in_array('Dwoo_ICompilable', $interfaces) !==
false ||
in_array('Dwoo_ICompilable_Block', $interfaces) !==
false) {
$pluginType |=
Dwoo::COMPILABLE_PLUGIN;
$pluginType =
Dwoo::FUNC_PLUGIN;
$pluginType =
Dwoo::FUNC_PLUGIN |
Dwoo::COMPILABLE_PLUGIN;
$pluginType =
Dwoo::SMARTY_MODIFIER;
$pluginType =
Dwoo::SMARTY_FUNCTION;
$pluginType =
Dwoo::SMARTY_BLOCK;
$pluginType =
Dwoo::NATIVE_PLUGIN;
} elseif (is_object($this->dwoo->getPluginProxy()) &&
$this->dwoo->getPluginProxy()->handles($name)) {
$pluginType =
Dwoo::PROXY_PLUGIN;
if (($pluginType & Dwoo::COMPILABLE_PLUGIN) ===
0 &&
($pluginType & Dwoo::NATIVE_PLUGIN) ===
0 &&
($pluginType & Dwoo::PROXY_PLUGIN) ===
0) {
* allows a plugin to load another one at compile time, this will also mark
* it as used by this template so it will be loaded at runtime (which can be
* useful for compiled plugins that rely on another plugin when their compiled
* @param string $name the plugin name
* runs htmlentities over the matched <?php ?> blocks when the security policy enforces that
* @param array $match matched php block
* @return string the htmlentities-converted string
* maps the parameters received from the template onto the parameters required by the given callback
* @param array $params the array of parameters
* @param callback $callback the function or method to reflect on to find out the required parameters
* @param int $callType the type of call in the template, 0 = no params, 1 = php-style call, 2 = named parameters call
* @param array $map the parameter map to use, if not provided it will be built from the callback
* @return array parameters sorted in the correct order with missing optional parameters filled
protected function mapParams(array $params, $callback, $callType=
2, $map =
null)
// transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
foreach ($params as $p) {
// loops over the param map and assigns values from the template or default value for unset optional params
while (list
($k,$v) =
each($map)) {
// "rest" array parameter, fill every remaining params in it and then break
foreach ($ps as $i=>
$p) {
$paramlist[$v[0]] =
array($tmp, $tmp2);
unset
($tmp, $tmp2, $i, $p);
} elseif (isset
($ps[$v[0]])) {
// parameter is defined as named param
$paramlist[$v[0]] =
$ps[$v[0]];
} elseif (isset
($ps[$k])) {
// parameter is defined as ordered param
$paramlist[$v[0]] =
$ps[$k];
} elseif ($v[1]===
false) {
// parameter is not defined and not optional, throw error
} elseif ($v[2]===
null) {
// enforce lowercased null if default value is null (php outputs NULL with var export)
$paramlist[$v[0]] =
array('null', null);
// outputs default value with var_export
$paramlist[$v[0]] =
array(var_export($v[2], true), $v[2]);
foreach ($ps as $i=>
$p) {
* returns the parameter map of the given callback, it filters out entries typed as Dwoo and Dwoo_Compiler and turns the rest parameter into a "*"
* @param callback $callback the function/method to reflect on
* @return array processed parameter map
return array(array('*', true));
$ref =
new ReflectionMethod($callback[0], $callback[1]);
$ref =
new ReflectionFunction($callback);
foreach ($ref->getParameters() as $param) {
if (($class =
$param->getClass()) !==
null &&
$class->name ===
'Dwoo') {
if (($class =
$param->getClass()) !==
null &&
$class->name ===
'Dwoo_Compiler') {
if ($param->getName() ===
'rest' &&
$param->isArray() ===
true) {
$out[] =
array('*', $param->isOptional(), null);
$out[] =
array($param->getName(), $param->isOptional(), $param->isOptional() ?
$param->getDefaultValue() :
null);
* returns a default instance of this compiler, used by default by all Dwoo templates that do not have a
* specific compiler assigned and when you do not override the default compiler factory function
* @see Dwoo::setDefaultCompilerFactory()
if (self::$instance ===
null) {
self::$instance =
new self;
Documentation generated on Sat, 18 Jul 2009 21:04:45 +0200 by phpDocumentor 1.4.0