table = 'mail_template';
}
/**
* @return int
*/
public function get_count()
{
$row = Database::select(
'count(*) as count',
$this->table,
['where' => ['url_id = ? ' => api_get_current_access_url_id()]],
'first'
);
return $row['count'];
}
/**
* Displays the title + grid.
*
* @return string html code
*/
public function display()
{
// Action links
$html = '
';
$html .= '
'.api_htmlentities(get_lang('Tags')).': '.$tags.'
';
$html .= '
'.api_htmlentities(get_lang('Filters')).': '.$filters.'
';
$html .= '
'.api_htmlentities(get_lang('Functions')).': '.$functions.'
';
$html .= '
'.api_htmlentities(get_lang('Available variables depend on the template type, for example {{ user.getUsername() }} or {{ user.getEmail() }}.')).'
';
$html .= '
'.api_htmlentities(get_lang('For security reasons, any other Twig function, filter (such as filter, map, reduce or sort) or PHP call is blocked.')).'
';
$html .= '
';
return $html;
}
/**
* Renders an admin-stored mail template body through a sandboxed Twig
* environment.
*
* Stored mail templates are untrusted content (any platform admin can edit
* them) and must never be compiled with the full application Twig: the
* non-sandboxed environment exposes the callable-accepting filters
* (filter/map/reduce/sort) that turn a template body into a Server-Side
* Template Injection → Remote Code Execution gadget.
*
* The sandbox here uses an explicit allow-list of tags, filters, functions
* and entity getters; everything else — including the RCE gadget filters —
* is rejected. When rendering is refused or fails, an empty string is
* returned so the caller falls back to the default file-based template.
*
* @param string $templateText The admin-stored Twig template body
* @param array $params The render context (template variables)
*
* @return string The rendered body, or '' when rendering is rejected
*/
public static function renderSandboxedTemplate(string $templateText, array $params): string
{
if ('' === trim($templateText)) {
return '';
}
$allowedMethods = [
User::class => [
'getId', 'getUsername', 'getFirstname', 'getLastname',
'getEmail', 'getStatus', 'getOfficialCode', 'getPhone',
],
];
$allowedProperties = [];
$policy = new SecurityPolicy(
self::ALLOWED_TAGS,
self::ALLOWED_FILTERS,
$allowedMethods,
$allowedProperties,
self::ALLOWED_FUNCTIONS
);
$twig = new Environment(
new ArrayLoader(['mail_template' => $templateText]),
['autoescape' => 'html', 'cache' => false, 'strict_variables' => false]
);
$twig->addExtension(new SandboxExtension($policy, true));
$twig->addFilter(new TwigFilter('get_lang', 'get_lang'));
$twig->addFunction(new TwigFunction('get_lang', 'get_lang'));
try {
return $twig->render('mail_template', $params);
} catch (Throwable $e) {
error_log('Refused to render stored mail template in sandbox: '.$e->getMessage());
return '';
}
}
}