Compare commits

..

37 Commits

Author SHA1 Message Date
slawkens
b3d1274ffe Release v0.8.8 2023-02-18 11:14:45 +01:00
slawkens
9de49b4b6a Update CHANGELOG.md 2023-02-18 11:13:56 +01:00
slawkens
e6a368c3ac Update CHANGELOG.md 2023-02-18 11:12:51 +01:00
slawkens
3dca1b519a 760 is correct permission 2023-02-16 10:16:07 +01:00
slawkens
ae8af396f4 fix #136 2023-02-16 08:56:08 +01:00
slawkens
38294420d5 patch from develop, IS_CLI fixes 2023-02-07 22:49:01 +01:00
slawkens
c0dee61add accounts.block has been removed 2023-02-07 22:46:47 +01:00
slawkens
a84c92e007 allow template pages to be placed in templates dir 2023-02-07 22:46:35 +01:00
slawkens
60a854e5fd new function> escapeHtml + fix css in admin menus 2023-02-06 17:39:23 +01:00
slawkens
fa9f7aab7c accounts.blocked is not used by AAC 2023-02-03 17:26:50 +01:00
slawkens
d697a556c2 Update online.php 2023-02-03 17:21:54 +01:00
slawkens
802fd831cb (probably) fix #204 2023-02-03 17:21:30 +01:00
slawkens
52ca8a844a Fix #178 2023-02-03 16:13:53 +01:00
slawkens
573fc819d3 fix db table detection failure 2023-02-03 16:05:21 +01:00
slawkens
ead9d79cb1 fix #185 2023-02-03 15:36:57 +01:00
slawkens
43c197316a feature: mail confirmed reward
Suggested by @EPuncker
2023-02-03 14:39:09 +01:00
slawkens
c318d3a9de Option to disable plugin adjusted 2023-02-03 14:09:39 +01:00
slawkens
80d3f5ffe8 Fix logout hook & add images/editor to .gitignore 2023-02-02 20:54:47 +01:00
slawkens
f9d85b10b7 Update .gitignore 2023-02-02 16:24:12 +01:00
slawkens
4028a58adc Update OTS_DB_PDOQuery_PHP71.php 2023-02-02 16:20:09 +01:00
slawkens
0a3a079b86 PHP 8.1 compatibility 2023-02-02 16:19:28 +01:00
slawkens
d691148c84 Revert "Fix compatibility with PHP 8.1"
This reverts commit 99338afacb.
2023-02-02 16:17:33 +01:00
slawkens
48f74b9c7a Update tinymce to v4.9.11 (latest release in 4.x series)
Taken from composer
2023-02-02 11:51:44 +01:00
slawkens
99338afacb Fix compatibility with PHP 8.1 2023-02-02 11:15:17 +01:00
slawkens
301c3b86e2 Add fill-mbstring, which is required by twig 2023-02-02 10:42:47 +01:00
slawkens
130f7ba405 Update Twig to v2.15.4 2023-02-02 10:37:45 +01:00
slawkens
e552bcfe82 Fix ipv6 introduced in latest TFS 2022-12-16 23:05:43 +01:00
the-overdriven
ad75499a91 Update admin.news.form.html.twig (#207)
rename Ticket to Ticker
2022-11-28 08:17:58 +01:00
slawkens
7ddcb441c8 nothing important..
some visual fixes
2022-11-04 09:28:51 +01:00
slawkens
99da8dbec1 Update account.change_mail.html.twig 2022-10-28 13:41:39 +02:00
slawkens
743d5164b3 Add more client versions 2022-10-28 13:41:35 +02:00
slawkens
1f7dfdca50 Add vocation into getTopPlayers 2022-10-28 13:41:23 +02:00
slawkens
2164d59331 Fix typo in br locale 2022-10-28 13:41:10 +02:00
slawkens
0d845b764b Add exception class
from develop
2022-10-28 13:40:16 +02:00
slawkens
0a2cd69a4b Add compat Gesior classes
To allow more custom pages be used with myaac
2022-09-12 14:16:36 +02:00
slawkens
ddb60fa1e0 Bump version to 0.8.8-dev 2022-09-12 11:13:21 +02:00
slawkens
b7e33c5e6d Fix config.account_premium_days for TFS 1.4+ 2022-09-10 21:37:42 +02:00
429 changed files with 8373 additions and 4360 deletions

6
.gitignore vendored
View File

@@ -24,6 +24,10 @@ templates/*
images/guilds/* images/guilds/*
!images/guilds/default.gif !images/guilds/default.gif
# editor images
images/editor/*
!images/editor/index.html
# cache # cache
system/cache/* system/cache/*
!system/cache/index.html !system/cache/index.html
@@ -49,6 +53,8 @@ plugins/*
!plugins/example.json !plugins/example.json
!plugins/account-create-hint.json !plugins/account-create-hint.json
!plugins/account-create-hint !plugins/account-create-hint
!plugins/email-confirmed-reward.json
!plugins/email-confirmed-reward
landing landing
# others/rest # others/rest

View File

@@ -1,5 +1,31 @@
# Changelog # Changelog
## [0.8.8 - 18.02.2023]
### Added
* mail confirmed reward
* support for latest group changes in TFS
* new function: escapeHtml
### Updated
* TinyMCE to v4.9.1 (latest release in 4.x series)
* Twig to v2.15.4
### Changed
* you can now place custom pages in your template directory under pages/ folder
* HOOK_LOGOUT parameters, now only account_id is passed
### Fixed
* ipv6 introduced in latest TFS
* config.account_premium_days for TFS 1.4+
* better compatibility with GesiorAAC
* PHP 8.1 compatibility
* myaac_ db table detection failure
* reload creatures error, when items cache has been cleared
### Removed
* accounts.blocked column, which is not used by AAC
## [0.8.7 - 31.08.2022] ## [0.8.7 - 31.08.2022]
### Added ### Added

View File

@@ -34,7 +34,7 @@ MyAAC is a free and open-source Automatic Account Creator (AAC) written in PHP.
chmod 660 images/guilds chmod 660 images/guilds
chmod 660 images/houses chmod 660 images/houses
chmod 660 images/gallery chmod 660 images/gallery
chmod -R 770 system/cache chmod -R 760 system/cache
Visit http://your_domain/install (http://localhost/install) and follow instructions in the browser. Visit http://your_domain/install (http://localhost/install) and follow instructions in the browser.

View File

@@ -279,7 +279,13 @@ else if ($id > 0 && isset($account) && $account->isLoaded()) {
<?php <?php
$acc_group = $account->getAccGroupId(); $acc_group = $account->getAccGroupId();
if ($hasTypeColumn) { if ($hasTypeColumn) {
$acc_type = array("Normal", "Tutor", "Senior Tutor", "Gamemaster", "God"); ?> $groups = new OTS_Groups_List();
$acc_type = array("Normal", "Tutor", "Senior Tutor", "Gamemaster", "God");
if ($groups->getHighestId() == 6) {
$acc_type = array("Normal", "Tutor", "Senior Tutor", "Gamemaster", "Community Manager", "God");
}
?>
<div class="col-xs-6"> <div class="col-xs-6">
<label for="group" class="control-label">Account Type:</label> <label for="group" class="control-label">Account Type:</label>
<select name="group" id="group" class="form-control"> <select name="group" id="group" class="form-control">

View File

@@ -89,7 +89,7 @@ if (isset($_REQUEST['template'])) {
if (isset($menus[$id])) { if (isset($menus[$id])) {
$i = 0; $i = 0;
foreach ($menus[$id] as $menu) { foreach ($menus[$id] as $menu) {
echo '<li class="ui-state-default" id="list-' . $id . '-' . $i . '"><label>Name:</label><input type="text" name="menu[' . $id . '][]" value="' . $menu['name'] . '"/> echo '<li class="ui-state-default" id="list-' . $id . '-' . $i . '"><label>Name:</label><input type="text" name="menu[' . $id . '][]" value="' . escapeHtml($menu['name']) . '"/>
<label>Link:</label><input type="text" name="menu_link[' . $id . '][]" value="' . $menu['link'] . '"/> <label>Link:</label><input type="text" name="menu_link[' . $id . '][]" value="' . $menu['link'] . '"/>
<input type="hidden" name="menu_blank[' . $id . '][]" value="0" /> <input type="hidden" name="menu_blank[' . $id . '][]" value="0" />
<label><input class="blank-checkbox" type="checkbox" ' . ($menu['blank'] == 1 ? 'checked' : '') . '/><span title="Open in New Window">Open in New Window</span></label> <label><input class="blank-checkbox" type="checkbox" ' . ($menu['blank'] == 1 ? 'checked' : '') . '/><span title="Open in New Window">Open in New Window</span></label>

View File

@@ -117,7 +117,7 @@ if($action == 'edit' || $action == 'new') {
'news_link_form' => '?p=news&action=' . ($action == 'edit' ? 'edit' : 'add'), 'news_link_form' => '?p=news&action=' . ($action == 'edit' ? 'edit' : 'add'),
'news_id' => isset($id) ? $id : null, 'news_id' => isset($id) ? $id : null,
'title' => isset($p_title) ? $p_title : '', 'title' => isset($p_title) ? $p_title : '',
'body' => isset($body) ? htmlentities($body, ENT_COMPAT, 'UTF-8') : '', 'body' => isset($body) ? escapeHtml($body) : '',
'type' => isset($type) ? $type : null, 'type' => isset($type) ? $type : null,
'player' => isset($player) && $player->isLoaded() ? $player : null, 'player' => isset($player) && $player->isLoaded() ? $player : null,
'player_id' => isset($player_id) ? $player_id : null, 'player_id' => isset($player_id) ? $player_id : null,

View File

@@ -105,7 +105,7 @@ $twig->display('admin.pages.form.html.twig', array(
'title' => $p_title, 'title' => $p_title,
'php' => $php, 'php' => $php,
'enable_tinymce' => $enable_tinymce, 'enable_tinymce' => $enable_tinymce,
'body' => isset($body) ? htmlentities($body, ENT_COMPAT, 'UTF-8') : '', 'body' => isset($body) ? escapeHtml($body) : '',
'groups' => $groups->getGroups(), 'groups' => $groups->getGroups(),
'access' => $access 'access' => $access
)); ));

View File

@@ -697,7 +697,14 @@ else if ($id > 0 && isset($player) && $player->isLoaded())
<label for="lastip" class="control-label">Last IP:</label> <label for="lastip" class="control-label">Last IP:</label>
<input type="text" class="form-control" id="lastip" name="lastip" <input type="text" class="form-control" id="lastip" name="lastip"
autocomplete="off" autocomplete="off"
maxlength="10" value="<?php echo longToIp($player->getLastIP()); ?>" maxlength="10" value="<?php
if (strlen($player->getLastIP()) > 11) {
echo inet_ntop($player->getLastIP());
}
else {
echo longToIp($player->getLastIP());
}
?>"
readonly/> readonly/>
</div> </div>
</div> </div>

View File

@@ -26,7 +26,7 @@
if (version_compare(phpversion(), '5.6', '<')) die('PHP version 5.6 or higher is required.'); if (version_compare(phpversion(), '5.6', '<')) die('PHP version 5.6 or higher is required.');
define('MYAAC', true); define('MYAAC', true);
define('MYAAC_VERSION', '0.8.7'); define('MYAAC_VERSION', '0.8.8');
define('DATABASE_VERSION', 33); define('DATABASE_VERSION', 33);
define('TABLE_PREFIX', 'myaac_'); define('TABLE_PREFIX', 'myaac_');
define('START_TIME', microtime(true)); define('START_TIME', microtime(true));
@@ -85,8 +85,10 @@ define('TFS_03', 4);
define('TFS_FIRST', TFS_02); define('TFS_FIRST', TFS_02);
define('TFS_LAST', TFS_03); define('TFS_LAST', TFS_03);
if (!IS_CLI) {
session_save_path(SYSTEM . 'php_sessions'); session_save_path(SYSTEM . 'php_sessions');
session_start(); session_start();
}
// basedir // basedir
$basedir = ''; $basedir = '';

View File

@@ -93,7 +93,14 @@ $config = array(
'account_management' => true, // disable if you're using other method to manage users (fe. tfs account manager) 'account_management' => true, // disable if you're using other method to manage users (fe. tfs account manager)
'account_create_auto_login' => false, // auto login after creating account? 'account_create_auto_login' => false, // auto login after creating account?
'account_create_character_create' => true, // allow directly to create character on create account page? 'account_create_character_create' => true, // allow directly to create character on create account page?
'account_mail_verify' => false, // force users to confirm their email addresses when registering account 'account_mail_verify' => false, // force users to confirm their email addresses when registering
'account_mail_confirmed_reward' => [ // reward users for confirming their E-Mails
// account_mail_verify needs to be enabled too
'premium_days' => 0,
'premium_points' => 0,
'coins' => 0,
'message' => 'You received %d %s for confirming your E-Mail address.' // example: You received 20 premium points for confirming your E-Mail address.
],
'account_mail_unique' => true, // email addresses cannot be duplicated? (one account = one email) 'account_mail_unique' => true, // email addresses cannot be duplicated? (one account = one email)
'account_premium_days' => 0, // default premium days on new account 'account_premium_days' => 0, // default premium days on new account
'account_premium_points' => 0, // default premium points on new account 'account_premium_points' => 0, // default premium points on new account

View File

@@ -176,6 +176,11 @@ $template_place_holders = array();
require_once SYSTEM . 'init.php'; require_once SYSTEM . 'init.php';
// verify myaac tables exists in database
if(!$db->hasTable('myaac_account_actions')) {
throw new RuntimeException('Seems that the table <strong>myaac_account_actions</strong> of MyAAC doesn\'t exist in the database. This is a fatal error. You can try to reinstall MyAAC by visiting <a href="' . BASE_URL . 'install">this</a> url.');
}
// event system // event system
require_once SYSTEM . 'hooks.php'; require_once SYSTEM . 'hooks.php';
$hooks = new Hooks(); $hooks = new Hooks();
@@ -187,11 +192,6 @@ require_once SYSTEM . 'status.php';
$twig->addGlobal('config', $config); $twig->addGlobal('config', $config);
$twig->addGlobal('status', $status); $twig->addGlobal('status', $status);
// verify myaac tables exists in database
if(!$db->hasTable('myaac_account_actions')) {
throw new RuntimeException('Seems that the table <strong>myaac_account_actions</strong> of MyAAC doesn\'t exist in the database. This is a fatal error. You can try to reinstall MyAAC by visiting <a href="' . BASE_URL . 'install">this</a> url.');
}
require SYSTEM . 'migrate.php'; require SYSTEM . 'migrate.php';
$hooks->trigger(HOOK_STARTUP); $hooks->trigger(HOOK_STARTUP);
@@ -312,8 +312,10 @@ if($load_it)
if(SITE_CLOSED && admin()) if(SITE_CLOSED && admin())
$content .= '<p class="note">Site is under maintenance (closed mode). Only privileged users can see it.</p>'; $content .= '<p class="note">Site is under maintenance (closed mode). Only privileged users can see it.</p>';
if($config['backward_support']) if($config['backward_support']) {
require SYSTEM . 'compat_pages.php'; require SYSTEM . 'compat/pages.php';
require SYSTEM . 'compat/classes.php';
}
$ignore = false; $ignore = false;
@@ -333,6 +335,9 @@ if($load_it)
)) . $content; )) . $content;
} }
} else { } else {
$file = TEMPLATES . $template_name . '/pages/' . $page . '.php';
if(!@file_exists($file) || preg_match('/[^A-z0-9_\-]/', $page))
{
$file = SYSTEM . 'pages/' . $page . '.php'; $file = SYSTEM . 'pages/' . $page . '.php';
if(!@file_exists($file) || preg_match('/[^A-z0-9_\-]/', $page)) if(!@file_exists($file) || preg_match('/[^A-z0-9_\-]/', $page))
{ {
@@ -340,6 +345,7 @@ if($load_it)
$file = SYSTEM . 'pages/404.php'; $file = SYSTEM . 'pages/404.php';
} }
} }
}
ob_start(); ob_start();
if($hooks->trigger(HOOK_BEFORE_PAGE)) { if($hooks->trigger(HOOK_BEFORE_PAGE)) {

View File

@@ -1,4 +1,4 @@
We have detected that you don't have access to write to the system/cache directory. Under linux you can fix it by using this two command, where first one should be enough (for apache):<br/><br/><span class="console">chown -R www-data.www-data /var/www/*</span><br/><span class="console">chmod -R 660 system/cache</span> We have detected that you don't have access to write to the system/cache directory. Under linux you can fix it by using this two command, where first one should be enough (for apache):<br/><br/><span class="console">chown -R www-data.www-data /var/www/*</span><br/><span class="console">chmod -R 760 system/cache</span>
<style type="text/css"> <style type="text/css">
.console { .console {

View File

@@ -66,7 +66,6 @@ else {
$new_account->setPassword(encrypt($password)); $new_account->setPassword(encrypt($password));
$new_account->setEMail($email); $new_account->setEMail($email);
$new_account->unblock();
$new_account->save(); $new_account->save();
$new_account->setCustomField('created', time()); $new_account->setCustomField('created', time());
@@ -83,7 +82,7 @@ else {
if($db->hasColumn('accounts', 'group_id')) if($db->hasColumn('accounts', 'group_id'))
$account_used->setCustomField('group_id', $groups->getHighestId()); $account_used->setCustomField('group_id', $groups->getHighestId());
if($db->hasColumn('accounts', 'type')) if($db->hasColumn('accounts', 'type'))
$account_used->setCustomField('type', 5); $account_used->setCustomField('type', 6);
if(!$player_db->isLoaded()) if(!$player_db->isLoaded())
$player->setAccountId($account_used->getId()); $player->setAccountId($account_used->getId());

View File

@@ -71,13 +71,8 @@ else {
success($locale['step_database_adding_field'] . ' accounts.key...'); success($locale['step_database_adding_field'] . ' accounts.key...');
} }
if(!$db->hasColumn('accounts', 'blocked')) {
if(query("ALTER TABLE `accounts` ADD `blocked` TINYINT(1) NOT NULL DEFAULT FALSE COMMENT 'internal usage' AFTER `key`;"))
success($locale['step_database_adding_field'] . ' accounts.blocked...');
}
if(!$db->hasColumn('accounts', 'created')) { if(!$db->hasColumn('accounts', 'created')) {
if(query("ALTER TABLE `accounts` ADD `created` INT(11) NOT NULL DEFAULT 0 AFTER `" . ($db->hasColumn('accounts', 'group_id') ? 'group_id' : 'blocked') . "`;")) if(query("ALTER TABLE `accounts` ADD `created` INT(11) NOT NULL DEFAULT 0 AFTER `" . ($db->hasColumn('accounts', 'group_id') ? 'group_id' : 'email') . "`;"))
success($locale['step_database_adding_field'] . ' accounts.created...'); success($locale['step_database_adding_field'] . ' accounts.created...');
} }

View File

@@ -0,0 +1,17 @@
{
"name": "EMail Confirmed Reward",
"description": "Reward users for confirming their E-Mail.",
"version": "1.0",
"author": "MyAAC Authors",
"contact": "www.my-aac.org",
"hooks": {
"mail-confirmed-reward": {
"type": "EMAIL_CONFIRMED",
"file": "plugins/email-confirmed-reward/reward.php"
}
},
"uninstall": [
"plugins/email-confirmed-reward.json",
"plugins/email-confirmed-reward"
]
}

View File

@@ -0,0 +1,33 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
$reward = config('account_mail_confirmed_reward');
$hasCoinsColumn = $db->hasColumn('accounts', 'coins');
if ($reward['coins'] > 0 && $hasCoinsColumn) {
log_append('email_confirm_error.log', 'accounts.coins column does not exist.');
}
if (!isset($account) || !$account->isLoaded()) {
log_append('email_confirm_error.log', 'Account not loaded.');
return;
}
if ($reward['premium_points'] > 0) {
$account->setCustomField('premium_points', (int)$account->getCustomField('premium_points') + $reward['premium_points']);
success(sprintf($reward['message'], $reward['premium_points'], 'premium points'));
}
if ($reward['coins'] > 0 && $hasCoinsColumn) {
$account->setCustomField('coins', (int)$account->getCustomField('coins') + $reward['coins']);
success(sprintf($reward['message'], $reward['coins'], 'coins'));
}
if ($reward['premium_days'] > 0) {
$account->setPremDays($account->getPremDays() + $reward['premium_days']);
$account->save();
success(sprintf($reward['message'], $reward['premium_days'], 'premium days'));
}

View File

@@ -9,6 +9,11 @@ $loader->register();
// register the base directories for the namespace prefix // register the base directories for the namespace prefix
$loader->addNamespace('Composer\Semver', LIBS . 'semver'); $loader->addNamespace('Composer\Semver', LIBS . 'semver');
$loader->addNamespace('Twig', LIBS . 'Twig'); $loader->addNamespace('Twig', LIBS . 'Twig');
$loader->addNamespace('Symfony\Polyfill\Mbstring', LIBS . 'polyfill-mbstring');
// load polyfill-mbstring bootstrap
require LIBS . 'polyfill-mbstring/bootstrap.php';
/** /**
* An example of a general-purpose implementation that includes the optional * An example of a general-purpose implementation that includes the optional
* functionality of allowing multiple base directories for a single namespace * functionality of allowing multiple base directories for a single namespace

View File

@@ -76,11 +76,13 @@ $config['clients'] = [
1096, 1096,
1097, 1097,
1098, 1098,
1100, 1100,
1102, 1102,
1140, 1140,
1150, 1150,
1180, 1180,
1200, 1200,
1202, 1202,
1215, 1215,
@@ -89,4 +91,12 @@ $config['clients'] = [
1240, 1240,
1251, 1251,
1260, 1260,
1270,
1280,
1285,
1286,
1290,
1291,
1300,
]; ];

15
system/compat/classes.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
/**
* Compat classes (backward support for Gesior AAC)
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2022 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
class Player extends OTS_Player {}
class Guild extends OTS_Guild {}
class GuildRank extends OTS_GuildRank {}
class House extends OTS_House {}

View File

@@ -39,7 +39,7 @@ function exception_handler($exception) {
// we just replace some values manually // we just replace some values manually
// cause in case Twig throws exception, we can show it too // cause in case Twig throws exception, we can show it too
$content = file_get_contents($template_file); $content = file_get_contents($template_file);
$content = str_replace(array('{{ BASE_URL }}', '{{ message }}', '{{ backtrace }}', '{{ powered_by }}'), array(BASE_URL, $message, $backtrace_formatted, base64_decode('UG93ZXJlZCBieSA8YSBocmVmPSJodHRwOi8vbXktYWFjLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPk15QUFDLjwvYT4=')), $content); $content = str_replace(array('{{ BASE_URL }}', '{{ exceptionClass }}', '{{ message }}', '{{ backtrace }}', '{{ powered_by }}'), array(BASE_URL, get_class($exception), $message, $backtrace_formatted, base64_decode('UG93ZXJlZCBieSA8YSBocmVmPSJodHRwOi8vbXktYWFjLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPk15QUFDLjwvYT4=')), $content);
echo $content; echo $content;
} }

View File

@@ -1042,7 +1042,7 @@ function getTopPlayers($limit = 5) {
$deleted = 'deletion'; $deleted = 'deletion';
$is_tfs10 = $db->hasTable('players_online'); $is_tfs10 = $db->hasTable('players_online');
$players = $db->query('SELECT `id`, `name`, `level`, `experience`, `looktype`' . ($db->hasColumn('players', 'lookaddons') ? ', `lookaddons`' : '') . ', `lookhead`, `lookbody`, `looklegs`, `lookfeet`' . ($is_tfs10 ? '' : ', `online`') . ' FROM `players` WHERE `group_id` < ' . config('highscores_groups_hidden') . ' AND `id` NOT IN (' . implode(', ', config('highscores_ids_hidden')) . ') AND `' . $deleted . '` = 0 AND `account_id` != 1 ORDER BY `experience` DESC LIMIT ' . (int)$limit)->fetchAll(); $players = $db->query('SELECT `id`, `name`, `level`, `vocation`, `experience`, `looktype`' . ($db->hasColumn('players', 'lookaddons') ? ', `lookaddons`' : '') . ', `lookhead`, `lookbody`, `looklegs`, `lookfeet`' . ($is_tfs10 ? '' : ', `online`') . ' FROM `players` WHERE `group_id` < ' . config('highscores_groups_hidden') . ' AND `id` NOT IN (' . implode(', ', config('highscores_ids_hidden')) . ') AND `' . $deleted . '` = 0 AND `account_id` != 1 ORDER BY `experience` DESC LIMIT ' . (int)$limit)->fetchAll();
if($is_tfs10) { if($is_tfs10) {
foreach($players as &$player) { foreach($players as &$player) {
@@ -1244,6 +1244,10 @@ function getCustomPage($page, &$success)
return $content; return $content;
} }
function escapeHtml($html) {
return htmlentities($html, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// validator functions // validator functions
require_once LIBS . 'validator.php'; require_once LIBS . 'validator.php';
require_once SYSTEM . 'compat.php'; require_once SYSTEM . 'compat/base.php';

View File

@@ -1,4 +1,6 @@
<?php <?php
require __DIR__ . '/../common.php'; require __DIR__ . '/../common.php';
if (IS_CLI) {
echo MYAAC_VERSION; echo MYAAC_VERSION;
}

View File

@@ -43,8 +43,9 @@ define('HOOK_ACCOUNT_CREATE_AFTER_TOWNS', 31);
define('HOOK_ACCOUNT_CREATE_BEFORE_SUBMIT_BUTTON', 32); define('HOOK_ACCOUNT_CREATE_BEFORE_SUBMIT_BUTTON', 32);
define('HOOK_ACCOUNT_CREATE_AFTER_FORM', 33); define('HOOK_ACCOUNT_CREATE_AFTER_FORM', 33);
define('HOOK_ACCOUNT_CREATE_AFTER_SUBMIT', 34); define('HOOK_ACCOUNT_CREATE_AFTER_SUBMIT', 34);
define('HOOK_EMAIL_CONFIRMED', 35);
define('HOOK_FIRST', HOOK_STARTUP); define('HOOK_FIRST', HOOK_STARTUP);
define('HOOK_LAST', HOOK_ACCOUNT_CREATE_AFTER_SUBMIT); define('HOOK_LAST', HOOK_EMAIL_CONFIRMED);
require_once LIBS . 'plugins.php'; require_once LIBS . 'plugins.php';
class Hook class Hook

View File

@@ -18,7 +18,7 @@ namespace Twig\Cache;
*/ */
class FilesystemCache implements CacheInterface class FilesystemCache implements CacheInterface
{ {
const FORCE_BYTECODE_INVALIDATION = 1; public const FORCE_BYTECODE_INVALIDATION = 1;
private $directory; private $directory;
private $options; private $options;
@@ -35,7 +35,7 @@ class FilesystemCache implements CacheInterface
public function generateKey($name, $className) public function generateKey($name, $className)
{ {
$hash = hash('sha256', $className); $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className);
return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php';
} }
@@ -67,7 +67,7 @@ class FilesystemCache implements CacheInterface
if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) {
// Compile cached file into bytecode cache // Compile cached file into bytecode cache
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) { if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
@opcache_invalidate($key, true); @opcache_invalidate($key, true);
} elseif (\function_exists('apc_compile_file')) { } elseif (\function_exists('apc_compile_file')) {
apc_compile_file($key); apc_compile_file($key);

View File

@@ -14,11 +14,9 @@ namespace Twig\Cache;
/** /**
* Implements a no-cache strategy. * Implements a no-cache strategy.
* *
* @final
*
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class NullCache implements CacheInterface final class NullCache implements CacheInterface
{ {
public function generateKey($name, $className) public function generateKey($name, $className)
{ {

View File

@@ -12,23 +12,22 @@
namespace Twig; namespace Twig;
use Twig\Node\ModuleNode; use Twig\Node\Node;
/** /**
* Compiles a node to PHP code. * Compiles a node to PHP code.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class Compiler implements \Twig_CompilerInterface class Compiler
{ {
protected $lastLine; private $lastLine;
protected $source; private $source;
protected $indentation; private $indentation;
protected $env; private $env;
protected $debugInfo = []; private $debugInfo = [];
protected $sourceOffset; private $sourceOffset;
protected $sourceLine; private $sourceLine;
protected $filename;
private $varNameSalt = 0; private $varNameSalt = 0;
public function __construct(Environment $env) public function __construct(Environment $env)
@@ -36,16 +35,6 @@ class Compiler implements \Twig_CompilerInterface
$this->env = $env; $this->env = $env;
} }
/**
* @deprecated since 1.25 (to be removed in 2.0)
*/
public function getFilename()
{
@trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED);
return $this->filename;
}
/** /**
* Returns the environment instance related to this compiler. * Returns the environment instance related to this compiler.
* *
@@ -73,7 +62,7 @@ class Compiler implements \Twig_CompilerInterface
* *
* @return $this * @return $this
*/ */
public function compile(\Twig_NodeInterface $node, $indentation = 0) public function compile(Node $node, $indentation = 0)
{ {
$this->lastLine = null; $this->lastLine = null;
$this->source = ''; $this->source = '';
@@ -84,17 +73,12 @@ class Compiler implements \Twig_CompilerInterface
$this->indentation = $indentation; $this->indentation = $indentation;
$this->varNameSalt = 0; $this->varNameSalt = 0;
if ($node instanceof ModuleNode) {
// to be removed in 2.0
$this->filename = $node->getTemplateName();
}
$node->compile($this); $node->compile($this);
return $this; return $this;
} }
public function subcompile(\Twig_NodeInterface $node, $raw = true) public function subcompile(Node $node, $raw = true)
{ {
if (false === $raw) { if (false === $raw) {
$this->source .= str_repeat(' ', $this->indentation * 4); $this->source .= str_repeat(' ', $this->indentation * 4);
@@ -124,9 +108,8 @@ class Compiler implements \Twig_CompilerInterface
* *
* @return $this * @return $this
*/ */
public function write() public function write(...$strings)
{ {
$strings = \func_get_args();
foreach ($strings as $string) { foreach ($strings as $string) {
$this->source .= str_repeat(' ', $this->indentation * 4).$string; $this->source .= str_repeat(' ', $this->indentation * 4).$string;
} }
@@ -134,22 +117,6 @@ class Compiler implements \Twig_CompilerInterface
return $this; return $this;
} }
/**
* Appends an indentation to the current PHP code after compilation.
*
* @return $this
*
* @deprecated since 1.27 (to be removed in 2.0).
*/
public function addIndentation()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use write(\'\') instead.', E_USER_DEPRECATED);
$this->source .= str_repeat(' ', $this->indentation * 4);
return $this;
}
/** /**
* Adds a quoted string to the compiled code. * Adds a quoted string to the compiled code.
* *
@@ -174,21 +141,21 @@ class Compiler implements \Twig_CompilerInterface
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')) { if (false !== $locale = setlocale(\LC_NUMERIC, '0')) {
setlocale(LC_NUMERIC, 'C'); setlocale(\LC_NUMERIC, 'C');
} }
$this->raw(var_export($value, true)); $this->raw(var_export($value, true));
if (false !== $locale) { if (false !== $locale) {
setlocale(LC_NUMERIC, $locale); setlocale(\LC_NUMERIC, $locale);
} }
} elseif (null === $value) { } elseif (null === $value) {
$this->raw('null'); $this->raw('null');
} elseif (\is_bool($value)) { } elseif (\is_bool($value)) {
$this->raw($value ? 'true' : 'false'); $this->raw($value ? 'true' : 'false');
} elseif (\is_array($value)) { } elseif (\is_array($value)) {
$this->raw('['); $this->raw('array(');
$first = true; $first = true;
foreach ($value as $key => $v) { foreach ($value as $key => $v) {
if (!$first) { if (!$first) {
@@ -199,7 +166,7 @@ class Compiler implements \Twig_CompilerInterface
$this->raw(' => '); $this->raw(' => ');
$this->repr($v); $this->repr($v);
} }
$this->raw(']'); $this->raw(')');
} else { } else {
$this->string($value); $this->string($value);
} }
@@ -212,22 +179,12 @@ class Compiler implements \Twig_CompilerInterface
* *
* @return $this * @return $this
*/ */
public function addDebugInfo(\Twig_NodeInterface $node) public function addDebugInfo(Node $node)
{ {
if ($node->getTemplateLine() != $this->lastLine) { if ($node->getTemplateLine() != $this->lastLine) {
$this->write(sprintf("// line %d\n", $node->getTemplateLine())); $this->write(sprintf("// line %d\n", $node->getTemplateLine()));
// 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) {
@trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED);
// 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->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
}
$this->sourceOffset = \strlen($this->source); $this->sourceOffset = \strlen($this->source);
$this->debugInfo[$this->sourceLine] = $node->getTemplateLine(); $this->debugInfo[$this->sourceLine] = $node->getTemplateLine();
@@ -281,7 +238,7 @@ class Compiler implements \Twig_CompilerInterface
public function getVarName() public function getVarName()
{ {
return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++)); return sprintf('__internal_compile_%d', $this->varNameSalt++);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -38,11 +38,9 @@ use Twig\Template;
*/ */
class Error extends \Exception class Error extends \Exception
{ {
protected $lineno; private $lineno;
// to be renamed to name in 2.0 private $name;
protected $filename; private $rawMessage;
protected $rawMessage;
private $sourcePath; private $sourcePath;
private $sourceCode; private $sourceCode;
@@ -57,22 +55,23 @@ class Error extends \Exception
* @param Source|string|null $source The source context where the error occurred * @param Source|string|null $source The source context where the error occurred
* @param \Exception $previous The previous exception * @param \Exception $previous The previous exception
*/ */
public function __construct($message, $lineno = -1, $source = null, \Exception $previous = null) public function __construct(string $message, int $lineno = -1, $source = null, \Exception $previous = null)
{ {
parent::__construct('', 0, $previous);
if (null === $source) { if (null === $source) {
$name = null; $name = null;
} elseif (!$source instanceof Source) { } elseif (!$source instanceof Source && !$source instanceof \Twig_Source) {
// for compat with the Twig C ext., passing the template name as string is accepted @trigger_error(sprintf('Passing a string as a source to %s is deprecated since Twig 2.6.1; pass a Twig\Source instance instead.', __CLASS__), \E_USER_DEPRECATED);
$name = $source; $name = $source;
} else { } else {
$name = $source->getName(); $name = $source->getName();
$this->sourceCode = $source->getCode(); $this->sourceCode = $source->getCode();
$this->sourcePath = $source->getPath(); $this->sourcePath = $source->getPath();
} }
parent::__construct('', 0, $previous);
$this->lineno = $lineno; $this->lineno = $lineno;
$this->filename = $name; $this->name = $name;
$this->rawMessage = $message; $this->rawMessage = $message;
$this->updateRepr(); $this->updateRepr();
} }
@@ -87,67 +86,6 @@ class Error extends \Exception
return $this->rawMessage; return $this->rawMessage;
} }
/**
* Gets the logical name where the error occurred.
*
* @return string The name
*
* @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead.
*/
public function getTemplateFile()
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->filename;
}
/**
* Sets the logical name where the error occurred.
*
* @param string $name The name
*
* @deprecated since 1.27 (to be removed in 2.0). Use setSourceContext() instead.
*/
public function setTemplateFile($name)
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
$this->filename = $name;
$this->updateRepr();
}
/**
* Gets the logical name where the error occurred.
*
* @return string The name
*
* @deprecated since 1.29 (to be removed in 2.0). Use getSourceContext() instead.
*/
public function getTemplateName()
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->filename;
}
/**
* Sets the logical name where the error occurred.
*
* @param string $name The name
*
* @deprecated since 1.29 (to be removed in 2.0). Use setSourceContext() instead.
*/
public function setTemplateName($name)
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
$this->filename = $name;
$this->sourceCode = $this->sourcePath = null;
$this->updateRepr();
}
/** /**
* Gets the template line where the error occurred. * Gets the template line where the error occurred.
* *
@@ -177,7 +115,7 @@ class Error extends \Exception
*/ */
public function getSourceContext() public function getSourceContext()
{ {
return $this->filename ? new Source($this->sourceCode, $this->filename, $this->sourcePath) : null; return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null;
} }
/** /**
@@ -186,10 +124,10 @@ class Error extends \Exception
public function setSourceContext(Source $source = null) public function setSourceContext(Source $source = null)
{ {
if (null === $source) { if (null === $source) {
$this->sourceCode = $this->filename = $this->sourcePath = null; $this->sourceCode = $this->name = $this->sourcePath = null;
} else { } else {
$this->sourceCode = $source->getCode(); $this->sourceCode = $source->getCode();
$this->filename = $source->getName(); $this->name = $source->getName();
$this->sourcePath = $source->getPath(); $this->sourcePath = $source->getPath();
} }
@@ -208,10 +146,7 @@ class Error extends \Exception
$this->updateRepr(); $this->updateRepr();
} }
/** private function updateRepr()
* @internal
*/
protected function updateRepr()
{ {
$this->message = $this->rawMessage; $this->message = $this->rawMessage;
@@ -234,11 +169,11 @@ class Error extends \Exception
$questionMark = true; $questionMark = true;
} }
if ($this->filename) { if ($this->name) {
if (\is_string($this->filename) || (\is_object($this->filename) && method_exists($this->filename, '__toString'))) { if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) {
$name = sprintf('"%s"', $this->filename); $name = sprintf('"%s"', $this->name);
} else { } else {
$name = json_encode($this->filename); $name = json_encode($this->name);
} }
$this->message .= sprintf(' in %s', $name); $this->message .= sprintf(' in %s', $name);
} }
@@ -256,20 +191,17 @@ class Error extends \Exception
} }
} }
/** private function guessTemplateInfo()
* @internal
*/
protected function guessTemplateInfo()
{ {
$template = null; $template = null;
$templateClass = null; $templateClass = null;
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT);
foreach ($backtrace as $trace) { foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Template && 'Twig_Template' !== \get_class($trace['object'])) { if (isset($trace['object']) && $trace['object'] instanceof Template && 'Twig\Template' !== \get_class($trace['object'])) {
$currentClass = \get_class($trace['object']); $currentClass = \get_class($trace['object']);
$isEmbedContainer = 0 === strpos($templateClass, $currentClass); $isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass);
if (null === $this->filename || ($this->filename == $trace['object']->getTemplateName() && !$isEmbedContainer)) { if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$template = $trace['object']; $template = $trace['object'];
$templateClass = \get_class($trace['object']); $templateClass = \get_class($trace['object']);
} }
@@ -277,8 +209,8 @@ class Error extends \Exception
} }
// update template name // update template name
if (null !== $template && null === $this->filename) { if (null !== $template && null === $this->name) {
$this->filename = $template->getTemplateName(); $this->name = $template->getTemplateName();
} }
// update template path if any // update template path if any
@@ -296,7 +228,7 @@ class Error extends \Exception
$file = $r->getFileName(); $file = $r->getFileName();
$exceptions = [$e = $this]; $exceptions = [$e = $this];
while ($e instanceof self && $e = $e->getPrevious()) { while ($e = $e->getPrevious()) {
$exceptions[] = $e; $exceptions[] = $e;
} }

View File

@@ -26,20 +26,6 @@ class SyntaxError extends Error
* @param array $items An array of possible items * @param array $items An array of possible items
*/ */
public function addSuggestions($name, array $items) public function addSuggestions($name, array $items)
{
if (!$alternatives = self::computeAlternatives($name, $items)) {
return;
}
$this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', $alternatives)));
}
/**
* @internal
*
* To be merged with the addSuggestions() method in 2.0.
*/
public static function computeAlternatives($name, $items)
{ {
$alternatives = []; $alternatives = [];
foreach ($items as $item) { foreach ($items as $item) {
@@ -48,9 +34,14 @@ class SyntaxError extends Error
$alternatives[$item] = $lev; $alternatives[$item] = $lev;
} }
} }
if (!$alternatives) {
return;
}
asort($alternatives); asort($alternatives);
return array_keys($alternatives); $this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives))));
} }
} }

View File

@@ -13,6 +13,7 @@
namespace Twig; namespace Twig;
use Twig\Error\SyntaxError; use Twig\Error\SyntaxError;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ArrowFunctionExpression; use Twig\Node\Expression\ArrowFunctionExpression;
use Twig\Node\Expression\AssignNameExpression; use Twig\Node\Expression\AssignNameExpression;
@@ -24,6 +25,7 @@ use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression; use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\ParentExpression; use Twig\Node\Expression\ParentExpression;
use Twig\Node\Expression\TestExpression;
use Twig\Node\Expression\Unary\NegUnary; use Twig\Node\Expression\Unary\NegUnary;
use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\NotUnary;
use Twig\Node\Expression\Unary\PosUnary; use Twig\Node\Expression\Unary\PosUnary;
@@ -43,30 +45,20 @@ use Twig\Node\Node;
*/ */
class ExpressionParser class ExpressionParser
{ {
const OPERATOR_LEFT = 1; public const OPERATOR_LEFT = 1;
const OPERATOR_RIGHT = 2; public const OPERATOR_RIGHT = 2;
protected $parser;
protected $unaryOperators;
protected $binaryOperators;
private $parser;
private $env; private $env;
private $unaryOperators;
private $binaryOperators;
public function __construct(Parser $parser, $env = null) public function __construct(Parser $parser, Environment $env)
{ {
$this->parser = $parser; $this->parser = $parser;
if ($env instanceof Environment) {
$this->env = $env; $this->env = $env;
$this->unaryOperators = $env->getUnaryOperators(); $this->unaryOperators = $env->getUnaryOperators();
$this->binaryOperators = $env->getBinaryOperators(); $this->binaryOperators = $env->getBinaryOperators();
} else {
@trigger_error('Passing the operators as constructor arguments to '.__METHOD__.' is deprecated since version 1.27. Pass the environment instead.', E_USER_DEPRECATED);
$this->env = $parser->getEnvironment();
$this->unaryOperators = func_get_arg(1);
$this->binaryOperators = func_get_arg(2);
}
} }
public function parseExpression($precedence = 0, $allowArrow = false) public function parseExpression($precedence = 0, $allowArrow = false)
@@ -86,7 +78,7 @@ class ExpressionParser
} elseif ('is' === $token->getValue()) { } elseif ('is' === $token->getValue()) {
$expr = $this->parseTestExpression($expr); $expr = $this->parseTestExpression($expr);
} elseif (isset($op['callable'])) { } elseif (isset($op['callable'])) {
$expr = \call_user_func($op['callable'], $this->parser, $expr); $expr = $op['callable']($this->parser, $expr);
} else { } else {
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
$class = $op['class']; $class = $op['class'];
@@ -111,57 +103,57 @@ class ExpressionParser
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
// short array syntax (one argument, no parentheses)? // short array syntax (one argument, no parentheses)?
if ($stream->look(1)->test(Token::ARROW_TYPE)) { if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) {
$line = $stream->getCurrent()->getLine(); $line = $stream->getCurrent()->getLine();
$token = $stream->expect(Token::NAME_TYPE); $token = $stream->expect(/* Token::NAME_TYPE */ 5);
$names = [new AssignNameExpression($token->getValue(), $token->getLine())]; $names = [new AssignNameExpression($token->getValue(), $token->getLine())];
$stream->expect(Token::ARROW_TYPE); $stream->expect(/* Token::ARROW_TYPE */ 12);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
} }
// first, determine if we are parsing an arrow function by finding => (long form) // first, determine if we are parsing an arrow function by finding => (long form)
$i = 0; $i = 0;
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) { if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
return null; return null;
} }
++$i; ++$i;
while (true) { while (true) {
// variable name // variable name
++$i; ++$i;
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) { if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break; break;
} }
++$i; ++$i;
} }
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) { if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
return null; return null;
} }
++$i; ++$i;
if (!$stream->look($i)->test(Token::ARROW_TYPE)) { if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) {
return null; return null;
} }
// yes, let's parse it properly // yes, let's parse it properly
$token = $stream->expect(Token::PUNCTUATION_TYPE, '('); $token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(');
$line = $token->getLine(); $line = $token->getLine();
$names = []; $names = [];
while (true) { while (true) {
$token = $stream->expect(Token::NAME_TYPE); $token = $stream->expect(/* Token::NAME_TYPE */ 5);
$names[] = new AssignNameExpression($token->getValue(), $token->getLine()); $names[] = new AssignNameExpression($token->getValue(), $token->getLine());
if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break; break;
} }
} }
$stream->expect(Token::PUNCTUATION_TYPE, ')'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')');
$stream->expect(Token::ARROW_TYPE); $stream->expect(/* Token::ARROW_TYPE */ 12);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
} }
protected function getPrimary() private function getPrimary(): AbstractExpression
{ {
$token = $this->parser->getCurrentToken(); $token = $this->parser->getCurrentToken();
@@ -172,10 +164,10 @@ class ExpressionParser
$class = $operator['class']; $class = $operator['class'];
return $this->parsePostfixExpression(new $class($expr, $token->getLine())); return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
} elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) { } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$this->parser->getStream()->next(); $this->parser->getStream()->next();
$expr = $this->parseExpression(); $expr = $this->parseExpression();
$this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); $this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'An opened parenthesis is not properly closed');
return $this->parsePostfixExpression($expr); return $this->parsePostfixExpression($expr);
} }
@@ -183,12 +175,12 @@ class ExpressionParser
return $this->parsePrimaryExpression(); return $this->parsePrimaryExpression();
} }
protected function parseConditionalExpression($expr) private function parseConditionalExpression($expr): AbstractExpression
{ {
while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) { while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, '?')) {
if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$expr2 = $this->parseExpression(); $expr2 = $this->parseExpression();
if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$expr3 = $this->parseExpression(); $expr3 = $this->parseExpression();
} else { } else {
$expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine()); $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
@@ -204,21 +196,21 @@ class ExpressionParser
return $expr; return $expr;
} }
protected function isUnary(Token $token) private function isUnary(Token $token): bool
{ {
return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]);
} }
protected function isBinary(Token $token) private function isBinary(Token $token): bool
{ {
return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]);
} }
public function parsePrimaryExpression() public function parsePrimaryExpression()
{ {
$token = $this->parser->getCurrentToken(); $token = $this->parser->getCurrentToken();
switch ($token->getType()) { switch ($token->getType()) {
case Token::NAME_TYPE: case /* Token::NAME_TYPE */ 5:
$this->parser->getStream()->next(); $this->parser->getStream()->next();
switch ($token->getValue()) { switch ($token->getValue()) {
case 'true': case 'true':
@@ -247,17 +239,17 @@ class ExpressionParser
} }
break; break;
case Token::NUMBER_TYPE: case /* Token::NUMBER_TYPE */ 6:
$this->parser->getStream()->next(); $this->parser->getStream()->next();
$node = new ConstantExpression($token->getValue(), $token->getLine()); $node = new ConstantExpression($token->getValue(), $token->getLine());
break; break;
case Token::STRING_TYPE: case /* Token::STRING_TYPE */ 7:
case Token::INTERPOLATION_START_TYPE: case /* Token::INTERPOLATION_START_TYPE */ 10:
$node = $this->parseStringExpression(); $node = $this->parseStringExpression();
break; break;
case Token::OPERATOR_TYPE: case /* Token::OPERATOR_TYPE */ 8:
if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
// in this context, string operators are variable names // in this context, string operators are variable names
$this->parser->getStream()->next(); $this->parser->getStream()->next();
@@ -267,10 +259,8 @@ class ExpressionParser
$class = $this->unaryOperators[$token->getValue()]['class']; $class = $this->unaryOperators[$token->getValue()]['class'];
$ref = new \ReflectionClass($class); $ref = new \ReflectionClass($class);
$negClass = 'Twig\Node\Expression\Unary\NegUnary'; if (!(\in_array($ref->getName(), [NegUnary::class, PosUnary::class, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos'])
$posClass = 'Twig\Node\Expression\Unary\PosUnary'; || $ref->isSubclassOf(NegUnary::class) || $ref->isSubclassOf(PosUnary::class)
if (!(\in_array($ref->getName(), [$negClass, $posClass, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos'])
|| $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass)
|| $ref->isSubclassOf('Twig_Node_Expression_Unary_Neg') || $ref->isSubclassOf('Twig_Node_Expression_Unary_Pos')) || $ref->isSubclassOf('Twig_Node_Expression_Unary_Neg') || $ref->isSubclassOf('Twig_Node_Expression_Unary_Pos'))
) { ) {
throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
@@ -285,11 +275,11 @@ class ExpressionParser
// no break // no break
default: default:
if ($token->test(Token::PUNCTUATION_TYPE, '[')) { if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) {
$node = $this->parseArrayExpression(); $node = $this->parseArrayExpression();
} elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) { } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) {
$node = $this->parseHashExpression(); $node = $this->parseHashExpression();
} elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
throw new SyntaxError(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); throw new SyntaxError(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
} else { } else {
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
@@ -307,12 +297,12 @@ class ExpressionParser
// a string cannot be followed by another string in a single expression // a string cannot be followed by another string in a single expression
$nextCanBeString = true; $nextCanBeString = true;
while (true) { while (true) {
if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) { if ($nextCanBeString && $token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) {
$nodes[] = new ConstantExpression($token->getValue(), $token->getLine()); $nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
$nextCanBeString = false; $nextCanBeString = false;
} elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) { } elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) {
$nodes[] = $this->parseExpression(); $nodes[] = $this->parseExpression();
$stream->expect(Token::INTERPOLATION_END_TYPE); $stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11);
$nextCanBeString = true; $nextCanBeString = true;
} else { } else {
break; break;
@@ -330,16 +320,16 @@ class ExpressionParser
public function parseArrayExpression() public function parseArrayExpression()
{ {
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected');
$node = new ArrayExpression([], $stream->getCurrent()->getLine()); $node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = true; $first = true;
while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) { while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
if (!$first) { if (!$first) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma');
// trailing ,? // trailing ,?
if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
break; break;
} }
} }
@@ -347,7 +337,7 @@ class ExpressionParser
$node->addElement($this->parseExpression()); $node->addElement($this->parseExpression());
} }
$stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed');
return $node; return $node;
} }
@@ -355,16 +345,16 @@ class ExpressionParser
public function parseHashExpression() public function parseHashExpression()
{ {
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected');
$node = new ArrayExpression([], $stream->getCurrent()->getLine()); $node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = true; $first = true;
while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
if (!$first) { if (!$first) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma');
// trailing ,? // trailing ,?
if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
break; break;
} }
} }
@@ -376,9 +366,18 @@ class ExpressionParser
// * a string -- 'a' // * a string -- 'a'
// * a name, which is equivalent to a string -- a // * a name, which is equivalent to a string -- a
// * an expression, which must be enclosed in parentheses -- (1 + 2) // * an expression, which must be enclosed in parentheses -- (1 + 2)
if (($token = $stream->nextIf(Token::STRING_TYPE)) || ($token = $stream->nextIf(Token::NAME_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) { if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) {
$key = new ConstantExpression($token->getValue(), $token->getLine()); $key = new ConstantExpression($token->getValue(), $token->getLine());
} elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
// {a} is a shortcut for {a:a}
if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) {
$value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine());
$node->addElement($value, $key);
continue;
}
} elseif (($token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) {
$key = new ConstantExpression($token->getValue(), $token->getLine());
} elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$key = $this->parseExpression(); $key = $this->parseExpression();
} else { } else {
$current = $stream->getCurrent(); $current = $stream->getCurrent();
@@ -386,12 +385,12 @@ class ExpressionParser
throw new SyntaxError(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".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); throw new SyntaxError(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".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext());
} }
$stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)');
$value = $this->parseExpression(); $value = $this->parseExpression();
$node->addElement($value, $key); $node->addElement($value, $key);
} }
$stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed');
return $node; return $node;
} }
@@ -400,7 +399,7 @@ class ExpressionParser
{ {
while (true) { while (true) {
$token = $this->parser->getCurrentToken(); $token = $this->parser->getCurrentToken();
if (Token::PUNCTUATION_TYPE == $token->getType()) { if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) {
if ('.' == $token->getValue() || '[' == $token->getValue()) { if ('.' == $token->getValue() || '[' == $token->getValue()) {
$node = $this->parseSubscriptExpression($node); $node = $this->parseSubscriptExpression($node);
} elseif ('|' == $token->getValue()) { } elseif ('|' == $token->getValue()) {
@@ -474,22 +473,22 @@ class ExpressionParser
if ('.' == $token->getValue()) { if ('.' == $token->getValue()) {
$token = $stream->next(); $token = $stream->next();
if ( if (
Token::NAME_TYPE == $token->getType() /* Token::NAME_TYPE */ 5 == $token->getType()
|| ||
Token::NUMBER_TYPE == $token->getType() /* Token::NUMBER_TYPE */ 6 == $token->getType()
|| ||
(Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) (/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
) { ) {
$arg = new ConstantExpression($token->getValue(), $lineno); $arg = new ConstantExpression($token->getValue(), $lineno);
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$type = Template::METHOD_CALL; $type = Template::METHOD_CALL;
foreach ($this->parseArguments() as $n) { foreach ($this->parseArguments() as $n) {
$arguments->addElement($n); $arguments->addElement($n);
} }
} }
} else { } else {
throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext()); throw new SyntaxError(sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());
} }
if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
@@ -499,11 +498,7 @@ class ExpressionParser
$name = $arg->getAttribute('value'); $name = $arg->getAttribute('value');
if ($this->parser->isReservedMacroName($name)) { $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno);
throw new SyntaxError(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext());
}
$node = new MethodCallExpression($node, 'get'.$name, $arguments, $lineno);
$node->setAttribute('safe', true); $node->setAttribute('safe', true);
return $node; return $node;
@@ -513,19 +508,19 @@ class ExpressionParser
// slice? // slice?
$slice = false; $slice = false;
if ($stream->test(Token::PUNCTUATION_TYPE, ':')) { if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$slice = true; $slice = true;
$arg = new ConstantExpression(0, $token->getLine()); $arg = new ConstantExpression(0, $token->getLine());
} else { } else {
$arg = $this->parseExpression(); $arg = $this->parseExpression();
} }
if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) { if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$slice = true; $slice = true;
} }
if ($slice) { if ($slice) {
if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
$length = new ConstantExpression(null, $token->getLine()); $length = new ConstantExpression(null, $token->getLine());
} else { } else {
$length = $this->parseExpression(); $length = $this->parseExpression();
@@ -535,12 +530,12 @@ class ExpressionParser
$arguments = new Node([$arg, $length]); $arguments = new Node([$arg, $length]);
$filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine()); $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine());
$stream->expect(Token::PUNCTUATION_TYPE, ']'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
return $filter; return $filter;
} }
$stream->expect(Token::PUNCTUATION_TYPE, ']'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
} }
return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
@@ -556,10 +551,10 @@ class ExpressionParser
public function parseFilterExpressionRaw($node, $tag = null) public function parseFilterExpressionRaw($node, $tag = null)
{ {
while (true) { while (true) {
$token = $this->parser->getStream()->expect(Token::NAME_TYPE); $token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5);
$name = new ConstantExpression($token->getValue(), $token->getLine()); $name = new ConstantExpression($token->getValue(), $token->getLine());
if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) { if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$arguments = new Node(); $arguments = new Node();
} else { } else {
$arguments = $this->parseArguments(true, false, true); $arguments = $this->parseArguments(true, false, true);
@@ -569,7 +564,7 @@ class ExpressionParser
$node = new $class($node, $name, $arguments, $token->getLine(), $tag); $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) { if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) {
break; break;
} }
@@ -594,21 +589,26 @@ class ExpressionParser
$args = []; $args = [];
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) { while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
if (!empty($args)) { if (!empty($args)) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'Arguments must be separated by a comma');
// if the comma above was a trailing comma, early exit the argument parse loop
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
break;
}
} }
if ($definition) { if ($definition) {
$token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name'); $token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name');
$value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine()); $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
} else { } else {
$value = $this->parseExpression(0, $allowArrow); $value = $this->parseExpression(0, $allowArrow);
} }
$name = null; $name = null;
if ($namedArguments && $token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) { if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) {
if (!$value instanceof NameExpression) { if (!$value instanceof NameExpression) {
throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext()); throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
} }
@@ -618,7 +618,7 @@ class ExpressionParser
$value = $this->parsePrimaryExpression(); $value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) { if (!$this->checkConstantExpression($value)) {
throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()); throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext());
} }
} else { } else {
$value = $this->parseExpression(0, $allowArrow); $value = $this->parseExpression(0, $allowArrow);
@@ -639,7 +639,7 @@ class ExpressionParser
} }
} }
} }
$stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis');
return new Node($args); return new Node($args);
} }
@@ -650,19 +650,19 @@ class ExpressionParser
$targets = []; $targets = [];
while (true) { while (true) {
$token = $this->parser->getCurrentToken(); $token = $this->parser->getCurrentToken();
if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
// in this context, string operators are variable names // in this context, string operators are variable names
$this->parser->getStream()->next(); $this->parser->getStream()->next();
} else { } else {
$stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to'); $stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to');
} }
$value = $token->getValue(); $value = $token->getValue();
if (\in_array(strtolower($value), ['true', 'false', 'none', 'null'])) { if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) {
throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext());
} }
$targets[] = new AssignNameExpression($value, $token->getLine()); $targets[] = new AssignNameExpression($value, $token->getLine());
if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break; break;
} }
} }
@@ -675,7 +675,7 @@ class ExpressionParser
$targets = []; $targets = [];
while (true) { while (true) {
$targets[] = $this->parseExpression(); $targets[] = $this->parseExpression();
if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) { if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break; break;
} }
} }
@@ -683,35 +683,42 @@ class ExpressionParser
return new Node($targets); return new Node($targets);
} }
private function parseNotTestExpression(\Twig_NodeInterface $node) private function parseNotTestExpression(Node $node): NotUnary
{ {
return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
} }
private function parseTestExpression(\Twig_NodeInterface $node) private function parseTestExpression(Node $node): TestExpression
{ {
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
list($name, $test) = $this->getTest($node->getTemplateLine()); list($name, $test) = $this->getTest($node->getTemplateLine());
$class = $this->getTestNodeClass($test); $class = $this->getTestNodeClass($test);
$arguments = null; $arguments = null;
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$arguments = $this->parseArguments(true); $arguments = $this->parseArguments(true);
} elseif ($test->hasOneMandatoryArgument()) {
$arguments = new Node([0 => $this->parsePrimaryExpression()]);
}
if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) {
$node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
$node->setAttribute('safe', true);
} }
return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
} }
private function getTest($line) private function getTest(int $line): array
{ {
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$name = $stream->expect(Token::NAME_TYPE)->getValue(); $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
if ($test = $this->env->getTest($name)) { if ($test = $this->env->getTest($name)) {
return [$name, $test]; return [$name, $test];
} }
if ($stream->test(Token::NAME_TYPE)) { if ($stream->test(/* Token::NAME_TYPE */ 5)) {
// try 2-words tests // try 2-words tests
$name = $name.' '.$this->parser->getCurrentToken()->getValue(); $name = $name.' '.$this->parser->getCurrentToken()->getValue();
@@ -728,11 +735,12 @@ class ExpressionParser
throw $e; throw $e;
} }
private function getTestNodeClass($test) private function getTestNodeClass(TwigTest $test): string
{ {
if ($test instanceof TwigTest && $test->isDeprecated()) { if ($test->isDeprecated()) {
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$message = sprintf('Twig Test "%s" is deprecated', $test->getName()); $message = sprintf('Twig Test "%s" is deprecated', $test->getName());
if (!\is_bool($test->getDeprecatedVersion())) { if (!\is_bool($test->getDeprecatedVersion())) {
$message .= sprintf(' since version %s', $test->getDeprecatedVersion()); $message .= sprintf(' since version %s', $test->getDeprecatedVersion());
} }
@@ -740,19 +748,15 @@ class ExpressionParser
$message .= sprintf('. Use "%s" instead', $test->getAlternative()); $message .= sprintf('. Use "%s" instead', $test->getAlternative());
} }
$src = $stream->getSourceContext(); $src = $stream->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $stream->getCurrent()->getLine()); $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
@trigger_error($message, E_USER_DEPRECATED); @trigger_error($message, \E_USER_DEPRECATED);
} }
if ($test instanceof TwigTest) {
return $test->getNodeClass(); return $test->getNodeClass();
} }
return $test instanceof \Twig_Test_Node ? $test->getClass() : 'Twig\Node\Expression\TestExpression'; private function getFunctionNodeClass(string $name, int $line): string
}
protected function getFunctionNodeClass($name, $line)
{ {
if (false === $function = $this->env->getFunction($name)) { if (false === $function = $this->env->getFunction($name)) {
$e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); $e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());
@@ -761,7 +765,7 @@ class ExpressionParser
throw $e; throw $e;
} }
if ($function instanceof TwigFunction && $function->isDeprecated()) { if ($function->isDeprecated()) {
$message = sprintf('Twig Function "%s" is deprecated', $function->getName()); $message = sprintf('Twig Function "%s" is deprecated', $function->getName());
if (!\is_bool($function->getDeprecatedVersion())) { if (!\is_bool($function->getDeprecatedVersion())) {
$message .= sprintf(' since version %s', $function->getDeprecatedVersion()); $message .= sprintf(' since version %s', $function->getDeprecatedVersion());
@@ -770,19 +774,15 @@ class ExpressionParser
$message .= sprintf('. Use "%s" instead', $function->getAlternative()); $message .= sprintf('. Use "%s" instead', $function->getAlternative());
} }
$src = $this->parser->getStream()->getSourceContext(); $src = $this->parser->getStream()->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, E_USER_DEPRECATED); @trigger_error($message, \E_USER_DEPRECATED);
} }
if ($function instanceof TwigFunction) {
return $function->getNodeClass(); return $function->getNodeClass();
} }
return $function instanceof \Twig_Function_Node ? $function->getClass() : 'Twig\Node\Expression\FunctionExpression'; private function getFilterNodeClass(string $name, int $line): string
}
protected function getFilterNodeClass($name, $line)
{ {
if (false === $filter = $this->env->getFilter($name)) { if (false === $filter = $this->env->getFilter($name)) {
$e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); $e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext());
@@ -791,7 +791,7 @@ class ExpressionParser
throw $e; throw $e;
} }
if ($filter instanceof TwigFilter && $filter->isDeprecated()) { if ($filter->isDeprecated()) {
$message = sprintf('Twig Filter "%s" is deprecated', $filter->getName()); $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName());
if (!\is_bool($filter->getDeprecatedVersion())) { if (!\is_bool($filter->getDeprecatedVersion())) {
$message .= sprintf(' since version %s', $filter->getDeprecatedVersion()); $message .= sprintf(' since version %s', $filter->getDeprecatedVersion());
@@ -800,20 +800,16 @@ class ExpressionParser
$message .= sprintf('. Use "%s" instead', $filter->getAlternative()); $message .= sprintf('. Use "%s" instead', $filter->getAlternative());
} }
$src = $this->parser->getStream()->getSourceContext(); $src = $this->parser->getStream()->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, E_USER_DEPRECATED); @trigger_error($message, \E_USER_DEPRECATED);
} }
if ($filter instanceof TwigFilter) {
return $filter->getNodeClass(); return $filter->getNodeClass();
} }
return $filter instanceof \Twig_Filter_Node ? $filter->getClass() : 'Twig\Node\Expression\FilterExpression';
}
// checks that the node only contains "constant" elements // checks that the node only contains "constant" elements
protected function checkConstantExpression(\Twig_NodeInterface $node) private function checkConstantExpression(Node $node): bool
{ {
if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
|| $node instanceof NegUnary || $node instanceof PosUnary || $node instanceof NegUnary || $node instanceof PosUnary

View File

@@ -11,17 +11,8 @@
namespace Twig\Extension; namespace Twig\Extension;
use Twig\Environment;
abstract class AbstractExtension implements ExtensionInterface abstract class AbstractExtension implements ExtensionInterface
{ {
/**
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_InitRuntimeInterface instead
*/
public function initRuntime(Environment $environment)
{
}
public function getTokenParsers() public function getTokenParsers()
{ {
return []; return [];
@@ -51,22 +42,6 @@ abstract class AbstractExtension implements ExtensionInterface
{ {
return []; return [];
} }
/**
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_GlobalsInterface instead
*/
public function getGlobals()
{
return [];
}
/**
* @deprecated since 1.26 (to be removed in 2.0), not used anymore internally
*/
public function getName()
{
return \get_class($this);
}
} }
class_alias('Twig\Extension\AbstractExtension', 'Twig_Extension'); class_alias('Twig\Extension\AbstractExtension', 'Twig_Extension');

File diff suppressed because it is too large Load Diff

View File

@@ -12,10 +12,7 @@
namespace Twig\Extension { namespace Twig\Extension {
use Twig\TwigFunction; use Twig\TwigFunction;
/** final class DebugExtension extends AbstractExtension
* @final
*/
class DebugExtension extends AbstractExtension
{ {
public function getFunctions() public function getFunctions()
{ {
@@ -33,11 +30,6 @@ class DebugExtension extends AbstractExtension
new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]), new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),
]; ];
} }
public function getName()
{
return 'debug';
}
} }
class_alias('Twig\Extension\DebugExtension', 'Twig_Extension_Debug'); class_alias('Twig\Extension\DebugExtension', 'Twig_Extension_Debug');
@@ -48,7 +40,7 @@ use Twig\Environment;
use Twig\Template; use Twig\Template;
use Twig\TemplateWrapper; use Twig\TemplateWrapper;
function twig_var_dump(Environment $env, $context, array $vars = []) function twig_var_dump(Environment $env, $context, ...$vars)
{ {
if (!$env->isDebug()) { if (!$env->isDebug()) {
return; return;
@@ -66,9 +58,7 @@ function twig_var_dump(Environment $env, $context, array $vars = [])
var_dump($vars); var_dump($vars);
} else { } else {
foreach ($vars as $var) { var_dump(...$vars);
var_dump($var);
}
} }
return ob_get_clean(); return ob_get_clean();

View File

@@ -10,16 +10,21 @@
*/ */
namespace Twig\Extension { namespace Twig\Extension {
use Twig\FileExtensionEscapingStrategy;
use Twig\NodeVisitor\EscaperNodeVisitor; use Twig\NodeVisitor\EscaperNodeVisitor;
use Twig\TokenParser\AutoEscapeTokenParser; use Twig\TokenParser\AutoEscapeTokenParser;
use Twig\TwigFilter; use Twig\TwigFilter;
/** final class EscaperExtension extends AbstractExtension
* @final
*/
class EscaperExtension extends AbstractExtension
{ {
protected $defaultStrategy; private $defaultStrategy;
private $escapers = [];
/** @internal */
public $safeClasses = [];
/** @internal */
public $safeLookup = [];
/** /**
* @param string|false|callable $defaultStrategy An escaping strategy * @param string|false|callable $defaultStrategy An escaping strategy
@@ -44,6 +49,8 @@ class EscaperExtension extends AbstractExtension
public function getFilters() public function getFilters()
{ {
return [ return [
new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]), new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]),
]; ];
} }
@@ -58,21 +65,8 @@ class EscaperExtension extends AbstractExtension
*/ */
public function setDefaultStrategy($defaultStrategy) public function setDefaultStrategy($defaultStrategy)
{ {
// for BC
if (true === $defaultStrategy) {
@trigger_error('Using "true" as the default strategy is deprecated since version 1.21. Use "html" instead.', E_USER_DEPRECATED);
$defaultStrategy = 'html';
}
if ('filename' === $defaultStrategy) {
@trigger_error('Using "filename" as the default strategy is deprecated since version 1.27. Use "name" instead.', E_USER_DEPRECATED);
$defaultStrategy = 'name';
}
if ('name' === $defaultStrategy) { if ('name' === $defaultStrategy) {
$defaultStrategy = ['\Twig\FileExtensionEscapingStrategy', 'guess']; $defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess'];
} }
$this->defaultStrategy = $defaultStrategy; $this->defaultStrategy = $defaultStrategy;
@@ -96,9 +90,47 @@ class EscaperExtension extends AbstractExtension
return $this->defaultStrategy; return $this->defaultStrategy;
} }
public function getName() /**
* Defines a new escaper to be used via the escape filter.
*
* @param string $strategy The strategy name that should be used as a strategy in the escape call
* @param callable $callable A valid PHP callable
*/
public function setEscaper($strategy, callable $callable)
{ {
return 'escaper'; $this->escapers[$strategy] = $callable;
}
/**
* Gets all defined escapers.
*
* @return callable[] An array of escapers
*/
public function getEscapers()
{
return $this->escapers;
}
public function setSafeClasses(array $safeClasses = [])
{
$this->safeClasses = [];
$this->safeLookup = [];
foreach ($safeClasses as $class => $strategies) {
$this->addSafeClass($class, $strategies);
}
}
public function addSafeClass(string $class, array $strategies)
{
$class = ltrim($class, '\\');
if (!isset($this->safeClasses[$class])) {
$this->safeClasses[$class] = [];
}
$this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);
foreach ($strategies as $strategy) {
$this->safeLookup[$strategy][$class] = true;
}
} }
} }
@@ -106,6 +138,14 @@ class_alias('Twig\Extension\EscaperExtension', 'Twig_Extension_Escaper');
} }
namespace { namespace {
use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Extension\CoreExtension;
use Twig\Extension\EscaperExtension;
use Twig\Markup;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Node;
/** /**
* Marks a variable as being safe. * Marks a variable as being safe.
* *
@@ -117,4 +157,272 @@ function twig_raw_filter($string)
{ {
return $string; return $string;
} }
/**
* Escapes a string.
*
* @param mixed $string The value to be escaped
* @param string $strategy The escaping strategy
* @param string $charset The charset
* @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
*
* @return string
*/
function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
{
if ($autoescape && $string instanceof Markup) {
return $string;
}
if (!\is_string($string)) {
if (\is_object($string) && method_exists($string, '__toString')) {
if ($autoescape) {
$c = \get_class($string);
$ext = $env->getExtension(EscaperExtension::class);
if (!isset($ext->safeClasses[$c])) {
$ext->safeClasses[$c] = [];
foreach (class_parents($string) + class_implements($string) as $class) {
if (isset($ext->safeClasses[$class])) {
$ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class]));
foreach ($ext->safeClasses[$class] as $s) {
$ext->safeLookup[$s][$c] = true;
}
}
}
}
if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) {
return (string) $string;
}
}
$string = (string) $string;
} elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) {
return $string;
}
}
if ('' === $string) {
return '';
}
if (null === $charset) {
$charset = $env->getCharset();
}
switch ($strategy) {
case 'html':
// see https://www.php.net/htmlspecialchars
// Using a static variable to avoid initializing the array
// each time the function is called. Moving the declaration on the
// top of the function slow downs other escaping strategies.
static $htmlspecialcharsCharsets = [
'ISO-8859-1' => true, 'ISO8859-1' => true,
'ISO-8859-15' => true, 'ISO8859-15' => true,
'utf-8' => true, 'UTF-8' => true,
'CP866' => true, 'IBM866' => true, '866' => true,
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
'1251' => true,
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
'BIG5' => true, '950' => true,
'GB2312' => true, '936' => true,
'BIG5-HKSCS' => true,
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
'EUC-JP' => true, 'EUCJP' => true,
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
];
if (isset($htmlspecialcharsCharsets[$charset])) {
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
}
if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
// cache the lowercase variant for future iterations
$htmlspecialcharsCharsets[$charset] = true;
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
}
$string = twig_convert_encoding($string, 'UTF-8', $charset);
$string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
return iconv('UTF-8', $charset, $string);
case 'js':
// escape all non-alphanumeric characters
// into their \x or \uHHHH representations
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) {
$char = $matches[0];
/*
* A few characters have short escape sequences in JSON and JavaScript.
* Escape sequences supported only by JavaScript, not JSON, are omitted.
* \" is also supported but omitted, because the resulting string is not HTML safe.
*/
static $shortMap = [
'\\' => '\\\\',
'/' => '\\/',
"\x08" => '\b',
"\x0C" => '\f',
"\x0A" => '\n',
"\x0D" => '\r',
"\x09" => '\t',
];
if (isset($shortMap[$char])) {
return $shortMap[$char];
}
$codepoint = mb_ord($char, 'UTF-8');
if (0x10000 > $codepoint) {
return sprintf('\u%04X', $codepoint);
}
// Split characters outside the BMP into surrogate pairs
// https://tools.ietf.org/html/rfc2781.html#section-2.1
$u = $codepoint - 0x10000;
$high = 0xD800 | ($u >> 10);
$low = 0xDC00 | ($u & 0x3FF);
return sprintf('\u%04X\u%04X', $high, $low);
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'css':
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) {
$char = $matches[0];
return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8'));
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'html_attr':
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) {
/**
* This function is adapted from code coming from Zend Framework.
*
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://framework.zend.com/license/new-bsd New BSD License
*/
$chr = $matches[0];
$ord = \ord($chr);
/*
* The following replaces characters undefined in HTML with the
* hex entity for the Unicode replacement character.
*/
if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) {
return '&#xFFFD;';
}
/*
* Check if the current character to escape has a name entity we should
* replace it with while grabbing the hex value of the character.
*/
if (1 === \strlen($chr)) {
/*
* While HTML supports far more named entities, the lowest common denominator
* has become HTML5's XML Serialisation which is restricted to the those named
* entities that XML supports. Using HTML entities would result in this error:
* XML Parsing Error: undefined entity
*/
static $entityMap = [
34 => '&quot;', /* quotation mark */
38 => '&amp;', /* ampersand */
60 => '&lt;', /* less-than sign */
62 => '&gt;', /* greater-than sign */
];
if (isset($entityMap[$ord])) {
return $entityMap[$ord];
}
return sprintf('&#x%02X;', $ord);
}
/*
* Per OWASP recommendations, we'll use hex entities for any other
* characters where a named entity does not exist.
*/
return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8'));
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'url':
return rawurlencode($string);
default:
// check the ones set on CoreExtension for BC (to be removed in 3.0)
$legacyEscapers = $env->getExtension(CoreExtension::class)->getEscapers(false);
if (array_key_exists($strategy, $legacyEscapers)) {
return $legacyEscapers[$strategy]($env, $string, $charset);
}
$escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
if (array_key_exists($strategy, $escapers)) {
return $escapers[$strategy]($env, $string, $charset);
}
$escapers = array_merge($legacyEscapers, $escapers);
$validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers)));
throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
}
}
/**
* @internal
*/
function twig_escape_filter_is_safe(Node $filterArgs)
{
foreach ($filterArgs as $arg) {
if ($arg instanceof ConstantExpression) {
return [$arg->getAttribute('value')];
}
return [];
}
return ['html'];
}
} }

View File

@@ -11,7 +11,6 @@
namespace Twig\Extension; namespace Twig\Extension;
use Twig\Environment;
use Twig\NodeVisitor\NodeVisitorInterface; use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface; use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter; use Twig\TwigFilter;
@@ -25,15 +24,6 @@ use Twig\TwigTest;
*/ */
interface ExtensionInterface interface ExtensionInterface
{ {
/**
* Initializes the runtime environment.
*
* This is where you can load some file that contains filter functions for instance.
*
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_InitRuntimeInterface instead
*/
public function initRuntime(Environment $environment);
/** /**
* Returns the token parser instances to add to the existing list. * Returns the token parser instances to add to the existing list.
* *
@@ -75,24 +65,6 @@ interface ExtensionInterface
* @return array<array> First array of unary operators, second array of binary operators * @return array<array> First array of unary operators, second array of binary operators
*/ */
public function getOperators(); public function getOperators();
/**
* Returns a list of global variables to add to the existing list.
*
* @return array An array of global variables
*
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_GlobalsInterface instead
*/
public function getGlobals();
/**
* Returns the name of the extension.
*
* @return string The extension name
*
* @deprecated since 1.26 (to be removed in 2.0), not used anymore internally
*/
public function getName();
} }
class_alias('Twig\Extension\ExtensionInterface', 'Twig_ExtensionInterface'); class_alias('Twig\Extension\ExtensionInterface', 'Twig_ExtensionInterface');

View File

@@ -21,6 +21,12 @@ namespace Twig\Extension;
*/ */
interface GlobalsInterface interface GlobalsInterface
{ {
/**
* Returns a list of global variables to add to the existing list.
*
* @return array An array of global variables
*/
public function getGlobals();
} }
class_alias('Twig\Extension\GlobalsInterface', 'Twig_Extension_GlobalsInterface'); class_alias('Twig\Extension\GlobalsInterface', 'Twig_Extension_GlobalsInterface');

View File

@@ -11,6 +11,8 @@
namespace Twig\Extension; namespace Twig\Extension;
use Twig\Environment;
/** /**
* Enables usage of the deprecated Twig\Extension\AbstractExtension::initRuntime() method. * Enables usage of the deprecated Twig\Extension\AbstractExtension::initRuntime() method.
* *
@@ -18,9 +20,17 @@ namespace Twig\Extension;
* deprecated initRuntime() method in your extensions. * deprecated initRuntime() method in your extensions.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Twig 2.7, to be removed in 3.0
*/ */
interface InitRuntimeInterface interface InitRuntimeInterface
{ {
/**
* Initializes the runtime environment.
*
* This is where you can load some file that contains filter functions for instance.
*/
public function initRuntime(Environment $environment);
} }
class_alias('Twig\Extension\InitRuntimeInterface', 'Twig_Extension_InitRuntimeInterface'); class_alias('Twig\Extension\InitRuntimeInterface', 'Twig_Extension_InitRuntimeInterface');

View File

@@ -13,12 +13,9 @@ namespace Twig\Extension;
use Twig\NodeVisitor\OptimizerNodeVisitor; use Twig\NodeVisitor\OptimizerNodeVisitor;
/** final class OptimizerExtension extends AbstractExtension
* @final
*/
class OptimizerExtension extends AbstractExtension
{ {
protected $optimizers; private $optimizers;
public function __construct($optimizers = -1) public function __construct($optimizers = -1)
{ {
@@ -29,11 +26,6 @@ class OptimizerExtension extends AbstractExtension
{ {
return [new OptimizerNodeVisitor($this->optimizers)]; return [new OptimizerNodeVisitor($this->optimizers)];
} }
public function getName()
{
return 'optimizer';
}
} }
class_alias('Twig\Extension\OptimizerExtension', 'Twig_Extension_Optimizer'); class_alias('Twig\Extension\OptimizerExtension', 'Twig_Extension_Optimizer');

View File

@@ -41,12 +41,7 @@ class ProfilerExtension extends AbstractExtension
public function getNodeVisitors() public function getNodeVisitors()
{ {
return [new ProfilerNodeVisitor(\get_class($this))]; return [new ProfilerNodeVisitor(static::class)];
}
public function getName()
{
return 'profiler';
} }
} }

View File

@@ -12,17 +12,17 @@
namespace Twig\Extension; namespace Twig\Extension;
use Twig\NodeVisitor\SandboxNodeVisitor; use Twig\NodeVisitor\SandboxNodeVisitor;
use Twig\Sandbox\SecurityNotAllowedMethodError;
use Twig\Sandbox\SecurityNotAllowedPropertyError;
use Twig\Sandbox\SecurityPolicyInterface; use Twig\Sandbox\SecurityPolicyInterface;
use Twig\Source;
use Twig\TokenParser\SandboxTokenParser; use Twig\TokenParser\SandboxTokenParser;
/** final class SandboxExtension extends AbstractExtension
* @final
*/
class SandboxExtension extends AbstractExtension
{ {
protected $sandboxedGlobally; private $sandboxedGlobally;
protected $sandboxed; private $sandboxed;
protected $policy; private $policy;
public function __construct(SecurityPolicyInterface $policy, $sandboxed = false) public function __construct(SecurityPolicyInterface $policy, $sandboxed = false)
{ {
@@ -77,33 +77,49 @@ class SandboxExtension extends AbstractExtension
} }
} }
public function checkMethodAllowed($obj, $method) public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null)
{ {
if ($this->isSandboxed()) { if ($this->isSandboxed()) {
try {
$this->policy->checkMethodAllowed($obj, $method); $this->policy->checkMethodAllowed($obj, $method);
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
} }
} }
public function checkPropertyAllowed($obj, $method) public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null)
{ {
if ($this->isSandboxed()) { if ($this->isSandboxed()) {
$this->policy->checkPropertyAllowed($obj, $method); try {
$this->policy->checkPropertyAllowed($obj, $property);
} catch (SecurityNotAllowedPropertyError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
} }
} }
public function ensureToStringAllowed($obj) public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null)
{ {
if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) { if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) {
try {
$this->policy->checkMethodAllowed($obj, '__toString'); $this->policy->checkMethodAllowed($obj, '__toString');
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
} }
return $obj; return $obj;
} }
public function getName()
{
return 'sandbox';
}
} }
class_alias('Twig\Extension\SandboxExtension', 'Twig_Extension_Sandbox'); class_alias('Twig\Extension\SandboxExtension', 'Twig_Extension_Sandbox');

View File

@@ -13,32 +13,32 @@ namespace Twig\Extension;
use Twig\NodeVisitor\NodeVisitorInterface; use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface; use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
/** /**
* Internal class. * Used by \Twig\Environment as a staging area.
*
* This class is used by \Twig\Environment as a staging area and must not be used directly.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
* *
* @internal * @internal
*/ */
class StagingExtension extends AbstractExtension final class StagingExtension extends AbstractExtension
{ {
protected $functions = []; private $functions = [];
protected $filters = []; private $filters = [];
protected $visitors = []; private $visitors = [];
protected $tokenParsers = []; private $tokenParsers = [];
protected $globals = []; private $tests = [];
protected $tests = [];
public function addFunction($name, $function) public function addFunction(TwigFunction $function)
{ {
if (isset($this->functions[$name])) { if (isset($this->functions[$function->getName()])) {
@trigger_error(sprintf('Overriding function "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); throw new \LogicException(sprintf('Function "%s" is already registered.', $function->getName()));
} }
$this->functions[$name] = $function; $this->functions[$function->getName()] = $function;
} }
public function getFunctions() public function getFunctions()
@@ -46,13 +46,13 @@ class StagingExtension extends AbstractExtension
return $this->functions; return $this->functions;
} }
public function addFilter($name, $filter) public function addFilter(TwigFilter $filter)
{ {
if (isset($this->filters[$name])) { if (isset($this->filters[$filter->getName()])) {
@trigger_error(sprintf('Overriding filter "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); throw new \LogicException(sprintf('Filter "%s" is already registered.', $filter->getName()));
} }
$this->filters[$name] = $filter; $this->filters[$filter->getName()] = $filter;
} }
public function getFilters() public function getFilters()
@@ -73,7 +73,7 @@ class StagingExtension extends AbstractExtension
public function addTokenParser(TokenParserInterface $parser) public function addTokenParser(TokenParserInterface $parser)
{ {
if (isset($this->tokenParsers[$parser->getTag()])) { if (isset($this->tokenParsers[$parser->getTag()])) {
@trigger_error(sprintf('Overriding tag "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $parser->getTag()), E_USER_DEPRECATED); throw new \LogicException(sprintf('Tag "%s" is already registered.', $parser->getTag()));
} }
$this->tokenParsers[$parser->getTag()] = $parser; $this->tokenParsers[$parser->getTag()] = $parser;
@@ -84,34 +84,19 @@ class StagingExtension extends AbstractExtension
return $this->tokenParsers; return $this->tokenParsers;
} }
public function addGlobal($name, $value) public function addTest(TwigTest $test)
{ {
$this->globals[$name] = $value; if (isset($this->tests[$test->getName()])) {
throw new \LogicException(sprintf('Test "%s" is already registered.', $test->getName()));
} }
public function getGlobals() $this->tests[$test->getName()] = $test;
{
return $this->globals;
}
public function addTest($name, $test)
{
if (isset($this->tests[$name])) {
@trigger_error(sprintf('Overriding test "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED);
}
$this->tests[$name] = $test;
} }
public function getTests() public function getTests()
{ {
return $this->tests; return $this->tests;
} }
public function getName()
{
return 'staging';
}
} }
class_alias('Twig\Extension\StagingExtension', 'Twig_Extension_Staging'); class_alias('Twig\Extension\StagingExtension', 'Twig_Extension_Staging');

View File

@@ -12,10 +12,7 @@
namespace Twig\Extension { namespace Twig\Extension {
use Twig\TwigFunction; use Twig\TwigFunction;
/** final class StringLoaderExtension extends AbstractExtension
* @final
*/
class StringLoaderExtension extends AbstractExtension
{ {
public function getFunctions() public function getFunctions()
{ {
@@ -23,11 +20,6 @@ class StringLoaderExtension extends AbstractExtension
new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]), new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]),
]; ];
} }
public function getName()
{
return 'string_loader';
}
} }
class_alias('Twig\Extension\StringLoaderExtension', 'Twig_Extension_StringLoader'); class_alias('Twig\Extension\StringLoaderExtension', 'Twig_Extension_StringLoader');
@@ -47,7 +39,7 @@ use Twig\TemplateWrapper;
* *
* @return TemplateWrapper * @return TemplateWrapper
*/ */
function twig_template_from_string(Environment $env, $template, $name = null) function twig_template_from_string(Environment $env, $template, string $name = null)
{ {
return $env->createTemplate((string) $template, $name); return $env->createTemplate((string) $template, $name);
} }

View File

@@ -0,0 +1,475 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Error\RuntimeError;
use Twig\Extension\ExtensionInterface;
use Twig\Extension\GlobalsInterface;
use Twig\Extension\InitRuntimeInterface;
use Twig\Extension\StagingExtension;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class ExtensionSet
{
private $extensions;
private $initialized = false;
private $runtimeInitialized = false;
private $staging;
private $parsers;
private $visitors;
private $filters;
private $tests;
private $functions;
private $unaryOperators;
private $binaryOperators;
private $globals;
private $functionCallbacks = [];
private $filterCallbacks = [];
private $lastModified = 0;
public function __construct()
{
$this->staging = new StagingExtension();
}
/**
* Initializes the runtime environment.
*
* @deprecated since Twig 2.7
*/
public function initRuntime(Environment $env)
{
if ($this->runtimeInitialized) {
return;
}
$this->runtimeInitialized = true;
foreach ($this->extensions as $extension) {
if ($extension instanceof InitRuntimeInterface) {
$extension->initRuntime($env);
}
}
}
public function hasExtension(string $class): bool
{
$class = ltrim($class, '\\');
if (!isset($this->extensions[$class]) && class_exists($class, false)) {
// For BC/FC with namespaced aliases
$class = (new \ReflectionClass($class))->name;
}
return isset($this->extensions[$class]);
}
public function getExtension(string $class): ExtensionInterface
{
$class = ltrim($class, '\\');
if (!isset($this->extensions[$class]) && class_exists($class, false)) {
// For BC/FC with namespaced aliases
$class = (new \ReflectionClass($class))->name;
}
if (!isset($this->extensions[$class])) {
throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class));
}
return $this->extensions[$class];
}
/**
* @param ExtensionInterface[] $extensions
*/
public function setExtensions(array $extensions)
{
foreach ($extensions as $extension) {
$this->addExtension($extension);
}
}
/**
* @return ExtensionInterface[]
*/
public function getExtensions(): array
{
return $this->extensions;
}
public function getSignature(): string
{
return json_encode(array_keys($this->extensions));
}
public function isInitialized(): bool
{
return $this->initialized || $this->runtimeInitialized;
}
public function getLastModified(): int
{
if (0 !== $this->lastModified) {
return $this->lastModified;
}
foreach ($this->extensions as $extension) {
$r = new \ReflectionObject($extension);
if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) {
$this->lastModified = $extensionTime;
}
}
return $this->lastModified;
}
public function addExtension(ExtensionInterface $extension)
{
$class = \get_class($extension);
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class));
}
if (isset($this->extensions[$class])) {
throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.', $class));
}
$this->extensions[$class] = $extension;
}
public function addFunction(TwigFunction $function)
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName()));
}
$this->staging->addFunction($function);
}
/**
* @return TwigFunction[]
*/
public function getFunctions(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->functions;
}
/**
* @return TwigFunction|false
*/
public function getFunction(string $name)
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->functions[$name])) {
return $this->functions[$name];
}
foreach ($this->functions as $pattern => $function) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$function->setArguments($matches);
return $function;
}
}
foreach ($this->functionCallbacks as $callback) {
if (false !== $function = $callback($name)) {
return $function;
}
}
return false;
}
public function registerUndefinedFunctionCallback(callable $callable)
{
$this->functionCallbacks[] = $callable;
}
public function addFilter(TwigFilter $filter)
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName()));
}
$this->staging->addFilter($filter);
}
/**
* @return TwigFilter[]
*/
public function getFilters(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->filters;
}
/**
* @return TwigFilter|false
*/
public function getFilter(string $name)
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->filters[$name])) {
return $this->filters[$name];
}
foreach ($this->filters as $pattern => $filter) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$filter->setArguments($matches);
return $filter;
}
}
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = $callback($name)) {
return $filter;
}
}
return false;
}
public function registerUndefinedFilterCallback(callable $callable)
{
$this->filterCallbacks[] = $callable;
}
public function addNodeVisitor(NodeVisitorInterface $visitor)
{
if ($this->initialized) {
throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.');
}
$this->staging->addNodeVisitor($visitor);
}
/**
* @return NodeVisitorInterface[]
*/
public function getNodeVisitors(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->visitors;
}
public function addTokenParser(TokenParserInterface $parser)
{
if ($this->initialized) {
throw new \LogicException('Unable to add a token parser as extensions have already been initialized.');
}
$this->staging->addTokenParser($parser);
}
/**
* @return TokenParserInterface[]
*/
public function getTokenParsers(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->parsers;
}
public function getGlobals(): array
{
if (null !== $this->globals) {
return $this->globals;
}
$globals = [];
foreach ($this->extensions as $extension) {
if (!$extension instanceof GlobalsInterface) {
continue;
}
$extGlobals = $extension->getGlobals();
if (!\is_array($extGlobals)) {
throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension)));
}
$globals = array_merge($globals, $extGlobals);
}
if ($this->initialized) {
$this->globals = $globals;
}
return $globals;
}
public function addTest(TwigTest $test)
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName()));
}
$this->staging->addTest($test);
}
/**
* @return TwigTest[]
*/
public function getTests(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->tests;
}
/**
* @return TwigTest|false
*/
public function getTest(string $name)
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->tests[$name])) {
return $this->tests[$name];
}
foreach ($this->tests as $pattern => $test) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count) {
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$test->setArguments($matches);
return $test;
}
}
}
return false;
}
public function getUnaryOperators(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->unaryOperators;
}
public function getBinaryOperators(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->binaryOperators;
}
private function initExtensions()
{
$this->parsers = [];
$this->filters = [];
$this->functions = [];
$this->tests = [];
$this->visitors = [];
$this->unaryOperators = [];
$this->binaryOperators = [];
foreach ($this->extensions as $extension) {
$this->initExtension($extension);
}
$this->initExtension($this->staging);
// Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception
$this->initialized = true;
}
private function initExtension(ExtensionInterface $extension)
{
// filters
foreach ($extension->getFilters() as $filter) {
$this->filters[$filter->getName()] = $filter;
}
// functions
foreach ($extension->getFunctions() as $function) {
$this->functions[$function->getName()] = $function;
}
// tests
foreach ($extension->getTests() as $test) {
$this->tests[$test->getName()] = $test;
}
// token parsers
foreach ($extension->getTokenParsers() as $parser) {
if (!$parser instanceof TokenParserInterface) {
throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.');
}
$this->parsers[] = $parser;
}
// node visitors
foreach ($extension->getNodeVisitors() as $visitor) {
$this->visitors[] = $visitor;
}
// operators
if ($operators = $extension->getOperators()) {
if (!\is_array($operators)) {
throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators)));
}
if (2 !== \count($operators)) {
throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators)));
}
$this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
$this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
}
}
}
class_alias('Twig\ExtensionSet', 'Twig_ExtensionSet');

View File

@@ -41,7 +41,7 @@ class FileExtensionEscapingStrategy
$name = substr($name, 0, -5); $name = substr($name, 0, -5);
} }
$extension = pathinfo($name, PATHINFO_EXTENSION); $extension = pathinfo($name, \PATHINFO_EXTENSION);
switch ($extension) { switch ($extension) {
case 'js': case 'js':

View File

@@ -19,39 +19,36 @@ use Twig\Error\SyntaxError;
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class Lexer implements \Twig_LexerInterface class Lexer
{ {
protected $tokens; private $tokens;
protected $code; private $code;
protected $cursor; private $cursor;
protected $lineno; private $lineno;
protected $end; private $end;
protected $state; private $state;
protected $states; private $states;
protected $brackets; private $brackets;
protected $env; private $env;
// to be renamed to $name in 2.0 (where it is private)
protected $filename;
protected $options;
protected $regexes;
protected $position;
protected $positions;
protected $currentVarBlockLine;
private $source; private $source;
private $options;
private $regexes;
private $position;
private $positions;
private $currentVarBlockLine;
const STATE_DATA = 0; public const STATE_DATA = 0;
const STATE_BLOCK = 1; public const STATE_BLOCK = 1;
const STATE_VAR = 2; public const STATE_VAR = 2;
const STATE_STRING = 3; public const STATE_STRING = 3;
const STATE_INTERPOLATION = 4; public const STATE_INTERPOLATION = 4;
const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; public const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A'; public const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A';
const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
const REGEX_DQ_STRING_DELIM = '/"/A'; public const REGEX_DQ_STRING_DELIM = '/"/A';
const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
const PUNCTUATION = '()[]{}?:.,|'; public const PUNCTUATION = '()[]{}?:.,|';
public function __construct(Environment $env, array $options = []) public function __construct(Environment $env, array $options = [])
{ {
@@ -100,9 +97,7 @@ class Lexer implements \Twig_LexerInterface
$this->options['whitespace_trim']. // - $this->options['whitespace_trim']. // -
'|'. '|'.
$this->options['whitespace_line_trim']. // ~ $this->options['whitespace_line_trim']. // ~
')?\s*'. ')?\s*endverbatim\s*'.
'(?:end%s)'. // endraw or endverbatim
'\s*'.
'(?:'. '(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%} preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}
'|'. '|'.
@@ -117,7 +112,7 @@ class Lexer implements \Twig_LexerInterface
// #} // #}
'lex_comment' => '{ 'lex_comment' => '{
(?:'. (?:'.
preg_quote($this->options['whitespace_trim']).preg_quote($this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n? preg_quote($this->options['whitespace_trim'].$this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n?
'|'. '|'.
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]* preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]*
'|'. '|'.
@@ -127,9 +122,7 @@ class Lexer implements \Twig_LexerInterface
// verbatim %} // verbatim %}
'lex_block_raw' => '{ 'lex_block_raw' => '{
\s* \s*verbatim\s*
(raw|verbatim)
\s*
(?:'. (?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s* preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s*
'|'. '|'.
@@ -160,28 +153,10 @@ class Lexer implements \Twig_LexerInterface
]; ];
} }
public function tokenize($code, $name = null) public function tokenize(Source $source)
{ {
if (!$code instanceof Source) { $this->source = $source;
@trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); $this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode());
$this->source = new Source($code, $name);
} else {
$this->source = $code;
}
if (((int) ini_get('mbstring.func_overload')) & 2) {
@trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED);
}
if (\function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
} else {
$mbEncoding = null;
}
$this->code = str_replace(["\r\n", "\r"], "\n", $this->source->getCode());
$this->filename = $this->source->getName();
$this->cursor = 0; $this->cursor = 0;
$this->lineno = 1; $this->lineno = 1;
$this->end = \strlen($this->code); $this->end = \strlen($this->code);
@@ -192,7 +167,7 @@ class Lexer implements \Twig_LexerInterface
$this->position = -1; $this->position = -1;
// find all token starts in one go // find all token starts in one go
preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE); preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \PREG_OFFSET_CAPTURE);
$this->positions = $matches; $this->positions = $matches;
while ($this->cursor < $this->end) { while ($this->cursor < $this->end) {
@@ -221,25 +196,21 @@ class Lexer implements \Twig_LexerInterface
} }
} }
$this->pushToken(Token::EOF_TYPE); $this->pushToken(/* Token::EOF_TYPE */ -1);
if (!empty($this->brackets)) { if (!empty($this->brackets)) {
list($expect, $lineno) = array_pop($this->brackets); list($expect, $lineno) = array_pop($this->brackets);
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
} }
if ($mbEncoding) {
mb_internal_encoding($mbEncoding);
}
return new TokenStream($this->tokens, $this->source); return new TokenStream($this->tokens, $this->source);
} }
protected function lexData() private function lexData()
{ {
// 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 ($this->position == \count($this->positions[0]) - 1) { if ($this->position == \count($this->positions[0]) - 1) {
$this->pushToken(Token::TEXT_TYPE, substr($this->code, $this->cursor)); $this->pushToken(/* Token::TEXT_TYPE */ 0, substr($this->code, $this->cursor));
$this->cursor = $this->end; $this->cursor = $this->end;
return; return;
@@ -268,7 +239,7 @@ class Lexer implements \Twig_LexerInterface
$text = rtrim($text, " \t\0\x0B"); $text = rtrim($text, " \t\0\x0B");
} }
} }
$this->pushToken(Token::TEXT_TYPE, $text); $this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
$this->moveCursor($textContent.$position[0]); $this->moveCursor($textContent.$position[0]);
switch ($this->positions[1][$this->position][0]) { switch ($this->positions[1][$this->position][0]) {
@@ -280,30 +251,30 @@ class Lexer implements \Twig_LexerInterface
// raw data? // raw data?
if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) { if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->lexRawData($match[1]); $this->lexRawData();
// {% line \d+ %} // {% line \d+ %}
} elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) { } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->lineno = (int) $match[1]; $this->lineno = (int) $match[1];
} else { } else {
$this->pushToken(Token::BLOCK_START_TYPE); $this->pushToken(/* Token::BLOCK_START_TYPE */ 1);
$this->pushState(self::STATE_BLOCK); $this->pushState(self::STATE_BLOCK);
$this->currentVarBlockLine = $this->lineno; $this->currentVarBlockLine = $this->lineno;
} }
break; break;
case $this->options['tag_variable'][0]: case $this->options['tag_variable'][0]:
$this->pushToken(Token::VAR_START_TYPE); $this->pushToken(/* Token::VAR_START_TYPE */ 2);
$this->pushState(self::STATE_VAR); $this->pushState(self::STATE_VAR);
$this->currentVarBlockLine = $this->lineno; $this->currentVarBlockLine = $this->lineno;
break; break;
} }
} }
protected function lexBlock() private function lexBlock()
{ {
if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) { if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::BLOCK_END_TYPE); $this->pushToken(/* Token::BLOCK_END_TYPE */ 3);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->popState(); $this->popState();
} else { } else {
@@ -311,10 +282,10 @@ class Lexer implements \Twig_LexerInterface
} }
} }
protected function lexVar() private function lexVar()
{ {
if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) { if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::VAR_END_TYPE); $this->pushToken(/* Token::VAR_END_TYPE */ 4);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->popState(); $this->popState();
} else { } else {
@@ -322,7 +293,7 @@ class Lexer implements \Twig_LexerInterface
} }
} }
protected function lexExpression() private function lexExpression()
{ {
// whitespace // whitespace
if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) { if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) {
@@ -340,21 +311,21 @@ class Lexer implements \Twig_LexerInterface
} }
// operators // operators
elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) { elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0])); $this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0]));
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
} }
// names // names
elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) { elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::NAME_TYPE, $match[0]); $this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
} }
// numbers // numbers
elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) { elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) {
$number = (float) $match[0]; // floats $number = (float) $match[0]; // floats
if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) { if (ctype_digit($match[0]) && $number <= \PHP_INT_MAX) {
$number = (int) $match[0]; // integers lower than the maximum $number = (int) $match[0]; // integers lower than the maximum
} }
$this->pushToken(Token::NUMBER_TYPE, $number); $this->pushToken(/* Token::NUMBER_TYPE */ 6, $number);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
} }
// punctuation // punctuation
@@ -375,12 +346,12 @@ class Lexer implements \Twig_LexerInterface
} }
} }
$this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); $this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]);
++$this->cursor; ++$this->cursor;
} }
// strings // strings
elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) { elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1))); $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1)));
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
} }
// opening double quoted string // opening double quoted string
@@ -395,14 +366,10 @@ class Lexer implements \Twig_LexerInterface
} }
} }
protected function lexRawData($tag) private function lexRawData()
{ {
if ('raw' === $tag) { if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
@trigger_error(sprintf('Twig Tag "raw" is deprecated since version 1.21. Use "verbatim" instead in %s at line %d.', $this->filename, $this->lineno), E_USER_DEPRECATED); throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source);
}
if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError(sprintf('Unexpected end of file: Unclosed "%s" block.', $tag), $this->lineno, $this->source);
} }
$text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
@@ -420,27 +387,27 @@ class Lexer implements \Twig_LexerInterface
} }
} }
$this->pushToken(Token::TEXT_TYPE, $text); $this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
} }
protected function lexComment() private function lexComment()
{ {
if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { if (!preg_match($this->regexes['lex_comment'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source); throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source);
} }
$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() private function lexString()
{ {
if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) { if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) {
$this->brackets[] = [$this->options['interpolation'][0], $this->lineno]; $this->brackets[] = [$this->options['interpolation'][0], $this->lineno];
$this->pushToken(Token::INTERPOLATION_START_TYPE); $this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->pushState(self::STATE_INTERPOLATION); $this->pushState(self::STATE_INTERPOLATION);
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) { } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) {
$this->pushToken(Token::STRING_TYPE, stripcslashes($match[0])); $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0]));
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
list($expect, $lineno) = array_pop($this->brackets); list($expect, $lineno) = array_pop($this->brackets);
@@ -456,12 +423,12 @@ class Lexer implements \Twig_LexerInterface
} }
} }
protected function lexInterpolation() private function lexInterpolation()
{ {
$bracket = end($this->brackets); $bracket = end($this->brackets);
if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) { if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) {
array_pop($this->brackets); array_pop($this->brackets);
$this->pushToken(Token::INTERPOLATION_END_TYPE); $this->pushToken(/* Token::INTERPOLATION_END_TYPE */ 11);
$this->moveCursor($match[0]); $this->moveCursor($match[0]);
$this->popState(); $this->popState();
} else { } else {
@@ -469,23 +436,23 @@ class Lexer implements \Twig_LexerInterface
} }
} }
protected function pushToken($type, $value = '') private function pushToken($type, $value = '')
{ {
// do not push empty text tokens // do not push empty text tokens
if (Token::TEXT_TYPE === $type && '' === $value) { if (/* Token::TEXT_TYPE */ 0 === $type && '' === $value) {
return; return;
} }
$this->tokens[] = new Token($type, $value, $this->lineno); $this->tokens[] = new Token($type, $value, $this->lineno);
} }
protected function moveCursor($text) private function moveCursor($text)
{ {
$this->cursor += \strlen($text); $this->cursor += \strlen($text);
$this->lineno += substr_count($text, "\n"); $this->lineno += substr_count($text, "\n");
} }
protected function getOperatorRegex() private function getOperatorRegex()
{ {
$operators = array_merge( $operators = array_merge(
['='], ['='],
@@ -499,11 +466,15 @@ class Lexer implements \Twig_LexerInterface
$regex = []; $regex = [];
foreach ($operators as $operator => $length) { foreach ($operators as $operator => $length) {
// 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, a parenthesis, an opening map [ or sequence {
if (ctype_alpha($operator[$length - 1])) {
$r = preg_quote($operator, '/').'(?=[\s()])';
} else {
$r = preg_quote($operator, '/'); $r = preg_quote($operator, '/');
if (ctype_alpha($operator[$length - 1])) {
$r .= '(?=[\s()\[{])';
}
// an operator that begins with a character must not have a dot or pipe before
if (ctype_alpha($operator[0])) {
$r = '(?<![\.\|])'.$r;
} }
// an operator with a space can be any amount of whitespaces // an operator with a space can be any amount of whitespaces
@@ -515,13 +486,13 @@ class Lexer implements \Twig_LexerInterface
return '/'.implode('|', $regex).'/A'; return '/'.implode('|', $regex).'/A';
} }
protected function pushState($state) private function pushState($state)
{ {
$this->states[] = $this->state; $this->states[] = $this->state;
$this->state = $state; $this->state = $state;
} }
protected function popState() private function popState()
{ {
if (0 === \count($this->states)) { if (0 === \count($this->states)) {
throw new \LogicException('Cannot pop state without a previous state.'); throw new \LogicException('Cannot pop state without a previous state.');

View File

@@ -24,13 +24,11 @@ use Twig\Source;
* *
* This loader should only be used for unit testing. * This loader should only be used for unit testing.
* *
* @final
*
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface final class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
{ {
protected $templates = []; private $templates = [];
/** /**
* @param array $templates An array of templates (keys are the names, and values are the source code) * @param array $templates An array of templates (keys are the names, and values are the source code)
@@ -48,19 +46,7 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
*/ */
public function setTemplate($name, $template) public function setTemplate($name, $template)
{ {
$this->templates[(string) $name] = $template; $this->templates[$name] = $template;
}
public function getSource($name)
{
@trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED);
$name = (string) $name;
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}
return $this->templates[$name];
} }
public function getSourceContext($name) public function getSourceContext($name)
@@ -75,12 +61,11 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
public function exists($name) public function exists($name)
{ {
return isset($this->templates[(string) $name]); return isset($this->templates[$name]);
} }
public function getCacheKey($name) public function getCacheKey($name)
{ {
$name = (string) $name;
if (!isset($this->templates[$name])) { if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
} }
@@ -90,7 +75,6 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
public function isFresh($name, $time) public function isFresh($name, $time)
{ {
$name = (string) $name;
if (!isset($this->templates[$name])) { if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
} }

View File

@@ -12,19 +12,16 @@
namespace Twig\Loader; namespace Twig\Loader;
use Twig\Error\LoaderError; use Twig\Error\LoaderError;
use Twig\Source;
/** /**
* Loads templates from other loaders. * Loads templates from other loaders.
* *
* @final
*
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface final class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
{ {
private $hasSourceCache = []; private $hasSourceCache = [];
protected $loaders = []; private $loaders = [];
/** /**
* @param LoaderInterface[] $loaders * @param LoaderInterface[] $loaders
@@ -50,40 +47,16 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
return $this->loaders; return $this->loaders;
} }
public function getSource($name)
{
@trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED);
$exceptions = [];
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) {
continue;
}
try {
return $loader->getSource($name);
} catch (LoaderError $e) {
$exceptions[] = $e->getMessage();
}
}
throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
}
public function getSourceContext($name) public function getSourceContext($name)
{ {
$exceptions = []; $exceptions = [];
foreach ($this->loaders as $loader) { foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { if (!$loader->exists($name)) {
continue; continue;
} }
try { try {
if ($loader instanceof SourceContextLoaderInterface) {
return $loader->getSourceContext($name); return $loader->getSourceContext($name);
}
return new Source($loader->getSource($name), $name);
} catch (LoaderError $e) { } catch (LoaderError $e) {
$exceptions[] = $e->getMessage(); $exceptions[] = $e->getMessage();
} }
@@ -94,31 +67,14 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
public function exists($name) public function exists($name)
{ {
$name = (string) $name;
if (isset($this->hasSourceCache[$name])) { if (isset($this->hasSourceCache[$name])) {
return $this->hasSourceCache[$name]; return $this->hasSourceCache[$name];
} }
foreach ($this->loaders as $loader) { foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface) {
if ($loader->exists($name)) { if ($loader->exists($name)) {
return $this->hasSourceCache[$name] = true; return $this->hasSourceCache[$name] = true;
} }
continue;
}
try {
if ($loader instanceof SourceContextLoaderInterface) {
$loader->getSourceContext($name);
} else {
$loader->getSource($name);
}
return $this->hasSourceCache[$name] = true;
} catch (LoaderError $e) {
}
} }
return $this->hasSourceCache[$name] = false; return $this->hasSourceCache[$name] = false;
@@ -128,7 +84,7 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
{ {
$exceptions = []; $exceptions = [];
foreach ($this->loaders as $loader) { foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { if (!$loader->exists($name)) {
continue; continue;
} }
@@ -146,7 +102,7 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
{ {
$exceptions = []; $exceptions = [];
foreach ($this->loaders as $loader) { foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { if (!$loader->exists($name)) {
continue; continue;
} }

View File

@@ -12,22 +12,12 @@
namespace Twig\Loader; namespace Twig\Loader;
/** /**
* Adds an exists() method for loaders. * Empty interface for Twig 1.x compatibility.
* *
* @author Florin Patan <florinpatan@gmail.com> * @deprecated since Twig 2.7, to be removed in 3.0
*
* @deprecated since 1.12 (to be removed in 3.0)
*/ */
interface ExistsLoaderInterface interface ExistsLoaderInterface extends LoaderInterface
{ {
/**
* 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 bool If the template source code is handled by this loader or not
*/
public function exists($name);
} }
class_alias('Twig\Loader\ExistsLoaderInterface', 'Twig_ExistsLoaderInterface'); class_alias('Twig\Loader\ExistsLoaderInterface', 'Twig_ExistsLoaderInterface');

View File

@@ -22,7 +22,7 @@ use Twig\Source;
class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
{ {
/** Identifier of the main namespace. */ /** Identifier of the main namespace. */
const MAIN_NAMESPACE = '__main__'; public const MAIN_NAMESPACE = '__main__';
protected $paths = []; protected $paths = [];
protected $cache = []; protected $cache = [];
@@ -34,10 +34,10 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
* @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|null $rootPath The root path common to all relative paths (null for getcwd()) * @param string|null $rootPath The root path common to all relative paths (null for getcwd())
*/ */
public function __construct($paths = [], $rootPath = null) public function __construct($paths = [], string $rootPath = null)
{ {
$this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR; $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR;
if (false !== $realPath = realpath($rootPath)) { if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) {
$this->rootPath = $realPath.\DIRECTORY_SEPARATOR; $this->rootPath = $realPath.\DIRECTORY_SEPARATOR;
} }
@@ -136,17 +136,6 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
} }
} }
public function getSource($name)
{
@trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED);
if (null === ($path = $this->findTemplate($name)) || false === $path) {
return '';
}
return file_get_contents($path);
}
public function getSourceContext($name) public function getSourceContext($name)
{ {
if (null === ($path = $this->findTemplate($name)) || false === $path) { if (null === ($path = $this->findTemplate($name)) || false === $path) {
@@ -177,13 +166,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
return true; return true;
} }
try {
return null !== ($path = $this->findTemplate($name, false)) && false !== $path; return null !== ($path = $this->findTemplate($name, false)) && false !== $path;
} catch (LoaderError $e) {
@trigger_error(sprintf('In %s::findTemplate(), you must accept a second argument that when set to "false" returns "false" instead of throwing an exception. Not supporting this argument is deprecated since version 1.27.', \get_class($this)), E_USER_DEPRECATED);
return false;
}
} }
public function isFresh($name, $time) public function isFresh($name, $time)
@@ -199,13 +182,15 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
/** /**
* Checks if the template can be found. * Checks if the template can be found.
* *
* In Twig 3.0, findTemplate must return a string or null (returning false won't work anymore).
*
* @param string $name The template name * @param string $name The template name
* @param bool $throw Whether to throw an exception when an error occurs
* *
* @return string|false|null The template name or false/null * @return string|false|null The template name or false/null
*/ */
protected function findTemplate($name) protected function findTemplate($name, $throw = true)
{ {
$throw = \func_num_args() > 1 ? func_get_arg(1) : true;
$name = $this->normalizeName($name); $name = $this->normalizeName($name);
if (isset($this->cache[$name])) { if (isset($this->cache[$name])) {
@@ -221,9 +206,9 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
} }
try { try {
$this->validateName($name);
list($namespace, $shortname) = $this->parseName($name); list($namespace, $shortname) = $this->parseName($name);
$this->validateName($shortname);
} catch (LoaderError $e) { } catch (LoaderError $e) {
if (!$throw) { if (!$throw) {
return false; return false;
@@ -265,7 +250,12 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
throw new LoaderError($this->errorCache[$name]); throw new LoaderError($this->errorCache[$name]);
} }
protected function parseName($name, $default = self::MAIN_NAMESPACE) private function normalizeName($name)
{
return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name));
}
private function parseName($name, $default = self::MAIN_NAMESPACE)
{ {
if (isset($name[0]) && '@' == $name[0]) { if (isset($name[0]) && '@' == $name[0]) {
if (false === $pos = strpos($name, '/')) { if (false === $pos = strpos($name, '/')) {
@@ -281,12 +271,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
return [$default, $name]; return [$default, $name];
} }
protected function normalizeName($name) private function validateName($name)
{
return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name));
}
protected function validateName($name)
{ {
if (false !== strpos($name, "\0")) { if (false !== strpos($name, "\0")) {
throw new LoaderError('A template name cannot contain NUL bytes.'); throw new LoaderError('A template name cannot contain NUL bytes.');
@@ -312,10 +297,10 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
{ {
return strspn($file, '/\\', 0, 1) return strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0]) || (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === substr($file, 1, 1) && ':' === $file[1]
&& strspn($file, '/\\', 2, 1) && strspn($file, '/\\', 2, 1)
) )
|| null !== parse_url($file, PHP_URL_SCHEME) || null !== parse_url($file, \PHP_URL_SCHEME)
; ;
} }
} }

View File

@@ -12,6 +12,7 @@
namespace Twig\Loader; namespace Twig\Loader;
use Twig\Error\LoaderError; use Twig\Error\LoaderError;
use Twig\Source;
/** /**
* Interface all loaders must implement. * Interface all loaders must implement.
@@ -21,17 +22,15 @@ use Twig\Error\LoaderError;
interface LoaderInterface interface LoaderInterface
{ {
/** /**
* Gets the source code of a template, given its name. * Returns the source context for a given template logical name.
* *
* @param string $name The name of the template to load * @param string $name The template logical name
* *
* @return string The template source code * @return Source
* *
* @throws LoaderError When $name is not found * @throws LoaderError When $name is not found
*
* @deprecated since 1.27 (to be removed in 2.0), implement Twig\Loader\SourceContextLoaderInterface
*/ */
public function getSource($name); public function getSourceContext($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.
@@ -56,6 +55,15 @@ interface LoaderInterface
* @throws LoaderError When $name is not found * @throws LoaderError When $name is not found
*/ */
public function isFresh($name, $time); public function isFresh($name, $time);
/**
* 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 bool If the template source code is handled by this loader or not
*/
public function exists($name);
} }
class_alias('Twig\Loader\LoaderInterface', 'Twig_LoaderInterface'); class_alias('Twig\Loader\LoaderInterface', 'Twig_LoaderInterface');

View File

@@ -11,28 +11,11 @@
namespace Twig\Loader; namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/** /**
* Adds a getSourceContext() method for loaders. * Empty interface for Twig 1.x compatibility.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since 1.27 (to be removed in 3.0)
*/ */
interface SourceContextLoaderInterface interface SourceContextLoaderInterface extends LoaderInterface
{ {
/**
* Returns the source context for a given template logical name.
*
* @param string $name The template logical name
*
* @return Source
*
* @throws LoaderError When $name is not found
*/
public function getSourceContext($name);
} }
class_alias('Twig\Loader\SourceContextLoaderInterface', 'Twig_SourceContextLoaderInterface'); class_alias('Twig\Loader\SourceContextLoaderInterface', 'Twig_SourceContextLoaderInterface');

View File

@@ -16,10 +16,10 @@ namespace Twig;
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class Markup implements \Countable class Markup implements \Countable, \JsonSerializable
{ {
protected $content; private $content;
protected $charset; private $charset;
public function __construct($content, $charset) public function __construct($content, $charset)
{ {
@@ -32,9 +32,22 @@ class Markup implements \Countable
return $this->content; return $this->content;
} }
/**
* @return int
*/
#[\ReturnTypeWillChange]
public function count() public function count()
{ {
return \function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : \strlen($this->content); return mb_strlen($this->content, $this->charset);
}
/**
* @return mixed
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->content;
} }
} }

View File

@@ -26,7 +26,7 @@ use Twig\Compiler;
*/ */
class AutoEscapeNode extends Node class AutoEscapeNode extends Node
{ {
public function __construct($value, \Twig_NodeInterface $body, $lineno, $tag = 'autoescape') public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape')
{ {
parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag); parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag);
} }

View File

@@ -21,7 +21,7 @@ use Twig\Compiler;
*/ */
class BlockNode extends Node class BlockNode extends Node
{ {
public function __construct($name, \Twig_NodeInterface $body, $lineno, $tag = null) public function __construct(string $name, Node $body, int $lineno, string $tag = null)
{ {
parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag); parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag);
} }
@@ -32,6 +32,7 @@ class BlockNode extends Node
->addDebugInfo($this) ->addDebugInfo($this)
->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n") ->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n")
->indent() ->indent()
->write("\$macros = \$this->macros;\n")
; ;
$compiler $compiler

View File

@@ -21,7 +21,7 @@ use Twig\Compiler;
*/ */
class BlockReferenceNode extends Node implements NodeOutputInterface class BlockReferenceNode extends Node implements NodeOutputInterface
{ {
public function __construct($name, $lineno, $tag = null) public function __construct(string $name, int $lineno, string $tag = null)
{ {
parent::__construct([], ['name' => $name], $lineno, $tag); parent::__construct([], ['name' => $name], $lineno, $tag);
} }

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class CheckSecurityCallNode extends Node
{
public function compile(Compiler $compiler)
{
$compiler
->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n")
->write("\$this->checkSecurity();\n")
;
}
}

View File

@@ -18,9 +18,9 @@ use Twig\Compiler;
*/ */
class CheckSecurityNode extends Node class CheckSecurityNode extends Node
{ {
protected $usedFilters; private $usedFilters;
protected $usedTags; private $usedTags;
protected $usedFunctions; private $usedFunctions;
public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) public function __construct(array $usedFilters, array $usedTags, array $usedFunctions)
{ {
@@ -45,10 +45,13 @@ class CheckSecurityNode extends Node
} }
$compiler $compiler
->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n") ->write("\n")
->write('$tags = ')->repr(array_filter($tags))->raw(";\n") ->write("public function checkSecurity()\n")
->write('$filters = ')->repr(array_filter($filters))->raw(";\n") ->write("{\n")
->write('$functions = ')->repr(array_filter($functions))->raw(";\n\n") ->indent()
->write('static $tags = ')->repr(array_filter($tags))->raw(";\n")
->write('static $filters = ')->repr(array_filter($filters))->raw(";\n")
->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n")
->write("try {\n") ->write("try {\n")
->indent() ->indent()
->write("\$this->sandbox->checkSecurity(\n") ->write("\$this->sandbox->checkSecurity(\n")
@@ -61,7 +64,7 @@ class CheckSecurityNode extends Node
->outdent() ->outdent()
->write("} catch (SecurityError \$e) {\n") ->write("} catch (SecurityError \$e) {\n")
->indent() ->indent()
->write("\$e->setSourceContext(\$this->getSourceContext());\n\n") ->write("\$e->setSourceContext(\$this->source);\n\n")
->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") ->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n")
->indent() ->indent()
->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n")
@@ -78,6 +81,8 @@ class CheckSecurityNode extends Node
->write("throw \$e;\n") ->write("throw \$e;\n")
->outdent() ->outdent()
->write("}\n\n") ->write("}\n\n")
->outdent()
->write("}\n")
; ;
} }
} }

View File

@@ -33,10 +33,13 @@ class CheckToStringNode extends AbstractExpression
public function compile(Compiler $compiler) public function compile(Compiler $compiler)
{ {
$expr = $this->getNode('expr');
$compiler $compiler
->raw('$this->sandbox->ensureToStringAllowed(') ->raw('$this->sandbox->ensureToStringAllowed(')
->subcompile($this->getNode('expr')) ->subcompile($expr)
->raw(')') ->raw(', ')
->repr($expr->getTemplateLine())
->raw(', $this->source)')
; ;
} }
} }

View File

@@ -22,7 +22,7 @@ use Twig\Node\Expression\ConstantExpression;
*/ */
class DeprecatedNode extends Node class DeprecatedNode extends Node
{ {
public function __construct(AbstractExpression $expr, $lineno, $tag = null) public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{ {
parent::__construct(['expr' => $expr], [], $lineno, $tag); parent::__construct(['expr' => $expr], [], $lineno, $tag);
} }

View File

@@ -21,7 +21,7 @@ use Twig\Node\Expression\AbstractExpression;
*/ */
class DoNode extends Node class DoNode extends Node
{ {
public function __construct(AbstractExpression $expr, $lineno, $tag = null) public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{ {
parent::__construct(['expr' => $expr], [], $lineno, $tag); parent::__construct(['expr' => $expr], [], $lineno, $tag);
} }

View File

@@ -23,13 +23,11 @@ use Twig\Node\Expression\ConstantExpression;
class EmbedNode extends IncludeNode class EmbedNode extends IncludeNode
{ {
// we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) // 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($name, $index, AbstractExpression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null)
{ {
parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
$this->setAttribute('name', $name); $this->setAttribute('name', $name);
// to be removed in 2.0, used name instead
$this->setAttribute('filename', $name);
$this->setAttribute('index', $index); $this->setAttribute('index', $index);
} }

View File

@@ -15,9 +15,9 @@ use Twig\Compiler;
class ArrayExpression extends AbstractExpression class ArrayExpression extends AbstractExpression
{ {
protected $index; private $index;
public function __construct(array $elements, $lineno) public function __construct(array $elements, int $lineno)
{ {
parent::__construct($elements, [], $lineno); parent::__construct($elements, [], $lineno);

View File

@@ -44,7 +44,7 @@ class ArrowFunctionExpression extends AbstractExpression
; ;
} }
$compiler $compiler
->raw(') use ($context) { ') ->raw(') use ($context, $macros) { ')
; ;
foreach ($this->getNode('names') as $name) { foreach ($this->getNode('names') as $name) {
$compiler $compiler

View File

@@ -14,10 +14,11 @@ namespace Twig\Node\Expression\Binary;
use Twig\Compiler; use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Node;
abstract class AbstractBinary extends AbstractExpression abstract class AbstractBinary extends AbstractExpression
{ {
public function __construct(\Twig_NodeInterface $left, \Twig_NodeInterface $right, $lineno) public function __construct(Node $left, Node $right, int $lineno)
{ {
parent::__construct(['left' => $left, 'right' => $right], [], $lineno); parent::__construct(['left' => $left, 'right' => $right], [], $lineno);
} }

View File

@@ -15,21 +15,6 @@ use Twig\Compiler;
class PowerBinary extends AbstractBinary class PowerBinary extends AbstractBinary
{ {
public function compile(Compiler $compiler)
{
if (\PHP_VERSION_ID >= 50600) {
return parent::compile($compiler);
}
$compiler
->raw('pow(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
public function operator(Compiler $compiler) public function operator(Compiler $compiler)
{ {
return $compiler->raw('**'); return $compiler->raw('**');

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class SpaceshipBinary extends AbstractBinary
{
public function operator(Compiler $compiler)
{
return $compiler->raw('<=>');
}
}

View File

@@ -22,17 +22,8 @@ use Twig\Node\Node;
*/ */
class BlockReferenceExpression extends AbstractExpression class BlockReferenceExpression extends AbstractExpression
{ {
/** public function __construct(Node $name, ?Node $template, int $lineno, string $tag = null)
* @param Node|null $template
*/
public function __construct(\Twig_NodeInterface $name, $template = null, $lineno, $tag = null)
{ {
if (\is_bool($template)) {
@trigger_error(sprintf('The %s method "$asString" argument is deprecated since version 1.28 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED);
$template = null;
}
$nodes = ['name' => $name]; $nodes = ['name' => $name];
if (null !== $template) { if (null !== $template) {
$nodes['template'] = $template; $nodes['template'] = $template;
@@ -58,7 +49,7 @@ class BlockReferenceExpression extends AbstractExpression
} }
} }
private function compileTemplateCall(Compiler $compiler, $method) private function compileTemplateCall(Compiler $compiler, string $method): Compiler
{ {
if (!$this->hasNode('template')) { if (!$this->hasNode('template')) {
$compiler->write('$this'); $compiler->write('$this');
@@ -75,12 +66,11 @@ class BlockReferenceExpression extends AbstractExpression
} }
$compiler->raw(sprintf('->%s', $method)); $compiler->raw(sprintf('->%s', $method));
$this->compileBlockArguments($compiler);
return $compiler; return $this->compileBlockArguments($compiler);
} }
private function compileBlockArguments(Compiler $compiler) private function compileBlockArguments(Compiler $compiler): Compiler
{ {
$compiler $compiler
->raw('(') ->raw('(')

View File

@@ -22,39 +22,39 @@ abstract class CallExpression extends AbstractExpression
protected function compileCallable(Compiler $compiler) protected function compileCallable(Compiler $compiler)
{ {
$closingParenthesis = false; $callable = $this->getAttribute('callable');
$isArray = false;
if ($this->hasAttribute('callable') && $callable = $this->getAttribute('callable')) {
if (\is_string($callable) && false === strpos($callable, '::')) { if (\is_string($callable) && false === strpos($callable, '::')) {
$compiler->raw($callable); $compiler->raw($callable);
} else { } else {
list($r, $callable) = $this->reflectCallable($callable); [$r, $callable] = $this->reflectCallable($callable);
if ($r instanceof \ReflectionMethod && \is_string($callable[0])) {
if ($r->isStatic()) { if (\is_string($callable)) {
$compiler->raw($callable);
} elseif (\is_array($callable) && \is_string($callable[0])) {
if (!$r instanceof \ReflectionMethod || $r->isStatic()) {
$compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1]));
} else { } else {
$compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
} }
} elseif ($r instanceof \ReflectionMethod && $callable[0] instanceof ExtensionInterface) { } elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) {
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($callable[0]), $callable[1])); $class = \get_class($callable[0]);
if (!$compiler->getEnvironment()->hasExtension($class)) {
// Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class));
} else { } else {
$type = ucfirst($this->getAttribute('type')); $compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\')));
$compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), ', $type, $this->getAttribute('name')));
$closingParenthesis = true;
$isArray = true;
}
}
} else {
$compiler->raw($this->getAttribute('thing')->compile());
} }
$this->compileArguments($compiler, $isArray); $compiler->raw(sprintf('->%s', $callable[1]));
} else {
if ($closingParenthesis) { $compiler->raw(sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $this->getAttribute('name')));
$compiler->raw(')');
} }
} }
$this->compileArguments($compiler);
}
protected function compileArguments(Compiler $compiler, $isArray = false) protected function compileArguments(Compiler $compiler, $isArray = false)
{ {
$compiler->raw($isArray ? '[' : '('); $compiler->raw($isArray ? '[' : '(');
@@ -93,10 +93,8 @@ abstract class CallExpression extends AbstractExpression
} }
if ($this->hasNode('arguments')) { if ($this->hasNode('arguments')) {
$callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null; $callable = $this->getAttribute('callable');
$arguments = $this->getArguments($callable, $this->getNode('arguments')); $arguments = $this->getArguments($callable, $this->getNode('arguments'));
foreach ($arguments as $node) { foreach ($arguments as $node) {
if (!$first) { if (!$first) {
$compiler->raw(', '); $compiler->raw(', ');
@@ -142,14 +140,23 @@ abstract class CallExpression extends AbstractExpression
throw new \LogicException($message); throw new \LogicException($message);
} }
$callableParameters = $this->getCallableParameters($callable, $isVariadic); list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic);
$arguments = []; $arguments = [];
$names = []; $names = [];
$missingArguments = []; $missingArguments = [];
$optionalArguments = []; $optionalArguments = [];
$pos = 0; $pos = 0;
foreach ($callableParameters as $callableParameter) { foreach ($callableParameters as $callableParameter) {
$names[] = $name = $this->normalizeName($callableParameter->name); $name = $this->normalizeName($callableParameter->name);
if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) {
if ('start' === $name) {
$name = 'low';
} elseif ('end' === $name) {
$name = 'high';
}
}
$names[] = $name;
if (\array_key_exists($name, $parameters)) { if (\array_key_exists($name, $parameters)) {
if (\array_key_exists($pos, $parameters)) { if (\array_key_exists($pos, $parameters)) {
@@ -187,7 +194,7 @@ abstract class CallExpression extends AbstractExpression
} }
if ($isVariadic) { if ($isVariadic) {
$arbitraryArguments = new ArrayExpression([], -1); $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1);
foreach ($parameters as $key => $value) { foreach ($parameters as $key => $value) {
if (\is_int($key)) { if (\is_int($key)) {
$arbitraryArguments->addElement($value); $arbitraryArguments->addElement($value);
@@ -230,12 +237,9 @@ abstract class CallExpression extends AbstractExpression
return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name));
} }
private function getCallableParameters($callable, $isVariadic) private function getCallableParameters($callable, bool $isVariadic): array
{ {
list($r) = $this->reflectCallable($callable); [$r, , $callableName] = $this->reflectCallable($callable);
if (null === $r) {
return [];
}
$parameters = $r->getParameters(); $parameters = $r->getParameters();
if ($this->hasNode('node')) { if ($this->hasNode('node')) {
@@ -252,21 +256,21 @@ abstract class CallExpression extends AbstractExpression
array_shift($parameters); array_shift($parameters);
} }
} }
$isPhpVariadic = false;
if ($isVariadic) { if ($isVariadic) {
$argument = end($parameters); $argument = end($parameters);
if ($argument && $argument->isArray() && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { $isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName();
if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {
array_pop($parameters); array_pop($parameters);
} elseif ($argument && $argument->isVariadic()) {
array_pop($parameters);
$isPhpVariadic = true;
} else { } else {
$callableName = $r->name;
if ($r instanceof \ReflectionMethod) {
$callableName = $r->getDeclaringClass()->name.'::'.$callableName;
}
throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name'))); throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name')));
} }
} }
return $parameters; return [$parameters, $isPhpVariadic];
} }
private function reflectCallable($callable) private function reflectCallable($callable)
@@ -275,30 +279,44 @@ abstract class CallExpression extends AbstractExpression
return $this->reflector; return $this->reflector;
} }
if (\is_array($callable)) { if (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
if (!method_exists($callable[0], $callable[1])) { $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)];
// __call()
return [null, []];
}
$r = new \ReflectionMethod($callable[0], $callable[1]);
} elseif (\is_object($callable) && !$callable instanceof \Closure) {
$r = new \ReflectionObject($callable);
$r = $r->getMethod('__invoke');
$callable = [$callable, '__invoke'];
} elseif (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
$class = substr($callable, 0, $pos);
$method = substr($callable, $pos + 2);
if (!method_exists($class, $method)) {
// __staticCall()
return [null, []];
}
$r = new \ReflectionMethod($callable);
$callable = [$class, $method];
} else {
$r = new \ReflectionFunction($callable);
} }
return $this->reflector = [$r, $callable]; if (\is_array($callable) && method_exists($callable[0], $callable[1])) {
$r = new \ReflectionMethod($callable[0], $callable[1]);
return $this->reflector = [$r, $callable, $r->class.'::'.$r->name];
}
$checkVisibility = $callable instanceof \Closure;
try {
$closure = \Closure::fromCallable($callable);
} catch (\TypeError $e) {
throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e);
}
$r = new \ReflectionFunction($closure);
if (false !== strpos($r->name, '{closure}')) {
return $this->reflector = [$r, $callable, 'Closure'];
}
if ($object = $r->getClosureThis()) {
$callable = [$object, $r->name];
$callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name;
} elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {
$callableName = $class->name.'::'.$r->name;
} elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {
$callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
} else {
$callable = $callableName = $r->name;
}
if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) {
$callable = $r->getClosure();
}
return $this->reflector = [$r, $callable, $callableName];
} }
} }

View File

@@ -16,7 +16,7 @@ use Twig\Compiler;
class ConditionalExpression extends AbstractExpression class ConditionalExpression extends AbstractExpression
{ {
public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, $lineno) public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, int $lineno)
{ {
parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno); parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno);
} }

View File

@@ -16,7 +16,7 @@ use Twig\Compiler;
class ConstantExpression extends AbstractExpression class ConstantExpression extends AbstractExpression
{ {
public function __construct($value, $lineno) public function __construct($value, int $lineno)
{ {
parent::__construct([], ['value' => $value], $lineno); parent::__construct([], ['value' => $value], $lineno);
} }

View File

@@ -29,7 +29,7 @@ use Twig\Node\Node;
*/ */
class DefaultFilter extends FilterExpression class DefaultFilter extends FilterExpression
{ {
public function __construct(\Twig_NodeInterface $node, ConstantExpression $filterName, \Twig_NodeInterface $arguments, $lineno, $tag = null) public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
{ {
$default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine()); $default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine());

View File

@@ -13,11 +13,11 @@
namespace Twig\Node\Expression; namespace Twig\Node\Expression;
use Twig\Compiler; use Twig\Compiler;
use Twig\TwigFilter; use Twig\Node\Node;
class FilterExpression extends CallExpression class FilterExpression extends CallExpression
{ {
public function __construct(\Twig_NodeInterface $node, ConstantExpression $filterName, \Twig_NodeInterface $arguments, $lineno, $tag = null) public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
{ {
parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag); parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag);
} }
@@ -29,16 +29,11 @@ class FilterExpression extends CallExpression
$this->setAttribute('name', $name); $this->setAttribute('name', $name);
$this->setAttribute('type', 'filter'); $this->setAttribute('type', 'filter');
$this->setAttribute('thing', $filter);
$this->setAttribute('needs_environment', $filter->needsEnvironment()); $this->setAttribute('needs_environment', $filter->needsEnvironment());
$this->setAttribute('needs_context', $filter->needsContext()); $this->setAttribute('needs_context', $filter->needsContext());
$this->setAttribute('arguments', $filter->getArguments()); $this->setAttribute('arguments', $filter->getArguments());
if ($filter instanceof \Twig_FilterCallableInterface || $filter instanceof TwigFilter) {
$this->setAttribute('callable', $filter->getCallable()); $this->setAttribute('callable', $filter->getCallable());
}
if ($filter instanceof TwigFilter) {
$this->setAttribute('is_variadic', $filter->isVariadic()); $this->setAttribute('is_variadic', $filter->isVariadic());
}
$this->compileCallable($compiler); $this->compileCallable($compiler);
} }

View File

@@ -12,11 +12,11 @@
namespace Twig\Node\Expression; namespace Twig\Node\Expression;
use Twig\Compiler; use Twig\Compiler;
use Twig\TwigFunction; use Twig\Node\Node;
class FunctionExpression extends CallExpression class FunctionExpression extends CallExpression
{ {
public function __construct($name, \Twig_NodeInterface $arguments, $lineno) public function __construct(string $name, Node $arguments, int $lineno)
{ {
parent::__construct(['arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno); parent::__construct(['arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno);
} }
@@ -28,21 +28,15 @@ class FunctionExpression extends CallExpression
$this->setAttribute('name', $name); $this->setAttribute('name', $name);
$this->setAttribute('type', 'function'); $this->setAttribute('type', 'function');
$this->setAttribute('thing', $function);
$this->setAttribute('needs_environment', $function->needsEnvironment()); $this->setAttribute('needs_environment', $function->needsEnvironment());
$this->setAttribute('needs_context', $function->needsContext()); $this->setAttribute('needs_context', $function->needsContext());
$this->setAttribute('arguments', $function->getArguments()); $this->setAttribute('arguments', $function->getArguments());
if ($function instanceof \Twig_FunctionCallableInterface || $function instanceof TwigFunction) {
$callable = $function->getCallable(); $callable = $function->getCallable();
if ('constant' === $name && $this->getAttribute('is_defined_test')) { if ('constant' === $name && $this->getAttribute('is_defined_test')) {
$callable = 'twig_constant_is_defined'; $callable = 'twig_constant_is_defined';
} }
$this->setAttribute('callable', $callable); $this->setAttribute('callable', $callable);
}
if ($function instanceof TwigFunction) {
$this->setAttribute('is_variadic', $function->isVariadic()); $this->setAttribute('is_variadic', $function->isVariadic());
}
$this->compileCallable($compiler); $this->compileCallable($compiler);
} }

View File

@@ -13,67 +13,76 @@
namespace Twig\Node\Expression; namespace Twig\Node\Expression;
use Twig\Compiler; use Twig\Compiler;
use Twig\Extension\SandboxExtension;
use Twig\Template; use Twig\Template;
class GetAttrExpression extends AbstractExpression class GetAttrExpression extends AbstractExpression
{ {
public function __construct(AbstractExpression $node, AbstractExpression $attribute, AbstractExpression $arguments = null, $type, $lineno) public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno)
{ {
$nodes = ['node' => $node, 'attribute' => $attribute]; $nodes = ['node' => $node, 'attribute' => $attribute];
if (null !== $arguments) { if (null !== $arguments) {
$nodes['arguments'] = $arguments; $nodes['arguments'] = $arguments;
} }
parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false], $lineno); parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno);
} }
public function compile(Compiler $compiler) public function compile(Compiler $compiler)
{ {
if ($this->getAttribute('disable_c_ext')) { $env = $compiler->getEnvironment();
@trigger_error(sprintf('Using the "disable_c_ext" attribute on %s is deprecated since version 1.30 and will be removed in 2.0.', __CLASS__), E_USER_DEPRECATED);
// optimize array calls
if (
$this->getAttribute('optimizable')
&& (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check'))
&& !$this->getAttribute('is_defined_test')
&& Template::ARRAY_CALL === $this->getAttribute('type')
) {
$var = '$'.$compiler->getVarName();
$compiler
->raw('(('.$var.' = ')
->subcompile($this->getNode('node'))
->raw(') && is_array(')
->raw($var)
->raw(') || ')
->raw($var)
->raw(' instanceof ArrayAccess ? (')
->raw($var)
->raw('[')
->subcompile($this->getNode('attribute'))
->raw('] ?? null) : null)')
;
return;
} }
if (\function_exists('twig_template_get_attributes') && !$this->getAttribute('disable_c_ext')) { $compiler->raw('twig_get_attribute($this->env, $this->source, ');
$compiler->raw('twig_template_get_attributes($this, ');
} else {
$compiler->raw('$this->getAttribute(');
}
if ($this->getAttribute('ignore_strict_check')) { if ($this->getAttribute('ignore_strict_check')) {
$this->getNode('node')->setAttribute('ignore_strict_check', true); $this->getNode('node')->setAttribute('ignore_strict_check', true);
} }
$compiler->subcompile($this->getNode('node')); $compiler
->subcompile($this->getNode('node'))
->raw(', ')
->subcompile($this->getNode('attribute'))
;
$compiler->raw(', ')->subcompile($this->getNode('attribute'));
// only generate optional arguments when needed (to make generated code more readable)
$needFourth = $this->getAttribute('ignore_strict_check');
$needThird = $needFourth || $this->getAttribute('is_defined_test');
$needSecond = $needThird || Template::ANY_CALL !== $this->getAttribute('type');
$needFirst = $needSecond || $this->hasNode('arguments');
if ($needFirst) {
if ($this->hasNode('arguments')) { if ($this->hasNode('arguments')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments')); $compiler->raw(', ')->subcompile($this->getNode('arguments'));
} else { } else {
$compiler->raw(', []'); $compiler->raw(', []');
} }
}
if ($needSecond) { $compiler->raw(', ')
$compiler->raw(', ')->repr($this->getAttribute('type')); ->repr($this->getAttribute('type'))
} ->raw(', ')->repr($this->getAttribute('is_defined_test'))
->raw(', ')->repr($this->getAttribute('ignore_strict_check'))
if ($needThird) { ->raw(', ')->repr($env->hasExtension(SandboxExtension::class))
$compiler->raw(', ')->repr($this->getAttribute('is_defined_test')); ->raw(', ')->repr($this->getNode('node')->getTemplateLine())
} ->raw(')')
;
if ($needFourth) {
$compiler->raw(', ')->repr($this->getAttribute('ignore_strict_check'));
}
$compiler->raw(')');
} }
} }

View File

@@ -15,9 +15,9 @@ use Twig\Compiler;
class MethodCallExpression extends AbstractExpression class MethodCallExpression extends AbstractExpression
{ {
public function __construct(AbstractExpression $node, $method, ArrayExpression $arguments, $lineno) public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno)
{ {
parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false], $lineno); parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno);
if ($node instanceof NameExpression) { if ($node instanceof NameExpression) {
$node->setAttribute('always_defined', true); $node->setAttribute('always_defined', true);
@@ -26,11 +26,24 @@ class MethodCallExpression extends AbstractExpression
public function compile(Compiler $compiler) public function compile(Compiler $compiler)
{ {
if ($this->getAttribute('is_defined_test')) {
$compiler $compiler
->subcompile($this->getNode('node')) ->raw('method_exists($macros[')
->raw('->') ->repr($this->getNode('node')->getAttribute('name'))
->raw($this->getAttribute('method')) ->raw('], ')
->raw('(') ->repr($this->getAttribute('method'))
->raw(')')
;
return;
}
$compiler
->raw('twig_call_macro($macros[')
->repr($this->getNode('node')->getAttribute('name'))
->raw('], ')
->repr($this->getAttribute('method'))
->raw(', [')
; ;
$first = true; $first = true;
foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
@@ -41,7 +54,10 @@ class MethodCallExpression extends AbstractExpression
$compiler->subcompile($pair['value']); $compiler->subcompile($pair['value']);
} }
$compiler->raw(')'); $compiler
->raw('], ')
->repr($this->getTemplateLine())
->raw(', $context, $this->getSourceContext())');
} }
} }

View File

@@ -16,13 +16,13 @@ use Twig\Compiler;
class NameExpression extends AbstractExpression class NameExpression extends AbstractExpression
{ {
protected $specialVars = [ private $specialVars = [
'_self' => '$this', '_self' => '$this->getTemplateName()',
'_context' => '$context', '_context' => '$context',
'_charset' => '$this->env->getCharset()', '_charset' => '$this->env->getCharset()',
]; ];
public function __construct($name, $lineno) public function __construct(string $name, int $lineno)
{ {
parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno);
} }
@@ -36,7 +36,7 @@ class NameExpression extends AbstractExpression
if ($this->getAttribute('is_defined_test')) { if ($this->getAttribute('is_defined_test')) {
if ($this->isSpecial()) { if ($this->isSpecial()) {
$compiler->repr(true); $compiler->repr(true);
} elseif (\PHP_VERSION_ID >= 700400) { } elseif (\PHP_VERSION_ID >= 70400) {
$compiler $compiler
->raw('array_key_exists(') ->raw('array_key_exists(')
->string($name) ->string($name)
@@ -60,45 +60,25 @@ class NameExpression extends AbstractExpression
->raw(']') ->raw(']')
; ;
} else { } else {
if (\PHP_VERSION_ID >= 70000) { if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {
// use PHP 7 null coalescing operator
$compiler $compiler
->raw('($context[') ->raw('($context[')
->string($name) ->string($name)
->raw('] ?? ') ->raw('] ?? null)')
; ;
if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {
$compiler->raw('null)');
} else { } else {
$compiler->raw('$this->getContext($context, ')->string($name)->raw('))');
}
} elseif (\PHP_VERSION_ID >= 50400) {
// PHP 5.4 ternary operator performance was optimized
$compiler $compiler
->raw('(isset($context[') ->raw('(isset($context[')
->string($name) ->string($name)
->raw(']) ? $context[') ->raw(']) || array_key_exists(')
->string($name) ->string($name)
->raw('] : ') ->raw(', $context) ? $context[')
;
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) ->string($name)
; ->raw('] : (function () { throw new RuntimeError(\'Variable ')
->string($name)
if ($this->getAttribute('ignore_strict_check')) { ->raw(' does not exist.\', ')
$compiler->raw(', true'); ->repr($this->lineno)
} ->raw(', $this->source); })()')
$compiler
->raw(')') ->raw(')')
; ;
} }

View File

@@ -20,7 +20,7 @@ use Twig\Node\Node;
class NullCoalesceExpression extends ConditionalExpression class NullCoalesceExpression extends ConditionalExpression
{ {
public function __construct(\Twig_NodeInterface $left, \Twig_NodeInterface $right, $lineno) public function __construct(Node $left, Node $right, int $lineno)
{ {
$test = new DefinedTest(clone $left, 'defined', new Node(), $left->getTemplateLine()); $test = new DefinedTest(clone $left, 'defined', new Node(), $left->getTemplateLine());
// for "block()", we don't need the null test as the return value is always a string // for "block()", we don't need the null test as the return value is always a string
@@ -44,7 +44,7 @@ class NullCoalesceExpression extends ConditionalExpression
* cases might be implemented as an optimizer node visitor, but has not been done * cases might be implemented as an optimizer node visitor, but has not been done
* as benefits are probably not worth the added complexity. * as benefits are probably not worth the added complexity.
*/ */
if (\PHP_VERSION_ID >= 70000 && $this->getNode('expr2') instanceof NameExpression) { if ($this->getNode('expr2') instanceof NameExpression) {
$this->getNode('expr2')->setAttribute('always_defined', true); $this->getNode('expr2')->setAttribute('always_defined', true);
$compiler $compiler
->raw('((') ->raw('((')

View File

@@ -21,7 +21,7 @@ use Twig\Compiler;
*/ */
class ParentExpression extends AbstractExpression class ParentExpression extends AbstractExpression
{ {
public function __construct($name, $lineno, $tag = null) public function __construct(string $name, int $lineno, string $tag = null)
{ {
parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag); parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag);
} }

View File

@@ -15,7 +15,7 @@ use Twig\Compiler;
class TempNameExpression extends AbstractExpression class TempNameExpression extends AbstractExpression
{ {
public function __construct($name, $lineno) public function __construct(string $name, int $lineno)
{ {
parent::__construct([], ['name' => $name], $lineno); parent::__construct([], ['name' => $name], $lineno);
} }

View File

@@ -18,8 +18,10 @@ use Twig\Node\Expression\BlockReferenceExpression;
use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FunctionExpression; use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\TestExpression; use Twig\Node\Expression\TestExpression;
use Twig\Node\Node;
/** /**
* Checks if a variable is defined in the current context. * Checks if a variable is defined in the current context.
@@ -33,7 +35,7 @@ use Twig\Node\Expression\TestExpression;
*/ */
class DefinedTest extends TestExpression class DefinedTest extends TestExpression
{ {
public function __construct(\Twig_NodeInterface $node, $name, \Twig_NodeInterface $arguments = null, $lineno) public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
{ {
if ($node instanceof NameExpression) { if ($node instanceof NameExpression) {
$node->setAttribute('is_defined_test', true); $node->setAttribute('is_defined_test', true);
@@ -46,6 +48,8 @@ class DefinedTest extends TestExpression
$node->setAttribute('is_defined_test', true); $node->setAttribute('is_defined_test', true);
} elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) { } elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) {
$node = new ConstantExpression(true, $node->getTemplateLine()); $node = new ConstantExpression(true, $node->getTemplateLine());
} elseif ($node instanceof MethodCallExpression) {
$node->setAttribute('is_defined_test', true);
} else { } else {
throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); throw new SyntaxError('The "defined" test only works with simple variables.', $lineno);
} }
@@ -53,8 +57,9 @@ class DefinedTest extends TestExpression
parent::__construct($node, $name, $arguments, $lineno); parent::__construct($node, $name, $arguments, $lineno);
} }
protected function changeIgnoreStrictCheck(GetAttrExpression $node) private function changeIgnoreStrictCheck(GetAttrExpression $node)
{ {
$node->setAttribute('optimizable', false);
$node->setAttribute('ignore_strict_check', true); $node->setAttribute('ignore_strict_check', true);
if ($node->getNode('node') instanceof GetAttrExpression) { if ($node->getNode('node') instanceof GetAttrExpression) {

View File

@@ -28,7 +28,7 @@ class OddTest extends TestExpression
$compiler $compiler
->raw('(') ->raw('(')
->subcompile($this->getNode('node')) ->subcompile($this->getNode('node'))
->raw(' % 2 == 1') ->raw(' % 2 != 0')
->raw(')') ->raw(')')
; ;
} }

View File

@@ -12,11 +12,11 @@
namespace Twig\Node\Expression; namespace Twig\Node\Expression;
use Twig\Compiler; use Twig\Compiler;
use Twig\TwigTest; use Twig\Node\Node;
class TestExpression extends CallExpression class TestExpression extends CallExpression
{ {
public function __construct(\Twig_NodeInterface $node, $name, \Twig_NodeInterface $arguments = null, $lineno) public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
{ {
$nodes = ['node' => $node]; $nodes = ['node' => $node];
if (null !== $arguments) { if (null !== $arguments) {
@@ -33,16 +33,9 @@ class TestExpression extends CallExpression
$this->setAttribute('name', $name); $this->setAttribute('name', $name);
$this->setAttribute('type', 'test'); $this->setAttribute('type', 'test');
$this->setAttribute('thing', $test);
if ($test instanceof TwigTest) {
$this->setAttribute('arguments', $test->getArguments()); $this->setAttribute('arguments', $test->getArguments());
}
if ($test instanceof \Twig_TestCallableInterface || $test instanceof TwigTest) {
$this->setAttribute('callable', $test->getCallable()); $this->setAttribute('callable', $test->getCallable());
}
if ($test instanceof TwigTest) {
$this->setAttribute('is_variadic', $test->isVariadic()); $this->setAttribute('is_variadic', $test->isVariadic());
}
$this->compileCallable($compiler); $this->compileCallable($compiler);
} }

View File

@@ -14,10 +14,11 @@ namespace Twig\Node\Expression\Unary;
use Twig\Compiler; use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Node;
abstract class AbstractUnary extends AbstractExpression abstract class AbstractUnary extends AbstractExpression
{ {
public function __construct(\Twig_NodeInterface $node, $lineno) public function __construct(Node $node, int $lineno)
{ {
parent::__construct(['node' => $node], [], $lineno); parent::__construct(['node' => $node], [], $lineno);
} }

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class VariadicExpression extends ArrayExpression
{
public function compile(Compiler $compiler)
{
$compiler->raw('...');
parent::compile($compiler);
}
}

View File

@@ -20,7 +20,7 @@ use Twig\Compiler;
*/ */
class FlushNode extends Node class FlushNode extends Node
{ {
public function __construct($lineno, $tag) public function __construct(int $lineno, string $tag)
{ {
parent::__construct([], [], $lineno, $tag); parent::__construct([], [], $lineno, $tag);
} }

View File

@@ -20,7 +20,7 @@ use Twig\Compiler;
*/ */
class ForLoopNode extends Node class ForLoopNode extends Node
{ {
public function __construct($lineno, $tag = null) public function __construct(int $lineno, string $tag = null)
{ {
parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag); parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag);
} }

View File

@@ -23,9 +23,9 @@ use Twig\Node\Expression\AssignNameExpression;
*/ */
class ForNode extends Node class ForNode extends Node
{ {
protected $loop; private $loop;
public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, AbstractExpression $ifexpr = null, \Twig_NodeInterface $body, \Twig_NodeInterface $else = null, $lineno, $tag = null) public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?AbstractExpression $ifexpr, Node $body, ?Node $else, int $lineno, string $tag = null)
{ {
$body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]); $body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]);

View File

@@ -21,7 +21,7 @@ use Twig\Compiler;
*/ */
class IfNode extends Node class IfNode extends Node
{ {
public function __construct(\Twig_NodeInterface $tests, \Twig_NodeInterface $else = null, $lineno, $tag = null) public function __construct(Node $tests, ?Node $else, int $lineno, string $tag = null)
{ {
$nodes = ['tests' => $tests]; $nodes = ['tests' => $tests];
if (null !== $else) { if (null !== $else) {
@@ -50,8 +50,11 @@ class IfNode extends Node
->subcompile($this->getNode('tests')->getNode($i)) ->subcompile($this->getNode('tests')->getNode($i))
->raw(") {\n") ->raw(") {\n")
->indent() ->indent()
->subcompile($this->getNode('tests')->getNode($i + 1))
; ;
// The node might not exists if the content is empty
if ($this->getNode('tests')->hasNode($i + 1)) {
$compiler->subcompile($this->getNode('tests')->getNode($i + 1));
}
} }
if ($this->hasNode('else')) { if ($this->hasNode('else')) {

View File

@@ -22,20 +22,28 @@ use Twig\Node\Expression\NameExpression;
*/ */
class ImportNode extends Node class ImportNode extends Node
{ {
public function __construct(AbstractExpression $expr, AbstractExpression $var, $lineno, $tag = null) public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true)
{ {
parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno, $tag); parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag);
} }
public function compile(Compiler $compiler) public function compile(Compiler $compiler)
{ {
$compiler $compiler
->addDebugInfo($this) ->addDebugInfo($this)
->write('') ->write('$macros[')
->subcompile($this->getNode('var')) ->repr($this->getNode('var')->getAttribute('name'))
->raw(' = ') ->raw('] = ')
; ;
if ($this->getAttribute('global')) {
$compiler
->raw('$this->macros[')
->repr($this->getNode('var')->getAttribute('name'))
->raw('] = ')
;
}
if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) { if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) {
$compiler->raw('$this'); $compiler->raw('$this');
} else { } else {

View File

@@ -22,7 +22,7 @@ use Twig\Node\Expression\AbstractExpression;
*/ */
class IncludeNode extends Node implements NodeOutputInterface class IncludeNode extends Node implements NodeOutputInterface
{ {
public function __construct(AbstractExpression $expr, AbstractExpression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null)
{ {
$nodes = ['expr' => $expr]; $nodes = ['expr' => $expr];
if (null !== $variables) { if (null !== $variables) {

View File

@@ -21,9 +21,9 @@ use Twig\Error\SyntaxError;
*/ */
class MacroNode extends Node class MacroNode extends Node
{ {
const VARARGS_NAME = 'varargs'; public const VARARGS_NAME = 'varargs';
public function __construct($name, \Twig_NodeInterface $body, \Twig_NodeInterface $arguments, $lineno, $tag = null) public function __construct(string $name, Node $body, Node $arguments, int $lineno, string $tag = null)
{ {
foreach ($arguments as $argumentName => $argument) { foreach ($arguments as $argumentName => $argument) {
if (self::VARARGS_NAME === $argumentName) { if (self::VARARGS_NAME === $argumentName) {
@@ -38,7 +38,7 @@ class MacroNode extends Node
{ {
$compiler $compiler
->addDebugInfo($this) ->addDebugInfo($this)
->write(sprintf('public function get%s(', $this->getAttribute('name'))) ->write(sprintf('public function macro_%s(', $this->getAttribute('name')))
; ;
$count = \count($this->getNode('arguments')); $count = \count($this->getNode('arguments'));
@@ -54,21 +54,16 @@ class MacroNode extends Node
} }
} }
if (\PHP_VERSION_ID >= 50600) {
if ($count) { if ($count) {
$compiler->raw(', '); $compiler->raw(', ');
} }
$compiler->raw('...$__varargs__');
}
$compiler $compiler
->raw('...$__varargs__')
->raw(")\n") ->raw(")\n")
->write("{\n") ->write("{\n")
->indent() ->indent()
; ->write("\$macros = \$this->macros;\n")
$compiler
->write("\$context = \$this->env->mergeGlobals([\n") ->write("\$context = \$this->env->mergeGlobals([\n")
->indent() ->indent()
; ;
@@ -88,19 +83,8 @@ class MacroNode extends Node
->raw(' => ') ->raw(' => ')
; ;
if (\PHP_VERSION_ID >= 50600) {
$compiler->raw("\$__varargs__,\n");
} else {
$compiler
->raw('func_num_args() > ')
->repr($count)
->raw(' ? array_slice(func_get_args(), ')
->repr($count)
->raw(") : [],\n")
;
}
$compiler $compiler
->raw("\$__varargs__,\n")
->outdent() ->outdent()
->write("]);\n\n") ->write("]);\n\n")
->write("\$blocks = [];\n\n") ->write("\$blocks = [];\n\n")
@@ -114,19 +98,14 @@ class MacroNode extends Node
->write("try {\n") ->write("try {\n")
->indent() ->indent()
->subcompile($this->getNode('body')) ->subcompile($this->getNode('body'))
->raw("\n")
->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n")
->outdent() ->outdent()
->write("} catch (\Exception \$e) {\n") ->write("} finally {\n")
->indent() ->indent()
->write("ob_end_clean();\n\n") ->write("ob_end_clean();\n")
->write("throw \$e;\n")
->outdent() ->outdent()
->write("} catch (\Throwable \$e) {\n") ->write("}\n")
->indent()
->write("ob_end_clean();\n\n")
->write("throw \$e;\n")
->outdent()
->write("}\n\n")
->write("return ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n")
->outdent() ->outdent()
->write("}\n\n") ->write("}\n\n")
; ;

View File

@@ -25,16 +25,15 @@ use Twig\Source;
* display_end, constructor_start, constructor_end, and class_end. * display_end, constructor_start, constructor_end, and class_end.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*
* @final since Twig 2.4.0
*/ */
class ModuleNode extends Node class ModuleNode extends Node
{ {
public function __construct(\Twig_NodeInterface $body, AbstractExpression $parent = null, \Twig_NodeInterface $blocks, \Twig_NodeInterface $macros, \Twig_NodeInterface $traits, $embeddedTemplates, $name, $source = '') public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source)
{ {
if (!$name instanceof Source) { if (__CLASS__ !== static::class) {
@trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); @trigger_error('Overriding '.__CLASS__.' is deprecated since Twig 2.4.0 and the class will be final in 3.0.', \E_USER_DEPRECATED);
$source = new Source($source, $name);
} else {
$source = $name;
} }
$nodes = [ $nodes = [
@@ -54,16 +53,11 @@ class ModuleNode extends Node
// embedded templates are set as attributes so that they are only visited once by the visitors // embedded templates are set as attributes so that they are only visited once by the visitors
parent::__construct($nodes, [ parent::__construct($nodes, [
// source to be remove in 2.0
'source' => $source->getCode(),
// filename to be remove in 2.0 (use getTemplateName() instead)
'filename' => $source->getName(),
'index' => null, 'index' => null,
'embedded_templates' => $embeddedTemplates, 'embedded_templates' => $embeddedTemplates,
], 1); ], 1);
// populate the template name of all node children // populate the template name of all node children
$this->setTemplateName($source->getName());
$this->setSourceContext($source); $this->setSourceContext($source);
} }
@@ -89,16 +83,7 @@ class ModuleNode extends Node
$this->compileClassHeader($compiler); $this->compileClassHeader($compiler);
if (
\count($this->getNode('blocks'))
|| \count($this->getNode('traits'))
|| !$this->hasNode('parent')
|| $this->getNode('parent') instanceof ConstantExpression
|| \count($this->getNode('constructor_start'))
|| \count($this->getNode('constructor_end'))
) {
$this->compileConstructor($compiler); $this->compileConstructor($compiler);
}
$this->compileGetParent($compiler); $this->compileGetParent($compiler);
@@ -114,8 +99,6 @@ class ModuleNode extends Node
$this->compileDebugInfo($compiler); $this->compileDebugInfo($compiler);
$this->compileGetSource($compiler);
$this->compileGetSourceContext($compiler); $this->compileGetSourceContext($compiler);
$this->compileClassFooter($compiler); $this->compileClassFooter($compiler);
@@ -166,6 +149,7 @@ class ModuleNode extends Node
->write("use Twig\Environment;\n") ->write("use Twig\Environment;\n")
->write("use Twig\Error\LoaderError;\n") ->write("use Twig\Error\LoaderError;\n")
->write("use Twig\Error\RuntimeError;\n") ->write("use Twig\Error\RuntimeError;\n")
->write("use Twig\Extension\SandboxExtension;\n")
->write("use Twig\Markup;\n") ->write("use Twig\Markup;\n")
->write("use Twig\Sandbox\SecurityError;\n") ->write("use Twig\Sandbox\SecurityError;\n")
->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n") ->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n")
@@ -179,9 +163,11 @@ class ModuleNode extends Node
// if the template name contains */, add a blank to avoid a PHP parse error // if the template name contains */, add a blank to avoid a PHP parse error
->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n") ->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n")
->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index'))) ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index')))
->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass(false)))
->write("{\n") ->write("{\n")
->indent() ->indent()
->write("private \$source;\n")
->write("private \$macros = [];\n\n")
; ;
} }
@@ -192,6 +178,7 @@ class ModuleNode extends Node
->indent() ->indent()
->subcompile($this->getNode('constructor_start')) ->subcompile($this->getNode('constructor_start'))
->write("parent::__construct(\$env);\n\n") ->write("parent::__construct(\$env);\n\n")
->write("\$this->source = \$this->getSourceContext();\n\n")
; ;
// parent // parent
@@ -203,18 +190,24 @@ class ModuleNode extends Node
if ($countTraits) { if ($countTraits) {
// traits // traits
foreach ($this->getNode('traits') as $i => $trait) { foreach ($this->getNode('traits') as $i => $trait) {
$this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i));
$node = $trait->getNode('template'); $node = $trait->getNode('template');
$compiler $compiler
->addDebugInfo($node) ->addDebugInfo($node)
->write(sprintf('$_trait_%s = $this->loadTemplate(', $i))
->subcompile($node)
->raw(', ')
->repr($node->getTemplateName())
->raw(', ')
->repr($node->getTemplateLine())
->raw(");\n")
->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i))
->indent() ->indent()
->write("throw new RuntimeError('Template \"'.") ->write("throw new RuntimeError('Template \"'.")
->subcompile($trait->getNode('template')) ->subcompile($trait->getNode('template'))
->raw(".'\" cannot be used as a trait.', ") ->raw(".'\" cannot be used as a trait.', ")
->repr($node->getTemplateLine()) ->repr($node->getTemplateLine())
->raw(", \$this->getSourceContext());\n") ->raw(", \$this->source);\n")
->outdent() ->outdent()
->write("}\n") ->write("}\n")
->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i))
@@ -226,13 +219,13 @@ class ModuleNode extends Node
->string($key) ->string($key)
->raw("])) {\n") ->raw("])) {\n")
->indent() ->indent()
->write("throw new RuntimeError(sprintf('Block ") ->write("throw new RuntimeError('Block ")
->string($key) ->string($key)
->raw(' is not defined in trait ') ->raw(' is not defined in trait ')
->subcompile($trait->getNode('template')) ->subcompile($trait->getNode('template'))
->raw(".'), ") ->raw(".', ")
->repr($node->getTemplateLine()) ->repr($node->getTemplateLine())
->raw(", \$this->getSourceContext());\n") ->raw(", \$this->source);\n")
->outdent() ->outdent()
->write("}\n\n") ->write("}\n\n")
@@ -318,6 +311,7 @@ class ModuleNode extends Node
$compiler $compiler
->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n") ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n")
->indent() ->indent()
->write("\$macros = \$this->macros;\n")
->subcompile($this->getNode('display_start')) ->subcompile($this->getNode('display_start'))
->subcompile($this->getNode('body')) ->subcompile($this->getNode('body'))
; ;
@@ -440,20 +434,6 @@ class ModuleNode extends Node
; ;
} }
protected function compileGetSource(Compiler $compiler)
{
$compiler
->write("/** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */\n")
->write("public function getSource()\n", "{\n")
->indent()
->write("@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED);\n\n")
->write('return $this->getSourceContext()->getCode();')
->raw("\n")
->outdent()
->write("}\n\n")
;
}
protected function compileGetSourceContext(Compiler $compiler) protected function compileGetSourceContext(Compiler $compiler)
{ {
$compiler $compiler

View File

@@ -20,7 +20,7 @@ use Twig\Source;
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class Node implements \Twig_NodeInterface class Node implements \Countable, \IteratorAggregate
{ {
protected $nodes; protected $nodes;
protected $attributes; protected $attributes;
@@ -36,11 +36,11 @@ class Node implements \Twig_NodeInterface
* @param int $lineno The line number * @param int $lineno The line number
* @param string $tag The tag name associated with the Node * @param string $tag The tag name associated with the Node
*/ */
public function __construct(array $nodes = [], array $attributes = [], $lineno = 0, $tag = null) public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0, string $tag = null)
{ {
foreach ($nodes as $name => $node) { foreach ($nodes as $name => $node) {
if (!$node instanceof \Twig_NodeInterface) { if (!$node instanceof self) {
@trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, \get_class($this)), E_USER_DEPRECATED); throw new \InvalidArgumentException(sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, static::class));
} }
} }
$this->nodes = $nodes; $this->nodes = $nodes;
@@ -56,7 +56,7 @@ class Node implements \Twig_NodeInterface
$attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
} }
$repr = [\get_class($this).'('.implode(', ', $attributes)]; $repr = [static::class.'('.implode(', ', $attributes)];
if (\count($this->nodes)) { if (\count($this->nodes)) {
foreach ($this->nodes as $name => $node) { foreach ($this->nodes as $name => $node) {
@@ -77,41 +77,6 @@ class Node implements \Twig_NodeInterface
return implode("\n", $repr); return implode("\n", $repr);
} }
/**
* @deprecated since 1.16.1 (to be removed in 2.0)
*/
public function toXml($asDom = false)
{
@trigger_error(sprintf('%s is deprecated since version 1.16.1 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED);
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$dom->appendChild($xml = $dom->createElement('twig'));
$xml->appendChild($node = $dom->createElement('node'));
$node->setAttribute('class', \get_class($this));
foreach ($this->attributes as $name => $value) {
$node->appendChild($attribute = $dom->createElement('attribute'));
$attribute->setAttribute('name', $name);
$attribute->appendChild($dom->createTextNode($value));
}
foreach ($this->nodes as $name => $n) {
if (null === $n) {
continue;
}
$child = $n->toXml(true)->getElementsByTagName('node')->item(0);
$child = $dom->importNode($child, true);
$child->setAttribute('name', $name);
$node->appendChild($child);
}
return $asDom ? $dom : $dom->saveXML();
}
public function compile(Compiler $compiler) public function compile(Compiler $compiler)
{ {
foreach ($this->nodes as $node) { foreach ($this->nodes as $node) {
@@ -124,16 +89,6 @@ class Node implements \Twig_NodeInterface
return $this->lineno; return $this->lineno;
} }
/**
* @deprecated since 1.27 (to be removed in 2.0)
*/
public function getLine()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateLine() instead.', E_USER_DEPRECATED);
return $this->lineno;
}
public function getNodeTag() public function getNodeTag()
{ {
return $this->tag; return $this->tag;
@@ -153,7 +108,7 @@ class Node implements \Twig_NodeInterface
public function getAttribute($name) public function getAttribute($name)
{ {
if (!\array_key_exists($name, $this->attributes)) { if (!\array_key_exists($name, $this->attributes)) {
throw new \LogicException(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, static::class));
} }
return $this->attributes[$name]; return $this->attributes[$name];
@@ -178,7 +133,7 @@ class Node implements \Twig_NodeInterface
*/ */
public function hasNode($name) public function hasNode($name)
{ {
return \array_key_exists($name, $this->nodes); return isset($this->nodes[$name]);
} }
/** /**
@@ -186,19 +141,15 @@ class Node implements \Twig_NodeInterface
*/ */
public function getNode($name) public function getNode($name)
{ {
if (!\array_key_exists($name, $this->nodes)) { if (!isset($this->nodes[$name])) {
throw new \LogicException(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, static::class));
} }
return $this->nodes[$name]; return $this->nodes[$name];
} }
public function setNode($name, $node = null) public function setNode($name, self $node)
{ {
if (!$node instanceof \Twig_NodeInterface) {
@trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, \get_class($this)), E_USER_DEPRECATED);
}
$this->nodes[$name] = $node; $this->nodes[$name] = $node;
} }
@@ -207,65 +158,59 @@ class Node implements \Twig_NodeInterface
unset($this->nodes[$name]); unset($this->nodes[$name]);
} }
/**
* @return int
*/
#[\ReturnTypeWillChange]
public function count() public function count()
{ {
return \count($this->nodes); return \count($this->nodes);
} }
/**
* @return \Traversable
*/
#[\ReturnTypeWillChange]
public function getIterator() public function getIterator()
{ {
return new \ArrayIterator($this->nodes); return new \ArrayIterator($this->nodes);
} }
public function setTemplateName($name) /**
* @deprecated since 2.8 (to be removed in 3.0)
*/
public function setTemplateName($name/*, $triggerDeprecation = true */)
{ {
$triggerDeprecation = 2 > \func_num_args() || \func_get_arg(1);
if ($triggerDeprecation) {
@trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use setSourceContext() instead.', \E_USER_DEPRECATED);
}
$this->name = $name; $this->name = $name;
foreach ($this->nodes as $node) { foreach ($this->nodes as $node) {
if (null !== $node) { $node->setTemplateName($name, $triggerDeprecation);
$node->setTemplateName($name);
}
} }
} }
public function getTemplateName() public function getTemplateName()
{ {
return $this->name; return $this->sourceContext ? $this->sourceContext->getName() : null;
} }
public function setSourceContext(Source $source) public function setSourceContext(Source $source)
{ {
$this->sourceContext = $source; $this->sourceContext = $source;
foreach ($this->nodes as $node) { foreach ($this->nodes as $node) {
if ($node instanceof Node) {
$node->setSourceContext($source); $node->setSourceContext($source);
} }
}
$this->setTemplateName($source->getName(), false);
} }
public function getSourceContext() public function getSourceContext()
{ {
return $this->sourceContext; return $this->sourceContext;
} }
/**
* @deprecated since 1.27 (to be removed in 2.0)
*/
public function setFilename($name)
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use setTemplateName() instead.', E_USER_DEPRECATED);
$this->setTemplateName($name);
}
/**
* @deprecated since 1.27 (to be removed in 2.0)
*/
public function getFilename()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateName() instead.', E_USER_DEPRECATED);
return $this->name;
}
} }
class_alias('Twig\Node\Node', 'Twig_Node'); class_alias('Twig\Node\Node', 'Twig_Node');

View File

@@ -22,7 +22,7 @@ use Twig\Node\Expression\AbstractExpression;
*/ */
class PrintNode extends Node implements NodeOutputInterface class PrintNode extends Node implements NodeOutputInterface
{ {
public function __construct(AbstractExpression $expr, $lineno, $tag = null) public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{ {
parent::__construct(['expr' => $expr], [], $lineno, $tag); parent::__construct(['expr' => $expr], [], $lineno, $tag);
} }

View File

@@ -20,7 +20,7 @@ use Twig\Compiler;
*/ */
class SandboxNode extends Node class SandboxNode extends Node
{ {
public function __construct(\Twig_NodeInterface $body, $lineno, $tag = null) public function __construct(Node $body, int $lineno, string $tag = null)
{ {
parent::__construct(['body' => $body], [], $lineno, $tag); parent::__construct(['body' => $body], [], $lineno, $tag);
} }
@@ -34,12 +34,19 @@ class SandboxNode extends Node
->write("\$this->sandbox->enableSandbox();\n") ->write("\$this->sandbox->enableSandbox();\n")
->outdent() ->outdent()
->write("}\n") ->write("}\n")
->write("try {\n")
->indent()
->subcompile($this->getNode('body')) ->subcompile($this->getNode('body'))
->outdent()
->write("} finally {\n")
->indent()
->write("if (!\$alreadySandboxed) {\n") ->write("if (!\$alreadySandboxed) {\n")
->indent() ->indent()
->write("\$this->sandbox->disableSandbox();\n") ->write("\$this->sandbox->disableSandbox();\n")
->outdent() ->outdent()
->write("}\n") ->write("}\n")
->outdent()
->write("}\n")
; ;
} }
} }

View File

@@ -13,7 +13,6 @@ namespace Twig\Node;
use Twig\Compiler; use Twig\Compiler;
use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
/** /**
* Adds a check for the __toString() method when the variable is an object and the sandbox is activated. * Adds a check for the __toString() method when the variable is an object and the sandbox is activated.
@@ -42,28 +41,14 @@ class SandboxedPrintNode extends PrintNode
; ;
} else { } else {
$compiler $compiler
->write('$this->env->getExtension(\'\Twig\Extension\SandboxExtension\')->ensureToStringAllowed(') ->write('$this->extensions[SandboxExtension::class]->ensureToStringAllowed(')
->subcompile($expr) ->subcompile($expr)
->raw(");\n") ->raw(', ')
->repr($expr->getTemplateLine())
->raw(", \$this->source);\n")
; ;
} }
} }
/**
* Removes node filters.
*
* This is mostly needed when another visitor adds filters (like the escaper one).
*
* @return Node
*/
protected function removeNodeFilter(Node $node)
{
if ($node instanceof FilterExpression) {
return $this->removeNodeFilter($node->getNode('node'));
}
return $node;
}
} }
class_alias('Twig\Node\SandboxedPrintNode', 'Twig_Node_SandboxedPrint'); class_alias('Twig\Node\SandboxedPrintNode', 'Twig_Node_SandboxedPrint');

View File

@@ -21,7 +21,7 @@ use Twig\Node\Expression\ConstantExpression;
*/ */
class SetNode extends Node implements NodeCaptureInterface class SetNode extends Node implements NodeCaptureInterface
{ {
public function __construct($capture, \Twig_NodeInterface $names, \Twig_NodeInterface $values, $lineno, $tag = null) public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null)
{ {
parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag); parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag);

View File

@@ -18,11 +18,13 @@ use Twig\Compiler;
* *
* It removes spaces between HTML tags. * It removes spaces between HTML tags.
* *
* @deprecated since Twig 2.7, to be removed in 3.0
*
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class SpacelessNode extends Node class SpacelessNode extends Node implements NodeOutputInterface
{ {
public function __construct(\Twig_NodeInterface $body, $lineno, $tag = 'spaceless') public function __construct(Node $body, int $lineno, string $tag = 'spaceless')
{ {
parent::__construct(['body' => $body], [], $lineno, $tag); parent::__construct(['body' => $body], [], $lineno, $tag);
} }

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