fenom/src/Fenom/Compiler.php

1099 lines
33 KiB
PHP
Raw Normal View History

2013-01-25 18:36:16 +04:00
<?php
2013-04-28 11:33:36 +04:00
/*
2013-06-28 11:53:53 +04:00
* This file is part of Fenom.
2013-04-28 11:33:36 +04:00
*
* (c) 2013 Ivan Shalganov
*
2013-04-28 18:08:57 +04:00
* For the full copyright and license information, please view the license.md
2013-04-28 11:33:36 +04:00
* file that was distributed with this source code.
*/
2013-06-28 11:53:53 +04:00
namespace Fenom;
2014-02-27 16:30:44 +04:00
2015-01-07 15:24:57 +03:00
use Doctrine\Instantiator\Exception\InvalidArgumentException;
2016-05-06 23:04:08 +03:00
use Fenom\Error\CompileException;
2013-07-29 16:15:52 +04:00
use Fenom\Error\InvalidUsageException;
use Fenom\Error\UnexpectedTokenException;
2013-01-25 18:36:16 +04:00
/**
* Compilers collection
2013-06-28 11:53:53 +04:00
* @package Fenom
2013-07-04 01:28:10 +04:00
* @author Ivan Shalganov <a.cobest@gmail.com>
*/
2013-07-29 14:58:14 +04:00
class Compiler
{
2013-01-25 18:36:16 +04:00
/**
* Tag {include ...}
*
* @static
* @param Tokenizer $tokens
2014-04-12 01:00:58 +04:00
* @param Tag $tag
2014-01-20 11:54:28 +04:00
* @throws \LogicException
2013-01-25 18:36:16 +04:00
* @return string
*/
2014-04-12 01:00:58 +04:00
public static function tagInclude(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-05-06 14:22:58 +04:00
$tpl = $tag->tpl;
$name = false;
2013-04-28 11:33:36 +04:00
$cname = $tpl->parsePlainArg($tokens, $name);
2014-05-06 14:22:58 +04:00
$p = $tpl->parseParams($tokens);
2014-01-20 11:54:28 +04:00
if ($name) {
2014-02-27 16:30:44 +04:00
if ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE) {
2016-10-09 23:40:37 +03:00
$_t = $tpl;
$recursion = false;
while($_t->parent) {
if($_t->parent->getName() == $name) { // recursion detected
$recursion = true;
}
$_t = $_t->parent;
}
if(!$recursion) {
$inc = $tpl->getStorage()->getRawTemplate($tpl);
$inc->load($name, true);
$tpl->addDepend($inc);
$var = $tpl->tmpVar();
if ($p) {
return $var . ' = $var; $var = ' . self::toArray($p) . ' + $var; ?>' . $inc->getBody() . '<?php $var = ' . $var . '; unset(' . $var . ');';
} else {
return $var . ' = $var; ?>' . $inc->getBody() . '<?php $var = ' . $var . '; unset(' . $var . ');';
}
2014-01-20 11:54:28 +04:00
}
2014-02-27 16:30:44 +04:00
} elseif (!$tpl->getStorage()->templateExists($name)) {
2014-01-20 11:54:28 +04:00
throw new \LogicException("Template $name not found");
2013-02-20 19:51:06 +04:00
}
2014-01-20 11:54:28 +04:00
}
2014-02-27 16:30:44 +04:00
if ($p) {
return '$tpl->getStorage()->getTemplate(' . $cname . ')->display(' . self::toArray($p) . ' + $var);';
2013-01-25 18:36:16 +04:00
} else {
return '$tpl->getStorage()->getTemplate(' . $cname . ')->display($var);';
2013-01-25 18:36:16 +04:00
}
}
2013-01-25 18:36:16 +04:00
2013-09-02 17:40:58 +04:00
/**
* Tag {insert ...}
* @param Tokenizer $tokens
2014-04-12 01:00:58 +04:00
* @param Tag $tag
2013-09-02 17:40:58 +04:00
* @throws Error\InvalidUsageException
2014-04-12 01:00:58 +04:00
* @return string
2013-09-02 17:40:58 +04:00
*/
2014-04-12 01:00:58 +04:00
public static function tagInsert(Tokenizer $tokens, Tag $tag)
2013-09-02 17:40:58 +04:00
{
2014-04-12 01:00:58 +04:00
$tpl = $tag->tpl;
2013-09-02 17:40:58 +04:00
$tpl->parsePlainArg($tokens, $name);
if (!$name) {
throw new InvalidUsageException("Tag {insert} accept only static template name");
}
$inc = $tpl->getStorage()->compile($name, false);
$tpl->addDepend($inc);
2013-09-14 19:55:53 +04:00
return '?>' . $inc->getBody() . '<?php';
2013-09-02 17:40:58 +04:00
}
2013-01-25 18:36:16 +04:00
/**
* Open tag {if ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function ifOpen(Tokenizer $tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2013-01-25 18:36:16 +04:00
$scope["else"] = false;
2013-08-11 19:55:30 +04:00
return 'if(' . $scope->tpl->parseExpr($tokens) . ') {';
}
/**
* Tag {elseif ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @throws InvalidUsageException
* @return string
*/
public static function tagElseIf(Tokenizer $tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
if ($scope["else"]) {
throw new InvalidUsageException('Incorrect use of the tag {elseif}');
2013-01-25 18:36:16 +04:00
}
2013-08-11 19:55:30 +04:00
return '} elseif(' . $scope->tpl->parseExpr($tokens) . ') {';
}
2013-01-25 18:36:16 +04:00
/**
* Tag {else}
*
* @param Tokenizer $tokens
* @param Tag $scope
2013-01-25 18:36:16 +04:00
* @return string
*/
2015-01-07 15:24:57 +03:00
public static function tagElse($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2013-01-25 18:36:16 +04:00
$scope["else"] = true;
return '} else {';
}
2013-01-25 18:36:16 +04:00
/**
* Open tag {foreach ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-03-15 00:12:02 +04:00
* @throws UnexpectedTokenException
* @throws InvalidUsageException
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function foreachOpen(Tokenizer $tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2016-05-06 23:04:08 +03:00
$scope["else"] = false;
$scope["key"] = null;
$scope["prepend"] = "";
$scope["before"] = array();
$scope["after"] = array();
$scope["body"] = array();
if ($tokens->is('[')) { // array
2015-02-22 23:07:26 +03:00
$count = 0;
2016-05-06 23:04:08 +03:00
$scope['from'] = $scope->tpl->parseArray($tokens, $count);
$scope['check'] = $count;
$scope["var"] = $scope->tpl->tmpVar();
$scope['prepend'] = $scope["var"].' = '.$scope['from'].';';
$scope['from'] = $scope["var"];
} else { // expression
$scope['from'] = $scope->tpl->parseExpr($tokens, $is_var);
2014-06-28 16:38:50 +04:00
if($is_var) {
2016-05-06 23:04:08 +03:00
$scope['check'] = '!empty('.$scope['from'].') && (is_array('.$scope['from'].') || '.$scope['from'].' instanceof \Traversable)';
2014-06-28 16:38:50 +04:00
} else {
$scope["var"] = $scope->tpl->tmpVar();
2016-05-06 23:04:08 +03:00
$scope['prepend'] = $scope["var"].' = '.$scope['from'].';';
$scope['from'] = $scope["var"];
$scope['check'] = 'is_array('.$scope['from'].') && count('.$scope['from'].') || ('.$scope['from'].' instanceof \Traversable)';
2014-06-28 16:38:50 +04:00
}
2013-01-25 18:36:16 +04:00
}
2016-05-06 23:04:08 +03:00
if($tokens->is(T_AS)) {
2013-01-25 18:36:16 +04:00
$tokens->next();
2013-09-02 17:40:58 +04:00
$value = $scope->tpl->parseVariable($tokens);
2016-05-06 23:04:08 +03:00
if ($tokens->is(T_DOUBLE_ARROW)) {
$tokens->next();
$scope["key"] = $value;
$scope["value"] = $scope->tpl->parseVariable($tokens);
} else {
$scope["value"] = $value;
}
} else {
$scope["value"] = '$_un';
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
while ($token = $tokens->key()) {
2013-01-25 18:36:16 +04:00
$param = $tokens->get(T_STRING);
2016-05-06 23:04:08 +03:00
$var_name = self::foreachProp($scope, $param);
2013-01-25 18:36:16 +04:00
$tokens->getNext("=");
$tokens->next();
2016-05-06 23:04:08 +03:00
$scope['before'][] = $scope->tpl->parseVariable($tokens)." = &". $var_name;
2013-01-25 18:36:16 +04:00
}
2016-05-06 23:04:08 +03:00
return '';
2013-01-25 18:36:16 +04:00
}
/**
* Tag {foreachelse}
*
* @param Tokenizer $tokens
* @param Tag $scope
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function foreachElse($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2013-01-25 18:36:16 +04:00
$scope["no-break"] = $scope["no-continue"] = $scope["else"] = true;
2016-05-06 23:04:08 +03:00
$after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
return " {$after} } } else {";
}
/**
* @param Tag $scope
* @param string $prop
* @return string
* @throws CompileException
*/
public static function foreachProp(Tag $scope, $prop) {
if(empty($scope["props"][$prop])) {
$var_name = $scope["props"][$prop] = $scope->tpl->tmpVar()."_".$prop;
switch($prop) {
case "index":
$scope["before"][] = $var_name . ' = 0';
$scope["after"][] = $var_name . '++';
break;
case "first":
$scope["before"][] = $var_name . ' = true';
$scope["after"][] = $var_name . ' && (' . $var_name . ' = false )';
break;
case "last":
$scope["before"][] = $var_name . ' = false';
$scope["uid"] = $scope->tpl->tmpVar();
$scope["before"][] = $scope["uid"] . " = count({$scope["from"]})";
$scope["body"][] = 'if(!--' . $scope["uid"] . ') ' . $var_name . ' = true';
break;
default:
throw new CompileException("Unknown foreach property '$prop'");
}
}
return $scope["props"][$prop];
2013-01-25 18:36:16 +04:00
}
/**
* Close tag {/foreach}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function foreachClose($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2016-05-06 23:04:08 +03:00
$before = $scope["before"] ? implode("; ", $scope["before"]) . ";" : "";
$head = $scope["body"] ? implode("; ", $scope["body"]) . ";" : "";
$body = $scope->getContent();
if ($scope["key"]) {
$code = "<?php {$scope["prepend"]} if({$scope["check"]}) {\n $before foreach({$scope["from"]} as {$scope["key"]} => {$scope["value"]}) { $head?>$body";
} else {
$code = "<?php {$scope["prepend"]} if({$scope["check"]}) {\n $before foreach({$scope["from"]} as {$scope["value"]}) { $head?>$body";
}
$scope->replaceContent($code);
2013-07-29 14:58:14 +04:00
if ($scope["else"]) {
2013-01-25 18:36:16 +04:00
return '}';
} else {
2016-05-06 23:04:08 +03:00
$after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
return " {$after} } }";
2013-01-25 18:36:16 +04:00
}
}
/**
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-09-02 17:40:58 +04:00
* @throws Error\UnexpectedTokenException
* @throws Error\InvalidUsageException
2013-01-25 18:36:16 +04:00
* @return string
2015-02-22 23:07:26 +03:00
* @codeCoverageIgnore
2013-01-25 18:36:16 +04:00
*/
public static function forOpen(Tokenizer $tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2015-02-22 23:07:26 +03:00
trigger_error("Fenom: tag {for} deprecated, use {foreach 1..4 as \$value} (in {$scope->tpl->getName()}:{$scope->line})", E_USER_DEPRECATED);
2014-10-15 01:01:55 +04:00
$p = array(
2014-05-06 14:22:58 +04:00
"index" => false,
"first" => false,
"last" => false,
"step" => 1,
"to" => false,
2015-01-07 15:24:57 +03:00
// "max" => false,
// "min" => false
2014-05-06 14:22:58 +04:00
);
2013-01-25 18:36:16 +04:00
$scope["after"] = $before = $body = array();
2014-05-06 14:22:58 +04:00
$i = array('', '');
$c = "";
$var = $scope->tpl->parseTerm($tokens, $is_var);
2013-09-02 17:40:58 +04:00
if (!$is_var) {
throw new UnexpectedTokenException($tokens);
}
2013-01-25 18:36:16 +04:00
$tokens->get("=");
$tokens->next();
2013-08-11 19:55:30 +04:00
$val = $scope->tpl->parseExpr($tokens);
2014-05-06 14:22:58 +04:00
$p = $scope->tpl->parseParams($tokens, $p);
2013-01-25 18:36:16 +04:00
2013-07-29 14:58:14 +04:00
if (is_numeric($p["step"])) {
if ($p["step"] > 0) {
2013-01-25 18:36:16 +04:00
$condition = "$var <= {$p['to']}";
2014-05-06 14:22:58 +04:00
if ($p["last"]) {
$c = "($var + {$p['step']}) > {$p['to']}";
}
2013-07-29 14:58:14 +04:00
} elseif ($p["step"] < 0) {
2013-01-25 18:36:16 +04:00
$condition = "$var >= {$p['to']}";
2014-05-06 14:22:58 +04:00
if ($p["last"]) {
$c = "($var + {$p['step']}) < {$p['to']}";
}
2013-01-25 18:36:16 +04:00
} else {
2014-04-09 18:03:49 +04:00
throw new InvalidUsageException("Invalid step value");
2013-01-25 18:36:16 +04:00
}
} else {
$condition = "({$p['step']} > 0 && $var <= {$p['to']} || {$p['step']} < 0 && $var >= {$p['to']})";
2014-05-06 14:22:58 +04:00
if ($p["last"]) {
$c = "({$p['step']} > 0 && ($var + {$p['step']}) <= {$p['to']} || {$p['step']} < 0 && ($var + {$p['step']}) >= {$p['to']})";
}
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
if ($p["first"]) {
2014-05-06 14:22:58 +04:00
$before[] = $p["first"] . ' = true';
2013-07-29 14:58:14 +04:00
$scope["after"][] = $p["first"] . ' && (' . $p["first"] . ' = false )';
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
if ($p["last"]) {
$before[] = $p["last"] . ' = false';
2014-05-06 14:22:58 +04:00
$body[] = "if($c) {$p['last']} = true";
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
if ($p["index"]) {
$i[0] .= $p["index"] . ' = 0,';
$i[1] .= $p["index"] . '++,';
2013-01-25 18:36:16 +04:00
}
2014-05-06 14:22:58 +04:00
$scope["else"] = false;
2013-01-25 18:36:16 +04:00
$scope["else_cond"] = "$var==$val";
2014-05-06 14:22:58 +04:00
$before = $before ? implode("; ", $before) . ";" : "";
$body = $body ? implode("; ", $body) . ";" : "";
$scope["after"] = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
2013-01-25 18:36:16 +04:00
return "$before for({$i[0]} $var=$val; $condition;{$i[1]} $var+={$p['step']}) { $body";
}
/**
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-01-25 18:36:16 +04:00
* @return string
2015-02-22 23:07:26 +03:00
* @codeCoverageIgnore
2013-01-25 18:36:16 +04:00
*/
2015-01-07 15:24:57 +03:00
public static function forElse($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2013-01-25 18:36:16 +04:00
$scope["no-break"] = $scope["no-continue"] = true;
2014-05-06 14:22:58 +04:00
$scope["else"] = true;
2013-01-25 18:36:16 +04:00
return " } if({$scope['else_cond']}) {";
}
/**
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-01-25 18:36:16 +04:00
* @return string
2015-02-22 23:07:26 +03:00
* @codeCoverageIgnore
2013-01-25 18:36:16 +04:00
*/
public static function forClose($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
if ($scope["else"]) {
2013-01-25 18:36:16 +04:00
return '}';
} else {
return " {$scope['after']} }";
}
}
/**
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function whileOpen(Tokenizer $tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2013-08-11 19:55:30 +04:00
return 'while(' . $scope->tpl->parseExpr($tokens) . ') {';
2013-01-25 18:36:16 +04:00
}
/**
* Open tag {switch}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function switchOpen(Tokenizer $tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2014-05-06 14:22:58 +04:00
$expr = $scope->tpl->parseExpr($tokens);
$scope["case"] = array();
$scope["last"] = array();
2013-09-02 17:40:58 +04:00
$scope["default"] = '';
2014-05-06 14:22:58 +04:00
$scope["var"] = $scope->tpl->tmpVar();
$scope["expr"] = $scope["var"] . ' = strval(' . $expr . ')';
2013-02-20 19:51:06 +04:00
// lazy init
2013-01-25 18:36:16 +04:00
return '';
}
2013-09-02 17:40:58 +04:00
/**
* Resort cases for {switch}
* @param Tag $scope
2013-09-02 17:40:58 +04:00
*/
private static function _caseResort(Tag $scope)
2013-09-02 17:40:58 +04:00
{
$content = $scope->cutContent();
2014-05-14 17:07:48 +04:00
foreach ($scope["last"] as $case) {
if($case === false) {
$scope["default"] .= $content;
} else {
2013-09-02 17:40:58 +04:00
if (!isset($scope["case"][$case])) {
$scope["case"][$case] = "";
}
$scope["case"][$case] .= $content;
}
}
$scope["last"] = array();
}
2013-01-25 18:36:16 +04:00
/**
* Tag {case ...}
*
* @static
* @param Tokenizer $tokens
2014-05-14 17:07:48 +04:00
* @param Tag $tag
2013-01-25 18:36:16 +04:00
* @return string
*/
2014-05-14 17:07:48 +04:00
public static function tagCase(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-05-14 17:07:48 +04:00
self::_caseResort($tag);
2013-09-02 17:40:58 +04:00
do {
2014-05-14 17:07:48 +04:00
if($tokens->is(T_DEFAULT)) {
$tag["last"][] = false;
$tokens->next();
} else {
2016-02-23 13:16:48 +03:00
$tag["last"][] = $tag->tpl->parseScalar($tokens);
2014-05-14 17:07:48 +04:00
}
2013-09-02 17:40:58 +04:00
if ($tokens->is(',')) {
$tokens->next();
} else {
break;
}
} while (true);
return '';
}
/**
* Tag {default}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-09-02 17:40:58 +04:00
* @return string
*/
public static function tagDefault($tokens, Tag $scope)
2013-09-02 17:40:58 +04:00
{
self::_caseResort($scope);
2014-05-14 17:07:48 +04:00
$scope["last"][] = false;
2013-09-02 17:40:58 +04:00
return '';
2013-01-25 18:36:16 +04:00
}
/**
2013-09-02 17:40:58 +04:00
* Close tag {switch}
2013-01-25 18:36:16 +04:00
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
2013-01-25 18:36:16 +04:00
* @return string
*/
2015-01-07 15:24:57 +03:00
public static function switchClose($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2013-09-02 17:40:58 +04:00
self::_caseResort($scope);
2014-05-06 14:22:58 +04:00
$expr = $scope["var"];
$code = $scope["expr"] . ";\n";
2013-09-02 17:40:58 +04:00
$default = $scope["default"];
foreach ($scope["case"] as $case => $content) {
2014-02-27 16:30:44 +04:00
if (is_numeric($case)) {
2013-09-05 03:35:02 +04:00
$case = "'$case'";
}
2013-09-02 17:40:58 +04:00
$code .= "if($expr == $case) {\n?>$content<?php\n} else";
2013-01-25 18:36:16 +04:00
}
2014-02-27 16:30:44 +04:00
$code .= " {\n?>$default<?php\n}\nunset(" . $scope["var"] . ")";
2013-09-02 17:40:58 +04:00
return $code;
}
2013-01-25 18:36:16 +04:00
/**
2013-09-02 17:40:58 +04:00
* Tag {continue}
2013-01-25 18:36:16 +04:00
*
* @static
2013-02-20 19:51:06 +04:00
* @param Tokenizer $tokens
* @param Tag $scope
2013-09-02 17:40:58 +04:00
* @throws InvalidUsageException
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function tagContinue($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2013-09-02 17:40:58 +04:00
if (empty($scope["no-continue"])) {
return 'continue;';
} else {
throw new InvalidUsageException("Improper usage of the tag {continue}");
2013-01-25 18:36:16 +04:00
}
}
2013-01-25 18:36:16 +04:00
/**
* Tag {break}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @throws InvalidUsageException
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function tagBreak($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
if (empty($scope["no-break"])) {
2013-01-25 18:36:16 +04:00
return 'break;';
} else {
throw new InvalidUsageException("Improper usage of the tag {break}");
2013-01-25 18:36:16 +04:00
}
}
2013-01-25 18:36:16 +04:00
/**
* Dispatch {extends} tag
* @param Tokenizer $tokens
2014-04-12 01:00:58 +04:00
* @param Tag $tag
2014-02-22 20:34:53 +04:00
* @throws Error\InvalidUsageException
* @return string
*/
2014-04-12 01:00:58 +04:00
public static function tagExtends(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-04-12 01:00:58 +04:00
$tpl = $tag->tpl;
2014-02-22 20:34:53 +04:00
if ($tpl->extends) {
throw new InvalidUsageException("Only one {extends} allowed");
2013-07-29 14:58:14 +04:00
} elseif ($tpl->getStackSize()) {
2014-04-10 10:58:14 +04:00
throw new InvalidUsageException("Tag {extends} can not be nested");
2013-01-25 18:36:16 +04:00
}
2014-02-22 20:34:53 +04:00
$cname = $tpl->parsePlainArg($tokens, $name);
2014-02-27 16:30:44 +04:00
if ($name) {
2014-02-22 20:34:53 +04:00
$tpl->extends = $name;
} else {
$tpl->dynamic_extends = $cname;
2013-05-19 02:04:52 +04:00
}
2014-02-27 16:30:44 +04:00
if (!$tpl->extend_body) {
2014-02-22 20:34:53 +04:00
$tpl->addPostCompile(__CLASS__ . "::extendBody");
2014-02-26 23:57:29 +04:00
$tpl->extend_body = true;
2013-01-25 18:36:16 +04:00
}
}
/**
2013-03-04 12:40:32 +04:00
* Post compile action for {extends ...} tag
* @param Template $tpl
2014-02-22 20:34:53 +04:00
* @param string $body
*/
2014-02-22 20:34:53 +04:00
public static function extendBody($tpl, &$body)
2013-07-29 14:58:14 +04:00
{
2014-02-27 16:30:44 +04:00
if ($tpl->dynamic_extends) {
if (!$tpl->ext_stack) {
2014-02-26 23:57:29 +04:00
$tpl->ext_stack[] = $tpl->getName();
2014-02-22 20:34:53 +04:00
}
2014-02-27 16:30:44 +04:00
foreach ($tpl->ext_stack as &$t) {
2014-02-26 23:57:29 +04:00
$stack[] = "'$t'";
}
$stack[] = $tpl->dynamic_extends;
2014-05-06 14:22:58 +04:00
$body = '<?php $tpl->getStorage()->display(array(' . implode(', ', $stack) . '), $var); ?>';
2014-02-22 20:34:53 +04:00
} else {
$child = $tpl;
2014-02-27 16:30:44 +04:00
while ($child && $child->extends) {
2014-02-22 20:34:53 +04:00
$parent = $tpl->extend($child->extends);
2014-05-06 14:22:58 +04:00
$child = $parent->extends ? $parent : false;
2013-07-03 12:10:50 +04:00
}
2014-02-26 23:57:29 +04:00
$tpl->extends = false;
2013-07-03 12:10:50 +04:00
}
2014-02-26 23:57:29 +04:00
$tpl->extend_body = false;
2013-01-25 18:36:16 +04:00
}
2013-02-20 19:51:06 +04:00
/**
2013-04-28 11:33:36 +04:00
* Tag {use ...}
2013-02-20 19:51:06 +04:00
* @param Tokenizer $tokens
2014-04-12 01:00:58 +04:00
* @param Tag $tag
* @throws Error\InvalidUsageException
2013-03-04 12:40:32 +04:00
* @return string
2013-02-20 19:51:06 +04:00
*/
2014-04-12 01:00:58 +04:00
public static function tagUse(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-04-12 01:00:58 +04:00
$tpl = $tag->tpl;
2013-07-29 14:58:14 +04:00
if ($tpl->getStackSize()) {
throw new InvalidUsageException("Tag {use} can not be nested");
}
$tpl->parsePlainArg($tokens, $name);
2013-07-29 14:58:14 +04:00
if ($name) {
$tpl->importBlocks($name);
2013-03-04 12:40:32 +04:00
} else {
2014-04-09 18:03:49 +04:00
throw new InvalidUsageException('Invalid template name for tag {use}');
2013-02-20 19:51:06 +04:00
}
}
/**
* Tag {block ...}
* @param Tokenizer $tokens
* @param Tag $scope
* @throws \RuntimeException
* @return string
*/
public static function tagBlockOpen(Tokenizer $tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2013-07-03 12:10:50 +04:00
$scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name);
2014-02-27 16:30:44 +04:00
if (!$name) {
2014-04-09 18:03:49 +04:00
throw new \RuntimeException("Invalid block name");
}
2014-05-06 14:22:58 +04:00
$scope["name"] = $name;
$scope["use_parent"] = false;
2013-01-25 18:36:16 +04:00
}
/**
* @param Tokenizer $tokens
* @param Tag $scope
*/
public static function tagBlockClose($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2014-05-06 14:22:58 +04:00
$tpl = $scope->tpl;
$name = $scope["name"];
2014-02-27 16:30:44 +04:00
if (isset($tpl->blocks[$name])) { // block defined
$block = & $tpl->blocks[$name];
if ($block['use_parent']) {
$parent = $scope->getContent();
2014-02-27 16:30:44 +04:00
$block['block'] = str_replace($block['use_parent'] . " ?>", "?>" . $parent, $block['block']);
}
2014-02-27 16:30:44 +04:00
if (!$block["import"]) { // not from {use} - redefine block
$scope->replaceContent($block["block"]);
return;
2014-02-27 16:30:44 +04:00
} elseif ($block["import"] != $tpl->getName()) { // tag {use} was in another template
$tpl->blocks[$scope["name"]]["import"] = false;
$scope->replaceContent($block["block"]);
2013-07-03 12:10:50 +04:00
}
}
2014-02-22 20:34:53 +04:00
2014-02-22 20:56:21 +04:00
$tpl->blocks[$scope["name"]] = array(
2014-05-06 14:22:58 +04:00
"from" => $tpl->getName(),
"import" => false,
2014-02-27 16:30:44 +04:00
"use_parent" => $scope["use_parent"],
2014-05-06 14:22:58 +04:00
"block" => $scope->getContent()
2014-02-22 20:56:21 +04:00
);
2013-01-25 18:36:16 +04:00
}
/**
* Tag {parent}
*
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
*/
public static function tagParent($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
$block_scope = $scope->tpl->getParentScope('block');
2014-02-27 16:30:44 +04:00
if (!$block_scope['use_parent']) {
2015-01-30 16:52:58 +03:00
$block_scope['use_parent'] = "/* %%parent#{$scope['name']}%% */";
2013-02-20 19:51:06 +04:00
}
return $block_scope['use_parent'];
2013-02-20 19:51:06 +04:00
}
2013-01-25 18:36:16 +04:00
/**
* Standard close tag {/...}
*
* @return string
*/
2013-07-29 14:58:14 +04:00
public static function stdClose()
{
return '}';
}
2013-01-25 18:36:16 +04:00
/**
* Standard function parser
2013-01-25 18:36:16 +04:00
*
* @param Tokenizer $tokens
2014-04-12 01:00:58 +04:00
* @param Tag $tag
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function stdFuncParser(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-07-03 12:07:20 +04:00
if(is_string($tag->callback)) {
return $tag->out($tag->callback . "(" . self::toArray($tag->tpl->parseParams($tokens)) . ', $tpl, $var)');
2014-07-03 12:07:20 +04:00
} else {
return '$info = $tpl->getStorage()->getTag('.var_export($tag->name, true).');'.PHP_EOL.
$tag->out('call_user_func_array($info["function"], array('.self::toArray($tag->tpl->parseParams($tokens)).', $tpl, &$var))');
2014-07-03 12:07:20 +04:00
}
2013-01-25 18:36:16 +04:00
}
/**
* Smart function parser
*
* @param Tokenizer $tokens
2014-04-12 01:00:58 +04:00
* @param Tag $tag
* @return string
*/
public static function smartFuncParser(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
if (strpos($tag->callback, "::") || is_array($tag->callback)) {
list($class, $method) = explode("::", $tag->callback, 2);
$ref = new \ReflectionMethod($class, $method);
} else {
$ref = new \ReflectionFunction($tag->callback);
}
2014-05-06 14:22:58 +04:00
$args = array();
2014-04-12 01:00:58 +04:00
$params = $tag->tpl->parseParams($tokens);
2013-07-29 14:58:14 +04:00
foreach ($ref->getParameters() as $param) {
if (isset($params[$param->getName()])) {
$args[] = $params[$param->getName()];
} elseif (isset($params[$param->getPosition()])) {
$args[] = $params[$param->getPosition()];
} elseif ($param->isOptional()) {
$args[] = var_export($param->getDefaultValue(), true);
}
}
2014-05-06 00:45:37 +04:00
return $tag->out($tag->callback . "(" . implode(", ", $args) . ')');
}
2013-01-25 18:36:16 +04:00
/**
* Standard function open tag parser
*
* @param Tokenizer $tokens
* @param Tag $tag
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function stdFuncOpen(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
$tag["params"] = self::toArray($tag->tpl->parseParams($tokens));
2014-05-06 00:45:37 +04:00
$tag->setOption(\Fenom::AUTO_ESCAPE, false);
2013-01-25 18:36:16 +04:00
return 'ob_start();';
}
/**
* Standard function close tag parser
*
* @param Tokenizer $tokens
* @param Tag $tag
2013-01-25 18:36:16 +04:00
* @return string
*/
public static function stdFuncClose($tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-05-06 00:45:37 +04:00
$tag->restore(\Fenom::AUTO_ESCAPE);
2014-07-03 12:07:20 +04:00
if(is_string($tag->callback)) {
return $tag->out($tag->callback . "(" . $tag["params"] . ', ob_get_clean(), $tpl, $var)');
2014-07-03 12:07:20 +04:00
} else {
return '$info = $tpl->getStorage()->getTag('.var_export($tag->name, true).');'.PHP_EOL.
$tag->out('call_user_func_array($info["function"], array(' . $tag["params"] . ', ob_get_clean(), $tpl, &$var))');
2014-07-03 12:07:20 +04:00
}
2013-01-25 18:36:16 +04:00
}
2013-02-23 02:03:05 +04:00
/**
* Convert array of code to string array
* @param $params
* @return string
*/
2013-07-29 14:58:14 +04:00
public static function toArray($params)
{
2013-01-25 18:36:16 +04:00
$_code = array();
2013-07-29 14:58:14 +04:00
foreach ($params as $k => $v) {
$_code[] = '"' . $k . '" => ' . $v;
2013-01-25 18:36:16 +04:00
}
2013-07-29 14:58:14 +04:00
return 'array(' . implode(",", $_code) . ')';
2013-01-25 18:36:16 +04:00
}
2013-07-03 12:10:50 +04:00
/**
* @param Tokenizer $tokens
* @param Tag $scope
2013-07-03 12:10:50 +04:00
* @return string
*/
public static function setOpen(Tokenizer $tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
if($tokens->is(T_VARIABLE)) {
$var = $scope->tpl->parseVariable($tokens);
} elseif($tokens->is('$')) {
$var = $scope->tpl->parseAccessor($tokens, $is_var);
if(!$is_var) {
throw new InvalidUsageException("Accessor is not writable");
}
} else {
throw new InvalidUsageException("{set} and {add} accept only variable");
}
2014-08-06 00:08:51 +04:00
$before = $after = "";
if($scope->name == 'add') {
$before = "if(!isset($var)) {\n";
$after = "\n}";
}
2014-08-06 01:17:21 +04:00
if ($tokens->is(Tokenizer::MACRO_EQUALS, '[')) { // inline tag {var ...}
$equal = $tokens->getAndNext();
if($equal == '[') {
$tokens->need(']')->next()->need('=')->next();
$equal = '[]=';
}
$scope->close();
2013-07-29 14:58:14 +04:00
if ($tokens->is("[")) {
2014-08-06 01:17:21 +04:00
return $before.$var . $equal . $scope->tpl->parseArray($tokens) . ';'.$after;
2013-07-03 12:10:50 +04:00
} else {
2014-08-06 01:17:21 +04:00
return $before.$var . $equal . $scope->tpl->parseExpr($tokens) . ';'.$after;
2013-07-03 12:10:50 +04:00
}
} else {
$scope["name"] = $var;
2013-07-29 14:58:14 +04:00
if ($tokens->is('|')) {
2014-08-06 00:08:51 +04:00
$scope["value"] = $before . $scope->tpl->parseModifier($tokens, "ob_get_clean()").';'.$after;
2013-07-03 12:10:50 +04:00
} else {
2014-08-06 00:08:51 +04:00
$scope["value"] = $before . "ob_get_clean();" . $after;
2013-07-03 12:10:50 +04:00
}
return 'ob_start();';
}
}
/**
* @param Tokenizer $tokens
* @param Tag $scope
2013-07-03 12:10:50 +04:00
* @return string
*/
2015-01-07 15:24:57 +03:00
public static function setClose($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
return $scope["name"] . '=' . $scope["value"] . ';';
2013-03-17 14:37:23 +04:00
}
2016-05-08 18:26:01 +03:00
public static function tagDo($tokens, Tag $scope)
{
return $scope->tpl->parseExpr($tokens).';';
}
2013-01-25 18:36:16 +04:00
/**
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
*/
2015-01-07 15:24:57 +03:00
public static function filterOpen($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
$scope["filter"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()");
return "ob_start();";
}
/**
* @param $tokens
* @param Tag $scope
* @return string
*/
public static function filterClose($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
return "echo " . $scope["filter"] . ";";
2013-02-20 19:51:06 +04:00
}
/**
* Tag {cycle}
*
* @param Tokenizer $tokens
2014-04-12 01:00:58 +04:00
* @param Tag $tag
* @throws Error\InvalidUsageException
* @return string
*/
2014-04-12 01:00:58 +04:00
public static function tagCycle(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-04-12 01:00:58 +04:00
$tpl = $tag->tpl;
2013-07-29 14:58:14 +04:00
if ($tokens->is("[")) {
2013-07-03 12:10:50 +04:00
$exp = $tpl->parseArray($tokens);
} else {
2013-08-11 19:55:30 +04:00
$exp = $tpl->parseExpr($tokens);
2013-07-03 12:10:50 +04:00
}
2013-07-29 14:58:14 +04:00
if ($tokens->valid()) {
2013-02-20 19:51:06 +04:00
$p = $tpl->parseParams($tokens);
2013-07-29 14:58:14 +04:00
if (empty($p["index"])) {
throw new InvalidUsageException("Cycle may contain only index attribute");
2013-02-20 19:51:06 +04:00
} else {
2013-07-29 14:58:14 +04:00
return 'echo ' . __CLASS__ . '::cycle(' . $exp . ', ' . $p["index"] . ')';
2013-02-20 19:51:06 +04:00
}
} else {
2013-03-15 00:57:28 +04:00
$var = $tpl->tmpVar();
2013-07-29 14:58:14 +04:00
return 'echo ' . __CLASS__ . '::cycle(' . $exp . ", isset($var) ? ++$var : ($var = 0) )";
2013-02-20 19:51:06 +04:00
}
}
2013-07-03 12:10:50 +04:00
/**
* Runtime cycle callback
* @param mixed $vals
* @param $index
* @return mixed
*/
2013-07-29 14:58:14 +04:00
public static function cycle($vals, $index)
{
2013-03-15 00:57:28 +04:00
return $vals[$index % count($vals)];
}
2013-02-21 22:51:24 +04:00
/**
* Import macros from templates
*
* @param Tokenizer $tokens
2014-04-12 01:00:58 +04:00
* @param Tag $tag
* @throws Error\UnexpectedTokenException
* @throws Error\InvalidUsageException
2013-03-15 00:12:02 +04:00
* @return string
2013-02-21 22:51:24 +04:00
*/
2014-04-12 01:00:58 +04:00
public static function tagImport(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-05-06 14:22:58 +04:00
$tpl = $tag->tpl;
$import = array();
2013-07-29 14:58:14 +04:00
if ($tokens->is('[')) {
$tokens->next();
2013-07-29 14:58:14 +04:00
while ($tokens->valid()) {
if ($tokens->is(Tokenizer::MACRO_STRING)) {
$import[$tokens->current()] = true;
$tokens->next();
2013-07-29 14:58:14 +04:00
} elseif ($tokens->is(']')) {
$tokens->next();
break;
2013-07-29 14:58:14 +04:00
} elseif ($tokens->is(',')) {
2013-03-15 00:12:02 +04:00
$tokens->next();
} else {
break;
}
}
2013-07-29 14:58:14 +04:00
if ($tokens->current() != "from") {
2013-03-15 00:12:02 +04:00
throw new UnexpectedTokenException($tokens);
}
$tokens->next();
}
2013-04-28 11:33:36 +04:00
$tpl->parsePlainArg($tokens, $name);
2013-07-29 14:58:14 +04:00
if (!$name) {
2014-04-09 18:03:49 +04:00
throw new InvalidUsageException("Invalid template name");
2013-02-21 22:51:24 +04:00
}
2013-07-29 14:58:14 +04:00
if ($tokens->is(T_AS)) {
2013-02-23 13:29:20 +04:00
$alias = $tokens->next()->get(Tokenizer::MACRO_STRING);
2013-07-29 14:58:14 +04:00
if ($alias === "macro") {
$alias = "";
}
2013-02-23 13:29:20 +04:00
$tokens->next();
} else {
$alias = "";
}
2013-02-21 22:51:24 +04:00
$donor = $tpl->getStorage()->getRawTemplate()->load($name, true);
2013-07-29 14:58:14 +04:00
if ($donor->macros) {
foreach ($donor->macros as $name => $macro) {
if ($p = strpos($name, ".")) {
2013-02-23 13:29:20 +04:00
$name = substr($name, $p);
}
2013-07-29 14:58:14 +04:00
if ($import && !isset($import[$name])) {
2013-03-15 00:12:02 +04:00
continue;
}
2013-07-29 14:58:14 +04:00
if ($alias) {
$name = $alias . '.' . $name;
2013-02-23 13:29:20 +04:00
}
$tpl->macros[$name] = $macro;
}
2013-02-21 22:51:24 +04:00
$tpl->addDepend($donor);
}
return '';
2013-02-21 22:51:24 +04:00
}
/**
* Define macro
2013-02-21 22:51:24 +04:00
*
* @param Tokenizer $tokens
* @param Tag $scope
* @throws InvalidUsageException
2013-02-21 22:51:24 +04:00
*/
public static function macroOpen(Tokenizer $tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
2014-05-06 14:22:58 +04:00
$scope["name"] = $tokens->get(Tokenizer::MACRO_STRING);
2013-08-05 13:07:16 +04:00
$scope["recursive"] = false;
2014-05-06 14:22:58 +04:00
$args = array();
$defaults = array();
2013-07-29 14:58:14 +04:00
if (!$tokens->valid()) {
2013-02-23 02:03:05 +04:00
return;
}
$tokens->next();
2016-06-09 13:44:43 +03:00
if ($tokens->is('(') || !$tokens->isNext(')')) {
$tokens->next();
while ($tokens->is(Tokenizer::MACRO_STRING, T_VARIABLE)) {
$param = $tokens->current();
if ($tokens->is(T_VARIABLE)) {
$param = ltrim($param, '$');
}
2013-02-23 13:29:20 +04:00
$tokens->next();
$args[] = $param;
if ($tokens->is('=')) {
$tokens->next();
if ($tokens->is(T_CONSTANT_ENCAPSED_STRING, T_LNUMBER, T_DNUMBER) || $tokens->isSpecialVal()) {
$defaults[$param] = $tokens->getAndNext();
} else {
throw new InvalidUsageException("Macro parameters may have only scalar defaults");
}
2013-02-23 02:03:05 +04:00
}
$tokens->skipIf(',');
2013-02-23 02:03:05 +04:00
}
$tokens->skipIf(')');
2013-02-21 22:51:24 +04:00
}
2013-07-29 14:53:21 +04:00
$scope["macro"] = array(
2014-05-06 14:22:58 +04:00
"name" => $scope["name"],
"args" => $args,
"defaults" => $defaults,
"body" => "",
2013-08-05 13:07:16 +04:00
"recursive" => false
2013-07-29 14:53:21 +04:00
);
2013-02-23 02:03:05 +04:00
return;
2013-02-21 22:51:24 +04:00
}
2013-02-23 02:03:05 +04:00
/**
* @param Tokenizer $tokens
* @param Tag $scope
2013-02-23 02:03:05 +04:00
*/
2015-01-07 15:24:57 +03:00
public static function macroClose($tokens, Tag $scope)
2013-07-29 14:58:14 +04:00
{
if ($scope["recursive"]) {
2013-08-05 13:07:16 +04:00
$scope["macro"]["recursive"] = true;
2013-07-29 14:53:21 +04:00
}
2014-05-06 14:22:58 +04:00
$scope["macro"]["body"] = $scope->cutContent();
2013-07-29 14:58:14 +04:00
$scope->tpl->macros[$scope["name"]] = $scope["macro"];
2013-02-21 22:51:24 +04:00
}
/**
* Output value as is, without escaping
*
* @param Tokenizer $tokens
2014-04-12 01:00:58 +04:00
* @param Tag $tag
* @return string
*/
2014-04-12 01:00:58 +04:00
public static function tagRaw(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-04-17 23:27:59 +04:00
return 'echo ' . $tag->tpl->parseExpr($tokens);
}
/**
* @param Tokenizer $tokens
2014-05-06 00:45:37 +04:00
* @param Tag $tag
*/
2014-05-08 12:56:37 +04:00
public static function escapeOpen(Tokenizer $tokens, Tag $tag)
2013-07-29 14:58:14 +04:00
{
2014-05-06 00:45:37 +04:00
$expected = ($tokens->get(T_STRING) == "true" ? true : false);
$tokens->next();
2014-05-06 00:45:37 +04:00
$tag->setOption(\Fenom::AUTO_ESCAPE, $expected);
}
2014-05-08 12:56:37 +04:00
/**
* Do nothing
*/
public static function nope()
{
}
/**
* @param Tokenizer $tokens
2014-05-06 00:45:37 +04:00
* @param Tag $tag
*/
2014-05-08 12:56:37 +04:00
public static function stripOpen(Tokenizer $tokens, Tag $tag)
2014-05-06 14:22:58 +04:00
{
2014-05-08 12:56:37 +04:00
$expected = ($tokens->get(T_STRING) == "true" ? true : false);
$tokens->next();
$tag->setOption(\Fenom::AUTO_STRIP, $expected);
2014-05-06 14:22:58 +04:00
}
/**
* Tag {ignore}
* @param Tokenizer $tokens
* @param Tag $tag
*/
2015-01-07 15:24:57 +03:00
public static function ignoreOpen($tokens, Tag $tag)
{
$tag->tpl->ignore('ignore');
}
2014-06-28 22:15:30 +04:00
/**
* Tag {unset ...}
* @param Tokenizer $tokens
* @param Tag $tag
* @return string
*/
public static function tagUnset(Tokenizer $tokens, Tag $tag)
{
2014-06-29 14:56:10 +04:00
$unset = array();
2014-06-28 22:15:30 +04:00
while($tokens->valid()) {
$unset[] = $tag->tpl->parseVariable($tokens);
}
return 'unset('.implode(", ", $unset).')';
}
2016-04-11 20:19:31 +03:00
public static function tagPaste(Tokenizer $tokens, Tag $tag)
{
$name = $tokens->get(T_CONSTANT_ENCAPSED_STRING);
$tokens->next();
if(isset($tag->tpl->blocks[$name])) {
return "?>".substr($tag->tpl->blocks[$name]["block"], 1, -1)."<?php ";
} else {
return "";
}
}
2013-01-25 18:36:16 +04:00
}