[web] implemented twig templating system (#4264)
Signed-off-by: Kristian Feldsam <feldsam@gmail.com>master
parent
2c5628c0e5
commit
0b64967ec5
|
@ -51,6 +51,8 @@ data/hooks/postfix/*
|
||||||
data/hooks/rspamd/*
|
data/hooks/rspamd/*
|
||||||
data/hooks/sogo/*
|
data/hooks/sogo/*
|
||||||
data/hooks/unbound/*
|
data/hooks/unbound/*
|
||||||
|
data/web/templates/cache/*
|
||||||
|
!data/web/templates/cache/.gitkeep
|
||||||
data/web/.well-known/acme-challenge
|
data/web/.well-known/acme-challenge
|
||||||
data/web/css/build/0081-custom-mailcow.css
|
data/web/css/build/0081-custom-mailcow.css
|
||||||
data/web/inc/vars.local.inc.php
|
data/web/inc/vars.local.inc.php
|
||||||
|
|
|
@ -181,6 +181,11 @@ fi
|
||||||
# Fix permissions for global filters
|
# Fix permissions for global filters
|
||||||
chown -R 82:82 /global_sieve/*
|
chown -R 82:82 /global_sieve/*
|
||||||
|
|
||||||
|
# Fix permissions on twig cache folder
|
||||||
|
chown -R 82:82 /web/templates/cache
|
||||||
|
# Clear cache
|
||||||
|
find /web/templates/cache/* -not -name '.gitkeep' -delete
|
||||||
|
|
||||||
[[ ! -f /etc/nginx/conf.d/ZZZ-ejabberd.conf ]] && echo '# Autogenerated by mailcow' > /etc/nginx/conf.d/ZZZ-ejabberd.conf
|
[[ ! -f /etc/nginx/conf.d/ZZZ-ejabberd.conf ]] && echo '# Autogenerated by mailcow' > /etc/nginx/conf.d/ZZZ-ejabberd.conf
|
||||||
chown 82:82 /etc/nginx/conf.d/ZZZ-ejabberd.conf
|
chown 82:82 /etc/nginx/conf.d/ZZZ-ejabberd.conf
|
||||||
|
|
||||||
|
|
1611
data/web/admin.php
1611
data/web/admin.php
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
1832
data/web/edit.php
1832
data/web/edit.php
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/footer.php';
|
|
||||||
logger();
|
logger();
|
||||||
|
|
||||||
$hash = $js_minifier->getDataHash();
|
$hash = $js_minifier->getDataHash();
|
||||||
|
@ -8,313 +7,42 @@ if(!file_exists($JSPath)) {
|
||||||
$js_minifier->minify($JSPath);
|
$js_minifier->minify($JSPath);
|
||||||
cleanupJS($hash);
|
cleanupJS($hash);
|
||||||
}
|
}
|
||||||
?>
|
|
||||||
<script src="/cache/<?=basename($JSPath)?>"></script>
|
$alertbox_log_parser = alertbox_log_parser($_SESSION);
|
||||||
<script>
|
$alerts = [];
|
||||||
<?php
|
if (is_array($alertbox_log_parser)) {
|
||||||
$lang_footer = json_encode($lang['footer']);
|
foreach ($alertbox_log_parser as $log) {
|
||||||
$lang_acl = json_encode($lang['acl']);
|
$alert[$log['type']][] = $log['msg'];
|
||||||
$lang_tfa = json_encode($lang['tfa']);
|
|
||||||
$lang_fido2 = json_encode($lang['fido2']);
|
|
||||||
echo "var lang_footer = ". $lang_footer . ";\n";
|
|
||||||
echo "var lang_acl = ". $lang_acl . ";\n";
|
|
||||||
echo "var lang_tfa = ". $lang_tfa . ";\n";
|
|
||||||
echo "var lang_fido2 = ". $lang_fido2 . ";\n";
|
|
||||||
echo "var docker_timeout = ". $DOCKER_TIMEOUT * 1000 . ";\n";
|
|
||||||
?>
|
|
||||||
$(window).scroll(function() {
|
|
||||||
sessionStorage.scrollTop = $(this).scrollTop();
|
|
||||||
});
|
|
||||||
// Select language and reopen active URL without POST
|
|
||||||
function setLang(sel) {
|
|
||||||
$.post( "<?= $_SERVER['REQUEST_URI']; ?>", {lang: sel} );
|
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
|
||||||
}
|
|
||||||
// FIDO2 functions
|
|
||||||
function arrayBufferToBase64(buffer) {
|
|
||||||
let binary = '';
|
|
||||||
let bytes = new Uint8Array(buffer);
|
|
||||||
let len = bytes.byteLength;
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
binary += String.fromCharCode( bytes[ i ] );
|
|
||||||
}
|
}
|
||||||
return window.btoa(binary);
|
$alert = array_filter(array_unique($alerts));
|
||||||
}
|
foreach($alert as $alert_type => $alert_msg) {
|
||||||
function recursiveBase64StrToArrayBuffer(obj) {
|
$alerts[$alert_type] = json_encode(implode('<hr class="alert-hr">', $alert_msg));
|
||||||
let prefix = '=?BINARY?B?';
|
|
||||||
let suffix = '?=';
|
|
||||||
if (typeof obj === 'object') {
|
|
||||||
for (let key in obj) {
|
|
||||||
if (typeof obj[key] === 'string') {
|
|
||||||
let str = obj[key];
|
|
||||||
if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) {
|
|
||||||
str = str.substring(prefix.length, str.length - suffix.length);
|
|
||||||
let binary_string = window.atob(str);
|
|
||||||
let len = binary_string.length;
|
|
||||||
let bytes = new Uint8Array(len);
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
bytes[i] = binary_string.charCodeAt(i);
|
|
||||||
}
|
|
||||||
obj[key] = bytes.buffer;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
recursiveBase64StrToArrayBuffer(obj[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$(window).load(function() {
|
|
||||||
$(".overlay").hide();
|
|
||||||
});
|
|
||||||
$(document).ready(function() {
|
|
||||||
$(document).on('shown.bs.modal', function(e) {
|
|
||||||
modal_id = $(e.relatedTarget).data('target');
|
|
||||||
$(modal_id).attr("aria-hidden","false");
|
|
||||||
});
|
|
||||||
// TFA, CSRF, Alerts in footer.inc.php
|
|
||||||
// Other general functions in mailcow.js
|
|
||||||
<?php
|
|
||||||
$alertbox_log_parser = alertbox_log_parser($_SESSION);
|
|
||||||
if (is_array($alertbox_log_parser)) {
|
|
||||||
foreach($alertbox_log_parser as $log) {
|
|
||||||
$alerts[$log['type']][] = $log['msg'];
|
|
||||||
}
|
|
||||||
$alerts = array_filter(array_unique($alerts));
|
|
||||||
foreach($alerts as $alert_type => $alert_msg) {
|
|
||||||
?>
|
|
||||||
mailcow_alert_box(<?=json_encode(implode('<hr class="alert-hr">', $alert_msg));?>, <?=$alert_type;?>);
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
unset($_SESSION['return']);
|
unset($_SESSION['return']);
|
||||||
}
|
}
|
||||||
?>
|
|
||||||
// Confirm TFA modal
|
|
||||||
<?php if (isset($_SESSION['pending_tfa_method'])):?>
|
|
||||||
$('#ConfirmTFAModal').modal({
|
|
||||||
backdrop: 'static',
|
|
||||||
keyboard: false
|
|
||||||
});
|
|
||||||
$('#u2f_status_auth').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_u2f + '</p>');
|
|
||||||
$('#ConfirmTFAModal').on('shown.bs.modal', function(){
|
|
||||||
$(this).find('input[name=token]').focus();
|
|
||||||
// If U2F
|
|
||||||
if(document.getElementById("u2f_auth_data") !== null) {
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
cache: false,
|
|
||||||
dataType: 'script',
|
|
||||||
url: "/api/v1/get/u2f-authentication/<?= (isset($_SESSION['pending_mailcow_cc_username'])) ? rawurlencode($_SESSION['pending_mailcow_cc_username']) : null; ?>",
|
|
||||||
complete: function(data){
|
|
||||||
$('#u2f_status_auth').html(lang_tfa.waiting_usb_auth);
|
|
||||||
data;
|
|
||||||
setTimeout(function() {
|
|
||||||
console.log("Ready to authenticate");
|
|
||||||
u2f.sign(appId, challenge, registeredKeys, function(data) {
|
|
||||||
var form = document.getElementById('u2f_auth_form');
|
|
||||||
var auth = document.getElementById('u2f_auth_data');
|
|
||||||
console.log("Authenticate callback", data);
|
|
||||||
auth.value = JSON.stringify(data);
|
|
||||||
form.submit();
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#ConfirmTFAModal').on('hidden.bs.modal', function(){
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
cache: false,
|
|
||||||
dataType: 'script',
|
|
||||||
url: '/inc/ajax/destroy_tfa_auth.php',
|
|
||||||
complete: function(data){
|
|
||||||
window.location = window.location.href.split("#")[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
<?php endif; ?>
|
|
||||||
// Validate FIDO2
|
|
||||||
$("#fido2-login").click(function(){
|
|
||||||
$('#fido2-alerts').html();
|
|
||||||
if (!window.fetch || !navigator.credentials || !navigator.credentials.create) {
|
|
||||||
window.alert('Browser not supported.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.fetch("/api/v1/get/fido2-get-args", {method:'GET',cache:'no-cache'}).then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
}).then(function(json) {
|
|
||||||
if (json.success === false) {
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
recursiveBase64StrToArrayBuffer(json);
|
|
||||||
return json;
|
|
||||||
}).then(function(getCredentialArgs) {
|
|
||||||
return navigator.credentials.get(getCredentialArgs);
|
|
||||||
}).then(function(cred) {
|
|
||||||
return {
|
|
||||||
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
|
|
||||||
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
|
||||||
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
|
|
||||||
signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null
|
|
||||||
};
|
|
||||||
}).then(JSON.stringify).then(function(AuthenticatorAttestationResponse) {
|
|
||||||
return window.fetch("/api/v1/process/fido2-args", {method:'POST', body: AuthenticatorAttestationResponse, cache:'no-cache'});
|
|
||||||
}).then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
}).then(function(json) {
|
|
||||||
if (json.success) {
|
|
||||||
window.location = window.location.href.split("#")[0];
|
|
||||||
} else {
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
}).catch(function(err) {
|
|
||||||
if (typeof err.message === 'undefined') {
|
|
||||||
mailcow_alert_box(lang_fido2.fido2_validation_failed, "danger");
|
|
||||||
} else {
|
|
||||||
mailcow_alert_box(lang_fido2.fido2_validation_failed + ":<br><i>" + err.message + "</i>", "danger");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Set TFA/FIDO2
|
|
||||||
$("#register-fido2, #register-fido2-touchid").click(function(){
|
|
||||||
let t = $(this);
|
|
||||||
|
|
||||||
$("option:selected").prop("selected", false);
|
|
||||||
if (!window.fetch || !navigator.credentials || !navigator.credentials.create) {
|
|
||||||
window.alert('Browser not supported.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.fetch("/api/v1/get/fido2-registration/<?= (isset($_SESSION['mailcow_cc_username'])) ? rawurlencode($_SESSION['mailcow_cc_username']) : null; ?>", {method:'GET',cache:'no-cache'}).then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
}).then(function(json) {
|
|
||||||
if (json.success === false) {
|
|
||||||
throw new Error(json.msg);
|
|
||||||
}
|
|
||||||
recursiveBase64StrToArrayBuffer(json);
|
|
||||||
|
|
||||||
// set attestation to node if we are registering apple touch id
|
|
||||||
if(t.attr('id') === 'register-fido2-touchid') {
|
|
||||||
json.publicKey.attestation = 'none';
|
|
||||||
json.publicKey.authenticatorSelection.authenticatorAttachment = "platform";
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
}).then(function(createCredentialArgs) {
|
|
||||||
console.log(createCredentialArgs);
|
|
||||||
return navigator.credentials.create(createCredentialArgs);
|
|
||||||
}).then(function(cred) {
|
|
||||||
return {
|
|
||||||
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
|
||||||
attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null
|
|
||||||
};
|
|
||||||
}).then(JSON.stringify).then(function(AuthenticatorAttestationResponse) {
|
|
||||||
return window.fetch("/api/v1/add/fido2-registration", {method:'POST', body: AuthenticatorAttestationResponse, cache:'no-cache'});
|
|
||||||
}).then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
}).then(function(json) {
|
|
||||||
if (json.success) {
|
|
||||||
window.location = window.location.href.split("#")[0];
|
|
||||||
} else {
|
|
||||||
throw new Error(json.msg);
|
|
||||||
}
|
|
||||||
}).catch(function(err) {
|
|
||||||
$('#fido2-alerts').html('<span class="text-danger"><b>' + err.message + '</b></span>');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
$('#selectTFA').change(function () {
|
|
||||||
if ($(this).val() == "yubi_otp") {
|
|
||||||
$('#YubiOTPModal').modal('show');
|
|
||||||
$("option:selected").prop("selected", false);
|
|
||||||
}
|
|
||||||
if ($(this).val() == "totp") {
|
|
||||||
$('#TOTPModal').modal('show');
|
|
||||||
request_token = $('#tfa-qr-img').data('totp-secret');
|
|
||||||
$.ajax({
|
|
||||||
url: '/inc/ajax/qr_gen.php',
|
|
||||||
data: {
|
|
||||||
token: request_token,
|
|
||||||
},
|
|
||||||
}).done(function (result) {
|
|
||||||
$("#tfa-qr-img").attr("src", result);
|
|
||||||
});
|
|
||||||
$("option:selected").prop("selected", false);
|
|
||||||
}
|
|
||||||
if ($(this).val() == "u2f") {
|
|
||||||
$('#U2FModal').modal('show');
|
|
||||||
$("option:selected").prop("selected", false);
|
|
||||||
$("#start_u2f_register").click(function(){
|
|
||||||
$('#u2f_return_code').html('');
|
|
||||||
$('#u2f_return_code').hide();
|
|
||||||
$('#u2f_status_reg').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_u2f + '</p>');
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
cache: false,
|
|
||||||
dataType: 'script',
|
|
||||||
url: "/api/v1/get/u2f-registration/<?= (isset($_SESSION['mailcow_cc_username'])) ? rawurlencode($_SESSION['mailcow_cc_username']) : null; ?>",
|
|
||||||
complete: function(data){
|
|
||||||
data;
|
|
||||||
setTimeout(function() {
|
|
||||||
console.log("Ready to register");
|
|
||||||
$('#u2f_status_reg').html(lang_tfa.waiting_usb_register);
|
|
||||||
u2f.register(appId, registerRequests, registeredKeys, function(deviceResponse) {
|
|
||||||
var form = document.getElementById('u2f_reg_form');
|
|
||||||
var reg = document.getElementById('u2f_register_data');
|
|
||||||
console.log("Register callback: ", data);
|
|
||||||
if (deviceResponse.errorCode && deviceResponse.errorCode != 0) {
|
|
||||||
var u2f_return_code = document.getElementById('u2f_return_code');
|
|
||||||
u2f_return_code.style.display = u2f_return_code.style.display === 'none' ? '' : null;
|
|
||||||
if (deviceResponse.errorCode == "4") {
|
|
||||||
deviceResponse.errorCode = "4 - The presented device is not eligible for this request. For a registration request this may mean that the token is already registered, and for a sign request it may mean that the token does not know the presented key handle";
|
|
||||||
}
|
|
||||||
else if (deviceResponse.errorCode == "5") {
|
|
||||||
deviceResponse.errorCode = "5 - Timeout reached before request could be satisfied.";
|
|
||||||
}
|
|
||||||
u2f_return_code.innerHTML = lang_tfa.error_code + ': ' + deviceResponse.errorCode + ' ' + lang_tfa.reload_retry;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reg.value = JSON.stringify(deviceResponse);
|
|
||||||
form.submit();
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if ($(this).val() == "none") {
|
|
||||||
$('#DisableTFAModal').modal('show');
|
|
||||||
$("option:selected").prop("selected", false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reload after session timeout
|
$globalVariables = [
|
||||||
var session_lifetime = <?=((int)$SESSION_LIFETIME * 1000) + 15000;?>;
|
'js_path' => '/cache/'.basename($JSPath),
|
||||||
<?php
|
'pending_tfa_method' => @$_SESSION['pending_tfa_method'],
|
||||||
if (isset($_SESSION['mailcow_cc_username'])):
|
'pending_mailcow_cc_username' => @$_SESSION['pending_mailcow_cc_username'],
|
||||||
?>
|
'lang_footer' => json_encode($lang['footer']),
|
||||||
setTimeout(function() {
|
'lang_acl' => json_encode($lang['acl']),
|
||||||
location.reload();
|
'lang_tfa' => json_encode($lang['tfa']),
|
||||||
}, session_lifetime);
|
'lang_fido2' => json_encode($lang['fido2']),
|
||||||
<?php
|
'docker_timeout' => $DOCKER_TIMEOUT,
|
||||||
endif;
|
'session_lifetime' => (int)$SESSION_LIFETIME,
|
||||||
?>
|
'csrf_token' => $_SESSION['CSRF']['TOKEN'],
|
||||||
|
'pagination_size' => $PAGINATION_SIZE,
|
||||||
|
'log_pagination_size' => $LOG_PAGINATION_SIZE,
|
||||||
|
'alerts' => $alerts,
|
||||||
|
];
|
||||||
|
|
||||||
// CSRF
|
foreach ($globalVariables as $globalVariableName => $globalVariableValue) {
|
||||||
$('<input type="hidden" value="<?= $_SESSION['CSRF']['TOKEN']; ?>">').attr('name', 'csrf_token').appendTo('form');
|
$twig->addGlobal($globalVariableName, $globalVariableValue);
|
||||||
if (sessionStorage.scrollTop != "undefined") {
|
}
|
||||||
$(window).scrollTop(sessionStorage.scrollTop);
|
|
||||||
}
|
echo $twig->render($template, $template_data);
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container footer">
|
|
||||||
<?php if (!empty($UI_TEXTS['ui_footer'])) { ?>
|
|
||||||
<hr><span class="rot-enc"><?=str_rot13($UI_TEXTS['ui_footer']);?></span>
|
|
||||||
<?php } ?>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
<?php
|
|
||||||
if (isset($_SESSION['mailcow_cc_api'])) {
|
if (isset($_SESSION['mailcow_cc_api'])) {
|
||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
session_unset();
|
session_unset();
|
||||||
|
|
|
@ -234,8 +234,9 @@ function customize($_action, $_item, $_data = null) {
|
||||||
$img_data = explode('base64,', customize('get', 'main_logo'));
|
$img_data = explode('base64,', customize('get', 'main_logo'));
|
||||||
if ($img_data[1]) {
|
if ($img_data[1]) {
|
||||||
$image->readImageBlob(base64_decode($img_data[1]));
|
$image->readImageBlob(base64_decode($img_data[1]));
|
||||||
|
return $image->identifyImage();
|
||||||
}
|
}
|
||||||
return $image->identifyImage();
|
return false;
|
||||||
}
|
}
|
||||||
catch (ImagickException $e) {
|
catch (ImagickException $e) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
|
@ -249,4 +250,4 @@ function customize($_action, $_item, $_data = null) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,148 +1,55 @@
|
||||||
<!DOCTYPE html>
|
<?php
|
||||||
<html lang="<?= $_SESSION['mailcow_locale'] ?>">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
|
|
||||||
<meta name="theme-color" content="#F5D76E"/>
|
|
||||||
<meta http-equiv="Referrer-Policy" content="same-origin">
|
|
||||||
<title><?=$UI_TEXTS['title_name'];?></title>
|
|
||||||
<?php
|
|
||||||
if (preg_match("/mailbox/i", $_SERVER['REQUEST_URI'])) {
|
|
||||||
$css_minifier->add('/web/css/site/mailbox.css');
|
|
||||||
}
|
|
||||||
if (preg_match("/admin/i", $_SERVER['REQUEST_URI'])) {
|
|
||||||
$css_minifier->add('/web/css/site/admin.css');
|
|
||||||
}
|
|
||||||
if (preg_match("/user/i", $_SERVER['REQUEST_URI'])) {
|
|
||||||
$css_minifier->add('/web/css/site/user.css');
|
|
||||||
}
|
|
||||||
if (preg_match("/edit/i", $_SERVER['REQUEST_URI'])) {
|
|
||||||
$css_minifier->add('/web/css/site/edit.css');
|
|
||||||
}
|
|
||||||
if (preg_match("/(quarantine|qhandler)/i", $_SERVER['REQUEST_URI'])) {
|
|
||||||
$css_minifier->add('/web/css/site/quarantine.css');
|
|
||||||
}
|
|
||||||
if (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) {
|
|
||||||
$css_minifier->add('/web/css/site/debug.css');
|
|
||||||
}
|
|
||||||
if ($_SERVER['REQUEST_URI'] == '/') {
|
|
||||||
$css_minifier->add('/web/css/site/index.css');
|
|
||||||
}
|
|
||||||
|
|
||||||
$hash = $css_minifier->getDataHash();
|
// CSS
|
||||||
$CSSPath = '/tmp/' . $hash . '.css';
|
if (preg_match("/mailbox/i", $_SERVER['REQUEST_URI'])) {
|
||||||
if(!file_exists($CSSPath)) {
|
$css_minifier->add('/web/css/site/mailbox.css');
|
||||||
$css_minifier->minify($CSSPath);
|
}
|
||||||
cleanupCSS($hash);
|
if (preg_match("/admin/i", $_SERVER['REQUEST_URI'])) {
|
||||||
}
|
$css_minifier->add('/web/css/site/admin.css');
|
||||||
?>
|
}
|
||||||
<link rel="stylesheet" href="/cache/<?=basename($CSSPath)?>">
|
if (preg_match("/user/i", $_SERVER['REQUEST_URI'])) {
|
||||||
<?php if (strtolower(trim($DEFAULT_THEME)) != "lumen") { ?>
|
$css_minifier->add('/web/css/site/user.css');
|
||||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/<?= strtolower(trim($DEFAULT_THEME)); ?>/bootstrap.min.css">
|
}
|
||||||
<?php } ?>
|
if (preg_match("/edit/i", $_SERVER['REQUEST_URI'])) {
|
||||||
<link rel="shortcut icon" href="/favicon.png" type="image/png">
|
$css_minifier->add('/web/css/site/edit.css');
|
||||||
<link rel="icon" href="/favicon.png" type="image/png">
|
}
|
||||||
</head>
|
if (preg_match("/(quarantine|qhandler)/i", $_SERVER['REQUEST_URI'])) {
|
||||||
<body id="top">
|
$css_minifier->add('/web/css/site/quarantine.css');
|
||||||
<div class="overlay"></div>
|
}
|
||||||
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
|
if (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) {
|
||||||
<div class="container-fluid">
|
$css_minifier->add('/web/css/site/debug.css');
|
||||||
<div class="navbar-header">
|
}
|
||||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
if ($_SERVER['REQUEST_URI'] == '/') {
|
||||||
<span class="icon-bar"></span>
|
$css_minifier->add('/web/css/site/index.css');
|
||||||
<span class="icon-bar"></span>
|
}
|
||||||
<span class="icon-bar"></span>
|
|
||||||
</button>
|
$hash = $css_minifier->getDataHash();
|
||||||
<a class="navbar-brand" href="/"><img alt="mailcow-logo" src="<?=($main_logo = customize('get', 'main_logo')) ? $main_logo : '/img/cow_mailcow.svg';?>"></a>
|
$CSSPath = '/tmp/' . $hash . '.css';
|
||||||
</div>
|
if(!file_exists($CSSPath)) {
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
$css_minifier->minify($CSSPath);
|
||||||
<ul class="nav navbar-nav navbar-right">
|
cleanupCSS($hash);
|
||||||
<?php
|
}
|
||||||
if (isset($_SESSION['mailcow_locale'])) {
|
|
||||||
?>
|
$globalVariables = [
|
||||||
<li class="dropdown<?=(isset($_SESSION['mailcow_locale']) && count($AVAILABLE_LANGUAGES) === 1) ? ' lang-link-disabled"' : '' ?>">
|
'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'),
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="flag-icon flag-icon-<?= $_SESSION['mailcow_locale']; ?>"></span><span class="caret"></span></a>
|
'mailcow_locale' => @$_SESSION['mailcow_locale'],
|
||||||
<ul class="dropdown-menu" role="menu">
|
'mailcow_cc_role' => @$_SESSION['mailcow_cc_role'],
|
||||||
<?php
|
'mailcow_cc_username' => @$_SESSION['mailcow_cc_username'],
|
||||||
foreach ($AVAILABLE_LANGUAGES as $c => $v) {
|
'is_master' => preg_match('/y|yes/i', getenv('MASTER')),
|
||||||
?>
|
'dual_login' => @$_SESSION['dual-login'],
|
||||||
<li<?= ($_SESSION['mailcow_locale'] == $c) ? ' class="active"' : ''; ?>><a href="?<?= http_build_query(array_merge($_GET, array('lang' => $c))); ?>"><span class="flag-icon flag-icon-<?=$c;?>"></span> <?=$v;?></a></li>
|
'ui_texts' => $UI_TEXTS,
|
||||||
<?php
|
'css_path' => '/cache/'.basename($CSSPath),
|
||||||
}
|
'theme' => strtolower(trim($DEFAULT_THEME)),
|
||||||
?>
|
'logo' => customize('get', 'main_logo'),
|
||||||
</ul>
|
'available_languages' => $AVAILABLE_LANGUAGES,
|
||||||
</li>
|
'lang' => $lang,
|
||||||
<?php
|
'skip_sogo' => (getenv('SKIP_SOGO') == 'y'),
|
||||||
}
|
'mailcow_apps' => $MAILCOW_APPS,
|
||||||
if (isset($_SESSION['mailcow_cc_role'])) {
|
'app_links' => customize('get', 'app_links'),
|
||||||
?>
|
'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'),
|
||||||
<li class="dropdown">
|
'uri' => $_SERVER['REQUEST_URI'],
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><?= $lang['header']['mailcow_settings']; ?> <span class="caret"></span></a>
|
];
|
||||||
<ul class="dropdown-menu" role="menu">
|
|
||||||
<?php
|
foreach ($globalVariables as $globalVariableName => $globalVariableValue) {
|
||||||
if (isset($_SESSION['mailcow_cc_role'])) {
|
$twig->addGlobal($globalVariableName, $globalVariableValue);
|
||||||
if ($_SESSION['mailcow_cc_role'] == 'admin') {
|
}
|
||||||
?>
|
|
||||||
<li<?= (preg_match("/admin/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/admin"><?= $lang['header']['administration']; ?></a></li>
|
|
||||||
<li<?= (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/debug"><?= $lang['header']['debug']; ?></a></li>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
if ($_SESSION['mailcow_cc_role'] == 'admin' || $_SESSION['mailcow_cc_role'] == 'domainadmin') {
|
|
||||||
?>
|
|
||||||
<li<?= (preg_match("/mailbox/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/mailbox"><?= $lang['header']['mailboxes']; ?></a></li>
|
|
||||||
<?php } if ($_SESSION['mailcow_cc_role'] != 'admin') { ?>
|
|
||||||
<li<?= (preg_match("/user/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/user"><?= $lang['header']['user_settings']; ?></a></li>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<?php if (isset($_SESSION['mailcow_cc_role'])) { ?>
|
|
||||||
<li<?= (preg_match("/quarantine/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/quarantine"><i class="bi bi-inbox-fill"></i> <?= $lang['header']['quarantine']; ?></a></li>
|
|
||||||
<?php } if ($_SESSION['mailcow_cc_role'] == 'admin' && getenv('SKIP_SOGO') != "y") { ?>
|
|
||||||
<li><a href data-toggle="modal" data-container="sogo-mailcow" data-target="#RestartContainer"><i class="bi bi-arrow-repeat"></i> <?= $lang['header']['restart_sogo']; ?></a></li>
|
|
||||||
<?php } ?>
|
|
||||||
<li class="dropdown">
|
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="bi bi-link-45deg"></i> <?=$UI_TEXTS['apps_name'];?> <span class="caret"></span></a>
|
|
||||||
<ul class="dropdown-menu" role="menu">
|
|
||||||
<?php foreach ($MAILCOW_APPS as $app) {
|
|
||||||
if (getenv('SKIP_SOGO') == "y" && preg_match('/^\/SOGo/i', $app['link'])) { continue; }
|
|
||||||
?>
|
|
||||||
<li title="<?=(isset($app['description'])) ? htmlspecialchars($app['description']) : '';?>"><a href="<?=(isset($app['link'])) ? htmlspecialchars($app['link']) : '';?>"><?=(isset($app['name'])) ? htmlspecialchars($app['name']) : '';?></a></li>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
$app_links = customize('get', 'app_links');
|
|
||||||
if ($app_links) {
|
|
||||||
foreach ($app_links as $row) {
|
|
||||||
foreach ($row as $key => $val) {
|
|
||||||
?>
|
|
||||||
<li><a href="<?= htmlspecialchars($val); ?>"><?= htmlspecialchars($key); ?></a></li>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<?php } if (!isset($_SESSION['dual-login']) && isset($_SESSION['mailcow_cc_username'])) { ?>
|
|
||||||
<li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?></b> <i class="bi bi-power"></i></a></li>
|
|
||||||
<?php } elseif (isset($_SESSION['dual-login'])) { ?>
|
|
||||||
<li class="logged-in-as"><a href="#" onclick="logout.submit()"><b class="username-lia"><?= htmlspecialchars($_SESSION['mailcow_cc_username']); ?> <span class="text-info">(<?= htmlspecialchars($_SESSION['dual-login']['username']); ?>)</span> </b><i class="bi bi-power"></i></a></li>
|
|
||||||
<?php } if (!preg_match('/y|yes/i', getenv('MASTER'))) { ?>
|
|
||||||
<li class="text-warning slave-info">[ slave ]</li>
|
|
||||||
<?php } ?>
|
|
||||||
</ul>
|
|
||||||
</div><!--/.nav-collapse -->
|
|
||||||
</div><!--/.container-fluid -->
|
|
||||||
</nav>
|
|
||||||
<form action="/" method="post" id="logout"><input type="hidden" name="logout"></form>
|
|
||||||
<?php if (!empty($UI_TEXTS['ui_announcement_text']) &&
|
|
||||||
in_array($UI_TEXTS['ui_announcement_type'], array('info', 'warning', 'danger')) &&
|
|
||||||
$UI_TEXTS['ui_announcement_active'] == 1 &&
|
|
||||||
parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) != '/') { ?>
|
|
||||||
<div class="container">
|
|
||||||
<div class="alert alert-<?=$UI_TEXTS['ui_announcement_type'];?>"><?=$UI_TEXTS['ui_announcement_text'];?></div>
|
|
||||||
</div>
|
|
||||||
<?php } ?>
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"matthiasmullie/minify": "^1.3",
|
"matthiasmullie/minify": "^1.3",
|
||||||
"bshaffer/oauth2-server-php": "^1.11",
|
"bshaffer/oauth2-server-php": "^1.11",
|
||||||
"mustangostang/spyc": "^0.6.3",
|
"mustangostang/spyc": "^0.6.3",
|
||||||
"directorytree/ldaprecord": "^2.4"
|
"directorytree/ldaprecord": "^2.4",
|
||||||
|
"twig/twig": "^3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "50acd623ff29bb513cd29819f4537aa0",
|
"content-hash": "139c1e5dec323144cd778ce80fd1847e",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "bshaffer/oauth2-server-php",
|
"name": "bshaffer/oauth2-server-php",
|
||||||
|
@ -62,10 +62,6 @@
|
||||||
"oauth",
|
"oauth",
|
||||||
"oauth2"
|
"oauth2"
|
||||||
],
|
],
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/bshaffer/oauth2-server-php/issues",
|
|
||||||
"source": "https://github.com/bshaffer/oauth2-server-php/tree/master"
|
|
||||||
},
|
|
||||||
"time": "2018-12-04T00:29:32+00:00"
|
"time": "2018-12-04T00:29:32+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -388,10 +384,6 @@
|
||||||
"paths",
|
"paths",
|
||||||
"relative"
|
"relative"
|
||||||
],
|
],
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/matthiasmullie/path-converter/issues",
|
|
||||||
"source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3"
|
|
||||||
},
|
|
||||||
"time": "2019-02-05T23:41:09+00:00"
|
"time": "2019-02-05T23:41:09+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1022,11 +1014,6 @@
|
||||||
"php",
|
"php",
|
||||||
"text"
|
"text"
|
||||||
],
|
],
|
||||||
"support": {
|
|
||||||
"email": "support@jevon.org",
|
|
||||||
"issues": "https://github.com/soundasleep/html2text/issues",
|
|
||||||
"source": "https://github.com/soundasleep/html2text/tree/master"
|
|
||||||
},
|
|
||||||
"time": "2017-04-19T22:01:50+00:00"
|
"time": "2017-04-19T22:01:50+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1096,6 +1083,85 @@
|
||||||
],
|
],
|
||||||
"time": "2021-03-23T23:28:01+00:00"
|
"time": "2021-03-23T23:28:01+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-ctype",
|
||||||
|
"version": "v1.23.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
|
||||||
|
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-ctype": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.23-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Ctype\\": ""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gert de Pagter",
|
||||||
|
"email": "BackEndTea@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for ctype functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"ctype",
|
||||||
|
"polyfill",
|
||||||
|
"portable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-02-19T12:13:01+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.23.1",
|
"version": "v1.23.1",
|
||||||
|
@ -1574,6 +1640,82 @@
|
||||||
},
|
},
|
||||||
"time": "2021-03-29T21:29:00+00:00"
|
"time": "2021-03-29T21:29:00+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "twig/twig",
|
||||||
|
"version": "v3.3.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/twigphp/Twig.git",
|
||||||
|
"reference": "21578f00e83d4a82ecfa3d50752b609f13de6790"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/twigphp/Twig/zipball/21578f00e83d4a82ecfa3d50752b609f13de6790",
|
||||||
|
"reference": "21578f00e83d4a82ecfa3d50752b609f13de6790",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/polyfill-ctype": "^1.8",
|
||||||
|
"symfony/polyfill-mbstring": "^1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"psr/container": "^1.0",
|
||||||
|
"symfony/phpunit-bridge": "^4.4.9|^5.0.9"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.3-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Twig\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com",
|
||||||
|
"homepage": "http://fabien.potencier.org",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Twig Team",
|
||||||
|
"role": "Contributors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Armin Ronacher",
|
||||||
|
"email": "armin.ronacher@active-4.com",
|
||||||
|
"role": "Project Founder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||||
|
"homepage": "https://twig.symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"templating"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/twigphp/Twig/issues",
|
||||||
|
"source": "https://github.com/twigphp/Twig/tree/v3.3.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-05-16T12:14:13+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "yubico/u2flib-server",
|
"name": "yubico/u2flib-server",
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -1609,10 +1751,6 @@
|
||||||
],
|
],
|
||||||
"description": "Library for U2F implementation",
|
"description": "Library for U2F implementation",
|
||||||
"homepage": "https://developers.yubico.com/php-u2flib-server",
|
"homepage": "https://developers.yubico.com/php-u2flib-server",
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/Yubico/php-u2flib-server/issues",
|
|
||||||
"source": "https://github.com/Yubico/php-u2flib-server/tree/1.0.2"
|
|
||||||
},
|
|
||||||
"time": "2018-09-07T08:16:44+00:00"
|
"time": "2018-09-07T08:16:44+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -6,10 +6,11 @@ $vendorDir = dirname(dirname(__FILE__));
|
||||||
$baseDir = dirname($vendorDir);
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
|
||||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||||
|
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
||||||
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
|
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
|
||||||
|
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
|
||||||
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
|
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
|
||||||
'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php',
|
'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php',
|
||||||
'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php',
|
'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php',
|
||||||
|
|
|
@ -6,9 +6,11 @@ $vendorDir = dirname(dirname(__FILE__));
|
||||||
$baseDir = dirname($vendorDir);
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
|
'Twig\\' => array($vendorDir . '/twig/twig/src'),
|
||||||
'Tightenco\\Collect\\' => array($vendorDir . '/tightenco/collect/src/Collect'),
|
'Tightenco\\Collect\\' => array($vendorDir . '/tightenco/collect/src/Collect'),
|
||||||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
||||||
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
||||||
|
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
|
||||||
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
|
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
|
||||||
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
|
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
|
||||||
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
|
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
|
||||||
|
|
|
@ -7,10 +7,11 @@ namespace Composer\Autoload;
|
||||||
class ComposerStaticInit873464e4bd965a3168f133248b1b218b
|
class ComposerStaticInit873464e4bd965a3168f133248b1b218b
|
||||||
{
|
{
|
||||||
public static $files = array (
|
public static $files = array (
|
||||||
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
|
||||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||||
|
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
||||||
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
|
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
|
||||||
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
|
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
|
||||||
|
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
|
||||||
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
|
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
|
||||||
'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php',
|
'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php',
|
||||||
'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php',
|
'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php',
|
||||||
|
@ -20,12 +21,14 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
|
||||||
public static $prefixLengthsPsr4 = array (
|
public static $prefixLengthsPsr4 = array (
|
||||||
'T' =>
|
'T' =>
|
||||||
array (
|
array (
|
||||||
|
'Twig\\' => 5,
|
||||||
'Tightenco\\Collect\\' => 18,
|
'Tightenco\\Collect\\' => 18,
|
||||||
),
|
),
|
||||||
'S' =>
|
'S' =>
|
||||||
array (
|
array (
|
||||||
'Symfony\\Polyfill\\Php80\\' => 23,
|
'Symfony\\Polyfill\\Php80\\' => 23,
|
||||||
'Symfony\\Polyfill\\Mbstring\\' => 26,
|
'Symfony\\Polyfill\\Mbstring\\' => 26,
|
||||||
|
'Symfony\\Polyfill\\Ctype\\' => 23,
|
||||||
'Symfony\\Contracts\\Translation\\' => 30,
|
'Symfony\\Contracts\\Translation\\' => 30,
|
||||||
'Symfony\\Component\\VarDumper\\' => 28,
|
'Symfony\\Component\\VarDumper\\' => 28,
|
||||||
'Symfony\\Component\\Translation\\' => 30,
|
'Symfony\\Component\\Translation\\' => 30,
|
||||||
|
@ -70,6 +73,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
|
||||||
);
|
);
|
||||||
|
|
||||||
public static $prefixDirsPsr4 = array (
|
public static $prefixDirsPsr4 = array (
|
||||||
|
'Twig\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/twig/twig/src',
|
||||||
|
),
|
||||||
'Tightenco\\Collect\\' =>
|
'Tightenco\\Collect\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect',
|
0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect',
|
||||||
|
@ -82,6 +89,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
|
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
|
||||||
),
|
),
|
||||||
|
'Symfony\\Polyfill\\Ctype\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
|
||||||
|
),
|
||||||
'Symfony\\Contracts\\Translation\\' =>
|
'Symfony\\Contracts\\Translation\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/symfony/translation-contracts',
|
0 => __DIR__ . '/..' . '/symfony/translation-contracts',
|
||||||
|
|
|
@ -1128,6 +1128,88 @@
|
||||||
],
|
],
|
||||||
"install-path": "../symfony/deprecation-contracts"
|
"install-path": "../symfony/deprecation-contracts"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-ctype",
|
||||||
|
"version": "v1.23.0",
|
||||||
|
"version_normalized": "1.23.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
|
||||||
|
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-ctype": "For best performance"
|
||||||
|
},
|
||||||
|
"time": "2021-02-19T12:13:01+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.23-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Ctype\\": ""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gert de Pagter",
|
||||||
|
"email": "BackEndTea@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for ctype functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"ctype",
|
||||||
|
"polyfill",
|
||||||
|
"portable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../symfony/polyfill-ctype"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.23.1",
|
"version": "v1.23.1",
|
||||||
|
@ -1624,6 +1706,85 @@
|
||||||
},
|
},
|
||||||
"install-path": "../tightenco/collect"
|
"install-path": "../tightenco/collect"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "twig/twig",
|
||||||
|
"version": "v3.3.2",
|
||||||
|
"version_normalized": "3.3.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/twigphp/Twig.git",
|
||||||
|
"reference": "21578f00e83d4a82ecfa3d50752b609f13de6790"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/twigphp/Twig/zipball/21578f00e83d4a82ecfa3d50752b609f13de6790",
|
||||||
|
"reference": "21578f00e83d4a82ecfa3d50752b609f13de6790",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/polyfill-ctype": "^1.8",
|
||||||
|
"symfony/polyfill-mbstring": "^1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"psr/container": "^1.0",
|
||||||
|
"symfony/phpunit-bridge": "^4.4.9|^5.0.9"
|
||||||
|
},
|
||||||
|
"time": "2021-05-16T12:14:13+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.3-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Twig\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com",
|
||||||
|
"homepage": "http://fabien.potencier.org",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Twig Team",
|
||||||
|
"role": "Contributors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Armin Ronacher",
|
||||||
|
"email": "armin.ronacher@active-4.com",
|
||||||
|
"role": "Project Founder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||||
|
"homepage": "https://twig.symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"templating"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/twigphp/Twig/issues",
|
||||||
|
"source": "https://github.com/twigphp/Twig/tree/v3.3.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../twig/twig"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "yubico/u2flib-server",
|
"name": "yubico/u2flib-server",
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
<?php return array(
|
<?php return array(
|
||||||
'root' => array(
|
'root' => array(
|
||||||
'pretty_version' => '1.0.0+no-version-set',
|
'pretty_version' => 'dev-master',
|
||||||
'version' => '1.0.0.0',
|
'version' => 'dev-master',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'reference' => NULL,
|
'reference' => '1c2923a4ddd7f89b3cf38c9594db289b7dd756d3',
|
||||||
'name' => '__root__',
|
'name' => '__root__',
|
||||||
'dev' => true,
|
'dev' => true,
|
||||||
),
|
),
|
||||||
'versions' => array(
|
'versions' => array(
|
||||||
'__root__' => array(
|
'__root__' => array(
|
||||||
'pretty_version' => '1.0.0+no-version-set',
|
'pretty_version' => 'dev-master',
|
||||||
'version' => '1.0.0.0',
|
'version' => 'dev-master',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'reference' => NULL,
|
'reference' => '1c2923a4ddd7f89b3cf38c9594db289b7dd756d3',
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
'bshaffer/oauth2-server-php' => array(
|
'bshaffer/oauth2-server-php' => array(
|
||||||
|
@ -184,6 +184,15 @@
|
||||||
'reference' => '5f38c8804a9e97d23e0c8d63341088cd8a22d627',
|
'reference' => '5f38c8804a9e97d23e0c8d63341088cd8a22d627',
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'symfony/polyfill-ctype' => array(
|
||||||
|
'pretty_version' => 'v1.23.0',
|
||||||
|
'version' => '1.23.0.0',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
|
||||||
|
'aliases' => array(),
|
||||||
|
'reference' => '46cd95797e9df938fdd2b03693b5fca5e64b01ce',
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'symfony/polyfill-mbstring' => array(
|
'symfony/polyfill-mbstring' => array(
|
||||||
'pretty_version' => 'v1.23.1',
|
'pretty_version' => 'v1.23.1',
|
||||||
'version' => '1.23.1.0',
|
'version' => '1.23.1.0',
|
||||||
|
@ -244,6 +253,15 @@
|
||||||
'reference' => 'b069783ab0c547bb894ebcf8e7f6024bb401f9d2',
|
'reference' => 'b069783ab0c547bb894ebcf8e7f6024bb401f9d2',
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'twig/twig' => array(
|
||||||
|
'pretty_version' => 'v3.3.2',
|
||||||
|
'version' => '3.3.2.0',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../twig/twig',
|
||||||
|
'aliases' => array(),
|
||||||
|
'reference' => '21578f00e83d4a82ecfa3d50752b609f13de6790',
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'yubico/u2flib-server' => array(
|
'yubico/u2flib-server' => array(
|
||||||
'pretty_version' => '1.0.2',
|
'pretty_version' => '1.0.2',
|
||||||
'version' => '1.0.2.0',
|
'version' => '1.0.2.0',
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Polyfill\Ctype;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ctype implementation through regex.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @author Gert de Pagter <BackEndTea@gmail.com>
|
||||||
|
*/
|
||||||
|
final class Ctype
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-alnum
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_alnum($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text is a letter, FALSE otherwise.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-alpha
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_alpha($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-cntrl
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_cntrl($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-digit
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_digit($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-graph
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_graph($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text is a lowercase letter.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-lower
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_lower($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-print
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_print($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-punct
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_punct($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-space
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_space($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text is an uppercase letter.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-upper
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_upper($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
|
||||||
|
*
|
||||||
|
* @see https://php.net/ctype-xdigit
|
||||||
|
*
|
||||||
|
* @param string|int $text
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function ctype_xdigit($text)
|
||||||
|
{
|
||||||
|
$text = self::convert_int_to_char_for_ctype($text);
|
||||||
|
|
||||||
|
return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts integers to their char versions according to normal ctype behaviour, if needed.
|
||||||
|
*
|
||||||
|
* If an integer between -128 and 255 inclusive is provided,
|
||||||
|
* it is interpreted as the ASCII value of a single character
|
||||||
|
* (negative values have 256 added in order to allow characters in the Extended ASCII range).
|
||||||
|
* Any other integer is interpreted as a string containing the decimal digits of the integer.
|
||||||
|
*
|
||||||
|
* @param string|int $int
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private static function convert_int_to_char_for_ctype($int)
|
||||||
|
{
|
||||||
|
if (!\is_int($int)) {
|
||||||
|
return $int;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($int < -128 || $int > 255) {
|
||||||
|
return (string) $int;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($int < 0) {
|
||||||
|
$int += 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
return \chr($int);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2018-2019 Fabien Potencier
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,12 @@
|
||||||
|
Symfony Polyfill / Ctype
|
||||||
|
========================
|
||||||
|
|
||||||
|
This component provides `ctype_*` functions to users who run php versions without the ctype extension.
|
||||||
|
|
||||||
|
More information can be found in the
|
||||||
|
[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
This library is released under the [MIT license](LICENSE).
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Symfony\Polyfill\Ctype as p;
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 80000) {
|
||||||
|
return require __DIR__.'/bootstrap80.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('ctype_alnum')) {
|
||||||
|
function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_alpha')) {
|
||||||
|
function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_cntrl')) {
|
||||||
|
function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_digit')) {
|
||||||
|
function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_graph')) {
|
||||||
|
function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_lower')) {
|
||||||
|
function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_print')) {
|
||||||
|
function ctype_print($text) { return p\Ctype::ctype_print($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_punct')) {
|
||||||
|
function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_space')) {
|
||||||
|
function ctype_space($text) { return p\Ctype::ctype_space($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_upper')) {
|
||||||
|
function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_xdigit')) {
|
||||||
|
function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Symfony\Polyfill\Ctype as p;
|
||||||
|
|
||||||
|
if (!function_exists('ctype_alnum')) {
|
||||||
|
function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_alpha')) {
|
||||||
|
function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_cntrl')) {
|
||||||
|
function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_digit')) {
|
||||||
|
function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_graph')) {
|
||||||
|
function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_lower')) {
|
||||||
|
function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_print')) {
|
||||||
|
function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_punct')) {
|
||||||
|
function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_space')) {
|
||||||
|
function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_upper')) {
|
||||||
|
function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); }
|
||||||
|
}
|
||||||
|
if (!function_exists('ctype_xdigit')) {
|
||||||
|
function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); }
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-ctype",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Symfony polyfill for ctype functions",
|
||||||
|
"keywords": ["polyfill", "compatibility", "portable", "ctype"],
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gert de Pagter",
|
||||||
|
"email": "BackEndTea@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": { "Symfony\\Polyfill\\Ctype\\": "" },
|
||||||
|
"files": [ "bootstrap.php" ]
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-ctype": "For best performance"
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.23-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
; top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
; Unix-style newlines
|
||||||
|
[*]
|
||||||
|
end_of_line = LF
|
||||||
|
|
||||||
|
[*.php]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.test]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.rst]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
|
@ -0,0 +1,3 @@
|
||||||
|
/extra/** export-ignore
|
||||||
|
/tests export-ignore
|
||||||
|
/phpunit.xml.dist export-ignore
|
|
@ -0,0 +1,173 @@
|
||||||
|
name: "CI"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '3.x'
|
||||||
|
|
||||||
|
env:
|
||||||
|
SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
name: "PHP ${{ matrix.php-version }}"
|
||||||
|
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version:
|
||||||
|
- '7.2.5'
|
||||||
|
- '7.3'
|
||||||
|
- '7.4'
|
||||||
|
- '8.0'
|
||||||
|
composer-options: ['']
|
||||||
|
experimental: [false]
|
||||||
|
include:
|
||||||
|
- { php-version: '8.1', experimental: true, composer-options: '--ignore-platform-req=php' }
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v2.3.3
|
||||||
|
|
||||||
|
- name: "Install PHP with extensions"
|
||||||
|
uses: shivammathur/setup-php@2.7.0
|
||||||
|
with:
|
||||||
|
coverage: "none"
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
ini-values: memory_limit=-1
|
||||||
|
tools: composer:v2
|
||||||
|
|
||||||
|
- name: "Add PHPUnit matcher"
|
||||||
|
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||||
|
|
||||||
|
- name: "Set composer cache directory"
|
||||||
|
id: composer-cache
|
||||||
|
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||||
|
|
||||||
|
- name: "Cache composer"
|
||||||
|
uses: actions/cache@v2.1.2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}
|
||||||
|
restore-keys: ${{ runner.os }}-${{ matrix.php-version }}-composer-
|
||||||
|
|
||||||
|
- run: composer install ${{ matrix.composer-options }}
|
||||||
|
|
||||||
|
- name: "Install PHPUnit"
|
||||||
|
run: vendor/bin/simple-phpunit install
|
||||||
|
|
||||||
|
- name: "PHPUnit version"
|
||||||
|
run: vendor/bin/simple-phpunit --version
|
||||||
|
|
||||||
|
- name: "Run tests"
|
||||||
|
run: vendor/bin/simple-phpunit
|
||||||
|
|
||||||
|
extension-tests:
|
||||||
|
needs:
|
||||||
|
- 'tests'
|
||||||
|
|
||||||
|
name: "${{ matrix.extension }} with PHP ${{ matrix.php-version }}"
|
||||||
|
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version:
|
||||||
|
- '7.2.5'
|
||||||
|
- '7.3'
|
||||||
|
- '7.4'
|
||||||
|
- '8.0'
|
||||||
|
extension:
|
||||||
|
- 'extra/cache-extra'
|
||||||
|
- 'extra/cssinliner-extra'
|
||||||
|
- 'extra/html-extra'
|
||||||
|
- 'extra/inky-extra'
|
||||||
|
- 'extra/intl-extra'
|
||||||
|
- 'extra/markdown-extra'
|
||||||
|
- 'extra/string-extra'
|
||||||
|
- 'extra/twig-extra-bundle'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v2.3.3
|
||||||
|
|
||||||
|
- name: "Install PHP with extensions"
|
||||||
|
uses: shivammathur/setup-php@2.7.0
|
||||||
|
with:
|
||||||
|
coverage: "none"
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
ini-values: memory_limit=-1
|
||||||
|
tools: composer:v2
|
||||||
|
|
||||||
|
- name: "Add PHPUnit matcher"
|
||||||
|
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||||
|
|
||||||
|
- name: "Set composer cache directory"
|
||||||
|
id: composer-cache
|
||||||
|
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||||
|
|
||||||
|
- name: "Cache composer"
|
||||||
|
uses: actions/cache@v2.1.2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-${{ matrix.php-version }}-${{ matrix.extension }}-${{ hashFiles('composer.json') }}
|
||||||
|
restore-keys: ${{ runner.os }}-${{ matrix.php-version }}-${{ matrix.extension }}-
|
||||||
|
|
||||||
|
- run: composer install
|
||||||
|
|
||||||
|
- name: "Install PHPUnit"
|
||||||
|
run: vendor/bin/simple-phpunit install
|
||||||
|
|
||||||
|
- name: "PHPUnit version"
|
||||||
|
run: vendor/bin/simple-phpunit --version
|
||||||
|
|
||||||
|
- if: matrix.extension == 'extra/markdown-extra' && matrix.php-version == '8.0'
|
||||||
|
working-directory: ${{ matrix.extension}}
|
||||||
|
run: composer config platform.php 7.4.99
|
||||||
|
|
||||||
|
- name: "Composer install"
|
||||||
|
working-directory: ${{ matrix.extension}}
|
||||||
|
run: composer install
|
||||||
|
|
||||||
|
- name: "Run tests"
|
||||||
|
working-directory: ${{ matrix.extension}}
|
||||||
|
run: ../../vendor/bin/simple-phpunit
|
||||||
|
#
|
||||||
|
# Drupal does not support Twig 3 now!
|
||||||
|
#
|
||||||
|
# integration-tests:
|
||||||
|
# needs:
|
||||||
|
# - 'tests'
|
||||||
|
#
|
||||||
|
# name: "Integration tests with PHP ${{ matrix.php-version }}"
|
||||||
|
#
|
||||||
|
# runs-on: 'ubuntu-20.04'
|
||||||
|
#
|
||||||
|
# continue-on-error: true
|
||||||
|
#
|
||||||
|
# strategy:
|
||||||
|
# matrix:
|
||||||
|
# php-version:
|
||||||
|
# - '7.3'
|
||||||
|
#
|
||||||
|
# steps:
|
||||||
|
# - name: "Checkout code"
|
||||||
|
# uses: actions/checkout@v2.3.3
|
||||||
|
#
|
||||||
|
# - name: "Install PHP with extensions"
|
||||||
|
# uses: shivammathur/setup-php@2.7.0
|
||||||
|
# with:
|
||||||
|
# coverage: "none"
|
||||||
|
# extensions: "gd, pdo_sqlite"
|
||||||
|
# php-version: ${{ matrix.php-version }}
|
||||||
|
# ini-values: memory_limit=-1
|
||||||
|
# tools: composer:v2
|
||||||
|
#
|
||||||
|
# - run: ./drupal_test.sh
|
||||||
|
# shell: "bash"
|
|
@ -0,0 +1,60 @@
|
||||||
|
name: "Documentation"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '3.x'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: "Build"
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: "Set up Python 3.7"
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: '3.7' # Semantic version range syntax or exact version of a Python version
|
||||||
|
|
||||||
|
- name: "Display Python version"
|
||||||
|
run: python -c "import sys; print(sys.version)"
|
||||||
|
|
||||||
|
- name: "Install Sphinx dependencies"
|
||||||
|
run: sudo apt-get install python-dev build-essential
|
||||||
|
|
||||||
|
- name: "Cache pip"
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('_build/.requirements.txt') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
|
||||||
|
- name: "Install Sphinx + requirements via pip"
|
||||||
|
working-directory: "doc"
|
||||||
|
run: pip install -r _build/.requirements.txt
|
||||||
|
|
||||||
|
- name: "Build documentation"
|
||||||
|
working-directory: "doc"
|
||||||
|
run: make -C _build SPHINXOPTS="-nqW -j auto" html
|
||||||
|
|
||||||
|
doctor-rst:
|
||||||
|
name: "DOCtor-RST"
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: "Run DOCtor-RST"
|
||||||
|
uses: docker://oskarstark/doctor-rst
|
||||||
|
with:
|
||||||
|
args: --short
|
||||||
|
env:
|
||||||
|
DOCS_DIR: 'doc/'
|
|
@ -0,0 +1,4 @@
|
||||||
|
/composer.lock
|
||||||
|
/phpunit.xml
|
||||||
|
/vendor
|
||||||
|
.phpunit.result.cache
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return (new PhpCsFixer\Config())
|
||||||
|
->setRules([
|
||||||
|
'@Symfony' => true,
|
||||||
|
'@Symfony:risky' => true,
|
||||||
|
'@PHPUnit75Migration:risky' => true,
|
||||||
|
'php_unit_dedicate_assert' => ['target' => '5.6'],
|
||||||
|
'array_syntax' => ['syntax' => 'short'],
|
||||||
|
'php_unit_fqcn_annotation' => true,
|
||||||
|
'no_unreachable_default_argument_value' => false,
|
||||||
|
'braces' => ['allow_single_line_closure' => true],
|
||||||
|
'heredoc_to_nowdoc' => false,
|
||||||
|
'ordered_imports' => true,
|
||||||
|
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
|
||||||
|
'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'],
|
||||||
|
])
|
||||||
|
->setRiskyAllowed(true)
|
||||||
|
->setFinder((new PhpCsFixer\Finder())->in(__DIR__))
|
||||||
|
;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009-2021 by the Twig Team.
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of Twig nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,24 @@
|
||||||
|
Twig, the flexible, fast, and secure template language for PHP
|
||||||
|
==============================================================
|
||||||
|
|
||||||
|
Twig is a template language for PHP, released under the new BSD license (code
|
||||||
|
and documentation).
|
||||||
|
|
||||||
|
Twig uses a syntax similar to the Django and Jinja template languages which
|
||||||
|
inspired the Twig runtime environment.
|
||||||
|
|
||||||
|
Sponsors
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<a href="https://blackfire.io/docs/introduction?utm_source=twig&utm_medium=github_readme&utm_campaign=logo">
|
||||||
|
<img src="https://static.blackfire.io/assets/intemporals/logo/png/blackfire-io_secondary_horizontal_transparent.png?1" width="255px" alt="Blackfire.io">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
More Information
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Read the `documentation`_ for more information.
|
||||||
|
|
||||||
|
.. _documentation: https://twig.symfony.com/documentation
|
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
"name": "twig/twig",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||||
|
"keywords": ["templating"],
|
||||||
|
"homepage": "https://twig.symfony.com",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com",
|
||||||
|
"homepage": "http://fabien.potencier.org",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Twig Team",
|
||||||
|
"role": "Contributors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Armin Ronacher",
|
||||||
|
"email": "armin.ronacher@active-4.com",
|
||||||
|
"role": "Project Founder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"symfony/polyfill-mbstring": "^1.3",
|
||||||
|
"symfony/polyfill-ctype": "^1.8"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/phpunit-bridge": "^4.4.9|^5.0.9",
|
||||||
|
"psr/container": "^1.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4" : {
|
||||||
|
"Twig\\" : "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4" : {
|
||||||
|
"Twig\\Tests\\" : "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.3-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
rules:
|
||||||
|
american_english: ~
|
||||||
|
avoid_repetetive_words: ~
|
||||||
|
blank_line_after_directive: ~
|
||||||
|
blank_line_before_directive: ~
|
||||||
|
composer_dev_option_not_at_the_end: ~
|
||||||
|
correct_code_block_directive_based_on_the_content: ~
|
||||||
|
deprecated_directive_should_have_version: ~
|
||||||
|
ensure_order_of_code_blocks_in_configuration_block: ~
|
||||||
|
extension_xlf_instead_of_xliff: ~
|
||||||
|
indention: ~
|
||||||
|
lowercase_as_in_use_statements: ~
|
||||||
|
max_blank_lines:
|
||||||
|
max: 2
|
||||||
|
no_blank_line_after_filepath_in_php_code_block: ~
|
||||||
|
no_blank_line_after_filepath_in_twig_code_block: ~
|
||||||
|
no_blank_line_after_filepath_in_xml_code_block: ~
|
||||||
|
no_blank_line_after_filepath_in_yaml_code_block: ~
|
||||||
|
no_composer_req: ~
|
||||||
|
no_explicit_use_of_code_block_php: ~
|
||||||
|
no_inheritdoc: ~
|
||||||
|
no_namespace_after_use_statements: ~
|
||||||
|
no_php_open_tag_in_code_block_php_directive: ~
|
||||||
|
no_space_before_self_xml_closing_tag: ~
|
||||||
|
ordered_use_statements: ~
|
||||||
|
php_prefix_before_bin_console: ~
|
||||||
|
replace_code_block_types: ~
|
||||||
|
replacement: ~
|
||||||
|
short_array_syntax: ~
|
||||||
|
typo: ~
|
||||||
|
unused_links: ~
|
||||||
|
use_deprecated_directive_instead_of_versionadded: ~
|
||||||
|
use_https_xsd_urls: ~
|
||||||
|
valid_inline_highlighted_namespaces: ~
|
||||||
|
valid_use_statements: ~
|
||||||
|
versionadded_directive_should_have_version: ~
|
||||||
|
yaml_instead_of_yml_suffix: ~
|
||||||
|
yarn_dev_option_at_the_end: ~
|
||||||
|
|
||||||
|
versionadded_directive_major_version:
|
||||||
|
major_version: 3
|
||||||
|
|
||||||
|
versionadded_directive_min_version:
|
||||||
|
min_version: '3.0'
|
||||||
|
|
||||||
|
deprecated_directive_major_version:
|
||||||
|
major_version: 3
|
||||||
|
|
||||||
|
deprecated_directive_min_version:
|
||||||
|
min_version: '3.0'
|
||||||
|
|
||||||
|
whitelist:
|
||||||
|
lines:
|
||||||
|
- 'I like Twig.<br />'
|
|
@ -0,0 +1,6 @@
|
||||||
|
docutils==0.13.1
|
||||||
|
Pygments==2.2.0
|
||||||
|
sphinx==1.8.5
|
||||||
|
git+https://github.com/fabpot/sphinx-php.git@v2.0.0#egg_name=sphinx-php
|
||||||
|
jsx-lexer===0.0.8
|
||||||
|
sphinx_rtd_theme==0.5.0
|
|
@ -0,0 +1,153 @@
|
||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
BUILDDIR = .
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -c $(BUILDDIR) -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) ../
|
||||||
|
# the i18n builder cannot share the environment and doctrees with the others
|
||||||
|
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
|
||||||
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
|
@echo " epub to make an epub"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||||
|
@echo " text to make text files"
|
||||||
|
@echo " man to make manual pages"
|
||||||
|
@echo " texinfo to make Texinfo files"
|
||||||
|
@echo " info to make Texinfo files and run them through makeinfo"
|
||||||
|
@echo " gettext to make PO message catalogs"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
html:
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
singlehtml:
|
||||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Symfony.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Symfony.qhc"
|
||||||
|
|
||||||
|
devhelp:
|
||||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished."
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/Symfony"
|
||||||
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Symfony"
|
||||||
|
@echo "# devhelp"
|
||||||
|
|
||||||
|
epub:
|
||||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
|
latexpdf:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
text:
|
||||||
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
|
man:
|
||||||
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
|
texinfo:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||||
|
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||||
|
"(use \`make info' here to do that automatically)."
|
||||||
|
|
||||||
|
info:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo "Running Texinfo files through makeinfo..."
|
||||||
|
make -C $(BUILDDIR)/texinfo info
|
||||||
|
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||||
|
|
||||||
|
gettext:
|
||||||
|
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
|
@ -0,0 +1,271 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Symfony documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Sat Jul 28 21:58:57 2012.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#sys.path.append(os.path.abspath('_exts'))
|
||||||
|
|
||||||
|
# adding PhpLexer
|
||||||
|
from sphinx.highlighting import lexers
|
||||||
|
from pygments.lexers.special import TextLexer
|
||||||
|
from pygments.lexers.text import RstLexer
|
||||||
|
from pygments.lexers.web import PhpLexer
|
||||||
|
|
||||||
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
needs_sphinx = '1.8.5'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
extensions = [
|
||||||
|
'sphinx.ext.autodoc', 'sphinx.ext.doctest',
|
||||||
|
'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig',
|
||||||
|
'sphinx.ext.viewcode', 'sphinx.ext.extlinks',
|
||||||
|
'sensio.sphinx.codeblock', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode', 'sensio.sphinx.bestpractice'
|
||||||
|
]
|
||||||
|
|
||||||
|
#spelling_show_sugestions=True
|
||||||
|
#spelling_lang='en_US'
|
||||||
|
#spelling_word_list_filename='_build/spelling_word_list.txt'
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
# templates_path = ['_theme/_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = 'Twig'
|
||||||
|
copyright = ''
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
# version = '2.2'
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
# release = '2.2.13'
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
#today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
exclude_patterns = ['_build']
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
|
#default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
#add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
#add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
#show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
#pygments_style = 'symfonycom.sphinx.SensioStyle'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
# -- Settings for symfony doc extension ---------------------------------------------------
|
||||||
|
|
||||||
|
# enable highlighting for PHP code not between ``<?php ... ?>`` by default
|
||||||
|
lexers['php'] = PhpLexer(startinline=True)
|
||||||
|
lexers['rst'] = RstLexer()
|
||||||
|
|
||||||
|
config_block = {
|
||||||
|
'rst': 'reStructuredText',
|
||||||
|
}
|
||||||
|
|
||||||
|
# don't enable Sphinx Domains
|
||||||
|
primary_domain = None
|
||||||
|
|
||||||
|
# set url for API links
|
||||||
|
#api_url = 'https://github.com/symfony/symfony/blob/master/src/%s.php'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ---------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
html_theme_options = {
|
||||||
|
'logo_only': True,
|
||||||
|
'prev_next_buttons_location': None,
|
||||||
|
'style_nav_header_background': '#f0f0f0'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
#html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
#html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
#html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
#html_logo = '_static/symfony-logo.svg'
|
||||||
|
|
||||||
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
#html_favicon = None
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
#html_static_path = ['_static']
|
||||||
|
#html_css_files = ['rtd_custom.css']
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
#html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
#html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
#html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
#html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
#html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
#html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_sphinx = True
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
#html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
#html_file_suffix = None
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'Twig'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#'preamble': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
|
#latex_documents = []
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
#latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
#latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#latex_show_urls = False
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output --------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
#man_pages = []
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
#man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output ------------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
#texinfo_documents = []
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
#texinfo_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
#texinfo_domain_indices = True
|
||||||
|
|
||||||
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
|
#texinfo_show_urls = 'footnote'
|
||||||
|
|
||||||
|
# Use PHP syntax highlighting in code examples by default
|
||||||
|
highlight_language='php'
|
|
@ -0,0 +1,911 @@
|
||||||
|
Extending Twig
|
||||||
|
==============
|
||||||
|
|
||||||
|
Twig can be extended in many ways; you can add extra tags, filters, tests,
|
||||||
|
operators, global variables, and functions. You can even extend the parser
|
||||||
|
itself with node visitors.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The first section of this chapter describes how to extend Twig. If you want
|
||||||
|
to reuse your changes in different projects or if you want to share them
|
||||||
|
with others, you should then create an extension as described in the
|
||||||
|
following section.
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
When extending Twig without creating an extension, Twig won't be able to
|
||||||
|
recompile your templates when the PHP code is updated. To see your changes
|
||||||
|
in real-time, either disable template caching or package your code into an
|
||||||
|
extension (see the next section of this chapter).
|
||||||
|
|
||||||
|
Before extending Twig, you must understand the differences between all the
|
||||||
|
different possible extension points and when to use them.
|
||||||
|
|
||||||
|
First, remember that Twig has two main language constructs:
|
||||||
|
|
||||||
|
* ``{{ }}``: used to print the result of an expression evaluation;
|
||||||
|
|
||||||
|
* ``{% %}``: used to execute statements.
|
||||||
|
|
||||||
|
To understand why Twig exposes so many extension points, let's see how to
|
||||||
|
implement a *Lorem ipsum* generator (it needs to know the number of words to
|
||||||
|
generate).
|
||||||
|
|
||||||
|
You can use a ``lipsum`` *tag*:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% lipsum 40 %}
|
||||||
|
|
||||||
|
That works, but using a tag for ``lipsum`` is not a good idea for at least
|
||||||
|
three main reasons:
|
||||||
|
|
||||||
|
* ``lipsum`` is not a language construct;
|
||||||
|
* The tag outputs something;
|
||||||
|
* The tag is not flexible as you cannot use it in an expression:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
|
||||||
|
|
||||||
|
In fact, you rarely need to create tags; and that's good news because tags are
|
||||||
|
the most complex extension point.
|
||||||
|
|
||||||
|
Now, let's use a ``lipsum`` *filter*:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 40|lipsum }}
|
||||||
|
|
||||||
|
Again, it works. But a filter should transform the passed value to something
|
||||||
|
else. Here, we use the value to indicate the number of words to generate (so,
|
||||||
|
``40`` is an argument of the filter, not the value we want to transform).
|
||||||
|
|
||||||
|
Next, let's use a ``lipsum`` *function*:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ lipsum(40) }}
|
||||||
|
|
||||||
|
Here we go. For this specific example, the creation of a function is the
|
||||||
|
extension point to use. And you can use it anywhere an expression is accepted:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'some text' ~ lipsum(40) ~ 'some more text' }}
|
||||||
|
|
||||||
|
{% set lipsum = lipsum(40) %}
|
||||||
|
|
||||||
|
Lastly, you can also use a *global* object with a method able to generate lorem
|
||||||
|
ipsum text:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ text.lipsum(40) }}
|
||||||
|
|
||||||
|
As a rule of thumb, use functions for frequently used features and global
|
||||||
|
objects for everything else.
|
||||||
|
|
||||||
|
Keep in mind the following when you want to extend Twig:
|
||||||
|
|
||||||
|
========== ========================== ========== =========================
|
||||||
|
What? Implementation difficulty? How often? When?
|
||||||
|
========== ========================== ========== =========================
|
||||||
|
*macro* simple frequent Content generation
|
||||||
|
*global* simple frequent Helper object
|
||||||
|
*function* simple frequent Content generation
|
||||||
|
*filter* simple frequent Value transformation
|
||||||
|
*tag* complex rare DSL language construct
|
||||||
|
*test* simple rare Boolean decision
|
||||||
|
*operator* simple rare Values transformation
|
||||||
|
========== ========================== ========== =========================
|
||||||
|
|
||||||
|
Globals
|
||||||
|
-------
|
||||||
|
|
||||||
|
A global variable is like any other template variable, except that it's
|
||||||
|
available in all templates and macros::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->addGlobal('text', new Text());
|
||||||
|
|
||||||
|
You can then use the ``text`` variable anywhere in a template:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ text.lipsum(40) }}
|
||||||
|
|
||||||
|
Filters
|
||||||
|
-------
|
||||||
|
|
||||||
|
Creating a filter consists of associating a name with a PHP callable::
|
||||||
|
|
||||||
|
// an anonymous function
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', function ($string) {
|
||||||
|
return str_rot13($string);
|
||||||
|
});
|
||||||
|
|
||||||
|
// or a simple PHP function
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', 'str_rot13');
|
||||||
|
|
||||||
|
// or a class static method
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', 'SomeClass::rot13Filter');
|
||||||
|
|
||||||
|
// or a class method
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', [$this, 'rot13Filter']);
|
||||||
|
// the one below needs a runtime implementation (see below for more information)
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', ['SomeClass', 'rot13Filter']);
|
||||||
|
|
||||||
|
The first argument passed to the ``\Twig\TwigFilter`` constructor is the name of the
|
||||||
|
filter you will use in templates and the second one is the PHP callable to
|
||||||
|
associate with it.
|
||||||
|
|
||||||
|
Then, add the filter to the Twig environment::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->addFilter($filter);
|
||||||
|
|
||||||
|
And here is how to use it in a template:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'Twig'|rot13 }}
|
||||||
|
|
||||||
|
{# will output Gjvt #}
|
||||||
|
|
||||||
|
When called by Twig, the PHP callable receives the left side of the filter
|
||||||
|
(before the pipe ``|``) as the first argument and the extra arguments passed
|
||||||
|
to the filter (within parentheses ``()``) as extra arguments.
|
||||||
|
|
||||||
|
For instance, the following code:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'TWIG'|lower }}
|
||||||
|
{{ now|date('d/m/Y') }}
|
||||||
|
|
||||||
|
is compiled to something like the following::
|
||||||
|
|
||||||
|
<?php echo strtolower('TWIG') ?>
|
||||||
|
<?php echo twig_date_format_filter($now, 'd/m/Y') ?>
|
||||||
|
|
||||||
|
The ``\Twig\TwigFilter`` class takes an array of options as its last argument::
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', 'str_rot13', $options);
|
||||||
|
|
||||||
|
Environment-aware Filters
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you want to access the current environment instance in your filter, set the
|
||||||
|
``needs_environment`` option to ``true``; Twig will pass the current
|
||||||
|
environment as the first argument to the filter call::
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $string) {
|
||||||
|
// get the current charset for instance
|
||||||
|
$charset = $env->getCharset();
|
||||||
|
|
||||||
|
return str_rot13($string);
|
||||||
|
}, ['needs_environment' => true]);
|
||||||
|
|
||||||
|
Context-aware Filters
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you want to access the current context in your filter, set the
|
||||||
|
``needs_context`` option to ``true``; Twig will pass the current context as
|
||||||
|
the first argument to the filter call (or the second one if
|
||||||
|
``needs_environment`` is also set to ``true``)::
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', function ($context, $string) {
|
||||||
|
// ...
|
||||||
|
}, ['needs_context' => true]);
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('rot13', function (\Twig\Environment $env, $context, $string) {
|
||||||
|
// ...
|
||||||
|
}, ['needs_context' => true, 'needs_environment' => true]);
|
||||||
|
|
||||||
|
Automatic Escaping
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If automatic escaping is enabled, the output of the filter may be escaped
|
||||||
|
before printing. If your filter acts as an escaper (or explicitly outputs HTML
|
||||||
|
or JavaScript code), you will want the raw output to be printed. In such a
|
||||||
|
case, set the ``is_safe`` option::
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('nl2br', 'nl2br', ['is_safe' => ['html']]);
|
||||||
|
|
||||||
|
Some filters may need to work on input that is already escaped or safe, for
|
||||||
|
example when adding (safe) HTML tags to originally unsafe output. In such a
|
||||||
|
case, set the ``pre_escape`` option to escape the input data before it is run
|
||||||
|
through your filter::
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('somefilter', 'somefilter', ['pre_escape' => 'html', 'is_safe' => ['html']]);
|
||||||
|
|
||||||
|
Variadic Filters
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
When a filter should accept an arbitrary number of arguments, set the
|
||||||
|
``is_variadic`` option to ``true``; Twig will pass the extra arguments as the
|
||||||
|
last argument to the filter call as an array::
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('thumbnail', function ($file, array $options = []) {
|
||||||
|
// ...
|
||||||
|
}, ['is_variadic' => true]);
|
||||||
|
|
||||||
|
Be warned that :ref:`named arguments <named-arguments>` passed to a variadic
|
||||||
|
filter cannot be checked for validity as they will automatically end up in the
|
||||||
|
option array.
|
||||||
|
|
||||||
|
Dynamic Filters
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A filter name containing the special ``*`` character is a dynamic filter and
|
||||||
|
the ``*`` part will match any string::
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('*_path', function ($name, $arguments) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
The following filters are matched by the above defined dynamic filter:
|
||||||
|
|
||||||
|
* ``product_path``
|
||||||
|
* ``category_path``
|
||||||
|
|
||||||
|
A dynamic filter can define more than one dynamic parts::
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('*_path_*', function ($name, $suffix, $arguments) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
The filter receives all dynamic part values before the normal filter arguments,
|
||||||
|
but after the environment and the context. For instance, a call to
|
||||||
|
``'foo'|a_path_b()`` will result in the following arguments to be passed to the
|
||||||
|
filter: ``('a', 'b', 'foo')``.
|
||||||
|
|
||||||
|
Deprecated Filters
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can mark a filter as being deprecated by setting the ``deprecated`` option
|
||||||
|
to ``true``. You can also give an alternative filter that replaces the
|
||||||
|
deprecated one when that makes sense::
|
||||||
|
|
||||||
|
$filter = new \Twig\TwigFilter('obsolete', function () {
|
||||||
|
// ...
|
||||||
|
}, ['deprecated' => true, 'alternative' => 'new_one']);
|
||||||
|
|
||||||
|
When a filter is deprecated, Twig emits a deprecation notice when compiling a
|
||||||
|
template using it. See :ref:`deprecation-notices` for more information.
|
||||||
|
|
||||||
|
Functions
|
||||||
|
---------
|
||||||
|
|
||||||
|
Functions are defined in the exact same way as filters, but you need to create
|
||||||
|
an instance of ``\Twig\TwigFunction``::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$function = new \Twig\TwigFunction('function_name', function () {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
$twig->addFunction($function);
|
||||||
|
|
||||||
|
Functions support the same features as filters, except for the ``pre_escape``
|
||||||
|
and ``preserves_safety`` options.
|
||||||
|
|
||||||
|
Tests
|
||||||
|
-----
|
||||||
|
|
||||||
|
Tests are defined in the exact same way as filters and functions, but you need
|
||||||
|
to create an instance of ``\Twig\TwigTest``::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$test = new \Twig\TwigTest('test_name', function () {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
$twig->addTest($test);
|
||||||
|
|
||||||
|
Tests allow you to create custom application specific logic for evaluating
|
||||||
|
boolean conditions. As a simple example, let's create a Twig test that checks if
|
||||||
|
objects are 'red'::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$test = new \Twig\TwigTest('red', function ($value) {
|
||||||
|
if (isset($value->color) && $value->color == 'red') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isset($value->paint) && $value->paint == 'red') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$twig->addTest($test);
|
||||||
|
|
||||||
|
Test functions must always return ``true``/``false``.
|
||||||
|
|
||||||
|
When creating tests you can use the ``node_class`` option to provide custom test
|
||||||
|
compilation. This is useful if your test can be compiled into PHP primitives.
|
||||||
|
This is used by many of the tests built into Twig::
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Node\Expression\TestExpression;
|
||||||
|
use Twig\TwigTest;
|
||||||
|
|
||||||
|
$twig = new Environment($loader);
|
||||||
|
$test = new TwigTest(
|
||||||
|
'odd',
|
||||||
|
null,
|
||||||
|
['node_class' => OddTestExpression::class]);
|
||||||
|
$twig->addTest($test);
|
||||||
|
|
||||||
|
class OddTestExpression extends TestExpression
|
||||||
|
{
|
||||||
|
public function compile(\Twig\Compiler $compiler)
|
||||||
|
{
|
||||||
|
$compiler
|
||||||
|
->raw('(')
|
||||||
|
->subcompile($this->getNode('node'))
|
||||||
|
->raw(' % 2 != 0')
|
||||||
|
->raw(')')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The above example shows how you can create tests that use a node class. The node
|
||||||
|
class has access to one sub-node called ``node``. This sub-node contains the
|
||||||
|
value that is being tested. When the ``odd`` filter is used in code such as:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% if my_value is odd %}
|
||||||
|
|
||||||
|
The ``node`` sub-node will contain an expression of ``my_value``. Node-based
|
||||||
|
tests also have access to the ``arguments`` node. This node will contain the
|
||||||
|
various other arguments that have been provided to your test.
|
||||||
|
|
||||||
|
If you want to pass a variable number of positional or named arguments to the
|
||||||
|
test, set the ``is_variadic`` option to ``true``. Tests support dynamic
|
||||||
|
names (see dynamic filters for the syntax).
|
||||||
|
|
||||||
|
Tags
|
||||||
|
----
|
||||||
|
|
||||||
|
One of the most exciting features of a template engine like Twig is the
|
||||||
|
possibility to define new **language constructs**. This is also the most complex
|
||||||
|
feature as you need to understand how Twig's internals work.
|
||||||
|
|
||||||
|
Most of the time though, a tag is not needed:
|
||||||
|
|
||||||
|
* If your tag generates some output, use a **function** instead.
|
||||||
|
|
||||||
|
* If your tag modifies some content and returns it, use a **filter** instead.
|
||||||
|
|
||||||
|
For instance, if you want to create a tag that converts a Markdown formatted
|
||||||
|
text to HTML, create a ``markdown`` filter instead:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ '**markdown** text'|markdown }}
|
||||||
|
|
||||||
|
If you want use this filter on large amounts of text, wrap it with the
|
||||||
|
:doc:`apply <tags/apply>` tag:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% apply markdown %}
|
||||||
|
Title
|
||||||
|
=====
|
||||||
|
|
||||||
|
Much better than creating a tag as you can **compose** filters.
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
* If your tag does not output anything, but only exists because of a side
|
||||||
|
effect, create a **function** that returns nothing and call it via the
|
||||||
|
:doc:`filter <tags/do>` tag.
|
||||||
|
|
||||||
|
For instance, if you want to create a tag that logs text, create a ``log``
|
||||||
|
function instead and call it via the :doc:`do <tags/do>` tag:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% do log('Log some things') %}
|
||||||
|
|
||||||
|
If you still want to create a tag for a new language construct, great!
|
||||||
|
|
||||||
|
Let's create a ``set`` tag that allows the definition of simple variables from
|
||||||
|
within a template. The tag can be used like follows:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set name = "value" %}
|
||||||
|
|
||||||
|
{{ name }}
|
||||||
|
|
||||||
|
{# should output value #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``set`` tag is part of the Core extension and as such is always
|
||||||
|
available. The built-in version is slightly more powerful and supports
|
||||||
|
multiple assignments by default.
|
||||||
|
|
||||||
|
Three steps are needed to define a new tag:
|
||||||
|
|
||||||
|
* Defining a Token Parser class (responsible for parsing the template code);
|
||||||
|
|
||||||
|
* Defining a Node class (responsible for converting the parsed code to PHP);
|
||||||
|
|
||||||
|
* Registering the tag.
|
||||||
|
|
||||||
|
Registering a new tag
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Add a tag by calling the ``addTokenParser`` method on the ``\Twig\Environment``
|
||||||
|
instance::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->addTokenParser(new Project_Set_TokenParser());
|
||||||
|
|
||||||
|
Defining a Token Parser
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Now, let's see the actual code of this class::
|
||||||
|
|
||||||
|
class Project_Set_TokenParser extends \Twig\TokenParser\AbstractTokenParser
|
||||||
|
{
|
||||||
|
public function parse(\Twig\Token $token)
|
||||||
|
{
|
||||||
|
$parser = $this->parser;
|
||||||
|
$stream = $parser->getStream();
|
||||||
|
|
||||||
|
$name = $stream->expect(\Twig\Token::NAME_TYPE)->getValue();
|
||||||
|
$stream->expect(\Twig\Token::OPERATOR_TYPE, '=');
|
||||||
|
$value = $parser->getExpressionParser()->parseExpression();
|
||||||
|
$stream->expect(\Twig\Token::BLOCK_END_TYPE);
|
||||||
|
|
||||||
|
return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTag()
|
||||||
|
{
|
||||||
|
return 'set';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The ``getTag()`` method must return the tag we want to parse, here ``set``.
|
||||||
|
|
||||||
|
The ``parse()`` method is invoked whenever the parser encounters a ``set``
|
||||||
|
tag. It should return a ``\Twig\Node\Node`` instance that represents the node (the
|
||||||
|
``Project_Set_Node`` calls creating is explained in the next section).
|
||||||
|
|
||||||
|
The parsing process is simplified thanks to a bunch of methods you can call
|
||||||
|
from the token stream (``$this->parser->getStream()``):
|
||||||
|
|
||||||
|
* ``getCurrent()``: Gets the current token in the stream.
|
||||||
|
|
||||||
|
* ``next()``: Moves to the next token in the stream, *but returns the old one*.
|
||||||
|
|
||||||
|
* ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether
|
||||||
|
the current token is of a particular type or value (or both). The value may be an
|
||||||
|
array of several possible values.
|
||||||
|
|
||||||
|
* ``expect($type[, $value[, $message]])``: If the current token isn't of the given
|
||||||
|
type/value a syntax error is thrown. Otherwise, if the type and value are correct,
|
||||||
|
the token is returned and the stream moves to the next token.
|
||||||
|
|
||||||
|
* ``look()``: Looks at the next token without consuming it.
|
||||||
|
|
||||||
|
Parsing expressions is done by calling the ``parseExpression()`` like we did for
|
||||||
|
the ``set`` tag.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
Reading the existing ``TokenParser`` classes is the best way to learn all
|
||||||
|
the nitty-gritty details of the parsing process.
|
||||||
|
|
||||||
|
Defining a Node
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``Project_Set_Node`` class itself is quite short::
|
||||||
|
|
||||||
|
class Project_Set_Node extends \Twig\Node\Node
|
||||||
|
{
|
||||||
|
public function __construct($name, \Twig\Node\Expression\AbstractExpression $value, $line, $tag = null)
|
||||||
|
{
|
||||||
|
parent::__construct(['value' => $value], ['name' => $name], $line, $tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(\Twig\Compiler $compiler)
|
||||||
|
{
|
||||||
|
$compiler
|
||||||
|
->addDebugInfo($this)
|
||||||
|
->write('$context[\''.$this->getAttribute('name').'\'] = ')
|
||||||
|
->subcompile($this->getNode('value'))
|
||||||
|
->raw(";\n")
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The compiler implements a fluid interface and provides methods that helps the
|
||||||
|
developer generate beautiful and readable PHP code:
|
||||||
|
|
||||||
|
* ``subcompile()``: Compiles a node.
|
||||||
|
|
||||||
|
* ``raw()``: Writes the given string as is.
|
||||||
|
|
||||||
|
* ``write()``: Writes the given string by adding indentation at the beginning
|
||||||
|
of each line.
|
||||||
|
|
||||||
|
* ``string()``: Writes a quoted string.
|
||||||
|
|
||||||
|
* ``repr()``: Writes a PHP representation of a given value (see
|
||||||
|
``\Twig\Node\ForNode`` for a usage example).
|
||||||
|
|
||||||
|
* ``addDebugInfo()``: Adds the line of the original template file related to
|
||||||
|
the current node as a comment.
|
||||||
|
|
||||||
|
* ``indent()``: Indents the generated code (see ``\Twig\Node\BlockNode`` for a
|
||||||
|
usage example).
|
||||||
|
|
||||||
|
* ``outdent()``: Outdents the generated code (see ``\Twig\Node\BlockNode`` for a
|
||||||
|
usage example).
|
||||||
|
|
||||||
|
.. _creating_extensions:
|
||||||
|
|
||||||
|
Creating an Extension
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The main motivation for writing an extension is to move often used code into a
|
||||||
|
reusable class like adding support for internationalization. An extension can
|
||||||
|
define tags, filters, tests, operators, functions, and node visitors.
|
||||||
|
|
||||||
|
Most of the time, it is useful to create a single extension for your project,
|
||||||
|
to host all the specific tags and filters you want to add to Twig.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
When packaging your code into an extension, Twig is smart enough to
|
||||||
|
recompile your templates whenever you make a change to it (when
|
||||||
|
``auto_reload`` is enabled).
|
||||||
|
|
||||||
|
An extension is a class that implements the following interface::
|
||||||
|
|
||||||
|
interface \Twig\Extension\ExtensionInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the token parser instances to add to the existing list.
|
||||||
|
*
|
||||||
|
* @return \Twig\TokenParser\TokenParserInterface[]
|
||||||
|
*/
|
||||||
|
public function getTokenParsers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the node visitor instances to add to the existing list.
|
||||||
|
*
|
||||||
|
* @return \Twig\NodeVisitor\NodeVisitorInterface[]
|
||||||
|
*/
|
||||||
|
public function getNodeVisitors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of filters to add to the existing list.
|
||||||
|
*
|
||||||
|
* @return \Twig\TwigFilter[]
|
||||||
|
*/
|
||||||
|
public function getFilters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of tests to add to the existing list.
|
||||||
|
*
|
||||||
|
* @return \Twig\TwigTest[]
|
||||||
|
*/
|
||||||
|
public function getTests();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of functions to add to the existing list.
|
||||||
|
*
|
||||||
|
* @return \Twig\TwigFunction[]
|
||||||
|
*/
|
||||||
|
public function getFunctions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of operators to add to the existing list.
|
||||||
|
*
|
||||||
|
* @return array<array> First array of unary operators, second array of binary operators
|
||||||
|
*/
|
||||||
|
public function getOperators();
|
||||||
|
}
|
||||||
|
|
||||||
|
To keep your extension class clean and lean, inherit from the built-in
|
||||||
|
``\Twig\Extension\AbstractExtension`` class instead of implementing the interface as it provides
|
||||||
|
empty implementations for all methods::
|
||||||
|
|
||||||
|
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
This extension does nothing for now. We will customize it in the next sections.
|
||||||
|
|
||||||
|
You can save your extension anywhere on the filesystem, as all extensions must
|
||||||
|
be registered explicitly to be available in your templates.
|
||||||
|
|
||||||
|
You can register an extension by using the ``addExtension()`` method on your
|
||||||
|
main ``Environment`` object::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->addExtension(new Project_Twig_Extension());
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
The Twig core extensions are great examples of how extensions work.
|
||||||
|
|
||||||
|
Globals
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
Global variables can be registered in an extension via the ``getGlobals()``
|
||||||
|
method::
|
||||||
|
|
||||||
|
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
|
||||||
|
{
|
||||||
|
public function getGlobals(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'text' => new Text(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Functions
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
Functions can be registered in an extension via the ``getFunctions()``
|
||||||
|
method::
|
||||||
|
|
||||||
|
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
|
||||||
|
{
|
||||||
|
public function getFunctions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new \Twig\TwigFunction('lipsum', 'generate_lipsum'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Filters
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
To add a filter to an extension, you need to override the ``getFilters()``
|
||||||
|
method. This method must return an array of filters to add to the Twig
|
||||||
|
environment::
|
||||||
|
|
||||||
|
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
|
||||||
|
{
|
||||||
|
public function getFilters()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new \Twig\TwigFilter('rot13', 'str_rot13'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Tags
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
Adding a tag in an extension can be done by overriding the
|
||||||
|
``getTokenParsers()`` method. This method must return an array of tags to add
|
||||||
|
to the Twig environment::
|
||||||
|
|
||||||
|
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
|
||||||
|
{
|
||||||
|
public function getTokenParsers()
|
||||||
|
{
|
||||||
|
return [new Project_Set_TokenParser()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
In the above code, we have added a single new tag, defined by the
|
||||||
|
``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
|
||||||
|
responsible for parsing the tag and compiling it to PHP.
|
||||||
|
|
||||||
|
Operators
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
The ``getOperators()`` methods lets you add new operators. Here is how to add
|
||||||
|
the ``!``, ``||``, and ``&&`` operators::
|
||||||
|
|
||||||
|
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
|
||||||
|
{
|
||||||
|
public function getOperators()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'!' => ['precedence' => 50, 'class' => \Twig\Node\Expression\Unary\NotUnary::class],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'||' => ['precedence' => 10, 'class' => \Twig\Node\Expression\Binary\OrBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
|
||||||
|
'&&' => ['precedence' => 15, 'class' => \Twig\Node\Expression\Binary\AndBinary::class, 'associativity' => \Twig\ExpressionParser::OPERATOR_LEFT],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Tests
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
The ``getTests()`` method lets you add new test functions::
|
||||||
|
|
||||||
|
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
|
||||||
|
{
|
||||||
|
public function getTests()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new \Twig\TwigTest('even', 'twig_test_even'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Definition vs Runtime
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Twig filters, functions, and tests runtime implementations can be defined as
|
||||||
|
any valid PHP callable:
|
||||||
|
|
||||||
|
* **functions/static methods**: Simple to implement and fast (used by all Twig
|
||||||
|
core extensions); but it is hard for the runtime to depend on external
|
||||||
|
objects;
|
||||||
|
|
||||||
|
* **closures**: Simple to implement;
|
||||||
|
|
||||||
|
* **object methods**: More flexible and required if your runtime code depends
|
||||||
|
on external objects.
|
||||||
|
|
||||||
|
The simplest way to use methods is to define them on the extension itself::
|
||||||
|
|
||||||
|
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
|
||||||
|
{
|
||||||
|
private $rot13Provider;
|
||||||
|
|
||||||
|
public function __construct($rot13Provider)
|
||||||
|
{
|
||||||
|
$this->rot13Provider = $rot13Provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFunctions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new \Twig\TwigFunction('rot13', [$this, 'rot13']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rot13($value)
|
||||||
|
{
|
||||||
|
return $this->rot13Provider->rot13($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
This is very convenient but not recommended as it makes template compilation
|
||||||
|
depend on runtime dependencies even if they are not needed (think for instance
|
||||||
|
as a dependency that connects to a database engine).
|
||||||
|
|
||||||
|
You can decouple the extension definitions from their runtime implementations by
|
||||||
|
registering a ``\Twig\RuntimeLoader\RuntimeLoaderInterface`` instance on the
|
||||||
|
environment that knows how to instantiate such runtime classes (runtime classes
|
||||||
|
must be autoload-able)::
|
||||||
|
|
||||||
|
class RuntimeLoader implements \Twig\RuntimeLoader\RuntimeLoaderInterface
|
||||||
|
{
|
||||||
|
public function load($class)
|
||||||
|
{
|
||||||
|
// implement the logic to create an instance of $class
|
||||||
|
// and inject its dependencies
|
||||||
|
// most of the time, it means using your dependency injection container
|
||||||
|
if ('Project_Twig_RuntimeExtension' === $class) {
|
||||||
|
return new $class(new Rot13Provider());
|
||||||
|
} else {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$twig->addRuntimeLoader(new RuntimeLoader());
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Twig comes with a PSR-11 compatible runtime loader
|
||||||
|
(``\Twig\RuntimeLoader\ContainerRuntimeLoader``).
|
||||||
|
|
||||||
|
It is now possible to move the runtime logic to a new
|
||||||
|
``Project_Twig_RuntimeExtension`` class and use it directly in the extension::
|
||||||
|
|
||||||
|
class Project_Twig_RuntimeExtension
|
||||||
|
{
|
||||||
|
private $rot13Provider;
|
||||||
|
|
||||||
|
public function __construct($rot13Provider)
|
||||||
|
{
|
||||||
|
$this->rot13Provider = $rot13Provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rot13($value)
|
||||||
|
{
|
||||||
|
return $this->rot13Provider->rot13($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Project_Twig_Extension extends \Twig\Extension\AbstractExtension
|
||||||
|
{
|
||||||
|
public function getFunctions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new \Twig\TwigFunction('rot13', ['Project_Twig_RuntimeExtension', 'rot13']),
|
||||||
|
// or
|
||||||
|
new \Twig\TwigFunction('rot13', 'Project_Twig_RuntimeExtension::rot13'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Testing an Extension
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Functional Tests
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can create functional tests for extensions by creating the following file
|
||||||
|
structure in your test directory::
|
||||||
|
|
||||||
|
Fixtures/
|
||||||
|
filters/
|
||||||
|
foo.test
|
||||||
|
bar.test
|
||||||
|
functions/
|
||||||
|
foo.test
|
||||||
|
bar.test
|
||||||
|
tags/
|
||||||
|
foo.test
|
||||||
|
bar.test
|
||||||
|
IntegrationTest.php
|
||||||
|
|
||||||
|
The ``IntegrationTest.php`` file should look like this::
|
||||||
|
|
||||||
|
use Twig\Test\IntegrationTestCase;
|
||||||
|
|
||||||
|
class Project_Tests_IntegrationTest extends IntegrationTestCase
|
||||||
|
{
|
||||||
|
public function getExtensions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new Project_Twig_Extension1(),
|
||||||
|
new Project_Twig_Extension2(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFixturesDir()
|
||||||
|
{
|
||||||
|
return __DIR__.'/Fixtures/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Fixtures examples can be found within the Twig repository
|
||||||
|
`tests/Twig/Fixtures`_ directory.
|
||||||
|
|
||||||
|
Node Tests
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Testing the node visitors can be complex, so extend your test cases from
|
||||||
|
``\Twig\Test\NodeTestCase``. Examples can be found in the Twig repository
|
||||||
|
`tests/Twig/Node`_ directory.
|
||||||
|
|
||||||
|
.. _`tests/Twig/Fixtures`: https://github.com/twigphp/Twig/tree/3.x/tests/Fixtures
|
||||||
|
.. _`tests/Twig/Node`: https://github.com/twigphp/Twig/tree/3.x/tests/Node
|
|
@ -0,0 +1,583 @@
|
||||||
|
Twig for Developers
|
||||||
|
===================
|
||||||
|
|
||||||
|
This chapter describes the API to Twig and not the template language. It will
|
||||||
|
be most useful as reference to those implementing the template interface to
|
||||||
|
the application and not those who are creating Twig templates.
|
||||||
|
|
||||||
|
Basics
|
||||||
|
------
|
||||||
|
|
||||||
|
Twig uses a central object called the **environment** (of class
|
||||||
|
``\Twig\Environment``). Instances of this class are used to store the
|
||||||
|
configuration and extensions, and are used to load templates.
|
||||||
|
|
||||||
|
Most applications create one ``\Twig\Environment`` object on application
|
||||||
|
initialization and use that to load templates. In some cases, it might be useful
|
||||||
|
to have multiple environments side by side, with different configurations.
|
||||||
|
|
||||||
|
The typical way to configure Twig to load templates for an application looks
|
||||||
|
roughly like this::
|
||||||
|
|
||||||
|
require_once '/path/to/vendor/autoload.php';
|
||||||
|
|
||||||
|
$loader = new \Twig\Loader\FilesystemLoader('/path/to/templates');
|
||||||
|
$twig = new \Twig\Environment($loader, [
|
||||||
|
'cache' => '/path/to/compilation_cache',
|
||||||
|
]);
|
||||||
|
|
||||||
|
This creates a template environment with a default configuration and a loader
|
||||||
|
that looks up templates in the ``/path/to/templates/`` directory. Different
|
||||||
|
loaders are available and you can also write your own if you want to load
|
||||||
|
templates from a database or other resources.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Notice that the second argument of the environment is an array of options.
|
||||||
|
The ``cache`` option is a compilation cache directory, where Twig caches
|
||||||
|
the compiled templates to avoid the parsing phase for sub-sequent
|
||||||
|
requests. It is very different from the cache you might want to add for
|
||||||
|
the evaluated templates. For such a need, you can use any available PHP
|
||||||
|
cache library.
|
||||||
|
|
||||||
|
Rendering Templates
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
To load a template from a Twig environment, call the ``load()`` method which
|
||||||
|
returns a ``\Twig\TemplateWrapper`` instance::
|
||||||
|
|
||||||
|
$template = $twig->load('index.html');
|
||||||
|
|
||||||
|
To render the template with some variables, call the ``render()`` method::
|
||||||
|
|
||||||
|
echo $template->render(['the' => 'variables', 'go' => 'here']);
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``display()`` method is a shortcut to output the rendered template.
|
||||||
|
|
||||||
|
You can also load and render the template in one fell swoop::
|
||||||
|
|
||||||
|
echo $twig->render('index.html', ['the' => 'variables', 'go' => 'here']);
|
||||||
|
|
||||||
|
If a template defines blocks, they can be rendered individually via the
|
||||||
|
``renderBlock()`` call::
|
||||||
|
|
||||||
|
echo $template->renderBlock('block_name', ['the' => 'variables', 'go' => 'here']);
|
||||||
|
|
||||||
|
.. _environment_options:
|
||||||
|
|
||||||
|
Environment Options
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
When creating a new ``\Twig\Environment`` instance, you can pass an array of
|
||||||
|
options as the constructor second argument::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader, ['debug' => true]);
|
||||||
|
|
||||||
|
The following options are available:
|
||||||
|
|
||||||
|
* ``debug`` *boolean*
|
||||||
|
|
||||||
|
When set to ``true``, the generated templates have a
|
||||||
|
``__toString()`` method that you can use to display the generated nodes
|
||||||
|
(default to ``false``).
|
||||||
|
|
||||||
|
* ``charset`` *string* (defaults to ``utf-8``)
|
||||||
|
|
||||||
|
The charset used by the templates.
|
||||||
|
|
||||||
|
* ``cache`` *string* or ``false``
|
||||||
|
|
||||||
|
An absolute path where to store the compiled templates, or
|
||||||
|
``false`` to disable caching (which is the default).
|
||||||
|
|
||||||
|
* ``auto_reload`` *boolean*
|
||||||
|
|
||||||
|
When developing with Twig, it's useful to recompile the
|
||||||
|
template whenever the source code changes. If you don't provide a value for
|
||||||
|
the ``auto_reload`` option, it will be determined automatically based on the
|
||||||
|
``debug`` value.
|
||||||
|
|
||||||
|
* ``strict_variables`` *boolean*
|
||||||
|
|
||||||
|
If set to ``false``, Twig will silently ignore invalid
|
||||||
|
variables (variables and or attributes/methods that do not exist) and
|
||||||
|
replace them with a ``null`` value. When set to ``true``, Twig throws an
|
||||||
|
exception instead (default to ``false``).
|
||||||
|
|
||||||
|
* ``autoescape`` *string*
|
||||||
|
|
||||||
|
Sets the default auto-escaping strategy (``name``, ``html``, ``js``, ``css``,
|
||||||
|
``url``, ``html_attr``, or a PHP callback that takes the template "filename"
|
||||||
|
and returns the escaping strategy to use -- the callback cannot be a function
|
||||||
|
name to avoid collision with built-in escaping strategies); set it to
|
||||||
|
``false`` to disable auto-escaping. The ``name`` escaping strategy determines
|
||||||
|
the escaping strategy to use for a template based on the template filename
|
||||||
|
extension (this strategy does not incur any overhead at runtime as
|
||||||
|
auto-escaping is done at compilation time.)
|
||||||
|
|
||||||
|
* ``optimizations`` *integer*
|
||||||
|
|
||||||
|
A flag that indicates which optimizations to apply
|
||||||
|
(default to ``-1`` -- all optimizations are enabled; set it to ``0`` to
|
||||||
|
disable).
|
||||||
|
|
||||||
|
Loaders
|
||||||
|
-------
|
||||||
|
|
||||||
|
Loaders are responsible for loading templates from a resource such as the file
|
||||||
|
system.
|
||||||
|
|
||||||
|
Compilation Cache
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
All template loaders can cache the compiled templates on the filesystem for
|
||||||
|
future reuse. It speeds up Twig a lot as templates are only compiled once.
|
||||||
|
|
||||||
|
Built-in Loaders
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Here is a list of the built-in loaders:
|
||||||
|
|
||||||
|
``\Twig\Loader\FilesystemLoader``
|
||||||
|
.................................
|
||||||
|
|
||||||
|
``\Twig\Loader\FilesystemLoader`` loads templates from the file system. This loader
|
||||||
|
can find templates in folders on the file system and is the preferred way to
|
||||||
|
load them::
|
||||||
|
|
||||||
|
$loader = new \Twig\Loader\FilesystemLoader($templateDir);
|
||||||
|
|
||||||
|
It can also look for templates in an array of directories::
|
||||||
|
|
||||||
|
$loader = new \Twig\Loader\FilesystemLoader([$templateDir1, $templateDir2]);
|
||||||
|
|
||||||
|
With such a configuration, Twig will first look for templates in
|
||||||
|
``$templateDir1`` and if they do not exist, it will fallback to look for them
|
||||||
|
in the ``$templateDir2``.
|
||||||
|
|
||||||
|
You can add or prepend paths via the ``addPath()`` and ``prependPath()``
|
||||||
|
methods::
|
||||||
|
|
||||||
|
$loader->addPath($templateDir3);
|
||||||
|
$loader->prependPath($templateDir4);
|
||||||
|
|
||||||
|
The filesystem loader also supports namespaced templates. This allows to group
|
||||||
|
your templates under different namespaces which have their own template paths.
|
||||||
|
|
||||||
|
When using the ``setPaths()``, ``addPath()``, and ``prependPath()`` methods,
|
||||||
|
specify the namespace as the second argument (when not specified, these
|
||||||
|
methods act on the "main" namespace)::
|
||||||
|
|
||||||
|
$loader->addPath($templateDir, 'admin');
|
||||||
|
|
||||||
|
Namespaced templates can be accessed via the special
|
||||||
|
``@namespace_name/template_path`` notation::
|
||||||
|
|
||||||
|
$twig->render('@admin/index.html', []);
|
||||||
|
|
||||||
|
``\Twig\Loader\FilesystemLoader`` support absolute and relative paths. Using relative
|
||||||
|
paths is preferred as it makes the cache keys independent of the project root
|
||||||
|
directory (for instance, it allows warming the cache from a build server where
|
||||||
|
the directory might be different from the one used on production servers)::
|
||||||
|
|
||||||
|
$loader = new \Twig\Loader\FilesystemLoader('templates', getcwd().'/..');
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When not passing the root path as a second argument, Twig uses ``getcwd()``
|
||||||
|
for relative paths.
|
||||||
|
|
||||||
|
``\Twig\Loader\ArrayLoader``
|
||||||
|
............................
|
||||||
|
|
||||||
|
``\Twig\Loader\ArrayLoader`` loads a template from a PHP array. It is passed an
|
||||||
|
array of strings bound to template names::
|
||||||
|
|
||||||
|
$loader = new \Twig\Loader\ArrayLoader([
|
||||||
|
'index.html' => 'Hello {{ name }}!',
|
||||||
|
]);
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
|
||||||
|
echo $twig->render('index.html', ['name' => 'Fabien']);
|
||||||
|
|
||||||
|
This loader is very useful for unit testing. It can also be used for small
|
||||||
|
projects where storing all templates in a single PHP file might make sense.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
When using the ``Array`` loader with a cache mechanism, you should know that
|
||||||
|
a new cache key is generated each time a template content "changes" (the
|
||||||
|
cache key being the source code of the template). If you don't want to see
|
||||||
|
your cache grows out of control, you need to take care of clearing the old
|
||||||
|
cache file by yourself.
|
||||||
|
|
||||||
|
``\Twig\Loader\ChainLoader``
|
||||||
|
............................
|
||||||
|
|
||||||
|
``\Twig\Loader\ChainLoader`` delegates the loading of templates to other loaders::
|
||||||
|
|
||||||
|
$loader1 = new \Twig\Loader\ArrayLoader([
|
||||||
|
'base.html' => '{% block content %}{% endblock %}',
|
||||||
|
]);
|
||||||
|
$loader2 = new \Twig\Loader\ArrayLoader([
|
||||||
|
'index.html' => '{% extends "base.html" %}{% block content %}Hello {{ name }}{% endblock %}',
|
||||||
|
'base.html' => 'Will never be loaded',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$loader = new \Twig\Loader\ChainLoader([$loader1, $loader2]);
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
|
||||||
|
When looking for a template, Twig tries each loader in turn and returns as soon
|
||||||
|
as the template is found. When rendering the ``index.html`` template from the
|
||||||
|
above example, Twig will load it with ``$loader2`` but the ``base.html``
|
||||||
|
template will be loaded from ``$loader1``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You can also add loaders via the ``addLoader()`` method.
|
||||||
|
|
||||||
|
Create your own Loader
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
All loaders implement the ``\Twig\Loader\LoaderInterface``::
|
||||||
|
|
||||||
|
interface \Twig\Loader\LoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the source context for a given template logical name.
|
||||||
|
*
|
||||||
|
* @param string $name The template logical name
|
||||||
|
*
|
||||||
|
* @return \Twig\Source
|
||||||
|
*
|
||||||
|
* @throws \Twig\Error\LoaderError When $name is not found
|
||||||
|
*/
|
||||||
|
public function getSourceContext($name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the cache key to use for the cache for a given template name.
|
||||||
|
*
|
||||||
|
* @param string $name The name of the template to load
|
||||||
|
*
|
||||||
|
* @return string The cache key
|
||||||
|
*
|
||||||
|
* @throws \Twig\Error\LoaderError When $name is not found
|
||||||
|
*/
|
||||||
|
public function getCacheKey($name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the template is still fresh.
|
||||||
|
*
|
||||||
|
* @param string $name The template name
|
||||||
|
* @param timestamp $time The last modification time of the cached template
|
||||||
|
*
|
||||||
|
* @return bool true if the template is fresh, false otherwise
|
||||||
|
*
|
||||||
|
* @throws \Twig\Error\LoaderError When $name is not found
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
The ``isFresh()`` method must return ``true`` if the current cached template
|
||||||
|
is still fresh, given the last modification time, or ``false`` otherwise.
|
||||||
|
|
||||||
|
The ``getSourceContext()`` method must return an instance of ``\Twig\Source``.
|
||||||
|
|
||||||
|
Using Extensions
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Twig extensions are packages that add new features to Twig. Register an
|
||||||
|
extension via the ``addExtension()`` method::
|
||||||
|
|
||||||
|
$twig->addExtension(new \Twig\Extension\SandboxExtension());
|
||||||
|
|
||||||
|
Twig comes bundled with the following extensions:
|
||||||
|
|
||||||
|
* *Twig\Extension\CoreExtension*: Defines all the core features of Twig.
|
||||||
|
|
||||||
|
* *Twig\Extension\DebugExtension*: Defines the ``dump`` function to help debug
|
||||||
|
template variables.
|
||||||
|
|
||||||
|
* *Twig\Extension\EscaperExtension*: Adds automatic output-escaping and the
|
||||||
|
possibility to escape/unescape blocks of code.
|
||||||
|
|
||||||
|
* *Twig\Extension\SandboxExtension*: Adds a sandbox mode to the default Twig
|
||||||
|
environment, making it safe to evaluate untrusted code.
|
||||||
|
|
||||||
|
* *Twig\Extension\ProfilerExtension*: Enables the built-in Twig profiler.
|
||||||
|
|
||||||
|
* *Twig\Extension\OptimizerExtension*: Optimizes the node tree before
|
||||||
|
compilation.
|
||||||
|
|
||||||
|
* *Twig\Extension\StringLoaderExtension*: Defines the ``template_from_string``
|
||||||
|
function to allow loading templates from string in a template.
|
||||||
|
|
||||||
|
The Core, Escaper, and Optimizer extensions are registered by default.
|
||||||
|
|
||||||
|
Built-in Extensions
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
This section describes the features added by the built-in extensions.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
Read the chapter about :doc:`extending Twig <advanced>` to learn how to
|
||||||
|
create your own extensions.
|
||||||
|
|
||||||
|
Core Extension
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``core`` extension defines all the core features of Twig:
|
||||||
|
|
||||||
|
* :doc:`Tags <tags/index>`;
|
||||||
|
* :doc:`Filters <filters/index>`;
|
||||||
|
* :doc:`Functions <functions/index>`;
|
||||||
|
* :doc:`Tests <tests/index>`.
|
||||||
|
|
||||||
|
Escaper Extension
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``escaper`` extension adds automatic output escaping to Twig. It defines a
|
||||||
|
tag, ``autoescape``, and a filter, ``raw``.
|
||||||
|
|
||||||
|
When creating the escaper extension, you can switch on or off the global
|
||||||
|
output escaping strategy::
|
||||||
|
|
||||||
|
$escaper = new \Twig\Extension\EscaperExtension('html');
|
||||||
|
$twig->addExtension($escaper);
|
||||||
|
|
||||||
|
If set to ``html``, all variables in templates are escaped (using the ``html``
|
||||||
|
escaping strategy), except those using the ``raw`` filter:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ article.to_html|raw }}
|
||||||
|
|
||||||
|
You can also change the escaping mode locally by using the ``autoescape`` tag:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% autoescape 'html' %}
|
||||||
|
{{ var }}
|
||||||
|
{{ var|raw }} {# var won't be escaped #}
|
||||||
|
{{ var|escape }} {# var won't be double-escaped #}
|
||||||
|
{% endautoescape %}
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The ``autoescape`` tag has no effect on included files.
|
||||||
|
|
||||||
|
The escaping rules are implemented as follows:
|
||||||
|
|
||||||
|
* Literals (integers, booleans, arrays, ...) used in the template directly as
|
||||||
|
variables or filter arguments are never automatically escaped:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{{ "Twig<br/>" }} {# won't be escaped #}
|
||||||
|
|
||||||
|
{% set text = "Twig<br/>" %}
|
||||||
|
{{ text }} {# will be escaped #}
|
||||||
|
|
||||||
|
* Expressions which the result is a literal or a variable marked safe
|
||||||
|
are never automatically escaped:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{{ foo ? "Twig<br/>" : "<br/>Twig" }} {# won't be escaped #}
|
||||||
|
|
||||||
|
{% set text = "Twig<br/>" %}
|
||||||
|
{{ true ? text : "<br/>Twig" }} {# will be escaped #}
|
||||||
|
{{ false ? text : "<br/>Twig" }} {# won't be escaped #}
|
||||||
|
|
||||||
|
{% set text = "Twig<br/>" %}
|
||||||
|
{{ foo ? text|raw : "<br/>Twig" }} {# won't be escaped #}
|
||||||
|
|
||||||
|
* Objects with a ``__toString`` method are converted to strings and
|
||||||
|
escaped. You can mark some classes and/or interfaces as being safe for some
|
||||||
|
strategies via ``EscaperExtension::addSafeClass()``:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
// mark object of class Foo as safe for the HTML strategy
|
||||||
|
$escaper->addSafeClass('Foo', ['html']);
|
||||||
|
|
||||||
|
// mark object of interface Foo as safe for the HTML strategy
|
||||||
|
$escaper->addSafeClass('FooInterface', ['html']);
|
||||||
|
|
||||||
|
// mark object of class Foo as safe for the HTML and JS strategies
|
||||||
|
$escaper->addSafeClass('Foo', ['html', 'js']);
|
||||||
|
|
||||||
|
// mark object of class Foo as safe for all strategies
|
||||||
|
$escaper->addSafeClass('Foo', ['all']);
|
||||||
|
|
||||||
|
* Escaping is applied before printing, after any other filter is applied:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ var|upper }} {# is equivalent to {{ var|upper|escape }} #}
|
||||||
|
|
||||||
|
* The `raw` filter should only be used at the end of the filter chain:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ var|raw|upper }} {# will be escaped #}
|
||||||
|
|
||||||
|
{{ var|upper|raw }} {# won't be escaped #}
|
||||||
|
|
||||||
|
* Automatic escaping is not applied if the last filter in the chain is marked
|
||||||
|
safe for the current context (e.g. ``html`` or ``js``). ``escape`` and
|
||||||
|
``escape('html')`` are marked safe for HTML, ``escape('js')`` is marked
|
||||||
|
safe for JavaScript, ``raw`` is marked safe for everything.
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% autoescape 'js' %}
|
||||||
|
{{ var|escape('html') }} {# will be escaped for HTML and JavaScript #}
|
||||||
|
{{ var }} {# will be escaped for JavaScript #}
|
||||||
|
{{ var|escape('js') }} {# won't be double-escaped #}
|
||||||
|
{% endautoescape %}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Note that autoescaping has some limitations as escaping is applied on
|
||||||
|
expressions after evaluation. For instance, when working with
|
||||||
|
concatenation, ``{{ foo|raw ~ bar }}`` won't give the expected result as
|
||||||
|
escaping is applied on the result of the concatenation, not on the
|
||||||
|
individual variables (so, the ``raw`` filter won't have any effect here).
|
||||||
|
|
||||||
|
Sandbox Extension
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``sandbox`` extension can be used to evaluate untrusted code. Access to
|
||||||
|
unsafe attributes and methods is prohibited. The sandbox security is managed
|
||||||
|
by a policy instance. By default, Twig comes with one policy class:
|
||||||
|
``\Twig\Sandbox\SecurityPolicy``. This class allows you to white-list some
|
||||||
|
tags, filters, properties, and methods::
|
||||||
|
|
||||||
|
$tags = ['if'];
|
||||||
|
$filters = ['upper'];
|
||||||
|
$methods = [
|
||||||
|
'Article' => ['getTitle', 'getBody'],
|
||||||
|
];
|
||||||
|
$properties = [
|
||||||
|
'Article' => ['title', 'body'],
|
||||||
|
];
|
||||||
|
$functions = ['range'];
|
||||||
|
$policy = new \Twig\Sandbox\SecurityPolicy($tags, $filters, $methods, $properties, $functions);
|
||||||
|
|
||||||
|
With the previous configuration, the security policy will only allow usage of
|
||||||
|
the ``if`` tag, and the ``upper`` filter. Moreover, the templates will only be
|
||||||
|
able to call the ``getTitle()`` and ``getBody()`` methods on ``Article``
|
||||||
|
objects, and the ``title`` and ``body`` public properties. Everything else
|
||||||
|
won't be allowed and will generate a ``\Twig\Sandbox\SecurityError`` exception.
|
||||||
|
|
||||||
|
The policy object is the first argument of the sandbox constructor::
|
||||||
|
|
||||||
|
$sandbox = new \Twig\Extension\SandboxExtension($policy);
|
||||||
|
$twig->addExtension($sandbox);
|
||||||
|
|
||||||
|
By default, the sandbox mode is disabled and should be enabled when including
|
||||||
|
untrusted template code by using the ``sandbox`` tag:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% sandbox %}
|
||||||
|
{% include 'user.html' %}
|
||||||
|
{% endsandbox %}
|
||||||
|
|
||||||
|
You can sandbox all templates by passing ``true`` as the second argument of
|
||||||
|
the extension constructor::
|
||||||
|
|
||||||
|
$sandbox = new \Twig\Extension\SandboxExtension($policy, true);
|
||||||
|
|
||||||
|
Profiler Extension
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``profiler`` extension enables a profiler for Twig templates; it should
|
||||||
|
only be used on your development machines as it adds some overhead::
|
||||||
|
|
||||||
|
$profile = new \Twig\Profiler\Profile();
|
||||||
|
$twig->addExtension(new \Twig\Extension\ProfilerExtension($profile));
|
||||||
|
|
||||||
|
$dumper = new \Twig\Profiler\Dumper\TextDumper();
|
||||||
|
echo $dumper->dump($profile);
|
||||||
|
|
||||||
|
A profile contains information about time and memory consumption for template,
|
||||||
|
block, and macro executions.
|
||||||
|
|
||||||
|
You can also dump the data in a `Blackfire.io <https://blackfire.io/>`_
|
||||||
|
compatible format::
|
||||||
|
|
||||||
|
$dumper = new \Twig\Profiler\Dumper\BlackfireDumper();
|
||||||
|
file_put_contents('/path/to/profile.prof', $dumper->dump($profile));
|
||||||
|
|
||||||
|
Upload the profile to visualize it (create a `free account
|
||||||
|
<https://blackfire.io/signup?utm_source=twig&utm_medium=doc&utm_campaign=profiler>`_
|
||||||
|
first):
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
blackfire --slot=7 upload /path/to/profile.prof
|
||||||
|
|
||||||
|
Optimizer Extension
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ``optimizer`` extension optimizes the node tree before compilation::
|
||||||
|
|
||||||
|
$twig->addExtension(new \Twig\Extension\OptimizerExtension());
|
||||||
|
|
||||||
|
By default, all optimizations are turned on. You can select the ones you want
|
||||||
|
to enable by passing them to the constructor::
|
||||||
|
|
||||||
|
$optimizer = new \Twig\Extension\OptimizerExtension(\Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_FOR);
|
||||||
|
|
||||||
|
$twig->addExtension($optimizer);
|
||||||
|
|
||||||
|
Twig supports the following optimizations:
|
||||||
|
|
||||||
|
* ``\Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_ALL``, enables all optimizations
|
||||||
|
(this is the default value).
|
||||||
|
|
||||||
|
* ``\Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_NONE``, disables all optimizations.
|
||||||
|
This reduces the compilation time, but it can increase the execution time
|
||||||
|
and the consumed memory.
|
||||||
|
|
||||||
|
* ``\Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_FOR``, optimizes the ``for`` tag by
|
||||||
|
removing the ``loop`` variable creation whenever possible.
|
||||||
|
|
||||||
|
* ``\Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER``, removes the ``raw``
|
||||||
|
filter whenever possible.
|
||||||
|
|
||||||
|
* ``\Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_VAR_ACCESS``, simplifies the creation
|
||||||
|
and access of variables in the compiled templates whenever possible.
|
||||||
|
|
||||||
|
Exceptions
|
||||||
|
----------
|
||||||
|
|
||||||
|
Twig can throw exceptions:
|
||||||
|
|
||||||
|
* ``\Twig\Error\Error``: The base exception for all errors.
|
||||||
|
|
||||||
|
* ``\Twig\Error\SyntaxError``: Thrown to tell the user that there is a problem with
|
||||||
|
the template syntax.
|
||||||
|
|
||||||
|
* ``\Twig\Error\RuntimeError``: Thrown when an error occurs at runtime (when a filter
|
||||||
|
does not exist for instance).
|
||||||
|
|
||||||
|
* ``\Twig\Error\LoaderError``: Thrown when an error occurs during template loading.
|
||||||
|
|
||||||
|
* ``\Twig\Sandbox\SecurityError``: Thrown when an unallowed tag, filter, or
|
||||||
|
method is called in a sandboxed template.
|
|
@ -0,0 +1,101 @@
|
||||||
|
Coding Standards
|
||||||
|
================
|
||||||
|
|
||||||
|
When writing Twig templates, we recommend you to follow these official coding
|
||||||
|
standards:
|
||||||
|
|
||||||
|
* Put one (and only one) space after the start of a delimiter (``{{``, ``{%``,
|
||||||
|
and ``{#``) and before the end of a delimiter (``}}``, ``%}``, and ``#}``):
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ foo }}
|
||||||
|
{# comment #}
|
||||||
|
{% if foo %}{% endif %}
|
||||||
|
|
||||||
|
When using the whitespace control character, do not put any spaces between
|
||||||
|
it and the delimiter:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{- foo -}}
|
||||||
|
{#- comment -#}
|
||||||
|
{%- if foo -%}{%- endif -%}
|
||||||
|
|
||||||
|
* Put one (and only one) space before and after the following operators:
|
||||||
|
comparison operators (``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``), math
|
||||||
|
operators (``+``, ``-``, ``/``, ``*``, ``%``, ``//``, ``**``), logic
|
||||||
|
operators (``not``, ``and``, ``or``), ``~``, ``is``, ``in``, and the ternary
|
||||||
|
operator (``?:``):
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 1 + 2 }}
|
||||||
|
{{ foo ~ bar }}
|
||||||
|
{{ true ? true : false }}
|
||||||
|
|
||||||
|
* Put one (and only one) space after the ``:`` sign in hashes and ``,`` in
|
||||||
|
arrays and hashes:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ [1, 2, 3] }}
|
||||||
|
{{ {'foo': 'bar'} }}
|
||||||
|
|
||||||
|
* Do not put any spaces after an opening parenthesis and before a closing
|
||||||
|
parenthesis in expressions:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 1 + (2 * 3) }}
|
||||||
|
|
||||||
|
* Do not put any spaces before and after string delimiters:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'foo' }}
|
||||||
|
{{ "foo" }}
|
||||||
|
|
||||||
|
* Do not put any spaces before and after the following operators: ``|``,
|
||||||
|
``.``, ``..``, ``[]``:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ foo|upper|lower }}
|
||||||
|
{{ user.name }}
|
||||||
|
{{ user[name] }}
|
||||||
|
{% for i in 1..12 %}{% endfor %}
|
||||||
|
|
||||||
|
* Do not put any spaces before and after the parenthesis used for filter and
|
||||||
|
function calls:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ foo|default('foo') }}
|
||||||
|
{{ range(1..10) }}
|
||||||
|
|
||||||
|
* Do not put any spaces before and after the opening and the closing of arrays
|
||||||
|
and hashes:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ [1, 2, 3] }}
|
||||||
|
{{ {'foo': 'bar'} }}
|
||||||
|
|
||||||
|
* Use lower cased and underscored variable names:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set foo = 'foo' %}
|
||||||
|
{% set foo_bar = 'foo' %}
|
||||||
|
|
||||||
|
* Indent your code inside tags (use the same indentation as the one used for
|
||||||
|
the target language of the rendered template):
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% block foo %}
|
||||||
|
{% if true %}
|
||||||
|
true
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,6 @@
|
||||||
|
Deprecated Features
|
||||||
|
===================
|
||||||
|
|
||||||
|
This document lists deprecated features in Twig 3.x. Deprecated features are
|
||||||
|
kept for backward compatibility and removed in the next major release (a
|
||||||
|
feature that was deprecated in Twig 3.x is removed in Twig 4.0).
|
|
@ -0,0 +1,18 @@
|
||||||
|
``abs``
|
||||||
|
=======
|
||||||
|
|
||||||
|
The ``abs`` filter returns the absolute value.
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# number = -5 #}
|
||||||
|
|
||||||
|
{{ number|abs }}
|
||||||
|
|
||||||
|
{# outputs 5 #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, Twig uses the PHP `abs`_ function.
|
||||||
|
|
||||||
|
.. _`abs`: https://secure.php.net/abs
|
|
@ -0,0 +1,44 @@
|
||||||
|
``batch``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``batch`` filter "batches" items by returning a list of lists with the
|
||||||
|
given number of items. A second parameter can be provided and used to fill in
|
||||||
|
missing items:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% set items = ['a', 'b', 'c', 'd'] %}
|
||||||
|
|
||||||
|
<table>
|
||||||
|
{% for row in items|batch(3, 'No item') %}
|
||||||
|
<tr>
|
||||||
|
{% for column in row %}
|
||||||
|
<td>{{ column }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
The above example will be rendered as:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>a</td>
|
||||||
|
<td>b</td>
|
||||||
|
<td>c</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>d</td>
|
||||||
|
<td>No item</td>
|
||||||
|
<td>No item</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``size``: The size of the batch; fractional numbers will be rounded up
|
||||||
|
* ``fill``: Used to fill in missing items
|
||||||
|
* ``preserve_keys``: Whether to preserve keys or not
|
|
@ -0,0 +1,11 @@
|
||||||
|
``capitalize``
|
||||||
|
==============
|
||||||
|
|
||||||
|
The ``capitalize`` filter capitalizes a value. The first character will be
|
||||||
|
uppercase, all others lowercase:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'my first car'|capitalize }}
|
||||||
|
|
||||||
|
{# outputs 'My first car' #}
|
|
@ -0,0 +1,24 @@
|
||||||
|
``column``
|
||||||
|
==========
|
||||||
|
|
||||||
|
The ``column`` filter returns the values from a single column in the input
|
||||||
|
array.
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
|
||||||
|
|
||||||
|
{% set fruits = items|column('fruit') %}
|
||||||
|
|
||||||
|
{# fruits now contains ['apple', 'orange'] #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, Twig uses the PHP `array_column`_ function.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``name``: The column name to extract
|
||||||
|
|
||||||
|
.. _`array_column`: https://secure.php.net/array_column
|
|
@ -0,0 +1,22 @@
|
||||||
|
``convert_encoding``
|
||||||
|
====================
|
||||||
|
|
||||||
|
The ``convert_encoding`` filter converts a string from one encoding to
|
||||||
|
another. The first argument is the expected output charset and the second one
|
||||||
|
is the input charset:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This filter relies on the `iconv`_ extension.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``to``: The output charset
|
||||||
|
* ``from``: The input charset
|
||||||
|
|
||||||
|
.. _`iconv`: https://secure.php.net/iconv
|
|
@ -0,0 +1,44 @@
|
||||||
|
``country_name``
|
||||||
|
================
|
||||||
|
|
||||||
|
The ``country_name`` filter returns the country name given its ISO-3166
|
||||||
|
two-letter code:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# France #}
|
||||||
|
{{ 'FR'|country_name }}
|
||||||
|
|
||||||
|
By default, the filter uses the current locale. You can pass it explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# États-Unis #}
|
||||||
|
{{ 'US'|country_name('fr') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``country_name`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
|
@ -0,0 +1,47 @@
|
||||||
|
``currency_name``
|
||||||
|
=================
|
||||||
|
|
||||||
|
The ``currency_name`` filter returns the currency name given its three-letter
|
||||||
|
code:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# Euro #}
|
||||||
|
{{ 'EUR'|currency_name }}
|
||||||
|
|
||||||
|
{# Japanese Yen #}
|
||||||
|
{{ 'JPY'|currency_name }}
|
||||||
|
|
||||||
|
By default, the filter uses the current locale. You can pass it explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# yen japonais #}
|
||||||
|
{{ 'JPY'|currency_name('fr_FR') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``currency_name`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
|
@ -0,0 +1,47 @@
|
||||||
|
``currency_symbol``
|
||||||
|
===================
|
||||||
|
|
||||||
|
The ``currency_symbol`` filter returns the currency symbol given its three-letter
|
||||||
|
code:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# € #}
|
||||||
|
{{ 'EUR'|currency_symbol }}
|
||||||
|
|
||||||
|
{# ¥ #}
|
||||||
|
{{ 'JPY'|currency_symbol }}
|
||||||
|
|
||||||
|
By default, the filter uses the current locale. You can pass it explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# ¥ #}
|
||||||
|
{{ 'JPY'|currency_symbol('fr') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``currency_symbol`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
|
@ -0,0 +1,55 @@
|
||||||
|
``data_uri``
|
||||||
|
============
|
||||||
|
|
||||||
|
The ``data_uri`` filter generates a URL using the data scheme as defined in
|
||||||
|
`RFC 2397`_:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{{ image_data|data_uri }}
|
||||||
|
|
||||||
|
{{ source('path_to_image')|data_uri }}
|
||||||
|
|
||||||
|
{# force the mime type, disable the guessing of the mime type #}
|
||||||
|
{{ image_data|data_uri(mime="image/svg") }}
|
||||||
|
|
||||||
|
{# also works with plain text #}
|
||||||
|
{{ '<b>foobar</b>'|data_uri(mime="text/html") }}
|
||||||
|
|
||||||
|
{# add some extra parameters #}
|
||||||
|
{{ '<b>foobar</b>'|data_uri(mime="text/html", parameters={charset: "ascii"}) }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``data_uri`` filter is part of the ``HtmlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/html-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Html\HtmlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new HtmlExtension());
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The filter does not perform any length validation on purpose (limit depends
|
||||||
|
on the usage context), validation should be done before calling this filter.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``mime``: The mime type
|
||||||
|
* ``parameters``: An array of parameters
|
||||||
|
|
||||||
|
.. _RFC 2397: https://tools.ietf.org/html/rfc2397
|
|
@ -0,0 +1,78 @@
|
||||||
|
``date``
|
||||||
|
========
|
||||||
|
|
||||||
|
The ``date`` filter formats a date to a given format:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ post.published_at|date("m/d/Y") }}
|
||||||
|
|
||||||
|
The format specifier is the same as supported by `date`_,
|
||||||
|
except when the filtered data is of type `DateInterval`_, when the format must conform to
|
||||||
|
`DateInterval::format`_ instead.
|
||||||
|
|
||||||
|
The ``date`` filter accepts strings (it must be in a format supported by the
|
||||||
|
`strtotime`_ function), `DateTime`_ instances, or `DateInterval`_ instances. For
|
||||||
|
instance, to display the current date, filter the word "now":
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ "now"|date("m/d/Y") }}
|
||||||
|
|
||||||
|
To escape words and characters in the date format use ``\\`` in front of each
|
||||||
|
character:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ post.published_at|date("F jS \\a\\t g:ia") }}
|
||||||
|
|
||||||
|
If the value passed to the ``date`` filter is ``null``, it will return the
|
||||||
|
current date by default. If an empty string is desired instead of the current
|
||||||
|
date, use a ternary operator:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ post.published_at is empty ? "" : post.published_at|date("m/d/Y") }}
|
||||||
|
|
||||||
|
If no format is provided, Twig will use the default one: ``F j, Y H:i``. This
|
||||||
|
default can be changed by calling the ``setDateFormat()`` method on the
|
||||||
|
``core`` extension instance. The first argument is the default format for
|
||||||
|
dates and the second one is the default format for date intervals::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->getExtension(\Twig\Extension\CoreExtension::class)->setDateFormat('d/m/Y', '%d days');
|
||||||
|
|
||||||
|
Timezone
|
||||||
|
--------
|
||||||
|
|
||||||
|
By default, the date is displayed by applying the default timezone (the one
|
||||||
|
specified in php.ini or declared in Twig -- see below), but you can override
|
||||||
|
it by explicitly specifying a timezone:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ post.published_at|date("m/d/Y", "Europe/Paris") }}
|
||||||
|
|
||||||
|
If the date is already a DateTime object, and if you want to keep its current
|
||||||
|
timezone, pass ``false`` as the timezone value:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ post.published_at|date("m/d/Y", false) }}
|
||||||
|
|
||||||
|
The default timezone can also be set globally by calling ``setTimezone()``::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->getExtension(\Twig\Extension\CoreExtension::class)->setTimezone('Europe/Paris');
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``format``: The date format
|
||||||
|
* ``timezone``: The date timezone
|
||||||
|
|
||||||
|
.. _`strtotime`: https://secure.php.net/strtotime
|
||||||
|
.. _`DateTime`: https://secure.php.net/DateTime
|
||||||
|
.. _`DateInterval`: https://secure.php.net/DateInterval
|
||||||
|
.. _`date`: https://secure.php.net/date
|
||||||
|
.. _`DateInterval::format`: https://secure.php.net/DateInterval.format
|
|
@ -0,0 +1,20 @@
|
||||||
|
``date_modify``
|
||||||
|
===============
|
||||||
|
|
||||||
|
The ``date_modify`` filter modifies a date with a given modifier string:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ post.published_at|date_modify("+1 day")|date("m/d/Y") }}
|
||||||
|
|
||||||
|
The ``date_modify`` filter accepts strings (it must be in a format supported
|
||||||
|
by the `strtotime`_ function) or `DateTime`_ instances. You can combine
|
||||||
|
it with the :doc:`date<date>` filter for formatting.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``modifier``: The modifier
|
||||||
|
|
||||||
|
.. _`strtotime`: https://secure.php.net/strtotime
|
||||||
|
.. _`DateTime`: https://secure.php.net/DateTime
|
|
@ -0,0 +1,42 @@
|
||||||
|
``default``
|
||||||
|
===========
|
||||||
|
|
||||||
|
The ``default`` filter returns the passed default value if the value is
|
||||||
|
undefined or empty, otherwise the value of the variable:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ var|default('var is not defined') }}
|
||||||
|
|
||||||
|
{{ var.foo|default('foo item on var is not defined') }}
|
||||||
|
|
||||||
|
{{ var['foo']|default('foo item on var is not defined') }}
|
||||||
|
|
||||||
|
{{ ''|default('passed var is empty') }}
|
||||||
|
|
||||||
|
When using the ``default`` filter on an expression that uses variables in some
|
||||||
|
method calls, be sure to use the ``default`` filter whenever a variable can be
|
||||||
|
undefined:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ var.method(foo|default('foo'))|default('foo') }}
|
||||||
|
|
||||||
|
Using the ``default`` filter on a boolean variable might trigger unexpected behavior, as
|
||||||
|
``false`` is treated as an empty value. Consider using ``??`` instead:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set foo = false %}
|
||||||
|
{{ foo|default(true) }} {# true #}
|
||||||
|
{{ foo ?? true }} {# false #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Read the documentation for the :doc:`defined<../tests/defined>` and
|
||||||
|
:doc:`empty<../tests/empty>` tests to learn more about their semantics.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``default``: The default value
|
|
@ -0,0 +1,117 @@
|
||||||
|
``escape``
|
||||||
|
==========
|
||||||
|
|
||||||
|
The ``escape`` filter escapes a string using strategies that depend on the
|
||||||
|
context.
|
||||||
|
|
||||||
|
By default, it uses the HTML escaping strategy:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{ user.username|escape }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
For convenience, the ``e`` filter is defined as an alias:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{ user.username|e }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
The ``escape`` filter can also be used in other contexts than HTML thanks to
|
||||||
|
an optional argument which defines the escaping strategy to use:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ user.username|e }}
|
||||||
|
{# is equivalent to #}
|
||||||
|
{{ user.username|e('html') }}
|
||||||
|
|
||||||
|
And here is how to escape variables included in JavaScript code:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ user.username|escape('js') }}
|
||||||
|
{{ user.username|e('js') }}
|
||||||
|
|
||||||
|
The ``escape`` filter supports the following escaping strategies for HTML
|
||||||
|
documents:
|
||||||
|
|
||||||
|
* ``html``: escapes a string for the **HTML body** context.
|
||||||
|
|
||||||
|
* ``js``: escapes a string for the **JavaScript** context.
|
||||||
|
|
||||||
|
* ``css``: escapes a string for the **CSS** context. CSS escaping can be
|
||||||
|
applied to any string being inserted into CSS and escapes everything except
|
||||||
|
alphanumerics.
|
||||||
|
|
||||||
|
* ``url``: escapes a string for the **URI or parameter** contexts. This should
|
||||||
|
not be used to escape an entire URI; only a subcomponent being inserted.
|
||||||
|
|
||||||
|
* ``html_attr``: escapes a string for the **HTML attribute** context.
|
||||||
|
|
||||||
|
Note that doing contextual escaping in HTML documents is hard and choosing the
|
||||||
|
right escaping strategy depends on a lot of factors. Please, read related
|
||||||
|
documentation like `the OWASP prevention cheat sheet
|
||||||
|
<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md>`_
|
||||||
|
to learn more about this topic.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, ``escape`` uses the PHP native `htmlspecialchars`_ function
|
||||||
|
for the HTML escaping strategy.
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
When using automatic escaping, Twig tries to not double-escape a variable
|
||||||
|
when the automatic escaping strategy is the same as the one applied by the
|
||||||
|
escape filter; but that does not work when using a variable as the
|
||||||
|
escaping strategy:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set strategy = 'html' %}
|
||||||
|
|
||||||
|
{% autoescape 'html' %}
|
||||||
|
{{ var|escape('html') }} {# won't be double-escaped #}
|
||||||
|
{{ var|escape(strategy) }} {# will be double-escaped #}
|
||||||
|
{% endautoescape %}
|
||||||
|
|
||||||
|
When using a variable as the escaping strategy, you should disable
|
||||||
|
automatic escaping:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set strategy = 'html' %}
|
||||||
|
|
||||||
|
{% autoescape 'html' %}
|
||||||
|
{{ var|escape(strategy)|raw }} {# won't be double-escaped #}
|
||||||
|
{% endautoescape %}
|
||||||
|
|
||||||
|
Custom Escapers
|
||||||
|
---------------
|
||||||
|
|
||||||
|
You can define custom escapers by calling the ``setEscaper()`` method on the
|
||||||
|
escaper extension instance. The first argument is the escaper name (to be
|
||||||
|
used in the ``escape`` call) and the second one must be a valid PHP callable::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->getExtension(\Twig\Extension\EscaperExtension::class)->setEscaper('csv', 'csv_escaper');
|
||||||
|
|
||||||
|
When called by Twig, the callable receives the Twig environment instance, the
|
||||||
|
string to escape, and the charset.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Built-in escapers cannot be overridden mainly because they should be
|
||||||
|
considered as the final implementation and also for better performance.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``strategy``: The escaping strategy
|
||||||
|
* ``charset``: The string charset
|
||||||
|
|
||||||
|
.. _`htmlspecialchars`: https://secure.php.net/htmlspecialchars
|
|
@ -0,0 +1,55 @@
|
||||||
|
``filter``
|
||||||
|
==========
|
||||||
|
|
||||||
|
The ``filter`` filter filters elements of a sequence or a mapping using an arrow
|
||||||
|
function. The arrow function receives the value of the sequence or mapping:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set sizes = [34, 36, 38, 40, 42] %}
|
||||||
|
|
||||||
|
{{ sizes|filter(v => v > 38)|join(', ') }}
|
||||||
|
{# output 40, 42 #}
|
||||||
|
|
||||||
|
Combined with the ``for`` tag, it allows to filter the items to iterate over:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% for v in sizes|filter(v => v > 38) -%}
|
||||||
|
{{ v }}
|
||||||
|
{% endfor %}
|
||||||
|
{# output 40 42 #}
|
||||||
|
|
||||||
|
It also works with mappings:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set sizes = {
|
||||||
|
xs: 34,
|
||||||
|
s: 36,
|
||||||
|
m: 38,
|
||||||
|
l: 40,
|
||||||
|
xl: 42,
|
||||||
|
} %}
|
||||||
|
|
||||||
|
{% for k, v in sizes|filter(v => v > 38) -%}
|
||||||
|
{{ k }} = {{ v }}
|
||||||
|
{% endfor %}
|
||||||
|
{# output l = 40 xl = 42 #}
|
||||||
|
|
||||||
|
The arrow function also receives the key as a second argument:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% for k, v in sizes|filter((v, k) => v > 38 and k != "xl") -%}
|
||||||
|
{{ k }} = {{ v }}
|
||||||
|
{% endfor %}
|
||||||
|
{# output l = 40 #}
|
||||||
|
|
||||||
|
Note that the arrow function has access to the current context.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``array``: The sequence or mapping
|
||||||
|
* ``arrow``: The arrow function
|
|
@ -0,0 +1,22 @@
|
||||||
|
``first``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``first`` filter returns the first "element" of a sequence, a mapping, or
|
||||||
|
a string:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ [1, 2, 3, 4]|first }}
|
||||||
|
{# outputs 1 #}
|
||||||
|
|
||||||
|
{{ { a: 1, b: 2, c: 3, d: 4 }|first }}
|
||||||
|
{# outputs 1 #}
|
||||||
|
|
||||||
|
{{ '1234'|first }}
|
||||||
|
{# outputs 1 #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It also works with objects implementing the `Traversable`_ interface.
|
||||||
|
|
||||||
|
.. _`Traversable`: https://secure.php.net/manual/en/class.traversable.php
|
|
@ -0,0 +1,18 @@
|
||||||
|
``format``
|
||||||
|
==========
|
||||||
|
|
||||||
|
The ``format`` filter formats a given string by replacing the placeholders
|
||||||
|
(placeholders follows the `sprintf`_ notation):
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ "I like %s and %s."|format(foo, "bar") }}
|
||||||
|
|
||||||
|
{# outputs I like foo and bar
|
||||||
|
if the foo parameter equals to the foo string. #}
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:doc:`replace<replace>`
|
||||||
|
|
||||||
|
.. _`sprintf`: https://secure.php.net/sprintf
|
|
@ -0,0 +1,77 @@
|
||||||
|
``format_currency``
|
||||||
|
===================
|
||||||
|
|
||||||
|
The ``format_currency`` filter formats a number as a currency:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# €1,000,000.00 #}
|
||||||
|
{{ '1000000'|format_currency('EUR') }}
|
||||||
|
|
||||||
|
You can pass attributes to tweak the output:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# €12.34 #}
|
||||||
|
{{ '12.345'|format_currency('EUR', {rounding_mode: 'floor'}) }}
|
||||||
|
|
||||||
|
{# €1,000,000.0000 #}
|
||||||
|
{{ '1000000'|format_currency('EUR', {fraction_digit: 4}) }}
|
||||||
|
|
||||||
|
The list of supported options:
|
||||||
|
|
||||||
|
* ``grouping_used``;
|
||||||
|
* ``decimal_always_shown``;
|
||||||
|
* ``max_integer_digit``;
|
||||||
|
* ``min_integer_digit``;
|
||||||
|
* ``integer_digit``;
|
||||||
|
* ``max_fraction_digit``;
|
||||||
|
* ``min_fraction_digit``;
|
||||||
|
* ``fraction_digit``;
|
||||||
|
* ``multiplier``;
|
||||||
|
* ``grouping_size``;
|
||||||
|
* ``rounding_mode``;
|
||||||
|
* ``rounding_increment``;
|
||||||
|
* ``format_width``;
|
||||||
|
* ``padding_position``;
|
||||||
|
* ``secondary_grouping_size``;
|
||||||
|
* ``significant_digits_used``;
|
||||||
|
* ``min_significant_digits_used``;
|
||||||
|
* ``max_significant_digits_used``;
|
||||||
|
* ``lenient_parse``.
|
||||||
|
|
||||||
|
By default, the filter uses the current locale. You can pass it explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# 1.000.000,00 € #}
|
||||||
|
{{ '1000000'|format_currency('EUR', locale='de') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``format_currency`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``currency``: The currency
|
||||||
|
* ``attrs``: A map of attributes
|
||||||
|
* ``locale``: The locale
|
|
@ -0,0 +1,36 @@
|
||||||
|
``format_date``
|
||||||
|
===============
|
||||||
|
|
||||||
|
The ``format_date`` filter formats a date. It behaves in the exact same way as
|
||||||
|
the :doc:`format_datetime<format_datetime>` filter, but without the time.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``format_date`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
||||||
|
* ``dateFormat``: The date format
|
||||||
|
* ``pattern``: A date time pattern
|
||||||
|
* ``timezone``: The date timezone
|
||||||
|
* ``calendar``: The calendar (Gregorian by default)
|
|
@ -0,0 +1,73 @@
|
||||||
|
``format_datetime``
|
||||||
|
===================
|
||||||
|
|
||||||
|
The ``format_datetime`` filter formats a date time:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# Aug 7, 2019, 11:39:12 PM #}
|
||||||
|
{{ '2019-08-07 23:39:12'|format_datetime() }}
|
||||||
|
|
||||||
|
You can tweak the output for the date part and the time part:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# 23:39 #}
|
||||||
|
{{ '2019-08-07 23:39:12'|format_datetime('none', 'short', locale='fr') }}
|
||||||
|
|
||||||
|
{# 07/08/2019 #}
|
||||||
|
{{ '2019-08-07 23:39:12'|format_datetime('short', 'none', locale='fr') }}
|
||||||
|
|
||||||
|
{# mercredi 7 août 2019 23:39:12 UTC #}
|
||||||
|
{{ '2019-08-07 23:39:12'|format_datetime('full', 'full', locale='fr') }}
|
||||||
|
|
||||||
|
Supported values are: ``none``, ``short``, ``medium``, ``long``, and ``full``.
|
||||||
|
|
||||||
|
For greater flexiblity, you can even define your own pattern (see the `ICU user
|
||||||
|
guide
|
||||||
|
<https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax>`_
|
||||||
|
for supported patterns).
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# 11 oclock PM, GMT #}
|
||||||
|
{{ '2019-08-07 23:39:12'|format_datetime(pattern="hh 'oclock' a, zzzz") }}
|
||||||
|
|
||||||
|
By default, the filter uses the current locale. You can pass it explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# 7 août 2019 23:39:12 #}
|
||||||
|
{{ '2019-08-07 23:39:12'|format_datetime(locale='fr') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``format_datetime`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
||||||
|
* ``dateFormat``: The date format
|
||||||
|
* ``timeFormat``: The time format
|
||||||
|
* ``pattern``: A date time pattern
|
||||||
|
* ``timezone``: The date timezone
|
||||||
|
* ``calendar``: The calendar (Gregorian by default)
|
|
@ -0,0 +1,117 @@
|
||||||
|
``format_number``
|
||||||
|
=================
|
||||||
|
|
||||||
|
The ``format_number`` filter formats a number:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ '12.345'|format_number }}
|
||||||
|
|
||||||
|
You can pass attributes to tweak the output:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# 12.34 #}
|
||||||
|
{{ '12.345'|format_number({rounding_mode: 'floor'}) }}
|
||||||
|
|
||||||
|
{# 1000000.0000 #}
|
||||||
|
{{ '1000000'|format_number({fraction_digit: 4}) }}
|
||||||
|
|
||||||
|
The list of supported options:
|
||||||
|
|
||||||
|
* ``grouping_used``;
|
||||||
|
* ``decimal_always_shown``;
|
||||||
|
* ``max_integer_digit``;
|
||||||
|
* ``min_integer_digit``;
|
||||||
|
* ``integer_digit``;
|
||||||
|
* ``max_fraction_digit``;
|
||||||
|
* ``min_fraction_digit``;
|
||||||
|
* ``fraction_digit``;
|
||||||
|
* ``multiplier``;
|
||||||
|
* ``grouping_size``;
|
||||||
|
* ``rounding_mode``;
|
||||||
|
* ``rounding_increment``;
|
||||||
|
* ``format_width``;
|
||||||
|
* ``padding_position``;
|
||||||
|
* ``secondary_grouping_size``;
|
||||||
|
* ``significant_digits_used``;
|
||||||
|
* ``min_significant_digits_used``;
|
||||||
|
* ``max_significant_digits_used``;
|
||||||
|
* ``lenient_parse``.
|
||||||
|
|
||||||
|
Besides plain numbers, the filter can also format numbers in various styles:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# 1,234% #}
|
||||||
|
{{ '12.345'|format_number(style='percent') }}
|
||||||
|
|
||||||
|
{# twelve point three four five #}
|
||||||
|
{{ '12.345'|format_number(style='spellout') }}
|
||||||
|
|
||||||
|
{# 12 sec. #}
|
||||||
|
{{ '12'|format_duration_number }}
|
||||||
|
|
||||||
|
The list of supported styles:
|
||||||
|
|
||||||
|
* ``decimal``;
|
||||||
|
* ``currency``;
|
||||||
|
* ``percent``;
|
||||||
|
* ``scientific``;
|
||||||
|
* ``spellout``;
|
||||||
|
* ``ordinal``;
|
||||||
|
* ``duration``.
|
||||||
|
|
||||||
|
As a shortcut, you can use the ``format_*_number`` filters by replacing `*` with
|
||||||
|
a style:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# 1,234% #}
|
||||||
|
{{ '12.345'|format_percent_number }}
|
||||||
|
|
||||||
|
{# twelve point three four five #}
|
||||||
|
{{ '12.345'|format_spellout_number }}
|
||||||
|
|
||||||
|
You can pass attributes to tweak the output:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# 12.3% #}
|
||||||
|
{{ '0.12345'|format_percent_number({rounding_mode: 'floor', fraction_digit: 1}) }}
|
||||||
|
|
||||||
|
By default, the filter uses the current locale. You can pass it explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# 12,345 #}
|
||||||
|
{{ '12.345'|format_number(locale='fr') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``format_number`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
||||||
|
* ``attrs``: A map of attributes
|
||||||
|
* ``style``: The style of the number output
|
|
@ -0,0 +1,36 @@
|
||||||
|
``format_time``
|
||||||
|
===============
|
||||||
|
|
||||||
|
The ``format_time`` filter formats a time. It behaves in the exact same way as
|
||||||
|
the :doc:`format_datetime<format_datetime>` filter, but without the date.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``format_time`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
||||||
|
* ``timeFormat``: The time format
|
||||||
|
* ``pattern``: A date time pattern
|
||||||
|
* ``timezone``: The date timezone
|
||||||
|
* ``calendar``: The calendar (Gregorian by default)
|
|
@ -0,0 +1,77 @@
|
||||||
|
``html_to_markdown``
|
||||||
|
====================
|
||||||
|
|
||||||
|
The ``html_to_markdown`` filter converts a block of HTML to Markdown:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% apply html_to_markdown %}
|
||||||
|
<html>
|
||||||
|
<h1>Hello!</h1>
|
||||||
|
</html>
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
You can also use the filter on an entire template which you ``include``:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ include('some_template.html.twig')|html_to_markdown }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``html_to_markdown`` filter is part of the ``MarkdownExtension`` which
|
||||||
|
is not installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/markdown-extra
|
||||||
|
|
||||||
|
On Symfony projects, you can automatically enable it by installing the
|
||||||
|
``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Or add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Markdown\MarkdownExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new MarkdownExtension());
|
||||||
|
|
||||||
|
If you are not using Symfony, you must also register the extension runtime::
|
||||||
|
|
||||||
|
use Twig\Extra\Markdown\DefaultMarkdown;
|
||||||
|
use Twig\Extra\Markdown\MarkdownRuntime;
|
||||||
|
use Twig\RuntimeLoader\RuntimeLoaderInterface;
|
||||||
|
|
||||||
|
$twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
|
||||||
|
public function load($class) {
|
||||||
|
if (MarkdownRuntime::class === $class) {
|
||||||
|
return new MarkdownRuntime(new DefaultMarkdown());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
``html_to_markdown`` is just a frontend; the actual conversion is done by one of
|
||||||
|
the following compatible libraries, from which you can choose:
|
||||||
|
|
||||||
|
* `erusev/parsedown`_
|
||||||
|
* `league/html-to-markdown`_
|
||||||
|
* `michelf/php-markdown`_
|
||||||
|
|
||||||
|
Depending on the library, you can also add some options by passing them as an argument
|
||||||
|
to the filter. Example for ``league/html-to-markdown``:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% apply html_to_markdown({hard_break: false}) %}
|
||||||
|
<html>
|
||||||
|
<h1>Hello!</h1>
|
||||||
|
</html>
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
.. _erusev/parsedown: https://github.com/erusev/parsedown
|
||||||
|
.. _league/html-to-markdown: https://github.com/thephpleague/html-to-markdown
|
||||||
|
.. _michelf/php-markdown: https://github.com/michelf/php-markdown
|
|
@ -0,0 +1,60 @@
|
||||||
|
Filters
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
abs
|
||||||
|
batch
|
||||||
|
capitalize
|
||||||
|
column
|
||||||
|
convert_encoding
|
||||||
|
country_name
|
||||||
|
currency_name
|
||||||
|
currency_symbol
|
||||||
|
data_uri
|
||||||
|
date
|
||||||
|
date_modify
|
||||||
|
default
|
||||||
|
escape
|
||||||
|
filter
|
||||||
|
first
|
||||||
|
format
|
||||||
|
format_currency
|
||||||
|
format_date
|
||||||
|
format_datetime
|
||||||
|
format_number
|
||||||
|
format_time
|
||||||
|
html_to_markdown
|
||||||
|
inline_css
|
||||||
|
inky_to_html
|
||||||
|
join
|
||||||
|
json_encode
|
||||||
|
keys
|
||||||
|
language_name
|
||||||
|
last
|
||||||
|
length
|
||||||
|
locale_name
|
||||||
|
lower
|
||||||
|
map
|
||||||
|
markdown_to_html
|
||||||
|
merge
|
||||||
|
nl2br
|
||||||
|
number_format
|
||||||
|
raw
|
||||||
|
reduce
|
||||||
|
replace
|
||||||
|
reverse
|
||||||
|
round
|
||||||
|
slice
|
||||||
|
slug
|
||||||
|
sort
|
||||||
|
spaceless
|
||||||
|
split
|
||||||
|
striptags
|
||||||
|
timezone_name
|
||||||
|
title
|
||||||
|
trim
|
||||||
|
u
|
||||||
|
upper
|
||||||
|
url_encode
|
|
@ -0,0 +1,42 @@
|
||||||
|
``inky_to_html``
|
||||||
|
================
|
||||||
|
|
||||||
|
The ``inky_to_html`` filter processes an `inky email template
|
||||||
|
<https://github.com/zurb/inky>`_:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% apply inky_to_html %}
|
||||||
|
<row>
|
||||||
|
<columns large="6"></columns>
|
||||||
|
<columns large="6"></columns>
|
||||||
|
</row>
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
You can also use the filter on an included file:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ include('some_template.inky.twig')|inky_to_html }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``inky_to_html`` filter is part of the ``InkyExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/inky-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Inky\InkyExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new InkyExtension());
|
|
@ -0,0 +1,66 @@
|
||||||
|
``inline_css``
|
||||||
|
==============
|
||||||
|
|
||||||
|
The ``inline_css`` filter inline CSS styles in HTML documents:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% apply inline_css %}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
p { color: red; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hello CSS!</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
You can also add some stylesheets by passing them as arguments to the filter:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% apply inline_css(source("some_styles.css"), source("another.css")) %}
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>Hello CSS!</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
Styles loaded via the filter override the styles defined in the ``<style>`` tag
|
||||||
|
of the HTML document.
|
||||||
|
|
||||||
|
You can also use the filter on an included file:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ include('some_template.html.twig')|inline_css }}
|
||||||
|
|
||||||
|
{{ include('some_template.html.twig')|inline_css(source("some_styles.css")) }}
|
||||||
|
|
||||||
|
Note that the CSS inliner works on an entire HTML document, not a fragment.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``inline_css`` filter is part of the ``CssInlinerExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/cssinliner-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\CssInliner\CssInlinerExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new CssInlinerExtension());
|
|
@ -0,0 +1,32 @@
|
||||||
|
``join``
|
||||||
|
========
|
||||||
|
|
||||||
|
The ``join`` filter returns a string which is the concatenation of the items
|
||||||
|
of a sequence:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ [1, 2, 3]|join }}
|
||||||
|
{# returns 123 #}
|
||||||
|
|
||||||
|
The separator between elements is an empty string per default, but you can
|
||||||
|
define it with the optional first parameter:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ [1, 2, 3]|join('|') }}
|
||||||
|
{# outputs 1|2|3 #}
|
||||||
|
|
||||||
|
A second parameter can also be provided that will be the separator used between
|
||||||
|
the last two items of the sequence:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ [1, 2, 3]|join(', ', ' and ') }}
|
||||||
|
{# outputs 1, 2 and 3 #}
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``glue``: The separator
|
||||||
|
* ``and``: The separator for the last pair of input items
|
|
@ -0,0 +1,23 @@
|
||||||
|
``json_encode``
|
||||||
|
===============
|
||||||
|
|
||||||
|
The ``json_encode`` filter returns the JSON representation of a value:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ data|json_encode() }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, Twig uses the PHP `json_encode`_ function.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``options``: A bitmask of `json_encode options`_: ``{{
|
||||||
|
data|json_encode(constant('JSON_PRETTY_PRINT')) }}``.
|
||||||
|
Combine constants using :ref:`bitwise operators<template_logic>`:
|
||||||
|
``{{ data|json_encode(constant('JSON_PRETTY_PRINT') b-or constant('JSON_HEX_QUOT')) }}``
|
||||||
|
|
||||||
|
.. _`json_encode`: https://secure.php.net/json_encode
|
||||||
|
.. _`json_encode options`: https://secure.php.net/manual/en/json.constants.php
|
|
@ -0,0 +1,11 @@
|
||||||
|
``keys``
|
||||||
|
========
|
||||||
|
|
||||||
|
The ``keys`` filter returns the keys of an array. It is useful when you want to
|
||||||
|
iterate over the keys of an array:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% for key in array|keys %}
|
||||||
|
...
|
||||||
|
{% endfor %}
|
|
@ -0,0 +1,47 @@
|
||||||
|
``language_name``
|
||||||
|
=================
|
||||||
|
|
||||||
|
The ``language_name`` filter returns the language name given its two-letter
|
||||||
|
code:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# German #}
|
||||||
|
{{ 'de'|language_name }}
|
||||||
|
|
||||||
|
By default, the filter uses the current locale. You can pass it explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# allemand #}
|
||||||
|
{{ 'de'|language_name('fr') }}
|
||||||
|
|
||||||
|
{# français canadien #}
|
||||||
|
{{ 'fr_CA'|language_name('fr_FR') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``language_name`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
|
@ -0,0 +1,22 @@
|
||||||
|
``last``
|
||||||
|
========
|
||||||
|
|
||||||
|
The ``last`` filter returns the last "element" of a sequence, a mapping, or
|
||||||
|
a string:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ [1, 2, 3, 4]|last }}
|
||||||
|
{# outputs 4 #}
|
||||||
|
|
||||||
|
{{ { a: 1, b: 2, c: 3, d: 4 }|last }}
|
||||||
|
{# outputs 4 #}
|
||||||
|
|
||||||
|
{{ '1234'|last }}
|
||||||
|
{# outputs 4 #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It also works with objects implementing the `Traversable`_ interface.
|
||||||
|
|
||||||
|
.. _`Traversable`: https://secure.php.net/manual/en/class.traversable.php
|
|
@ -0,0 +1,19 @@
|
||||||
|
``length``
|
||||||
|
==========
|
||||||
|
|
||||||
|
The ``length`` filter returns the number of items of a sequence or mapping, or
|
||||||
|
the length of a string.
|
||||||
|
|
||||||
|
For objects that implement the ``Countable`` interface, ``length`` will use the
|
||||||
|
return value of the ``count()`` method.
|
||||||
|
|
||||||
|
For objects that implement the ``__toString()`` magic method (and not ``Countable``),
|
||||||
|
it will return the length of the string provided by that method.
|
||||||
|
|
||||||
|
For objects that implement the ``Traversable`` interface, ``length`` will use the return value of the ``iterator_count()`` method.
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% if users|length > 10 %}
|
||||||
|
...
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,47 @@
|
||||||
|
``locale_name``
|
||||||
|
===============
|
||||||
|
|
||||||
|
The ``locale_name`` filter returns the locale name given its two-letter
|
||||||
|
code:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# German #}
|
||||||
|
{{ 'de'|locale_name }}
|
||||||
|
|
||||||
|
By default, the filter uses the current locale. You can pass it explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# allemand #}
|
||||||
|
{{ 'de'|locale_name('fr') }}
|
||||||
|
|
||||||
|
{# français (Canada) #}
|
||||||
|
{{ 'fr_CA'|locale_name('fr_FR') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``locale_name`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
|
@ -0,0 +1,10 @@
|
||||||
|
``lower``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``lower`` filter converts a value to lowercase:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'WELCOME'|lower }}
|
||||||
|
|
||||||
|
{# outputs 'welcome' #}
|
|
@ -0,0 +1,34 @@
|
||||||
|
``map``
|
||||||
|
=======
|
||||||
|
|
||||||
|
The ``map`` filter applies an arrow function to the elements of a sequence or a
|
||||||
|
mapping. The arrow function receives the value of the sequence or mapping:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set people = [
|
||||||
|
{first: "Bob", last: "Smith"},
|
||||||
|
{first: "Alice", last: "Dupond"},
|
||||||
|
] %}
|
||||||
|
|
||||||
|
{{ people|map(p => "#{p.first} #{p.last}")|join(', ') }}
|
||||||
|
{# outputs Bob Smith, Alice Dupond #}
|
||||||
|
|
||||||
|
The arrow function also receives the key as a second argument:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set people = {
|
||||||
|
"Bob": "Smith",
|
||||||
|
"Alice": "Dupond",
|
||||||
|
} %}
|
||||||
|
|
||||||
|
{{ people|map((last, first) => "#{first} #{last}")|join(', ') }}
|
||||||
|
{# outputs Bob Smith, Alice Dupond #}
|
||||||
|
|
||||||
|
Note that the arrow function has access to the current context.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``arrow``: The arrow function
|
|
@ -0,0 +1,72 @@
|
||||||
|
``markdown_to_html``
|
||||||
|
====================
|
||||||
|
|
||||||
|
The ``markdown_to_html`` filter converts a block of Markdown to HTML:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% apply markdown_to_html %}
|
||||||
|
Title
|
||||||
|
======
|
||||||
|
|
||||||
|
Hello!
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
Note that you can indent the Markdown content as leading whitespaces will be
|
||||||
|
removed consistently before conversion:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% apply markdown_to_html %}
|
||||||
|
Title
|
||||||
|
======
|
||||||
|
|
||||||
|
Hello!
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
You can also use the filter on an included file or a variable:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ include('some_template.markdown.twig')|markdown_to_html }}
|
||||||
|
|
||||||
|
{{ changelog|markdown_to_html }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``markdown_to_html`` filter is part of the ``MarkdownExtension`` which
|
||||||
|
is not installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/markdown-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Markdown\MarkdownExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new MarkdownExtension());
|
||||||
|
|
||||||
|
If you are not using Symfony, you must also register the extension runtime::
|
||||||
|
|
||||||
|
use Twig\Extra\Markdown\DefaultMarkdown;
|
||||||
|
use Twig\Extra\Markdown\MarkdownRuntime;
|
||||||
|
use Twig\RuntimeLoader\RuntimeLoaderInterface;
|
||||||
|
|
||||||
|
$twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
|
||||||
|
public function load($class) {
|
||||||
|
if (MarkdownRuntime::class === $class) {
|
||||||
|
return new MarkdownRuntime(new DefaultMarkdown());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Afterwards you need to install a markdown library of your choice. Some of them are
|
||||||
|
mentioned in the ``require-dev`` section of the ``twig/markdown-extra`` package.
|
|
@ -0,0 +1,48 @@
|
||||||
|
``merge``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``merge`` filter merges an array with another array:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set values = [1, 2] %}
|
||||||
|
|
||||||
|
{% set values = values|merge(['apple', 'orange']) %}
|
||||||
|
|
||||||
|
{# values now contains [1, 2, 'apple', 'orange'] #}
|
||||||
|
|
||||||
|
New values are added at the end of the existing ones.
|
||||||
|
|
||||||
|
The ``merge`` filter also works on hashes:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set items = { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown' } %}
|
||||||
|
|
||||||
|
{% set items = items|merge({ 'peugeot': 'car', 'renault': 'car' }) %}
|
||||||
|
|
||||||
|
{# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car' } #}
|
||||||
|
|
||||||
|
For hashes, the merging process occurs on the keys: if the key does not
|
||||||
|
already exist, it is added but if the key already exists, its value is
|
||||||
|
overridden.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
If you want to ensure that some values are defined in an array (by given
|
||||||
|
default values), reverse the two elements in the call:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
|
||||||
|
|
||||||
|
{% set items = { 'apple': 'unknown' }|merge(items) %}
|
||||||
|
|
||||||
|
{# items now contains { 'apple': 'fruit', 'orange': 'fruit' } #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, Twig uses the PHP `array_merge`_ function. It supports
|
||||||
|
Traversable objects by transforming those to arrays.
|
||||||
|
|
||||||
|
.. _`array_merge`: https://secure.php.net/array_merge
|
|
@ -0,0 +1,19 @@
|
||||||
|
``nl2br``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``nl2br`` filter inserts HTML line breaks before all newlines in a string:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{{ "I like Twig.\nYou will like it too."|nl2br }}
|
||||||
|
{# outputs
|
||||||
|
|
||||||
|
I like Twig.<br />
|
||||||
|
You will like it too.
|
||||||
|
|
||||||
|
#}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``nl2br`` filter pre-escapes the input before applying the
|
||||||
|
transformation.
|
|
@ -0,0 +1,51 @@
|
||||||
|
``number_format``
|
||||||
|
=================
|
||||||
|
|
||||||
|
The ``number_format`` filter formats numbers. It is a wrapper around PHP's
|
||||||
|
`number_format`_ function:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 200.35|number_format }}
|
||||||
|
|
||||||
|
You can control the number of decimal places, decimal point, and thousands
|
||||||
|
separator using the additional arguments:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 9800.333|number_format(2, '.', ',') }}
|
||||||
|
|
||||||
|
To format negative numbers or math calculation, wrap the previous statement
|
||||||
|
with parentheses (needed because of Twig's :ref:`precedence of operators
|
||||||
|
<twig-expressions>`):
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ -9800.333|number_format(2, '.', ',') }} {# outputs : -9 #}
|
||||||
|
{{ (-9800.333)|number_format(2, '.', ',') }} {# outputs : -9,800.33 #}
|
||||||
|
{{ 1 + 0.2|number_format(2) }} {# outputs : 1.2 #}
|
||||||
|
{{ (1 + 0.2)|number_format(2) }} {# outputs : 1.20 #}
|
||||||
|
|
||||||
|
If no formatting options are provided then Twig will use the default formatting
|
||||||
|
options of:
|
||||||
|
|
||||||
|
* 0 decimal places.
|
||||||
|
* ``.`` as the decimal point.
|
||||||
|
* ``,`` as the thousands separator.
|
||||||
|
|
||||||
|
These defaults can be changed through the core extension::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->getExtension(\Twig\Extension\CoreExtension::class)->setNumberFormat(3, '.', ',');
|
||||||
|
|
||||||
|
The defaults set for ``number_format`` can be over-ridden upon each call using the
|
||||||
|
additional parameters.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``decimal``: The number of decimal points to display
|
||||||
|
* ``decimal_point``: The character(s) to use for the decimal point
|
||||||
|
* ``thousand_sep``: The character(s) to use for the thousands separator
|
||||||
|
|
||||||
|
.. _`number_format`: https://secure.php.net/number_format
|
|
@ -0,0 +1,12 @@
|
||||||
|
``raw``
|
||||||
|
=======
|
||||||
|
|
||||||
|
The ``raw`` filter marks the value as being "safe", which means that in an
|
||||||
|
environment with automatic escaping enabled this variable will not be escaped
|
||||||
|
if ``raw`` is the last filter applied to it:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% autoescape %}
|
||||||
|
{{ var|raw }} {# var won't be escaped #}
|
||||||
|
{% endautoescape %}
|
|
@ -0,0 +1,29 @@
|
||||||
|
``reduce``
|
||||||
|
==========
|
||||||
|
|
||||||
|
The ``reduce`` filter iteratively reduces a sequence or a mapping to a single
|
||||||
|
value using an arrow function, so as to reduce it to a single value. The arrow
|
||||||
|
function receives the return value of the previous iteration and the current
|
||||||
|
value of the sequence or mapping:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set numbers = [1, 2, 3] %}
|
||||||
|
|
||||||
|
{{ numbers|reduce((carry, v) => carry + v) }}
|
||||||
|
{# output 6 #}
|
||||||
|
|
||||||
|
The ``reduce`` filter takes an ``initial`` value as a second argument:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ numbers|reduce((carry, v) => carry + v, 10) }}
|
||||||
|
{# output 16 #}
|
||||||
|
|
||||||
|
Note that the arrow function has access to the current context.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``arrow``: The arrow function
|
||||||
|
* ``initial``: The initial value
|
|
@ -0,0 +1,27 @@
|
||||||
|
``replace``
|
||||||
|
===========
|
||||||
|
|
||||||
|
The ``replace`` filter formats a given string by replacing the placeholders
|
||||||
|
(placeholders are free-form):
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }}
|
||||||
|
|
||||||
|
{# outputs I like foo and bar
|
||||||
|
if the foo parameter equals to the foo string. #}
|
||||||
|
|
||||||
|
{# using % as a delimiter is purely conventional and optional #}
|
||||||
|
|
||||||
|
{{ "I like this and --that--."|replace({'this': foo, '--that--': "bar"}) }}
|
||||||
|
|
||||||
|
{# outputs I like foo and bar #}
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``from``: The placeholder values
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:doc:`format<format>`
|
|
@ -0,0 +1,44 @@
|
||||||
|
``reverse``
|
||||||
|
===========
|
||||||
|
|
||||||
|
The ``reverse`` filter reverses a sequence, a mapping, or a string:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% for user in users|reverse %}
|
||||||
|
...
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{{ '1234'|reverse }}
|
||||||
|
|
||||||
|
{# outputs 4321 #}
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
For sequences and mappings, numeric keys are not preserved. To reverse
|
||||||
|
them as well, pass ``true`` as an argument to the ``reverse`` filter:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% for key, value in {1: "a", 2: "b", 3: "c"}|reverse %}
|
||||||
|
{{ key }}: {{ value }}
|
||||||
|
{%- endfor %}
|
||||||
|
|
||||||
|
{# output: 0: c 1: b 2: a #}
|
||||||
|
|
||||||
|
{% for key, value in {1: "a", 2: "b", 3: "c"}|reverse(true) %}
|
||||||
|
{{ key }}: {{ value }}
|
||||||
|
{%- endfor %}
|
||||||
|
|
||||||
|
{# output: 3: c 2: b 1: a #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It also works with objects implementing the `Traversable`_ interface.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``preserve_keys``: Preserve keys when reversing a mapping or a sequence.
|
||||||
|
|
||||||
|
.. _`Traversable`: https://secure.php.net/Traversable
|
|
@ -0,0 +1,34 @@
|
||||||
|
``round``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``round`` filter rounds a number to a given precision:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 42.55|round }}
|
||||||
|
{# outputs 43 #}
|
||||||
|
|
||||||
|
{{ 42.55|round(1, 'floor') }}
|
||||||
|
{# outputs 42.5 #}
|
||||||
|
|
||||||
|
The ``round`` filter takes two optional arguments; the first one specifies the
|
||||||
|
precision (default is ``0``) and the second the rounding method (default is
|
||||||
|
``common``):
|
||||||
|
|
||||||
|
* ``common`` rounds either up or down (rounds the value up to precision decimal
|
||||||
|
places away from zero, when it is half way there -- making 1.5 into 2 and
|
||||||
|
-1.5 into -2);
|
||||||
|
|
||||||
|
* ``ceil`` always rounds up;
|
||||||
|
|
||||||
|
* ``floor`` always rounds down.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``//`` operator is equivalent to ``|round(0, 'floor')``.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``precision``: The rounding precision
|
||||||
|
* ``method``: The rounding method
|
|
@ -0,0 +1,68 @@
|
||||||
|
``slice``
|
||||||
|
===========
|
||||||
|
|
||||||
|
The ``slice`` filter extracts a slice of a sequence, a mapping, or a string:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% for i in [1, 2, 3, 4, 5]|slice(1, 2) %}
|
||||||
|
{# will iterate over 2 and 3 #}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{{ '12345'|slice(1, 2) }}
|
||||||
|
|
||||||
|
{# outputs 23 #}
|
||||||
|
|
||||||
|
You can use any valid expression for both the start and the length:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% for i in [1, 2, 3, 4, 5]|slice(start, length) %}
|
||||||
|
{# ... #}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
As syntactic sugar, you can also use the ``[]`` notation:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% for i in [1, 2, 3, 4, 5][start:length] %}
|
||||||
|
{# ... #}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{{ '12345'[1:2] }} {# will display "23" #}
|
||||||
|
|
||||||
|
{# you can omit the first argument -- which is the same as 0 #}
|
||||||
|
{{ '12345'[:2] }} {# will display "12" #}
|
||||||
|
|
||||||
|
{# you can omit the last argument -- which will select everything till the end #}
|
||||||
|
{{ '12345'[2:] }} {# will display "345" #}
|
||||||
|
|
||||||
|
The ``slice`` filter works as the `array_slice`_ PHP function for arrays and
|
||||||
|
`mb_substr`_ for strings with a fallback to `substr`_.
|
||||||
|
|
||||||
|
If the start is non-negative, the sequence will start at that start in the
|
||||||
|
variable. If start is negative, the sequence will start that far from the end
|
||||||
|
of the variable.
|
||||||
|
|
||||||
|
If length is given and is positive, then the sequence will have up to that
|
||||||
|
many elements in it. If the variable is shorter than the length, then only the
|
||||||
|
available variable elements will be present. If length is given and is
|
||||||
|
negative then the sequence will stop that many elements from the end of the
|
||||||
|
variable. If it is omitted, then the sequence will have everything from offset
|
||||||
|
up until the end of the variable.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It also works with objects implementing the `Traversable`_ interface.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``start``: The start of the slice
|
||||||
|
* ``length``: The size of the slice
|
||||||
|
* ``preserve_keys``: Whether to preserve key or not (when the input is an array)
|
||||||
|
|
||||||
|
.. _`Traversable`: https://secure.php.net/manual/en/class.traversable.php
|
||||||
|
.. _`array_slice`: https://secure.php.net/array_slice
|
||||||
|
.. _`mb_substr`: https://secure.php.net/mb-substr
|
||||||
|
.. _`substr`: https://secure.php.net/substr
|
|
@ -0,0 +1,59 @@
|
||||||
|
``slug``
|
||||||
|
========
|
||||||
|
|
||||||
|
The ``slug`` filter transforms a given string into another string that
|
||||||
|
only includes safe ASCII characters.
|
||||||
|
|
||||||
|
Here is an example:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'Wôrķšƥáçè ~~sèťtïñğš~~'|slug }}
|
||||||
|
Workspace-settings
|
||||||
|
|
||||||
|
The default separator between words is a dash (``-``), but you can
|
||||||
|
define a selector of your choice by passing it as an argument:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'Wôrķšƥáçè ~~sèťtïñğš~~'|slug('/') }}
|
||||||
|
Workspace/settings
|
||||||
|
|
||||||
|
The slugger automatically detects the language of the original
|
||||||
|
string, but you can also specify it explicitly using the second
|
||||||
|
argument:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ '...'|slug('-', 'ko') }}
|
||||||
|
|
||||||
|
The ``slug`` filter uses the method by the same name in Symfony's
|
||||||
|
`AsciiSlugger <https://symfony.com/doc/current/components/string.html#slugger>`_.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``slug`` filter is part of the ``StringExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/string-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\String\StringExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new StringExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``separator``: The separator that is used to join words (defaults to ``-``)
|
||||||
|
* ``locale``: The locale of the original string (if none is specified, it will be automatically detected)
|
|
@ -0,0 +1,42 @@
|
||||||
|
``sort``
|
||||||
|
========
|
||||||
|
|
||||||
|
The ``sort`` filter sorts an array:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% for user in users|sort %}
|
||||||
|
...
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, Twig uses the PHP `asort`_ function to maintain index
|
||||||
|
association. It supports Traversable objects by transforming
|
||||||
|
those to arrays.
|
||||||
|
|
||||||
|
You can pass an arrow function to sort the array:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% set fruits = [
|
||||||
|
{ name: 'Apples', quantity: 5 },
|
||||||
|
{ name: 'Oranges', quantity: 2 },
|
||||||
|
{ name: 'Grapes', quantity: 4 },
|
||||||
|
] %}
|
||||||
|
|
||||||
|
{% for fruit in fruits|sort((a, b) => a.quantity <=> b.quantity)|column('name') %}
|
||||||
|
{{ fruit }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{# output in this order: Oranges, Grapes, Apples #}
|
||||||
|
|
||||||
|
Note the usage of the `spaceship`_ operator to simplify the comparison.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``arrow``: An arrow function
|
||||||
|
|
||||||
|
.. _`asort`: https://secure.php.net/asort
|
||||||
|
.. _`spaceship`: https://www.php.net/manual/en/language.operators.comparison.php
|
|
@ -0,0 +1,56 @@
|
||||||
|
``spaceless``
|
||||||
|
=============
|
||||||
|
|
||||||
|
Use the ``spaceless`` filter to remove whitespace *between HTML tags*, not
|
||||||
|
whitespace within HTML tags or whitespace in plain text:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{{
|
||||||
|
"<div>
|
||||||
|
<strong>foo</strong>
|
||||||
|
</div>
|
||||||
|
"|spaceless }}
|
||||||
|
|
||||||
|
{# output will be <div><strong>foo</strong></div> #}
|
||||||
|
|
||||||
|
You can combine ``spaceless`` with the ``apply`` tag to apply the transformation
|
||||||
|
on large amounts of HTML:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% apply spaceless %}
|
||||||
|
<div>
|
||||||
|
<strong>foo</strong>
|
||||||
|
</div>
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
{# output will be <div><strong>foo</strong></div> #}
|
||||||
|
|
||||||
|
This tag is not meant to "optimize" the size of the generated HTML content but
|
||||||
|
merely to avoid extra whitespace between HTML tags to avoid browser rendering
|
||||||
|
quirks under some circumstances.
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
|
||||||
|
As the filter uses a regular expression behind the scenes, its performance
|
||||||
|
is directly related to the text size you are working on (remember that
|
||||||
|
filters are executed at runtime).
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
If you want to optimize the size of the generated HTML content, gzip
|
||||||
|
compress the output instead.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
If you want to create a tag that actually removes all extra whitespace in
|
||||||
|
an HTML string, be warned that this is not as easy as it seems to be
|
||||||
|
(think of ``textarea`` or ``pre`` tags for instance). Using a third-party
|
||||||
|
library like Tidy is probably a better idea.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
For more information on whitespace control, read the
|
||||||
|
:ref:`dedicated section <templates-whitespace-control>` of the documentation and learn how
|
||||||
|
you can also use the whitespace control modifier on your tags.
|
|
@ -0,0 +1,50 @@
|
||||||
|
``split``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``split`` filter splits a string by the given delimiter and returns a list
|
||||||
|
of strings:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set foo = "one,two,three"|split(',') %}
|
||||||
|
{# foo contains ['one', 'two', 'three'] #}
|
||||||
|
|
||||||
|
You can also pass a ``limit`` argument:
|
||||||
|
|
||||||
|
* If ``limit`` is positive, the returned array will contain a maximum of
|
||||||
|
limit elements with the last element containing the rest of string;
|
||||||
|
|
||||||
|
* If ``limit`` is negative, all components except the last -limit are
|
||||||
|
returned;
|
||||||
|
|
||||||
|
* If ``limit`` is zero, then this is treated as 1.
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set foo = "one,two,three,four,five"|split(',', 3) %}
|
||||||
|
{# foo contains ['one', 'two', 'three,four,five'] #}
|
||||||
|
|
||||||
|
If the ``delimiter`` is an empty string, then value will be split by equal
|
||||||
|
chunks. Length is set by the ``limit`` argument (one character by default).
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set foo = "123"|split('') %}
|
||||||
|
{# foo contains ['1', '2', '3'] #}
|
||||||
|
|
||||||
|
{% set bar = "aabbcc"|split('', 2) %}
|
||||||
|
{# bar contains ['aa', 'bb', 'cc'] #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, Twig uses the PHP `explode`_ or `str_split`_ (if delimiter is
|
||||||
|
empty) functions for string splitting.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``delimiter``: The delimiter
|
||||||
|
* ``limit``: The limit argument
|
||||||
|
|
||||||
|
.. _`explode`: https://secure.php.net/explode
|
||||||
|
.. _`str_split`: https://secure.php.net/str_split
|
|
@ -0,0 +1,29 @@
|
||||||
|
``striptags``
|
||||||
|
=============
|
||||||
|
|
||||||
|
The ``striptags`` filter strips SGML/XML tags and replace adjacent whitespace
|
||||||
|
by one space:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ some_html|striptags }}
|
||||||
|
|
||||||
|
You can also provide tags which should not be stripped:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{{ some_html|striptags('<br><p>') }}
|
||||||
|
|
||||||
|
In this example, the ``<br/>``, ``<br>``, ``<p>``, and ``</p>`` tags won't be
|
||||||
|
removed from the string.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, Twig uses the PHP `strip_tags`_ function.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``allowable_tags``: Tags which should not be stripped
|
||||||
|
|
||||||
|
.. _`strip_tags`: https://secure.php.net/strip_tags
|
|
@ -0,0 +1,46 @@
|
||||||
|
``timezone_name``
|
||||||
|
=================
|
||||||
|
|
||||||
|
The ``timezone_name`` filter returns the timezone name given a timezone identifier:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# Central European Time (Paris) #}
|
||||||
|
{{ 'Europe/Paris'|timezone_name }}
|
||||||
|
|
||||||
|
{# Pacific Time (Los Angeles) #}
|
||||||
|
{{ 'America/Los_Angeles'|timezone_name }}
|
||||||
|
|
||||||
|
By default, the filter uses the current locale. You can pass it explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# heure du Pacifique nord-américain (Los Angeles) #}
|
||||||
|
{{ 'America/Los_Angeles'|timezone_name('fr') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``timezone_name`` filter is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``locale``: The locale
|
|
@ -0,0 +1,11 @@
|
||||||
|
``title``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``title`` filter returns a titlecased version of the value. Words will
|
||||||
|
start with uppercase letters, all remaining characters are lowercase:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'my first car'|title }}
|
||||||
|
|
||||||
|
{# outputs 'My First Car' #}
|
|
@ -0,0 +1,39 @@
|
||||||
|
``trim``
|
||||||
|
========
|
||||||
|
|
||||||
|
The ``trim`` filter strips whitespace (or other characters) from the beginning
|
||||||
|
and end of a string:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ ' I like Twig. '|trim }}
|
||||||
|
|
||||||
|
{# outputs 'I like Twig.' #}
|
||||||
|
|
||||||
|
{{ ' I like Twig.'|trim('.') }}
|
||||||
|
|
||||||
|
{# outputs ' I like Twig' #}
|
||||||
|
|
||||||
|
{{ ' I like Twig. '|trim(side='left') }}
|
||||||
|
|
||||||
|
{# outputs 'I like Twig. ' #}
|
||||||
|
|
||||||
|
{{ ' I like Twig. '|trim(' ', 'right') }}
|
||||||
|
|
||||||
|
{# outputs ' I like Twig.' #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, Twig uses the PHP `trim`_, `ltrim`_, and `rtrim`_ functions.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``character_mask``: The characters to strip
|
||||||
|
|
||||||
|
* ``side``: The default is to strip from the left and the right (`both`) sides, but `left`
|
||||||
|
and `right` will strip from either the left side or right side only
|
||||||
|
|
||||||
|
.. _`trim`: https://secure.php.net/trim
|
||||||
|
.. _`ltrim`: https://secure.php.net/ltrim
|
||||||
|
.. _`rtrim`: https://secure.php.net/rtrim
|
|
@ -0,0 +1,87 @@
|
||||||
|
``u``
|
||||||
|
=====
|
||||||
|
|
||||||
|
The ``u`` filter wraps a text in a Unicode object (a `Symfony UnicodeString
|
||||||
|
instance <https://symfony.com/doc/current/components/string.html>`_) that
|
||||||
|
exposes methods to "manipulate" the string.
|
||||||
|
|
||||||
|
Let's see some common use cases.
|
||||||
|
|
||||||
|
Wrapping a text to a given number of characters:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'Symfony String + Twig = <3'|u.wordwrap(5) }}
|
||||||
|
Symfony
|
||||||
|
String
|
||||||
|
+
|
||||||
|
Twig
|
||||||
|
= <3
|
||||||
|
|
||||||
|
Truncating a string:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'Lorem ipsum'|u.truncate(8) }}
|
||||||
|
Lorem ip
|
||||||
|
|
||||||
|
{{ 'Lorem ipsum'|u.truncate(8, '...') }}
|
||||||
|
Lorem...
|
||||||
|
|
||||||
|
The ``truncate`` method also accepts a third argument to preserve whole words:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'Lorem ipsum dolor'|u.truncate(10, '...', false) }}
|
||||||
|
Lorem ipsum...
|
||||||
|
|
||||||
|
Converting a string to *snake* case or *camelCase*:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'SymfonyStringWithTwig'|u.snake }}
|
||||||
|
symfony_string_with_twig
|
||||||
|
|
||||||
|
{{ 'symfony_string with twig'|u.camel.title }}
|
||||||
|
SymfonyStringWithTwig
|
||||||
|
|
||||||
|
You can also chain methods:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'Symfony String + Twig = <3'|u.wordwrap(5).upper }}
|
||||||
|
SYMFONY
|
||||||
|
STRING
|
||||||
|
+
|
||||||
|
TWIG
|
||||||
|
= <3
|
||||||
|
|
||||||
|
For large strings manipulation, use the ``apply`` tag:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% apply u.wordwrap(5) %}
|
||||||
|
Some large amount of text...
|
||||||
|
{% endapply %}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``u`` filter is part of the ``StringExtension`` which is not installed
|
||||||
|
by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/string-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\String\StringExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new StringExtension());
|
|
@ -0,0 +1,10 @@
|
||||||
|
``upper``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``upper`` filter converts a value to uppercase:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ 'welcome'|upper }}
|
||||||
|
|
||||||
|
{# outputs 'WELCOME' #}
|
|
@ -0,0 +1,23 @@
|
||||||
|
``url_encode``
|
||||||
|
==============
|
||||||
|
|
||||||
|
The ``url_encode`` filter percent encodes a given string as URL segment
|
||||||
|
or an array as query string:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ "path-seg*ment"|url_encode }}
|
||||||
|
{# outputs "path-seg%2Ament" #}
|
||||||
|
|
||||||
|
{{ "string with spaces"|url_encode }}
|
||||||
|
{# outputs "string%20with%20spaces" #}
|
||||||
|
|
||||||
|
{{ {'param': 'value', 'foo': 'bar'}|url_encode }}
|
||||||
|
{# outputs "param=value&foo=bar" #}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Internally, Twig uses the PHP `rawurlencode`_ or the `http_build_query`_ function.
|
||||||
|
|
||||||
|
.. _`rawurlencode`: https://secure.php.net/rawurlencode
|
||||||
|
.. _`http_build_query`: https://secure.php.net/http_build_query
|
|
@ -0,0 +1,23 @@
|
||||||
|
``attribute``
|
||||||
|
=============
|
||||||
|
|
||||||
|
The ``attribute`` function can be used to access a "dynamic" attribute of a
|
||||||
|
variable:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ attribute(object, method) }}
|
||||||
|
{{ attribute(object, method, arguments) }}
|
||||||
|
{{ attribute(array, item) }}
|
||||||
|
|
||||||
|
In addition, the ``defined`` test can check for the existence of a dynamic
|
||||||
|
attribute:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ attribute(object, method) is defined ? 'Method exists' : 'Method does not exist' }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The resolution algorithm is the same as the one used for the ``.``
|
||||||
|
notation, except that the item can be any valid expression.
|
|
@ -0,0 +1,37 @@
|
||||||
|
``block``
|
||||||
|
=========
|
||||||
|
|
||||||
|
When a template uses inheritance and if you want to print a block multiple
|
||||||
|
times, use the ``block`` function:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
|
||||||
|
<h1>{{ block('title') }}</h1>
|
||||||
|
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
|
||||||
|
The ``block`` function can also be used to display one block from another
|
||||||
|
template:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ block("title", "common_blocks.twig") }}
|
||||||
|
|
||||||
|
Use the ``defined`` test to check if a block exists in the context of the
|
||||||
|
current template:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% if block("footer") is defined %}
|
||||||
|
...
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if block("footer", "common_blocks.twig") is defined %}
|
||||||
|
...
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>`
|
|
@ -0,0 +1,23 @@
|
||||||
|
``constant``
|
||||||
|
============
|
||||||
|
|
||||||
|
``constant`` returns the constant value for a given string:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ some_date|date(constant('DATE_W3C')) }}
|
||||||
|
{{ constant('Namespace\\Classname::CONSTANT_NAME') }}
|
||||||
|
|
||||||
|
You can read constants from object instances as well:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ constant('RSS', date) }}
|
||||||
|
|
||||||
|
Use the ``defined`` test to check if a constant is defined:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% if constant('SOME_CONST') is defined %}
|
||||||
|
...
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,32 @@
|
||||||
|
``country_timezones``
|
||||||
|
=====================
|
||||||
|
|
||||||
|
The ``country_timezones`` function returns the names of the timezones associated
|
||||||
|
with a given country code:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# Europe/Paris #}
|
||||||
|
{{ country_timezones('FR')|join(', ') }}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``country_timezones`` function is part of the ``IntlExtension`` which is not
|
||||||
|
installed by default. Install it first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/intl-extra
|
||||||
|
|
||||||
|
Then, on Symfony projects, install the ``twig/extra-bundle``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ composer require twig/extra-bundle
|
||||||
|
|
||||||
|
Otherwise, add the extension explicitly on the Twig environment::
|
||||||
|
|
||||||
|
use Twig\Extra\Intl\IntlExtension;
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment(...);
|
||||||
|
$twig->addExtension(new IntlExtension());
|
|
@ -0,0 +1,28 @@
|
||||||
|
``cycle``
|
||||||
|
=========
|
||||||
|
|
||||||
|
The ``cycle`` function cycles on an array of values:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set start_year = date() | date('Y') %}
|
||||||
|
{% set end_year = start_year + 5 %}
|
||||||
|
|
||||||
|
{% for year in start_year..end_year %}
|
||||||
|
{{ cycle(['odd', 'even'], loop.index0) }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
The array can contain any number of values:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set fruits = ['apple', 'orange', 'citrus'] %}
|
||||||
|
|
||||||
|
{% for i in 0..10 %}
|
||||||
|
{{ cycle(fruits, i) }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``position``: The cycle position
|
|
@ -0,0 +1,44 @@
|
||||||
|
``date``
|
||||||
|
========
|
||||||
|
|
||||||
|
Converts an argument to a date to allow date comparison:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% if date(user.created_at) < date('-2days') %}
|
||||||
|
{# do something #}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
The argument must be in one of PHP’s supported `date and time formats`_.
|
||||||
|
|
||||||
|
You can pass a timezone as the second argument:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% if date(user.created_at) < date('-2days', 'Europe/Paris') %}
|
||||||
|
{# do something #}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
If no argument is passed, the function returns the current date:
|
||||||
|
|
||||||
|
.. code-block:: html+twig
|
||||||
|
|
||||||
|
{% if date(user.created_at) < date() %}
|
||||||
|
{# always! #}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You can set the default timezone globally by calling ``setTimezone()`` on
|
||||||
|
the ``core`` extension instance::
|
||||||
|
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->getExtension(\Twig\Extension\CoreExtension::class)->setTimezone('Europe/Paris');
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``date``: The date
|
||||||
|
* ``timezone``: The timezone
|
||||||
|
|
||||||
|
.. _`date and time formats`: https://secure.php.net/manual/en/datetime.formats.php
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue