Browse Source

Merge branch 'master' of github.com:vichan-devel/Tinyboard

Conflicts:
	js/post-hider.js
pull/40/head
czaks 11 years ago
parent
commit
a84c4510fc
  1. 17
      README.md
  2. 4
      banned.php
  3. 7
      imgcaptcha_im.php
  4. 5
      imgcaptcha_p.php
  5. 12
      inc/anti-bot.php
  6. 1123
      inc/config.php
  7. 37
      inc/database.php
  8. 79
      inc/display.php
  9. 6
      inc/filters.php
  10. 969
      inc/functions.php
  11. 37
      inc/ic-encrypt.php
  12. 204
      inc/image.php
  13. 136
      inc/imgcaptcha.php
  14. 20
      inc/lib/Twig/Autoloader.php
  15. 82
      inc/lib/Twig/Compiler.php
  16. 10
      inc/lib/Twig/CompilerInterface.php
  17. 510
      inc/lib/Twig/Environment.php
  18. 144
      inc/lib/Twig/Error.php
  19. 15
      inc/lib/Twig/Error/Loader.php
  20. 3
      inc/lib/Twig/Error/Runtime.php
  21. 3
      inc/lib/Twig/Error/Syntax.php
  22. 28
      inc/lib/Twig/ExistsLoaderInterface.php
  23. 306
      inc/lib/Twig/ExpressionParser.php
  24. 4
      inc/lib/Twig/Extension.php
  25. 993
      inc/lib/Twig/Extension/Core.php
  26. 71
      inc/lib/Twig/Extension/Debug.php
  27. 44
      inc/lib/Twig/Extension/Escaper.php
  28. 113
      inc/lib/Twig/Extension/Staging.php
  29. 64
      inc/lib/Twig/Extension/StringLoader.php
  30. 25
      inc/lib/Twig/ExtensionInterface.php
  31. 2
      inc/lib/Twig/Extensions/Extension/I18n.php
  32. 59
      inc/lib/Twig/Extensions/Extension/Tinyboard.php
  33. 11
      inc/lib/Twig/Extensions/Node/Trans.php
  34. 6
      inc/lib/Twig/Extensions/TokenParser/Trans.php
  35. 31
      inc/lib/Twig/Filter.php
  36. 8
      inc/lib/Twig/Filter/Function.php
  37. 11
      inc/lib/Twig/Filter/Method.php
  38. 39
      inc/lib/Twig/Filter/Node.php
  39. 23
      inc/lib/Twig/FilterCallableInterface.php
  40. 22
      inc/lib/Twig/FilterInterface.php
  41. 25
      inc/lib/Twig/Function.php
  42. 8
      inc/lib/Twig/Function/Function.php
  43. 11
      inc/lib/Twig/Function/Method.php
  44. 39
      inc/lib/Twig/Function/Node.php
  45. 23
      inc/lib/Twig/FunctionCallableInterface.php
  46. 18
      inc/lib/Twig/FunctionInterface.php
  47. 226
      inc/lib/Twig/Lexer.php
  48. 10
      inc/lib/Twig/LexerInterface.php
  49. 39
      inc/lib/Twig/Loader/Array.php
  50. 79
      inc/lib/Twig/Loader/Chain.php
  51. 129
      inc/lib/Twig/Loader/Filesystem.php
  52. 34
      inc/lib/Twig/Loader/String.php
  53. 21
      inc/lib/Twig/LoaderInterface.php
  54. 14
      inc/lib/Twig/Markup.php
  55. 11
      inc/lib/Twig/Node.php
  56. 3
      inc/lib/Twig/Node/AutoEscape.php
  57. 3
      inc/lib/Twig/Node/Block.php
  58. 3
      inc/lib/Twig/Node/BlockReference.php
  59. 19
      inc/lib/Twig/Node/Body.php
  60. 38
      inc/lib/Twig/Node/Do.php
  61. 38
      inc/lib/Twig/Node/Embed.php
  62. 3
      inc/lib/Twig/Node/Expression.php
  63. 51
      inc/lib/Twig/Node/Expression/Array.php
  64. 6
      inc/lib/Twig/Node/Expression/AssignName.php
  65. 4
      inc/lib/Twig/Node/Expression/Binary/FloorDiv.php
  66. 3
      inc/lib/Twig/Node/Expression/BlockReference.php
  67. 178
      inc/lib/Twig/Node/Expression/Call.php
  68. 3
      inc/lib/Twig/Node/Expression/ExtensionReference.php
  69. 60
      inc/lib/Twig/Node/Expression/Filter.php
  70. 43
      inc/lib/Twig/Node/Expression/Filter/Default.php
  71. 38
      inc/lib/Twig/Node/Expression/Function.php
  72. 56
      inc/lib/Twig/Node/Expression/GetAttr.php
  73. 41
      inc/lib/Twig/Node/Expression/MethodCall.php
  74. 71
      inc/lib/Twig/Node/Expression/Name.php
  75. 24
      inc/lib/Twig/Node/Expression/Parent.php
  76. 26
      inc/lib/Twig/Node/Expression/TempName.php
  77. 44
      inc/lib/Twig/Node/Expression/Test.php
  78. 46
      inc/lib/Twig/Node/Expression/Test/Constant.php
  79. 54
      inc/lib/Twig/Node/Expression/Test/Defined.php
  80. 33
      inc/lib/Twig/Node/Expression/Test/Divisibleby.php
  81. 32
      inc/lib/Twig/Node/Expression/Test/Even.php
  82. 31
      inc/lib/Twig/Node/Expression/Test/Null.php
  83. 32
      inc/lib/Twig/Node/Expression/Test/Odd.php
  84. 29
      inc/lib/Twig/Node/Expression/Test/Sameas.php
  85. 36
      inc/lib/Twig/Node/Flush.php
  86. 63
      inc/lib/Twig/Node/For.php
  87. 55
      inc/lib/Twig/Node/ForLoop.php
  88. 3
      inc/lib/Twig/Node/If.php
  89. 3
      inc/lib/Twig/Node/Import.php
  90. 45
      inc/lib/Twig/Node/Include.php
  91. 59
      inc/lib/Twig/Node/Macro.php
  92. 127
      inc/lib/Twig/Node/Module.php
  93. 3
      inc/lib/Twig/Node/Print.php
  94. 3
      inc/lib/Twig/Node/Sandbox.php
  95. 23
      inc/lib/Twig/Node/SandboxedModule.php
  96. 3
      inc/lib/Twig/Node/SandboxedPrint.php
  97. 9
      inc/lib/Twig/Node/Set.php
  98. 35
      inc/lib/Twig/Node/SetTemp.php
  99. 3
      inc/lib/Twig/Node/Spaceless.php
  100. 3
      inc/lib/Twig/Node/Text.php

17
README.md

@ -17,16 +17,21 @@ license. It is written in PHP and has few dependencies.
Requirements Requirements
------------ ------------
1. PHP >= 5.2.5 1. PHP >= 5.2.5
2. [mbstring](http://www.php.net/manual/en/mbstring.installation.php) 2. MySQL server
(--enable-mbstring) 3. [mbstring](http://www.php.net/manual/en/mbstring.installation.php)
3. [PHP-GD](http://php.net/manual/en/book.image.php) 4. [PHP GD](http://www.php.net/manual/en/intro.image.php)
4. [PHP-PDO](http://php.net/manual/en/book.pdo.php) 5. [PHP PDO](http://www.php.net/manual/en/intro.pdo.php)
(only MySQL is supported at the moment)
We try to make sure Tinyboard is compatible with all major web servers and We try to make sure Tinyboard is compatible with all major web servers and
operating systems. Tinyboard does not include an Apache ```.htaccess``` file nor does operating systems. Tinyboard does not include an Apache ```.htaccess``` file nor does
it need one. it need one.
### Recommended
1. PHP >= 5.3
2. MySQL server >= 5.5.3
3. ImageMagick or command-line version (```convert``` and ```identify```)
4. [APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php), [XCache](http://xcache.lighttpd.net/) or [Memcached](http://www.php.net/manual/en/intro.memcached.php)
Contributing Contributing
------------ ------------
You can contribute to Tinyboard by: You can contribute to Tinyboard by:
@ -65,7 +70,7 @@ find support from a variety of sources:
* Documentation can be found [here](http://tinyboard.org/docs/). * Documentation can be found [here](http://tinyboard.org/docs/).
* You can join Tinyboard's IRC channel for support and general queries: * You can join Tinyboard's IRC channel for support and general queries:
[irc.datnode.net #tinyboard](irc://irc.datnode.net/tinyboard). [irc.datnode.net #tinyboard](irc://irc.datnode.net/tinyboard).
* You can find enterprise-grade support at [tinyboard.org](http://tinyboard.org/#support). * You may find help at [tinyboard.org](http://tinyboard.org/#help).
License License
-------- --------

4
banned.php

@ -1,7 +1,7 @@
<?php <?php
require_once 'inc/functions.php'; require_once 'inc/functions.php';
checkBan(); checkBan();
print "<html><head>Banned?</head><body>"; print "<!doctype html><html><head><meta charset='utf-8'><title>"._("Banned?")."</title></head><body>";
print "<h1>Nie jestes zbanowany.</h1>"; print "<h1>"._("You are not banned.")."</h1>";
print "</body></html>"; print "</body></html>";
?> ?>

7
imgcaptcha_im.php

@ -1,7 +0,0 @@
<?php
require_once("inc/functions.php");
require_once("inc/imgcaptcha.php");
$t = $_GET["cr"];
header("Content-Type: image/png");
generateImage($t);
?>

5
imgcaptcha_p.php

@ -1,5 +0,0 @@
<?php
require_once("inc/functions.php");
require_once("inc/imgcaptcha.php");
print generateCaptchaHash();
?>

12
inc/anti-bot.php

@ -182,12 +182,12 @@ function _create_antibot($board, $thread) {
$antibot = new AntiBot(array($board, $thread)); $antibot = new AntiBot(array($board, $thread));
query('DELETE FROM `antispam` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error()); query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error());
if ($thread) if ($thread)
$query = prepare('UPDATE `antispam` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread AND `expires` IS NULL'); $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread AND `expires` IS NULL');
else else
$query = prepare('UPDATE `antispam` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` IS NULL AND `expires` IS NULL'); $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` IS NULL AND `expires` IS NULL');
$query->bindValue(':board', $board); $query->bindValue(':board', $board);
if ($thread) if ($thread)
@ -195,7 +195,7 @@ function _create_antibot($board, $thread) {
$query->bindValue(':expires', $config['spam']['hidden_inputs_expire']); $query->bindValue(':expires', $config['spam']['hidden_inputs_expire']);
$query->execute() or error(db_error($query)); $query->execute() or error(db_error($query));
$query = prepare('INSERT INTO `antispam` VALUES (:board, :thread, :hash, UNIX_TIMESTAMP(), NULL, 0)'); $query = prepare('INSERT INTO ``antispam`` VALUES (:board, :thread, :hash, UNIX_TIMESTAMP(), NULL, 0)');
$query->bindValue(':board', $board); $query->bindValue(':board', $board);
$query->bindValue(':thread', $thread); $query->bindValue(':thread', $thread);
$query->bindValue(':hash', $antibot->hash()); $query->bindValue(':hash', $antibot->hash());
@ -248,7 +248,7 @@ function checkSpam(array $extra_salt = array()) {
if ($hash != $_hash) if ($hash != $_hash)
return true; return true;
$query = prepare('SELECT `passed` FROM `antispam` WHERE `hash` = :hash'); $query = prepare('SELECT `passed` FROM ``antispam`` WHERE `hash` = :hash');
$query->bindValue(':hash', $hash); $query->bindValue(':hash', $hash);
$query->execute() or error(db_error($query)); $query->execute() or error(db_error($query));
if ((($passed = $query->fetchColumn(0)) === false) || ($passed > $config['spam']['hidden_inputs_max_pass'])) { if ((($passed = $query->fetchColumn(0)) === false) || ($passed > $config['spam']['hidden_inputs_max_pass'])) {
@ -260,7 +260,7 @@ function checkSpam(array $extra_salt = array()) {
} }
function incrementSpamHash($hash) { function incrementSpamHash($hash) {
$query = prepare('UPDATE `antispam` SET `passed` = `passed` + 1 WHERE `hash` = :hash'); $query = prepare('UPDATE ``antispam`` SET `passed` = `passed` + 1 WHERE `hash` = :hash');
$query->bindValue(':hash', $hash); $query->bindValue(':hash', $hash);
$query->execute() or error(db_error($query)); $query->execute() or error(db_error($query));
} }

1123
inc/config.php

File diff suppressed because it is too large

37
inc/database.php

@ -51,12 +51,16 @@ function sql_open() {
try { try {
$options = array( $options = array(
PDO::ATTR_TIMEOUT => $config['db']['timeout'], PDO::ATTR_TIMEOUT => $config['db']['timeout'],
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
); );
if ($config['db']['persistent']) if ($config['db']['persistent'])
$options[PDO::ATTR_PERSISTENT] = true; $options[PDO::ATTR_PERSISTENT] = true;
return $pdo = new PDO($dsn, $config['db']['user'], $config['db']['password'], $options); $pdo = new PDO($dsn, $config['db']['user'], $config['db']['password'], $options);
if (mysql_version() >= 50503)
query('SET NAMES utf8mb4') or error(db_error());
else
query('SET NAMES utf8') or error(db_error());
return $pdo;
} catch(PDOException $e) { } catch(PDOException $e) {
$message = $e->getMessage(); $message = $e->getMessage();
@ -65,13 +69,26 @@ function sql_open() {
$message = str_replace($config['db']['password'], '<em>hidden</em>', $message); $message = str_replace($config['db']['password'], '<em>hidden</em>', $message);
// Print error // Print error
error('Database error: ' . $message); error(_('Database error: ') . $message);
} }
} }
// 5.6.10 becomes 50610
function mysql_version() {
global $pdo;
$version = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
$v = explode('.', $version);
if (count($v) != 3)
return false;
return (int) sprintf("%02d%02d%02d", $v[0], $v[1], $v[2]);
}
function prepare($query) { function prepare($query) {
global $pdo, $debug, $config; global $pdo, $debug, $config;
$query = preg_replace('/``('.$config['board_regex'].')``/u', '`' . $config['db']['prefix'] . '$1`', $query);
sql_open(); sql_open();
if ($config['debug']) if ($config['debug'])
@ -83,6 +100,8 @@ function prepare($query) {
function query($query) { function query($query) {
global $pdo, $debug, $config; global $pdo, $debug, $config;
$query = preg_replace('/``('.$config['board_regex'].')``/u', '`' . $config['db']['prefix'] . '$1`', $query);
sql_open(); sql_open();
if ($config['debug']) { if ($config['debug']) {
@ -102,14 +121,14 @@ function query($query) {
return $pdo->query($query); return $pdo->query($query);
} }
function db_error($PDOStatement=null) { function db_error($PDOStatement = null) {
global $pdo; global $pdo, $db_error;
if (isset($PDOStatement)) { if (isset($PDOStatement)) {
$err = $PDOStatement->errorInfo(); $db_error = $PDOStatement->errorInfo();
return $err[2]; return $db_error[2];
} }
$err = $pdo->errorInfo(); $db_error = $pdo->errorInfo();
return $err[2]; return $db_error[2];
} }

79
inc/display.php

@ -25,6 +25,7 @@ function doBoardListPart($list, $root) {
$body = ''; $body = '';
foreach ($list as $board) { foreach ($list as $board) {
if (is_array($board)) if (is_array($board))
// $body .= ' [' . doBoardListPart($board, $root) . '] ';
$body .= ' <span class="sub">[' . doBoardListPart($board, $root) . ']</span> '; $body .= ' <span class="sub">[' . doBoardListPart($board, $root) . ']</span> ';
else { else {
if (($key = array_search($board, $list)) && gettype($key) == 'string') { if (($key = array_search($board, $list)) && gettype($key) == 'string') {
@ -56,8 +57,8 @@ function createBoardlist($mod=false) {
); );
} }
function error($message, $priority = true) { function error($message, $priority = true, $debug_stuff = false) {
global $board, $mod, $config; global $board, $mod, $config, $db_error;
if ($config['syslog'] && $priority !== false) { if ($config['syslog'] && $priority !== false) {
// Use LOG_NOTICE instead of LOG_ERR or LOG_WARNING because most error message are not significant. // Use LOG_NOTICE instead of LOG_ERR or LOG_WARNING because most error message are not significant.
@ -68,18 +69,22 @@ function error($message, $priority = true) {
// Running from CLI // Running from CLI
die('Error: ' . $message . "\n"); die('Error: ' . $message . "\n");
} }
if ($config['debug'] && isset($db_error)) {
$debug_stuff = array_combine(array('SQLSTATE', 'Error code', 'Error message'), $db_error);
}
die(Element('page.html', array( die(Element('page.html', array(
'config'=>$config, 'config' => $config,
'title'=>'Error', 'title' => _('Error'),
'subtitle'=>'An error has occured.', 'subtitle' => _('An error has occured.'),
'body'=>'<center>' . 'body' => Element('error.html', array(
'<h2>' . _($message) . '</h2>' . 'config' => $config,
(isset($board) ? 'message' => $message,
"<p><a href=\"" . $config['root'] . 'mod' => $mod,
($mod ? $config['file_mod'] . '?/' : '') . 'board' => isset($board) ? $board : false,
$board['dir'] . $config['file_index'] . "\">Go back</a>.</p>" : '') . 'debug' => is_array($debug_stuff) ? str_replace("\n", '&#10;', utf8tohtml(print_r($debug_stuff, true))) : utf8tohtml($debug_stuff)
'</center>' ))
))); )));
} }
@ -207,7 +212,7 @@ function truncate($body, $url, $max_lines = false, $max_chars = false) {
$body = preg_replace('/&[^;]*$/', '', $body); $body = preg_replace('/&[^;]*$/', '', $body);
} }
$body .= '<span class="toolong">Post too long. Click <a href="' . $url . '">here</a> to view the full text.</span>'; $body .= '<span class="toolong">'.sprintf(_('Post too long. Click <a href="%s">here</a> to view the full text.'), $url).'</span>';
} }
return $body; return $body;
@ -235,7 +240,7 @@ function bidi_cleanup($str){
function secure_link_confirm($text, $title, $confirm_message, $href) { function secure_link_confirm($text, $title, $confirm_message, $href) {
global $config; global $config;
return '<a onclick="if (event.which==2) return true;if (confirm(\'' . htmlentities(addslashes($confirm_message)) . '\')) document.location=\'?/' . htmlentities(addslashes($href . '/' . make_secure_link_token($href))) . '\';return false;" title="' . htmlentities($title) . '" href="?/' . $href . '">' . $text . '</a>'; return '<a onclick="if (event.which==2) return true;if (confirm(\'' . htmlentities(addslashes($confirm_message)) . '\')) document.location=\'?/' . htmlspecialchars(addslashes($href . '/' . make_secure_link_token($href))) . '\';return false;" title="' . htmlentities($title) . '" href="?/' . $href . '">' . $text . '</a>';
} }
function secure_link($href) { function secure_link($href) {
return $href . '/' . make_secure_link_token($href); return $href . '/' . make_secure_link_token($href);
@ -299,7 +304,7 @@ class Post {
// Fix internal links // Fix internal links
// Very complicated regex // Very complicated regex
$this->body = preg_replace( $this->body = preg_replace(
'/<a((([a-zA-Z]+="[^"]+")|[a-zA-Z]+=[a-zA-Z]+|\s)*)href="' . preg_quote($config['root'], '/') . '(' . sprintf(preg_quote($config['board_path'], '/'), '\w+') . ')/', '/<a((([a-zA-Z]+="[^"]+")|[a-zA-Z]+=[a-zA-Z]+|\s)*)href="' . preg_quote($config['root'], '/') . '(' . sprintf(preg_quote($config['board_path'], '/'), $config['board_regex']) . ')/u',
'<a $1href="?/$4', '<a $1href="?/$4',
$this->body $this->body
); );
@ -318,31 +323,31 @@ class Post {
// Delete // Delete
if (hasPermission($config['mod']['delete'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['delete'], $board['uri'], $this->mod))
$built .= ' ' . secure_link_confirm($config['mod']['link_delete'], 'Delete', 'Are you sure you want to delete this?', $board['uri'] . '/delete/' . $this->id); $built .= ' ' . secure_link_confirm($config['mod']['link_delete'], 'Delete', 'Are you sure you want to delete this?', $board['dir'] . 'delete/' . $this->id);
// Delete all posts by IP // Delete all posts by IP
if (hasPermission($config['mod']['deletebyip'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['deletebyip'], $board['uri'], $this->mod))
$built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip'], 'Delete all posts by IP', 'Are you sure you want to delete all posts by this IP address?', $board['uri'] . '/deletebyip/' . $this->id); $built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip'], 'Delete all posts by IP', 'Are you sure you want to delete all posts by this IP address?', $board['dir'] . 'deletebyip/' . $this->id);
// Delete all posts by IP (global) // Delete all posts by IP (global)
if (hasPermission($config['mod']['deletebyip_global'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['deletebyip_global'], $board['uri'], $this->mod))
$built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip_global'], 'Delete all posts by IP across all boards', 'Are you sure you want to delete all posts by this IP address, across all boards?', $board['uri'] . '/deletebyip/' . $this->id . '/global'); $built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip_global'], 'Delete all posts by IP across all boards', 'Are you sure you want to delete all posts by this IP address, across all boards?', $board['dir'] . 'deletebyip/' . $this->id . '/global');
// Ban // Ban
if (hasPermission($config['mod']['ban'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['ban'], $board['uri'], $this->mod))
$built .= ' <a title="Ban" href="?/' . $board['uri'] . '/ban/' . $this->id . '">' . $config['mod']['link_ban'] . '</a>'; $built .= ' <a title="'._('Ban').'" href="?/' . $board['dir'] . 'ban/' . $this->id . '">' . $config['mod']['link_ban'] . '</a>';
// Ban & Delete // Ban & Delete
if (hasPermission($config['mod']['bandelete'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['bandelete'], $board['uri'], $this->mod))
$built .= ' <a title="Ban & Delete" href="?/' . $board['uri'] . '/ban&amp;delete/' . $this->id . '">' . $config['mod']['link_bandelete'] . '</a>'; $built .= ' <a title="'._('Ban & Delete').'" href="?/' . $board['dir'] . 'ban&amp;delete/' . $this->id . '">' . $config['mod']['link_bandelete'] . '</a>';
// Delete file (keep post) // Delete file (keep post)
if (!empty($this->file) && hasPermission($config['mod']['deletefile'], $board['uri'], $this->mod)) if (!empty($this->file) && hasPermission($config['mod']['deletefile'], $board['uri'], $this->mod))
$built .= ' ' . secure_link_confirm($config['mod']['link_deletefile'], 'Delete file', 'Are you sure you want to delete this file?', $board['uri'] . '/deletefile/' . $this->id); $built .= ' ' . secure_link_confirm($config['mod']['link_deletefile'], _('Delete file'), _('Are you sure you want to delete this file?'), $board['dir'] . 'deletefile/' . $this->id);
// Edit post // Edit post
if (hasPermission($config['mod']['editpost'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['editpost'], $board['uri'], $this->mod))
$built .= ' <a title="Edit post" href="?/' . $board['uri'] . '/edit' . ($config['mod']['raw_html_default'] ? '_raw' : '') . '/' . $this->id . '">' . $config['mod']['link_editpost'] . '</a>'; $built .= ' <a title="'._('Edit post').'" href="?/' . $board['dir'] . 'edit' . ($config['mod']['raw_html_default'] ? '_raw' : '') . '/' . $this->id . '">' . $config['mod']['link_editpost'] . '</a>';
if (!empty($built)) if (!empty($built))
$built = '<span class="controls">' . $built . '</span>'; $built = '<span class="controls">' . $built . '</span>';
@ -398,7 +403,7 @@ class Thread {
// Fix internal links // Fix internal links
// Very complicated regex // Very complicated regex
$this->body = preg_replace( $this->body = preg_replace(
'/<a((([a-zA-Z]+="[^"]+")|[a-zA-Z]+=[a-zA-Z]+|\s)*)href="' . preg_quote($config['root'], '/') . '(' . sprintf(preg_quote($config['board_path'], '/'), '\w+') . ')/', '/<a((([a-zA-Z]+="[^"]+")|[a-zA-Z]+=[a-zA-Z]+|\s)*)href="' . preg_quote($config['root'], '/') . '(' . sprintf(preg_quote($config['board_path'], '/'), $config['board_regex']) . ')/u',
'<a $1href="?/$4', '<a $1href="?/$4',
$this->body $this->body
); );
@ -419,54 +424,54 @@ class Thread {
// Mod controls (on posts) // Mod controls (on posts)
// Delete // Delete
if (hasPermission($config['mod']['delete'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['delete'], $board['uri'], $this->mod))
$built .= ' ' . secure_link_confirm($config['mod']['link_delete'], 'Delete', 'Are you sure you want to delete this?', $board['uri'] . '/delete/' . $this->id); $built .= ' ' . secure_link_confirm($config['mod']['link_delete'], _('Delete'), _('Are you sure you want to delete this?'), $board['dir'] . 'delete/' . $this->id);
// Delete all posts by IP // Delete all posts by IP
if (hasPermission($config['mod']['deletebyip'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['deletebyip'], $board['uri'], $this->mod))
$built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip'], 'Delete all posts by IP', 'Are you sure you want to delete all posts by this IP address?', $board['uri'] . '/deletebyip/' . $this->id); $built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip'], _('Delete all posts by IP'), _('Are you sure you want to delete all posts by this IP address?'), $board['dir'] . 'deletebyip/' . $this->id);
// Delete all posts by IP (global) // Delete all posts by IP (global)
if (hasPermission($config['mod']['deletebyip_global'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['deletebyip_global'], $board['uri'], $this->mod))
$built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip_global'], 'Delete all posts by IP across all boards', 'Are you sure you want to delete all posts by this IP address, across all boards?', $board['uri'] . '/deletebyip/' . $this->id . '/global'); $built .= ' ' . secure_link_confirm($config['mod']['link_deletebyip_global'], _('Delete all posts by IP across all boards'), _('Are you sure you want to delete all posts by this IP address, across all boards?'), $board['dir'] . 'deletebyip/' . $this->id . '/global');
// Ban // Ban
if (hasPermission($config['mod']['ban'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['ban'], $board['uri'], $this->mod))
$built .= ' <a title="Ban" href="?/' . $board['uri'] . '/ban/' . $this->id . '">' . $config['mod']['link_ban'] . '</a>'; $built .= ' <a title="'._('Ban').'" href="?/' . $board['dir'] . 'ban/' . $this->id . '">' . $config['mod']['link_ban'] . '</a>';
// Ban & Delete // Ban & Delete
if (hasPermission($config['mod']['bandelete'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['bandelete'], $board['uri'], $this->mod))
$built .= ' <a title="Ban & Delete" href="?/' . $board['uri'] . '/ban&amp;delete/' . $this->id . '">' . $config['mod']['link_bandelete'] . '</a>'; $built .= ' <a title="'._('Ban & Delete').'" href="?/' . $board['dir'] . 'ban&amp;delete/' . $this->id . '">' . $config['mod']['link_bandelete'] . '</a>';
// Delete file (keep post) // Delete file (keep post)
if (!empty($this->file) && $this->file != 'deleted' && hasPermission($config['mod']['deletefile'], $board['uri'], $this->mod)) if (!empty($this->file) && $this->file != 'deleted' && hasPermission($config['mod']['deletefile'], $board['uri'], $this->mod))
$built .= ' ' . secure_link_confirm($config['mod']['link_deletefile'], 'Delete file', 'Are you sure you want to delete this file?', $board['uri'] . '/deletefile/' . $this->id); $built .= ' ' . secure_link_confirm($config['mod']['link_deletefile'], _('Delete file'), _('Are you sure you want to delete this file?'), $board['dir'] . 'deletefile/' . $this->id);
// Sticky // Sticky
if (hasPermission($config['mod']['sticky'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['sticky'], $board['uri'], $this->mod))
if ($this->sticky) if ($this->sticky)
$built .= ' <a title="Make thread not sticky" href="?/' . secure_link($board['uri'] . '/unsticky/' . $this->id) . '">' . $config['mod']['link_desticky'] . '</a>'; $built .= ' <a title="'._('Make thread not sticky').'" href="?/' . secure_link($board['dir'] . 'unsticky/' . $this->id) . '">' . $config['mod']['link_desticky'] . '</a>';
else else
$built .= ' <a title="Make thread sticky" href="?/' . secure_link($board['uri'] . '/sticky/' . $this->id) . '">' . $config['mod']['link_sticky'] . '</a>'; $built .= ' <a title="'._('Make thread sticky').'" href="?/' . secure_link($board['dir'] . 'sticky/' . $this->id) . '">' . $config['mod']['link_sticky'] . '</a>';
if (hasPermission($config['mod']['bumplock'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['bumplock'], $board['uri'], $this->mod))
if ($this->bumplocked) if ($this->bumplocked)
$built .= ' <a title="Allow thread to be bumped" href="?/' . secure_link($board['uri'] . '/bumpunlock/' . $this->id) . '">' . $config['mod']['link_bumpunlock'] . '</a>'; $built .= ' <a title="'._('Allow thread to be bumped').'" href="?/' . secure_link($board['dir'] . 'bumpunlock/' . $this->id) . '">' . $config['mod']['link_bumpunlock'] . '</a>';
else else
$built .= ' <a title="Prevent thread from being bumped" href="?/' . secure_link($board['uri'] . '/bumplock/' . $this->id) . '">' . $config['mod']['link_bumplock'] . '</a>'; $built .= ' <a title="'._('Prevent thread from being bumped').'" href="?/' . secure_link($board['dir'] . 'bumplock/' . $this->id) . '">' . $config['mod']['link_bumplock'] . '</a>';
// Lock // Lock
if (hasPermission($config['mod']['lock'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['lock'], $board['uri'], $this->mod))
if ($this->locked) if ($this->locked)
$built .= ' <a title="Unlock thread" href="?/' . secure_link($board['uri'] . '/unlock/' . $this->id) . '">' . $config['mod']['link_unlock'] . '</a>'; $built .= ' <a title="'._('Unlock thread').'" href="?/' . secure_link($board['dir'] . 'unlock/' . $this->id) . '">' . $config['mod']['link_unlock'] . '</a>';
else else
$built .= ' <a title="Lock thread" href="?/' . secure_link($board['uri'] . '/lock/' . $this->id) . '">' . $config['mod']['link_lock'] . '</a>'; $built .= ' <a title="'._('Lock thread').'" href="?/' . secure_link($board['dir'] . 'lock/' . $this->id) . '">' . $config['mod']['link_lock'] . '</a>';
if (hasPermission($config['mod']['move'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['move'], $board['uri'], $this->mod))
$built .= ' <a title="Move thread to another board" href="?/' . $board['uri'] . '/move/' . $this->id . '">' . $config['mod']['link_move'] . '</a>'; $built .= ' <a title="'._('Move thread to another board').'" href="?/' . $board['dir'] . 'move/' . $this->id . '">' . $config['mod']['link_move'] . '</a>';
// Edit post // Edit post
if (hasPermission($config['mod']['editpost'], $board['uri'], $this->mod)) if (hasPermission($config['mod']['editpost'], $board['uri'], $this->mod))
$built .= ' <a title="Edit post" href="?/' . $board['uri'] . '/edit' . ($config['mod']['raw_html_default'] ? '_raw' : '') . '/' . $this->id . '">' . $config['mod']['link_editpost'] . '</a>'; $built .= ' <a title="'._('Edit post').'" href="?/' . $board['dir'] . 'edit' . ($config['mod']['raw_html_default'] ? '_raw' : '') . '/' . $this->id . '">' . $config['mod']['link_editpost'] . '</a>';
if (!empty($built)) if (!empty($built))
$built = '<span class="controls op">' . $built . '</span>'; $built = '<span class="controls op">' . $built . '</span>';

6
inc/filters.php

@ -81,7 +81,7 @@ class Filter {
else else
$all_boards = false; $all_boards = false;
$query = prepare("INSERT INTO `bans` VALUES (NULL, :ip, :mod, :set, :expires, :reason, :board, 0)"); $query = prepare("INSERT INTO ``bans`` VALUES (NULL, :ip, :mod, :set, :expires, :reason, :board, 0)");
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':mod', -1); $query->bindValue(':mod', -1);
$query->bindValue(':set', time()); $query->bindValue(':set', time());
@ -132,10 +132,10 @@ class Filter {
function do_filters(array $post) { function do_filters(array $post) {
global $config; global $config;
if (!isset($config['flood_filters'])) if (!isset($config['filters']))
return; return;
foreach ($config['flood_filters'] as $arr) { foreach ($config['filters'] as $arr) {
$filter = new Filter($arr); $filter = new Filter($arr);
if ($filter->check($post)) if ($filter->check($post))
$filter->action(); $filter->action();

969
inc/functions.php

File diff suppressed because it is too large

37
inc/ic-encrypt.php

@ -1,37 +0,0 @@
<?php
// Z internetow.
class Encryption {
public function safe_b64encode($string) {
$data = base64_encode($string);
$data = str_replace(array('+','/','='),array('-','_',''),$data);
return $data;
}
public function safe_b64decode($string) {
$data = str_replace(array('-','_'),array('+','/'),$string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
return base64_decode($data);
}
public function encode($key, $value){
if(!$value){return false;}
$text = $value;
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, $iv);
return trim($this->safe_b64encode($crypttext));
}
public function decode($key, $value){
if(!$value){return false;}
$crypttext = $this->safe_b64decode($value);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$decrypttext = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $crypttext, MCRYPT_MODE_ECB, $iv);
return trim($decrypttext);
}
}
?>

204
inc/image.php

@ -11,7 +11,7 @@ if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
class Image { class Image {
public $src, $format, $image, $size; public $src, $format, $image, $size;
public function __construct($src, $format = false) { public function __construct($src, $format = false, $size = false) {
global $config; global $config;
$this->src = $src; $this->src = $src;
@ -19,7 +19,7 @@ class Image {
if ($config['thumb_method'] == 'imagick') { if ($config['thumb_method'] == 'imagick') {
$classname = 'ImageImagick'; $classname = 'ImageImagick';
} elseif ($config['thumb_method'] == 'convert' || $config['thumb_method'] == 'convert+gifsicle') { } elseif (in_array($config['thumb_method'], array('convert', 'convert+gifsicle', 'gm', 'gm+gifsicle'))) {
$classname = 'ImageConvert'; $classname = 'ImageConvert';
} else { } else {
$classname = 'Image' . strtoupper($this->format); $classname = 'Image' . strtoupper($this->format);
@ -28,7 +28,7 @@ class Image {
} }
} }
$this->image = new $classname($this); $this->image = new $classname($this, $size);
if (!$this->image->valid()) { if (!$this->image->valid()) {
$this->delete(); $this->delete();
@ -44,8 +44,6 @@ class Image {
public function resize($extension, $max_width, $max_height) { public function resize($extension, $max_width, $max_height) {
global $config; global $config;
$gifsicle = false;
if ($config['thumb_method'] == 'imagick') { if ($config['thumb_method'] == 'imagick') {
$classname = 'ImageImagick'; $classname = 'ImageImagick';
@ -54,6 +52,13 @@ class Image {
} elseif ($config['thumb_method'] == 'convert+gifsicle') { } elseif ($config['thumb_method'] == 'convert+gifsicle') {
$classname = 'ImageConvert'; $classname = 'ImageConvert';
$gifsicle = true; $gifsicle = true;
} elseif ($config['thumb_method'] == 'gm') {
$classname = 'ImageConvert';
$gm = true;
} elseif ($config['thumb_method'] == 'gm+gifsicle') {
$classname = 'ImageConvert';
$gm = true;
$gifsicle = true;
} else { } else {
$classname = 'Image' . strtoupper($extension); $classname = 'Image' . strtoupper($extension);
if (!class_exists($classname)) { if (!class_exists($classname)) {
@ -81,11 +86,8 @@ class Image {
$height = $max_height; $height = $max_height;
} }
if ($gifsicle) {
$thumb->gifsicle = 1;
}
$thumb->_resize($this->image->image, $width, $height); $thumb->_resize($this->image->image, $width, $height);
return $thumb; return $thumb;
} }
@ -120,10 +122,15 @@ class ImageBase extends ImageGD {
return (bool)$this->image; return (bool)$this->image;
} }
public function __construct($img) { public function __construct($img, $size = false) {
if (method_exists($this, 'init')) if (method_exists($this, 'init'))
$this->init(); $this->init();
if ($size && $size[0] > 0 && $size[1] > 0) {
$this->width = $size[0];
$this->height = $size[1];
}
if ($img !== false) { if ($img !== false) {
$this->src = $img->src; $this->src = $img->src;
$this->from(); $this->from();
@ -175,6 +182,10 @@ class ImageImagick extends ImageBase {
} }
} }
public function to($src) { public function to($src) {
global $config;
if ($config['strip_exif']) {
$this->image->stripImage();
}
if (preg_match('/\.gif$/i', $src)) if (preg_match('/\.gif$/i', $src))
$this->image->writeImages($src, true); $this->image->writeImages($src, true);
else else
@ -225,18 +236,37 @@ class ImageImagick extends ImageBase {
class ImageConvert extends ImageBase { class ImageConvert extends ImageBase {
public $width, $height, $temp, $gifsicle; public $width, $height, $temp, $gm = false, $gifsicle = false;
public function init() { public function init() {
global $config; global $config;
if ($config['thumb_method'] == 'gm' || $config['thumb_method'] == 'gm+gifsicle')
$this->gm = true;
if ($config['thumb_method'] == 'convert+gifsicle' || $config['thumb_method'] == 'gm+gifsicle')
$this->gifsicle = true;
$this->temp = false; $this->temp = false;
} }
public function from() { public function get_size($src, $try_gd_first = true) {
$size = trim(shell_exec('identify -format "%w %h" ' . escapeshellarg($this->src . '[0]'))); if ($try_gd_first) {
if (preg_match('/^(\d+) (\d+)$/', $size, $m)) { if ($size = @getimagesize($src))
$this->width = $m[1]; return $size;
$this->height = $m[2]; }
$size = shell_exec_error(($this->gm ? 'gm ' : '') . 'identify -format "%w %h" ' . escapeshellarg($src . '[0]'));
if (preg_match('/^(\d+) (\d+)$/', $size, $m))
return array($m[1], $m[2]);
return false;
}
public function from() {
if ($this->width > 0 && $this->height > 0) {
$this->image = true;
return;
}
$size = $this->get_size($this->src, false);
if ($size) {
$this->width = $size[0];
$this->height = $size[1];
$this->image = true; $this->image = true;
} else { } else {
@ -245,9 +275,22 @@ class ImageConvert extends ImageBase {
} }
} }
public function to($src) { public function to($src) {
global $config;
if (!$this->temp) { if (!$this->temp) {
// $config['redraw_image'] if ($config['strip_exif']) {
shell_exec('convert ' . escapeshellarg($this->src) . ' ' . escapeshellarg($src)); if($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' .
escapeshellarg($this->src) . ' -auto-orient -strip ' . escapeshellarg($src))) {
$this->destroy();
error('Failed to redraw image!', null, $error);
}
} else {
if($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' .
escapeshellarg($this->src) . ' -auto-orient ' . escapeshellarg($src))) {
$this->destroy();
error('Failed to redraw image!', null, $error);
}
}
} else { } else {
rename($this->temp, $src); rename($this->temp, $src);
chmod($src, 0664); chmod($src, 0664);
@ -272,24 +315,119 @@ class ImageConvert extends ImageBase {
} }
$this->temp = tempnam($config['tmp'], 'imagick'); $this->temp = tempnam($config['tmp'], 'imagick');
$quality = $config['thumb_quality'] * 10; $config['thumb_keep_animation_frames'] = (int)$config['thumb_keep_animation_frames'];
if ($this->format == 'gif' && ($config['thumb_ext'] == 'gif' || $config['thumb_ext'] == '') && $config['thumb_keep_animation_frames'] > 1) { if ($this->format == 'gif' && ($config['thumb_ext'] == 'gif' || $config['thumb_ext'] == '') && $config['thumb_keep_animation_frames'] > 1) {
if ($this->gifsicle) { if ($this->gifsicle) {
if (shell_exec("gifsicle --unoptimize -O2 --resize {$this->width}x{$this->height} < " . if (($error = shell_exec_error("gifsicle -w --unoptimize -O2 --resize {$this->width}x{$this->height} < " .
escapeshellarg($this->src . '') . " > " . escapeshellarg($this->temp)) || !file_exists($this->temp)) escapeshellarg($this->src . '') . " \"#0-{$config['thumb_keep_animation_frames']}\" -o " .
error('Failed to resize image!'); escapeshellarg($this->temp), true)) || !file_exists($this->temp))
} error('Failed to resize image!', null, $error);
else { } else {
if (shell_exec("convert -background transparent -filter Point -sample {$this->width}x{$this->height} +antialias -quality {$quality} " . if ($config['convert_manual_orient'] && ($this->format == 'jpg' || $this->format == 'jpeg'))
escapeshellarg($this->src . '') . " " . escapeshellarg($this->temp)) || !file_exists($this->temp)) $convert_args = str_replace('-auto-orient', ImageConvert::jpeg_exif_orientation($this->src), $config['convert_args']);
error('Failed to resize image!'); elseif ($config['convert_manual_orient'])
$convert_args = str_replace('-auto-orient', '', $config['convert_args']);
else
$convert_args = &$config['convert_args'];
if (($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' .
sprintf($convert_args,
$this->width,
$this->height,
escapeshellarg($this->src),
$this->width,
$this->height,
escapeshellarg($this->temp)))) || !file_exists($this->temp))
error('Failed to resize image!', null, $error);
if ($size = $this->get_size($this->temp)) {
$this->width = $size[0];
$this->height = $size[1];
}
} }
} else { } else {
if (shell_exec("convert -background transparent -flatten -filter Point -scale {$this->width}x{$this->height} +antialias -quality {$quality} " . if ($config['convert_manual_orient'] && ($this->format == 'jpg' || $this->format == 'jpeg'))
escapeshellarg($this->src . '[0]') . " " . escapeshellarg($this->temp)) || !file_exists($this->temp)) $convert_args = str_replace('-auto-orient', ImageConvert::jpeg_exif_orientation($this->src), $config['convert_args']);
error('Failed to resize image!'); elseif ($config['convert_manual_orient'])
$convert_args = str_replace('-auto-orient', '', $config['convert_args']);
else
$convert_args = &$config['convert_args'];
if (($error = shell_exec_error(($this->gm ? 'gm ' : '') . 'convert ' .
sprintf($convert_args,
$this->width,
$this->height,
escapeshellarg($this->src . '[0]'),
$this->width,
$this->height,
escapeshellarg($this->temp)))) || !file_exists($this->temp))
error('Failed to resize image!', null, $error);
if ($size = $this->get_size($this->temp)) {
$this->width = $size[0];
$this->height = $size[1];
}
}
}
// For when -auto-orient doesn't exist (older versions)
static public function jpeg_exif_orientation($src, $exif = false) {
if (!$exif) {
$exif = @exif_read_data($src);
if (!isset($exif['Orientation']))
return false;
}
switch($exif['Orientation']) {
case 1:
// Normal
return false;
case 2:
// 888888
// 88
// 8888
// 88
// 88
return '-flop';
case 3:
// 88
// 88
// 8888
// 88
// 888888
return '-flip -flop';
case 4:
// 88
// 88
// 8888
// 88
// 888888
return '-flip';
case 5:
// 8888888888
// 88 88
// 88
return '-rotate 90 -flop';
case 6:
// 88
// 88 88
// 8888888888
return '-rotate 90';
case 7:
// 88
// 88 88
// 8888888888
return '-rotate "-90" -flop';
case 8:
// 8888888888
// 88 88
// 88
return '-rotate "-90"';
} }
} }
} }
@ -300,7 +438,7 @@ class ImagePNG extends ImageBase {
} }
public function to($src) { public function to($src) {
global $config; global $config;
imagepng($this->image, $src, $config['thumb_quality']); imagepng($this->image, $src);
} }
public function resize() { public function resize() {
$this->GD_create(); $this->GD_create();
@ -313,7 +451,7 @@ class ImagePNG extends ImageBase {
class ImageGIF extends ImageBase { class ImageGIF extends ImageBase {
public function from() { public function from() {
$this->image = @imagecreatefromgif ($this->src); $this->image = @imagecreatefromgif($this->src);
} }
public function to($src) { public function to($src) {
imagegif ($this->image, $src); imagegif ($this->image, $src);

136
inc/imgcaptcha.php

@ -1,136 +0,0 @@
<?php
// Wiem, ze ten kod to czysta ohyda. Coz.
require_once("inc/functions.php");
require_once("inc/ic-encrypt.php");
global $config;
function getImages() {
global $config;
$lines = explode("\n",file_get_contents($config["imgcaptcha_list"]));
for($i=0;$i<count($lines);$i++) { $lines[$i] = explode(",",$lines[$i]); }
return $lines;
}
function getIPath($img) {
global $config;
return $config["imgcaptcha_images"] . "/" . $img;
}
function pickImage($lines) {
$src = FALSE;
while($src == FALSE) {
$pick = rand(0,count($lines)-1);
if($lines[$pick][0] != "") $src = imagecreatefrompng(getIPath($lines[$pick][0]));
}
imagedestroy($src);
return $pick;
}
function ncfix($a) {
if($a>255) { return 255; }
if($a<0) { return 0; }
return $a;
}
function randString($length, $charset='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=')
{
$str = '';
$count = strlen($charset);
while ($length--) {
$str .= $charset[rand(0, $count-1)];
}
return $str;
}
function generateCaptchaHash() {
global $config;
$lines = getImages();
$pick = pickImage($lines);
$enctext = $pick . ",," . time() . ",," . $_SERVER["REMOTE_ADDR"] . ",," . randString(12);
$converter = new Encryption;
return $converter->encode($config["imgcaptcha_key"],$enctext);
}
function ic_verifyHash($enctext, $output) {
global $config;
//print "VERIFY: " . $enctext . " " . $output . "<br>";
$converter = new Encryption;
$dectext = explode(",,",$converter->decode($config["imgcaptcha_key"],$enctext));
if(count($dectext)<4) return true;
$lines = getImages();
$pick = $dectext[0];
$time = time()-$dectext[1];
if($time>$config["imgcaptcha_time_limit"]) return true;
$lp = $lines[$pick];
for($i=1;$i<count($lp);$i++) {
if(strcasecmp($lp[$i],$output)==0) return false;
}
return true;
}
function getPick($enctext)
{
global $config;
$converter = new Encryption;
$dectext = explode(",,",$converter->decode($config["imgcaptcha_key"],$enctext));
if(count($dectext)<=1) return; //SC
$lines = getImages();
return $dectext[0];
}
function generateImage($enctext)
{
global $config;
$lines = getImages();
$pick = getPick($enctext);
if(!isset($lines[$pick])) return; //SC
$src = imagecreatefrompng(getIPath($lines[$pick][0]));
if($src == FALSE) return; //SC
$maxc = 8;
$icw = $config["imgcaptcha_width"];
$ich = $config["imgcaptcha_height"];
$dst = imagecreatetruecolor($icw,$ich);
$srcxm = imagesx($src)-$icw;
$srcym = imagesy($src)-$ich;
$srcx = rand(0,$srcxm-1);
$srcy = rand(0,$srcym-1);
imagecopy($dst,$src,0,0,$srcx,$srcy,$icw,$ich);
// Obfuscation step 1
imagecopymergegray($dst,$dst,0,0,0,0,$icw,$ich,rand(20,45));
// Obfuscation step 1.5
for($i=0;$i<8;$i++) {
$w = rand(5,10); $h = rand(5,10);
$x = rand(0,$icw-1-$w); $y = rand(0,$ich-1-$h);
$x2 = rand(0,$icw-1); $y2 = rand(0,$ich-1);
imagefilledrectangle($dst,$x,$y,$x+$w,$y+$h,imagecolorat($dst,$x2,$y2));
}
for($i=0;$i<5;$i++) {
$w = rand(20,40); $h = rand(20,40);
$x = rand(0,$icw-1-$w); $y = rand(0,$ich-1-$h);
imagecopymergegray($dst,$dst,$x,$y,$x,$y,$w,$h,0);
}
// Obfuscation step 2
for($i=0;$i<$icw*$ich;$i++) {
$x = $i%$icw; $y = $i/$icw;
$c = imagecolorat($dst,$x,$y);
if(rand(0,4) == 2) { $nc = $c ^ rand(0,16777215); }
else { $nc = imagecolorat($dst,rand(0,$icw-1),rand(0,$ich-1)); }
if(rand(18,24)!=21 and $c != 0 and $c != 0xFF00FF)
{
$nc = ncfix(($c&0xFF) + rand(-16,16)) | ncfix((($c>>8)&0xFF) + rand(-8,8))<<8 | ncfix((($c>>16)&0xFF) + rand(-32,32))<<16;
$nc1 = $nc&0xFF ^ ($nc>>8)&0xFF ^ ($nc>>16)&0xFF;
} else {
$nc1 = $nc&0xFF;
if($nc1>($maxc*25)) $nc1 = $nc % ($maxc*25);
}
$nc2 = $nc1 | $nc1<<8 | $nc1<<16;
if(rand(0,1)==0) $nc2=$nc;
imagesetpixel($dst,$x,$y,$nc2);
}
// Obfuscation step 3
for($i=0;$i<rand(10,30);$i++) {
$x1 = rand(0,$icw-1); $x2 = rand(0,$icw-1); $y1 = rand(0,$ich-1); $y2 = rand(0,$ich-1);
$color = imagecolorallocate($dst, rand(0,$maxc)*25, rand(0,$maxc)*25, rand(0,$maxc)*25);
imageline($dst,$x1,$y1,$x2,$y2,$color);
}
imagepng($dst);
}
//header('Content-Type: image/png');
//$t = generateCaptchaHash();
//generateImage($t);
?>

20
inc/lib/Twig/Autoloader.php

@ -12,28 +12,30 @@
/** /**
* Autoloads Twig classes. * Autoloads Twig classes.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Autoloader class Twig_Autoloader
{ {
/** /**
* Registers Twig_Autoloader as an SPL autoloader. * Registers Twig_Autoloader as an SPL autoloader.
*
* @param Boolean $prepend Whether to prepend the autoloader or not.
*/ */
static public function register() public static function register($prepend = false)
{ {
ini_set('unserialize_callback_func', 'spl_autoload_call'); if (version_compare(phpversion(), '5.3.0', '>=')) {
spl_autoload_register(array(new self, 'autoload')); spl_autoload_register(array(new self, 'autoload'), true, $prepend);
} else {
spl_autoload_register(array(new self, 'autoload'));
}
} }
/** /**
* Handles autoloading of classes. * Handles autoloading of classes.
* *
* @param string $class A class name. * @param string $class A class name.
*
* @return boolean Returns true if the class has been loaded
*/ */
static public function autoload($class) public static function autoload($class)
{ {
if (0 !== strpos($class, 'Twig')) { if (0 !== strpos($class, 'Twig')) {
return; return;

82
inc/lib/Twig/Compiler.php

@ -13,8 +13,7 @@
/** /**
* Compiles a node to PHP code. * Compiles a node to PHP code.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Compiler implements Twig_CompilerInterface class Twig_Compiler implements Twig_CompilerInterface
{ {
@ -22,6 +21,10 @@ class Twig_Compiler implements Twig_CompilerInterface
protected $source; protected $source;
protected $indentation; protected $indentation;
protected $env; protected $env;
protected $debugInfo;
protected $sourceOffset;
protected $sourceLine;
protected $filename;
/** /**
* Constructor. * Constructor.
@ -31,6 +34,12 @@ class Twig_Compiler implements Twig_CompilerInterface
public function __construct(Twig_Environment $env) public function __construct(Twig_Environment $env)
{ {
$this->env = $env; $this->env = $env;
$this->debugInfo = array();
}
public function getFilename()
{
return $this->filename;
} }
/** /**
@ -56,8 +65,8 @@ class Twig_Compiler implements Twig_CompilerInterface
/** /**
* Compiles a node. * Compiles a node.
* *
* @param Twig_NodeInterface $node The node to compile * @param Twig_NodeInterface $node The node to compile
* @param integer $indent The current indentation * @param integer $indentation The current indentation
* *
* @return Twig_Compiler The current compiler instance * @return Twig_Compiler The current compiler instance
*/ */
@ -65,8 +74,15 @@ class Twig_Compiler implements Twig_CompilerInterface
{ {
$this->lastLine = null; $this->lastLine = null;
$this->source = ''; $this->source = '';
$this->sourceOffset = 0;
// source code starts at 1 (as we then increment it when we encounter new lines)
$this->sourceLine = 1;
$this->indentation = $indentation; $this->indentation = $indentation;
if ($node instanceof Twig_Node_Module) {
$this->filename = $node->getAttribute('filename');
}
$node->compile($this); $node->compile($this);
return $this; return $this;
@ -86,7 +102,7 @@ class Twig_Compiler implements Twig_CompilerInterface
/** /**
* Adds a raw string to the compiled code. * Adds a raw string to the compiled code.
* *
* @param string $string The string * @param string $string The string
* *
* @return Twig_Compiler The current compiler instance * @return Twig_Compiler The current compiler instance
*/ */
@ -113,6 +129,11 @@ class Twig_Compiler implements Twig_CompilerInterface
return $this; return $this;
} }
/**
* Appends an indentation to the current PHP code after compilation.
*
* @return Twig_Compiler The current compiler instance
*/
public function addIndentation() public function addIndentation()
{ {
$this->source .= str_repeat(' ', $this->indentation * 4); $this->source .= str_repeat(' ', $this->indentation * 4);
@ -123,7 +144,7 @@ class Twig_Compiler implements Twig_CompilerInterface
/** /**
* Adds a quoted string to the compiled code. * Adds a quoted string to the compiled code.
* *
* @param string $string The string * @param string $value The string
* *
* @return Twig_Compiler The current compiler instance * @return Twig_Compiler The current compiler instance
*/ */
@ -137,19 +158,27 @@ class Twig_Compiler implements Twig_CompilerInterface
/** /**
* Returns a PHP representation of a given value. * Returns a PHP representation of a given value.
* *
* @param mixed $value The value to convert * @param mixed $value The value to convert
* *
* @return Twig_Compiler The current compiler instance * @return Twig_Compiler The current compiler instance
*/ */
public function repr($value) public function repr($value)
{ {
if (is_int($value) || is_float($value)) { if (is_int($value) || is_float($value)) {
if (false !== $locale = setlocale(LC_NUMERIC, 0)) {
setlocale(LC_NUMERIC, 'C');
}
$this->raw($value); $this->raw($value);
} else if (null === $value) {
if (false !== $locale) {
setlocale(LC_NUMERIC, $locale);
}
} elseif (null === $value) {
$this->raw('null'); $this->raw('null');
} else if (is_bool($value)) { } elseif (is_bool($value)) {
$this->raw($value ? 'true' : 'false'); $this->raw($value ? 'true' : 'false');
} else if (is_array($value)) { } elseif (is_array($value)) {
$this->raw('array('); $this->raw('array(');
$i = 0; $i = 0;
foreach ($value as $key => $value) { foreach ($value as $key => $value) {
@ -178,17 +207,35 @@ class Twig_Compiler implements Twig_CompilerInterface
public function addDebugInfo(Twig_NodeInterface $node) public function addDebugInfo(Twig_NodeInterface $node)
{ {
if ($node->getLine() != $this->lastLine) { if ($node->getLine() != $this->lastLine) {
$this->lastLine = $node->getLine();
$this->write("// line {$node->getLine()}\n"); $this->write("// line {$node->getLine()}\n");
// when mbstring.func_overload is set to 2
// mb_substr_count() replaces substr_count()
// but they have different signatures!
if (((int) ini_get('mbstring.func_overload')) & 2) {
// this is much slower than the "right" version
$this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n");
} else {
$this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
}
$this->sourceOffset = strlen($this->source);
$this->debugInfo[$this->sourceLine] = $node->getLine();
$this->lastLine = $node->getLine();
} }
return $this; return $this;
} }
public function getDebugInfo()
{
return $this->debugInfo;
}
/** /**
* Indents the generated code. * Indents the generated code.
* *
* @param integer $indent The number of indentation to add * @param integer $step The number of indentation to add
* *
* @return Twig_Compiler The current compiler instance * @return Twig_Compiler The current compiler instance
*/ */
@ -202,18 +249,19 @@ class Twig_Compiler implements Twig_CompilerInterface
/** /**
* Outdents the generated code. * Outdents the generated code.
* *
* @param integer $indent The number of indentation to remove * @param integer $step The number of indentation to remove
* *
* @return Twig_Compiler The current compiler instance * @return Twig_Compiler The current compiler instance
*/ */
public function outdent($step = 1) public function outdent($step = 1)
{ {
$this->indentation -= $step; // can't outdent by more steps than the current indentation level
if ($this->indentation < $step) {
if ($this->indentation < 0) { throw new LogicException('Unable to call outdent() as the indentation would become negative');
throw new Twig_Error('Unable to call outdent() as the indentation would become negative');
} }
$this->indentation -= $step;
return $this; return $this;
} }
} }

10
inc/lib/Twig/CompilerInterface.php

@ -12,24 +12,24 @@
/** /**
* Interface implemented by compiler classes. * Interface implemented by compiler classes.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com> * @deprecated since 1.12 (to be removed in 2.0)
*/ */
interface Twig_CompilerInterface interface Twig_CompilerInterface
{ {
/** /**
* Compiles a node. * Compiles a node.
* *
* @param Twig_NodeInterface $node The node to compile * @param Twig_NodeInterface $node The node to compile
* *
* @return Twig_CompilerInterface The current compiler instance * @return Twig_CompilerInterface The current compiler instance
*/ */
function compile(Twig_NodeInterface $node); public function compile(Twig_NodeInterface $node);
/** /**
* Gets the current PHP code after compilation. * Gets the current PHP code after compilation.
* *
* @return string The PHP code * @return string The PHP code
*/ */
function getSource(); public function getSource();
} }

510
inc/lib/Twig/Environment.php

@ -12,12 +12,11 @@
/** /**
* Stores the Twig configuration. * Stores the Twig configuration.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Environment class Twig_Environment
{ {
const VERSION = '1.2.0'; const VERSION = '1.13.1';
protected $charset; protected $charset;
protected $loader; protected $loader;
@ -36,6 +35,7 @@ class Twig_Environment
protected $functions; protected $functions;
protected $globals; protected $globals;
protected $runtimeInitialized; protected $runtimeInitialized;
protected $extensionInitialized;
protected $loadedTemplates; protected $loadedTemplates;
protected $strictVariables; protected $strictVariables;
protected $unaryOperators; protected $unaryOperators;
@ -43,23 +43,23 @@ class Twig_Environment
protected $templateClassPrefix = '__TwigTemplate_'; protected $templateClassPrefix = '__TwigTemplate_';
protected $functionCallbacks; protected $functionCallbacks;
protected $filterCallbacks; protected $filterCallbacks;
protected $staging;
/** /**
* Constructor. * Constructor.
* *
* Available options: * Available options:
* *
* * debug: When set to `true`, the generated templates have a __toString() * * debug: When set to true, it automatically set "auto_reload" to true as
* method that you can use to display the generated nodes (default to * well (default to false).
* false).
* *
* * charset: The charset used by the templates (default to utf-8). * * charset: The charset used by the templates (default to UTF-8).
* *
* * base_template_class: The base template class to use for generated * * base_template_class: The base template class to use for generated
* templates (default to Twig_Template). * templates (default to Twig_Template).
* *
* * cache: An absolute path where to store the compiled templates, or * * cache: An absolute path where to store the compiled templates, or
* false to disable compilation cache (default) * false to disable compilation cache (default).
* *
* * auto_reload: Whether to reload the template is the original source changed. * * auto_reload: Whether to reload the template is the original source changed.
* If you don't provide the auto_reload option, it will be * If you don't provide the auto_reload option, it will be
@ -68,14 +68,18 @@ class Twig_Environment
* * strict_variables: Whether to ignore invalid variables in templates * * strict_variables: Whether to ignore invalid variables in templates
* (default to false). * (default to false).
* *
* * autoescape: Whether to enable auto-escaping (default to true); * * autoescape: Whether to enable auto-escaping (default to html):
* * false: disable auto-escaping
* * true: equivalent to html
* * html, js: set the autoescaping to one of the supported strategies
* * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename"
* *
* * optimizations: A flag that indicates which optimizations to apply * * optimizations: A flag that indicates which optimizations to apply
* (default to -1 which means that all optimizations are enabled; * (default to -1 which means that all optimizations are enabled;
* set it to 0 to disable) * set it to 0 to disable).
* *
* @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
* @param array $options An array of options * @param array $options An array of options
*/ */
public function __construct(Twig_LoaderInterface $loader = null, $options = array()) public function __construct(Twig_LoaderInterface $loader = null, $options = array())
{ {
@ -88,26 +92,27 @@ class Twig_Environment
'charset' => 'UTF-8', 'charset' => 'UTF-8',
'base_template_class' => 'Twig_Template', 'base_template_class' => 'Twig_Template',
'strict_variables' => false, 'strict_variables' => false,
'autoescape' => true, 'autoescape' => 'html',
'cache' => false, 'cache' => false,
'auto_reload' => null, 'auto_reload' => null,
'optimizations' => -1, 'optimizations' => -1,
), $options); ), $options);
$this->debug = (bool) $options['debug']; $this->debug = (bool) $options['debug'];
$this->charset = $options['charset']; $this->charset = strtoupper($options['charset']);
$this->baseTemplateClass = $options['base_template_class']; $this->baseTemplateClass = $options['base_template_class'];
$this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
$this->extensions = array(
'core' => new Twig_Extension_Core(),
'escaper' => new Twig_Extension_Escaper((bool) $options['autoescape']),
'optimizer' => new Twig_Extension_Optimizer($options['optimizations']),
);
$this->strictVariables = (bool) $options['strict_variables']; $this->strictVariables = (bool) $options['strict_variables'];
$this->runtimeInitialized = false; $this->runtimeInitialized = false;
$this->setCache($options['cache']); $this->setCache($options['cache']);
$this->functionCallbacks = array(); $this->functionCallbacks = array();
$this->filterCallbacks = array(); $this->filterCallbacks = array();
$this->addExtension(new Twig_Extension_Core());
$this->addExtension(new Twig_Extension_Escaper($options['autoescape']));
$this->addExtension(new Twig_Extension_Optimizer($options['optimizations']));
$this->extensionInitialized = false;
$this->staging = new Twig_Extension_Staging();
} }
/** /**
@ -250,13 +255,14 @@ class Twig_Environment
/** /**
* Gets the template class associated with the given string. * Gets the template class associated with the given string.
* *
* @param string $name The name for which to calculate the template class name * @param string $name The name for which to calculate the template class name
* @param integer $index The index if it is an embedded template
* *
* @return string The template class name * @return string The template class name
*/ */
public function getTemplateClass($name) public function getTemplateClass($name, $index = null)
{ {
return $this->templateClassPrefix.md5($this->loader->getCacheKey($name)); return $this->templateClassPrefix.md5($this->getLoader()->getCacheKey($name)).(null === $index ? '' : '_'.$index);
} }
/** /**
@ -282,16 +288,28 @@ class Twig_Environment
return $this->loadTemplate($name)->render($context); return $this->loadTemplate($name)->render($context);
} }
/**
* Displays a template.
*
* @param string $name The template name
* @param array $context An array of parameters to pass to the template
*/
public function display($name, array $context = array())
{
$this->loadTemplate($name)->display($context);
}
/** /**
* Loads a template by name. * Loads a template by name.
* *
* @param string $name The template name * @param string $name The template name
* @param integer $index The index if it is an embedded template
* *
* @return Twig_TemplateInterface A template instance representing the given template name * @return Twig_TemplateInterface A template instance representing the given template name
*/ */
public function loadTemplate($name) public function loadTemplate($name, $index = null)
{ {
$cls = $this->getTemplateClass($name); $cls = $this->getTemplateClass($name, $index);
if (isset($this->loadedTemplates[$cls])) { if (isset($this->loadedTemplates[$cls])) {
return $this->loadedTemplates[$cls]; return $this->loadedTemplates[$cls];
@ -299,10 +317,10 @@ class Twig_Environment
if (!class_exists($cls, false)) { if (!class_exists($cls, false)) {
if (false === $cache = $this->getCacheFilename($name)) { if (false === $cache = $this->getCacheFilename($name)) {
eval('?>'.$this->compileSource($this->loader->getSource($name), $name)); eval('?>'.$this->compileSource($this->getLoader()->getSource($name), $name));
} else { } else {
if (!is_file($cache) || ($this->isAutoReload() && !$this->loader->isFresh($name, filemtime($cache)))) { if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
$this->writeCacheFile($cache, $this->compileSource($this->loader->getSource($name), $name)); $this->writeCacheFile($cache, $this->compileSource($this->getLoader()->getSource($name), $name));
} }
require_once $cache; require_once $cache;
@ -316,6 +334,30 @@ class Twig_Environment
return $this->loadedTemplates[$cls] = new $cls($this); return $this->loadedTemplates[$cls] = new $cls($this);
} }
/**
* Returns true if the template is still fresh.
*
* Besides checking the loader for freshness information,
* this method also checks if the enabled extensions have
* not changed.
*
* @param string $name The template name
* @param timestamp $time The last modification time of the cached template
*
* @return Boolean true if the template is fresh, false otherwise
*/
public function isTemplateFresh($name, $time)
{
foreach ($this->extensions as $extension) {
$r = new ReflectionObject($extension);
if (filemtime($r->getFileName()) > $time) {
return false;
}
}
return $this->getLoader()->isFresh($name, $time);
}
public function resolveTemplate($names) public function resolveTemplate($names)
{ {
if (!is_array($names)) { if (!is_array($names)) {
@ -510,6 +552,10 @@ class Twig_Environment
*/ */
public function getLoader() public function getLoader()
{ {
if (null === $this->loader) {
throw new LogicException('You must set a loader first.');
}
return $this->loader; return $this->loader;
} }
@ -520,7 +566,7 @@ class Twig_Environment
*/ */
public function setCharset($charset) public function setCharset($charset)
{ {
$this->charset = $charset; $this->charset = strtoupper($charset);
} }
/** /**
@ -580,16 +626,28 @@ class Twig_Environment
*/ */
public function addExtension(Twig_ExtensionInterface $extension) public function addExtension(Twig_ExtensionInterface $extension)
{ {
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName()));
}
$this->extensions[$extension->getName()] = $extension; $this->extensions[$extension->getName()] = $extension;
} }
/** /**
* Removes an extension by name. * Removes an extension by name.
* *
* This method is deprecated and you should not use it.
*
* @param string $name The extension name * @param string $name The extension name
*
* @deprecated since 1.12 (to be removed in 2.0)
*/ */
public function removeExtension($name) public function removeExtension($name)
{ {
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name));
}
unset($this->extensions[$name]); unset($this->extensions[$name]);
} }
@ -622,39 +680,46 @@ class Twig_Environment
*/ */
public function addTokenParser(Twig_TokenParserInterface $parser) public function addTokenParser(Twig_TokenParserInterface $parser)
{ {
if (null === $this->parsers) { if ($this->extensionInitialized) {
$this->getTokenParsers(); throw new LogicException('Unable to add a token parser as extensions have already been initialized.');
} }
$this->parsers->addTokenParser($parser); $this->staging->addTokenParser($parser);
} }
/** /**
* Gets the registered Token Parsers. * Gets the registered Token Parsers.
* *
* @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances * @return Twig_TokenParserBrokerInterface A broker containing token parsers
*/ */
public function getTokenParsers() public function getTokenParsers()
{ {
if (null === $this->parsers) { if (!$this->extensionInitialized) {
$this->parsers = new Twig_TokenParserBroker; $this->initExtensions();
foreach ($this->getExtensions() as $extension) {
$parsers = $extension->getTokenParsers();
foreach($parsers as $parser) {
if ($parser instanceof Twig_TokenParserInterface) {
$this->parsers->addTokenParser($parser);
} else if ($parser instanceof Twig_TokenParserBrokerInterface) {
$this->parsers->addTokenParserBroker($parser);
} else {
throw new Twig_Error_Runtime('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
}
}
}
} }
return $this->parsers; return $this->parsers;
} }
/**
* Gets registered tags.
*
* Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
*
* @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances
*/
public function getTags()
{
$tags = array();
foreach ($this->getTokenParsers()->getParsers() as $parser) {
if ($parser instanceof Twig_TokenParserInterface) {
$tags[$parser->getTag()] = $parser;
}
}
return $tags;
}
/** /**
* Registers a Node Visitor. * Registers a Node Visitor.
* *
@ -662,11 +727,11 @@ class Twig_Environment
*/ */
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
{ {
if (null === $this->visitors) { if ($this->extensionInitialized) {
$this->getNodeVisitors(); throw new LogicException('Unable to add a node visitor as extensions have already been initialized.', $extension->getName());
} }
$this->visitors[] = $visitor; $this->staging->addNodeVisitor($visitor);
} }
/** /**
@ -676,11 +741,8 @@ class Twig_Environment
*/ */
public function getNodeVisitors() public function getNodeVisitors()
{ {
if (null === $this->visitors) { if (!$this->extensionInitialized) {
$this->visitors = array(); $this->initExtensions();
foreach ($this->getExtensions() as $extension) {
$this->visitors = array_merge($this->visitors, $extension->getNodeVisitors());
}
} }
return $this->visitors; return $this->visitors;
@ -689,16 +751,25 @@ class Twig_Environment
/** /**
* Registers a Filter. * Registers a Filter.
* *
* @param string $name The filter name * @param string|Twig_SimpleFilter $name The filter name or a Twig_SimpleFilter instance
* @param Twig_FilterInterface $visitor A Twig_FilterInterface instance * @param Twig_FilterInterface|Twig_SimpleFilter $filter A Twig_FilterInterface instance or a Twig_SimpleFilter instance
*/ */
public function addFilter($name, Twig_FilterInterface $filter) public function addFilter($name, $filter = null)
{ {
if (null === $this->filters) { if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) {
$this->loadFilters(); throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter');
} }
$this->filters[$name] = $filter; if ($name instanceof Twig_SimpleFilter) {
$filter = $name;
$name = $filter->getName();
}
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name));
}
$this->staging->addFilter($name, $filter);
} }
/** /**
@ -709,18 +780,31 @@ class Twig_Environment
* *
* @param string $name The filter name * @param string $name The filter name
* *
* @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exists * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist
*/ */
public function getFilter($name) public function getFilter($name)
{ {
if (null === $this->filters) { if (!$this->extensionInitialized) {
$this->loadFilters(); $this->initExtensions();
} }
if (isset($this->filters[$name])) { if (isset($this->filters[$name])) {
return $this->filters[$name]; return $this->filters[$name];
} }
foreach ($this->filters as $pattern => $filter) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count) {
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$filter->setArguments($matches);
return $filter;
}
}
}
foreach ($this->filterCallbacks as $callback) { foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = call_user_func($callback, $name)) { if (false !== $filter = call_user_func($callback, $name)) {
return $filter; return $filter;
@ -738,29 +822,43 @@ class Twig_Environment
/** /**
* Gets the registered Filters. * Gets the registered Filters.
* *
* Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback.
*
* @return Twig_FilterInterface[] An array of Twig_FilterInterface instances * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances
*
* @see registerUndefinedFilterCallback
*/ */
protected function loadFilters() public function getFilters()
{ {
$this->filters = array(); if (!$this->extensionInitialized) {
foreach ($this->getExtensions() as $extension) { $this->initExtensions();
$this->filters = array_merge($this->filters, $extension->getFilters());
} }
return $this->filters;
} }
/** /**
* Registers a Test. * Registers a Test.
* *
* @param string $name The test name * @param string|Twig_SimpleTest $name The test name or a Twig_SimpleTest instance
* @param Twig_TestInterface $visitor A Twig_TestInterface instance * @param Twig_TestInterface|Twig_SimpleTest $test A Twig_TestInterface instance or a Twig_SimpleTest instance
*/ */
public function addTest($name, Twig_TestInterface $test) public function addTest($name, $test = null)
{ {
if (null === $this->tests) { if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) {
$this->getTests(); throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest');
} }
$this->tests[$name] = $test; if ($name instanceof Twig_SimpleTest) {
$test = $name;
$name = $test->getName();
}
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name));
}
$this->staging->addTest($name, $test);
} }
/** /**
@ -770,29 +868,55 @@ class Twig_Environment
*/ */
public function getTests() public function getTests()
{ {
if (null === $this->tests) { if (!$this->extensionInitialized) {
$this->tests = array(); $this->initExtensions();
foreach ($this->getExtensions() as $extension) {
$this->tests = array_merge($this->tests, $extension->getTests());
}
} }
return $this->tests; return $this->tests;
} }
/**
* Gets a test by name.
*
* @param string $name The test name
*
* @return Twig_Test|false A Twig_Test instance or false if the test does not exist
*/
public function getTest($name)
{
if (!$this->extensionInitialized) {
$this->initExtensions();
}
if (isset($this->tests[$name])) {
return $this->tests[$name];
}
return false;
}
/** /**
* Registers a Function. * Registers a Function.
* *
* @param string $name The function name * @param string|Twig_SimpleFunction $name The function name or a Twig_SimpleFunction instance
* @param Twig_FunctionInterface $function A Twig_FunctionInterface instance * @param Twig_FunctionInterface|Twig_SimpleFunction $function A Twig_FunctionInterface instance or a Twig_SimpleFunction instance
*/ */
public function addFunction($name, Twig_FunctionInterface $function) public function addFunction($name, $function = null)
{ {
if (null === $this->functions) { if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) {
$this->loadFunctions(); throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction');
} }
$this->functions[$name] = $function; if ($name instanceof Twig_SimpleFunction) {
$function = $name;
$name = $function->getName();
}
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name));
}
$this->staging->addFunction($name, $function);
} }
/** /**
@ -803,18 +927,31 @@ class Twig_Environment
* *
* @param string $name function name * @param string $name function name
* *
* @return Twig_Function|false A Twig_Function instance or false if the function does not exists * @return Twig_Function|false A Twig_Function instance or false if the function does not exist
*/ */
public function getFunction($name) public function getFunction($name)
{ {
if (null === $this->functions) { if (!$this->extensionInitialized) {
$this->loadFunctions(); $this->initExtensions();
} }
if (isset($this->functions[$name])) { if (isset($this->functions[$name])) {
return $this->functions[$name]; return $this->functions[$name];
} }
foreach ($this->functions as $pattern => $function) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count) {
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$function->setArguments($matches);
return $function;
}
}
}
foreach ($this->functionCallbacks as $callback) { foreach ($this->functionCallbacks as $callback) {
if (false !== $function = call_user_func($callback, $name)) { if (false !== $function = call_user_func($callback, $name)) {
return $function; return $function;
@ -829,27 +966,53 @@ class Twig_Environment
$this->functionCallbacks[] = $callable; $this->functionCallbacks[] = $callable;
} }
protected function loadFunctions() /**
* Gets registered functions.
*
* Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
*
* @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances
*
* @see registerUndefinedFunctionCallback
*/
public function getFunctions()
{ {
$this->functions = array(); if (!$this->extensionInitialized) {
foreach ($this->getExtensions() as $extension) { $this->initExtensions();
$this->functions = array_merge($this->functions, $extension->getFunctions());
} }
return $this->functions;
} }
/** /**
* Registers a Global. * Registers a Global.
* *
* New globals can be added before compiling or rendering a template;
* but after, you can only update existing globals.
*
* @param string $name The global name * @param string $name The global name
* @param mixed $value The global value * @param mixed $value The global value
*/ */
public function addGlobal($name, $value) public function addGlobal($name, $value)
{ {
if (null === $this->globals) { if ($this->extensionInitialized || $this->runtimeInitialized) {
$this->getGlobals(); if (null === $this->globals) {
$this->globals = $this->initGlobals();
}
/* This condition must be uncommented in Twig 2.0
if (!array_key_exists($name, $this->globals)) {
throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
}
*/
} }
$this->globals[$name] = $value; if ($this->extensionInitialized || $this->runtimeInitialized) {
// update the value
$this->globals[$name] = $value;
} else {
$this->staging->addGlobal($name, $value);
}
} }
/** /**
@ -859,16 +1022,37 @@ class Twig_Environment
*/ */
public function getGlobals() public function getGlobals()
{ {
if (!$this->runtimeInitialized && !$this->extensionInitialized) {
return $this->initGlobals();
}
if (null === $this->globals) { if (null === $this->globals) {
$this->globals = array(); $this->globals = $this->initGlobals();
foreach ($this->getExtensions() as $extension) {
$this->globals = array_merge($this->globals, $extension->getGlobals());
}
} }
return $this->globals; return $this->globals;
} }
/**
* Merges a context with the defined globals.
*
* @param array $context An array representing the context
*
* @return array The context merged with the globals
*/
public function mergeGlobals(array $context)
{
// we don't use array_merge as the context being generally
// bigger than globals, this code is faster.
foreach ($this->getGlobals() as $key => $value) {
if (!array_key_exists($key, $context)) {
$context[$key] = $value;
}
}
return $context;
}
/** /**
* Gets the registered unary Operators. * Gets the registered unary Operators.
* *
@ -876,8 +1060,8 @@ class Twig_Environment
*/ */
public function getUnaryOperators() public function getUnaryOperators()
{ {
if (null === $this->unaryOperators) { if (!$this->extensionInitialized) {
$this->initOperators(); $this->initExtensions();
} }
return $this->unaryOperators; return $this->unaryOperators;
@ -890,24 +1074,121 @@ class Twig_Environment
*/ */
public function getBinaryOperators() public function getBinaryOperators()
{ {
if (null === $this->binaryOperators) { if (!$this->extensionInitialized) {
$this->initOperators(); $this->initExtensions();
} }
return $this->binaryOperators; return $this->binaryOperators;
} }
protected function initOperators() public function computeAlternatives($name, $items)
{
$alternatives = array();
foreach ($items as $item) {
$lev = levenshtein($name, $item);
if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
$alternatives[$item] = $lev;
}
}
asort($alternatives);
return array_keys($alternatives);
}
protected function initGlobals()
{
$globals = array();
foreach ($this->extensions as $extension) {
$extGlob = $extension->getGlobals();
if (!is_array($extGlob)) {
throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension)));
}
$globals[] = $extGlob;
}
$globals[] = $this->staging->getGlobals();
return call_user_func_array('array_merge', $globals);
}
protected function initExtensions()
{ {
if ($this->extensionInitialized) {
return;
}
$this->extensionInitialized = true;
$this->parsers = new Twig_TokenParserBroker();
$this->filters = array();
$this->functions = array();
$this->tests = array();
$this->visitors = array();
$this->unaryOperators = array(); $this->unaryOperators = array();
$this->binaryOperators = array(); $this->binaryOperators = array();
foreach ($this->getExtensions() as $extension) {
$operators = $extension->getOperators();
if (!$operators) { foreach ($this->extensions as $extension) {
continue; $this->initExtension($extension);
}
$this->initExtension($this->staging);
}
protected function initExtension(Twig_ExtensionInterface $extension)
{
// filters
foreach ($extension->getFilters() as $name => $filter) {
if ($name instanceof Twig_SimpleFilter) {
$filter = $name;
$name = $filter->getName();
} elseif ($filter instanceof Twig_SimpleFilter) {
$name = $filter->getName();
}
$this->filters[$name] = $filter;
}
// functions
foreach ($extension->getFunctions() as $name => $function) {
if ($name instanceof Twig_SimpleFunction) {
$function = $name;
$name = $function->getName();
} elseif ($function instanceof Twig_SimpleFunction) {
$name = $function->getName();
}
$this->functions[$name] = $function;
}
// tests
foreach ($extension->getTests() as $name => $test) {
if ($name instanceof Twig_SimpleTest) {
$test = $name;
$name = $test->getName();
} elseif ($test instanceof Twig_SimpleTest) {
$name = $test->getName();
} }
$this->tests[$name] = $test;
}
// token parsers
foreach ($extension->getTokenParsers() as $parser) {
if ($parser instanceof Twig_TokenParserInterface) {
$this->parsers->addTokenParser($parser);
} elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
$this->parsers->addTokenParserBroker($parser);
} else {
throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
}
}
// node visitors
foreach ($extension->getNodeVisitors() as $visitor) {
$this->visitors[] = $visitor;
}
// operators
if ($operators = $extension->getOperators()) {
if (2 !== count($operators)) { if (2 !== count($operators)) {
throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension))); throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension)));
} }
@ -919,20 +1200,25 @@ class Twig_Environment
protected function writeCacheFile($file, $content) protected function writeCacheFile($file, $content)
{ {
if (!is_dir(dirname($file))) { $dir = dirname($file);
mkdir(dirname($file), 0777, true); if (!is_dir($dir)) {
if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
}
} elseif (!is_writable($dir)) {
throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
} }
$tmpFile = tempnam(dirname($file), basename($file)); $tmpFile = tempnam(dirname($file), basename($file));
if (false !== @file_put_contents($tmpFile, $content)) { if (false !== @file_put_contents($tmpFile, $content)) {
// rename does not work on Win32 before 5.2.6 // rename does not work on Win32 before 5.2.6
if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) { if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {
chmod($file, 0644); @chmod($file, 0666 & ~umask());
return; return;
} }
} }
throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file)); throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file));
} }
} }

144
inc/lib/Twig/Error.php

@ -12,8 +12,24 @@
/** /**
* Twig base exception. * Twig base exception.
* *
* @package twig * This exception class and its children must only be used when
* @author Fabien Potencier <fabien@symfony.com> * an error occurs during the loading of a template, when a syntax error
* is detected in a template, or when rendering a template. Other
* errors must use regular PHP exception classes (like when the template
* cache directory is not writable for instance).
*
* To help debugging template issues, this class tracks the original template
* name and line where the error occurred.
*
* Whenever possible, you must set these information (original template name
* and line number) yourself by passing them to the constructor. If some or all
* these information are not available from where you throw the exception, then
* this class will guess them automatically (when the line number is set to -1
* and/or the filename is set to null). As this is a costly operation, this
* can be disabled by passing false for both the filename and the line number
* when creating a new instance of this class.
*
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Error extends Exception class Twig_Error extends Exception
{ {
@ -25,6 +41,15 @@ class Twig_Error extends Exception
/** /**
* Constructor. * Constructor.
* *
* Set both the line number and the filename to false to
* disable automatic guessing of the original template name
* and line number.
*
* Set the line number to -1 to enable its automatic guessing.
* Set the filename to null to enable its automatic guessing.
*
* By default, automatic guessing is enabled.
*
* @param string $message The error message * @param string $message The error message
* @param integer $lineno The template line where the error occurred * @param integer $lineno The template line where the error occurred
* @param string $filename The template file name where the error occurred * @param string $filename The template file name where the error occurred
@ -32,22 +57,23 @@ class Twig_Error extends Exception
*/ */
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null) public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
{ {
if (-1 === $lineno || null === $filename) { if (version_compare(PHP_VERSION, '5.3.0', '<')) {
list($lineno, $filename) = $this->findTemplateInfo(null !== $previous ? $previous : $this, $lineno, $filename); $this->previous = $previous;
parent::__construct('');
} else {
parent::__construct('', 0, $previous);
} }
$this->lineno = $lineno; $this->lineno = $lineno;
$this->filename = $filename; $this->filename = $filename;
if (-1 === $this->lineno || null === $this->filename) {
$this->guessTemplateInfo();
}
$this->rawMessage = $message; $this->rawMessage = $message;
$this->updateRepr(); $this->updateRepr();
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
$this->previous = $previous;
parent::__construct($this->message);
} else {
parent::__construct($this->message, 0, $previous);
}
} }
/** /**
@ -104,13 +130,21 @@ class Twig_Error extends Exception
$this->updateRepr(); $this->updateRepr();
} }
public function guess()
{
$this->guessTemplateInfo();
$this->updateRepr();
}
/** /**
* For PHP < 5.3.0, provides access to the getPrevious() method. * For PHP < 5.3.0, provides access to the getPrevious() method.
* *
* @param string $method The method name * @param string $method The method name
* @param array $arguments The parameters to be passed to the method * @param array $arguments The parameters to be passed to the method
* *
* @return Exception The previous exception or null * @return Exception The previous exception or null
*
* @throws BadMethodCallException
*/ */
public function __call($method, $arguments) public function __call($method, $arguments)
{ {
@ -131,11 +165,16 @@ class Twig_Error extends Exception
$dot = true; $dot = true;
} }
if (null !== $this->filename) { if ($this->filename) {
$this->message .= sprintf(' in %s', json_encode($this->filename)); if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) {
$filename = sprintf('"%s"', $this->filename);
} else {
$filename = json_encode($this->filename);
}
$this->message .= sprintf(' in %s', $filename);
} }
if ($this->lineno >= 0) { if ($this->lineno && $this->lineno >= 0) {
$this->message .= sprintf(' at line %d', $this->lineno); $this->message .= sprintf(' at line %d', $this->lineno);
} }
@ -144,52 +183,57 @@ class Twig_Error extends Exception
} }
} }
protected function findTemplateInfo(Exception $e, $currentLine, $currentFile) protected function guessTemplateInfo()
{ {
if (!function_exists('token_get_all')) { $template = null;
return array($currentLine, $currentFile);
if (version_compare(phpversion(), '5.3.6', '>=')) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
} else {
$backtrace = debug_backtrace();
} }
$traces = $e->getTrace(); foreach ($backtrace as $trace) {
foreach ($traces as $i => $trace) { if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) {
if (!isset($trace['class']) || 'Twig_Template' === $trace['class']) { if (null === $this->filename || $this->filename == $trace['object']->getTemplateName()) {
continue; $template = $trace['object'];
}
} }
}
$r = new ReflectionClass($trace['class']); // update template filename
if (!$r->implementsInterface('Twig_TemplateInterface')) { if (null !== $template && null === $this->filename) {
continue; $this->filename = $template->getTemplateName();
} }
if (!is_file($r->getFilename())) { if (null === $template || $this->lineno > -1) {
// probably an eval()'d code return;
return array($currentLine, $currentFile); }
}
if (0 === $i) { $r = new ReflectionObject($template);
$line = $e->getLine(); $file = $r->getFileName();
} else {
$line = isset($traces[$i - 1]['line']) ? $traces[$i - 1]['line'] : -log(0);
}
$tokens = token_get_all(file_get_contents($r->getFilename())); $exceptions = array($e = $this);
$templateline = -1; while (($e instanceof self || method_exists($e, 'getPrevious')) && $e = $e->getPrevious()) {
$template = null; $exceptions[] = $e;
foreach ($tokens as $token) { }
if (isset($token[2]) && $token[2] >= $line) {
return array($templateline, $template); while ($e = array_pop($exceptions)) {
$traces = $e->getTrace();
while ($trace = array_shift($traces)) {
if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
continue;
} }
if (T_COMMENT === $token[0] && null === $template && preg_match('#/\* +(.+) +\*/#', $token[1], $match)) { foreach ($template->getDebugInfo() as $codeLine => $templateLine) {
$template = $match[1]; if ($codeLine <= $trace['line']) {
} elseif (T_COMMENT === $token[0] && preg_match('#^//\s*line (\d+)\s*$#', $token[1], $match)) { // update template line
$templateline = $match[1]; $this->lineno = $templateLine;
return;
}
} }
} }
return array($currentLine, $template);
} }
return array($currentLine, $currentFile);
} }
} }

15
inc/lib/Twig/Error/Loader.php

@ -12,9 +12,20 @@
/** /**
* Exception thrown when an error occurs during template loading. * Exception thrown when an error occurs during template loading.
* *
* @package twig * Automatic template information guessing is always turned off as
* @author Fabien Potencier <fabien@symfony.com> * if a template cannot be loaded, there is nothing to guess.
* However, when a template is loaded from another one, then, we need
* to find the current context and this is automatically done by
* Twig_Template::displayWithErrorHandling().
*
* This strategy makes Twig_Environment::resolveTemplate() much faster.
*
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Error_Loader extends Twig_Error class Twig_Error_Loader extends Twig_Error
{ {
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
{
parent::__construct($message, false, false, $previous);
}
} }

3
inc/lib/Twig/Error/Runtime.php

@ -13,8 +13,7 @@
/** /**
* Exception thrown when an error occurs at runtime. * Exception thrown when an error occurs at runtime.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Error_Runtime extends Twig_Error class Twig_Error_Runtime extends Twig_Error
{ {

3
inc/lib/Twig/Error/Syntax.php

@ -13,8 +13,7 @@
/** /**
* Exception thrown when a syntax error occurs during lexing or parsing of a template. * Exception thrown when a syntax error occurs during lexing or parsing of a template.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Error_Syntax extends Twig_Error class Twig_Error_Syntax extends Twig_Error
{ {

28
inc/lib/Twig/ExistsLoaderInterface.php

@ -0,0 +1,28 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Adds an exists() method for loaders.
*
* @author Florin Patan <florinpatan@gmail.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
interface Twig_ExistsLoaderInterface
{
/**
* Check if we have the source code of a template, given its name.
*
* @param string $name The name of the template to check if we can load
*
* @return boolean If the template source code is handled by this loader or not
*/
public function exists($name);
}

306
inc/lib/Twig/ExpressionParser.php

@ -18,8 +18,7 @@
* @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
* @see http://en.wikipedia.org/wiki/Operator-precedence_parser * @see http://en.wikipedia.org/wiki/Operator-precedence_parser
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_ExpressionParser class Twig_ExpressionParser
{ {
@ -89,9 +88,19 @@ class Twig_ExpressionParser
{ {
while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) { while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) {
$this->parser->getStream()->next(); $this->parser->getStream()->next();
$expr2 = $this->parseExpression(); if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'The ternary operator must have a default value'); $expr2 = $this->parseExpression();
$expr3 = $this->parseExpression(); if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$this->parser->getStream()->next();
$expr3 = $this->parseExpression();
} else {
$expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine());
}
} else {
$this->parser->getStream()->next();
$expr2 = $expr;
$expr3 = $this->parseExpression();
}
$expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
} }
@ -143,31 +152,67 @@ class Twig_ExpressionParser
break; break;
case Twig_Token::NUMBER_TYPE: case Twig_Token::NUMBER_TYPE:
case Twig_Token::STRING_TYPE:
$this->parser->getStream()->next(); $this->parser->getStream()->next();
$node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
break; break;
case Twig_Token::STRING_TYPE:
case Twig_Token::INTERPOLATION_START_TYPE:
$node = $this->parseStringExpression();
break;
default: default:
if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) { if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
$node = $this->parseArrayExpression(); $node = $this->parseArrayExpression();
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
$node = $this->parseHashExpression(); $node = $this->parseHashExpression();
} else { } else {
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine()); throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine(), $this->parser->getFilename());
} }
} }
return $this->parsePostfixExpression($node); return $this->parsePostfixExpression($node);
} }
public function parseStringExpression()
{
$stream = $this->parser->getStream();
$nodes = array();
// a string cannot be followed by another string in a single expression
$nextCanBeString = true;
while (true) {
if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) {
$token = $stream->next();
$nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
$nextCanBeString = false;
} elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) {
$stream->next();
$nodes[] = $this->parseExpression();
$stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
$nextCanBeString = true;
} else {
break;
}
}
$expr = array_shift($nodes);
foreach ($nodes as $node) {
$expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine());
}
return $expr;
}
public function parseArrayExpression() public function parseArrayExpression()
{ {
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
$elements = array();
$node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
if (!empty($elements)) { if (!$first) {
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
// trailing ,? // trailing ,?
@ -175,21 +220,24 @@ class Twig_ExpressionParser
break; break;
} }
} }
$first = false;
$elements[] = $this->parseExpression(); $node->addElement($this->parseExpression());
} }
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); return $node;
} }
public function parseHashExpression() public function parseHashExpression()
{ {
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
$elements = array();
$node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
if (!empty($elements)) { if (!$first) {
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
// trailing ,? // trailing ,?
@ -197,19 +245,33 @@ class Twig_ExpressionParser
break; break;
} }
} }
$first = false;
if (!$stream->test(Twig_Token::STRING_TYPE) && !$stream->test(Twig_Token::NUMBER_TYPE)) {
// a hash key can be:
//
// * a number -- 12
// * a string -- 'a'
// * a name, which is equivalent to a string -- a
// * an expression, which must be enclosed in parentheses -- (1 + 2)
if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) {
$token = $stream->next();
$key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
} elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$key = $this->parseExpression();
} else {
$current = $stream->getCurrent(); $current = $stream->getCurrent();
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string or a number (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine());
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine(), $this->parser->getFilename());
} }
$key = $stream->next()->getValue();
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
$elements[$key] = $this->parseExpression(); $value = $this->parseExpression();
$node->addElement($value, $key);
} }
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); return $node;
} }
public function parsePostfixExpression($node) public function parsePostfixExpression($node)
@ -234,43 +296,56 @@ class Twig_ExpressionParser
public function getFunctionNode($name, $line) public function getFunctionNode($name, $line)
{ {
$args = $this->parseArguments();
switch ($name) { switch ($name) {
case 'parent': case 'parent':
$args = $this->parseArguments();
if (!count($this->parser->getBlockStack())) { if (!count($this->parser->getBlockStack())) {
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line); throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename());
} }
if (!$this->parser->getParent()) { if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend another one is forbidden', $line); throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line, $this->parser->getFilename());
} }
return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line); return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
case 'block': case 'block':
return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $line); return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line);
case 'attribute': case 'attribute':
$args = $this->parseArguments();
if (count($args) < 2) { if (count($args) < 2) {
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attribute)', $line); throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
} }
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line); return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line);
default: default:
if (null !== $alias = $this->parser->getImportedFunction($name)) { if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
return new Twig_Node_Expression_GetAttr($alias['node'], new Twig_Node_Expression_Constant($alias['name'], $line), $args, Twig_TemplateInterface::METHOD_CALL, $line); $arguments = new Twig_Node_Expression_Array(array(), $line);
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
$node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
$node->setAttribute('safe', true);
return $node;
} }
return new Twig_Node_Expression_Function($name, $args, $line); $args = $this->parseArguments(true);
$class = $this->getFunctionNodeClass($name, $line);
return new $class($name, $args, $line);
} }
} }
public function parseSubscriptExpression($node) public function parseSubscriptExpression($node)
{ {
$token = $this->parser->getStream()->next(); $stream = $this->parser->getStream();
$token = $stream->next();
$lineno = $token->getLine(); $lineno = $token->getLine();
$arguments = new Twig_Node(); $arguments = new Twig_Node_Expression_Array(array(), $lineno);
$type = Twig_TemplateInterface::ANY_CALL; $type = Twig_TemplateInterface::ANY_CALL;
if ($token->getValue() == '.') { if ($token->getValue() == '.') {
$token = $this->parser->getStream()->next(); $token = $stream->next();
if ( if (
$token->getType() == Twig_Token::NAME_TYPE $token->getType() == Twig_Token::NAME_TYPE
|| ||
@ -280,20 +355,60 @@ class Twig_ExpressionParser
) { ) {
$arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno); $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$type = Twig_TemplateInterface::METHOD_CALL; $type = Twig_TemplateInterface::METHOD_CALL;
$arguments = $this->parseArguments(); foreach ($this->parseArguments() as $n) {
} else { $arguments->addElement($n);
$arguments = new Twig_Node(); }
} }
} else { } else {
throw new Twig_Error_Syntax('Expected name or number', $lineno); throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
}
if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
if (!$arg instanceof Twig_Node_Expression_Constant) {
throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
}
$node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno);
$node->setAttribute('safe', true);
return $node;
} }
} else { } else {
$type = Twig_TemplateInterface::ARRAY_CALL; $type = Twig_TemplateInterface::ARRAY_CALL;
$arg = $this->parseExpression(); // slice?
$this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ']'); $slice = false;
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$slice = true;
$arg = new Twig_Node_Expression_Constant(0, $token->getLine());
} else {
$arg = $this->parseExpression();
}
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$slice = true;
$stream->next();
}
if ($slice) {
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
$length = new Twig_Node_Expression_Constant(null, $token->getLine());
} else {
$length = $this->parseExpression();
}
$class = $this->getFilterNodeClass('slice', $token->getLine());
$arguments = new Twig_Node(array($arg, $length));
$filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
return $filter;
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
} }
return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno); return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
@ -315,10 +430,12 @@ class Twig_ExpressionParser
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$arguments = new Twig_Node(); $arguments = new Twig_Node();
} else { } else {
$arguments = $this->parseArguments(); $arguments = $this->parseArguments(true);
} }
$node = new Twig_Node_Expression_Filter($node, $name, $arguments, $token->getLine(), $tag); $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
$node = new $class($node, $name, $arguments, $token->getLine(), $tag);
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) { if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
break; break;
@ -330,17 +447,62 @@ class Twig_ExpressionParser
return $node; return $node;
} }
public function parseArguments() /**
* Parses arguments.
*
* @param Boolean $namedArguments Whether to allow named arguments or not
* @param Boolean $definition Whether we are parsing arguments for a function definition
*/
public function parseArguments($namedArguments = false, $definition = false)
{ {
$args = array(); $args = array();
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must be opened by a parenthesis'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) { while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
if (!empty($args)) { if (!empty($args)) {
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
} }
$args[] = $this->parseExpression();
if ($definition) {
$token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name');
$value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine());
} else {
$value = $this->parseExpression();
}
$name = null;
if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
$token = $stream->next();
if (!$value instanceof Twig_Node_Expression_Name) {
throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename());
}
$name = $value->getAttribute('name');
if ($definition) {
$value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) {
throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $this->parser->getFilename());
}
} else {
$value = $this->parseExpression();
}
}
if ($definition) {
if (null === $name) {
$name = $value->getAttribute('name');
$value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
}
$args[$name] = $value;
} else {
if (null === $name) {
$args[] = $value;
} else {
$args[$name] = $value;
}
}
} }
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
@ -353,7 +515,7 @@ class Twig_ExpressionParser
while (true) { while (true) {
$token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to'); $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
if (in_array($token->getValue(), array('true', 'false', 'none'))) { if (in_array($token->getValue(), array('true', 'false', 'none'))) {
throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine()); throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename());
} }
$targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine()); $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
@ -379,4 +541,60 @@ class Twig_ExpressionParser
return new Twig_Node($targets); return new Twig_Node($targets);
} }
protected function getFunctionNodeClass($name, $line)
{
$env = $this->parser->getEnvironment();
if (false === $function = $env->getFunction($name)) {
$message = sprintf('The function "%s" does not exist', $name);
if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFunctions()))) {
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
}
if ($function instanceof Twig_SimpleFunction) {
return $function->getNodeClass();
}
return $function instanceof Twig_Function_Node ? $function->getClass() : 'Twig_Node_Expression_Function';
}
protected function getFilterNodeClass($name, $line)
{
$env = $this->parser->getEnvironment();
if (false === $filter = $env->getFilter($name)) {
$message = sprintf('The filter "%s" does not exist', $name);
if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFilters()))) {
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
}
throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
}
if ($filter instanceof Twig_SimpleFilter) {
return $filter->getNodeClass();
}
return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter';
}
// checks that the node only contains "constant" elements
protected function checkConstantExpression(Twig_NodeInterface $node)
{
if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array)) {
return false;
}
foreach ($node as $n) {
if (!$this->checkConstantExpression($n)) {
return false;
}
}
return true;
}
} }

4
inc/lib/Twig/Extension.php

@ -82,9 +82,9 @@ abstract class Twig_Extension implements Twig_ExtensionInterface
} }
/** /**
* Returns a list of global functions to add to the existing list. * Returns a list of global variables to add to the existing list.
* *
* @return array An array of global functions * @return array An array of global variables
*/ */
public function getGlobals() public function getGlobals()
{ {

993
inc/lib/Twig/Extension/Core.php

File diff suppressed because it is too large

71
inc/lib/Twig/Extension/Debug.php

@ -0,0 +1,71 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Extension_Debug extends Twig_Extension
{
/**
* Returns a list of global functions to add to the existing list.
*
* @return array An array of global functions
*/
public function getFunctions()
{
// dump is safe if var_dump is overridden by xdebug
$isDumpOutputHtmlSafe = extension_loaded('xdebug')
// false means that it was not set (and the default is on) or it explicitly enabled
&& (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump'))
// false means that it was not set (and the default is on) or it explicitly enabled
// xdebug.overload_var_dump produces HTML only when html_errors is also enabled
&& (false === ini_get('html_errors') || ini_get('html_errors'))
|| 'cli' === php_sapi_name()
;
return array(
new Twig_SimpleFunction('dump', 'twig_var_dump', array('is_safe' => $isDumpOutputHtmlSafe ? array('html') : array(), 'needs_context' => true, 'needs_environment' => true)),
);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'debug';
}
}
function twig_var_dump(Twig_Environment $env, $context)
{
if (!$env->isDebug()) {
return;
}
ob_start();
$count = func_num_args();
if (2 === $count) {
$vars = array();
foreach ($context as $key => $value) {
if (!$value instanceof Twig_Template) {
$vars[$key] = $value;
}
}
var_dump($vars);
} else {
for ($i = 2; $i < $count; $i++) {
var_dump(func_get_arg($i));
}
}
return ob_get_clean();
}

44
inc/lib/Twig/Extension/Escaper.php

@ -10,11 +10,11 @@
*/ */
class Twig_Extension_Escaper extends Twig_Extension class Twig_Extension_Escaper extends Twig_Extension
{ {
protected $autoescape; protected $defaultStrategy;
public function __construct($autoescape = true) public function __construct($defaultStrategy = 'html')
{ {
$this->autoescape = $autoescape; $this->setDefaultStrategy($defaultStrategy);
} }
/** /**
@ -45,13 +45,44 @@ class Twig_Extension_Escaper extends Twig_Extension
public function getFilters() public function getFilters()
{ {
return array( return array(
'raw' => new Twig_Filter_Function('twig_raw_filter', array('is_safe' => array('all'))), new Twig_SimpleFilter('raw', 'twig_raw_filter', array('is_safe' => array('all'))),
); );
} }
public function isGlobal() /**
* Sets the default strategy to use when not defined by the user.
*
* The strategy can be a valid PHP callback that takes the template
* "filename" as an argument and returns the strategy to use.
*
* @param mixed $defaultStrategy An escaping strategy
*/
public function setDefaultStrategy($defaultStrategy)
{
// for BC
if (true === $defaultStrategy) {
$defaultStrategy = 'html';
}
$this->defaultStrategy = $defaultStrategy;
}
/**
* Gets the default strategy to use when not defined by the user.
*
* @param string $filename The template "filename"
*
* @return string The default strategy to use for the template
*/
public function getDefaultStrategy($filename)
{ {
return $this->autoescape; // disable string callables to avoid calling a function named html or js,
// or any other upcoming escaping strategy
if (!is_string($this->defaultStrategy) && is_callable($this->defaultStrategy)) {
return call_user_func($this->defaultStrategy, $filename);
}
return $this->defaultStrategy;
} }
/** /**
@ -74,4 +105,3 @@ function twig_raw_filter($string)
{ {
return $string; return $string;
} }

113
inc/lib/Twig/Extension/Staging.php

@ -0,0 +1,113 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Internal class.
*
* This class is used by Twig_Environment as a staging area and must not be used directly.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Extension_Staging extends Twig_Extension
{
protected $functions = array();
protected $filters = array();
protected $visitors = array();
protected $tokenParsers = array();
protected $globals = array();
protected $tests = array();
public function addFunction($name, $function)
{
$this->functions[$name] = $function;
}
/**
* {@inheritdoc}
*/
public function getFunctions()
{
return $this->functions;
}
public function addFilter($name, $filter)
{
$this->filters[$name] = $filter;
}
/**
* {@inheritdoc}
*/
public function getFilters()
{
return $this->filters;
}
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
{
$this->visitors[] = $visitor;
}
/**
* {@inheritdoc}
*/
public function getNodeVisitors()
{
return $this->visitors;
}
public function addTokenParser(Twig_TokenParserInterface $parser)
{
$this->tokenParsers[] = $parser;
}
/**
* {@inheritdoc}
*/
public function getTokenParsers()
{
return $this->tokenParsers;
}
public function addGlobal($name, $value)
{
$this->globals[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function getGlobals()
{
return $this->globals;
}
public function addTest($name, $test)
{
$this->tests[$name] = $test;
}
/**
* {@inheritdoc}
*/
public function getTests()
{
return $this->tests;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'staging';
}
}

64
inc/lib/Twig/Extension/StringLoader.php

@ -0,0 +1,64 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Extension_StringLoader extends Twig_Extension
{
/**
* {@inheritdoc}
*/
public function getFunctions()
{
return array(
new Twig_SimpleFunction('template_from_string', 'twig_template_from_string', array('needs_environment' => true)),
);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'string_loader';
}
}
/**
* Loads a template from a string.
*
* <pre>
* {{ include(template_from_string("Hello {{ name }}")) }}
* </pre>
*
* @param Twig_Environment $env A Twig_Environment instance
* @param string $template A template as a string
*
* @return Twig_Template A Twig_Template instance
*/
function twig_template_from_string(Twig_Environment $env, $template)
{
static $loader;
if (null === $loader) {
$loader = new Twig_Loader_String();
}
$current = $env->getLoader();
$env->setLoader($loader);
try {
$template = $env->loadTemplate($template);
} catch (Exception $e) {
$env->setLoader($current);
throw $e;
}
$env->setLoader($current);
return $template;
}

25
inc/lib/Twig/ExtensionInterface.php

@ -12,8 +12,7 @@
/** /**
* Interface implemented by extension classes. * Interface implemented by extension classes.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
interface Twig_ExtensionInterface interface Twig_ExtensionInterface
{ {
@ -24,61 +23,61 @@ interface Twig_ExtensionInterface
* *
* @param Twig_Environment $environment The current Twig_Environment instance * @param Twig_Environment $environment The current Twig_Environment instance
*/ */
function initRuntime(Twig_Environment $environment); public function initRuntime(Twig_Environment $environment);
/** /**
* Returns the token parser instances to add to the existing list. * Returns the token parser instances to add to the existing list.
* *
* @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
*/ */
function getTokenParsers(); public function getTokenParsers();
/** /**
* Returns the node visitor instances to add to the existing list. * Returns the node visitor instances to add to the existing list.
* *
* @return array An array of Twig_NodeVisitorInterface instances * @return array An array of Twig_NodeVisitorInterface instances
*/ */
function getNodeVisitors(); public function getNodeVisitors();
/** /**
* Returns a list of filters to add to the existing list. * Returns a list of filters to add to the existing list.
* *
* @return array An array of filters * @return array An array of filters
*/ */
function getFilters(); public function getFilters();
/** /**
* Returns a list of tests to add to the existing list. * Returns a list of tests to add to the existing list.
* *
* @return array An array of tests * @return array An array of tests
*/ */
function getTests(); public function getTests();
/** /**
* Returns a list of functions to add to the existing list. * Returns a list of functions to add to the existing list.
* *
* @return array An array of functions * @return array An array of functions
*/ */
function getFunctions(); public function getFunctions();
/** /**
* Returns a list of operators to add to the existing list. * Returns a list of operators to add to the existing list.
* *
* @return array An array of operators * @return array An array of operators
*/ */
function getOperators(); public function getOperators();
/** /**
* Returns a list of global functions to add to the existing list. * Returns a list of global variables to add to the existing list.
* *
* @return array An array of global functions * @return array An array of global variables
*/ */
function getGlobals(); public function getGlobals();
/** /**
* Returns the name of the extension. * Returns the name of the extension.
* *
* @return string The extension name * @return string The extension name
*/ */
function getName(); public function getName();
} }

2
inc/lib/Twig/Extensions/Extension/I18n.php

@ -28,7 +28,7 @@ class Twig_Extensions_Extension_I18n extends Twig_Extension
public function getFilters() public function getFilters()
{ {
return array( return array(
'trans' => new Twig_Filter_Function('gettext'), new Twig_SimpleFilter('trans', 'gettext'),
); );
} }

59
inc/lib/Twig/Extensions/Extension/Tinyboard.php

@ -9,24 +9,23 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
*/ */
public function getFilters() public function getFilters()
{ {
return Array( return array(
'filesize' => new Twig_Filter_Function('format_bytes'), new Twig_SimpleFilter('filesize', 'format_bytes'),
'truncate' => new Twig_Filter_Function('twig_truncate_filter'), new Twig_SimpleFilter('truncate', 'twig_truncate_filter'),
'truncate_body' => new Twig_Filter_Function('truncate'), new Twig_SimpleFilter('truncate_body', 'truncate'),
'extension' => new Twig_Filter_Function('twig_extension_filter'), new Twig_SimpleFilter('extension', 'twig_extension_filter'),
'sprintf' => new Twig_Filter_Function('sprintf'), new Twig_SimpleFilter('sprintf', 'sprintf'),
'capcode' => new Twig_Filter_Function('capcode'), new Twig_SimpleFilter('capcode', 'capcode'),
'hasPermission' => new Twig_Filter_Function('twig_hasPermission_filter'), new Twig_SimpleFilter('hasPermission', 'twig_hasPermission_filter'),
'date' => new Twig_Filter_Function('twig_date_filter'), new Twig_SimpleFilter('date', 'twig_date_filter'),
'poster_id' => new Twig_Filter_Function('poster_id'), new Twig_SimpleFilter('poster_id', 'poster_id'),
'remove_whitespace' => new Twig_Filter_Function('twig_remove_whitespace_filter'), new Twig_SimpleFilter('remove_whitespace', 'twig_remove_whitespace_filter'),
'count' => new Twig_Filter_Function('count'), new Twig_SimpleFilter('count', 'count'),
'ago' => new Twig_Filter_Function('ago'), new Twig_SimpleFilter('ago', 'ago'),
'until' => new Twig_Filter_Function('until'), new Twig_SimpleFilter('until', 'until'),
'split' => new Twig_Filter_Function('twig_split_filter'), new Twig_SimpleFilter('push', 'twig_push_filter'),
'push' => new Twig_Filter_Function('twig_push_filter'), new Twig_SimpleFilter('bidi_cleanup', 'bidi_cleanup'),
'bidi_cleanup' => new Twig_Filter_Function('bidi_cleanup'), new Twig_SimpleFilter('addslashes', 'addslashes')
'addslashes' => new Twig_Filter_Function('addslashes')
); );
} }
@ -37,12 +36,12 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
*/ */
public function getFunctions() public function getFunctions()
{ {
return Array( return array(
'time' => new Twig_Filter_Function('time'), new Twig_SimpleFunction('time', 'time'),
'floor' => new Twig_Filter_Function('floor'), new Twig_SimpleFunction('floor', 'floor'),
'timezone' => new Twig_Filter_Function('twig_timezone_function'), new Twig_SimpleFunction('timezone', 'twig_timezone_function'),
'hiddenInputs' => new Twig_Filter_Function('hiddenInputs'), new Twig_SimpleFunction('hiddenInputs', 'hiddenInputs'),
'hiddenInputsHash' => new Twig_Filter_Function('hiddenInputsHash'), new Twig_SimpleFunction('hiddenInputsHash', 'hiddenInputsHash'),
); );
} }
@ -61,10 +60,6 @@ function twig_timezone_function() {
return 'Z'; return 'Z';
} }
function twig_split_filter($str, $delim) {
return explode($delim, $str);
}
function twig_push_filter($array, $value) { function twig_push_filter($array, $value) {
array_push($array, $value); array_push($array, $value);
return $array; return $array;
@ -83,9 +78,9 @@ function twig_hasPermission_filter($mod, $permission, $board = null) {
} }
function twig_extension_filter($value, $case_insensitive = true) { function twig_extension_filter($value, $case_insensitive = true) {
$ext = substr($value, strrpos($value, '.') + 1); $ext = mb_substr($value, mb_strrpos($value, '.') + 1);
if($case_insensitive) if($case_insensitive)
$ext = strtolower($ext); $ext = mb_strtolower($ext);
return $ext; return $ext;
} }
@ -96,11 +91,11 @@ function twig_sprintf_filter( $value, $var) {
function twig_truncate_filter($value, $length = 30, $preserve = false, $separator = '&hellip;') { function twig_truncate_filter($value, $length = 30, $preserve = false, $separator = '&hellip;') {
if (mb_strlen($value) > $length) { if (mb_strlen($value) > $length) {
if ($preserve) { if ($preserve) {
if (false !== ($breakpoint = strpos($value, ' ', $length))) { if (false !== ($breakpoint = mb_strpos($value, ' ', $length))) {
$length = $breakpoint; $length = $breakpoint;
} }
} }
return substr($value, 0, $length) . $separator; return mb_substr($value, 0, $length) . $separator;
} }
return $value; return $value;
} }

11
inc/lib/Twig/Extensions/Node/Trans.php

@ -94,22 +94,25 @@ class Twig_Extensions_Node_Trans extends Twig_Node
; ;
} }
$compiler->raw(');'); $compiler->raw(");\n");
} }
} }
protected function compileString(Twig_NodeInterface $body) protected function compileString(Twig_NodeInterface $body)
{ {
if ($body instanceof Twig_Node_Expression_Name || $body instanceof Twig_Node_Expression_Constant) { if ($body instanceof Twig_Node_Expression_Name || $body instanceof Twig_Node_Expression_Constant || $body instanceof Twig_Node_Expression_TempName) {
return array($body, array()); return array($body, array());
} }
$vars = array(); $vars = array();
if (count($body)) { if (count($body)) {
$msg = ''; $msg = '';
foreach ($body as $node) { foreach ($body as $node) {
if (get_class($node) === 'Twig_Node' && $node->getNode(0) instanceof Twig_Node_SetTemp) {
$node = $node->getNode(1);
}
if ($node instanceof Twig_Node_Print) { if ($node instanceof Twig_Node_Print) {
$n = $node->getNode('expr'); $n = $node->getNode('expr');
while ($n instanceof Twig_Node_Expression_Filter) { while ($n instanceof Twig_Node_Expression_Filter) {
@ -127,4 +130,4 @@ class Twig_Extensions_Node_Trans extends Twig_Node
return array(new Twig_Node(array(new Twig_Node_Expression_Constant(trim($msg), $body->getLine()))), $vars); return array(new Twig_Node(array(new Twig_Node_Expression_Constant(trim($msg), $body->getLine()))), $vars);
} }
} }

6
inc/lib/Twig/Extensions/TokenParser/Trans.php

@ -43,12 +43,12 @@ class Twig_Extensions_TokenParser_Trans extends Twig_TokenParser
return new Twig_Extensions_Node_Trans($body, $plural, $count, $lineno, $this->getTag()); return new Twig_Extensions_Node_Trans($body, $plural, $count, $lineno, $this->getTag());
} }
public function decideForFork($token) public function decideForFork(Twig_Token $token)
{ {
return $token->test(array('plural', 'endtrans')); return $token->test(array('plural', 'endtrans'));
} }
public function decideForEnd($token) public function decideForEnd(Twig_Token $token)
{ {
return $token->test('endtrans'); return $token->test('endtrans');
} }
@ -77,4 +77,4 @@ class Twig_Extensions_TokenParser_Trans extends Twig_TokenParser
throw new Twig_Error_Syntax(sprintf('The text to be translated with "trans" can only contain references to simple variables'), $lineno); throw new Twig_Error_Syntax(sprintf('The text to be translated with "trans" can only contain references to simple variables'), $lineno);
} }
} }
} }

31
inc/lib/Twig/Filter.php

@ -12,12 +12,15 @@
/** /**
* Represents a template filter. * Represents a template filter.
* *
* @package twig * Use Twig_SimpleFilter instead.
* @author Fabien Potencier <fabien@symfony.com> *
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/ */
abstract class Twig_Filter implements Twig_FilterInterface abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface
{ {
protected $options; protected $options;
protected $arguments = array();
public function __construct(array $options = array()) public function __construct(array $options = array())
{ {
@ -25,9 +28,21 @@ abstract class Twig_Filter implements Twig_FilterInterface
'needs_environment' => false, 'needs_environment' => false,
'needs_context' => false, 'needs_context' => false,
'pre_escape' => null, 'pre_escape' => null,
'preserves_safety' => null,
'callable' => null,
), $options); ), $options);
} }
public function setArguments($arguments)
{
$this->arguments = $arguments;
}
public function getArguments()
{
return $this->arguments;
}
public function needsEnvironment() public function needsEnvironment()
{ {
return $this->options['needs_environment']; return $this->options['needs_environment'];
@ -47,12 +62,20 @@ abstract class Twig_Filter implements Twig_FilterInterface
if (isset($this->options['is_safe_callback'])) { if (isset($this->options['is_safe_callback'])) {
return call_user_func($this->options['is_safe_callback'], $filterArgs); return call_user_func($this->options['is_safe_callback'], $filterArgs);
} }
}
return array(); public function getPreservesSafety()
{
return $this->options['preserves_safety'];
} }
public function getPreEscape() public function getPreEscape()
{ {
return $this->options['pre_escape']; return $this->options['pre_escape'];
} }
public function getCallable()
{
return $this->options['callable'];
}
} }

8
inc/lib/Twig/Filter/Function.php

@ -12,8 +12,10 @@
/** /**
* Represents a function template filter. * Represents a function template filter.
* *
* @package twig * Use Twig_SimpleFilter instead.
* @author Fabien Potencier <fabien@symfony.com> *
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/ */
class Twig_Filter_Function extends Twig_Filter class Twig_Filter_Function extends Twig_Filter
{ {
@ -21,6 +23,8 @@ class Twig_Filter_Function extends Twig_Filter
public function __construct($function, array $options = array()) public function __construct($function, array $options = array())
{ {
$options['callable'] = $function;
parent::__construct($options); parent::__construct($options);
$this->function = $function; $this->function = $function;

11
inc/lib/Twig/Filter/Method.php

@ -12,15 +12,20 @@
/** /**
* Represents a method template filter. * Represents a method template filter.
* *
* @package twig * Use Twig_SimpleFilter instead.
* @author Fabien Potencier <fabien@symfony.com> *
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/ */
class Twig_Filter_Method extends Twig_Filter class Twig_Filter_Method extends Twig_Filter
{ {
protected $extension, $method; protected $extension;
protected $method;
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{ {
$options['callable'] = array($extension, $method);
parent::__construct($options); parent::__construct($options);
$this->extension = $extension; $this->extension = $extension;

39
inc/lib/Twig/Filter/Node.php

@ -0,0 +1,39 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a template filter as a node.
*
* Use Twig_SimpleFilter instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
class Twig_Filter_Node extends Twig_Filter
{
protected $class;
public function __construct($class, array $options = array())
{
parent::__construct($options);
$this->class = $class;
}
public function getClass()
{
return $this->class;
}
public function compile()
{
}
}

23
inc/lib/Twig/FilterCallableInterface.php

@ -0,0 +1,23 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a callable template filter.
*
* Use Twig_SimpleFilter instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
interface Twig_FilterCallableInterface
{
public function getCallable();
}

22
inc/lib/Twig/FilterInterface.php

@ -12,8 +12,10 @@
/** /**
* Represents a template filter. * Represents a template filter.
* *
* @package twig * Use Twig_SimpleFilter instead.
* @author Fabien Potencier <fabien@symfony.com> *
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/ */
interface Twig_FilterInterface interface Twig_FilterInterface
{ {
@ -22,13 +24,19 @@ interface Twig_FilterInterface
* *
* @return string The PHP code for the filter * @return string The PHP code for the filter
*/ */
function compile(); public function compile();
public function needsEnvironment();
public function needsContext();
public function getSafe(Twig_Node $filterArgs);
function needsEnvironment(); public function getPreservesSafety();
function needsContext(); public function getPreEscape();
function getSafe(Twig_Node $filterArgs); public function setArguments($arguments);
function getPreEscape(); public function getArguments();
} }

25
inc/lib/Twig/Function.php

@ -12,21 +12,35 @@
/** /**
* Represents a template function. * Represents a template function.
* *
* @package twig * Use Twig_SimpleFunction instead.
* @author Fabien Potencier <fabien@symfony.com> *
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/ */
abstract class Twig_Function implements Twig_FunctionInterface abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface
{ {
protected $options; protected $options;
protected $arguments = array();
public function __construct(array $options = array()) public function __construct(array $options = array())
{ {
$this->options = array_merge(array( $this->options = array_merge(array(
'needs_environment' => false, 'needs_environment' => false,
'needs_context' => false, 'needs_context' => false,
'callable' => null,
), $options); ), $options);
} }
public function setArguments($arguments)
{
$this->arguments = $arguments;
}
public function getArguments()
{
return $this->arguments;
}
public function needsEnvironment() public function needsEnvironment()
{ {
return $this->options['needs_environment']; return $this->options['needs_environment'];
@ -49,4 +63,9 @@ abstract class Twig_Function implements Twig_FunctionInterface
return array(); return array();
} }
public function getCallable()
{
return $this->options['callable'];
}
} }

8
inc/lib/Twig/Function/Function.php

@ -13,8 +13,10 @@
/** /**
* Represents a function template function. * Represents a function template function.
* *
* @package twig * Use Twig_SimpleFunction instead.
* @author Arnaud Le Blanc <arnaud.lb@gmail.com> *
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/ */
class Twig_Function_Function extends Twig_Function class Twig_Function_Function extends Twig_Function
{ {
@ -22,6 +24,8 @@ class Twig_Function_Function extends Twig_Function
public function __construct($function, array $options = array()) public function __construct($function, array $options = array())
{ {
$options['callable'] = $function;
parent::__construct($options); parent::__construct($options);
$this->function = $function; $this->function = $function;

11
inc/lib/Twig/Function/Method.php

@ -13,15 +13,20 @@
/** /**
* Represents a method template function. * Represents a method template function.
* *
* @package twig * Use Twig_SimpleFunction instead.
* @author Arnaud Le Blanc <arnaud.lb@gmail.com> *
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/ */
class Twig_Function_Method extends Twig_Function class Twig_Function_Method extends Twig_Function
{ {
protected $extension, $method; protected $extension;
protected $method;
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
{ {
$options['callable'] = array($extension, $method);
parent::__construct($options); parent::__construct($options);
$this->extension = $extension; $this->extension = $extension;

39
inc/lib/Twig/Function/Node.php

@ -0,0 +1,39 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a template function as a node.
*
* Use Twig_SimpleFunction instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
class Twig_Function_Node extends Twig_Function
{
protected $class;
public function __construct($class, array $options = array())
{
parent::__construct($options);
$this->class = $class;
}
public function getClass()
{
return $this->class;
}
public function compile()
{
}
}

23
inc/lib/Twig/FunctionCallableInterface.php

@ -0,0 +1,23 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a callable template function.
*
* Use Twig_SimpleFunction instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/
interface Twig_FunctionCallableInterface
{
public function getCallable();
}

18
inc/lib/Twig/FunctionInterface.php

@ -13,8 +13,10 @@
/** /**
* Represents a template function. * Represents a template function.
* *
* @package twig * Use Twig_SimpleFunction instead.
* @author Arnaud Le Blanc <arnaud.lb@gmail.com> *
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
* @deprecated since 1.12 (to be removed in 2.0)
*/ */
interface Twig_FunctionInterface interface Twig_FunctionInterface
{ {
@ -23,11 +25,15 @@ interface Twig_FunctionInterface
* *
* @return string The PHP code for the function * @return string The PHP code for the function
*/ */
function compile(); public function compile();
public function needsEnvironment();
public function needsContext();
function needsEnvironment(); public function getSafe(Twig_Node $filterArgs);
function needsContext(); public function setArguments($arguments);
function getSafe(Twig_Node $filterArgs); public function getArguments();
} }

226
inc/lib/Twig/Lexer.php

@ -13,8 +13,7 @@
/** /**
* Lexes a template string. * Lexes a template string.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Lexer implements Twig_LexerInterface class Twig_Lexer implements Twig_LexerInterface
{ {
@ -24,21 +23,28 @@ class Twig_Lexer implements Twig_LexerInterface
protected $lineno; protected $lineno;
protected $end; protected $end;
protected $state; protected $state;
protected $states;
protected $brackets; protected $brackets;
protected $env; protected $env;
protected $filename; protected $filename;
protected $options; protected $options;
protected $operatorRegex; protected $regexes;
protected $position;
const STATE_DATA = 0; protected $positions;
const STATE_BLOCK = 1; protected $currentVarBlockLine;
const STATE_VAR = 2;
const STATE_DATA = 0;
const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; const STATE_BLOCK = 1;
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A'; const STATE_VAR = 2;
const REGEX_STRING = '/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; const STATE_STRING = 3;
const PUNCTUATION = '()[]{}?:.,|'; const STATE_INTERPOLATION = 4;
const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
const REGEX_DQ_STRING_DELIM = '/"/A';
const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
const PUNCTUATION = '()[]{}?:.,|';
public function __construct(Twig_Environment $env, array $options = array()) public function __construct(Twig_Environment $env, array $options = array())
{ {
@ -49,14 +55,28 @@ class Twig_Lexer implements Twig_LexerInterface
'tag_block' => array('{%', '%}'), 'tag_block' => array('{%', '%}'),
'tag_variable' => array('{{', '}}'), 'tag_variable' => array('{{', '}}'),
'whitespace_trim' => '-', 'whitespace_trim' => '-',
'interpolation' => array('#{', '}'),
), $options); ), $options);
$this->regexes = array(
'lex_var' => '/\s*'.preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A',
'lex_block' => '/\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')\n?/A',
'lex_raw_data' => '/('.preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim'], '/').'|'.preg_quote($this->options['tag_block'][0], '/').')\s*(?:end%s)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/s',
'operator' => $this->getOperatorRegex(),
'lex_comment' => '/(?:'.preg_quote($this->options['whitespace_trim'], '/').preg_quote($this->options['tag_comment'][1], '/').'\s*|'.preg_quote($this->options['tag_comment'][1], '/').')\n?/s',
'lex_block_raw' => '/\s*(raw|verbatim)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/As',
'lex_block_line' => '/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As',
'lex_tokens_start' => '/('.preg_quote($this->options['tag_variable'][0], '/').'|'.preg_quote($this->options['tag_block'][0], '/').'|'.preg_quote($this->options['tag_comment'][0], '/').')('.preg_quote($this->options['whitespace_trim'], '/').')?/s',
'interpolation_start' => '/'.preg_quote($this->options['interpolation'][0], '/').'\s*/A',
'interpolation_end' => '/\s*'.preg_quote($this->options['interpolation'][1], '/').'/A',
);
} }
/** /**
* Tokenizes a source code. * Tokenizes a source code.
* *
* @param string $code The source code * @param string $code The source code
* @param string $filename A unique identifier for the source code * @param string $filename A unique identifier for the source code
* *
* @return Twig_TokenStream A token stream instance * @return Twig_TokenStream A token stream instance
*/ */
@ -74,7 +94,13 @@ class Twig_Lexer implements Twig_LexerInterface
$this->end = strlen($this->code); $this->end = strlen($this->code);
$this->tokens = array(); $this->tokens = array();
$this->state = self::STATE_DATA; $this->state = self::STATE_DATA;
$this->states = array();
$this->brackets = array(); $this->brackets = array();
$this->position = -1;
// find all token starts in one go
preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE);
$this->positions = $matches;
while ($this->cursor < $this->end) { while ($this->cursor < $this->end) {
// dispatch to the lexing functions depending // dispatch to the lexing functions depending
@ -91,6 +117,14 @@ class Twig_Lexer implements Twig_LexerInterface
case self::STATE_VAR: case self::STATE_VAR:
$this->lexVar(); $this->lexVar();
break; break;
case self::STATE_STRING:
$this->lexString();
break;
case self::STATE_INTERPOLATION:
$this->lexInterpolation();
break;
} }
} }
@ -110,77 +144,66 @@ class Twig_Lexer implements Twig_LexerInterface
protected function lexData() protected function lexData()
{ {
$pos = $this->end;
$append = '';
// Find the first token after the cursor
foreach (array('tag_comment', 'tag_variable', 'tag_block') as $type) {
$tmpPos = strpos($this->code, $this->options[$type][0], $this->cursor);
if (false !== $tmpPos && $tmpPos < $pos) {
$trimBlock = false;
$append = '';
$pos = $tmpPos;
$token = $this->options[$type][0];
if (strpos($this->code, $this->options['whitespace_trim'], $pos) === ($pos + strlen($token))) {
$trimBlock = true;
$append = $this->options['whitespace_trim'];
}
}
}
// if no matches are left we return the rest of the template as simple text token // if no matches are left we return the rest of the template as simple text token
if ($pos === $this->end) { if ($this->position == count($this->positions[0]) - 1) {
$this->pushToken(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor)); $this->pushToken(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor));
$this->cursor = $this->end; $this->cursor = $this->end;
return; return;
} }
// Find the first token after the current cursor
$position = $this->positions[0][++$this->position];
while ($position[1] < $this->cursor) {
if ($this->position == count($this->positions[0]) - 1) {
return;
}
$position = $this->positions[0][++$this->position];
}
// push the template text first // push the template text first
$text = $textContent = substr($this->code, $this->cursor, $pos - $this->cursor); $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor);
if (true === $trimBlock) { if (isset($this->positions[2][$this->position][0])) {
$text = rtrim($text); $text = rtrim($text);
} }
$this->pushToken(Twig_Token::TEXT_TYPE, $text); $this->pushToken(Twig_Token::TEXT_TYPE, $text);
$this->moveCursor($textContent.$token.$append); $this->moveCursor($textContent.$position[0]);
switch ($token) { switch ($this->positions[1][$this->position][0]) {
case $this->options['tag_comment'][0]: case $this->options['tag_comment'][0]:
$this->lexComment(); $this->lexComment();
break; break;
case $this->options['tag_block'][0]: case $this->options['tag_block'][0]:
// raw data? // raw data?
if (preg_match('/\s*raw\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', $this->code, $match, null, $this->cursor)) { if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->lexRawData(); $this->lexRawData($match[1]);
$this->state = self::STATE_DATA;
// {% line \d+ %} // {% line \d+ %}
} else if (preg_match('/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', $this->code, $match, null, $this->cursor)) { } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->lineno = (int) $match[1]; $this->lineno = (int) $match[1];
$this->state = self::STATE_DATA;
} else { } else {
$this->pushToken(Twig_Token::BLOCK_START_TYPE); $this->pushToken(Twig_Token::BLOCK_START_TYPE);
$this->state = self::STATE_BLOCK; $this->pushState(self::STATE_BLOCK);
$this->currentVarBlockLine = $this->lineno;
} }
break; break;
case $this->options['tag_variable'][0]: case $this->options['tag_variable'][0]:
$this->pushToken(Twig_Token::VAR_START_TYPE); $this->pushToken(Twig_Token::VAR_START_TYPE);
$this->state = self::STATE_VAR; $this->pushState(self::STATE_VAR);
$this->currentVarBlockLine = $this->lineno;
break; break;
} }
} }
protected function lexBlock() protected function lexBlock()
{ {
$trimTag = preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/'); if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, null, $this->cursor)) {
$endTag = preg_quote($this->options['tag_block'][1], '/');
if (empty($this->brackets) && preg_match('/\s*(?:'.$trimTag.'\s*|\s*'.$endTag.')\n?/A', $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::BLOCK_END_TYPE); $this->pushToken(Twig_Token::BLOCK_END_TYPE);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->state = self::STATE_DATA; $this->popState();
} else { } else {
$this->lexExpression(); $this->lexExpression();
} }
@ -188,13 +211,10 @@ class Twig_Lexer implements Twig_LexerInterface
protected function lexVar() protected function lexVar()
{ {
$trimTag = preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/'); if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, null, $this->cursor)) {
$endTag = preg_quote($this->options['tag_variable'][1], '/');
if (empty($this->brackets) && preg_match('/\s*'.$trimTag.'\s*|\s*'.$endTag.'/A', $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::VAR_END_TYPE); $this->pushToken(Twig_Token::VAR_END_TYPE);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->state = self::STATE_DATA; $this->popState();
} else { } else {
$this->lexExpression(); $this->lexExpression();
} }
@ -207,12 +227,12 @@ class Twig_Lexer implements Twig_LexerInterface
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
if ($this->cursor >= $this->end) { if ($this->cursor >= $this->end) {
throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s"', $this->state === self::STATE_BLOCK ? 'block' : 'variable')); throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $this->state === self::STATE_BLOCK ? 'block' : 'variable'), $this->currentVarBlockLine, $this->filename);
} }
} }
// operators // operators
if (preg_match($this->getOperatorRegex(), $this->code, $match, null, $this->cursor)) { if (preg_match($this->regexes['operator'], $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]); $this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
} }
@ -223,7 +243,11 @@ class Twig_Lexer implements Twig_LexerInterface
} }
// numbers // numbers
elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) { elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::NUMBER_TYPE, ctype_digit($match[0]) ? (int) $match[0] : (float) $match[0]); $number = (float) $match[0]; // floats
if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) {
$number = (int) $match[0]; // integers lower than the maximum
}
$this->pushToken(Twig_Token::NUMBER_TYPE, $number);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
} }
// punctuation // punctuation
@ -252,35 +276,80 @@ class Twig_Lexer implements Twig_LexerInterface
$this->pushToken(Twig_Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1))); $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)));
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
} }
// opening double quoted string
elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
$this->brackets[] = array('"', $this->lineno);
$this->pushState(self::STATE_STRING);
$this->moveCursor($match[0]);
}
// unlexable // unlexable
else { else {
throw new Twig_Error_Syntax(sprintf('Unexpected character "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename); throw new Twig_Error_Syntax(sprintf('Unexpected character "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename);
} }
} }
protected function lexRawData() protected function lexRawData($tag)
{ {
if (!preg_match('/'.preg_quote($this->options['tag_block'][0], '/').'\s*endraw\s*'.preg_quote($this->options['tag_block'][1], '/').'/s', $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "block"')); throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s" block', $tag), $this->lineno, $this->filename);
} }
$text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
$this->pushToken(Twig_Token::TEXT_TYPE, $text);
$this->moveCursor($text.$match[0][0]); $this->moveCursor($text.$match[0][0]);
if (false !== strpos($match[1][0], $this->options['whitespace_trim'])) {
$text = rtrim($text);
}
$this->pushToken(Twig_Token::TEXT_TYPE, $text);
} }
protected function lexComment() protected function lexComment()
{ {
$commentEndRegex = '/(?:'.preg_quote($this->options['whitespace_trim'], '/') if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
.preg_quote($this->options['tag_comment'][1], '/').'\s*|'
.preg_quote($this->options['tag_comment'][1], '/').')\n?/s';
if (!preg_match($commentEndRegex, $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new Twig_Error_Syntax('Unclosed comment', $this->lineno, $this->filename); throw new Twig_Error_Syntax('Unclosed comment', $this->lineno, $this->filename);
} }
$this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]);
} }
protected function lexString()
{
if (preg_match($this->regexes['interpolation_start'], $this->code, $match, null, $this->cursor)) {
$this->brackets[] = array($this->options['interpolation'][0], $this->lineno);
$this->pushToken(Twig_Token::INTERPOLATION_START_TYPE);
$this->moveCursor($match[0]);
$this->pushState(self::STATE_INTERPOLATION);
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, null, $this->cursor) && strlen($match[0]) > 0) {
$this->pushToken(Twig_Token::STRING_TYPE, stripcslashes($match[0]));
$this->moveCursor($match[0]);
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
list($expect, $lineno) = array_pop($this->brackets);
if ($this->code[$this->cursor] != '"') {
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
}
$this->popState();
++$this->cursor;
}
}
protected function lexInterpolation()
{
$bracket = end($this->brackets);
if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, null, $this->cursor)) {
array_pop($this->brackets);
$this->pushToken(Twig_Token::INTERPOLATION_END_TYPE);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
protected function pushToken($type, $value = '') protected function pushToken($type, $value = '')
{ {
// do not push empty text tokens // do not push empty text tokens
@ -299,10 +368,6 @@ class Twig_Lexer implements Twig_LexerInterface
protected function getOperatorRegex() protected function getOperatorRegex()
{ {
if (null !== $this->operatorRegex) {
return $this->operatorRegex;
}
$operators = array_merge( $operators = array_merge(
array('='), array('='),
array_keys($this->env->getUnaryOperators()), array_keys($this->env->getUnaryOperators()),
@ -317,12 +382,27 @@ class Twig_Lexer implements Twig_LexerInterface
// an operator that ends with a character must be followed by // an operator that ends with a character must be followed by
// a whitespace or a parenthesis // a whitespace or a parenthesis
if (ctype_alpha($operator[$length - 1])) { if (ctype_alpha($operator[$length - 1])) {
$regex[] = preg_quote($operator, '/').'(?=[ ()])'; $regex[] = preg_quote($operator, '/').'(?=[\s()])';
} else { } else {
$regex[] = preg_quote($operator, '/'); $regex[] = preg_quote($operator, '/');
} }
} }
return $this->operatorRegex = '/'.implode('|', $regex).'/A'; return '/'.implode('|', $regex).'/A';
}
protected function pushState($state)
{
$this->states[] = $this->state;
$this->state = $state;
}
protected function popState()
{
if (0 === count($this->states)) {
throw new Exception('Cannot pop state without a previous state');
}
$this->state = array_pop($this->states);
} }
} }

10
inc/lib/Twig/LexerInterface.php

@ -12,18 +12,18 @@
/** /**
* Interface implemented by lexer classes. * Interface implemented by lexer classes.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com> * @deprecated since 1.12 (to be removed in 2.0)
*/ */
interface Twig_LexerInterface interface Twig_LexerInterface
{ {
/** /**
* Tokenizes a source code. * Tokenizes a source code.
* *
* @param string $code The source code * @param string $code The source code
* @param string $filename A unique identifier for the source code * @param string $filename A unique identifier for the source code
* *
* @return Twig_TokenStream A token stream instance * @return Twig_TokenStream A token stream instance
*/ */
function tokenize($code, $filename = null); public function tokenize($code, $filename = null);
} }

39
inc/lib/Twig/Loader/Array.php

@ -17,10 +17,9 @@
* source code of the template). If you don't want to see your cache grows out of * source code of the template). If you don't want to see your cache grows out of
* control, you need to take care of clearing the old cache file by yourself. * control, you need to take care of clearing the old cache file by yourself.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Loader_Array implements Twig_LoaderInterface class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{ {
protected $templates; protected $templates;
@ -47,18 +46,15 @@ class Twig_Loader_Array implements Twig_LoaderInterface
*/ */
public function setTemplate($name, $template) public function setTemplate($name, $template)
{ {
$this->templates[$name] = $template; $this->templates[(string) $name] = $template;
} }
/** /**
* Gets the source code of a template, given its name. * {@inheritdoc}
*
* @param string $name The name of the template to load
*
* @return string The template source code
*/ */
public function getSource($name) public function getSource($name)
{ {
$name = (string) $name;
if (!isset($this->templates[$name])) { if (!isset($this->templates[$name])) {
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
} }
@ -67,14 +63,19 @@ class Twig_Loader_Array implements Twig_LoaderInterface
} }
/** /**
* Gets the cache key to use for the cache for a given template name. * {@inheritdoc}
* */
* @param string $name The name of the template to load public function exists($name)
* {
* @return string The cache key return isset($this->templates[(string) $name]);
}
/**
* {@inheritdoc}
*/ */
public function getCacheKey($name) public function getCacheKey($name)
{ {
$name = (string) $name;
if (!isset($this->templates[$name])) { if (!isset($this->templates[$name])) {
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
} }
@ -83,13 +84,15 @@ class Twig_Loader_Array implements Twig_LoaderInterface
} }
/** /**
* Returns true if the template is still fresh. * {@inheritdoc}
*
* @param string $name The template name
* @param timestamp $time The last modification time of the cached template
*/ */
public function isFresh($name, $time) public function isFresh($name, $time)
{ {
$name = (string) $name;
if (!isset($this->templates[$name])) {
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
}
return true; return true;
} }
} }

79
inc/lib/Twig/Loader/Chain.php

@ -12,11 +12,11 @@
/** /**
* Loads templates from other loaders. * Loads templates from other loaders.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Loader_Chain implements Twig_LoaderInterface class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{ {
private $hasSourceCache = array();
protected $loaders; protected $loaders;
/** /**
@ -40,61 +40,100 @@ class Twig_Loader_Chain implements Twig_LoaderInterface
public function addLoader(Twig_LoaderInterface $loader) public function addLoader(Twig_LoaderInterface $loader)
{ {
$this->loaders[] = $loader; $this->loaders[] = $loader;
$this->hasSourceCache = array();
} }
/** /**
* Gets the source code of a template, given its name. * {@inheritdoc}
*
* @param string $name The name of the template to load
*
* @return string The template source code
*/ */
public function getSource($name) public function getSource($name)
{ {
$exceptions = array();
foreach ($this->loaders as $loader) { foreach ($this->loaders as $loader) {
if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) {
continue;
}
try { try {
return $loader->getSource($name); return $loader->getSource($name);
} catch (Twig_Error_Loader $e) { } catch (Twig_Error_Loader $e) {
$exceptions[] = $e->getMessage();
} }
} }
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(', ', $exceptions)));
} }
/** /**
* Gets the cache key to use for the cache for a given template name. * {@inheritdoc}
* */
* @param string $name The name of the template to load public function exists($name)
* {
* @return string The cache key $name = (string) $name;
if (isset($this->hasSourceCache[$name])) {
return $this->hasSourceCache[$name];
}
foreach ($this->loaders as $loader) {
if ($loader instanceof Twig_ExistsLoaderInterface) {
if ($loader->exists($name)) {
return $this->hasSourceCache[$name] = true;
}
continue;
}
try {
$loader->getSource($name);
return $this->hasSourceCache[$name] = true;
} catch (Twig_Error_Loader $e) {
}
}
return $this->hasSourceCache[$name] = false;
}
/**
* {@inheritdoc}
*/ */
public function getCacheKey($name) public function getCacheKey($name)
{ {
$exceptions = array();
foreach ($this->loaders as $loader) { foreach ($this->loaders as $loader) {
if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) {
continue;
}
try { try {
return $loader->getCacheKey($name); return $loader->getCacheKey($name);
} catch (Twig_Error_Loader $e) { } catch (Twig_Error_Loader $e) {
$exceptions[] = get_class($loader).': '.$e->getMessage();
} }
} }
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(' ', $exceptions)));
} }
/** /**
* Returns true if the template is still fresh. * {@inheritdoc}
*
* @param string $name The template name
* @param timestamp $time The last modification time of the cached template
*/ */
public function isFresh($name, $time) public function isFresh($name, $time)
{ {
$exceptions = array();
foreach ($this->loaders as $loader) { foreach ($this->loaders as $loader) {
if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) {
continue;
}
try { try {
return $loader->isFresh($name, $time); return $loader->isFresh($name, $time);
} catch (Twig_Error_Loader $e) { } catch (Twig_Error_Loader $e) {
$exceptions[] = get_class($loader).': '.$e->getMessage();
} }
} }
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(' ', $exceptions)));
} }
} }

129
inc/lib/Twig/Loader/Filesystem.php

@ -12,10 +12,9 @@
/** /**
* Loads template from the filesystem. * Loads template from the filesystem.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Loader_Filesystem implements Twig_LoaderInterface class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{ {
protected $paths; protected $paths;
protected $cache; protected $cache;
@ -25,44 +24,64 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface
* *
* @param string|array $paths A path or an array of paths where to look for templates * @param string|array $paths A path or an array of paths where to look for templates
*/ */
public function __construct($paths) public function __construct($paths = array())
{ {
$this->setPaths($paths); if ($paths) {
$this->setPaths($paths);
}
} }
/** /**
* Returns the paths to the templates. * Returns the paths to the templates.
* *
* @param string $namespace A path namespace
*
* @return array The array of paths where to look for templates * @return array The array of paths where to look for templates
*/ */
public function getPaths() public function getPaths($namespace = '__main__')
{
return isset($this->paths[$namespace]) ? $this->paths[$namespace] : array();
}
/**
* Returns the path namespaces.
*
* The "__main__" namespace is always defined.
*
* @return array The array of defined namespaces
*/
public function getNamespaces()
{ {
return $this->paths; return array_keys($this->paths);
} }
/** /**
* Sets the paths where templates are stored. * Sets the paths where templates are stored.
* *
* @param string|array $paths A path or an array of paths where to look for templates * @param string|array $paths A path or an array of paths where to look for templates
* @param string $namespace A path namespace
*/ */
public function setPaths($paths) public function setPaths($paths, $namespace = '__main__')
{ {
if (!is_array($paths)) { if (!is_array($paths)) {
$paths = array($paths); $paths = array($paths);
} }
$this->paths = array(); $this->paths[$namespace] = array();
foreach ($paths as $path) { foreach ($paths as $path) {
$this->addPath($path); $this->addPath($path, $namespace);
} }
} }
/** /**
* Adds a path where templates are stored. * Adds a path where templates are stored.
* *
* @param string $path A path where to look for templates * @param string $path A path where to look for templates
* @param string $namespace A path name
*
* @throws Twig_Error_Loader
*/ */
public function addPath($path) public function addPath($path, $namespace = '__main__')
{ {
// invalidate the cache // invalidate the cache
$this->cache = array(); $this->cache = array();
@ -71,15 +90,37 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface
throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path)); throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
} }
$this->paths[] = $path; $this->paths[$namespace][] = rtrim($path, '/\\');
} }
/** /**
* Gets the source code of a template, given its name. * Prepends a path where templates are stored.
* *
* @param string $name The name of the template to load * @param string $path A path where to look for templates
* @param string $namespace A path name
* *
* @return string The template source code * @throws Twig_Error_Loader
*/
public function prependPath($path, $namespace = '__main__')
{
// invalidate the cache
$this->cache = array();
if (!is_dir($path)) {
throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
}
$path = rtrim($path, '/\\');
if (!isset($this->paths[$namespace])) {
$this->paths[$namespace][] = $path;
} else {
array_unshift($this->paths[$namespace], $path);
}
}
/**
* {@inheritdoc}
*/ */
public function getSource($name) public function getSource($name)
{ {
@ -87,11 +128,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface
} }
/** /**
* Gets the cache key to use for the cache for a given template name. * {@inheritdoc}
*
* @param string $name The name of the template to load
*
* @return string The cache key
*/ */
public function getCacheKey($name) public function getCacheKey($name)
{ {
@ -99,18 +136,36 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface
} }
/** /**
* Returns true if the template is still fresh. * {@inheritdoc}
* */
* @param string $name The template name public function exists($name)
* @param timestamp $time The last modification time of the cached template {
$name = (string) $name;
if (isset($this->cache[$name])) {
return true;
}
try {
$this->findTemplate($name);
return true;
} catch (Twig_Error_Loader $exception) {
return false;
}
}
/**
* {@inheritdoc}
*/ */
public function isFresh($name, $time) public function isFresh($name, $time)
{ {
return filemtime($this->findTemplate($name)) < $time; return filemtime($this->findTemplate($name)) <= $time;
} }
protected function findTemplate($name) protected function findTemplate($name)
{ {
$name = (string) $name;
// normalize name // normalize name
$name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')); $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/'));
@ -120,13 +175,28 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface
$this->validateName($name); $this->validateName($name);
foreach ($this->paths as $path) { $namespace = '__main__';
if (isset($name[0]) && '@' == $name[0]) {
if (false === $pos = strpos($name, '/')) {
throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
}
$namespace = substr($name, 1, $pos - 1);
$name = substr($name, $pos + 1);
}
if (!isset($this->paths[$namespace])) {
throw new Twig_Error_Loader(sprintf('There are no registered paths for namespace "%s".', $namespace));
}
foreach ($this->paths[$namespace] as $path) {
if (is_file($path.'/'.$name)) { if (is_file($path.'/'.$name)) {
return $this->cache[$name] = $path.'/'.$name; return $this->cache[$name] = $path.'/'.$name;
} }
} }
throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths))); throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])));
} }
protected function validateName($name) protected function validateName($name)
@ -135,6 +205,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface
throw new Twig_Error_Loader('A template name cannot contain NUL bytes.'); throw new Twig_Error_Loader('A template name cannot contain NUL bytes.');
} }
$name = ltrim($name, '/');
$parts = explode('/', $name); $parts = explode('/', $name);
$level = 0; $level = 0;
foreach ($parts as $part) { foreach ($parts as $part) {

34
inc/lib/Twig/Loader/String.php

@ -12,22 +12,21 @@
/** /**
* Loads a template from a string. * Loads a template from a string.
* *
* This loader should only be used for unit testing as it has many limitations
* (for instance, the include or extends tag does not make any sense for a string
* loader).
*
* When using this loader with a cache mechanism, you should know that a new cache * When using this loader with a cache mechanism, you should know that a new cache
* key is generated each time a template content "changes" (the cache key being the * key is generated each time a template content "changes" (the cache key being the
* source code of the template). If you don't want to see your cache grows out of * source code of the template). If you don't want to see your cache grows out of
* control, you need to take care of clearing the old cache file by yourself. * control, you need to take care of clearing the old cache file by yourself.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Loader_String implements Twig_LoaderInterface class Twig_Loader_String implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{ {
/** /**
* Gets the source code of a template, given its name. * {@inheritdoc}
*
* @param string $name The name of the template to load
*
* @return string The template source code
*/ */
public function getSource($name) public function getSource($name)
{ {
@ -35,11 +34,15 @@ class Twig_Loader_String implements Twig_LoaderInterface
} }
/** /**
* Gets the cache key to use for the cache for a given template name. * {@inheritdoc}
* */
* @param string $name The name of the template to load public function exists($name)
* {
* @return string The cache key return true;
}
/**
* {@inheritdoc}
*/ */
public function getCacheKey($name) public function getCacheKey($name)
{ {
@ -47,10 +50,7 @@ class Twig_Loader_String implements Twig_LoaderInterface
} }
/** /**
* Returns true if the template is still fresh. * {@inheritdoc}
*
* @param string $name The template name
* @param timestamp $time The last modification time of the cached template
*/ */
public function isFresh($name, $time) public function isFresh($name, $time)
{ {

21
inc/lib/Twig/LoaderInterface.php

@ -12,34 +12,41 @@
/** /**
* Interface all loaders must implement. * Interface all loaders must implement.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
interface Twig_LoaderInterface interface Twig_LoaderInterface
{ {
/** /**
* Gets the source code of a template, given its name. * Gets the source code of a template, given its name.
* *
* @param string $name The name of the template to load * @param string $name The name of the template to load
* *
* @return string The template source code * @return string The template source code
*
* @throws Twig_Error_Loader When $name is not found
*/ */
function getSource($name); public function getSource($name);
/** /**
* Gets the cache key to use for the cache for a given template name. * Gets the cache key to use for the cache for a given template name.
* *
* @param string $name The name of the template to load * @param string $name The name of the template to load
* *
* @return string The cache key * @return string The cache key
*
* @throws Twig_Error_Loader When $name is not found
*/ */
function getCacheKey($name); public function getCacheKey($name);
/** /**
* Returns true if the template is still fresh. * Returns true if the template is still fresh.
* *
* @param string $name The template name * @param string $name The template name
* @param timestamp $time The last modification time of the cached template * @param timestamp $time The last modification time of the cached template
*
* @return Boolean true if the template is fresh, false otherwise
*
* @throws Twig_Error_Loader When $name is not found
*/ */
function isFresh($name, $time); public function isFresh($name, $time);
} }

14
inc/lib/Twig/Markup.php

@ -12,20 +12,26 @@
/** /**
* Marks a content as safe. * Marks a content as safe.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Markup class Twig_Markup implements Countable
{ {
protected $content; protected $content;
protected $charset;
public function __construct($content) public function __construct($content, $charset)
{ {
$this->content = (string) $content; $this->content = (string) $content;
$this->charset = $charset;
} }
public function __toString() public function __toString()
{ {
return $this->content; return $this->content;
} }
public function count()
{
return function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : strlen($this->content);
}
} }

11
inc/lib/Twig/Node.php

@ -13,10 +13,9 @@
/** /**
* Represents a node in the AST. * Represents a node in the AST.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node implements Twig_NodeInterface, Countable, IteratorAggregate class Twig_Node implements Twig_NodeInterface
{ {
protected $nodes; protected $nodes;
protected $attributes; protected $attributes;
@ -134,12 +133,12 @@ class Twig_Node implements Twig_NodeInterface, Countable, IteratorAggregate
* *
* @param string The attribute name * @param string The attribute name
* *
* @return mixed The attribute value * @return mixed The attribute value
*/ */
public function getAttribute($name) public function getAttribute($name)
{ {
if (!array_key_exists($name, $this->attributes)) { if (!array_key_exists($name, $this->attributes)) {
throw new Twig_Error_Runtime(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this))); throw new LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this)));
} }
return $this->attributes[$name]; return $this->attributes[$name];
@ -188,7 +187,7 @@ class Twig_Node implements Twig_NodeInterface, Countable, IteratorAggregate
public function getNode($name) public function getNode($name)
{ {
if (!array_key_exists($name, $this->nodes)) { if (!array_key_exists($name, $this->nodes)) {
throw new Twig_Error_Runtime(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this))); throw new LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this)));
} }
return $this->nodes[$name]; return $this->nodes[$name];

3
inc/lib/Twig/Node/AutoEscape.php

@ -18,8 +18,7 @@
* *
* If autoescaping is disabled, then the value is false. * If autoescaping is disabled, then the value is false.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_AutoEscape extends Twig_Node class Twig_Node_AutoEscape extends Twig_Node
{ {

3
inc/lib/Twig/Node/Block.php

@ -13,8 +13,7 @@
/** /**
* Represents a block node. * Represents a block node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Block extends Twig_Node class Twig_Node_Block extends Twig_Node
{ {

3
inc/lib/Twig/Node/BlockReference.php

@ -13,8 +13,7 @@
/** /**
* Represents a block call node. * Represents a block call node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInterface class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInterface
{ {

19
inc/lib/Twig/Node/Body.php

@ -0,0 +1,19 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a body node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Body extends Twig_Node
{
}

38
inc/lib/Twig/Node/Do.php

@ -0,0 +1,38 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a do node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Do extends Twig_Node
{
public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null)
{
parent::__construct(array('expr' => $expr), array(), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write('')
->subcompile($this->getNode('expr'))
->raw(";\n")
;
}
}

38
inc/lib/Twig/Node/Embed.php

@ -0,0 +1,38 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents an embed node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Embed extends Twig_Node_Include
{
// we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module)
public function __construct($filename, $index, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
{
parent::__construct(new Twig_Node_Expression_Constant('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
$this->setAttribute('filename', $filename);
$this->setAttribute('index', $index);
}
protected function addGetTemplate(Twig_Compiler $compiler)
{
$compiler
->write("\$this->env->loadTemplate(")
->string($this->getAttribute('filename'))
->raw(', ')
->string($this->getAttribute('index'))
->raw(")")
;
}
}

3
inc/lib/Twig/Node/Expression.php

@ -13,8 +13,7 @@
/** /**
* Abstract class for all nodes that represents an expression. * Abstract class for all nodes that represents an expression.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
abstract class Twig_Node_Expression extends Twig_Node abstract class Twig_Node_Expression extends Twig_Node
{ {

51
inc/lib/Twig/Node/Expression/Array.php

@ -10,9 +10,54 @@
*/ */
class Twig_Node_Expression_Array extends Twig_Node_Expression class Twig_Node_Expression_Array extends Twig_Node_Expression
{ {
protected $index;
public function __construct(array $elements, $lineno) public function __construct(array $elements, $lineno)
{ {
parent::__construct($elements, array(), $lineno); parent::__construct($elements, array(), $lineno);
$this->index = -1;
foreach ($this->getKeyValuePairs() as $pair) {
if ($pair['key'] instanceof Twig_Node_Expression_Constant && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) {
$this->index = $pair['key']->getAttribute('value');
}
}
}
public function getKeyValuePairs()
{
$pairs = array();
foreach (array_chunk($this->nodes, 2) as $pair) {
$pairs[] = array(
'key' => $pair[0],
'value' => $pair[1],
);
}
return $pairs;
}
public function hasElement(Twig_Node_Expression $key)
{
foreach ($this->getKeyValuePairs() as $pair) {
// we compare the string representation of the keys
// to avoid comparing the line numbers which are not relevant here.
if ((string) $key == (string) $pair['key']) {
return true;
}
}
return false;
}
public function addElement(Twig_Node_Expression $value, Twig_Node_Expression $key = null)
{
if (null === $key) {
$key = new Twig_Node_Expression_Constant(++$this->index, $value->getLine());
}
array_push($this->nodes, $key, $value);
} }
/** /**
@ -24,16 +69,16 @@ class Twig_Node_Expression_Array extends Twig_Node_Expression
{ {
$compiler->raw('array('); $compiler->raw('array(');
$first = true; $first = true;
foreach ($this->nodes as $name => $node) { foreach ($this->getKeyValuePairs() as $pair) {
if (!$first) { if (!$first) {
$compiler->raw(', '); $compiler->raw(', ');
} }
$first = false; $first = false;
$compiler $compiler
->repr($name) ->subcompile($pair['key'])
->raw(' => ') ->raw(' => ')
->subcompile($node) ->subcompile($pair['value'])
; ;
} }
$compiler->raw(')'); $compiler->raw(')');

6
inc/lib/Twig/Node/Expression/AssignName.php

@ -19,6 +19,10 @@ class Twig_Node_Expression_AssignName extends Twig_Node_Expression_Name
*/ */
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
$compiler->raw(sprintf('$context[\'%s\']', $this->getAttribute('name'))); $compiler
->raw('$context[')
->string($this->getAttribute('name'))
->raw(']')
;
} }
} }

4
inc/lib/Twig/Node/Expression/Binary/FloorDiv.php

@ -17,9 +17,9 @@ class Twig_Node_Expression_Binary_FloorDiv extends Twig_Node_Expression_Binary
*/ */
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
$compiler->raw('floor('); $compiler->raw('intval(floor(');
parent::compile($compiler); parent::compile($compiler);
$compiler->raw(')'); $compiler->raw('))');
} }
public function operator(Twig_Compiler $compiler) public function operator(Twig_Compiler $compiler)

3
inc/lib/Twig/Node/Expression/BlockReference.php

@ -13,8 +13,7 @@
/** /**
* Represents a block call node. * Represents a block call node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Expression_BlockReference extends Twig_Node_Expression class Twig_Node_Expression_BlockReference extends Twig_Node_Expression
{ {

178
inc/lib/Twig/Node/Expression/Call.php

@ -0,0 +1,178 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
{
protected function compileCallable(Twig_Compiler $compiler)
{
$callable = $this->getAttribute('callable');
$closingParenthesis = false;
if ($callable) {
if (is_string($callable)) {
$compiler->raw($callable);
} elseif (is_array($callable) && $callable[0] instanceof Twig_ExtensionInterface) {
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', $callable[0]->getName(), $callable[1]));
} else {
$type = ucfirst($this->getAttribute('type'));
$compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), array', $type, $this->getAttribute('name')));
$closingParenthesis = true;
}
} else {
$compiler->raw($this->getAttribute('thing')->compile());
}
$this->compileArguments($compiler);
if ($closingParenthesis) {
$compiler->raw(')');
}
}
protected function compileArguments(Twig_Compiler $compiler)
{
$compiler->raw('(');
$first = true;
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
$compiler->raw('$this->env');
$first = false;
}
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->raw('$context');
$first = false;
}
if ($this->hasAttribute('arguments')) {
foreach ($this->getAttribute('arguments') as $argument) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->string($argument);
$first = false;
}
}
if ($this->hasNode('node')) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->subcompile($this->getNode('node'));
$first = false;
}
if ($this->hasNode('arguments') && null !== $this->getNode('arguments')) {
$callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null;
$arguments = $this->getArguments($callable, $this->getNode('arguments'));
foreach ($arguments as $node) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->subcompile($node);
$first = false;
}
}
$compiler->raw(')');
}
protected function getArguments($callable, $arguments)
{
$parameters = array();
$named = false;
foreach ($arguments as $name => $node) {
if (!is_int($name)) {
$named = true;
$name = $this->normalizeName($name);
} elseif ($named) {
throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name')));
}
$parameters[$name] = $node;
}
if (!$named) {
return $parameters;
}
if (!$callable) {
throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name')));
}
// manage named arguments
if (is_array($callable)) {
$r = new ReflectionMethod($callable[0], $callable[1]);
} elseif (is_object($callable) && !$callable instanceof Closure) {
$r = new ReflectionObject($callable);
$r = $r->getMethod('__invoke');
} else {
$r = new ReflectionFunction($callable);
}
$definition = $r->getParameters();
if ($this->hasNode('node')) {
array_shift($definition);
}
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
array_shift($definition);
}
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
array_shift($definition);
}
if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
foreach ($this->getAttribute('arguments') as $argument) {
array_shift($definition);
}
}
$arguments = array();
$pos = 0;
foreach ($definition as $param) {
$name = $this->normalizeName($param->name);
if (array_key_exists($name, $parameters)) {
if (array_key_exists($pos, $parameters)) {
throw new Twig_Error_Syntax(sprintf('Arguments "%s" is defined twice for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
}
$arguments[] = $parameters[$name];
unset($parameters[$name]);
} elseif (array_key_exists($pos, $parameters)) {
$arguments[] = $parameters[$pos];
unset($parameters[$pos]);
++$pos;
} elseif ($param->isDefaultValueAvailable()) {
$arguments[] = new Twig_Node_Expression_Constant($param->getDefaultValue(), -1);
} elseif ($param->isOptional()) {
break;
} else {
throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
}
}
foreach (array_keys($parameters) as $name) {
throw new Twig_Error_Syntax(sprintf('Unknown argument "%s" for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
}
return $arguments;
}
protected function normalizeName($name)
{
return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), $name));
}
}

3
inc/lib/Twig/Node/Expression/ExtensionReference.php

@ -12,8 +12,7 @@
/** /**
* Represents an extension call node. * Represents an extension call node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression
{ {

60
inc/lib/Twig/Node/Expression/Filter.php

@ -9,7 +9,7 @@
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
class Twig_Node_Expression_Filter extends Twig_Node_Expression class Twig_Node_Expression_Filter extends Twig_Node_Expression_Call
{ {
public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null) public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null)
{ {
@ -19,54 +19,18 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
$name = $this->getNode('filter')->getAttribute('value'); $name = $this->getNode('filter')->getAttribute('value');
if (false === $filter = $compiler->getEnvironment()->getFilter($name)) { $filter = $compiler->getEnvironment()->getFilter($name);
throw new Twig_Error_Syntax(sprintf('The filter "%s" does not exist', $name), $this->getLine());
$this->setAttribute('name', $name);
$this->setAttribute('type', 'filter');
$this->setAttribute('thing', $filter);
$this->setAttribute('needs_environment', $filter->needsEnvironment());
$this->setAttribute('needs_context', $filter->needsContext());
$this->setAttribute('arguments', $filter->getArguments());
if ($filter instanceof Twig_FilterCallableInterface || $filter instanceof Twig_SimpleFilter) {
$this->setAttribute('callable', $filter->getCallable());
} }
$node = $this->getNode('node'); $this->compileCallable($compiler);
// The default filter is intercepted when the filtered value
// is a name (like obj) or an attribute (like obj.attr)
// In such a case, it's compiled to {{ obj is defined ? obj|default('bar') : 'bar' }}
if ('default' === $name && ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr)) {
$compiler
->raw('((')
->subcompile(new Twig_Node_Expression_Test($node, 'defined', new Twig_Node(), $this->getLine()))
->raw(') ? (')
;
$this->compileFilter($compiler, $filter);
$compiler->raw(') : (');
if ($this->getNode('arguments')->hasNode(0)) {
$compiler->subcompile($this->getNode('arguments')->getNode(0));
} else {
$compiler->string('');
}
$compiler->raw('))');
} else {
$this->compileFilter($compiler, $filter);
}
}
protected function compileFilter(Twig_Compiler $compiler, Twig_FilterInterface $filter)
{
$compiler
->raw($filter->compile().'(')
->raw($filter->needsEnvironment() ? '$this->env, ' : '')
->raw($filter->needsContext() ? '$context, ' : '')
->subcompile($this->getNode('node'))
;
foreach ($this->getNode('arguments') as $node) {
$compiler
->raw(', ')
->subcompile($node)
;
}
$compiler->raw(')');
} }
} }

43
inc/lib/Twig/Node/Expression/Filter/Default.php

@ -0,0 +1,43 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Returns the value or the default value when it is undefined or empty.
*
* <pre>
* {{ var.foo|default('foo item on var is not defined') }}
* </pre>
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Expression_Filter_Default extends Twig_Node_Expression_Filter
{
public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null)
{
$default = new Twig_Node_Expression_Filter($node, new Twig_Node_Expression_Constant('default', $node->getLine()), $arguments, $node->getLine());
if ('default' === $filterName->getAttribute('value') && ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr)) {
$test = new Twig_Node_Expression_Test_Defined(clone $node, 'defined', new Twig_Node(), $node->getLine());
$false = count($arguments) ? $arguments->getNode(0) : new Twig_Node_Expression_Constant('', $node->getLine());
$node = new Twig_Node_Expression_Conditional($test, $default, $false, $node->getLine());
} else {
$node = $default;
}
parent::__construct($node, $filterName, $arguments, $lineno, $tag);
}
public function compile(Twig_Compiler $compiler)
{
$compiler->subcompile($this->getNode('node'));
}
}

38
inc/lib/Twig/Node/Expression/Function.php

@ -8,7 +8,7 @@
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
class Twig_Node_Expression_Function extends Twig_Node_Expression class Twig_Node_Expression_Function extends Twig_Node_Expression_Call
{ {
public function __construct($name, Twig_NodeInterface $arguments, $lineno) public function __construct($name, Twig_NodeInterface $arguments, $lineno)
{ {
@ -17,33 +17,19 @@ class Twig_Node_Expression_Function extends Twig_Node_Expression
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
$function = $compiler->getEnvironment()->getFunction($this->getAttribute('name')); $name = $this->getAttribute('name');
if (false === $function) { $function = $compiler->getEnvironment()->getFunction($name);
throw new Twig_Error_Syntax(sprintf('The function "%s" does not exist', $this->getAttribute('name')), $this->getLine());
}
$compiler
->raw($function->compile().'(')
->raw($function->needsEnvironment() ? '$this->env' : '')
;
if ($function->needsContext()) {
$compiler->raw($function->needsEnvironment() ? ', $context' : '$context');
}
$first = true; $this->setAttribute('name', $name);
foreach ($this->getNode('arguments') as $node) { $this->setAttribute('type', 'function');
if (!$first) { $this->setAttribute('thing', $function);
$compiler->raw(', '); $this->setAttribute('needs_environment', $function->needsEnvironment());
} else { $this->setAttribute('needs_context', $function->needsContext());
if ($function->needsEnvironment() || $function->needsContext()) { $this->setAttribute('arguments', $function->getArguments());
$compiler->raw(', '); if ($function instanceof Twig_FunctionCallableInterface || $function instanceof Twig_SimpleFunction) {
} $this->setAttribute('callable', $function->getCallable());
$first = false;
}
$compiler->subcompile($node);
} }
$compiler->raw(')'); $this->compileCallable($compiler);
} }
} }

56
inc/lib/Twig/Node/Expression/GetAttr.php

@ -11,43 +11,43 @@
*/ */
class Twig_Node_Expression_GetAttr extends Twig_Node_Expression class Twig_Node_Expression_GetAttr extends Twig_Node_Expression
{ {
public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_NodeInterface $arguments, $type, $lineno) public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_Node_Expression_Array $arguments, $type, $lineno)
{ {
parent::__construct(array('node' => $node, 'attribute' => $attribute, 'arguments' => $arguments), array('type' => $type), $lineno); parent::__construct(array('node' => $node, 'attribute' => $attribute, 'arguments' => $arguments), array('type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false), $lineno);
} }
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
$compiler->raw('$this->getAttribute('); if (function_exists('twig_template_get_attributes') && !$this->getAttribute('disable_c_ext')) {
$compiler->raw('twig_template_get_attributes($this, ');
if ($this->hasAttribute('is_defined_test') && $compiler->getEnvironment()->isStrictVariables()) {
$compiler->subcompile(new Twig_Node_Expression_Filter(
$this->getNode('node'),
new Twig_Node_Expression_Constant('default', $this->getLine()),
new Twig_Node(),
$this->getLine()
));
} else { } else {
$compiler->subcompile($this->getNode('node')); $compiler->raw('$this->getAttribute(');
} }
$compiler if ($this->getAttribute('ignore_strict_check')) {
->raw(', ') $this->getNode('node')->setAttribute('ignore_strict_check', true);
->subcompile($this->getNode('attribute'))
->raw(', array(')
;
foreach ($this->getNode('arguments') as $node) {
$compiler
->subcompile($node)
->raw(', ')
;
} }
$compiler $compiler->subcompile($this->getNode('node'));
->raw('), ')
->repr($this->getAttribute('type')) $compiler->raw(', ')->subcompile($this->getNode('attribute'));
->raw($this->hasAttribute('is_defined_test') ? ', true' : ', false')
->raw(')'); if (count($this->getNode('arguments')) || Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments'));
if (Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', ')->repr($this->getAttribute('type'));
}
if ($this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', '.($this->getAttribute('is_defined_test') ? 'true' : 'false'));
}
if ($this->getAttribute('ignore_strict_check')) {
$compiler->raw(', '.($this->getAttribute('ignore_strict_check') ? 'true' : 'false'));
}
}
$compiler->raw(')');
} }
} }

41
inc/lib/Twig/Node/Expression/MethodCall.php

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_Expression_MethodCall extends Twig_Node_Expression
{
public function __construct(Twig_Node_Expression $node, $method, Twig_Node_Expression_Array $arguments, $lineno)
{
parent::__construct(array('node' => $node, 'arguments' => $arguments), array('method' => $method, 'safe' => false), $lineno);
if ($node instanceof Twig_Node_Expression_Name) {
$node->setAttribute('always_defined', true);
}
}
public function compile(Twig_Compiler $compiler)
{
$compiler
->subcompile($this->getNode('node'))
->raw('->')
->raw($this->getAttribute('method'))
->raw('(')
;
$first = true;
foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
if (!$first) {
$compiler->raw(', ');
}
$first = false;
$compiler->subcompile($pair['value']);
}
$compiler->raw(')');
}
}

71
inc/lib/Twig/Node/Expression/Name.php

@ -11,31 +11,78 @@
*/ */
class Twig_Node_Expression_Name extends Twig_Node_Expression class Twig_Node_Expression_Name extends Twig_Node_Expression
{ {
protected $specialVars = array(
'_self' => '$this',
'_context' => '$context',
'_charset' => '$this->env->getCharset()',
);
public function __construct($name, $lineno) public function __construct($name, $lineno)
{ {
parent::__construct(array(), array('name' => $name), $lineno); parent::__construct(array(), array('name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false), $lineno);
} }
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
static $specialVars = array(
'_self' => '$this',
'_context' => '$context',
'_charset' => '$this->env->getCharset()',
);
$name = $this->getAttribute('name'); $name = $this->getAttribute('name');
if ($this->hasAttribute('is_defined_test')) { if ($this->getAttribute('is_defined_test')) {
if (isset($specialVars[$name])) { if ($this->isSpecial()) {
$compiler->repr(true); $compiler->repr(true);
} else { } else {
$compiler->raw('array_key_exists(')->repr($name)->raw(', $context)'); $compiler->raw('array_key_exists(')->repr($name)->raw(', $context)');
} }
} elseif (isset($specialVars[$name])) { } elseif ($this->isSpecial()) {
$compiler->raw($specialVars[$name]); $compiler->raw($this->specialVars[$name]);
} elseif ($this->getAttribute('always_defined')) {
$compiler
->raw('$context[')
->string($name)
->raw(']')
;
} else { } else {
$compiler->raw(sprintf('$this->getContext($context, \'%s\')', $name)); // remove the non-PHP 5.4 version when PHP 5.3 support is dropped
// as the non-optimized version is just a workaround for slow ternary operator
// when the context has a lot of variables
if (version_compare(phpversion(), '5.4.0RC1', '>=')) {
// PHP 5.4 ternary operator performance was optimized
$compiler
->raw('(isset($context[')
->string($name)
->raw(']) ? $context[')
->string($name)
->raw('] : ')
;
if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {
$compiler->raw('null)');
} else {
$compiler->raw('$this->getContext($context, ')->string($name)->raw('))');
}
} else {
$compiler
->raw('$this->getContext($context, ')
->string($name)
;
if ($this->getAttribute('ignore_strict_check')) {
$compiler->raw(', true');
}
$compiler
->raw(')')
;
}
} }
} }
public function isSpecial()
{
return isset($this->specialVars[$this->getAttribute('name')]);
}
public function isSimple()
{
return !$this->isSpecial() && !$this->getAttribute('is_defined_test');
}
} }

24
inc/lib/Twig/Node/Expression/Parent.php

@ -13,14 +13,13 @@
/** /**
* Represents a parent node. * Represents a parent node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Expression_Parent extends Twig_Node_Expression class Twig_Node_Expression_Parent extends Twig_Node_Expression
{ {
public function __construct($name, $lineno, $tag = null) public function __construct($name, $lineno, $tag = null)
{ {
parent::__construct(array(), array('name' => $name), $lineno, $tag); parent::__construct(array(), array('output' => false, 'name' => $name), $lineno, $tag);
} }
/** /**
@ -30,10 +29,19 @@ class Twig_Node_Expression_Parent extends Twig_Node_Expression
*/ */
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
$compiler if ($this->getAttribute('output')) {
->raw("\$this->renderParentBlock(") $compiler
->string($this->getAttribute('name')) ->addDebugInfo($this)
->raw(", \$context, \$blocks)") ->write("\$this->displayParentBlock(")
; ->string($this->getAttribute('name'))
->raw(", \$context, \$blocks);\n")
;
} else {
$compiler
->raw("\$this->renderParentBlock(")
->string($this->getAttribute('name'))
->raw(", \$context, \$blocks)")
;
}
} }
} }

26
inc/lib/Twig/Node/Expression/TempName.php

@ -0,0 +1,26 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_Expression_TempName extends Twig_Node_Expression
{
public function __construct($name, $lineno)
{
parent::__construct(array(), array('name' => $name), $lineno);
}
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('$_')
->raw($this->getAttribute('name'))
->raw('_')
;
}
}

44
inc/lib/Twig/Node/Expression/Test.php

@ -8,7 +8,7 @@
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
class Twig_Node_Expression_Test extends Twig_Node_Expression class Twig_Node_Expression_Test extends Twig_Node_Expression_Call
{ {
public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno) public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno)
{ {
@ -17,44 +17,16 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
$testMap = $compiler->getEnvironment()->getTests();
if (!isset($testMap[$this->getAttribute('name')])) {
throw new Twig_Error_Syntax(sprintf('The test "%s" does not exist', $this->getAttribute('name')), $this->getLine());
}
$name = $this->getAttribute('name'); $name = $this->getAttribute('name');
$node = $this->getNode('node'); $test = $compiler->getEnvironment()->getTest($name);
// defined is a special case
if ('defined' === $name) {
if ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr) {
$node->setAttribute('is_defined_test', true);
$compiler->subcompile($node);
$node->removeAttribute('is_defined_test');
} else {
throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine());
}
return;
}
$compiler
->raw($testMap[$name]->compile().'(')
->subcompile($node)
;
if (null !== $this->getNode('arguments')) {
$compiler->raw(', ');
$max = count($this->getNode('arguments')) - 1;
foreach ($this->getNode('arguments') as $i => $arg) {
$compiler->subcompile($arg);
if ($i != $max) { $this->setAttribute('name', $name);
$compiler->raw(', '); $this->setAttribute('type', 'test');
} $this->setAttribute('thing', $test);
} if ($test instanceof Twig_TestCallableInterface || $test instanceof Twig_SimpleTest) {
$this->setAttribute('callable', $test->getCallable());
} }
$compiler->raw(')'); $this->compileCallable($compiler);
} }
} }

46
inc/lib/Twig/Node/Expression/Test/Constant.php

@ -0,0 +1,46 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Checks if a variable is the exact same value as a constant.
*
* <pre>
* {% if post.status is constant('Post::PUBLISHED') %}
* the status attribute is exactly the same as Post::PUBLISHED
* {% endif %}
* </pre>
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Expression_Test_Constant extends Twig_Node_Expression_Test
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' === constant(')
;
if ($this->getNode('arguments')->hasNode(1)) {
$compiler
->raw('get_class(')
->subcompile($this->getNode('arguments')->getNode(1))
->raw(')."::".')
;
}
$compiler
->subcompile($this->getNode('arguments')->getNode(0))
->raw('))')
;
}
}

54
inc/lib/Twig/Node/Expression/Test/Defined.php

@ -0,0 +1,54 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Checks if a variable is defined in the current context.
*
* <pre>
* {# defined works with variable names and variable attributes #}
* {% if foo is defined %}
* {# ... #}
* {% endif %}
* </pre>
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test
{
public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno)
{
parent::__construct($node, $name, $arguments, $lineno);
if ($node instanceof Twig_Node_Expression_Name) {
$node->setAttribute('is_defined_test', true);
} elseif ($node instanceof Twig_Node_Expression_GetAttr) {
$node->setAttribute('is_defined_test', true);
$this->changeIgnoreStrictCheck($node);
} else {
throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine());
}
}
protected function changeIgnoreStrictCheck(Twig_Node_Expression_GetAttr $node)
{
$node->setAttribute('ignore_strict_check', true);
if ($node->getNode('node') instanceof Twig_Node_Expression_GetAttr) {
$this->changeIgnoreStrictCheck($node->getNode('node'));
}
}
public function compile(Twig_Compiler $compiler)
{
$compiler->subcompile($this->getNode('node'));
}
}

33
inc/lib/Twig/Node/Expression/Test/Divisibleby.php

@ -0,0 +1,33 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Checks if a variable is divisible by a number.
*
* <pre>
* {% if loop.index is divisibleby(3) %}
* </pre>
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Expression_Test_Divisibleby extends Twig_Node_Expression_Test
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('(0 == ')
->subcompile($this->getNode('node'))
->raw(' % ')
->subcompile($this->getNode('arguments')->getNode(0))
->raw(')')
;
}
}

32
inc/lib/Twig/Node/Expression/Test/Even.php

@ -0,0 +1,32 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Checks if a number is even.
*
* <pre>
* {{ var is even }}
* </pre>
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Expression_Test_Even extends Twig_Node_Expression_Test
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' % 2 == 0')
->raw(')')
;
}
}

31
inc/lib/Twig/Node/Expression/Test/Null.php

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Checks that a variable is null.
*
* <pre>
* {{ var is none }}
* </pre>
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Expression_Test_Null extends Twig_Node_Expression_Test
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('(null === ')
->subcompile($this->getNode('node'))
->raw(')')
;
}
}

32
inc/lib/Twig/Node/Expression/Test/Odd.php

@ -0,0 +1,32 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Checks if a number is odd.
*
* <pre>
* {{ var is odd }}
* </pre>
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' % 2 == 1')
->raw(')')
;
}
}

29
inc/lib/Twig/Node/Expression/Test/Sameas.php

@ -0,0 +1,29 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Checks if a variable is the same as another one (=== in PHP).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Expression_Test_Sameas extends Twig_Node_Expression_Test
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' === ')
->subcompile($this->getNode('arguments')->getNode(0))
->raw(')')
;
}
}

36
inc/lib/Twig/Node/Flush.php

@ -0,0 +1,36 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a flush node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Flush extends Twig_Node
{
public function __construct($lineno, $tag)
{
parent::__construct(array(), array(), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write("flush();\n")
;
}
}

63
inc/lib/Twig/Node/For.php

@ -13,14 +13,21 @@
/** /**
* Represents a for node. * Represents a for node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_For extends Twig_Node class Twig_Node_For extends Twig_Node
{ {
protected $loop;
public function __construct(Twig_Node_Expression_AssignName $keyTarget, Twig_Node_Expression_AssignName $valueTarget, Twig_Node_Expression $seq, Twig_Node_Expression $ifexpr = null, Twig_NodeInterface $body, Twig_NodeInterface $else = null, $lineno, $tag = null) public function __construct(Twig_Node_Expression_AssignName $keyTarget, Twig_Node_Expression_AssignName $valueTarget, Twig_Node_Expression $seq, Twig_Node_Expression $ifexpr = null, Twig_NodeInterface $body, Twig_NodeInterface $else = null, $lineno, $tag = null)
{ {
parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'ifexpr' => $ifexpr, 'body' => $body, 'else' => $else), array('with_loop' => true), $lineno, $tag); $body = new Twig_Node(array($body, $this->loop = new Twig_Node_ForLoop($lineno, $tag)));
if (null !== $ifexpr) {
$body = new Twig_Node_If(new Twig_Node(array($ifexpr, $body)), null, $lineno, $tag);
}
parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body, 'else' => $else), array('with_loop' => true, 'ifexpr' => null !== $ifexpr), $lineno, $tag);
} }
/** /**
@ -53,7 +60,7 @@ class Twig_Node_For extends Twig_Node
->write(");\n") ->write(");\n")
; ;
if (null === $this->getNode('ifexpr')) { if (!$this->getAttribute('ifexpr')) {
$compiler $compiler
->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {\n") ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {\n")
->indent() ->indent()
@ -68,6 +75,10 @@ class Twig_Node_For extends Twig_Node
} }
} }
$this->loop->setAttribute('else', null !== $this->getNode('else'));
$this->loop->setAttribute('with_loop', $this->getAttribute('with_loop'));
$this->loop->setAttribute('ifexpr', $this->getAttribute('ifexpr'));
$compiler $compiler
->write("foreach (\$context['_seq'] as ") ->write("foreach (\$context['_seq'] as ")
->subcompile($this->getNode('key_target')) ->subcompile($this->getNode('key_target'))
@ -75,47 +86,7 @@ class Twig_Node_For extends Twig_Node
->subcompile($this->getNode('value_target')) ->subcompile($this->getNode('value_target'))
->raw(") {\n") ->raw(") {\n")
->indent() ->indent()
; ->subcompile($this->getNode('body'))
if (null !== $this->getNode('ifexpr')) {
$compiler
->write("if (!(")
->subcompile($this->getNode('ifexpr'))
->raw(")) {\n")
->indent()
->write("continue;\n")
->outdent()
->write("}\n\n")
;
}
$compiler->subcompile($this->getNode('body'));
if (null !== $this->getNode('else')) {
$compiler->write("\$context['_iterated'] = true;\n");
}
if ($this->getAttribute('with_loop')) {
$compiler
->write("++\$context['loop']['index0'];\n")
->write("++\$context['loop']['index'];\n")
->write("\$context['loop']['first'] = false;\n")
;
if (null === $this->getNode('ifexpr')) {
$compiler
->write("if (isset(\$context['loop']['length'])) {\n")
->indent()
->write("--\$context['loop']['revindex0'];\n")
->write("--\$context['loop']['revindex'];\n")
->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n")
->outdent()
->write("}\n")
;
}
}
$compiler
->outdent() ->outdent()
->write("}\n") ->write("}\n")
; ;
@ -136,6 +107,6 @@ class Twig_Node_For extends Twig_Node
$compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n"); $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n");
// keep the values set in the inner context for variables defined in the outer context // keep the values set in the inner context for variables defined in the outer context
$compiler->write("\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));\n"); $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n");
} }
} }

55
inc/lib/Twig/Node/ForLoop.php

@ -0,0 +1,55 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Internal node used by the for node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_ForLoop extends Twig_Node
{
public function __construct($lineno, $tag = null)
{
parent::__construct(array(), array('with_loop' => false, 'ifexpr' => false, 'else' => false), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile(Twig_Compiler $compiler)
{
if ($this->getAttribute('else')) {
$compiler->write("\$context['_iterated'] = true;\n");
}
if ($this->getAttribute('with_loop')) {
$compiler
->write("++\$context['loop']['index0'];\n")
->write("++\$context['loop']['index'];\n")
->write("\$context['loop']['first'] = false;\n")
;
if (!$this->getAttribute('ifexpr')) {
$compiler
->write("if (isset(\$context['loop']['length'])) {\n")
->indent()
->write("--\$context['loop']['revindex0'];\n")
->write("--\$context['loop']['revindex'];\n")
->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n")
->outdent()
->write("}\n")
;
}
}
}
}

3
inc/lib/Twig/Node/If.php

@ -13,8 +13,7 @@
/** /**
* Represents an if node. * Represents an if node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_If extends Twig_Node class Twig_Node_If extends Twig_Node
{ {

3
inc/lib/Twig/Node/Import.php

@ -12,8 +12,7 @@
/** /**
* Represents an import node. * Represents an import node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Import extends Twig_Node class Twig_Node_Import extends Twig_Node
{ {

45
inc/lib/Twig/Node/Include.php

@ -13,8 +13,7 @@
/** /**
* Represents an include node. * Represents an include node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface
{ {
@ -39,21 +38,46 @@ class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface
; ;
} }
$this->addGetTemplate($compiler);
$compiler->raw('->display(');
$this->addTemplateArguments($compiler);
$compiler->raw(");\n");
if ($this->getAttribute('ignore_missing')) {
$compiler
->outdent()
->write("} catch (Twig_Error_Loader \$e) {\n")
->indent()
->write("// ignore missing template\n")
->outdent()
->write("}\n\n")
;
}
}
protected function addGetTemplate(Twig_Compiler $compiler)
{
if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) { if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) {
$compiler $compiler
->write("\$this->env->loadTemplate(") ->write("\$this->env->loadTemplate(")
->subcompile($this->getNode('expr')) ->subcompile($this->getNode('expr'))
->raw(")->display(") ->raw(")")
; ;
} else { } else {
$compiler $compiler
->write("\$template = \$this->env->resolveTemplate(") ->write("\$template = \$this->env->resolveTemplate(")
->subcompile($this->getNode('expr')) ->subcompile($this->getNode('expr'))
->raw(");\n") ->raw(");\n")
->write('$template->display(') ->write('$template')
; ;
} }
}
protected function addTemplateArguments(Twig_Compiler $compiler)
{
if (false === $this->getAttribute('only')) { if (false === $this->getAttribute('only')) {
if (null === $this->getNode('variables')) { if (null === $this->getNode('variables')) {
$compiler->raw('$context'); $compiler->raw('$context');
@ -71,18 +95,5 @@ class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface
$compiler->subcompile($this->getNode('variables')); $compiler->subcompile($this->getNode('variables'));
} }
} }
$compiler->raw(");\n");
if ($this->getAttribute('ignore_missing')) {
$compiler
->outdent()
->write("} catch (Twig_Error_Loader \$e) {\n")
->indent()
->write("// ignore missing template\n")
->outdent()
->write("}\n\n")
;
}
} }
} }

59
inc/lib/Twig/Node/Macro.php

@ -12,8 +12,7 @@
/** /**
* Represents a macro node. * Represents a macro node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Macro extends Twig_Node class Twig_Node_Macro extends Twig_Node
{ {
@ -29,43 +28,67 @@ class Twig_Node_Macro extends Twig_Node
*/ */
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
$arguments = array(); $compiler
foreach ($this->getNode('arguments') as $argument) { ->addDebugInfo($this)
$arguments[] = '$'.$argument->getAttribute('name').' = null'; ->write(sprintf("public function get%s(", $this->getAttribute('name')))
;
$count = count($this->getNode('arguments'));
$pos = 0;
foreach ($this->getNode('arguments') as $name => $default) {
$compiler
->raw('$_'.$name.' = ')
->subcompile($default)
;
if (++$pos < $count) {
$compiler->raw(', ');
}
} }
$compiler $compiler
->addDebugInfo($this) ->raw(")\n")
->write(sprintf("public function get%s(%s)\n", $this->getAttribute('name'), implode(', ', $arguments)), "{\n") ->write("{\n")
->indent()
->write("\$context = array_merge(\$this->env->getGlobals(), array(\n")
->indent() ->indent()
; ;
foreach ($this->getNode('arguments') as $argument) { if (!count($this->getNode('arguments'))) {
$compiler->write("\$context = \$this->env->getGlobals();\n\n");
} else {
$compiler $compiler
->write('') ->write("\$context = \$this->env->mergeGlobals(array(\n")
->string($argument->getAttribute('name')) ->indent()
->raw(' => $'.$argument->getAttribute('name')) ;
->raw(",\n")
foreach ($this->getNode('arguments') as $name => $default) {
$compiler
->write('')
->string($name)
->raw(' => $_'.$name)
->raw(",\n")
;
}
$compiler
->outdent()
->write("));\n\n")
; ;
} }
$compiler $compiler
->outdent() ->write("\$blocks = array();\n\n")
->write("));\n\n")
->write("ob_start();\n") ->write("ob_start();\n")
->write("try {\n") ->write("try {\n")
->indent() ->indent()
->subcompile($this->getNode('body')) ->subcompile($this->getNode('body'))
->outdent() ->outdent()
->write("} catch(Exception \$e) {\n") ->write("} catch (Exception \$e) {\n")
->indent() ->indent()
->write("ob_end_clean();\n\n") ->write("ob_end_clean();\n\n")
->write("throw \$e;\n") ->write("throw \$e;\n")
->outdent() ->outdent()
->write("}\n\n") ->write("}\n\n")
->write("return ob_get_clean();\n") ->write("return ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset());\n")
->outdent() ->outdent()
->write("}\n\n") ->write("}\n\n")
; ;

127
inc/lib/Twig/Node/Module.php

@ -13,14 +13,19 @@
/** /**
* Represents a module node. * Represents a module node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Module extends Twig_Node class Twig_Node_Module extends Twig_Node
{ {
public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $filename) public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $filename)
{ {
parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename), 1); // embedded templates are set as attributes so that they are only visited once by the visitors
parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename, 'index' => null, 'embedded_templates' => $embeddedTemplates), 1);
}
public function setIndex($index)
{
$this->setAttribute('index', $index);
} }
/** /**
@ -31,13 +36,21 @@ class Twig_Node_Module extends Twig_Node
public function compile(Twig_Compiler $compiler) public function compile(Twig_Compiler $compiler)
{ {
$this->compileTemplate($compiler); $this->compileTemplate($compiler);
foreach ($this->getAttribute('embedded_templates') as $template) {
$compiler->subcompile($template);
}
} }
protected function compileTemplate(Twig_Compiler $compiler) protected function compileTemplate(Twig_Compiler $compiler)
{ {
if (!$this->getAttribute('index')) {
$compiler->write('<?php');
}
$this->compileClassHeader($compiler); $this->compileClassHeader($compiler);
if (count($this->getNode('blocks')) || count($this->getNode('traits'))) { if (count($this->getNode('blocks')) || count($this->getNode('traits')) || null === $this->getNode('parent') || $this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
$this->compileConstructor($compiler); $this->compileConstructor($compiler);
} }
@ -57,29 +70,31 @@ class Twig_Node_Module extends Twig_Node
$this->compileIsTraitable($compiler); $this->compileIsTraitable($compiler);
$this->compileDebugInfo($compiler);
$this->compileClassFooter($compiler); $this->compileClassFooter($compiler);
} }
protected function compileGetParent(Twig_Compiler $compiler) protected function compileGetParent(Twig_Compiler $compiler)
{ {
if (null === $this->getNode('parent')) {
return;
}
$compiler $compiler
->write("protected function doGetParent(array \$context)\n", "{\n") ->write("protected function doGetParent(array \$context)\n", "{\n")
->indent() ->indent()
->write("return ") ->write("return ")
; ;
if (null === $this->getNode('parent')) { if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
$compiler->raw("false"); $compiler->subcompile($this->getNode('parent'));
} else { } else {
if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { $compiler
$compiler->subcompile($this->getNode('parent')); ->raw("\$this->env->resolveTemplate(")
} else { ->subcompile($this->getNode('parent'))
$compiler ->raw(")")
->raw("\$this->env->resolveTemplate(") ;
->subcompile($this->getNode('parent'))
->raw(")")
;
}
} }
$compiler $compiler
@ -91,21 +106,25 @@ class Twig_Node_Module extends Twig_Node
protected function compileDisplayBody(Twig_Compiler $compiler) protected function compileDisplayBody(Twig_Compiler $compiler)
{ {
$compiler->write("\$context = array_merge(\$this->env->getGlobals(), \$context);\n\n");
$compiler->subcompile($this->getNode('body')); $compiler->subcompile($this->getNode('body'));
if (null !== $this->getNode('parent')) { if (null !== $this->getNode('parent')) {
$compiler->write("\$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
$compiler->write("\$this->parent");
} else {
$compiler->write("\$this->getParent(\$context)");
}
$compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
} }
} }
protected function compileClassHeader(Twig_Compiler $compiler) protected function compileClassHeader(Twig_Compiler $compiler)
{ {
$compiler $compiler
->write("<?php\n\n") ->write("\n\n")
// if the filename contains */, add a blank to avoid a PHP parse error // if the filename contains */, add a blank to avoid a PHP parse error
->write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n") ->write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n")
->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'))) ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('index')))
->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass()))
->write("{\n") ->write("{\n")
->indent() ->indent()
@ -120,6 +139,17 @@ class Twig_Node_Module extends Twig_Node
->write("parent::__construct(\$env);\n\n") ->write("parent::__construct(\$env);\n\n")
; ;
// parent
if (null === $this->getNode('parent')) {
$compiler->write("\$this->parent = false;\n\n");
} elseif ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
$compiler
->write("\$this->parent = \$this->env->loadTemplate(")
->subcompile($this->getNode('parent'))
->raw(");\n\n")
;
}
$countTraits = count($this->getNode('traits')); $countTraits = count($this->getNode('traits'));
if ($countTraits) { if ($countTraits) {
// traits // traits
@ -151,18 +181,32 @@ class Twig_Node_Module extends Twig_Node
} }
} }
$compiler if ($countTraits > 1) {
->write("\$this->blocks = array_merge(\n") $compiler
->indent() ->write("\$this->traits = array_merge(\n")
; ->indent()
;
for ($i = 0; $i < $countTraits; $i++) {
$compiler
->write(sprintf("\$_trait_%s_blocks".($i == $countTraits - 1 ? '' : ',')."\n", $i))
;
}
for ($i = 0; $i < $countTraits; $i++) {
$compiler $compiler
->write(sprintf("\$_trait_%s_blocks,\n", $i)) ->outdent()
->write(");\n\n")
;
} else {
$compiler
->write("\$this->traits = \$_trait_0_blocks;\n\n")
; ;
} }
$compiler $compiler
->write("\$this->blocks = array_merge(\n")
->indent()
->write("\$this->traits,\n")
->write("array(\n") ->write("array(\n")
; ;
} else { } else {
@ -250,11 +294,21 @@ class Twig_Node_Module extends Twig_Node
// only contains blocks and use statements. // only contains blocks and use statements.
$traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros')); $traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros'));
if ($traitable) { if ($traitable) {
if (!count($nodes = $this->getNode('body'))) { if ($this->getNode('body') instanceof Twig_Node_Body) {
$nodes = new Twig_Node(array($this->getNode('body'))); $nodes = $this->getNode('body')->getNode(0);
} else {
$nodes = $this->getNode('body');
}
if (!count($nodes)) {
$nodes = new Twig_Node(array($nodes));
} }
foreach ($nodes as $node) { foreach ($nodes as $node) {
if (!count($node)) {
continue;
}
if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) { if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) {
continue; continue;
} }
@ -268,16 +322,31 @@ class Twig_Node_Module extends Twig_Node
} }
} }
if ($traitable) {
return;
}
$compiler $compiler
->write("public function isTraitable()\n", "{\n") ->write("public function isTraitable()\n", "{\n")
->indent() ->indent()
->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
->outdent() ->outdent()
->write("}\n\n")
;
}
protected function compileDebugInfo(Twig_Compiler $compiler)
{
$compiler
->write("public function getDebugInfo()\n", "{\n")
->indent()
->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
->outdent()
->write("}\n") ->write("}\n")
; ;
} }
public function compileLoadTemplate(Twig_Compiler $compiler, $node, $var) protected function compileLoadTemplate(Twig_Compiler $compiler, $node, $var)
{ {
if ($node instanceof Twig_Node_Expression_Constant) { if ($node instanceof Twig_Node_Expression_Constant) {
$compiler $compiler

3
inc/lib/Twig/Node/Print.php

@ -13,8 +13,7 @@
/** /**
* Represents a node that outputs an expression. * Represents a node that outputs an expression.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface
{ {

3
inc/lib/Twig/Node/Sandbox.php

@ -12,8 +12,7 @@
/** /**
* Represents a sandbox node. * Represents a sandbox node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Sandbox extends Twig_Node class Twig_Node_Sandbox extends Twig_Node
{ {

23
inc/lib/Twig/Node/SandboxedModule.php

@ -13,8 +13,7 @@
/** /**
* Represents a module node. * Represents a module node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_SandboxedModule extends Twig_Node_Module class Twig_Node_SandboxedModule extends Twig_Node_Module
{ {
@ -24,7 +23,9 @@ class Twig_Node_SandboxedModule extends Twig_Node_Module
public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags, array $usedFunctions) public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags, array $usedFunctions)
{ {
parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getNode('traits'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag()); parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getNode('traits'), $node->getAttribute('embedded_templates'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag());
$this->setAttribute('index', $node->getAttribute('index'));
$this->usedFilters = $usedFilters; $this->usedFilters = $usedFilters;
$this->usedTags = $usedTags; $this->usedTags = $usedTags;
@ -33,9 +34,7 @@ class Twig_Node_SandboxedModule extends Twig_Node_Module
protected function compileDisplayBody(Twig_Compiler $compiler) protected function compileDisplayBody(Twig_Compiler $compiler)
{ {
if (null === $this->getNode('parent')) { $compiler->write("\$this->checkSecurity();\n");
$compiler->write("\$this->checkSecurity();\n");
}
parent::compileDisplayBody($compiler); parent::compileDisplayBody($compiler);
} }
@ -45,7 +44,7 @@ class Twig_Node_SandboxedModule extends Twig_Node_Module
parent::compileDisplayFooter($compiler); parent::compileDisplayFooter($compiler);
$compiler $compiler
->write("protected function checkSecurity() {\n") ->write("protected function checkSecurity()\n", "{\n")
->indent() ->indent()
->write("\$this->env->getExtension('sandbox')->checkSecurity(\n") ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n")
->indent() ->indent()
@ -54,16 +53,6 @@ class Twig_Node_SandboxedModule extends Twig_Node_Module
->write(!$this->usedFunctions ? "array()\n" : "array('".implode('\', \'', $this->usedFunctions)."')\n") ->write(!$this->usedFunctions ? "array()\n" : "array('".implode('\', \'', $this->usedFunctions)."')\n")
->outdent() ->outdent()
->write(");\n") ->write(");\n")
;
if (null !== $this->getNode('parent')) {
$compiler
->raw("\n")
->write("\$this->parent->checkSecurity();\n")
;
}
$compiler
->outdent() ->outdent()
->write("}\n\n") ->write("}\n\n")
; ;

3
inc/lib/Twig/Node/SandboxedPrint.php

@ -17,8 +17,7 @@
* and if the sandbox is enabled, we need to check that the __toString() * and if the sandbox is enabled, we need to check that the __toString()
* method is allowed if 'article' is an object. * method is allowed if 'article' is an object.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_SandboxedPrint extends Twig_Node_Print class Twig_Node_SandboxedPrint extends Twig_Node_Print
{ {

9
inc/lib/Twig/Node/Set.php

@ -12,8 +12,7 @@
/** /**
* Represents a set node. * Represents a set node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Set extends Twig_Node class Twig_Node_Set extends Twig_Node
{ {
@ -67,7 +66,7 @@ class Twig_Node_Set extends Twig_Node
$compiler->subcompile($this->getNode('names'), false); $compiler->subcompile($this->getNode('names'), false);
if ($this->getAttribute('capture')) { if ($this->getAttribute('capture')) {
$compiler->raw(" = new Twig_Markup(ob_get_clean())"); $compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())");
} }
} }
@ -87,9 +86,9 @@ class Twig_Node_Set extends Twig_Node
} else { } else {
if ($this->getAttribute('safe')) { if ($this->getAttribute('safe')) {
$compiler $compiler
->raw("new Twig_Markup(") ->raw("('' === \$tmp = ")
->subcompile($this->getNode('values')) ->subcompile($this->getNode('values'))
->raw(")") ->raw(") ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())")
; ;
} else { } else {
$compiler->subcompile($this->getNode('values')); $compiler->subcompile($this->getNode('values'));

35
inc/lib/Twig/Node/SetTemp.php

@ -0,0 +1,35 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_SetTemp extends Twig_Node
{
public function __construct($name, $lineno)
{
parent::__construct(array(), array('name' => $name), $lineno);
}
public function compile(Twig_Compiler $compiler)
{
$name = $this->getAttribute('name');
$compiler
->addDebugInfo($this)
->write('if (isset($context[')
->string($name)
->raw('])) { $_')
->raw($name)
->raw('_ = $context[')
->repr($name)
->raw(']; } else { $_')
->raw($name)
->raw("_ = null; }\n")
;
}
}

3
inc/lib/Twig/Node/Spaceless.php

@ -14,8 +14,7 @@
* *
* It removes spaces between HTML tags. * It removes spaces between HTML tags.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Spaceless extends Twig_Node class Twig_Node_Spaceless extends Twig_Node
{ {

3
inc/lib/Twig/Node/Text.php

@ -13,8 +13,7 @@
/** /**
* Represents a text node. * Represents a text node.
* *
* @package twig * @author Fabien Potencier <fabien@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/ */
class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface
{ {

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

Loading…
Cancel
Save