diff --git a/composer.json b/composer.json index d6497cc..bf391d2 100644 --- a/composer.json +++ b/composer.json @@ -1,43 +1,44 @@ { - "name": "serge0design/pimcore-custom-twig", - "license": "GPL-3.0-or-later", - "type": "pimcore-bundle", - "keywords": [ - "pimcore", - "twig", - "Filter", - "Extension", - "Test" - ], - "description": "A collection of twig Extensions/Filters and Tests for Pimcore 11.", - "homepage": "https://github.com/serge0design/pimcore-custom-twig.git", - "authors": [ - { - "name": "serge.design - Serge", - "email": "github@serge.design", - "homepage": "https://serge-design.ch", - "role": "Developer" - } - ], - "autoload": { - "psr-4": { - "SergeDesign\\PimcoreCustomTwigBundle\\": "src/" - } - }, - "extra": { - "pimcore": { - "bundles": [ - "SergeDesign\\PimcoreCustomTwigBundle\\PimcoreCustomTwigBundle" - ] - } - }, - "require": { - "ext-intl": "*", - "ext-mbstring": "*", - "pimcore/pimcore": "^v11.1.0", - "symfony/ux-twig-component": "^2.7", - "twig/extra-bundle": "^3.5", - "twig/string-extra": "^3.5" - }, - "require-dev": {} + "name": "serge0design/pimcore-custom-twig", + "license": "GPL-3.0-or-later", + "type": "pimcore-bundle", + "keywords": [ + "pimcore", + "twig", + "Filter", + "Extension", + "Test" + ], + "description": "A collection of twig Extensions/Filters and Tests for Pimcore 11.", + "homepage": "https://github.com/serge0design/pimcore-custom-twig.git", + "authors": [ + { + "name": "serge.design - Serge", + "email": "github@serge.design", + "homepage": "https://serge-design.ch", + "role": "Developer" + } + ], + "autoload": { + "psr-4": { + "SergeDesign\\PimcoreCustomTwigBundle\\": "src/" + } + }, + "extra": { + "pimcore": { + "bundles": [ + "SergeDesign\\PimcoreCustomTwigBundle\\PimcoreCustomTwigBundle" + ] + } + }, + "require": { + "ext-iconv": "*", + "ext-intl": "*", + "ext-mbstring": "*", + "pimcore/pimcore": "^v11.1.0", + "symfony/ux-twig-component": "^2.7", + "twig/extra-bundle": "^3.5", + "twig/string-extra": "^3.5" + }, + "require-dev": {} } diff --git a/config/twig/extensions.yml b/config/twig/extensions.yml index 96b000d..4b985cd 100644 --- a/config/twig/extensions.yml +++ b/config/twig/extensions.yml @@ -1,21 +1,23 @@ services: - _defaults: - autowire: true - autoconfigure: true - public: false + _defaults: + autowire: true + autoconfigure: true + public: false - SergeDesign\PimcoreCustomTwigBundle\Twig\Extension\TwigBootstrapSvgIcon: - tags: [ 'twig.extension' ] + SergeDesign\PimcoreCustomTwigBundle\Twig\Extension\TwigBootstrapSvgIcon: + tags: + - { name: twig.extension } - SergeDesign\PimcoreCustomTwigBundle\Twig\Extension\TwigEditmodeLinkToObject: - tags: [ 'twig.extension' ] + SergeDesign\PimcoreCustomTwigBundle\Twig\Extension\TwigEditmodeLinkToObject: + tags: + - { name: twig.extension } - SergeDesign\PimcoreCustomTwigBundle\Twig\Extension\TwigLanguageSwitcher: - tags: [ 'twig.extension' ] - - SergeDesign\PimcoreCustomTwigBundle\Twig\Extension\TwigQrCode: - tags: [ 'twig.extension' ] - - SergeDesign\PimcoreCustomTwigBundle\Twig\Extension\TwigSvgFun: - tags: [ 'twig.extension' ] + SergeDesign\PimcoreCustomTwigBundle\Twig\Extension\TwigLanguageSwitcher: + tags: + - { name: twig.extension } + SergeDesign\PimcoreCustomTwigBundle\Twig\Extension\TwigQrCode: + arguments: + $translator: '@Pimcore\Translation\Translator' + tags: + - { name: twig.extension } diff --git a/config/twig/testing.yml b/config/twig/testing.yml index a1ebf3b..b6e4ee9 100644 --- a/config/twig/testing.yml +++ b/config/twig/testing.yml @@ -1,14 +1,16 @@ services: - _defaults: - autowire: true - autoconfigure: true - public: false + _defaults: + autowire: true + autoconfigure: true + public: false - SergeDesign\PimcoreCustomTwigBundle\Twig\Test\TwigTests: - tags: [ 'twig.extension' ] + SergeDesign\PimcoreCustomTwigBundle\Twig\Test\TwigTests: + tags: + - { name: twig.extension } - SergeDesign\PimcoreCustomTwigBundle\Twig\Test\TwigBundleChecker: - tags: [ 'twig.extension' ] - arguments: - - '@service_container' + SergeDesign\PimcoreCustomTwigBundle\Twig\Test\TwigBundleChecker: + tags: + - { name: twig.extension } + arguments: + $bundles: '%kernel.bundles%' diff --git a/docs/twig-filters.md b/docs/twig-filters.md index 79e9017..377124b 100644 --- a/docs/twig-filters.md +++ b/docs/twig-filters.md @@ -1,6 +1,12 @@ ## Twig Filter Examples -twigFilterArrayFlip: +test array + +``` +{% set array = ['apple', 'banana', 'cherry'] %} +``` + +twigFilterArrayFlip:
Exchanges all keys with their associated values in an array ``` @@ -9,7 +15,7 @@ Exchanges all keys with their associated values in an array {% endfor %} ``` -twigFilterArrayReverse: +twigFilterArrayReverse:
Return an array with elements in reverse order ``` @@ -18,7 +24,7 @@ Return an array with elements in reverse order {% endfor %} ``` -twigFilterArrayShuffle +twigFilterArrayShuffle
Return an array with elements in shuffled order ``` @@ -27,8 +33,8 @@ Return an array with elements in shuffled order {% endfor %} ``` -twigFilterPassedTimeToNow -usable for logins, orders, blogs, etc +twigFilterPassedTimeToNow
+usable for logins, orders, blogs, etc.
Outputs something like: Last Loggin ( 1day ago ) ``` @@ -42,6 +48,16 @@ twigFilterFileGetContents {{ svgImagePath|twigFilterFileGetContents }} ``` +Formatting currency output + +``` + {% set productPrice = 1234.56 %} {# Example price #} + {% set currencySymbol = 'CHF' %} {# Currency code #} + {% set locale = 'de_CH' %} {# Locale for formatting #} + {# Using the twigFilterFormatPrice filter to format the price #} + {{ productPrice|twigFilterFormatPrice(currencySymbol, 2, locale) }} +``` + #### Image twigFilterImgThumbnail @@ -63,6 +79,20 @@ Output inline Code: style="background-image: url('..'); {{ pimcoreImage|twigFilterCssBgImg('thumbnailName') }} ``` +Usage: + +``` +{% set pimcoreImage = pimcore_image("pimcoreImage") %} +{% if editmode %} + {{ pimcoreImage|raw }} +{% else %} + {% set pimcoreImage = pimcoreImage.image %} + {{ pimcoreImage|twigFilterImgThumbnail('thumbnailName', 'cssClass', 'altText', {"data-attr": "value"}) }} + {{ pimcoreImage|twigFilterImgThumbConfig('cssClass', 'altText', 100, 100, 100, 'png') }} + {{ pimcoreImage|twigFilterCssBgImg('contentimages') }} +{% endif %} +``` + #### String twigFilterGetMd5 @@ -80,7 +110,15 @@ twigFilterGetUniqid twigFilterStringNormalizer ``` -{{ "String"|twigFilterStringNormalizer }} +{% set string = '~¨^?\'"/-+.,;() &äöüÄÖÜßÉéÈèÊêEeËëÀàÁáÅåaÂâÃãªÆæCcÇçCcÍíÌìÎîÏïÓóÒòÔôºÕõŒOoØøÚúÙùÛûUuUuŠšSsŽžÑñ¡¿Ÿÿ_:' %} +{{ string|twigFilterStringNormalizer }} +``` + +twigFilterNormalizeFolderName + +``` +{% set string = '~¨^?\'"/-+.,;() &äöüÄÖÜßÉéÈèÊêEeËëÀàÁáÅåaÂâÃãªÆæCcÇçCcÍíÌìÎîÏïÓóÒòÔôºÕõŒOoØøÚúÙùÛûUuUuŠšSsŽžÑñ¡¿Ÿÿ_:' %} +{{ string|twigFilterNormalizeFolderName }} ``` twigFilterStrToLower @@ -89,6 +127,18 @@ twigFilterStrToLower {{ "String"|twigFilterStrToLower }} ``` +twigFilterStrToUpper + +``` + {{ "string"|twigFilterStrToUpper }} +``` + +twigFilterStrCapitalize + +``` + {{ "string"|twigFilterStrCapitalize }} +``` + twigFilterTruncate ``` @@ -106,7 +156,7 @@ twigFilterWordwrap twigFilterHrefUrl ``` -{{ "https://url.com/"|twigFilterHrefUrl }} + {{ "https://url.com/"|twigFilterHrefUrl('css_class', "_blank") }} ``` twigFilterHrefEmail @@ -127,11 +177,13 @@ twigFilterHrefWhatsApp {{ href|twigFilterHrefWhatsApp }} ``` -twigFilterHrefSocialMedia -based on Bootstrap Icons: https://icons.getbootstrap.com/ +twigFilterHrefSocialMedia
+based on Bootstrap Icons: https://icons.getbootstrap.com/
+r.i.p. twitter ``` -{{ href|twigFilterHrefSocialMedia }} +{% set social = "https://twitter.com" %} +{{ social|twigFilterHrefSocialMedia('twitter') }} ``` #### Time diff --git a/docs/twig-tests.md b/docs/twig-tests.md index e21c940..91bfb5d 100644 --- a/docs/twig-tests.md +++ b/docs/twig-tests.md @@ -10,7 +10,7 @@ Check if item is of MIME-Type image/svg+xml ``` twigTestIsArray\ -Check if item is array +Check if item is an array ``` {% if item is twigTestIsArray %} @@ -19,7 +19,7 @@ Check if item is array ``` twigTestIsBoolean\ -Check if item is bool +Check if item is of bool ``` {% if item is twigTestIsBoolean %} @@ -46,7 +46,7 @@ Check if item is countable ``` twigTestIsDir\ -Check if item is directory +Check if item is a directory ``` {% if item is twigTestIsDir %} @@ -55,7 +55,7 @@ Check if item is directory ``` twigTestIsFloat\ -Check if item is float +Check if item is of float ``` {% if item is twigTestIsFloat %} @@ -64,7 +64,7 @@ Check if item is float ``` twigTestIsInt\ -Check if is int +Check if is of int ``` {% if item is twigTestIsInt %} @@ -73,7 +73,7 @@ Check if is int ``` twigTestIsNumeric\ -Check if is numeric +Check if is of numeric ``` {% if item is twigTestIsNumeric %} @@ -82,7 +82,7 @@ Check if is numeric ``` twigTestIsNull\ -Check if item is null +Check if item is of null ``` {% if item is twigTestIsNull %} @@ -91,7 +91,7 @@ Check if item is null ``` twigTestIsObject\ -Check if item is object +Check if item is an object ``` {% if item is twigTestIsObject %} @@ -100,7 +100,7 @@ Check if item is object ``` twigTestIsResource\ -Check if item is resource +Check if item is a resource ``` {% if item is twigTestIsResource %} @@ -118,7 +118,7 @@ Check if item is scalar ``` twigTestIsString\ -Check if item is string +Check if item is a string ``` {% if item is twigTestIsString %} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index ba9c1ac..d1cbb7e 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -1,4 +1,5 @@ processConfiguration($configuration, $configs); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../../config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config')); $loader->load('services.yaml'); } } diff --git a/src/Nodes/SwitchNode.php b/src/Nodes/SwitchNode.php index fe99b1d..46772f7 100644 --- a/src/Nodes/SwitchNode.php +++ b/src/Nodes/SwitchNode.php @@ -9,7 +9,7 @@ class SwitchNode extends Node { - public function compile(Compiler $compiler) + final public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) diff --git a/src/PimcoreCustomTwigBundle.php b/src/PimcoreCustomTwigBundle.php index ec373b6..f1fae31 100644 --- a/src/PimcoreCustomTwigBundle.php +++ b/src/PimcoreCustomTwigBundle.php @@ -1,5 +1,4 @@ getLine(); $parser = $this->parser; @@ -44,9 +45,9 @@ public function parse(Token $token): SwitchNode $expressionParser = $parser->getExpressionParser(); $cases = []; - $end = false; + $end_switch = false; - while (!$end) { + while (!$end_switch) { $next = $stream->next(); switch ($next->getValue()) { @@ -76,14 +77,15 @@ public function parse(Token $token): SwitchNode break; case 'endswitch': - $end = true; + $end_switch = true; break; default: throw new SyntaxError(sprintf( 'Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', - $lineno), -1); + $lineno + ), -1); } } @@ -97,7 +99,7 @@ public function parse(Token $token): SwitchNode * @param Token $token * @return bool */ - public function decideIfFork(Token $token): bool + final public function decideIfFork(Token $token): bool { return $token->test(['case', 'default', 'endswitch']); } @@ -106,7 +108,7 @@ public function decideIfFork(Token $token): bool * @param Token $token * @return bool */ - public function decideIfEnd(Token $token): bool + final public function decideIfEnd(Token $token): bool { return $token->test(['endswitch']); } diff --git a/src/Twig/Extension/TwigBootstrapSvgIcon.php b/src/Twig/Extension/TwigBootstrapSvgIcon.php index 2bedfa5..44c6ab4 100644 --- a/src/Twig/Extension/TwigBootstrapSvgIcon.php +++ b/src/Twig/Extension/TwigBootstrapSvgIcon.php @@ -9,38 +9,46 @@ class TwigBootstrapSvgIcon extends AbstractExtension { + private Filesystem $filesystem; + private string $basePath; - public function getFunctions(): array + public function __construct(string $basePath = 'bundles/pimcorecustomtwig/svg/') + { + $this->filesystem = new Filesystem(); + $this->basePath = $basePath; + } + + final public function getFunctions(): array { return [ new TwigFunction( 'twigExtensionBootstrapSvgIcon', - [$this, 'getBootstrapSvgIcon'], ['is_safe' => ['html']]) + [$this, 'getBootstrapSvgIcon'], + ['is_safe' => ['html']] + ) ]; } - public function getBootstrapSvgIcon( + final public function getBootstrapSvgIcon( string $biName = 'bootstrap', string $fill = 'currentColor', float $biSize = 1.6 - ): string - { + ): string { + $iconFile = $this->basePath . 'bootstrap-icons.svg'; - $filesystem = new Filesystem(); - $iconFile = 'build/svg/bootstrap-icons.svg'; - - if (!$filesystem->exists($iconFile)) { - $iconFile = 'bundles/pimcorecustomtwig/svg/bootstrap-icons.svg'; + if (!$this->filesystem->exists($iconFile)) { + // Optionally handle the error or fallback + return ''; // Early return on file not found } - $svgFile = []; - $svgFile[] .= ''; - $svgFile[] .= ''; - $svgFile[] .= ''; - - return join($svgFile); + return << + + + HTML; } - } diff --git a/src/Twig/Extension/TwigEditmodeLinkToObject.php b/src/Twig/Extension/TwigEditmodeLinkToObject.php index 784af75..68d7697 100644 --- a/src/Twig/Extension/TwigEditmodeLinkToObject.php +++ b/src/Twig/Extension/TwigEditmodeLinkToObject.php @@ -5,33 +5,49 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; +use Pimcore\Http\Request\Resolver\EditmodeResolver; +use Symfony\Component\HttpFoundation\RequestStack; class TwigEditmodeLinkToObject extends AbstractExtension { + private string $iconPath = '/bundles/pimcoreadmin/img/material-icons/outline-edit-24px.svg'; + private EditmodeResolver $editmodeResolver; + private RequestStack $requestStack; - /** - * @inheritDoc - */ - public function getFunctions(): array + public function __construct(EditmodeResolver $editmodeResolver, RequestStack $requestStack) + { + $this->editmodeResolver = $editmodeResolver; + $this->requestStack = $requestStack; + } + + final public function getFunctions(): array { return [ - new TwigFunction('twigExtensionEditmodeLinkToObject', - [$this, 'getLinkToObject'], ['is_safe' => ['html']]) + new TwigFunction( + 'twigExtensionEditmodeLinkToObject', + [$this, 'getLinkToObject'], + ['is_safe' => ['html']] + ) ]; } - public function getLinkToObject(int $objectId): string + final public function getLinkToObject(int $objectId): string { + $request = $this->requestStack->getCurrentRequest(); + if (!$request || !$this->editmodeResolver->isEditmode($request)) { + // Return an empty string if not in edit mode or if request is null + return ''; + } - $linkToObject = []; - $linkToObject[] .= '
'; - $linkToObject[] .= ''; - $linkToObject[] .= 'iconPath, ENT_QUOTES, 'UTF-8'); - return join($linkToObject); + return << + + Edit Object + +
+ HTML; } } diff --git a/src/Twig/Extension/TwigLanguageSwitcher.php b/src/Twig/Extension/TwigLanguageSwitcher.php index aa9b213..3e10401 100644 --- a/src/Twig/Extension/TwigLanguageSwitcher.php +++ b/src/Twig/Extension/TwigLanguageSwitcher.php @@ -1,100 +1,112 @@ documentService = $documentService; + $this->logger = $logger; } /** - * @inheritDoc + * Registers Twig functions provided by this extension. + * + * @return array An array of TwigFunction instances. */ - public function getFunctions(): array + final public function getFunctions(): array { return [ - new TwigFunction('twigExtensionLocalizedLinks', - [$this, 'getLocalizedLinks']), - new TwigFunction('twigExtensionLanguageFlag', - [$this, 'getLanguageFlag']) + new TwigFunction('twigExtensionLocalizedLinks', [$this, 'getLocalizedLinks']), + new TwigFunction('twigExtensionLanguageFlag', [$this, 'getLanguageFlag']) ]; } - public function getLocalizedLinks(Document $document): array + /** + * Fetches localized links for a given document. + * + * @param Document $document The document to fetch localized links for. + * @return array An array of localized links, with language codes as keys and details as values. + */ + final public function getLocalizedLinks(Document $document): array { - $translations = $this->documentService->getTranslations($document); + try { + $translations = $this->documentService->getTranslations($document); + $validLanguages = Tool::getValidLanguages(); + $links = []; - $links = []; - foreach (Tool::getValidLanguages() as $language) { - $target = trim('/' . $language); + foreach ($validLanguages as $language) { + $target = '/' . $language; - //skip if root document for local is missing - if (!(Document::getByPath($target) instanceof Document)) { - continue; - } + if (!(Document::getByPath($target) instanceof Document)) { + continue; + } - if (isset($translations[$language])) { - $localizedDocument = Document::getById($translations[$language]); - if ($localizedDocument) { - $target = $localizedDocument->getFullPath(); + if (isset($translations[$language])) { + $localizedDocument = Document::getById($translations[$language]); + if ($localizedDocument) { + $target = $localizedDocument->getFullPath(); + } } + + $links[$language] = [ + 'link' => $target, + 'text' => \Locale::getDisplayLanguage($language) + ]; } - $links[$language] = [ - 'link' => $target, - 'text' => \Locale::getDisplayLanguage($language) - ]; + return $links; + } catch (\Exception $e) { + $this->logger->error('Error fetching localized links: ' . $e->getMessage()); + return []; } - - return $links; } /** - * @param string $language - * @return string + * Retrieves the web path to a flag icon for the specified language. + * + * @param string $language The language code. + * @return string The web path to the language's flag icon. */ - public function getLanguageFlag(string $language): string + final public function getLanguageFlag(string $language): string { $flag = ''; - if (Tool::isValidLanguage($language)) { - $flag = self::getLanguageFlagFile($language); + try { + if (Tool::isValidLanguage($language)) { + $flag = self::getLanguageFlagFile($language); + } + $flag = preg_replace('@^' . preg_quote(PIMCORE_WEB_ROOT, '@') . '@', '', $flag); + } catch (\Exception $e) { + $this->logger->error('Error fetching language flag: ' . $e->getMessage()); } - $flag = preg_replace('@^' . preg_quote(PIMCORE_WEB_ROOT, '@') . '@', '', $flag); return $flag; } /** - * @param string $language + * Determines the file path for a given language's flag icon. * - * @return string + * @param string $language The language code. + * @return string The file path for the flag icon. */ public static function getLanguageFlagFile(string $language): string { @@ -102,7 +114,23 @@ public static function getLanguageFlagFile(string $language): string $code = strtolower($language); $iconPath = $basePath . '/countries/_unknown.svg'; - $languageCountryMapping = [ + $languageCountryMapping = self::getLanguageCountryMapping(); + + if (array_key_exists($code, $languageCountryMapping)) { + $iconPath = $basePath . '/countries/' . $languageCountryMapping[$code] . '.svg'; + } + + return $iconPath; + } + + /** + * Returns a mapping of language codes to their corresponding country code for flag icons. + * + * @return array The mapping of language codes to country codes. + */ + private static function getLanguageCountryMapping(): array + { + return [ 'aa' => 'er', 'af' => 'za', 'am' => 'et', 'as' => 'in', 'ast' => 'es', 'asa' => 'tz', 'az' => 'az', 'bas' => 'cm', 'eu' => 'es', 'be' => 'by', 'bem' => 'zm', 'bez' => 'tz', 'bg' => 'bg', 'bm' => 'ml', 'bn' => 'bd', 'br' => 'fr', 'brx' => 'in', 'bs' => 'ba', 'cs' => 'cz', 'da' => 'dk', @@ -118,13 +146,7 @@ public static function getLanguageFlagFile(string $language): string 'cy' => 'gb-wls', 'cy-gb' => 'gb-wls', 'fy' => 'nl', 'xh' => 'za', 'yo' => 'bj', 'zu' => 'za', 'ta' => 'lk', 'te' => 'in', 'ss' => 'za', 'sw' => 'ke', 'so' => 'so', 'si' => 'lk', 'ii' => 'cn', 'zh-hans' => 'cn', 'sn' => 'zw', 'rm' => 'ch', 'pa' => 'in', 'fa' => 'ir', 'lv' => 'lv', 'gl' => 'es', - 'fil' => 'ph', + 'fil' => 'ph' ]; - - if (array_key_exists($code, $languageCountryMapping)) { - $iconPath = $basePath . '/countries/' . $languageCountryMapping[$code] . '.svg'; - } - - return $iconPath; } } diff --git a/src/Twig/Extension/TwigQrCode.php b/src/Twig/Extension/TwigQrCode.php index 5cf8ec2..0b7a362 100644 --- a/src/Twig/Extension/TwigQrCode.php +++ b/src/Twig/Extension/TwigQrCode.php @@ -23,39 +23,40 @@ public function __construct(Translator $translator) $this->translator = $translator; } - public function getFunctions(): array + final public function getFunctions(): array { return [ - new TwigFunction('twigExtensionQrImage', - [$this, 'getQrImg'], ['is_safe' => ['html']]) + new TwigFunction( + 'twigExtensionQrImage', + [$this, 'getQrImg'], + ['is_safe' => ['html']] + ) ]; } - public function getQrImg( + final public function getQrImg( string $qrData, int $size = 150, int $margin = 0, array $qrColor = [0, 0, 0], array $bgColor = [255, 225, 255] - ): string - { + ): string { - $qrImg = []; - $qrImg[] .= 'getQrData($qrData, $size, $margin, $qrColor, $bgColor)->getDataUri() . '" '; - $qrImg[] .= 'alt="' . $this->translator->trans("scan me") . '"/>'; + $src = $this->getQrData($qrData, $size, $margin, $qrColor, $bgColor)->getDataUri(); + $alt = $this->translator->trans("scan me"); - return join($qrImg); + return << + HTML; } - public function getQrData( + final public function getQrData( string $data, int $size, int $margin, array $qrColor, array $bgColor - ): ResultInterface - { + ): ResultInterface { $writer = new SvgWriter(); $qrCode = QrCode::create($data) ->setEncoding(new Encoding('UTF-8')) diff --git a/src/Twig/Extension/TwigSvgFun.php b/src/Twig/Extension/TwigSvgFun.php deleted file mode 100644 index 4e45560..0000000 --- a/src/Twig/Extension/TwigSvgFun.php +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - '.$value.''; - - return $svg; - } - -} diff --git a/src/Twig/Filter/TwigArrayFilters.php b/src/Twig/Filter/TwigArrayFilters.php index 5443da0..b28512e 100644 --- a/src/Twig/Filter/TwigArrayFilters.php +++ b/src/Twig/Filter/TwigArrayFilters.php @@ -11,44 +11,43 @@ class TwigArrayFilters extends AbstractExtension public function getFilters(): array { return [ - new TwigFilter('twigFilterArrayFlip', - [$this, 'arrayFlip']), - new TwigFilter('twigFilterArrayReverse', - [$this, 'arrayReverse']), - new TwigFilter('twigFilterArrayShuffle', - [$this, 'arrayShuffle']) + new TwigFilter('twigFilterArrayFlip', [$this, 'arrayFlip']), + new TwigFilter('twigFilterArrayReverse', [$this, 'arrayReverse']), + new TwigFilter('twigFilterArrayShuffle', [$this, 'arrayShuffle']) ]; } - public function arrayFlip(array $arr): iterable + /** + * Flips an array. + */ + final public function arrayFlip(iterable $arr): array { - if (is_array($arr)) { - return array_flip($arr); - } - return $arr; + return array_flip($this->iterableToArray($arr)); } - public function arrayReverse(array $arr): iterable + /** + * Reverses an array. + */ + final public function arrayReverse(iterable $arr, bool $preserveKeys = false): array { - if (is_array($arr)) { - return array_reverse($arr); - } - return $arr; + return array_reverse($this->iterableToArray($arr), $preserveKeys); } /** - * @param array|\Traversable $arr + * Shuffles an array. */ - public function arrayShuffle(array $arr): iterable + final public function arrayShuffle(iterable $arr): array { - if (is_array($arr)) { - if ($arr instanceof \Traversable) { - $arr = iterator_to_array($arr, false); - } - shuffle($arr); - return $arr; - } + $array = $this->iterableToArray($arr); + shuffle($array); + return $array; + } - return $arr; + /** + * Converts iterable to an array. + */ + private function iterableToArray(iterable $iterable): array + { + return is_array($iterable) ? $iterable : iterator_to_array($iterable, true); } } diff --git a/src/Twig/Filter/TwigDateDiff.php b/src/Twig/Filter/TwigDateDiff.php index 39254e3..21e34d8 100644 --- a/src/Twig/Filter/TwigDateDiff.php +++ b/src/Twig/Filter/TwigDateDiff.php @@ -1,18 +1,8 @@ 'year', 'm' => 'month', 'd' => 'day', @@ -29,80 +19,61 @@ class TwigDateDiff extends AbstractExtension 's' => 'second', ]; - private $translator; + private ?TranslatorInterface $translator; - public function __construct(TranslatorInterface $translator = null) + public function __construct(?TranslatorInterface $translator = null) { - // Ignore the IdentityTranslator, otherwise the parameters won't be replaced properly - if ($translator instanceof IdentityTranslator) { - $translator = null; - } $this->translator = $translator; } - public function getFilters(): array + final public function getFilters(): array { return [ - new TwigFilter('twigFilterPassedTimeToNow', - [$this, 'passedTimeToNow'], ['needs_environment' => true]) + new TwigFilter( + 'twigFilterPassedTimeToNow', + [$this, 'passedTimeToNow'], + ['needs_environment' => true] + ) ]; } - /** - * Filters for converting dates to a time ago string like Facebook and Twitter has. - * - * @param string|\DateTime $date a string or DateTime object to convert - * @param string|\DateTime $now A string or DateTime object to compare with. If none given, the current time will be used. - * - * @return string the converted time - */ - public function passedTimeToNow( + final public function passedTimeToNow( Environment $env, string $date, - string $now = null - ) - { - if (1 === preg_match('~^[1-9][0-9]*$~', $date)) { - - // Convert both dates to DateTime instances. - $date = twig_date_converter($env, $date); - $now = twig_date_converter($env, $now); - - // Get the difference between the two DateTime objects. - $diff = $date->diff($now); - - // Check for each interval if it appears in the $diff object. - foreach (self::$units as $attribute => $unit) { - $count = $diff->$attribute; - - if (0 !== $count) { - return $this->getPluralizedInterval($count, $diff->invert, $unit); - } + string $now = 'now' + ): string { + $date = $date instanceof \DateTimeInterface ? $date : twig_date_converter($env, $date); + $now = $now instanceof \DateTimeInterface ? $now : twig_date_converter($env, $now); + + $diff = $date->diff($now); + foreach (self::$units as $attribute => $unit) { + $count = $diff->$attribute; + if ($count !== 0) { + return $this->getPluralizedInterval($count, $diff->invert, $unit); } - - return ''; - } else { - - return $date; } + return 'just now'; } private function getPluralizedInterval( - $count, - $invert, - $unit - ) - { - if ($this->translator) { - $id = sprintf('diff.%s.%s', $invert ? 'in' : 'ago', $unit); - - return $this->translator->trans($id, (array)$count, ['%count%' => $count], 'date'); - } - - if (1 !== $count) { - $unit .= 's'; - } + int $count, + int $invert, + string $unit + ): string { + $key = sprintf('diff.%s.%s', $invert ? 'in' : 'ago', $unit); + $translationCount = ['%count%' => $count]; + + return $this->translator ? + $this->translator->trans($key, $translationCount, 'date') : + $this->formatWithoutTranslation($count, $invert, $unit); + } + private function formatWithoutTranslation( + int $count, + int $invert, + string $unit + ): string { + $unit .= $count === 1 ? '' : 's'; return $invert ? "in $count $unit" : "$count $unit ago"; } } diff --git a/src/Twig/Filter/TwigFileGetContents.php b/src/Twig/Filter/TwigFileGetContents.php index cd664af..dfe2b6a 100644 --- a/src/Twig/Filter/TwigFileGetContents.php +++ b/src/Twig/Filter/TwigFileGetContents.php @@ -1,5 +1,5 @@ assetsDirectory = $this->webRoot . '/var/assets'; + } + + final public function getFilters(): array { return [ - new TwigFilter('twigFilterFileGetContents', - [$this, 'getFileGetContents'], ['is_safe' => ['html']]) + new TwigFilter( + 'twigFilterFileGetContents', + [$this, 'getFileContents'], + ['is_safe' => ['html']] + ) ]; } - public function getFileGetContents(string $file): string + final public function getFileContents(string $file): string + { + $filePath = $this->getFilePath($file); + + if (is_file($filePath) && is_readable($filePath)) { + return file_get_contents($filePath); + } + + return ''; + } + + private function getFilePath(string $file): string { - $relativPath = "/" . trim($file, '/'); - $rootPath = PIMCORE_WEB_ROOT . $relativPath; - $assetPath = PIMCORE_WEB_ROOT . '/var/assets/' . $relativPath; + // Prevent directory traversal + $file = basename($file); - if (is_file($rootPath)) { - return file_get_contents($rootPath); + // Here you can add more checks, for example, file extension checks for added security + $assetPath = $this->assetsDirectory . '/' . trim($file, '/'); + if (is_file($assetPath) && is_readable($assetPath)) { + return $assetPath; } - if (is_file($assetPath)) { - return file_get_contents($assetPath); + // Fallback to checking in the web root if not found in assets + $rootPath = $this->webRoot . '/' . trim($file, '/'); + if (is_file($rootPath) && is_readable($rootPath)) { + return $rootPath; } - return $file; + return ''; } } diff --git a/src/Twig/Filter/TwigFormatPrice.php b/src/Twig/Filter/TwigFormatPrice.php index 23c59b3..b005975 100644 --- a/src/Twig/Filter/TwigFormatPrice.php +++ b/src/Twig/Filter/TwigFormatPrice.php @@ -1,4 +1,5 @@ formatCurrency($value, $currencySymbol); - return $return; + if (!empty($currencySymbol) && strpos($formattedValue, $currencySymbol) === false) { + $formattedValue = $currencySymbol . ' ' . $formattedValue; + } + return $formattedValue; } } diff --git a/src/Twig/Filter/TwigHrefFilters.php b/src/Twig/Filter/TwigHrefFilters.php index e2be41a..f098172 100644 --- a/src/Twig/Filter/TwigHrefFilters.php +++ b/src/Twig/Filter/TwigHrefFilters.php @@ -13,145 +13,120 @@ class TwigHrefFilters extends AbstractExtension /** * {@inheritdoc} */ - public function getFilters(): array + final public function getFilters(): array { return [ - new TwigFilter('twigFilterHrefUrl', - [$this, 'getHrefUrl'], ['is_safe' => ['html']]), - new TwigFilter('twigFilterHrefEmail', - [$this, 'getHrefEmail'], ['is_safe' => ['html']]), - new TwigFilter('twigFilterHrefPhone', - [$this, 'getHrefPhone'], ['is_safe' => ['html']]), - new TwigFilter('twigFilterHrefWhatsApp', - [$this, 'getHrefWhatsApp'], ['is_safe' => ['html']]), - new TwigFilter('twigFilterHrefSocialMedia', - [$this, 'getHrefSocialMedia'], ['is_safe' => ['html']]), + new TwigFilter( + 'twigFilterHrefUrl', + [$this, 'getHrefUrl'], + ['is_safe' => ['html']] + ), + new TwigFilter( + 'twigFilterHrefEmail', + [$this, 'getHrefEmail'], + ['is_safe' => ['html']] + ), + new TwigFilter( + 'twigFilterHrefPhone', + [$this, 'getHrefPhone'], + ['is_safe' => ['html']] + ), + new TwigFilter( + 'twigFilterHrefWhatsApp', + [$this, 'getHrefWhatsApp'], + ['is_safe' => ['html']] + ), + new TwigFilter( + 'twigFilterHrefSocialMedia', + [$this, 'getHrefSocialMedia'], + ['is_safe' => ['html']] + ), ]; } - public function getHrefUrl( + final public function getHrefUrl( string $url, string $class = '', string $target = '_blank' - ): string - { - if (filter_var($url, FILTER_VALIDATE_URL)) { - $webLink = []; - $webLink[] .= ''; - $webLink[] .= str_replace(['http://', 'https://'], '', $url); - $webLink[] .= ''; - - return join($webLink); - } else { - - return $url; - } + ): string { + + $classAttribute = $class ? 'class="' . htmlspecialchars($class) . '"' : ''; + $cleanUrl = htmlspecialchars($url); + $displayUrl = htmlspecialchars(str_replace(['http://', 'https://'], '', $url)); + + return "{$displayUrl}"; } - public function getHrefEmail( + final public function getHrefEmail( string $email, string $class = '', string $subject = '', string $body = '', string $cc = '', string $bcc = '' - ): string - { - if (filter_var($email, FILTER_VALIDATE_EMAIL)) { - - $emailLink = []; - $emailLink[] .= 'strToASCII('mailto:' . $email); - $emailLink[] .= $subject != '' ? '?subject=' . $subject : ''; - $emailLink[] .= $body != '' ? '&body=' . $body : ''; - $emailLink[] .= $cc != '' && filter_var($cc, FILTER_VALIDATE_EMAIL) ? '&cc=' . $this->strToASCII($cc) : ''; - $emailLink[] .= $bcc != '' && filter_var($bcc, FILTER_VALIDATE_EMAIL) ? '&bcc=' . $this->strToASCII($bcc) : ''; - $emailLink[] .= '">'; - $emailLink[] .= $this->strToASCII($email); - $emailLink[] .= ''; - - return join($emailLink); + ): string { + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + return htmlspecialchars($email); + } + $href = "mailto:" . urlencode($email); + $href .= $subject ? '?subject=' . urlencode($subject) : ''; + $href .= $body ? ($subject ? '&' : '?') . 'body=' . urlencode($body) : ''; + $href .= $cc ? ($subject || $body ? '&' : '?') . 'cc=' . urlencode($cc) : ''; + $href .= $bcc ? ($subject || $body || $cc ? '&' : '?') . 'bcc=' . urlencode($bcc) : ''; - } else { + $classAttribute = $class ? ' class="' . htmlspecialchars($class) . '"' : ''; + $emailDisplay = htmlspecialchars($email); - return $email; - } + return "{$emailDisplay}"; } - public function getHrefPhone( + final public function getHrefPhone( string $phone, string $hrefPrefix = 'tel:', string $countryCode = '+41', - ): string - { + ): string { return $this->getPhoneBaseLink($phone, $hrefPrefix, $countryCode); } - public function getHrefWhatsApp( + final public function getHrefWhatsApp( string $phone, string $hrefPrefix = 'https://wa.me/', string $countryCode = '+41' - ): string - { + ): string { return $this->getPhoneBaseLink($phone, $hrefPrefix, $countryCode); } - public function getPhoneBaseLink( + final public function getPhoneBaseLink( string $phone, string $hrefPrefix, string $countryCode - ): string - { - $nrToSanitize = $phone; - $number = filter_var($nrToSanitize, FILTER_SANITIZE_NUMBER_INT); + ): string { + $sanitizedNumber = filter_var($phone, FILTER_SANITIZE_NUMBER_INT); + $sanitizedNumber = ltrim($sanitizedNumber, '0'); + $fullNumber = $countryCode . $sanitizedNumber; - if (preg_match('/^[0-9]{10}+$/', $number)) { - - $phoneLink = []; - $phoneLink[] .= 'strToASCII($phone); - $phoneLink[] .= ''; - - return join($phoneLink); + if (!preg_match('/^\+[0-9]{10,}$/', $fullNumber)) { + return htmlspecialchars($phone); + } - } else { + $href = $hrefPrefix . $fullNumber; + $phoneDisplay = htmlspecialchars($phone); + $target = str_contains($hrefPrefix, 'https') ? ' target="_blank"' : ''; - return $phone; - } + return "{$phoneDisplay}"; } - public function getHrefSocialMedia( + final public function getHrefSocialMedia( string $url, string $name = 'bootstrap' - ): string - { - if (filter_var($url, FILTER_VALIDATE_URL)) { - $smLink = []; - $smLink[] .= ''; - $smLink[] .= (new TwigBootstrapSvgIcon)->getBootstrapSvgIcon($name); - $smLink[] .= ''; - - return join($smLink); - } else { - - return $url; + ): string { + if (!filter_var($url, FILTER_VALIDATE_URL)) { + return htmlspecialchars($url); } - } - public function strToASCII(string $string): string - { - $output = ''; - for ($i = 0; $i < strlen($string); $i++) { - $output .= '&#' . ord($string[$i]) . ';'; - } - return $output; - } + $cleanUrl = htmlspecialchars($url); + $icon = (new TwigBootstrapSvgIcon())->getBootstrapSvgIcon($name); // Consider Dependency Injection + return "{$icon}"; + } } diff --git a/src/Twig/Filter/TwigImages.php b/src/Twig/Filter/TwigImages.php index 22e5d8c..9834700 100644 --- a/src/Twig/Filter/TwigImages.php +++ b/src/Twig/Filter/TwigImages.php @@ -1,50 +1,61 @@ ['html']]), - new TwigFilter('twigFilterImgThumbConfig', [$this, 'getImgThumbConfig'], ['is_safe' => ['html']]), - new TwigFilter('twigFilterCssBgImg', [$this, 'getCssBgImg'], ['is_safe' => ['html']]), + new TwigFilter( + 'twigFilterImgThumbnail', + [$this, 'getImgThumbnail'], + ['is_safe' => ['html']] + ), + new TwigFilter( + 'twigFilterImgThumbConfig', + [$this, 'getImgThumbConfig'], + ['is_safe' => ['html']] + ), + new TwigFilter( + 'twigFilterCssBgImg', + [$this, 'getCssBgImg'], + ['is_safe' => ['html']] + ), ]; } - public function getImgThumbnail( - object $image, + final public function getImgThumbnail( + Image $image = null, string $thumbnailName = null, string $cssClass = 'img-fluid', string $alt = '', - array $attrData = [], - ): string|object - { - if ($image->getImage() instanceof Asset\Image) { - $attributes = ["class" => $cssClass, "alt" => $alt]; - $array = array_merge($attributes, $attrData); - - $img = $image->getThumbnail($thumbnailName)->getHtml(['imgAttributes' => $array]); - return $img; - - } else { - - return $image; + array $attrData = [] + ): string { + + $attributes = $this->mergeAttributes([ + "class" => $cssClass, + "alt" => $alt + ], $attrData); + + if ($image instanceof Image) { + if ($thumbnailName) { + $thumbnail = $image->getThumbnail($thumbnailName); + if ($thumbnail) { + return $thumbnail->getHtml(['imgAttributes' => $attributes]); + } + } + return 'htmlAttributes($attributes) . ' />'; } + return ''; } - public function getImgThumbConfig( - object $image, + final public function getImgThumbConfig( + Image $image = null, string $cssClass = 'img-fluid', string $alt = '', int $width = 250, @@ -52,39 +63,60 @@ public function getImgThumbConfig( int $quality = 90, string $format = 'webp', array $attrData = [], - bool $aspectratio = true, - ): string|object - { - if ($image->getImage() instanceof Asset\Image) { - $attributes = ["class" => $cssClass, "alt" => $alt]; - $array = array_merge($attributes, $attrData); - - $img = $image->getThumbnail([ - "width" => $width, - "height" => $height, - "aspectratio" => $aspectratio, - "quality" => $quality, - "format" => $format - ])->getHtml(['imgAttributes' => $array]); + bool $aspectratio = true + ): string { + + $attributes = $this->mergeAttributes([ + "class" => $cssClass, + "alt" => $alt + ], $attrData); + + $config = [ + "width" => $width, + "height" => $height, + "aspectratio" => $aspectratio, + "quality" => $quality, + "format" => $format + ]; - return $img; - } else { - return $image; + if ($image instanceof Image) { + $thumbnail = $image->getThumbnail($config); + if ($thumbnail) { + return $thumbnail->getHtml(['imgAttributes' => $attributes]); + } } + return ''; } - public function getCssBgImg( - object $image, - string $thumbnailName = null, - ): string - { - if ($image->getImage() instanceof Asset\Image) { - $thumb = $image->getThumbnail($thumbnailName); + final public function getCssBgImg( + Image $image = null, + string $thumbnailName = null + ): string { - return ' style="background-image: url(' . $thumb . ');" '; + if ($image instanceof Image) { + $thumb = $image->getThumbnail($thumbnailName); + if ($thumb) { + $url = htmlspecialchars($thumb->getPath(), ENT_QUOTES, 'UTF-8'); + return "style=\"background-image: url('{$url}');\""; + } } - return ''; } + private function mergeAttributes( + array $default, + array $additional + ): array { + return array_merge($default, $additional); + } + + private function htmlAttributes( + array $attributes + ): string { + $htmlParts = []; + foreach ($attributes as $key => $value) { + $htmlParts[] = htmlspecialchars($key, ENT_QUOTES, 'UTF-8') . '="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '"'; + } + return implode(' ', $htmlParts); + } } diff --git a/src/Twig/Filter/TwigStringFilters.php b/src/Twig/Filter/TwigStringFilters.php index 39b18c7..d6ac8e5 100644 --- a/src/Twig/Filter/TwigStringFilters.php +++ b/src/Twig/Filter/TwigStringFilters.php @@ -9,45 +9,78 @@ class TwigStringFilters extends AbstractExtension { - public function getFilters(): array + final public function getFilters(): array { return [ - new TwigFilter('twigFilterGetMd5', [$this, 'getMd5']), - new TwigFilter('twigFilterGetUniqid', [$this, 'getUniqid']), - new TwigFilter('twigFilterStringNormalizer', [$this, 'getStringNormalizer']), - new TwigFilter('twigFilterStrToLower', [$this, 'getStrToLower']), + new TwigFilter( + 'twigFilterGetMd5', + [$this, 'getMd5'] + ), + new TwigFilter( + 'twigFilterGetUniqid', + [$this, 'getUniqid'] + ), + new TwigFilter( + 'twigFilterStringNormalizer', + [$this, 'getStringNormalizer'] + ), + new TwigFilter( + 'twigFilterStrToLower', + 'strtolower' + ), + new TwigFilter( + 'twigFilterStrToUpper', + 'strtoupper' + ), + new TwigFilter( + 'twigFilterStrCapitalize', + 'ucwords' + ), + new TwigFilter( + 'twigFilterNormalizeFolderName', + [$this, 'getNormalizeFolderName'] + ), ]; } - public function getMd5(string $string, bool $binary = false): string - { + final public function getMd5( + string $string, + bool $binary = false + ): string { return md5($string, $binary); } - public function getUniqId(string $prefix = '', bool $moreEntropy = false): string - { + final public function getUniqId( + string $prefix = '', + bool $moreEntropy = false + ): string { return uniqid($prefix, $moreEntropy); } - public function getStrToLower(string $string): string - { - return strtolower($this->getStringNormalizer($string) ); + final public function getNormalizeFolderName( + string $string + ): string { + return strtolower($this->getStringNormalizer($string)); } - public static function getStringNormalizer(string $string): string - { - $search = ['~','¨','^','?', '\'', '"', '/', '-', '+', '.', ',', ';', '(', ')', ' ', '&', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', 'ß', 'É', 'é', 'È', 'è', 'Ê', 'ê', 'E', 'e', 'Ë', 'ë', + public static function getStringNormalizer( + string $string + ): string { + $normalized = iconv('UTF-8', 'ASCII//TRANSLIT', $string); + + $search = ['~', '¨', '^', '?', '\'', '"', '/', '-', '+', '.', ',', ';', '(', ')', ' ', '&', 'ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', 'ß', 'É', 'é', 'È', 'è', 'Ê', 'ê', 'E', 'e', 'Ë', 'ë', 'À', 'à', 'Á', 'á', 'Å', 'å', 'a', 'Â', 'â', 'Ã', 'ã', 'ª', 'Æ', 'æ', 'C', 'c', 'Ç', 'ç', 'C', 'c', 'Í', 'í', 'Ì', 'ì', 'Î', 'î', 'Ï', 'ï', 'Ó', 'ó', 'Ò', 'ò', 'Ô', 'ô', 'º', 'Õ', 'õ', 'Œ', 'O', 'o', 'Ø', 'ø', 'Ú', 'ú', 'Ù', 'ù', 'Û', 'û', 'U', 'u', 'U', 'u', 'Š', 'š', 'S', 's', - 'Ž', 'ž', 'Z', 'z', 'Z', 'z', 'L', 'l', 'N', 'n', 'Ñ', 'ñ', '¡', '¿', 'Ÿ', 'ÿ', '_', ':' ]; - $replace = ['','','', '', '', '', '', '-', '', '', '-', '-', '', '', '-', '', 'ae', 'oe', 'ue', 'Ae', 'Oe', 'Ue', 'ss', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', + 'Ž', 'ž', 'Ñ', 'ñ', '¡', '¿', 'Ÿ', 'ÿ', '_', ':']; + $replace = ['', '', '', '', '', '', '', '-', '', '', '-', '-', '', '', '-', '', 'ae', 'oe', 'ue', 'Ae', 'Oe', 'Ue', 'ss', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'A', 'a', 'A', 'a', 'A', 'a', 'a', 'A', 'a', 'A', 'a', 'a', 'AE', 'ae', 'C', 'c', 'C', 'c', 'C', 'c', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'O', 'o', 'O', 'o', 'O', 'o', 'o', 'O', 'o', 'OE', 'O', 'o', 'O', 'o', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'S', 's', 'S', 's', - 'Z', 'z', 'Z', 'z', 'Z', 'z', 'L', 'l', 'N', 'n', 'N', 'n', '', '', 'Y', 'y', '-', '-' ]; + 'Z', 'z', 'N', 'n', '', '', 'Y', 'y', '-', '-']; - $value = str_replace($search, $replace, $string); + $normalized = str_replace($search, $replace, $normalized); + $normalized = preg_replace('/[^a-zA-Z0-9\-]/', '', $normalized); - return $value; + return $normalized; } } diff --git a/src/Twig/Filter/TwigTextFilters.php b/src/Twig/Filter/TwigTextFilters.php index b56f86f..d5cf52a 100644 --- a/src/Twig/Filter/TwigTextFilters.php +++ b/src/Twig/Filter/TwigTextFilters.php @@ -1,17 +1,6 @@ - * https://github.com/twigphp/Twig-extensions/blob/master/src/TextExtension.php - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * https://github.com/twigphp/Twig-extensions/blob/master/LICENSE - */ - namespace SergeDesign\PimcoreCustomTwigBundle\Twig\Filter; use Twig\Extension\AbstractExtension; @@ -20,59 +9,47 @@ class TwigTextFilters extends AbstractExtension { - public function getFilters(): array + final public function getFilters(): array { return [ - new TwigFilter('twigFilterTruncate', - [$this, 'getTruncateFilter'], ['needs_environment' => true, 'is_safe' => ['html']]), - new TwigFilter('twigFilterWordwrap', - [$this, 'getWordwrapFilter'], ['needs_environment' => true, 'is_safe' => ['html']]), + new TwigFilter( + 'twigFilterTruncate', + [$this, 'truncateFilter'], + ['needs_environment' => true, 'is_safe' => ['html']] + ), + new TwigFilter( + 'twigFilterWordwrap', + [$this, 'wordwrapFilter'], + ['is_safe' => ['html']] + ), ]; } - public function getTruncateFilter( + final public function truncateFilter( Environment $env, string $value, int $length = 30, bool $preserve = false, string $separator = '...' - ): string - { - if (mb_strlen($value, $env->getCharset()) > $length) { - if ($preserve) { - // If breakpoint is on the last word, return the value without separator. - if (false === ($breakpoint = mb_strpos($value, ' ', $length, $env->getCharset()))) { - return $value; - } - $length = $breakpoint; - } - return rtrim(mb_substr($value, 0, $length, $env->getCharset())) . $separator; + ): string { + if (mb_strlen($value, $env->getCharset()) <= $length) { + return $value; } - return $value; - } - public function getWordwrapFilter( - Environment $env, - string $value, - int $length = 50, - string $separator = "\r\n", - bool $preserve = false - ): string - { - $sentences = []; - $previous = mb_regex_encoding(); - mb_regex_encoding($env->getCharset()); + if ($preserve) { + $cutOff = mb_strpos($value, ' ', $length, $env->getCharset()); + $length = ($cutOff !== false) ? $cutOff : $length; + } - $pieces = mb_split($separator, $value); - mb_regex_encoding($previous); + return rtrim(mb_substr($value, 0, $length, $env->getCharset())) . $separator; + } - foreach ($pieces as $piece) { - while (!$preserve && mb_strlen($piece, $env->getCharset()) > $length) { - $sentences[] = mb_substr($piece, 0, $length, $env->getCharset()); - $piece = mb_substr($piece, $length, 2048, $env->getCharset()); - } - $sentences[] = $piece; - } - return nl2br(implode($separator, $sentences)); + final public function wordwrapFilter( + string $value, + int $length = 50, + string $separator = "\n", + bool $cut = false + ): string { + return nl2br(wordwrap($value, $length, $separator, $cut)); } } diff --git a/src/Twig/Filter/TwigUnixToTime.php b/src/Twig/Filter/TwigUnixToTime.php index 5f03294..570adce 100644 --- a/src/Twig/Filter/TwigUnixToTime.php +++ b/src/Twig/Filter/TwigUnixToTime.php @@ -1,4 +1,5 @@ translator = $translator; } - public function getFilters(): array + final public function getFilters(): array { return [ - new TwigFilter('twigFilterUnixTimestampToTime', - [$this, 'getUnixTimestampToTime'], ['is_safe' => ['html']]), + new TwigFilter( + 'twigFilterUnixTimestampToTime', + [$this, 'unixTimestampToTime'], + ['is_safe' => ['html']] + ), ]; } - public function getUnixTimestampToTime(int $unixTimeStamp): string - { - - if (is_numeric($unixTimeStamp)) { - - $days = floor($unixTimeStamp / 86400); - $hrs = floor($unixTimeStamp / 3600); - $mins = intval(($unixTimeStamp / 60) % 60); - $sec = intval($unixTimeStamp % 60); - - if ($days > 1) { - $stringDays = 'Days'; - } else { - $stringDays = 'Day'; - } + final public function unixTimestampToTime( + int $unixTimestamp + ): string { + $time = new \DateTime("@$unixTimestamp"); + $now = new \DateTime('now'); + $interval = $now->diff($time); - if ($days > 0) { - //echo $days;exit; - $hrs = str_pad($hrs, 2, '0', STR_PAD_LEFT); - $hours = $hrs - ($days * 24); - $return_days = $days . ' ' . $this->translator->trans($stringDays) . ' '; - - $hrs = str_pad($hours, 2, '0', STR_PAD_LEFT); - } else { - $return_days = ""; - $hrs = str_pad($hrs, 2, '0', STR_PAD_LEFT); - } - - $mins = str_pad($mins, 2, '0', STR_PAD_LEFT); + $parts = []; + if ($interval->days > 0) { + $parts[] = $interval->format('%a') . ' ' . $this->translator->trans($interval->days > 1 ? 'Days' : 'Day'); + } - if ($sec > 0) { - $sec = ':' . str_pad($sec, 2, '0', STR_PAD_LEFT); - } else { - $sec = ''; - } - return $return_days . $hrs . ":" . $mins . $sec; + // Format hours and minutes + $parts[] = $interval->format('%H:%I'); - } else { - return $unixTimeStamp; + // Optionally include seconds + if ($interval->s > 0) { + $parts[] = $interval->format('%S'); } - } + return implode(':', $parts); + } } diff --git a/src/Twig/Statements/TwigSwitch.php b/src/Twig/Statements/TwigSwitch.php index df2077f..32477ef 100644 --- a/src/Twig/Statements/TwigSwitch.php +++ b/src/Twig/Statements/TwigSwitch.php @@ -3,13 +3,12 @@ namespace SergeDesign\PimcoreCustomTwigBundle\Twig\Statements; -use SergeDesign\PimcoreCustomTwigBundle\Tokenparsers\SwitchTokenParser; +use SergeDesign\PimcoreCustomTwigBundle\TokenParsers\SwitchTokenParser; use Twig\Extension\AbstractExtension; class TwigSwitch extends AbstractExtension { - - public final function getTokenParsers(): array + final public function getTokenParsers(): array { return [ new SwitchTokenParser(), diff --git a/src/Twig/Test/TwigBundleChecker.php b/src/Twig/Test/TwigBundleChecker.php index ce5a8e6..9050d09 100644 --- a/src/Twig/Test/TwigBundleChecker.php +++ b/src/Twig/Test/TwigBundleChecker.php @@ -1,36 +1,33 @@ container = $container; + $this->bundles = $bundles; } - public function getFunctions(): array + final public function getFunctions(): array { return [ - new TwigFunction('twigTestBundleChecker', [$this, 'getBundleChecker']), + new TwigFunction( + 'twigTestBundleChecker', + [$this, 'isBundleRegistered'] + ), ]; } - public function getBundleChecker(string $bundle): bool - { - return array_key_exists( - $bundle, - $this->container->getParameter('kernel.bundles') - ); + final public function isBundleRegistered( + string $bundle + ): bool { + return array_key_exists($bundle, $this->bundles); } - } diff --git a/src/Twig/Test/TwigTests.php b/src/Twig/Test/TwigTests.php index e808bd6..a1aa5fc 100644 --- a/src/Twig/Test/TwigTests.php +++ b/src/Twig/Test/TwigTests.php @@ -4,63 +4,43 @@ namespace SergeDesign\PimcoreCustomTwigBundle\Twig\Test; -use Twig\TwigTest; use Twig\Extension\AbstractExtension; +use Twig\TwigTest; use Pimcore\Model\Asset\Image; class TwigTests extends AbstractExtension { - - public function getTests(): array + final public function getTests(): array { - return [ - new TwigTest('pimcoreAssetSvg', function (string $value): bool { - return $value instanceof Image && $value->getMimeType() === "image/svg+xml"; - }), - new TwigTest('twigTestIsArray', function (mixed $value): bool { - return is_array($value); - }), - new TwigTest('twigTestIsBoolean', function (mixed $value): bool { - return is_bool($value); - }), - new TwigTest('twigTestIsCallable', function (mixed $value): bool { - return is_callable($value); - }), - new TwigTest('twigTestIsCountable', function (mixed $value): bool { - return is_countable($value); - }), - new TwigTest('twigTestIsDir', function (string $value): bool { - return is_dir($value); - }), - new TwigTest('twigTestIsFloat', function (mixed $value): bool { - return is_float($value); - }), - new TwigTest('twigTestIsInt', function (mixed $value): bool { - return is_int($value); - }), - new TwigTest('twigTestIsNumeric', function (mixed $value): bool { - return is_numeric($value); - }), - new TwigTest('twigTestIsNull', function (mixed $value): bool { - return is_null($value); - }), - new TwigTest('twigTestIsObject', function (mixed $value): bool { - return is_object($value); - }), - new TwigTest('twigTestIsResource', function (mixed $value): bool { - return is_resource($value); - }), - new TwigTest('twigTestIsScalar', function (mixed $value): bool { - return is_scalar($value); - }), - new TwigTest('twigTestIsString', function (mixed $value): bool { - return is_string($value); - }), - new TwigTest('twigTestIsset', function (mixed $var): bool { - return isset($var); - }) + $phpFunctionTests = [ + 'IsArray' => 'is_array', + 'IsBoolean' => 'is_bool', + 'IsCallable' => 'is_callable', + 'IsCountable' => 'is_countable', + 'IsDir' => 'is_dir', + 'IsFloat' => 'is_float', + 'IsInt' => 'is_int', + 'IsNumeric' => 'is_numeric', + 'IsNull' => 'is_null', + 'IsObject' => 'is_object', + 'IsResource' => 'is_resource', + 'IsScalar' => 'is_scalar', + 'IsString' => 'is_string', + 'Isset' => 'isset', ]; + + $tests = array_map(function ($functionName, $testName) { + return new TwigTest("twigTest{$testName}", $functionName); + }, $phpFunctionTests, array_keys($phpFunctionTests)); + + // Custom Tests + $tests[] = new TwigTest('pimcoreAssetSvg', [$this, 'isPimcoreAssetSvg']); + + return $tests; } + final public function isPimcoreAssetSvg($value): bool + { + return $value instanceof Image && $value->getMimeType() === "image/svg+xml"; + } } -