contributing.md 0000666 00000002317 13436752774 0007626 0 ustar 00 How to contribute & use the issue tracker
=========================================
The issue tracker is the preferred channel for bug reports, features requests
and submitting pull requests, but please respect the following restrictions:
* Please **do not** use the issue tracker for personal support requests (use
[Nette forum](http://forum.nette.org) or [Stack Overflow](http://stackoverflow.com)).
* Please **do not** derail or troll issues. Keep the discussion on topic and
respect the opinions of others.
* Use the GitHub **issue search** — check if the issue has already been
reported.
A good **bug report** shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Nette welcomes **pull requests**. If you'd like to contribute, please take a moment
to [read the guidelines](http://nette.org/en/contributing) in order to make
the contribution process easy and effective for everyone involved.
Thanks!
src/latte.php 0000666 00000002752 13436752774 0007211 0 ustar 00 'exceptions.php',
'Latte\\Compiler' => 'Compiler.php',
'Latte\\Engine' => 'Engine.php',
'Latte\\Helpers' => 'Helpers.php',
'Latte\\HtmlNode' => 'HtmlNode.php',
'Latte\\ILoader' => 'ILoader.php',
'Latte\\IMacro' => 'IMacro.php',
'Latte\\Loaders\\FileLoader' => 'Loaders/FileLoader.php',
'Latte\\Loaders\\StringLoader' => 'Loaders/StringLoader.php',
'Latte\\MacroNode' => 'MacroNode.php',
'Latte\\MacroTokens' => 'MacroTokens.php',
'Latte\\Macros\\BlockMacros' => 'Macros/BlockMacros.php',
'Latte\\Macros\\BlockMacrosRuntime' => 'Macros/BlockMacrosRuntime.php',
'Latte\\Macros\\CoreMacros' => 'Macros/CoreMacros.php',
'Latte\\Macros\\MacroSet' => 'Macros/MacroSet.php',
'Latte\\Object' => 'Object.php',
'Latte\\Parser' => 'Parser.php',
'Latte\\PhpWriter' => 'PhpWriter.php',
'Latte\\RegexpException' => 'exceptions.php',
'Latte\\RuntimeException' => 'exceptions.php',
'Latte\\Runtime\\CachingIterator' => 'Runtime/CachingIterator.php',
'Latte\\Runtime\\Filters' => 'Runtime/Filters.php',
'Latte\\Runtime\\Html' => 'Runtime/Html.php',
'Latte\\Runtime\\IHtmlString' => 'Runtime/IHtmlString.php',
'Latte\\Template' => 'Template.php',
'Latte\\Token' => 'Token.php',
'Latte\\TokenIterator' => 'TokenIterator.php',
'Latte\\Tokenizer' => 'Tokenizer.php',
);
if (isset($classMap[$className])) {
require __DIR__ . '/Latte/' . $classMap[$className];
}
});
src/Latte/HtmlNode.php 0000666 00000001317 13436752774 0010657 0 ustar 00 name = $name;
$this->parentNode = $parentNode;
}
}
src/Latte/Token.php 0000666 00000002375 13436752774 0010232 0 ustar 00 ? used for type HTML_TAG_BEGIN */
public $closing;
/** @var bool is tag empty {name/}? used for type MACRO_TAG */
public $empty;
}
src/Latte/Parser.php 0000666 00000027116 13436752774 0010406 0 ustar 00 array('\\{(?![\\s\'"{}])', '\\}'), // {...}
'double' => array('\\{\\{(?![\\s\'"{}])', '\\}\\}'), // {{...}}
'asp' => array('<%\s*', '\s*%>'), /* <%...%> */
'python' => array('\\{[{%]\s*', '\s*[%}]\\}'), // {% ... %} | {{ ... }}
'off' => array('[^\x00-\xFF]', ''),
);
/** @var string[] */
private $delimiters;
/** @var string source template */
private $input;
/** @var Token[] */
private $output;
/** @var int position on source template */
private $offset;
/** @var array */
private $context;
/** @var string */
private $lastHtmlTag;
/** @var string used by filter() */
private $syntaxEndTag;
/** @var int */
private $syntaxEndLevel = 0;
/** @var bool */
private $xmlMode;
/** @internal states */
const CONTEXT_HTML_TEXT = 'htmlText',
CONTEXT_CDATA = 'cdata',
CONTEXT_HTML_TAG = 'htmlTag',
CONTEXT_HTML_ATTRIBUTE = 'htmlAttribute',
CONTEXT_RAW = 'raw',
CONTEXT_HTML_COMMENT = 'htmlComment',
CONTEXT_MACRO = 'macro';
/**
* Process all {macros} and .
* @param string
* @return Token[]
*/
public function parse($input)
{
if (substr($input, 0, 3) === "\xEF\xBB\xBF") { // BOM
$input = substr($input, 3);
}
if (!preg_match('##u', $input)) {
throw new \InvalidArgumentException('Template is not valid UTF-8 stream.');
}
$input = str_replace("\r\n", "\n", $input);
$this->input = $input;
$this->output = array();
$this->offset = $tokenCount = 0;
$this->setSyntax($this->defaultSyntax);
$this->setContext(self::CONTEXT_HTML_TEXT);
$this->lastHtmlTag = $this->syntaxEndTag = NULL;
while ($this->offset < strlen($input)) {
if ($this->{"context".$this->context[0]}() === FALSE) {
break;
}
while ($tokenCount < count($this->output)) {
$this->filter($this->output[$tokenCount++]);
}
}
if ($this->context[0] === self::CONTEXT_MACRO) {
throw new CompileException('Malformed macro');
}
if ($this->offset < strlen($input)) {
$this->addToken(Token::TEXT, substr($this->input, $this->offset));
}
return $this->output;
}
/**
* Handles CONTEXT_HTML_TEXT.
*/
private function contextHtmlText()
{
$matches = $this->match('~
(?:(?<=\n|^)[ \t]*)?<(?P/?)(?P[a-z0-9:]+)| ## begin of HTML tag !--(?!>))| ## begin of HTML comment
(?P' . $this->delimiters[0] . ')
~xsi');
if (!empty($matches['htmlcomment'])) { // )| ## end of HTML comment
(?P' . $this->delimiters[0] . ')
~xsi');
if (!empty($matches['htmlcomment'])) { // -->
$this->addToken(Token::HTML_TAG_END, $matches[0]);
$this->setContext(self::CONTEXT_HTML_TEXT);
} else {
return $this->processMacro($matches);
}
}
/**
* Handles CONTEXT_RAW.
*/
private function contextRaw()
{
$matches = $this->match('~
(?P' . $this->delimiters[0] . ')
~xsi');
return $this->processMacro($matches);
}
/**
* Handles CONTEXT_MACRO.
*/
private function contextMacro()
{
$matches = $this->match('~
(?P\\*.*?\\*' . $this->delimiters[1] . '\n{0,2})|
(?P(?:
' . self::RE_STRING . '|
\{(?:' . self::RE_STRING . '|[^\'"{}])*+\}|
[^\'"{}]
)+?)
' . $this->delimiters[1] . '
(?P[ \t]*(?=\n))?
~xsiA');
if (!empty($matches['macro'])) {
$token = $this->addToken(Token::MACRO_TAG, $this->context[1][1] . $matches[0]);
list($token->name, $token->value, $token->modifiers, $token->empty) = $this->parseMacroTag($matches['macro']);
$this->context = $this->context[1][0];
} elseif (!empty($matches['comment'])) {
$this->addToken(Token::COMMENT, $this->context[1][1] . $matches[0]);
$this->context = $this->context[1][0];
} else {
throw new CompileException('Malformed macro');
}
}
private function processMacro($matches)
{
if (!empty($matches['macro'])) { // {macro} or {* *}
$this->setContext(self::CONTEXT_MACRO, array($this->context, $matches['macro']));
} else {
return FALSE;
}
}
/**
* Matches next token.
* @param string
* @return array
*/
private function match($re)
{
if (!preg_match($re, $this->input, $matches, PREG_OFFSET_CAPTURE, $this->offset)) {
if (preg_last_error()) {
throw new RegexpException(NULL, preg_last_error());
}
return array();
}
$value = substr($this->input, $this->offset, $matches[0][1] - $this->offset);
if ($value !== '') {
$this->addToken(Token::TEXT, $value);
}
$this->offset = $matches[0][1] + strlen($matches[0][0]);
foreach ($matches as $k => $v) {
$matches[$k] = $v[0];
}
return $matches;
}
/**
* @return self
*/
public function setContentType($type)
{
if (strpos($type, 'html') !== FALSE) {
$this->xmlMode = FALSE;
$this->setContext(self::CONTEXT_HTML_TEXT);
} elseif (strpos($type, 'xml') !== FALSE) {
$this->xmlMode = TRUE;
$this->setContext(self::CONTEXT_HTML_TEXT);
} else {
$this->setContext(self::CONTEXT_RAW);
}
return $this;
}
/**
* @return self
*/
public function setContext($context, $quote = NULL)
{
$this->context = array($context, $quote);
return $this;
}
/**
* Changes macro tag delimiters.
* @param string
* @return self
*/
public function setSyntax($type)
{
$type = $type ?: $this->defaultSyntax;
if (isset($this->syntaxes[$type])) {
$this->setDelimiters($this->syntaxes[$type][0], $this->syntaxes[$type][1]);
} else {
throw new \InvalidArgumentException("Unknown syntax '$type'");
}
return $this;
}
/**
* Changes macro tag delimiters.
* @param string left regular expression
* @param string right regular expression
* @return self
*/
public function setDelimiters($left, $right)
{
$this->delimiters = array($left, $right);
return $this;
}
/**
* Parses macro tag to name, arguments a modifiers parts.
* @param string {name arguments | modifiers}
* @return array
* @internal
*/
public function parseMacroTag($tag)
{
if (!preg_match('~^
(
(?P\?|/?[a-z]\w*+(?:[.:]\w+)*+(?!::|\(|\\\\))| ## ?, name, /name, but not function( or class:: or namespace\
(?P!?)(?P/?[=\~#%^&_]?) ## !expression, !=expression, ...
)(?P.*?)
(?P\|[a-z](?:'.Parser::RE_STRING.'|[^\'"])*(?/?\z)
()\z~isx', $tag, $match)) {
if (preg_last_error()) {
throw new RegexpException(NULL, preg_last_error());
}
return FALSE;
}
if ($match['name'] === '') {
$match['name'] = $match['shortname'] ?: '=';
if ($match['noescape']) {
if (!$this->shortNoEscape) {
trigger_error("The noescape shortcut {!...} is deprecated, use {...|noescape} modifier on line {$this->getLine()}.", E_USER_DEPRECATED);
}
$match['modifiers'] .= '|noescape';
}
}
return array($match['name'], trim($match['args']), $match['modifiers'], (bool) $match['empty']);
}
private function addToken($type, $text)
{
$this->output[] = $token = new Token;
$token->type = $type;
$token->text = $text;
$token->line = $this->getLine();
return $token;
}
public function getLine()
{
return substr_count($this->input, "\n", 0, max(1, $this->offset - 1)) + 1;
}
/**
* Process low-level macros.
*/
protected function filter(Token $token)
{
if ($token->type === Token::MACRO_TAG && $token->name === '/syntax') {
$this->setSyntax($this->defaultSyntax);
$token->type = Token::COMMENT;
} elseif ($token->type === Token::MACRO_TAG && $token->name === 'syntax') {
$this->setSyntax($token->value);
$token->type = Token::COMMENT;
} elseif ($token->type === Token::HTML_ATTRIBUTE && $token->name === 'n:syntax') {
$this->setSyntax($token->value);
$this->syntaxEndTag = $this->lastHtmlTag;
$this->syntaxEndLevel = 1;
$token->type = Token::COMMENT;
} elseif ($token->type === Token::HTML_TAG_BEGIN && $this->lastHtmlTag === $this->syntaxEndTag) {
$this->syntaxEndLevel++;
} elseif ($token->type === Token::HTML_TAG_END && $this->lastHtmlTag === ('/' . $this->syntaxEndTag) && --$this->syntaxEndLevel === 0) {
$this->setSyntax($this->defaultSyntax);
} elseif ($token->type === Token::MACRO_TAG && $token->name === 'contentType') {
$this->setContentType($token->value);
}
}
}
src/Latte/IMacro.php 0000666 00000001161 13436752774 0010314 0 ustar 00 1,'hr'=>1,'br'=>1,'input'=>1,'meta'=>1,'area'=>1,'embed'=>1,'keygen'=>1,'source'=>1,'base'=>1,
'col'=>1,'link'=>1,'param'=>1,'basefont'=>1,'frame'=>1,'isindex'=>1,'wbr'=>1,'command'=>1,'track'=>1
);
/**
* Checks callback.
* @return callable
*/
public static function checkCallback($callable)
{
if (!is_callable($callable, FALSE, $text)) {
throw new \InvalidArgumentException("Callback '$text' is not callable.");
}
return $callable;
}
/**
* Removes unnecessary blocks of PHP code.
* @param string
* @return string
*/
public static function optimizePhp($source, $lineLength = 80)
{
$res = $php = '';
$lastChar = ';';
$tokens = new \ArrayIterator(token_get_all($source));
foreach ($tokens as $n => $token) {
if (is_array($token)) {
if ($token[0] === T_INLINE_HTML) {
$lastChar = '';
$res .= $token[1];
} elseif ($token[0] === T_CLOSE_TAG) {
$next = isset($tokens[$n + 1]) ? $tokens[$n + 1] : NULL;
if (substr($res, -1) !== '<' && preg_match('#^<\?php\s*\z#', $php)) {
$php = ''; // removes empty (?php ?), but retains ((?php ?)?php
} elseif (is_array($next) && $next[0] === T_OPEN_TAG && (!isset($tokens[$n + 2][1]) || $tokens[$n + 2][1] !== 'xml')) { // remove ?)(?php
if (!strspn($lastChar, ';{}:/')) {
$php .= $lastChar = ';';
}
if (substr($next[1], -1) === "\n") {
$php .= "\n";
}
$tokens->next();
} elseif ($next) {
$res .= preg_replace('#;?(\s)*\z#', '$1', $php) . $token[1]; // remove last semicolon before ?)
if (strlen($res) - strrpos($res, "\n") > $lineLength
&& (!is_array($next) || strpos($next[1], "\n") === FALSE)
) {
$res .= "\n";
}
$php = '';
} else { // remove last ?)
if (!strspn($lastChar, '};')) {
$php .= ';';
}
}
} elseif ($token[0] === T_ELSE || $token[0] === T_ELSEIF) {
if ($tokens[$n + 1] === ':' && $lastChar === '}') {
$php .= ';'; // semicolon needed in if(): ... if() ... else:
}
$lastChar = '';
$php .= $token[1];
} elseif ($token[0] === T_OPEN_TAG && $token[1] === '' && isset($tokens[$n+1][1]) && $tokens[$n+1][1] === 'xml') {
$lastChar = '';
$res .= '<?';
for ($tokens->next(); $tokens->valid(); $tokens->next()) {
$token = $tokens->current();
$res .= is_array($token) ? $token[1] : $token;
if ($token[0] === T_CLOSE_TAG) {
break;
}
}
} else {
if (!in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG), TRUE)) {
$lastChar = '';
}
$php .= $token[1];
}
} else {
$php .= $lastChar = $token;
}
}
return $res . $php;
}
}
src/Latte/PhpWriter.php 0000666 00000024442 13436752774 0011075 0 ustar 00 tokenizer, $node->modifiers, $compiler);
}
public function __construct(MacroTokens $tokens, $modifiers = NULL, Compiler $compiler = NULL)
{
$this->tokens = $tokens;
$this->modifiers = $modifiers;
$this->compiler = $compiler;
}
/**
* Expands %node.word, %node.array, %node.args, %escape(), %modify(), %var, %raw, %word in code.
* @param string
* @return string
*/
public function write($mask)
{
$mask = preg_replace('#%(node|\d+)\.#', '%$1_', $mask);
$me = $this;
$mask = preg_replace_callback('#%escape(\(([^()]*+|(?1))+\))#', function($m) use ($me) {
return $me->escapeFilter(new MacroTokens(substr($m[1], 1, -1)))->joinAll();
}, $mask);
$mask = preg_replace_callback('#%modify(\(([^()]*+|(?1))+\))#', function($m) use ($me) {
return $me->formatModifiers(substr($m[1], 1, -1));
}, $mask);
$args = func_get_args();
$pos = $this->tokens->position;
$word = strpos($mask, '%node_word') === FALSE ? NULL : $this->tokens->fetchWord();
$code = preg_replace_callback('#([,+]\s*)?%(node_|\d+_|)(word|var|raw|array|args)(\?)?(\s*\+\s*)?()#',
function($m) use ($me, $word, & $args) {
list(, $l, $source, $format, $cond, $r) = $m;
switch ($source) {
case 'node_':
$arg = $word; break;
case '':
$arg = next($args); break;
default:
$arg = $args[$source + 1]; break;
}
switch ($format) {
case 'word':
$code = $me->formatWord($arg); break;
case 'args':
$code = $me->formatArgs(); break;
case 'array':
$code = $me->formatArray();
$code = $cond && $code === 'array()' ? '' : $code; break;
case 'var':
$code = var_export($arg, TRUE); break;
case 'raw':
$code = (string) $arg; break;
}
if ($cond && $code === '') {
return $r ? $l : $r;
} else {
return $l . $code . $r;
}
}, $mask);
$this->tokens->position = $pos;
return $code;
}
/**
* Formats modifiers calling.
* @param string
* @return string
*/
public function formatModifiers($var)
{
$tokens = new MacroTokens(ltrim($this->modifiers, '|'));
$tokens = $this->preprocess($tokens);
$tokens = $this->modifiersFilter($tokens, $var);
$tokens = $this->quoteFilter($tokens);
return $tokens->joinAll();
}
/**
* Formats macro arguments to PHP code. (It advances tokenizer to the end as a side effect.)
* @return string
*/
public function formatArgs(MacroTokens $tokens = NULL)
{
$tokens = $this->preprocess($tokens);
$tokens = $this->quoteFilter($tokens);
return $tokens->joinAll();
}
/**
* Formats macro arguments to PHP array. (It advances tokenizer to the end as a side effect.)
* @return string
*/
public function formatArray(MacroTokens $tokens = NULL)
{
$tokens = $this->preprocess($tokens);
$tokens = $this->expandFilter($tokens);
$tokens = $this->quoteFilter($tokens);
return $tokens->joinAll();
}
/**
* Formats parameter to PHP string.
* @param string
* @return string
*/
public function formatWord($s)
{
return (is_numeric($s) || preg_match('#^\$|[\'"]|^true\z|^false\z|^null\z#i', $s))
? $this->formatArgs(new MacroTokens($s))
: '"' . $s . '"';
}
/**
* Preprocessor for tokens. (It advances tokenizer to the end as a side effect.)
* @return MacroTokens
*/
public function preprocess(MacroTokens $tokens = NULL)
{
$tokens = $tokens === NULL ? $this->tokens : $tokens;
$tokens = $this->removeCommentsFilter($tokens);
$tokens = $this->shortTernaryFilter($tokens);
$tokens = $this->shortArraysFilter($tokens);
return $tokens;
}
/**
* Removes PHP comments.
* @return MacroTokens
*/
public function removeCommentsFilter(MacroTokens $tokens)
{
$res = new MacroTokens;
while ($tokens->nextToken()) {
if (!$tokens->isCurrent(MacroTokens::T_COMMENT)) {
$res->append($tokens->currentToken());
}
}
return $res;
}
/**
* Simplified ternary expressions without third part.
* @return MacroTokens
*/
public function shortTernaryFilter(MacroTokens $tokens)
{
$res = new MacroTokens;
$inTernary = array();
while ($tokens->nextToken()) {
if ($tokens->isCurrent('?')) {
$inTernary[] = $tokens->depth;
} elseif ($tokens->isCurrent(':')) {
array_pop($inTernary);
} elseif ($tokens->isCurrent(',', ')', ']') && end($inTernary) === $tokens->depth + !$tokens->isCurrent(',')) {
$res->append(' : NULL');
array_pop($inTernary);
}
$res->append($tokens->currentToken());
}
if ($inTernary) {
$res->append(' : NULL');
}
return $res;
}
/**
* Simplified array syntax [...]
* @return MacroTokens
*/
public function shortArraysFilter(MacroTokens $tokens)
{
$res = new MacroTokens;
$arrays = array();
while ($tokens->nextToken()) {
if ($tokens->isCurrent('[')) {
if ($arrays[] = !$tokens->isPrev(']', ')', MacroTokens::T_SYMBOL, MacroTokens::T_VARIABLE, MacroTokens::T_KEYWORD)) {
$res->append('array(');
continue;
}
} elseif ($tokens->isCurrent(']')) {
if (array_pop($arrays) === TRUE) {
$res->append(')');
continue;
}
}
$res->append($tokens->currentToken());
}
return $res;
}
/**
* Pseudocast (expand).
* @return MacroTokens
*/
public function expandFilter(MacroTokens $tokens)
{
$res = new MacroTokens('array(');
$expand = NULL;
while ($tokens->nextToken()) {
if ($tokens->isCurrent('(expand)') && $tokens->depth === 0) {
$expand = TRUE;
$res->append('),');
} elseif ($expand && $tokens->isCurrent(',') && !$tokens->depth) {
$expand = FALSE;
$res->append(', array(');
} else {
$res->append($tokens->currentToken());
}
}
if ($expand !== NULL) {
$res->prepend('array_merge(')->append($expand ? ', array()' : ')');
}
return $res->append(')');
}
/**
* Quotes symbols to strings.
* @return MacroTokens
*/
public function quoteFilter(MacroTokens $tokens)
{
$res = new MacroTokens;
while ($tokens->nextToken()) {
$res->append($tokens->isCurrent(MacroTokens::T_SYMBOL)
&& (!$tokens->isPrev() || $tokens->isPrev(',', '(', '[', '=>', ':', '?', '.', '<', '>', '<=', '>=', '===', '!==', '==', '!=', '<>', '&&', '||', '=', 'and', 'or', 'xor'))
&& (!$tokens->isNext() || $tokens->isNext(',', ';', ')', ']', '=>', ':', '?', '.', '<', '>', '<=', '>=', '===', '!==', '==', '!=', '<>', '&&', '||', 'and', 'or', 'xor'))
? "'" . $tokens->currentValue() . "'"
: $tokens->currentToken()
);
}
return $res;
}
/**
* Formats modifiers calling.
* @param MacroTokens
* @param string
* @throws CompileException
* @return MacroTokens
*/
public function modifiersFilter(MacroTokens $tokens, $var)
{
$inside = FALSE;
$res = new MacroTokens($var);
while ($tokens->nextToken()) {
if ($tokens->isCurrent(MacroTokens::T_WHITESPACE)) {
$res->append(' ');
} elseif ($inside) {
if ($tokens->isCurrent(':', ',')) {
$res->append(', ');
$tokens->nextAll(MacroTokens::T_WHITESPACE);
} elseif ($tokens->isCurrent('|')) {
$res->append(')');
$inside = FALSE;
} else {
$res->append($tokens->currentToken());
}
} else {
if ($tokens->isCurrent(MacroTokens::T_SYMBOL)) {
if ($this->compiler && $tokens->isCurrent('escape')) {
$res = $this->escapeFilter($res);
$tokens->nextToken('|');
} elseif (!strcasecmp($tokens->currentValue(), 'safeurl')) {
$res->prepend('Latte\Runtime\Filters::safeUrl(');
$inside = TRUE;
} else {
$res->prepend('$template->' . $tokens->currentValue() . '(');
$inside = TRUE;
}
} else {
throw new CompileException("Modifier name must be alphanumeric string, '{$tokens->currentValue()}' given.");
}
}
}
if ($inside) {
$res->append(')');
}
return $res;
}
/**
* Escapes expression in tokens.
* @return MacroTokens
*/
public function escapeFilter(MacroTokens $tokens)
{
$tokens = clone $tokens;
switch ($this->compiler->getContentType()) {
case Compiler::CONTENT_XHTML:
case Compiler::CONTENT_HTML:
$context = $this->compiler->getContext();
switch ($context[0]) {
case Compiler::CONTEXT_SINGLE_QUOTED_ATTR:
case Compiler::CONTEXT_DOUBLE_QUOTED_ATTR:
case Compiler::CONTEXT_UNQUOTED_ATTR:
if ($context[1] === Compiler::CONTENT_JS) {
$tokens->prepend('Latte\Runtime\Filters::escapeJs(')->append(')');
} elseif ($context[1] === Compiler::CONTENT_CSS) {
$tokens->prepend('Latte\Runtime\Filters::escapeCss(')->append(')');
}
$tokens->prepend('Latte\Runtime\Filters::escapeHtml(')->append($context[0] === Compiler::CONTEXT_SINGLE_QUOTED_ATTR ? ', ENT_QUOTES)' : ', ENT_COMPAT)');
if ($context[0] === Compiler::CONTEXT_UNQUOTED_ATTR) {
$tokens->prepend("'\"' . ")->append(" . '\"'");
}
return $tokens;
case Compiler::CONTEXT_COMMENT:
return $tokens->prepend('Latte\Runtime\Filters::escapeHtmlComment(')->append(')');
case Compiler::CONTENT_JS:
case Compiler::CONTENT_CSS:
return $tokens->prepend('Latte\Runtime\Filters::escape' . ucfirst($context[0]) . '(')->append(')');
default:
return $tokens->prepend('Latte\Runtime\Filters::escapeHtml(')->append(', ENT_NOQUOTES)');
}
case Compiler::CONTENT_XML:
$context = $this->compiler->getContext();
switch ($context[0]) {
case Compiler::CONTEXT_COMMENT:
return $tokens->prepend('Latte\Runtime\Filters::escapeHtmlComment(')->append(')');
default:
$tokens->prepend('Latte\Runtime\Filters::escapeXml(')->append(')');
if ($context[0] === Compiler::CONTEXT_UNQUOTED_ATTR) {
$tokens->prepend("'\"' . ")->append(" . '\"'");
}
return $tokens;
}
case Compiler::CONTENT_JS:
case Compiler::CONTENT_CSS:
case Compiler::CONTENT_ICAL:
return $tokens->prepend('Latte\Runtime\Filters::escape' . ucfirst($this->compiler->getContentType()) . '(')->append(')');
case Compiler::CONTENT_TEXT:
return $tokens;
default:
return $tokens->prepend('$template->escape(')->append(')');
}
}
}
src/Latte/exceptions.php 0000666 00000003064 13436752774 0011327 0 ustar 00 sourceCode = (string) $code;
$this->sourceLine = (int) $line;
$this->sourceName = (string) $name;
if (@is_file($name)) { // @ - may trigger error
$this->message = rtrim($this->message, '.')
. ' in ' . str_replace(dirname(dirname($name)), '...', $name) . ($line ? ":$line" : '');
}
return $this;
}
}
/**
* The exception that indicates error of the last Regexp execution.
*/
class RegexpException extends \Exception
{
static public $messages = array(
PREG_INTERNAL_ERROR => 'Internal error',
PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
5 => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point', // PREG_BAD_UTF8_OFFSET_ERROR
);
public function __construct($message, $code = NULL)
{
parent::__construct($message ?: (isset(self::$messages[$code]) ? self::$messages[$code] : 'Unknown error'), $code);
}
}
/**
* The exception that indicates error during rendering template.
*/
class RuntimeException extends \Exception
{
}
src/Latte/MacroTokens.php 0000666 00000006156 13436752774 0011400 0 ustar 00 parse($input));
$this->ignored = array(self::T_COMMENT, self::T_WHITESPACE);
}
public function parse($s)
{
self::$tokenizer = self::$tokenizer ?: new Tokenizer(array(
self::T_WHITESPACE => '\s+',
self::T_COMMENT => '(?s)/\*.*?\*/',
self::T_STRING => Parser::RE_STRING,
self::T_KEYWORD => '(?:true|false|null|and|or|xor|clone|new|instanceof|return|continue|break|[A-Z_][A-Z0-9_]{2,})(?![\w\pL_])', // keyword or const
self::T_CAST => '\((?:expand|string|array|int|integer|float|bool|boolean|object)\)', // type casting
self::T_VARIABLE => '\$[\w\pL_]+',
self::T_NUMBER => '[+-]?[0-9]+(?:\.[0-9]+)?(?:e[0-9]+)?',
self::T_SYMBOL => '[\w\pL_]+(?:-[\w\pL_]+)*',
self::T_CHAR => '::|=>|->|\+\+|--|<<|>>|<=|>=|===|!==|==|!=|<>|&&|\|\||[^"\']', // =>, any char except quotes
), 'u');
return self::$tokenizer->tokenize($s);
}
/**
* Appends simple token or string (will be parsed).
* @return MacroTokens
*/
public function append($val, $position = NULL)
{
if ($val != NULL) { // intentionally @
array_splice(
$this->tokens,
$position === NULL ? count($this->tokens) : $position, 0,
is_array($val) ? array($val) : $this->parse($val)
);
}
return $this;
}
/**
* Prepends simple token or string (will be parsed).
* @return MacroTokens
*/
public function prepend($val)
{
if ($val != NULL) { // intentionally @
array_splice($this->tokens, 0, 0, is_array($val) ? array($val) : $this->parse($val));
}
return $this;
}
/**
* Reads single token (optionally delimited by comma) from string.
* @param string
* @return string
*/
public function fetchWord()
{
$words = $this->fetchWords();
return $words ? implode(':', $words) : FALSE;
}
/**
* Reads single tokens delimited by colon from string.
* @param string
* @return array
*/
public function fetchWords()
{
do {
$words[] = $this->joinUntil(self::T_WHITESPACE, ',', ':');
} while ($this->nextToken(':'));
if (count($words) === 1 && ($space = $this->nextValue(self::T_WHITESPACE))
&& (($dot = $this->nextValue('.')) || $this->isPrev('.')))
{
$words[0] .= $space . $dot . $this->joinUntil(',');
}
$this->nextToken(',');
$this->nextAll(self::T_WHITESPACE, self::T_COMMENT);
return $words === array('') ? array() : $words;
}
public function reset()
{
$this->depth = 0;
return parent::reset();
}
protected function next()
{
parent::next();
if ($this->isCurrent('[', '(', '{')) {
$this->depth++;
} elseif ($this->isCurrent(']', ')', '}')) {
$this->depth--;
}
}
}
src/Latte/Loaders/StringLoader.php 0000666 00000001251 13436752774 0013130 0 ustar 00 isExpired($file, time())) {
touch($file);
}
return file_get_contents($file);
}
/**
* @return bool
*/
public function isExpired($file, $time)
{
return @filemtime($file) > $time; // @ - stat may fail
}
/**
* Returns fully qualified template name.
* @return string
*/
public function getChildName($file, $parent = NULL)
{
if ($parent && !preg_match('#/|\\\\|[a-z][a-z0-9+.-]*:#iA', $file)) {
$file = dirname($parent) . '/' . $file;
}
return $file;
}
}
src/Latte/Object.php 0000666 00000002437 13436752774 0010357 0 ustar 00 IMacro[]] */
private $macros;
/** @var \SplObjectStorage */
private $macroHandlers;
/** @var HtmlNode */
private $htmlNode;
/** @var MacroNode */
private $macroNode;
/** @var string[] */
private $attrCodes = array();
/** @var string */
private $contentType;
/** @var array [context, subcontext] */
private $context;
/** @var string */
private $templateId;
/** @var mixed */
private $lastAttrValue;
/** Context-aware escaping content types */
const CONTENT_HTML = Engine::CONTENT_HTML,
CONTENT_XHTML = Engine::CONTENT_XHTML,
CONTENT_XML = Engine::CONTENT_XML,
CONTENT_JS = Engine::CONTENT_JS,
CONTENT_CSS = Engine::CONTENT_CSS,
CONTENT_URL = Engine::CONTENT_URL,
CONTENT_ICAL = Engine::CONTENT_ICAL,
CONTENT_TEXT = Engine::CONTENT_TEXT;
/** @internal Context-aware escaping HTML contexts */
const CONTEXT_COMMENT = 'comment',
CONTEXT_SINGLE_QUOTED_ATTR = "'",
CONTEXT_DOUBLE_QUOTED_ATTR = '"',
CONTEXT_UNQUOTED_ATTR = '=';
public function __construct()
{
$this->macroHandlers = new \SplObjectStorage;
}
/**
* Adds new macro.
* @param string
* @return self
*/
public function addMacro($name, IMacro $macro)
{
$this->macros[$name][] = $macro;
$this->macroHandlers->attach($macro);
return $this;
}
/**
* Compiles tokens to PHP code.
* @param Token[]
* @return string
*/
public function compile(array $tokens, $className)
{
$this->templateId = substr(md5($className), 0, 10);
$this->tokens = $tokens;
$output = '';
$this->output = & $output;
$this->htmlNode = $this->macroNode = $this->context = NULL;
foreach ($this->macroHandlers as $handler) {
$handler->initialize($this);
}
foreach ($tokens as $this->position => $token) {
$this->{"process$token->type"}($token);
}
while ($this->htmlNode) {
if (!empty($this->htmlNode->macroAttrs)) {
throw new CompileException('Missing ' . self::printEndTag($this->macroNode));
}
$this->htmlNode = $this->htmlNode->parentNode;
}
$prologs = $epilogs = '';
foreach ($this->macroHandlers as $handler) {
$res = $handler->finalize();
$handlerName = get_class($handler);
$prologs .= empty($res[0]) ? '' : "";
$epilogs = (empty($res[1]) ? '' : "") . $epilogs;
}
$output = ($prologs ? $prologs . "\n" : '') . $output . $epilogs;
if ($this->macroNode) {
throw new CompileException('Missing ' . self::printEndTag($this->macroNode));
}
$output = $this->expandTokens($output);
$output = "params as $__k => $__v) $$__k = $__v; unset($__k, $__v);'
. '?>' . $output . "contentType = $type;
$this->context = NULL;
return $this;
}
/**
* @return string
*/
public function getContentType()
{
return $this->contentType;
}
/**
* @return self
*/
public function setContext($context, $sub = NULL)
{
$this->context = array($context, $sub);
return $this;
}
/**
* @return array [context, subcontext]
*/
public function getContext()
{
return $this->context;
}
/**
* @return string
*/
public function getTemplateId()
{
return $this->templateId;
}
/**
* @return MacroNode|NULL
*/
public function getMacroNode()
{
return $this->macroNode;
}
/**
* Returns current line number.
* @return int
*/
public function getLine()
{
return $this->tokens ? $this->tokens[$this->position]->line : NULL;
}
/** @internal */
public function expandTokens($s)
{
return strtr($s, $this->attrCodes);
}
private function processText(Token $token)
{
if (in_array($this->context[0], array(self::CONTEXT_SINGLE_QUOTED_ATTR, self::CONTEXT_DOUBLE_QUOTED_ATTR), TRUE)) {
if ($token->text === $this->context[0]) {
$this->setContext(self::CONTEXT_UNQUOTED_ATTR);
} elseif ($this->lastAttrValue === '') {
$this->lastAttrValue = $token->text;
}
}
$this->output .= $token->text;
}
private function processMacroTag(Token $token)
{
if (in_array($this->context[0], array(self::CONTEXT_SINGLE_QUOTED_ATTR, self::CONTEXT_DOUBLE_QUOTED_ATTR, self::CONTEXT_UNQUOTED_ATTR), TRUE)) {
$this->lastAttrValue = TRUE;
}
$isRightmost = !isset($this->tokens[$this->position + 1])
|| substr($this->tokens[$this->position + 1]->text, 0, 1) === "\n";
if ($token->name[0] === '/') {
$this->closeMacro((string) substr($token->name, 1), $token->value, $token->modifiers, $isRightmost);
} else {
$this->openMacro($token->name, $token->value, $token->modifiers, $isRightmost && !$token->empty);
if ($token->empty) {
$this->closeMacro($token->name, NULL, NULL, $isRightmost);
}
}
}
private function processHtmlTagBegin(Token $token)
{
if ($token->closing) {
while ($this->htmlNode) {
if (strcasecmp($this->htmlNode->name, $token->name) === 0) {
break;
}
if ($this->htmlNode->macroAttrs) {
throw new CompileException("Unexpected $token->name>, expecting " . self::printEndTag($this->macroNode));
}
$this->htmlNode = $this->htmlNode->parentNode;
}
if (!$this->htmlNode) {
$this->htmlNode = new HtmlNode($token->name);
}
$this->htmlNode->closing = TRUE;
$this->htmlNode->offset = strlen($this->output);
$this->setContext(NULL);
} elseif ($token->text === '') {
$this->output .= $token->text;
$this->setContext(NULL);
return;
}
$htmlNode = $this->htmlNode;
$isEmpty = !$htmlNode->closing && (strpos($token->text, '/') !== FALSE || $htmlNode->isEmpty);
$end = '';
if ($isEmpty && in_array($this->contentType, array(self::CONTENT_HTML, self::CONTENT_XHTML), TRUE)) { // auto-correct
$token->text = preg_replace('#^.*>#', $htmlNode->isEmpty && $this->contentType === self::CONTENT_XHTML ? ' />' : '>', $token->text);
if (!$htmlNode->isEmpty) {
$end = "$htmlNode->name>";
}
}
if (empty($htmlNode->macroAttrs)) {
$this->output .= $token->text . $end;
} else {
$code = substr($this->output, $htmlNode->offset) . $token->text;
$this->output = substr($this->output, 0, $htmlNode->offset);
$this->writeAttrsMacro($code);
if ($isEmpty) {
$htmlNode->closing = TRUE;
$this->writeAttrsMacro($end);
}
}
if ($isEmpty) {
$htmlNode->closing = TRUE;
}
$this->setContext(NULL);
if ($htmlNode->closing) {
$this->htmlNode = $this->htmlNode->parentNode;
} elseif ((($lower = strtolower($htmlNode->name)) === 'script' || $lower === 'style')
&& (!isset($htmlNode->attrs['type']) || preg_match('#(java|j|ecma|live)script|css#i', $htmlNode->attrs['type']))
) {
$this->setContext($lower === 'script' ? self::CONTENT_JS : self::CONTENT_CSS);
}
}
private function processHtmlAttribute(Token $token)
{
if (strncmp($token->name, Parser::N_PREFIX, strlen(Parser::N_PREFIX)) === 0) {
$name = substr($token->name, strlen(Parser::N_PREFIX));
if (isset($this->htmlNode->macroAttrs[$name])) {
throw new CompileException("Found multiple attributes $token->name.");
} elseif ($this->macroNode && $this->macroNode->htmlNode === $this->htmlNode) {
throw new CompileException("n:attributes must not appear inside macro; found $token->name inside {{$this->macroNode->name}}.");
}
$this->htmlNode->macroAttrs[$name] = $token->value;
return;
}
$this->lastAttrValue = & $this->htmlNode->attrs[$token->name];
$this->output .= $token->text;
if (in_array($token->value, array(self::CONTEXT_SINGLE_QUOTED_ATTR, self::CONTEXT_DOUBLE_QUOTED_ATTR), TRUE)) {
$this->lastAttrValue = '';
$contextMain = $token->value;
} else {
$this->lastAttrValue = $token->value;
$contextMain = self::CONTEXT_UNQUOTED_ATTR;
}
$context = NULL;
if (in_array($this->contentType, array(self::CONTENT_HTML, self::CONTENT_XHTML), TRUE)) {
$lower = strtolower($token->name);
if (substr($lower, 0, 2) === 'on') {
$context = self::CONTENT_JS;
} elseif ($lower === 'style') {
$context = self::CONTENT_CSS;
} elseif (in_array($lower, array('href', 'src', 'action', 'formaction'), TRUE)
|| ($lower === 'data' && strtolower($this->htmlNode->name) === 'object')
) {
$context = self::CONTENT_URL;
}
}
$this->setContext($contextMain, $context);
}
private function processComment(Token $token)
{
$isLeftmost = trim(substr($this->output, strrpos("\n$this->output", "\n"))) === '';
if (!$isLeftmost) {
$this->output .= substr($token->text, strlen(rtrim($token->text, "\n")));
}
}
/********************* macros ****************d*g**/
/**
* Generates code for {macro ...} to the output.
* @param string
* @param string
* @param string
* @param bool
* @return MacroNode
* @internal
*/
public function openMacro($name, $args = NULL, $modifiers = NULL, $isRightmost = FALSE, $nPrefix = NULL)
{
$node = $this->expandMacro($name, $args, $modifiers, $nPrefix);
if ($node->isEmpty) {
$this->writeCode($node->openingCode, $this->output, $node->replaced, $isRightmost);
} else {
$this->macroNode = $node;
$node->saved = array(& $this->output, $isRightmost);
$this->output = & $node->content;
}
return $node;
}
/**
* Generates code for {/macro ...} to the output.
* @param string
* @param string
* @param string
* @param bool
* @return MacroNode
* @internal
*/
public function closeMacro($name, $args = NULL, $modifiers = NULL, $isRightmost = FALSE, $nPrefix = NULL)
{
$node = $this->macroNode;
if (!$node || ($node->name !== $name && '' !== $name) || $modifiers
|| ($args && $node->args && strncmp("$node->args ", "$args ", strlen($args) + 1))
|| $nPrefix !== $node->prefix
) {
$name = $nPrefix
? "{$this->htmlNode->name}> for " . Parser::N_PREFIX . implode(' and ' . Parser::N_PREFIX, array_keys($this->htmlNode->macroAttrs))
: '{/' . $name . ($args ? ' ' . $args : '') . $modifiers . '}';
throw new CompileException("Unexpected $name" . ($node ? ', expecting ' . self::printEndTag($node) : ''));
}
$this->macroNode = $node->parentNode;
if (!$node->args) {
$node->setArgs($args);
}
$isLeftmost = $node->content ? trim(substr($this->output, strrpos("\n$this->output", "\n"))) === '' : FALSE;
$node->closing = TRUE;
$node->macro->nodeClosed($node);
$this->output = & $node->saved[0];
$this->writeCode($node->openingCode, $this->output, $node->replaced, $node->saved[1]);
$this->writeCode($node->closingCode, $node->content, $node->replaced, $isRightmost, $isLeftmost);
$this->output .= $node->content;
return $node;
}
private function writeCode($code, & $output, $replaced, $isRightmost, $isLeftmost = NULL)
{
if ($isRightmost) {
$leftOfs = strrpos("\n$output", "\n");
if ($isLeftmost === NULL) {
$isLeftmost = trim(substr($output, $leftOfs)) === '';
}
if ($replaced === NULL) {
$replaced = preg_match('#<\?php.*\secho\s#As', $code);
}
if ($isLeftmost && !$replaced) {
$output = substr($output, 0, $leftOfs); // alone macro without output -> remove indentation
} elseif (substr($code, -2) === '?>') {
$code .= "\n"; // double newline to avoid newline eating by PHP
}
}
$output .= $code;
}
/**
* Generates code for macro to the output.
* @param string
* @return void
* @internal
*/
public function writeAttrsMacro($code)
{
$attrs = $this->htmlNode->macroAttrs;
$left = $right = array();
foreach ($this->macros as $name => $foo) {
$attrName = MacroNode::PREFIX_INNER . "-$name";
if (isset($attrs[$attrName])) {
if ($this->htmlNode->closing) {
$left[] = array('closeMacro', $name, '', MacroNode::PREFIX_INNER);
} else {
array_unshift($right, array('openMacro', $name, $attrs[$attrName], MacroNode::PREFIX_INNER));
}
unset($attrs[$attrName]);
}
}
foreach (array_reverse($this->macros) as $name => $foo) {
$attrName = MacroNode::PREFIX_TAG . "-$name";
if (isset($attrs[$attrName])) {
$left[] = array('openMacro', $name, $attrs[$attrName], MacroNode::PREFIX_TAG);
array_unshift($right, array('closeMacro', $name, '', MacroNode::PREFIX_TAG));
unset($attrs[$attrName]);
}
}
foreach ($this->macros as $name => $foo) {
if (isset($attrs[$name])) {
if ($this->htmlNode->closing) {
$right[] = array('closeMacro', $name, '', MacroNode::PREFIX_NONE);
} else {
array_unshift($left, array('openMacro', $name, $attrs[$name], MacroNode::PREFIX_NONE));
}
unset($attrs[$name]);
}
}
if ($attrs) {
throw new CompileException('Unknown attribute ' . Parser::N_PREFIX
. implode(' and ' . Parser::N_PREFIX, array_keys($attrs)));
}
if (!$this->htmlNode->closing) {
$this->htmlNode->attrCode = & $this->attrCodes[$uniq = ' n:' . substr(lcg_value(), 2, 10)];
$code = substr_replace($code, $uniq, strrpos($code, '/>') ?: strrpos($code, '>'), 0);
}
foreach ($left as $item) {
$node = $this->{$item[0]}($item[1], $item[2], NULL, NULL, $item[3]);
if ($node->closing || $node->isEmpty) {
$this->htmlNode->attrCode .= $node->attrCode;
if ($node->isEmpty) {
unset($this->htmlNode->macroAttrs[$node->name]);
}
}
}
$this->output .= $code;
foreach ($right as $item) {
$node = $this->{$item[0]}($item[1], $item[2], NULL, NULL, $item[3]);
if ($node->closing) {
$this->htmlNode->attrCode .= $node->attrCode;
}
}
if ($right && substr($this->output, -2) === '?>') {
$this->output .= "\n";
}
}
/**
* Expands macro and returns node & code.
* @param string
* @param string
* @param string
* @return MacroNode
* @internal
*/
public function expandMacro($name, $args, $modifiers = NULL, $nPrefix = NULL)
{
$inScript = in_array($this->context[0], array(self::CONTENT_JS, self::CONTENT_CSS), TRUE);
if (empty($this->macros[$name])) {
throw new CompileException("Unknown macro {{$name}}" . ($inScript ? ' (in JavaScript or CSS, try to put a space after bracket.)' : ''));
}
if ($this->context[1] === self::CONTENT_URL) {
$modifiers = preg_replace('#\|nosafeurl\s?(?=\||\z)#i', '', $modifiers, -1, $found);
if (!$found && !preg_match('#\|datastream(?=\s|\||\z)#i', $modifiers)) {
$modifiers .= '|safeurl';
}
}
$modifiers = preg_replace('#\|noescape\s?(?=\||\z)#i', '', $modifiers, -1, $found);
if (!$found && strpbrk($name, '=~%^&_')) {
$modifiers .= '|escape';
}
if (!$found && $inScript && $name === '=' && preg_match('#["\'] *\z#', $this->tokens[$this->position - 1]->text)) {
throw new CompileException("Do not place {$this->tokens[$this->position]->text} inside quotes.");
}
foreach (array_reverse($this->macros[$name]) as $macro) {
$node = new MacroNode($macro, $name, $args, $modifiers, $this->macroNode, $this->htmlNode, $nPrefix);
if ($macro->nodeOpened($node) !== FALSE) {
return $node;
}
}
throw new CompileException('Unknown ' . ($nPrefix
? 'attribute ' . Parser::N_PREFIX . ($nPrefix === MacroNode::PREFIX_NONE ? '' : "$nPrefix-") . $name
: 'macro {' . $name . ($args ? " $args" : '') . '}'
));
}
private static function printEndTag(MacroNode $node)
{
if ($node->prefix) {
return "{$node->htmlNode->name}> for " . Parser::N_PREFIX
. implode(' and ' . Parser::N_PREFIX, array_keys($node->htmlNode->macroAttrs));
} else {
return "{/$node->name}";
}
}
}
src/Latte/Macros/BlockMacrosRuntime.php 0000666 00000002001 13436752774 0014123 0 ustar 00 blocks[$name])) {
throw new RuntimeException("Cannot include undefined block '$name'.");
}
$block = reset($context->blocks[$name]);
$block($context, $params);
}
/**
* Calls parent block.
* @return void
*/
public static function callBlockParent(\stdClass $context, $name, array $params)
{
if (empty($context->blocks[$name]) || ($block = next($context->blocks[$name])) === FALSE) {
throw new RuntimeException("Cannot include undefined parent block '$name'.");
}
$block($context, $params);
prev($context->blocks[$name]);
}
}
src/Latte/Macros/BlockMacros.php 0000666 00000025704 13436752774 0012576 0 ustar 00 addMacro('include', array($me, 'macroInclude'));
$me->addMacro('includeblock', array($me, 'macroIncludeBlock'));
$me->addMacro('extends', array($me, 'macroExtends'));
$me->addMacro('layout', array($me, 'macroExtends'));
$me->addMacro('block', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
$me->addMacro('define', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
$me->addMacro('snippet', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
$me->addMacro('snippetArea', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
$me->addMacro('ifset', array($me, 'macroIfset'), '}');
$me->addMacro('elseifset', array($me, 'macroIfset'), '}');
}
/**
* Initializes before template parsing.
* @return void
*/
public function initialize()
{
$this->namedBlocks = array();
$this->extends = NULL;
}
/**
* Finishes template parsing.
* @return array(prolog, epilog)
*/
public function finalize()
{
// try close last block
$last = $this->getCompiler()->getMacroNode();
if ($last && $last->name === 'block') {
$this->getCompiler()->closeMacro($last->name);
}
$epilog = $prolog = array();
if ($this->namedBlocks) {
foreach ($this->namedBlocks as $name => $code) {
$func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
$snippet = $name[0] === '_';
$prolog[] = "//\n// block $name\n//\n"
. "if (!function_exists(\$_b->blocks[" . var_export($name, TRUE) . "][] = '$func')) { "
. "function $func(\$_b, \$_args) { foreach (\$_args as \$__k => \$__v) \$\$__k = \$__v"
. ($snippet ? '; $_control->redrawControl(' . var_export(substr($name, 1), TRUE) . ', FALSE)' : '')
. "\n?>$codenamedBlocks || $this->extends) {
$prolog[] = '// template extending';
$prolog[] = '$_l->extends = '
. ($this->extends ? $this->extends : 'empty($_g->extended) && isset($_control) && $_control instanceof Nette\Application\UI\Presenter ? $_control->findLayoutTemplateFile() : NULL')
. '; $_g->extended = TRUE;';
$prolog[] = 'if ($_l->extends) { ' . ($this->namedBlocks ? 'ob_start();' : 'return $template->renderChildTemplate($_l->extends, get_defined_vars());') . '}';
}
return array(implode("\n\n", $prolog), implode("\n", $epilog));
}
/********************* macros ****************d*g**/
/**
* {include #block}
*/
public function macroInclude(MacroNode $node, PhpWriter $writer)
{
$destination = $node->tokenizer->fetchWord(); // destination [,] [params]
if (!preg_match('~#|[\w-]+\z~A', $destination)) {
return FALSE;
}
$destination = ltrim($destination, '#');
$parent = $destination === 'parent';
if ($destination === 'parent' || $destination === 'this') {
for ($item = $node->parentNode; $item && $item->name !== 'block' && !isset($item->data->name); $item = $item->parentNode);
if (!$item) {
throw new CompileException("Cannot include $destination block outside of any block.");
}
$destination = $item->data->name;
}
$name = strpos($destination, '$') === FALSE ? var_export($destination, TRUE) : $destination;
if (isset($this->namedBlocks[$destination]) && !$parent) {
$cmd = "call_user_func(reset(\$_b->blocks[$name]), \$_b, %node.array? + get_defined_vars())";
} else {
$cmd = 'Latte\Macros\BlockMacrosRuntime::callBlock' . ($parent ? 'Parent' : '') . "(\$_b, $name, %node.array? + " . ($parent ? 'get_defined_vars' : '$template->getParameters') . '())';
}
if ($node->modifiers) {
return $writer->write("ob_start(); $cmd; echo %modify(ob_get_clean())");
} else {
return $writer->write($cmd);
}
}
/**
* {includeblock "file"}
*/
public function macroIncludeBlock(MacroNode $node, PhpWriter $writer)
{
return $writer->write('ob_start(); $_b->templates[%var]->renderChildTemplate(%node.word, %node.array? + get_defined_vars()); echo rtrim(ob_get_clean())',
$this->getCompiler()->getTemplateId());
}
/**
* {extends auto | none | $var | "file"}
*/
public function macroExtends(MacroNode $node, PhpWriter $writer)
{
if (!$node->args) {
throw new CompileException("Missing destination in {{$node->name}}");
}
if (!empty($node->parentNode)) {
throw new CompileException("{{$node->name}} must be placed outside any macro.");
}
if ($this->extends !== NULL) {
throw new CompileException("Multiple {{$node->name}} declarations are not allowed.");
}
if ($node->args === 'none') {
$this->extends = 'FALSE';
} elseif ($node->args === 'auto') {
$this->extends = '$_presenter->findLayoutTemplateFile()';
} else {
$this->extends = $writer->write('%node.word%node.args');
}
return;
}
/**
* {block [[#]name]}
* {snippet [name [,]] [tag]}
* {snippetArea [name]}
* {define [#]name}
*/
public function macroBlock(MacroNode $node, PhpWriter $writer)
{
$name = $node->tokenizer->fetchWord();
if ($node->name === '#') {
trigger_error('Shortcut {#block} is deprecated.', E_USER_DEPRECATED);
} elseif ($node->name === 'block' && $name === FALSE) { // anonymous block
return $node->modifiers === '' ? '' : 'ob_start()';
}
$node->data->name = $name = ltrim($name, '#');
if ($name == NULL) {
if ($node->name === 'define') {
throw new CompileException('Missing block name.');
}
} elseif (strpos($name, '$') !== FALSE) { // dynamic block/snippet
if ($node->name === 'snippet') {
for ($parent = $node->parentNode; $parent && !($parent->name === 'snippet' || $parent->name === 'snippetArea'); $parent = $parent->parentNode);
if (!$parent) {
throw new CompileException('Dynamic snippets are allowed only inside static snippet/snippetArea.');
}
$parent->data->dynamic = TRUE;
$node->data->leave = TRUE;
$node->closingCode = "dynSnippets[\$_l->dynSnippetId] = ob_get_flush() ?>";
if ($node->prefix) {
$node->attrCode = $writer->write("dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)})) . '\"' ?>");
return $writer->write('ob_start()');
}
$tag = trim($node->tokenizer->fetchWord(), '<>');
$tag = $tag ? $tag : 'div';
$node->closingCode .= "\n$tag>";
return $writer->write("?>\n<$tag id=\"dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)}) ?>\">data->leave = TRUE;
$fname = $writer->formatWord($name);
$node->closingCode = "name === 'define' ? '' : "call_user_func(reset(\$_b->blocks[$fname]), \$_b, get_defined_vars())") . " ?>";
$func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
return "\n\n//\n// block $name\n//\n"
. "if (!function_exists(\$_b->blocks[$fname]['{$this->getCompiler()->getTemplateId()}'] = '$func')) { "
. "function $func(\$_b, \$_args) { foreach (\$_args as \$__k => \$__v) \$\$__k = \$__v";
}
}
// static snippet/snippetArea
if ($node->name === 'snippet' || $node->name === 'snippetArea') {
if ($node->prefix && isset($node->htmlNode->attrs['id'])) {
throw new CompileException('Cannot combine HTML attribute id with n:snippet.');
}
$node->data->name = $name = '_' . $name;
}
if (isset($this->namedBlocks[$name])) {
throw new CompileException("Cannot redeclare static {$node->name} '$name'");
}
$prolog = $this->namedBlocks ? '' : "if (\$_l->extends) { ob_end_clean(); return \$template->renderChildTemplate(\$_l->extends, get_defined_vars()); }\n";
$this->namedBlocks[$name] = TRUE;
$include = 'call_user_func(reset($_b->blocks[%var]), $_b, ' . (($node->name === 'snippet' || $node->name === 'snippetArea') ? '$template->getParameters()' : 'get_defined_vars()') . ')';
if ($node->modifiers) {
$include = "ob_start(); $include; echo %modify(ob_get_clean())";
}
if ($node->name === 'snippet') {
if ($node->prefix) {
$node->attrCode = $writer->write('getSnippetId(%var) . \'"\' ?>', (string) substr($name, 1));
return $writer->write($prolog . $include, $name);
}
$tag = trim($node->tokenizer->fetchWord(), '<>');
$tag = $tag ? $tag : 'div';
return $writer->write("$prolog ?>\n<$tag id=\"getSnippetId(%var) ?>\">\n$tag>name === 'define') {
return $prolog;
} else { // block, snippetArea
return $writer->write($prolog . $include, $name);
}
}
/**
* {/block}
* {/snippet}
* {/snippetArea}
* {/define}
*/
public function macroBlockEnd(MacroNode $node, PhpWriter $writer)
{
if (isset($node->data->name)) { // block, snippet, define
if ($node->name === 'snippet' && $node->prefix === MacroNode::PREFIX_NONE // n:snippet -> n:inner-snippet
&& preg_match('#^.*? n:\w+>\n?#s', $node->content, $m1) && preg_match('#[ \t]*<[^<]+\z#s', $node->content, $m2)
) {
$node->openingCode = $m1[0] . $node->openingCode;
$node->content = substr($node->content, strlen($m1[0]), -strlen($m2[0]));
$node->closingCode .= $m2[0];
}
if (empty($node->data->leave)) {
if ($node->name === 'snippetArea') {
$node->content = "snippetMode = isset(\$_snippetMode) && \$_snippetMode; ?>{$node->content}snippetMode = FALSE; ?>";
}
if (!empty($node->data->dynamic)) {
$node->content .= 'dynSnippets)) return $_l->dynSnippets; ?>';
}
if ($node->name === 'snippetArea') {
$node->content .= '';
}
$this->namedBlocks[$node->data->name] = $tmp = rtrim(ltrim($node->content, "\n"), " \t");
$node->content = substr_replace($node->content, $node->openingCode . "\n", strspn($node->content, "\n"), strlen($tmp));
$node->openingCode = '';
}
} elseif ($node->modifiers) { // anonymous block with modifier
return $writer->write('echo %modify(ob_get_clean())');
}
}
/**
* {ifset #block}
* {elseifset #block}
*/
public function macroIfset(MacroNode $node, PhpWriter $writer)
{
if (!preg_match('~#|[\w-]+\z~A', $node->args)) {
return FALSE;
}
$list = array();
while (($name = $node->tokenizer->fetchWord()) !== FALSE) {
$list[] = preg_match('~#|[\w-]+\z~A', $name)
? '$_b->blocks["' . ltrim($name, '#') . '"]'
: $writer->formatArgs(new Latte\MacroTokens($name));
}
return ($node->name === 'elseifset' ? '} else' : '')
. 'if (isset(' . implode(', ', $list) . ')) {';
}
}
src/Latte/Macros/MacroSet.php 0000666 00000005261 13436752774 0012110 0 ustar 00 compiler = $compiler;
}
public function addMacro($name, $begin, $end = NULL, $attr = NULL)
{
if (!$begin && !$end && !$attr) {
throw new \InvalidArgumentException("At least one argument must be specified for macro '$name'.");
}
foreach (array($begin, $end, $attr) as $arg) {
if ($arg && !is_string($arg)) {
Latte\Helpers::checkCallback($arg);
}
}
$this->macros[$name] = array($begin, $end, $attr);
$this->compiler->addMacro($name, $this);
return $this;
}
/**
* Initializes before template parsing.
* @return void
*/
public function initialize()
{
}
/**
* Finishes template parsing.
* @return array(prolog, epilog)
*/
public function finalize()
{
}
/**
* New node is found.
* @return bool
*/
public function nodeOpened(MacroNode $node)
{
list($begin, $end, $attr) = $this->macros[$node->name];
$node->isEmpty = !$end;
if ($attr && $node->prefix === $node::PREFIX_NONE) {
$node->isEmpty = TRUE;
$this->compiler->setContext(Latte\Compiler::CONTEXT_DOUBLE_QUOTED_ATTR);
$res = $this->compile($node, $attr);
if ($res === FALSE) {
return FALSE;
} elseif (!$node->attrCode) {
$node->attrCode = "";
}
$this->compiler->setContext(NULL);
} elseif ($begin) {
$res = $this->compile($node, $begin);
if ($res === FALSE || ($node->isEmpty && $node->prefix)) {
return FALSE;
} elseif (!$node->openingCode) {
$node->openingCode = "";
}
} elseif (!$end) {
return FALSE;
}
}
/**
* Node is closed.
* @return void
*/
public function nodeClosed(MacroNode $node)
{
if (isset($this->macros[$node->name][1])) {
$res = $this->compile($node, $this->macros[$node->name][1]);
if (!$node->closingCode) {
$node->closingCode = "";
}
}
}
/**
* Generates code.
* @return string
*/
private function compile(MacroNode $node, $def)
{
$node->tokenizer->reset();
$writer = Latte\PhpWriter::using($node, $this->compiler);
if (is_string($def)) {
return $writer->write($def);
} else {
return call_user_func($def, $node, $writer);
}
}
/**
* @return Latte\Compiler
*/
public function getCompiler()
{
return $this->compiler;
}
}
src/Latte/Macros/CoreMacros.php 0000666 00000030673 13436752774 0012435 0 ustar 00 value} set template parameter
* - {default var => value} set default template parameter
* - {dump $var}
* - {debugbreak}
* - {contentType ...} HTTP Content-Type header
* - {status ...} HTTP status
* - {l} {r} to display { }
*
* @author David Grudl
*/
class CoreMacros extends MacroSet
{
public static function install(Latte\Compiler $compiler)
{
$me = new static($compiler);
$me->addMacro('if', array($me, 'macroIf'), array($me, 'macroEndIf'));
$me->addMacro('elseif', '} elseif (%node.args) {');
$me->addMacro('else', array($me, 'macroElse'));
$me->addMacro('ifset', 'if (isset(%node.args)) {', '}');
$me->addMacro('elseifset', '} elseif (isset(%node.args)) {');
$me->addMacro('ifcontent', array($me, 'macroIfContent'), array($me, 'macroEndIfContent'));
$me->addMacro('switch', '$_l->switch[] = (%node.args); if (FALSE) {', '} array_pop($_l->switch)');
$me->addMacro('case', '} elseif (end($_l->switch) === (%node.args)) {');
$me->addMacro('foreach', '', array($me, 'macroEndForeach'));
$me->addMacro('for', 'for (%node.args) {', '}');
$me->addMacro('while', 'while (%node.args) {', '}');
$me->addMacro('continueIf', array($me, 'macroBreakContinueIf'));
$me->addMacro('breakIf', array($me, 'macroBreakContinueIf'));
$me->addMacro('first', 'if ($iterator->isFirst(%node.args)) {', '}');
$me->addMacro('last', 'if ($iterator->isLast(%node.args)) {', '}');
$me->addMacro('sep', 'if (!$iterator->isLast(%node.args)) {', '}');
$me->addMacro('var', array($me, 'macroVar'));
$me->addMacro('default', array($me, 'macroVar'));
$me->addMacro('dump', array($me, 'macroDump'));
$me->addMacro('debugbreak', array($me, 'macroDebugbreak'));
$me->addMacro('l', '?>{addMacro('r', '?>}addMacro('_', array($me, 'macroTranslate'), array($me, 'macroTranslate'));
$me->addMacro('=', array($me, 'macroExpr'));
$me->addMacro('?', array($me, 'macroExpr'));
$me->addMacro('capture', array($me, 'macroCapture'), array($me, 'macroCaptureEnd'));
$me->addMacro('include', array($me, 'macroInclude'));
$me->addMacro('use', array($me, 'macroUse'));
$me->addMacro('contentType', array($me, 'macroContentType'));
$me->addMacro('status', array($me, 'macroStatus'));
$me->addMacro('php', array($me, 'macroExpr'));
$me->addMacro('class', NULL, NULL, array($me, 'macroClass'));
$me->addMacro('attr', NULL, NULL, array($me, 'macroAttr'));
}
/**
* Finishes template parsing.
* @return array(prolog, epilog)
*/
public function finalize()
{
return array('list($_b, $_g, $_l) = $template->initialize('
. var_export($this->getCompiler()->getTemplateId(), TRUE) . ', '
. var_export($this->getCompiler()->getContentType(), TRUE)
. ')');
}
/********************* macros ****************d*g**/
/**
* {if ...}
*/
public function macroIf(MacroNode $node, PhpWriter $writer)
{
if ($node->data->capture = ($node->args === '')) {
return 'ob_start()';
}
if ($node->prefix === $node::PREFIX_TAG) {
return $writer->write($node->htmlNode->closing ? 'if (array_pop($_l->ifs)) {' : 'if ($_l->ifs[] = (%node.args)) {');
}
return $writer->write('if (%node.args) {');
}
/**
* {/if ...}
*/
public function macroEndIf(MacroNode $node, PhpWriter $writer)
{
if ($node->data->capture) {
if ($node->args === '') {
throw new CompileException('Missing condition in {if} macro.');
}
return $writer->write('if (%node.args) '
. (isset($node->data->else) ? '{ ob_end_clean(); ob_end_flush(); }' : 'ob_end_flush();')
. ' else '
. (isset($node->data->else) ? '{ $_l->else = ob_get_contents(); ob_end_clean(); ob_end_clean(); echo $_l->else; }' : 'ob_end_clean();')
);
}
return '}';
}
/**
* {else}
*/
public function macroElse(MacroNode $node, PhpWriter $writer)
{
$ifNode = $node->parentNode;
if ($ifNode && $ifNode->name === 'if' && $ifNode->data->capture) {
if (isset($ifNode->data->else)) {
throw new CompileException('Macro {if} supports only one {else}.');
}
$ifNode->data->else = TRUE;
return 'ob_start()';
}
return '} else {';
}
/**
* n:ifcontent
*/
public function macroIfContent(MacroNode $node, PhpWriter $writer)
{
if (!$node->prefix) {
throw new CompileException("Unknown macro {{$node->name}}, use n:{$node->name} attribute.");
} elseif ($node->prefix !== MacroNode::PREFIX_NONE) {
throw new CompileException("Unknown attribute n:{$node->prefix}-{$node->name}, use n:{$node->name} attribute.");
}
return $writer->write('ob_start()');
}
/**
* n:ifcontent
*/
public function macroEndIfContent(MacroNode $node, PhpWriter $writer)
{
preg_match('#(^.*?>)(.*)(<.*\z)#s', $node->content, $parts);
$node->content = $parts[1]
. ''
. $parts[2]
. 'ifcontent = ob_get_contents(); ob_end_flush() ?>'
. $parts[3];
return 'rtrim($_l->ifcontent) === "" ? ob_end_clean() : ob_end_flush()';
}
/**
* {_$var |modifiers}
*/
public function macroTranslate(MacroNode $node, PhpWriter $writer)
{
if ($node->closing) {
return $writer->write('echo %modify($template->translate(ob_get_clean()))');
} elseif ($node->isEmpty = ($node->args !== '')) {
return $writer->write('echo %modify($template->translate(%node.args))');
} else {
return 'ob_start()';
}
}
/**
* {include "file" [,] [params]}
*/
public function macroInclude(MacroNode $node, PhpWriter $writer)
{
$code = $writer->write('$_b->templates[%var]->renderChildTemplate(%node.word, %node.array? + $template->getParameters())',
$this->getCompiler()->getTemplateId());
if ($node->modifiers) {
return $writer->write('ob_start(); %raw; echo %modify(ob_get_clean())', $code);
} else {
return $code;
}
}
/**
* {use class MacroSet}
*/
public function macroUse(MacroNode $node, PhpWriter $writer)
{
call_user_func(Latte\Helpers::checkCallback(array($node->tokenizer->fetchWord(), 'install')), $this->getCompiler())
->initialize();
}
/**
* {capture $variable}
*/
public function macroCapture(MacroNode $node, PhpWriter $writer)
{
$variable = $node->tokenizer->fetchWord();
if (substr($variable, 0, 1) !== '$') {
throw new CompileException("Invalid capture block variable '$variable'");
}
$node->data->variable = $variable;
return 'ob_start()';
}
/**
* {/capture}
*/
public function macroCaptureEnd(MacroNode $node, PhpWriter $writer)
{
return $node->data->variable . $writer->write(' = %modify(ob_get_clean())');
}
/**
* {foreach ...}
*/
public function macroEndForeach(MacroNode $node, PhpWriter $writer)
{
if ($node->modifiers !== '|noiterator' && preg_match('#\W(\$iterator|include|require|get_defined_vars)\W#', $this->getCompiler()->expandTokens($node->content))) {
$node->openingCode = 'its[] = new Latte\Runtime\CachingIterator('
. preg_replace('#(.*)\s+as\s+#i', '$1) as ', $writer->formatArgs(), 1) . ') { ?>';
$node->closingCode = 'its); $iterator = end($_l->its) ?>';
} else {
$node->openingCode = 'formatArgs() . ') { ?>';
$node->closingCode = '';
}
}
/**
* {breakIf ...}
* {continueIf ...}
*/
public function macroBreakContinueIf(MacroNode $node, PhpWriter $writer)
{
$cmd = str_replace('If', '', $node->name);
if ($node->parentNode && $node->parentNode->prefix === $node::PREFIX_NONE) {
return $writer->write("if (%node.args) { echo \"{$node->parentNode->htmlNode->name}>\\n\"; $cmd; }");
}
return $writer->write("if (%node.args) $cmd");
}
/**
* n:class="..."
*/
public function macroClass(MacroNode $node, PhpWriter $writer)
{
if (isset($node->htmlNode->attrs['class'])) {
throw new CompileException('It is not possible to combine class with n:class.');
}
return $writer->write('if ($_l->tmp = array_filter(%node.array)) echo \' class="\' . %escape(implode(" ", array_unique($_l->tmp))) . \'"\'');
}
/**
* n:attr="..."
*/
public function macroAttr(MacroNode $node, PhpWriter $writer)
{
return $writer->write('echo Latte\Runtime\Filters::htmlAttributes(%node.array)');
}
/**
* {dump ...}
*/
public function macroDump(MacroNode $node, PhpWriter $writer)
{
$args = $writer->formatArgs();
return 'Tracy\Debugger::barDump(' . ($node->args ? $writer->write("array(%var => $args)", $args) : 'get_defined_vars()')
. ', "Template " . str_replace(dirname(dirname($template->getName())), "\xE2\x80\xA6", $template->getName()))';
}
/**
* {debugbreak ...}
*/
public function macroDebugbreak(MacroNode $node, PhpWriter $writer)
{
return $writer->write(($node->args == NULL ? '' : 'if (!(%node.args)); else')
. 'if (function_exists("debugbreak")) debugbreak(); elseif (function_exists("xdebug_break")) xdebug_break()');
}
/**
* {var ...}
* {default ...}
*/
public function macroVar(MacroNode $node, PhpWriter $writer)
{
if ($node->args === '' && $node->parentNode && $node->parentNode->name === 'switch') {
return '} else {';
}
$var = TRUE;
$tokens = $writer->preprocess();
$res = new Latte\MacroTokens;
while ($tokens->nextToken()) {
if ($var && $tokens->isCurrent(Latte\MacroTokens::T_SYMBOL, Latte\MacroTokens::T_VARIABLE)) {
if ($node->name === 'default') {
$res->append("'" . ltrim($tokens->currentValue(), '$') . "'");
} else {
$res->append('$' . ltrim($tokens->currentValue(), '$'));
}
$var = NULL;
} elseif ($tokens->isCurrent('=', '=>') && $tokens->depth === 0) {
$res->append($node->name === 'default' ? '=>' : '=');
$var = FALSE;
} elseif ($tokens->isCurrent(',') && $tokens->depth === 0) {
if ($var === NULL) {
$res->append($node->name === 'default' ? '=>NULL' : '=NULL');
}
$res->append($node->name === 'default' ? ',' : ';');
$var = TRUE;
} elseif ($var === NULL && $node->name === 'default' && !$tokens->isCurrent(Latte\MacroTokens::T_WHITESPACE)) {
throw new CompileException("Unexpected '{$tokens->currentValue()}' in {default $node->args}");
} else {
$res->append($tokens->currentToken());
}
}
if ($var === NULL) {
$res->append($node->name === 'default' ? '=>NULL' : '=NULL');
}
$out = $writer->quoteFilter($res)->joinAll();
return $node->name === 'default' ? "extract(array($out), EXTR_SKIP)" : $out;
}
/**
* {= ...}
* {? ...}
*/
public function macroExpr(MacroNode $node, PhpWriter $writer)
{
return $writer->write(($node->name === '=' ? 'echo ' : '') . '%modify(%node.args)');
}
/**
* {contentType ...}
*/
public function macroContentType(MacroNode $node, PhpWriter $writer)
{
if (strpos($node->args, 'xhtml') !== FALSE) {
$this->getCompiler()->setContentType(Latte\Compiler::CONTENT_XHTML);
} elseif (strpos($node->args, 'html') !== FALSE) {
$this->getCompiler()->setContentType(Latte\Compiler::CONTENT_HTML);
} elseif (strpos($node->args, 'xml') !== FALSE) {
$this->getCompiler()->setContentType(Latte\Compiler::CONTENT_XML);
} elseif (strpos($node->args, 'javascript') !== FALSE) {
$this->getCompiler()->setContentType(Latte\Compiler::CONTENT_JS);
} elseif (strpos($node->args, 'css') !== FALSE) {
$this->getCompiler()->setContentType(Latte\Compiler::CONTENT_CSS);
} elseif (strpos($node->args, 'calendar') !== FALSE) {
$this->getCompiler()->setContentType(Latte\Compiler::CONTENT_ICAL);
} else {
$this->getCompiler()->setContentType(Latte\Compiler::CONTENT_TEXT);
}
// temporary solution
if (strpos($node->args, '/')) {
return $writer->write('header(%var)', "Content-Type: $node->args");
}
}
/**
* {status ...}
*/
public function macroStatus(MacroNode $node, PhpWriter $writer)
{
return $writer->write((substr($node->args, -1) === '?' ? 'if (!headers_sent()) ' : '') .
'header((isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : "HTTP/1.1") . " " . %0.var, TRUE, %0.var)', (int) $node->args
);
}
}
src/Latte/Template.php 0000666 00000006070 13436752774 0010721 0 ustar 00 setParameters($params);
$this->engine = $engine;
$this->name = $name;
}
/**
* @return Engine
*/
public function getEngine()
{
return $this->engine;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Initializes block, global & local storage in template.
* @return [\stdClass, \stdClass, \stdClass]
* @internal
*/
public function initialize($templateId, $contentType)
{
Runtime\Filters::$xhtml = (bool) preg_match('#xml|xhtml#', $contentType);
// local storage
$this->params['_l'] = new \stdClass;
// block storage
if (isset($this->params['_b'])) {
$block = $this->params['_b'];
unset($this->params['_b']);
} else {
$block = new \stdClass;
}
$block->templates[$templateId] = $this;
// global storage
if (!isset($this->params['_g'])) {
$this->params['_g'] = new \stdClass;
}
return array($block, $this->params['_g'], $this->params['_l']);
}
/**
* Renders template.
* @return void
* @internal
*/
public function renderChildTemplate($name, array $params = array())
{
$name = $this->engine->getLoader()->getChildName($name, $this->name);
$this->engine->render($name, $params);
}
/**
* Call a template run-time filter. Do not call directly.
* @param string filter name
* @param array arguments
* @return mixed
*/
public function __call($name, $args)
{
return $this->engine->invokeFilter($name, $args);
}
/********************* template parameters ****************d*g**/
/**
* Sets all parameters.
* @param array
* @return self
*/
public function setParameters(array $params)
{
$this->params = $params;
$this->params['template'] = $this;
return $this;
}
/**
* Returns array of all parameters.
* @return array
*/
public function getParameters()
{
return $this->params;
}
/**
* Sets a template parameter. Do not call directly.
* @return void
*/
public function __set($name, $value)
{
$this->params[$name] = $value;
}
/**
* Returns a template parameter. Do not call directly.
* @return mixed value
*/
public function &__get($name)
{
if (!array_key_exists($name, $this->params)) {
trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
}
return $this->params[$name];
}
/**
* Determines whether parameter is defined. Do not call directly.
* @return bool
*/
public function __isset($name)
{
return isset($this->params[$name]);
}
/**
* Removes a template parameter. Do not call directly.
* @param string name
* @return void
*/
public function __unset($name)
{
unset($this->params[$name]);
}
}
src/Latte/TokenIterator.php 0000666 00000011276 13436752774 0011744 0 ustar 00 tokens = $tokens;
}
/**
* Returns current token.
* @return array|NULL
*/
public function currentToken()
{
return isset($this->tokens[$this->position])
? $this->tokens[$this->position]
: NULL;
}
/**
* Returns current token value.
* @return string|NULL
*/
public function currentValue()
{
return isset($this->tokens[$this->position])
? $this->tokens[$this->position][Tokenizer::VALUE]
: NULL;
}
/**
* Returns next token.
* @param int|string (optional) desired token type or value
* @return array|NULL
*/
public function nextToken()
{
return $this->scan(func_get_args(), TRUE, TRUE); // onlyFirst, advance
}
/**
* Returns next token value.
* @param int|string (optional) desired token type or value
* @return string|NULL
*/
public function nextValue()
{
return $this->scan(func_get_args(), TRUE, TRUE, TRUE); // onlyFirst, advance, strings
}
/**
* Returns all next tokens.
* @param int|string (optional) desired token type or value
* @return array[]
*/
public function nextAll()
{
return $this->scan(func_get_args(), FALSE, TRUE); // advance
}
/**
* Returns all next tokens until it sees a given token type or value.
* @param int|string token type or value to stop before
* @return array[]
*/
public function nextUntil($arg)
{
return $this->scan(func_get_args(), FALSE, TRUE, FALSE, TRUE); // advance, until
}
/**
* Returns concatenation of all next token values.
* @param int|string (optional) token type or value to be joined
* @return string
*/
public function joinAll()
{
return $this->scan(func_get_args(), FALSE, TRUE, TRUE); // advance, strings
}
/**
* Returns concatenation of all next tokens until it sees a given token type or value.
* @param int|string token type or value to stop before
* @return string
*/
public function joinUntil($arg)
{
return $this->scan(func_get_args(), FALSE, TRUE, TRUE, TRUE); // advance, strings, until
}
/**
* Checks the current token.
* @param int|string token type or value
* @return bool
*/
public function isCurrent($arg)
{
if (!isset($this->tokens[$this->position])) {
return FALSE;
}
$args = func_get_args();
$token = $this->tokens[$this->position];
return in_array($token[Tokenizer::VALUE], $args, TRUE)
|| (isset($token[Tokenizer::TYPE]) && in_array($token[Tokenizer::TYPE], $args, TRUE));
}
/**
* Checks the next token existence.
* @param int|string (optional) token type or value
* @return bool
*/
public function isNext()
{
return (bool) $this->scan(func_get_args(), TRUE, FALSE); // onlyFirst
}
/**
* Checks the previous token existence.
* @param int|string (optional) token type or value
* @return bool
*/
public function isPrev()
{
return (bool) $this->scan(func_get_args(), TRUE, FALSE, FALSE, FALSE, TRUE); // onlyFirst, prev
}
/**
* @return self
*/
public function reset()
{
$this->position = -1;
return $this;
}
/**
* Moves cursor to next token.
*/
protected function next()
{
$this->position++;
}
/**
* Looks for (first) (not) wanted tokens.
* @param array of desired token types or values
* @param bool
* @param bool
* @param bool
* @param bool
* @param bool
* @return mixed
*/
protected function scan($wanted, $onlyFirst, $advance, $strings = FALSE, $until = FALSE, $prev = FALSE)
{
$res = $onlyFirst ? NULL : ($strings ? '' : array());
$pos = $this->position + ($prev ? -1 : 1);
do {
if (!isset($this->tokens[$pos])) {
if (!$wanted && $advance && !$prev && $pos <= count($this->tokens)) {
$this->next();
}
return $res;
}
$token = $this->tokens[$pos];
$type = isset($token[Tokenizer::TYPE]) ? $token[Tokenizer::TYPE] : NULL;
if (!$wanted || (in_array($token[Tokenizer::VALUE], $wanted, TRUE) || in_array($type, $wanted, TRUE)) ^ $until) {
while ($advance && !$prev && $pos > $this->position) {
$this->next();
}
if ($onlyFirst) {
return $strings ? $token[Tokenizer::VALUE] : $token;
} elseif ($strings) {
$res .= $token[Tokenizer::VALUE];
} else {
$res[] = $token;
}
} elseif ($until || !in_array($type, $this->ignored, TRUE)) {
return $res;
}
$pos += $prev ? -1 : 1;
} while (TRUE);
}
}
src/Latte/Engine.php 0000666 00000017457 13436752774 0010366 0 ustar 00 array(), // dynamic
'bytes' => 'Latte\Runtime\Filters::bytes',
'capitalize' => 'Latte\Runtime\Filters::capitalize',
'datastream' => 'Latte\Runtime\Filters::dataStream',
'date' => 'Latte\Runtime\Filters::date',
'escapecss' => 'Latte\Runtime\Filters::escapeCss',
'escapehtml' => 'Latte\Runtime\Filters::escapeHtml',
'escapehtmlcomment' => 'Latte\Runtime\Filters::escapeHtmlComment',
'escapeical' => 'Latte\Runtime\Filters::escapeICal',
'escapejs' => 'Latte\Runtime\Filters::escapeJs',
'escapeurl' => 'rawurlencode',
'escapexml' => 'Latte\Runtime\Filters::escapeXML',
'firstupper' => 'Latte\Runtime\Filters::firstUpper',
'implode' => 'implode',
'indent' => 'Latte\Runtime\Filters::indent',
'lower' => 'Latte\Runtime\Filters::lower',
'nl2br' => 'Latte\Runtime\Filters::nl2br',
'number' => 'number_format',
'repeat' => 'str_repeat',
'replace' => 'Latte\Runtime\Filters::replace',
'replacere' => 'Latte\Runtime\Filters::replaceRe',
'safeurl' => 'Latte\Runtime\Filters::safeUrl',
'strip' => 'Latte\Runtime\Filters::strip',
'striptags' => 'strip_tags',
'substr' => 'Latte\Runtime\Filters::substring',
'trim' => 'Latte\Runtime\Filters::trim',
'truncate' => 'Latte\Runtime\Filters::truncate',
'upper' => 'Latte\Runtime\Filters::upper',
);
/**
* Renders template to output.
* @return void
*/
public function render($name, array $params = array())
{
$class = $this->getTemplateClass($name);
if (!class_exists($class, FALSE)) {
$this->loadCacheFile($name);
}
$template = new $class($params, $this, $name);
$template->render();
}
/**
* Renders template to string.
* @return string
*/
public function renderToString($name, array $params = array())
{
ob_start();
try {
$this->render($name, $params);
} catch (\Exception $e) {
ob_end_clean();
throw $e;
}
return ob_get_clean();
}
/**
* Compiles template to PHP code.
* @return string
*/
public function compile($name)
{
foreach ($this->onCompile ?: array() as $cb) {
call_user_func(Helpers::checkCallback($cb), $this);
}
$this->onCompile = array();
$source = $this->getLoader()->getContent($name);
try {
$tokens = $this->getParser()->setContentType($this->contentType)
->parse($source);
$code = $this->getCompiler()->setContentType($this->contentType)
->compile($tokens, $this->getTemplateClass($name));
} catch (\Exception $e) {
if (!$e instanceof CompileException) {
$e = new CompileException("Thrown exception '{$e->getMessage()}'", NULL, $e);
}
$line = isset($tokens) ? $this->getCompiler()->getLine() : $this->getParser()->getLine();
throw $e->setSource($source, $line, $name);
}
if (!preg_match('#\n|\?#', $name)) {
$code = "" . $code;
}
$code = Helpers::optimizePhp($code);
return $code;
}
/**
* @return void
*/
private function loadCacheFile($name)
{
if (!$this->tempDirectory) {
eval('?>' . $this->compile($name));
return;
}
$file = $this->getCacheFile($name);
if (!$this->isExpired($file, $name) && (@include $file) !== FALSE) { // @ - file may not exist
return;
}
if (!is_dir($this->tempDirectory)) {
@mkdir($this->tempDirectory); // @ - directory may already exist
}
$handle = fopen("$file.lock", 'c+');
if (!$handle || !flock($handle, LOCK_EX)) {
throw new \RuntimeException("Unable to acquire exclusive lock '$file.lock'.");
}
if (!is_file($file) || $this->isExpired($file, $name)) {
$code = $this->compile($name);
if (file_put_contents("$file.tmp", $code) !== strlen($code) || !rename("$file.tmp", $file)) {
@unlink("$file.tmp"); // @ - file may not exist
throw new \RuntimeException("Unable to create '$file'.");
}
}
if ((include $file) === FALSE) {
throw new \RuntimeException("Unable to load '$file'.");
}
flock($handle, LOCK_UN);
}
/**
* @param string
* @param string
* @return bool
*/
private function isExpired($file, $name)
{
return $this->autoRefresh && $this->getLoader()->isExpired($name, (int) @filemtime($file)); // @ - file may not exist
}
/**
* @return string
*/
public function getCacheFile($name)
{
$file = $this->getTemplateClass($name);
if (preg_match('#\b\w.{10,50}$#', $name, $m)) {
$file = trim(preg_replace('#\W+#', '-', $m[0]), '-') . '-' . $file;
}
return $this->tempDirectory . '/' . $file . '.php';
}
/**
* @return string
*/
public function getTemplateClass($name)
{
return 'Template' . md5("$this->tempDirectory\00$name");
}
/**
* Registers run-time filter.
* @param string|NULL
* @param callable
* @return self
*/
public function addFilter($name, $callback)
{
if ($name == NULL) { // intentionally ==
array_unshift($this->filters[NULL], $callback);
} else {
$this->filters[strtolower($name)] = $callback;
}
return $this;
}
/**
* Returns all run-time filters.
* @return callable[]
*/
public function getFilters()
{
return $this->filters;
}
/**
* Call a run-time filter.
* @param string filter name
* @param array arguments
* @return mixed
*/
public function invokeFilter($name, array $args)
{
$lname = strtolower($name);
if (!isset($this->filters[$lname])) {
$args2 = $args;
array_unshift($args2, $lname);
foreach ($this->filters[NULL] as $filter) {
$res = call_user_func_array(Helpers::checkCallback($filter), $args2);
if ($res !== NULL) {
return $res;
} elseif (isset($this->filters[$lname])) {
return call_user_func_array(Helpers::checkCallback($this->filters[$lname]), $args);
}
}
throw new \LogicException("Filter '$name' is not defined.");
}
return call_user_func_array(Helpers::checkCallback($this->filters[$lname]), $args);
}
/**
* Adds new macro.
* @return self
*/
public function addMacro($name, IMacro $macro)
{
$this->getCompiler()->addMacro($name, $macro);
return $this;
}
/**
* @return self
*/
public function setContentType($type)
{
$this->contentType = $type;
return $this;
}
/**
* Sets path to temporary directory.
* @return self
*/
public function setTempDirectory($path)
{
$this->tempDirectory = $path;
return $this;
}
/**
* Sets auto-refresh mode.
* @return self
*/
public function setAutoRefresh($on = TRUE)
{
$this->autoRefresh = (bool) $on;
return $this;
}
/**
* @return Parser
*/
public function getParser()
{
if (!$this->parser) {
$this->parser = new Parser;
}
return $this->parser;
}
/**
* @return Compiler
*/
public function getCompiler()
{
if (!$this->compiler) {
$this->compiler = new Compiler;
Macros\CoreMacros::install($this->compiler);
Macros\BlockMacros::install($this->compiler);
}
return $this->compiler;
}
/**
* @return self
*/
public function setLoader(ILoader $loader)
{
$this->loader = $loader;
return $this;
}
/**
* @return ILoader
*/
public function getLoader()
{
if (!$this->loader) {
$this->loader = new Loaders\FileLoader;
}
return $this->loader;
}
}
src/Latte/Tokenizer.php 0000666 00000003552 13436752774 0011122 0 ustar 00 pattern]
* @param string regular expression flag
*/
public function __construct(array $patterns, $flags = '')
{
$this->re = '~(' . implode(')|(', $patterns) . ')~A' . $flags;
$this->types = array_keys($patterns);
}
/**
* Tokenizes string.
* @param string
* @return array
*/
public function tokenize($input)
{
preg_match_all($this->re, $input, $tokens, PREG_SET_ORDER);
if (preg_last_error()) {
throw new RegexpException(NULL, preg_last_error());
}
$len = 0;
$count = count($this->types);
foreach ($tokens as & $match) {
$type = NULL;
for ($i = 1; $i <= $count; $i++) {
if (!isset($match[$i])) {
break;
} elseif ($match[$i] != NULL) {
$type = $this->types[$i - 1]; break;
}
}
$match = array(self::VALUE => $match[0], self::OFFSET => $len, self::TYPE => $type);
$len += strlen($match[self::VALUE]);
}
if ($len !== strlen($input)) {
list($line, $col) = $this->getCoordinates($input, $len);
$token = str_replace("\n", '\n', substr($input, $len, 10));
throw new CompileException("Unexpected '$token' on line $line, column $col.");
}
return $tokens;
}
/**
* Returns position of token in input string.
* @param string
* @param int
* @return array of [line, column]
*/
public static function getCoordinates($text, $offset)
{
$text = substr($text, 0, $offset);
return array(substr_count($text, "\n") + 1, $offset - strrpos("\n" . $text, "\n") + 1);
}
}
src/Latte/Runtime/Html.php 0000666 00000000761 13436752774 0011476 0 ustar 00 value = (string) $value;
}
/**
* @return string
*/
public function __toString()
{
return $this->value;
}
}
src/Latte/Runtime/Filters.php 0000666 00000023261 13436752774 0012202 0 ustar 00 __toString(TRUE);
}
$s = (string) $s;
if ($quotes !== ENT_NOQUOTES && strpos($s, '`') !== FALSE && strpbrk($s, ' <>"\'') === FALSE) {
$s .= ' ';
}
return htmlSpecialChars($s, $quotes, 'UTF-8');
}
/**
* Escapes string for use inside HTML comments.
* @param string UTF-8 encoding
* @return string
*/
public static function escapeHtmlComment($s)
{
$s = (string) $s;
if ($s && ($s[0] === '-' || $s[0] === '>' || $s[0] === '!')) {
$s = ' ' . $s;
}
return str_replace('-', '- ', $s); // dash is very problematic character in comments
}
/**
* Escapes string for use inside XML 1.0 template.
* @param string UTF-8 encoding
* @return string
*/
public static function escapeXML($s)
{
// XML 1.0: \x09 \x0A \x0D and C1 allowed directly, C0 forbidden
// XML 1.1: \x00 forbidden directly and as a character reference,
// \x09 \x0A \x0D \x85 allowed directly, C0, C1 and \x7F allowed as character references
return htmlSpecialChars(preg_replace('#[\x00-\x08\x0B\x0C\x0E-\x1F]+#', '', $s), ENT_QUOTES, 'UTF-8');
}
/**
* Escapes string for use inside CSS template.
* @param string UTF-8 encoding
* @return string
*/
public static function escapeCss($s)
{
// http://www.w3.org/TR/2006/WD-CSS21-20060411/syndata.html#q6
return addcslashes($s, "\x00..\x1F!\"#$%&'()*+,./:;<=>?@[\\]^`{|}~");
}
/**
* Escapes variables for use inside = 1) {
$s = preg_replace_callback('#<(textarea|pre).*?\\1#si', function($m) {
return strtr($m[0], " \t\r\n", "\x1F\x1E\x1D\x1A");
}, $s);
if (preg_last_error()) {
throw new Latte\RegexpException(NULL, preg_last_error());
}
$s = preg_replace('#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level), $s);
$s = strtr($s, "\x1F\x1E\x1D\x1A", " \t\r\n");
}
return $s;
}
/**
* Date/time formatting.
* @param string|int|DateTime|DateInterval
* @param string
* @return string
*/
public static function date($time, $format = NULL)
{
if ($time == NULL) { // intentionally ==
return NULL;
}
if (!isset($format)) {
$format = self::$dateFormat;
}
if ($time instanceof \DateInterval) {
return $time->format($format);
} elseif (is_numeric($time)) {
$time = new \DateTime('@' . $time);
$time->setTimeZone(new \DateTimeZone(date_default_timezone_get()));
} elseif (!$time instanceof \DateTime && !$time instanceof \DateTimeInterface) {
$time = new \DateTime($time);
}
return strpos($format, '%') === FALSE
? $time->format($format) // formats using date()
: strftime($format, $time->format('U')); // formats according to locales
}
/**
* Converts to human readable file size.
* @param int
* @param int
* @return string
*/
public static function bytes($bytes, $precision = 2)
{
$bytes = round($bytes);
$units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
foreach ($units as $unit) {
if (abs($bytes) < 1024 || $unit === end($units)) {
break;
}
$bytes = $bytes / 1024;
}
return round($bytes, $precision) . ' ' . $unit;
}
/**
* Performs a search and replace.
* @param string
* @param string
* @param string
* @return string
*/
public static function replace($subject, $search, $replacement = '')
{
return str_replace($search, $replacement, $subject);
}
/**
* Perform a regular expression search and replace.
* @param string
* @param string
* @return string
*/
public static function replaceRe($subject, $pattern, $replacement = '')
{
$res = preg_replace($pattern, $replacement, $subject);
if (preg_last_error()) {
throw new Latte\RegexpException(NULL, preg_last_error());
}
return $res;
}
/**
* The data: URI generator.
* @param string
* @param string
* @return string
*/
public static function dataStream($data, $type = NULL)
{
if ($type === NULL) {
$type = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $data);
}
return 'data:' . ($type ? "$type;" : '') . 'base64,' . base64_encode($data);
}
/**
* @param string
* @return string
*/
public static function nl2br($value)
{
return nl2br($value, self::$xhtml);
}
/**
* Returns a part of UTF-8 string.
* @param string
* @param int
* @param int
* @return string
*/
public static function substring($s, $start, $length = NULL)
{
if ($length === NULL) {
$length = self::length($s);
}
if (function_exists('mb_substr')) {
return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster
}
return iconv_substr($s, $start, $length, 'UTF-8');
}
/**
* Truncates string to maximal length.
* @param string UTF-8 encoding
* @param int
* @param string UTF-8 encoding
* @return string
*/
public static function truncate($s, $maxLen, $append = "\xE2\x80\xA6")
{
if (self::length($s) > $maxLen) {
$maxLen = $maxLen - self::length($append);
if ($maxLen < 1) {
return $append;
} elseif (preg_match('#^.{1,'.$maxLen.'}(?=[\s\x00-/:-@\[-`{-~])#us', $s, $matches)) {
return $matches[0] . $append;
} else {
return self::substring($s, 0, $maxLen) . $append;
}
}
return $s;
}
/**
* Convert to lower case.
* @return string
*/
public static function lower($s)
{
return mb_strtolower($s, 'UTF-8');
}
/**
* Convert to upper case.
* @return string
*/
public static function upper($s)
{
return mb_strtoupper($s, 'UTF-8');
}
/**
* Convert first character to upper case.
* @return string
*/
public static function firstUpper($s)
{
return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1);
}
/**
* Capitalize string.
* @return string
*/
public static function capitalize($s)
{
return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
}
/**
* Returns UTF-8 string length.
* @return int
*/
public static function length($s)
{
return strlen(utf8_decode($s)); // fastest way
}
/**
* Strips whitespace.
* @param string UTF-8 encoding
* @param string
* @return string
*/
public static function trim($s, $charlist = " \t\n\r\0\x0B\xC2\xA0")
{
$charlist = preg_quote($charlist, '#');
$s = preg_replace('#^['.$charlist.']+|['.$charlist.']+\z#u', '', $s);
if (preg_last_error()) {
throw new Latte\RegexpException(NULL, preg_last_error());
}
return $s;
}
/**
* Returns element's attributes.
* @return string
*/
public static function htmlAttributes($attrs)
{
if (!is_array($attrs)) {
return '';
}
$s = '';
foreach ($attrs as $key => $value) {
if ($value === NULL || $value === FALSE) {
continue;
} elseif ($value === TRUE) {
if (static::$xhtml) {
$s .= ' ' . $key . '="' . $key . '"';
} else {
$s .= ' ' . $key;
}
continue;
} elseif (is_array($value)) {
$tmp = NULL;
foreach ($value as $k => $v) {
if ($v != NULL) { // intentionally ==, skip NULLs & empty string
// composite 'style' vs. 'others'
$tmp[] = $v === TRUE ? $k : (is_string($k) ? $k . ':' . $v : $v);
}
}
if ($tmp === NULL) {
continue;
}
$value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
} else {
$value = (string) $value;
}
$q = strpos($value, '"') === FALSE ? '"' : "'";
$s .= ' ' . $key . '='
. $q . str_replace(array('&', $q), array('&', $q === '"' ? '"' : '''), $value)
. (strpos($value, '`') !== FALSE && strpbrk($value, ' <>"\'') === FALSE ? ' ' : '')
. $q;
}
return $s;
}
}
src/Latte/Runtime/IHtmlString.php 0000666 00000000420 13436752774 0012766 0 ustar 00 getIterator();
} while ($iterator instanceof \IteratorAggregate);
} elseif ($iterator instanceof \Traversable) {
if (!$iterator instanceof \Iterator) {
$iterator = new \IteratorIterator($iterator);
}
} else {
throw new \InvalidArgumentException(sprintf('Invalid argument passed to foreach; array or Traversable expected, %s given.', is_object($iterator) ? get_class($iterator) : gettype($iterator)));
}
parent::__construct($iterator, 0);
}
/**
* Is the current element the first one?
* @param int grid width
* @return bool
*/
public function isFirst($width = NULL)
{
return $this->counter === 1 || ($width && $this->counter !== 0 && (($this->counter - 1) % $width) === 0);
}
/**
* Is the current element the last one?
* @param int grid width
* @return bool
*/
public function isLast($width = NULL)
{
return !$this->hasNext() || ($width && ($this->counter % $width) === 0);
}
/**
* Is the iterator empty?
* @return bool
*/
public function isEmpty()
{
return $this->counter === 0;
}
/**
* Is the counter odd?
* @return bool
*/
public function isOdd()
{
return $this->counter % 2 === 1;
}
/**
* Is the counter even?
* @return bool
*/
public function isEven()
{
return $this->counter % 2 === 0;
}
/**
* Returns the counter.
* @return int
*/
public function getCounter()
{
return $this->counter;
}
/**
* Returns the count of elements.
* @return int
*/
public function count()
{
$inner = $this->getInnerIterator();
if ($inner instanceof \Countable) {
return $inner->count();
} else {
throw new \LogicException('Iterator is not countable.');
}
}
/**
* Forwards to the next element.
* @return void
*/
public function next()
{
parent::next();
if (parent::valid()) {
$this->counter++;
}
}
/**
* Rewinds the Iterator.
* @return void
*/
public function rewind()
{
parent::rewind();
$this->counter = parent::valid() ? 1 : 0;
}
/**
* Returns the next key.
* @return mixed
*/
public function getNextKey()
{
return $this->getInnerIterator()->key();
}
/**
* Returns the next element.
* @return mixed
*/
public function getNextValue()
{
return $this->getInnerIterator()->current();
}
/********************* Latte\Object behaviour + property accessor ****************d*g**/
/**
* Call to undefined method.
* @throws \LogicException
*/
public function __call($name, $args)
{
throw new \LogicException(sprintf('Call to undefined method %s::%s().', get_class($this), $name));
}
/**
* Returns property value.
* @throws \LogicException if the property is not defined.
*/
public function &__get($name)
{
if (method_exists($this, $m = 'get' . $name) || method_exists($this, $m = 'is' . $name)) {
$ret = $this->$m();
return $ret;
}
throw new \LogicException(sprintf('Cannot read an undeclared property %s::$%s.', get_class($this), $name));
}
/**
* Access to undeclared property.
* @throws \LogicException
*/
public function __set($name, $value)
{
throw new \LogicException(sprintf('Cannot write to an undeclared property %s::$%s.', get_class($this), $name));
}
/**
* Is property defined?
* @return bool
*/
public function __isset($name)
{
return method_exists($this, 'get' . $name) || method_exists($this, 'is' . $name);
}
/**
* Access to undeclared property.
* @throws \LogicException
*/
public function __unset($name)
{
throw new \LogicException(sprintf('Cannot unset the property %s::$%s.', get_class($this), $name));
}
}
src/Latte/MacroNode.php 0000666 00000003233 13436752774 0011013 0 ustar 00 macro = $macro;
$this->name = (string) $name;
$this->modifiers = (string) $modifiers;
$this->parentNode = $parentNode;
$this->htmlNode = $htmlNode;
$this->prefix = $prefix;
$this->data = new \stdClass;
$this->setArgs($args);
}
public function setArgs($args)
{
$this->args = (string) $args;
$this->tokenizer = new MacroTokens($this->args);
}
}
readme.md 0000666 00000024513 13436752774 0006356 0 ustar 00 [Latte](http://latte.nette.org): amazing template engine for PHP
================================================================
[![Downloads this Month](https://img.shields.io/packagist/dm/latte/latte.svg)](https://packagist.org/packages/latte/latte)
[![Build Status](https://travis-ci.org/nette/latte.svg?branch=master)](https://travis-ci.org/nette/latte)
Latte is a template engine for PHP which eases your work and
ensures the output is protected against vulnerabilities, such as XSS.
**Latte is fast:** it compiles templates to plain optimized PHP code.
**Latte is secure:** it is the first PHP engine introducing content-aware escaping.
**Latte speaks your language:** it has intuitive syntax and helps you to build better websites easily.
Getting Started
===============
Although PHP is originally a templating language, it is not particularly suited for writing templates. Let's have a look at an example of a PHP template that prints an array `$items` as a list:
```php
```
The code is rather confusing. Moreover, we must not forget to call `htmlSpecialChars` function. That's why there are so many different template engines for PHP. One of the best template engines is part of Nette Framework and it is called **Latte**. You'll love it!
The same template as the one above can be written easily in Latte:
```html
{foreach $items as $item}
- {$item|capitalize}
{/foreach}
```
As you can see there are two types of macros:
- **macro** in braces, for example `{foreach …}`
- **n:macro**, for example `n:if="…"`
How to render template? Just install Latte (it requires PHP 5.3.1 or later) by [downloading the latest package](https://github.com/nette/latte/releases) or using Composer:
```
php composer.phar require latte/latte
```
and run this code:
```php
$latte = new Latte\Engine;
$latte->setTempDirectory('/path/to/tempdir');
$parameters['items'] = array('one', 'two', 'three');
$latte->render('template.latte', $parameters);
```
Macros
======
You can find detailed description of all the default macros on the [extra page](http://doc.nette.org/en/default-macros). Furthermore, you can make your own macros.
Each pair macro, such as `{if} … {/if}`, operating upon single HTML element can be written in `n:macro` notation. So, it is possible to write the `{foreach}` macro in the same manner:
```html
```
With n:macros you can do much more interesting tricks as you will see in a moment.
`{$item|capitalize}` macro which prints the `$item` variable contains so called filter, in this case the `capitalize` filter which makes the first letter of each word uppercase.
Very important feature of Latte is that it **escapes variables by default**. Escaping is needed when printing a variable because we have to convert all the characters which have a special meaning in HTML to other sequences. In case we forget it can lead to a serious security hole called Cross Site Scripting (XSS).
Because of different escaping functions that are needed in different documents and different parts of a page, Latte features a unique technology of Context-Aware Escaping which recognizes the context in which the macro is placed and **chooses the right escaping mode.** You don't have to worry that your coder forgets about it causing you goose bumps because of a security hole. Which is great!
If the `$item` variable stores an HTML code and you want to print it without any alteration you just add the modifier noescape: `{$item|noescape}`. Forgetting the modifier mark won't cause any security holes in spirit of „less code, more security“ principle.
You can still use PHP inside the macros normally, including comments as well. But Latte also extends the PHP syntax with three pleasant features:
1. array can be written as `[1, 2, 3]`, which is the same as `array(1, 2, 3)` in PHP
3. we can omit quotes around the strings consisting of letters, numbers and dashes
2. short condition notation `$a ? 'b'` which is the same as `$a ? 'b' : null` in PHP
For example:
```html
{foreach [a, b, c] as $id} ... {/foreach}
{$cond ? hello} // prints 'hello' if $cond equals true
```
Latte also has a `{* comment macro *}` which doesn't get printed to the output.
n:macros
========
We showed that n:macros are supposed to be written directly into HTML tags as their special attributes. We also said that every pair macro (e.g. `{if} … {/if}`) can be written in n:macro notation. The macro then corresponds to the HTML element in which it is written:
```html
{var $items = ['I', '♥', 'Nette Framework']}
{$item}
```
Prints:
```html
I
♥
Nette Framework
```
By using `inner-` prefix we can alter the behavior so that the macro applies only to the body of the element:
```html
```
Prints:
```html
```
Or by using `tag-` prefix the macro is applied on the HTML tags only:
```html
Title
```
Depending on the value of `$url` variable this will print:
```html
// when $url is empty
Title
// when $url equals 'http://nette.org'
Title
```
However, n:macros are not only a shortcut for pair macros, there are some pure n:macros as well, for example the coder's best friend [n:class](http://doc.nette.org/en/default-macros#toc-n-class) macro.
Filters
=======
Latte allows calling filters by using the pipe sign notation (preceding space is allowed):
```html
{$heading|upper}
```
Filters (or modifiers) can be chained, in that case they apply in order from left to right:
```html
{$heading|lower|capitalize}
```
Parameters are put after the filter name separated by colon or comma:
```html
{$heading|truncate:20,''}
```
See the summary of [standard filters](http://doc.nette.org/en/default-filters) and how to make user-defined filters.
In templates we can use functions which change or format the data to a form we want. They are called *filters*. See the [summary of the default filters|default filters].
Filter can be registered by any callback or lambda function:
```php
$latte = new Latte\Engine;
$latte->addFilter('shortify', function ($s) {
return mb_substr($s, 0, 10); // shortens the text to 10 characters
});
```
In this case it would be better for the filter to get an extra parameter:
```php
$latte->addFilter('shortify', function ($s, $len = 10) {
return mb_substr($s, 0, $len);
});
```
We call it in a template like this:
```php
shortify($text, 100); ?>
```
Latte simplifies the notation - filters are denoted by the pipe sign, they can be chained (they apply in order from left to right). Parameters are separated by colon or comma:
```html
{$text|shortify:100}
```
Performance
===========
Latte is fast. It compiles the templates to native PHP code and stores them in cache on the disk. So they are as fast as if they would have been written in pure PHP.
The template is automatically recompiled each time we change the source file. While developing you just need to edit the templates in Latte and changes are visible in your browser instantly.
Debugging
=========
With each error or typo you will be informed by the Debugger with all the luxury. The template source code is displayed and the red line marks the error showing error message as well. With just a single click you can open the template in your favorite editor and fix the error at once. Easy peasy!
If you are using an IDE with code stepping you can go through the generated PHP code of the template.
Usability
=========
Latte syntax wasn't invented by engineers but came up from webdesigner's practical requests. We were looking for the friendliest syntax with which you can write even the most problematic constructions comfortably enough. You will be surprised how much help Latte can be.
You can find macros for advanced [layout](http://doc.nette.org/en/default-macros#toc-template-expanding-inheritance) managing, for **template inheritance**, nested blocks and so on. Syntax comes from PHP itself so you don't have to learn anything new and you can leverage your know-how.
Context-Aware Escaping
======================
Although the Cross Site Scripting (XSS) is one of the trivial ways of exploiting a web page it is the most common vulnerability but very serious. It can lead to identity theft and so on. The best defense is consistent escaping of printed data, ie. converting the characters which have a special meaning in the given context.
If the coder omits the escaping a security hole is made. That's why template engines implement automated escaping. The problem is that the web page has different contexts and each has different rules for escaping printed data. A security hole then shows up if the wrong escaping functions are used.
But Latte is sophisticated. It features unique technology of *Context-Aware Escaping* which recognizes the context in which the macro is placed and chooses the right escaping mode. What does that mean?
Latte doesn't need any manual work. **All is done automatically, consistently and correctly.** You don't have to worry about security holes.
Lets see how it works:
```html
{$movie}
```
If `$movie` variable stores `'Amarcord & 8 1/2'` string it generates the following output. Notice different escaping used in HTML and JavaScript and also in `onclick` attribute:
```html
Amarcord & 8 1/2
```
Thanks to Context-Aware Escaping the template is simple and your application perfectly secured against Cross Site Scripting. You can use PHP variables natively inside the JavaScript!
A pretty output
===============
Sticklers will enjoy the look of the HTML output which Latte generates. All tags are indented as they are supposed to. The code looks like it has been processed with some kind of *HTML code beautifier* :-)
composer.json 0000666 00000001407 13436752774 0007316 0 ustar 00 {
"name": "latte/latte",
"description": "Latte: the amazing template engine for PHP",
"keywords": ["templating", "twig"],
"homepage": "http://latte.nette.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "http://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "http://nette.org/contributors"
}
],
"require": {
"php": ">=5.3.1",
"ext-tokenizer": "*"
},
"require-dev": {
"nette/tester": "~1.3"
},
"suggest": {
"ext-mbstring": "to use filters like lower, upper, capitalize, ...",
"ext-fileinfo": "to use filter |datastream"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
}
}
license.md 0000666 00000005243 13436752774 0006542 0 ustar 00 Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (http://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)