diff --git a/.gitattributes b/.gitattributes index 75e18f85824..1ce832b520d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ +/doc/** export-ignore /extra/** export-ignore /tests export-ignore /phpunit.xml.dist export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62b4b346412..140483cc751 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: runs-on: 'ubuntu-latest' - continue-on-error: true + continue-on-error: ${{ matrix.experimental }} strategy: matrix: @@ -24,6 +24,9 @@ jobs: - '7.3' - '7.4' - '8.0' + - '8.1' + composer-options: [''] + experimental: [false] steps: - name: "Checkout code" @@ -51,7 +54,7 @@ jobs: key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }} restore-keys: ${{ runner.os }}-${{ matrix.php-version }}-composer- - - run: composer install + - run: composer install ${{ matrix.composer-options }} - name: "Install PHPUnit" run: vendor/bin/simple-phpunit install @@ -79,6 +82,7 @@ jobs: - '7.3' - '7.4' - '8.0' + - '8.1' extension: - 'extra/cache-extra' - 'extra/cssinliner-extra' @@ -88,6 +92,8 @@ jobs: - 'extra/markdown-extra' - 'extra/string-extra' - 'extra/twig-extra-bundle' + composer-options: [''] + experimental: [false] steps: - name: "Checkout code" @@ -165,5 +171,5 @@ jobs: # ini-values: memory_limit=-1 # tools: composer:v2 # -# - run: ./drupal_test.sh +# - run: bash ./tests/drupal_test.sh # shell: "bash" diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 88% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index b81882fbf2c..b07ac7fcabd 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -1,6 +1,6 @@ setRules([ '@Symfony' => true, '@Symfony:risky' => true, @@ -16,5 +16,5 @@ 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'], ]) ->setRiskyAllowed(true) - ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__)) + ->setFinder((new PhpCsFixer\Finder())->in(__DIR__)) ; diff --git a/CHANGELOG b/CHANGELOG index 6215efb81f0..459c05af516 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,43 @@ +# 3.3.8 (2022-02-04) + + * Fix a security issue when in a sandbox: the `sort` filter must require a Closure for the `arrow` parameter + +# 3.3.7 (2022-01-03) + +* Allow more null support when Twig expects a string (for better 8.1 support) +* Only use Commonmark extensions if markdown enabled + +# 3.3.6 (2022-01-03) + +* Only use Commonmark extensions if markdown enabled + +# 3.3.5 (2022-01-03) + +* Allow CommonMark extensions to easily be added +* Allow null when Twig expects a string (for better 8.1 support) +* Make some performance optimizations +* Allow Symfony translation contract v3+ + +# 3.3.4 (2021-11-25) + + * Bump minimum supported Symfony component versions + * Fix a deprecated message + +# 3.3.3 (2021-09-17) + + * Allow Symfony 6 + * Improve compatibility with PHP 8.1 + * Explicitly specify the encoding for mb_ord in JS escaper + +# 3.3.2 (2021-05-16) + + * Revert "Throw a proper exception when a template name is an absolute path (as it has never been supported)" + +# 3.3.1 (2021-05-12) + + * Fix PHP 8.1 compatibility + * Throw a proper exception when a template name is an absolute path (as it has never been supported) + # 3.3.0 (2021-02-08) * Fix macro calls in a "cache" tag @@ -78,1521 +118,3 @@ * removed the "base_template_class" option on Twig\Environment * bumped minimum PHP version to 7.2 * removed PSR-0 classes - -# 2.14.4 (2021-XX-XX) - - * Add the slug filter - -# 2.14.3 (2021-01-05) - - * Fix extra bundle compat with older versions of Symfony - -# 2.14.2 (2021-01-05) - - * Fix "odd" not working for negative numbers - -# 2.14.1 (2020-10-27) - -* Fix "include(template_from_string())" - -# 2.14.0 (2020-10-21) - - * Fix sandbox support when using "include(template_from_string())" - * Make round brackets optional for one argument tests like "same as" or "divisible by" - * Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a } - * Drop PHP 7.1 support - -# 2.13.1 (2020-08-05) - - * Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag - * Fix a regression when not using a space before an operator - * Restrict callables to closures in filters - * Allow trailing commas in argument lists (in calls as well as definitions) - -# 2.13.0 (2020-07-05) - - * Fix options not taken into account when using "Michelf\MarkdownExtra" - * Fix "Twig\Extra\Intl\IntlExtension::getCountryName()" to accept "null" as a first argument - * Drop support for PHP 7.0 - * Throw exception in case non-Traversable data is passed to "filter" - * Fix context optimization on PHP 7.4 - * Fix PHP 8 compatibility - * Fix ambiguous syntax parsing - -# 2.12.5 (2020-02-11) - - * Add a check to ensure that iconv() is defined - -# 2.12.4 (2020-02-11) - - * Avoid exceptions when an intl resource is not found - * Fix implementation of case-insensitivity for method names - -# 2.12.3 (2019-12-28) - - * fixed Symfony 5.0 support for the HTML extra extension - * fixed number formatter in Intl extra extension when using a formatter prototype - -# 2.12.2 (2019-11-11) - - * added supported for exponential numbers - -# 2.12.1 (2019-10-17) - - * added the String extension in the "extra" repositories: "u" filter - -# 2.12.0 (2019-10-05) - - * added the spaceship operator ("<=>"), useful when using an arrow function in the "sort" filter - * added support for an "arrow" function on the "sort" filter - * added the CssInliner extension in the "extra" repositories: "inline_css" - filter - * added the Inky extension in the "extra" repositories: "inky_to_html" filter - * added Intl extension in the "extra" repositories: "country_name", - "currency_name", "currency_symbol", "language_name", "locale_name", - "timezone_name", "format_currency", "format_number", - "format_*_number", "format_datetime", "format_date", and "format_time" - filters, and the "country_timezones" function - * added the Markdown extension in the "extra" repositories: "markdown_to_html", - and "html_to_markdown" filters - * added the HtmlExtension extension in the "extra" repositories: "date_uri" - filter, and "html_classes" function - * optimized "block('foo') ?? 'bar'" - * fixed the empty test on Traversable instances - * fixed array_key_exists() on objects - * fixed cache when opcache is installed but disabled - * fixed using macros in arrow functions - * fixed split filter on edge case - -# 2.11.3 (2019-06-18) - - * display partial output (PHP buffer) when an error occurs in debug mode - * fixed the filter filter (allow the result to be used several times) - * fixed macro auto-import when a template contains only macros - -# 2.11.2 (2019-06-05) - - * fixed macro auto-import - -# 2.11.1 (2019-06-04) - - * added support for "Twig\Markup" instances in the "in" test (again) - * allowed string operators as variables names in assignments - * fixed support for macros defined in parent templates - -# 2.11.0 (2019-05-31) - - * added the possibility to register classes/interfaces as being safe for the escaper ("EscaperExtension::addSafeClass()") - * deprecated CoreExtension::setEscaper() and CoreExtension::getEscapers() in favor of the same methods on EscaperExtension - * macros are now auto-imported in the template they are defined (under the ``_self`` variable) - * added support for macros on "is defined" tests - * fixed macros "import" when using the same name in the parent and child templates - * fixed recursive macros - * macros imported "globally" in a template are now available in macros without re-importing them - * fixed the "filter" filter when the argument is \Traversable but does not implement \Iterator (\SimpleXmlElement for instance) - * fixed a PHP fatal error when calling a macro imported in a block in a nested block - * fixed a PHP fatal error when calling a macro imported in the template in another macro - * fixed wrong error message on "import" and "from" - -# 2.10.0 (2019-05-14) - - * deprecated "if" conditions on "for" tags - * added "filter", "map", and "reduce" filters (and support for arrow functions) - * fixed partial output leak when a PHP fatal error occurs - * optimized context access on PHP 7.4 - -# 2.9.0 (2019-04-28) - - * deprecated returning "false" to remove a Node from NodeVisitorInterface::leaveNode() - * allowed Twig\NodeVisitor\NodeVisitorInterface::leaveNode() to return "null" instead of "false" (same meaning) - * deprecated the "filter" tag (use the "apply" tag instead) - * added the "apply" tag as a replacement for the "filter" tag - * allowed Twig\Loader\FilesystemLoader::findTemplate() to return "null" instead of "false" (same meaning) - * added support for "Twig\Markup" instances in the "in" test - * fixed "import" when macros are stored in a template string - * fixed Lexer when using custom options containing the # char - * added template line number to twig_get_attribute() - -# 2.8.1 (2019-04-16) - - * fixed EscaperNodeVisitor - * deprecated passing a 3rd, 4th, and 5th arguments to the Sandbox exception classes - * deprecated Node::setTemplateName() in favor of Node::setSourceContext() - -# 2.8.0 (2019-04-16) - - * added Traversable support for the length filter - * fixed some wrong location in error messages - * made exception creation faster - * made escaping on ternary expressions (?: and ??) more fine-grained - * added the possibility to give a nice name to string templates (template_from_string function) - * fixed the "with" behavior to always include the globals (for consistency with the "include" and "embed" tags) - * fixed "include" with "ignore missing" when an error loading occurs in the included template - * added support for a new whitespace trimming option ({%~ ~%}, {{~ ~}}, {#~ ~#}) - * added the "column" filter - -# 2.7.4 (2019-03-23) - - * fixed variadic support - * fixed CheckToStringNode implementation (broken when a function/filter is variadic) - -# 2.7.3 (2019-03-21) - - * fixed the spaceless filter so that it behaves like the spaceless tag - * fixed BC break on Environment::resolveTemplate() - * allowed Traversable objects to be used in the "with" tag - * allowed Traversable objects to be used in the "with" tag - * allowed Traversable objects to be used in the "with" argument of the "include" and "embed" tags - -# 2.7.2 (2019-03-12) - - * added TemplateWrapper::getTemplateName() - -# 2.7.1 (2019-03-12) - - * fixed class aliases - -# 2.7.0 (2019-03-12) - - * fixed sandbox security issue (under some circumstances, calling the - __toString() method on an object was possible even if not allowed by the - security policy) - * fixed batch filter clobbers array keys when fill parameter is used - * added preserveKeys support for the batch filter - * fixed "embed" support when used from "template_from_string" - * deprecated passing a Twig\Template to Twig\Environment::load()/Twig\Environment::resolveTemplate() - * added the possibility to pass a TemplateWrapper to Twig\Environment::load() - * marked Twig\Environment::getTemplateClass() as internal (implementation detail) - * improved the performance of the sandbox - * deprecated the spaceless tag - * added a spaceless filter - * added max value to the "random" function - * deprecated Twig\Extension\InitRuntimeInterface - * deprecated Twig\Loader\ExistsLoaderInterface - * deprecated PSR-0 classes in favor of namespaced ones - * made namespace classes the default classes (PSR-0 ones are aliases now) - * added Twig\Loader\ChainLoader::getLoaders() - * removed duplicated directory separator in FilesystemLoader - * deprecated the "base_template_class" option on Twig\Environment - * deprecated the Twig\Environment::getBaseTemplateClass() and - Twig\Environment::setBaseTemplateClass() methods - * changed internal code to use the namespaced classes as much as possible - * deprecated Twig_Parser::isReservedMacroName() - -# 2.6.2 (2019-01-14) - - * fixed regression (key exists check for non ArrayObject objects) - -# 2.6.1 (2019-01-14) - - * fixed ArrayObject access with a null value - * fixed embedded templates starting with a BOM - * fixed using a Twig_TemplateWrapper instance as an argument to extends - * fixed error location when calling an undefined block - * deprecated passing a string as a source on Twig_Error - * switched generated code to use the PHP short array notation - * fixed float representation in compiled templates - * added a second argument to the join filter (last separator configuration) - -# 2.6.0 (2018-12-16) - - * made sure twig_include returns a string - * fixed multi-byte UFT-8 in escape('html_attr') - * added the "deprecated" tag - * added support for dynamically named tests - * fixed GlobalsInterface extended class - * fixed filesystem loader throwing an exception instead of returning false - -# 2.5.0 (2018-07-13) - - * deprecated using the spaceless tag at the root level of a child template (noop anyway) - * deprecated the possibility to define a block in a non-capturing block in a child template - * added the Symfony ctype polyfill as a dependency - * fixed reporting the proper location for errors compiled in templates - * fixed the error handling for the optimized extension-based function calls - * ensured that syntax errors are triggered with the right line - * "js" filter now produces valid JSON - -# 2.4.8 (2018-04-02) - - * fixed a regression when using the "default" filter or the "defined" test on non-existing arrays - -# 2.4.7 (2018-03-20) - - * optimized runtime performance - * optimized parser performance by inlining the constant values - * fixed block names unicity - * fixed counting children of SimpleXMLElement objects - * added missing else clause to avoid infinite loops - * fixed .. (range operator) in sandbox policy - -# 2.4.6 (2018-03-03) - - * fixed a regression in the way the profiler is registered in templates - -# 2.4.5 (2018-03-02) - - * optimized the performance of calling an extension method at runtime - * optimized the performance of the dot operator for array and method calls - * added an exception when using "===" instead of "same as" - * fixed possible array to string conversion concealing actual error - * made variable names deterministic in compiled templates - * fixed length filter when passing an instance of IteratorAggregate - * fixed Environment::resolveTemplate to accept instances of TemplateWrapper - -# 2.4.4 (2017-09-27) - - * added Twig_Profiler_Profile::reset() - * fixed use TokenParser to return an empty Node - * added RuntimeExtensionInterface - * added circular reference detection when loading templates - * added support for runtime loaders in IntegrationTestCase - * fixed deprecation when using Twig_Profiler_Dumper_Html - * removed @final from Twig_Profiler_Dumper_Text - -# 2.4.3 (2017-06-07) - - * fixed namespaces introduction - -# 2.4.2 (2017-06-05) - - * fixed namespaces introduction - -# 2.4.1 (2017-06-05) - - * fixed namespaces introduction - -# 2.4.0 (2017-06-05) - - * added support for PHPUnit 6 when testing extensions - * fixed PHP 7.2 compatibility - * fixed template name generation in Twig_Environment::createTemplate() - * removed final tag on Twig_TokenParser_Include - * dropped HHVM support - * added namespaced aliases for all (non-deprecated) classes and interfaces - * marked Twig_Filter, Twig_Function, Twig_Test, Twig_Node_Module and Twig_Profiler_Profile as final via the @final annotation - -# 2.3.2 (2017-04-20) - - * fixed edge case in the method cache for Twig attributes - -# 2.3.1 (2017-04-18) - - * fixed the empty() test - -# 2.3.0 (2017-03-22) - - * fixed a race condition handling when writing cache files - * "length" filter now returns string length when applied to an object that does - not implement \Countable but provides __toString() - * "empty" test will now consider the return value of the __toString() method for - objects implement __toString() but not \Countable - * fixed JS escaping for unicode characters with higher code points - * added error message when calling `parent()` in a block that doesn't exist in the parent template - -# 2.2.0 (2017-02-26) - - * added a PSR-11 compatible runtime loader - * added `side` argument to `trim` to allow left or right trimming only. - -# 2.1.0 (2017-01-11) - - * fixed twig_get_attribute() - * added Twig_NodeCaptureInterface for nodes that capture all output - -# 2.0.0 (2017-01-05) - - * removed the C extension - * moved Twig_Environment::getAttribute() to twig_get_attribute() - * removed Twig_Environment::getLexer(), Twig_Environment::getParser(), Twig_Environment::getCompiler() - * removed Twig_Compiler::getFilename() - * added hasser support in Twig_Template::getAttribute() - * sped up the json_encode filter - * removed reserved macro names; all names can be used as macro - * removed Twig_Template::getEnvironment() - * changed _self variable to return the current template name - * made the loader a required argument of Twig_Environment constructor - * removed Twig_Environment::clearTemplateCache() - * removed Twig_Autoloader (use Composer instead) - * removed `true` as an equivalent to `html` for the auto-escaping strategy - * removed pre-1.8 autoescape tag syntax - * dropped support for PHP 5.x - * removed the ability to register a global variable after the runtime or the extensions have been initialized - * improved the performance of the filesystem loader - * removed features that were deprecated in 1.x - -# 1.44.3 (2021-XX-XX) - - * n/a - -# 1.44.2 (2021-01-05) - - * Fix "odd" not working for negative numbers - -# 1.44.1 (2020-10-27) - - * Fix "include(template_from_string())" - -# 1.44.0 (2020-10-21) - - * Remove implicit dependency on ext/iconv in JS escaper - * Fix sandbox support when using "include(template_from_string())" - * Make round brackets optional for one argument tests like "same as" or "divisible by" - * Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a } - * Fix filter(), map(), and reduce() to throw a RuntimeError instead of a PHP TypeError - * Drop PHP 7.1 support - -# 1.43.1 (2020-08-05) - - * Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag - * Fix a regression when not using a space before an operator - * Restrict callables to closures in filters - * Allow trailing commas in argument lists (in calls as well as definitions) - -# 1.43.0 (2020-07-05) - - * Throw exception in case non-Traversable data is passed to "filter" - * Fix context optimization on PHP 7.4 - * Fix PHP 8 compatibility - * Drop PHP 5.5 5.6, and 7.0 support - * Fix ambiguous syntax parsing - * In sandbox, the `filter`, `map` and `reduce` filters require Closures in `arrow` parameter - -# 1.42.5 (2020-02-11) - - * Fix implementation of case-insensitivity for method names - -# 1.42.4 (2019-11-11) - - * optimized "block('foo') ?? 'bar" - * added supported for exponential numbers - -# 1.42.3 (2019-08-24) - - * fixed the "split" filter when the delimiter is "0" - * fixed the "empty" test on Traversable instances - * fixed cache when opcache is installed but disabled - * fixed PHP 7.4 compatibility - * bumped the minimal PHP version to 5.5 - -# 1.42.2 (2019-06-18) - - * Display partial output (PHP buffer) when an error occurs in debug mode - -# 1.42.1 (2019-06-04) - - * added support for "Twig\Markup" instances in the "in" test (again) - * allowed string operators as variables names in assignments - -# 1.42.0 (2019-05-31) - - * fixed the "filter" filter when the argument is \Traversable but does not implement \Iterator (\SimpleXmlElement for instance) - * fixed a PHP fatal error when calling a macro imported in a block in a nested block - * fixed a PHP fatal error when calling a macro imported in the template in another macro - * fixed wrong error message on "import" and "from" - -# 1.41.0 (2019-05-14) - - * fixed support for PHP 7.4 - * added "filter", "map", and "reduce" filters (and support for arrow functions) - * fixed partial output leak when a PHP fatal error occurs - * optimized context access on PHP 7.4 - -# 1.40.1 (2019-04-29) - -# fixed regression in NodeTraverser - -# 1.40.0 (2019-04-28) - - * allowed Twig\NodeVisitor\NodeVisitorInterface::leaveNode() to return "null" instead of "false" (same meaning) - * added the "apply" tag as a replacement for the "filter" tag - * allowed Twig\Loader\FilesystemLoader::findTemplate() to return "null" instead of "false" (same meaning) - * added support for "Twig\Markup" instances in the "in" test - * fixed Lexer when using custom options containing the # char - * fixed "import" when macros are stored in a template string - -# 1.39.1 (2019-04-16) - - * fixed EscaperNodeVisitor - -# 1.39.0 (2019-04-16) - - * added Traversable support for the length filter - * fixed some wrong location in error messages - * made exception creation faster - * made escaping on ternary expressions (?: and ??) more fine-grained - * added the possibility to give a nice name to string templates (template_from_string function) - * fixed the "with" behavior to always include the globals (for consistency with the "include" and "embed" tags) - * fixed "include" with "ignore missing" when an error loading occurs in the included template - * added support for a new whitespace trimming option ({%~ ~%}, {{~ ~}}, {#~ ~#}) - -# 1.38.4 (2019-03-23) - - * fixed CheckToStringNode implementation (broken when a function/filter is variadic) - -# 1.38.3 (2019-03-21) - - * fixed the spaceless filter so that it behaves like the spaceless tag - * fixed BC break on Environment::resolveTemplate() - * fixed the bundled Autoloader to also load namespaced classes - * allowed Traversable objects to be used in the "with" tag - * allowed Traversable objects to be used in the "with" argument of the "include" and "embed" tags - -# 1.38.2 (2019-03-12) - - * added TemplateWrapper::getTemplateName() - -# 1.38.1 (2019-03-12) - - * fixed class aliases - -# 1.38.0 (2019-03-12) - - * fixed sandbox security issue (under some circumstances, calling the - __toString() method on an object was possible even if not allowed by the - security policy) - * fixed batch filter clobbers array keys when fill parameter is used - * added preserveKeys support for the batch filter - * fixed "embed" support when used from "template_from_string" - * added the possibility to pass a TemplateWrapper to Twig\Environment::load() - * improved the performance of the sandbox - * added a spaceless filter - * added max value to the "random" function - * made namespace classes the default classes (PSR-0 ones are aliases now) - * removed duplicated directory separator in FilesystemLoader - * added Twig\Loader\ChainLoader::getLoaders() - * changed internal code to use the namespaced classes as much as possible - -# 1.37.1 (2019-01-14) - - * fixed regression (key exists check for non ArrayObject objects) - * fixed logic in TemplateWrapper - -# 1.37.0 (2019-01-14) - - * fixed ArrayObject access with a null value - * fixed embedded templates starting with a BOM - * fixed using a Twig_TemplateWrapper instance as an argument to extends - * switched generated code to use the PHP short array notation - * dropped PHP 5.3 support - * fixed float representation in compiled templates - * added a second argument to the join filter (last separator configuration) - -# 1.36.0 (2018-12-16) - - * made sure twig_include returns a string - * fixed multi-byte UFT-8 in escape('html_attr') - * added the "deprecated" tag - * added support for dynamically named tests - * fixed GlobalsInterface extended class - * fixed filesystem loader throwing an exception instead of returning false - -# 1.35.4 (2018-07-13) - - * ensured that syntax errors are triggered with the right line - * added the Symfony ctype polyfill as a dependency - * "js" filter now produces valid JSON - -# 1.35.3 (2018-03-20) - - * fixed block names unicity - * fixed counting children of SimpleXMLElement objects - * added missing else clause to avoid infinite loops - * fixed .. (range operator) in sandbox policy - -# 1.35.2 (2018-03-03) - - * fixed a regression in the way the profiler is registered in templates - -# 1.35.1 (2018-03-02) - - * added an exception when using "===" instead of "same as" - * fixed possible array to string conversion concealing actual error - * made variable names deterministic in compiled templates - * fixed length filter when passing an instance of IteratorAggregate - * fixed Environment::resolveTemplate to accept instances of TemplateWrapper - -# 1.35.0 (2017-09-27) - - * added Twig_Profiler_Profile::reset() - * fixed use TokenParser to return an empty Node - * added RuntimeExtensionInterface - * added circular reference detection when loading templates - -# 1.34.4 (2017-07-04) - - * added support for runtime loaders in IntegrationTestCase - * fixed deprecation when using Twig_Profiler_Dumper_Html - -# 1.34.3 (2017-06-07) - - * fixed namespaces introduction - -# 1.34.2 (2017-06-05) - - * fixed namespaces introduction - -# 1.34.1 (2017-06-05) - - * fixed namespaces introduction - -# 1.34.0 (2017-06-05) - - * added support for PHPUnit 6 when testing extensions - * fixed PHP 7.2 compatibility - * fixed template name generation in Twig_Environment::createTemplate() - * removed final tag on Twig_TokenParser_Include - * added namespaced aliases for all (non-deprecated) classes and interfaces - * dropped HHVM support - * dropped PHP 5.2 support - -# 1.33.2 (2017-04-20) - - * fixed edge case in the method cache for Twig attributes - -# 1.33.1 (2017-04-18) - - * fixed the empty() test - -# 1.33.0 (2017-03-22) - - * fixed a race condition handling when writing cache files - * "length" filter now returns string length when applied to an object that does - not implement \Countable but provides __toString() - * "empty" test will now consider the return value of the __toString() method for - objects implement __toString() but not \Countable - * fixed JS escaping for unicode characters with higher code points - -# 1.32.0 (2017-02-26) - - * fixed deprecation notice in Twig_Util_DeprecationCollector - * added a PSR-11 compatible runtime loader - * added `side` argument to `trim` to allow left or right trimming only. - -# 1.31.0 (2017-01-11) - - * added Twig_NodeCaptureInterface for nodes that capture all output - * fixed marking the environment as initialized too early - * fixed C89 compat for the C extension - * turned fatal error into exception when a previously generated cache is corrupted - * fixed offline cache warm-ups for embedded templates - -# 1.30.0 (2016-12-23) - - * added Twig_FactoryRuntimeLoader - * deprecated function/test/filter/tag overriding - * deprecated the "disable_c_ext" attribute on Twig_Node_Expression_GetAttr - -# 1.29.0 (2016-12-13) - - * fixed sandbox being left enabled if an exception is thrown while rendering - * marked some classes as being final (via @final) - * made Twig_Error report real source path when possible - * added support for {{ _self }} to provide an upgrade path from 1.x to 2.0 (replaces {{ _self.templateName }}) - * deprecated silent display of undefined blocks - * deprecated support for mbstring.func_overload != 0 - -# 1.28.2 (2016-11-23) - - * fixed precedence between getFoo() and isFoo() in Twig_Template::getAttribute() - * improved a deprecation message - -# 1.28.1 (2016-11-18) - - * fixed block() function when used with a template argument - -# 1.28.0 (2016-11-17) - - * added support for the PHP 7 null coalescing operator for the ?? Twig implementation - * exposed a way to access template data and methods in a portable way - * changed context access to use the PHP 7 null coalescing operator when available - * added the "with" tag - * added support for a custom template on the block() function - * added "is defined" support for block() and constant() - * optimized the way attributes are fetched - -# 1.27.0 (2016-10-25) - - * deprecated Twig_Parser::getEnvironment() - * deprecated Twig_Parser::addHandler() and Twig_Parser::addNodeVisitor() - * deprecated Twig_Compiler::addIndentation() - * fixed regression when registering two extensions having the same class name - * deprecated Twig_LoaderInterface::getSource() (implement Twig_SourceContextLoaderInterface instead) - * fixed the filesystem loader with relative paths - * deprecated Twig_Node::getLine() in favor of Twig_Node::getTemplateLine() - * deprecated Twig_Template::getSource() in favor of Twig_Template::getSourceContext() - * deprecated Twig_Node::getFilename() in favor of Twig_Node::getTemplateName() - * deprecated the "filename" escaping strategy (use "name" instead) - * added Twig_Source to hold information about the original template - * deprecated Twig_Error::getTemplateFile() and Twig_Error::setTemplateFile() in favor of Twig_Error::getTemplateName() and Twig_Error::setTemplateName() - * deprecated Parser::getFilename() - * fixed template paths when a template name contains a protocol like vfs:// - * improved debugging with Twig_Sandbox_SecurityError exceptions for disallowed methods and properties - -# 1.26.1 (2016-10-05) - - * removed template source code from generated template classes when debug is disabled - * fixed default implementation of Twig_Template::getDebugInfo() for better BC - * fixed regression on static calls for functions/filters/tests - -# 1.26.0 (2016-10-02) - - * added template cache invalidation based on more environment options - * added a missing deprecation notice - * fixed template paths when a template is stored in a PHAR file - * allowed filters/functions/tests implementation to use a different class than the extension they belong to - * deprecated Twig_ExtensionInterface::getName() - -# 1.25.0 (2016-09-21) - - * changed the way we store template source in template classes - * removed usage of realpath in cache keys - * fixed Twig cache sharing when used with different versions of PHP - * removed embed parent workaround for simple use cases - * deprecated the ability to store non Node instances in Node::$nodes - * deprecated Twig_Environment::getLexer(), Twig_Environment::getParser(), Twig_Environment::getCompiler() - * deprecated Twig_Compiler::getFilename() - -# 1.24.2 (2016-09-01) - - * fixed static callables - * fixed a potential PHP warning when loading the cache - * fixed a case where the autoescaping does not work as expected - -# 1.24.1 (2016-05-30) - - * fixed reserved keywords (forbids true, false, null and none keywords for variables names) - * fixed support for PHP7 (Throwable support) - * marked the following methods as being internals on Twig_Environment: - getFunctions(), getFilters(), getTests(), getFunction(), getFilter(), getTest(), - getTokenParsers(), getTags(), getNodeVisitors(), getUnaryOperators(), getBinaryOperators(), - getFunctions(), getFilters(), getGlobals(), initGlobals(), initExtensions(), and initExtension() - -# 1.24.0 (2016-01-25) - - * adding support for the ?? operator - * fixed the defined test when used on a constant, a map, or a sequence - * undeprecated _self (should only be used to get the template name, not the template instance) - * fixed parsing on PHP7 - -# 1.23.3 (2016-01-11) - - * fixed typo - -# 1.23.2 (2015-01-11) - - * added versions in deprecated messages - * made file cache tolerant for trailing (back)slashes on directory configuration - * deprecated unused Twig_Node_Expression_ExtensionReference class - -# 1.23.1 (2015-11-05) - - * fixed some exception messages which triggered PHP warnings - * fixed BC on Twig_Test_NodeTestCase - -# 1.23.0 (2015-10-29) - - * deprecated the possibility to override an extension by registering another one with the same name - * deprecated Twig_ExtensionInterface::getGlobals() (added Twig_Extension_GlobalsInterface for BC) - * deprecated Twig_ExtensionInterface::initRuntime() (added Twig_Extension_InitRuntimeInterface for BC) - * deprecated Twig_Environment::computeAlternatives() - -# 1.22.3 (2015-10-13) - - * fixed regression when using null as a cache strategy - * improved performance when checking template freshness - * fixed warnings when loaded templates do not exist - * fixed template class name generation to prevent possible collisions - * fixed logic for custom escapers to call them even on integers and null values - * changed template cache names to take into account the Twig C extension - -# 1.22.2 (2015-09-22) - - * fixed a race condition in template loading - -# 1.22.1 (2015-09-15) - - * fixed regression in template_from_string - -# 1.22.0 (2015-09-13) - - * made Twig_Test_IntegrationTestCase more flexible - * added an option to force PHP bytecode invalidation when writing a compiled template into the cache - * fixed the profiler duration for the root node - * changed template cache names to take into account enabled extensions - * deprecated Twig_Environment::clearCacheFiles(), Twig_Environment::getCacheFilename(), - Twig_Environment::writeCacheFile(), and Twig_Environment::getTemplateClassPrefix() - * added a way to override the filesystem template cache system - * added a way to get the original template source from Twig_Template - -# 1.21.2 (2015-09-09) - - * fixed variable names for the deprecation triggering code - * fixed escaping strategy detection based on filename - * added Traversable support for replace, merge, and sort - * deprecated support for character by character replacement for the "replace" filter - -# 1.21.1 (2015-08-26) - - * fixed regression when using the deprecated Twig_Test_* classes - -# 1.21.0 (2015-08-24) - - * added deprecation notices for deprecated features - * added a deprecation "framework" for filters/functions/tests and test fixtures - -# 1.20.0 (2015-08-12) - - * forbid access to the Twig environment from templates and internal parts of Twig_Template - * fixed limited RCEs when in sandbox mode - * deprecated Twig_Template::getEnvironment() - * deprecated the _self variable for usage outside of the from and import tags - * added Twig_BaseNodeVisitor to ease the compatibility of node visitors - between 1.x and 2.x - -# 1.19.0 (2015-07-31) - - * fixed wrong error message when including an undefined template in a child template - * added support for variadic filters, functions, and tests - * added support for extra positional arguments in macros - * added ignore_missing flag to the source function - * fixed batch filter with zero items - * deprecated Twig_Environment::clearTemplateCache() - * fixed sandbox disabling when using the include function - -# 1.18.2 (2015-06-06) - - * fixed template/line guessing in exceptions for nested templates - * optimized the number of inodes and the size of realpath cache when using the cache - -# 1.18.1 (2015-04-19) - - * fixed memory leaks in the C extension - * deprecated Twig_Loader_String - * fixed the slice filter when used with a SimpleXMLElement object - * fixed filesystem loader when trying to load non-files (like directories) - -# 1.18.0 (2015-01-25) - - * fixed some error messages where the line was wrong (unknown variables or argument names) - * added a new way to customize the main Module node (via empty nodes) - * added Twig_Environment::createTemplate() to create a template from a string - * added a profiler - * fixed filesystem loader cache when different file paths are used for the same template - -# 1.17.0 (2015-01-14) - - * added a 'filename' autoescaping strategy, which dynamically chooses the - autoescaping strategy for a template based on template file extension. - -# 1.16.3 (2014-12-25) - - * fixed regression for dynamic parent templates - * fixed cache management with statcache - * fixed a regression in the slice filter - -# 1.16.2 (2014-10-17) - - * fixed timezone on dates as strings - * fixed 2-words test names when a custom node class is not used - * fixed macros when using an argument named like a PHP super global (like GET or POST) - * fixed date_modify when working with DateTimeImmutable - * optimized for loops - * fixed multi-byte characters handling in the split filter - * fixed a regression in the in operator - * fixed a regression in the slice filter - -# 1.16.1 (2014-10-10) - - * improved error reporting in a sandboxed template - * fixed missing error file/line information under certain circumstances - * fixed wrong error line number in some error messages - * fixed the in operator to use strict comparisons - * sped up the slice filter - * fixed for mb function overload mb_substr acting different - * fixed the attribute() function when passing a variable for the arguments - -# 1.16.0 (2014-07-05) - - * changed url_encode to always encode according to RFC 3986 - * fixed inheritance in a 'use'-hierarchy - * removed the __toString policy check when the sandbox is disabled - * fixed recursively calling blocks in templates with inheritance - -# 1.15.1 (2014-02-13) - - * fixed the conversion of the special '0000-00-00 00:00' date - * added an error message when trying to import an undefined block from a trait - * fixed a C extension crash when accessing defined but uninitialized property. - -# 1.15.0 (2013-12-06) - - * made ignoreStrictCheck in Template::getAttribute() works with __call() methods throwing BadMethodCallException - * added min and max functions - * added the round filter - * fixed a bug that prevented the optimizers to be enabled/disabled selectively - * fixed first and last filters for UTF-8 strings - * added a source function to include the content of a template without rendering it - * fixed the C extension sandbox behavior when get or set is prepend to method name - -# 1.14.2 (2013-10-30) - - * fixed error filename/line when an error occurs in an included file - * allowed operators that contain whitespaces to have more than one whitespace - * allowed tests to be made of 1 or 2 words (like "same as" or "divisible by") - -# 1.14.1 (2013-10-15) - - * made it possible to use named operators as variables - * fixed the possibility to have a variable named 'matches' - * added support for PHP 5.5 DateTimeInterface - -# 1.14.0 (2013-10-03) - - * fixed usage of the html_attr escaping strategy to avoid double-escaping with the html strategy - * added new operators: ends with, starts with, and matches - * fixed some compatibility issues with HHVM - * added a way to add custom escaping strategies - * fixed the C extension compilation on Windows - * fixed the batch filter when using a fill argument with an exact match of elements to batch - * fixed the filesystem loader cache when a template name exists in several namespaces - * fixed template_from_string when the template includes or extends other ones - * fixed a crash of the C extension on an edge case - -# 1.13.2 (2013-08-03) - - * fixed the error line number for an error occurs in and embedded template - * fixed crashes of the C extension on some edge cases - -# 1.13.1 (2013-06-06) - - * added the possibility to ignore the filesystem constructor argument in Twig_Loader_Filesystem - * fixed Twig_Loader_Chain::exists() for a loader which implements Twig_ExistsLoaderInterface - * adjusted backtrace call to reduce memory usage when an error occurs - * added support for object instances as the second argument of the constant test - * fixed the include function when used in an assignment - -# 1.13.0 (2013-05-10) - - * fixed getting a numeric-like item on a variable ('09' for instance) - * fixed getting a boolean or float key on an array, so it is consistent with PHP's array access: - `{{ array[false] }}` behaves the same as `echo $array[false];` (equals `$array[0]`) - * made the escape filter 20% faster for happy path (escaping string for html with UTF-8) - * changed ☃ to § in tests - * enforced usage of named arguments after positional ones - -# 1.12.3 (2013-04-08) - - * fixed a security issue in the filesystem loader where it was possible to include a template one - level above the configured path - * fixed fatal error that should be an exception when adding a filter/function/test too late - * added a batch filter - * added support for encoding an array as query string in the url_encode filter - -# 1.12.2 (2013-02-09) - - * fixed the timezone used by the date filter and function when the given date contains a timezone (like 2010-01-28T15:00:00+02:00) - * fixed globals when getGlobals is called early on - * added the first and last filter - -# 1.12.1 (2013-01-15) - - * added support for object instances as the second argument of the constant function - * relaxed globals management to avoid a BC break - * added support for {{ some_string[:2] }} - -# 1.12.0 (2013-01-08) - - * added verbatim as an alias for the raw tag to avoid confusion with the raw filter - * fixed registration of tests and functions as anonymous functions - * fixed globals management - -# 1.12.0-RC1 (2012-12-29) - - * added an include function (does the same as the include tag but in a more flexible way) - * added the ability to use any PHP callable to define filters, functions, and tests - * added a syntax error when using a loop variable that is not defined - * added the ability to set default values for macro arguments - * added support for named arguments for filters, tests, and functions - * moved filters/functions/tests syntax errors to the parser - * added support for extended ternary operator syntaxes - -# 1.11.1 (2012-11-11) - - * fixed debug info line numbering (was off by 2) - * fixed escaping when calling a macro inside another one (regression introduced in 1.9.1) - * optimized variable access on PHP 5.4 - * fixed a crash of the C extension when an exception was thrown from a macro called without being imported (using _self.XXX) - -# 1.11.0 (2012-11-07) - - * fixed macro compilation when a variable name is a PHP reserved keyword - * changed the date filter behavior to always apply the default timezone, except if false is passed as the timezone - * fixed bitwise operator precedences - * added the template_from_string function - * fixed default timezone usage for the date function - * optimized the way Twig exceptions are managed (to make them faster) - * added Twig_ExistsLoaderInterface (implementing this interface in your loader make the chain loader much faster) - -# 1.10.3 (2012-10-19) - - * fixed wrong template location in some error messages - * reverted a BC break introduced in 1.10.2 - * added a split filter - -# 1.10.2 (2012-10-15) - - * fixed macro calls on PHP 5.4 - -# 1.10.1 (2012-10-15) - - * made a speed optimization to macro calls when imported via the "import" tag - * fixed C extension compilation on Windows - * fixed a segfault in the C extension when using DateTime objects - -# 1.10.0 (2012-09-28) - - * extracted functional tests framework to make it reusable for third-party extensions - * added namespaced templates support in Twig_Loader_Filesystem - * added Twig_Loader_Filesystem::prependPath() - * fixed an error when a token parser pass a closure as a test to the subparse() method - -# 1.9.2 (2012-08-25) - - * fixed the in operator for objects that contain circular references - * fixed the C extension when accessing a public property of an object implementing the \ArrayAccess interface - -# 1.9.1 (2012-07-22) - - * optimized macro calls when auto-escaping is on - * fixed wrong parent class for Twig_Function_Node - * made Twig_Loader_Chain more explicit about problems - -# 1.9.0 (2012-07-13) - - * made the parsing independent of the template loaders - * fixed exception trace when an error occurs when rendering a child template - * added escaping strategies for CSS, URL, and HTML attributes - * fixed nested embed tag calls - * added the date_modify filter - -# 1.8.3 (2012-06-17) - - * fixed paths in the filesystem loader when passing a path that ends with a slash or a backslash - * fixed escaping when a project defines a function named html or js - * fixed chmod mode to apply the umask correctly - -# 1.8.2 (2012-05-30) - - * added the abs filter - * fixed a regression when using a number in template attributes - * fixed compiler when mbstring.func_overload is set to 2 - * fixed DateTimeZone support in date filter - -# 1.8.1 (2012-05-17) - - * fixed a regression when dealing with SimpleXMLElement instances in templates - * fixed "is_safe" value for the "dump" function when "html_errors" is not defined in php.ini - * switched to use mbstring whenever possible instead of iconv (you might need to update your encoding as mbstring and iconv encoding names sometimes differ) - -# 1.8.0 (2012-05-08) - - * enforced interface when adding tests, filters, functions, and node visitors from extensions - * fixed a side-effect of the date filter where the timezone might be changed - * simplified usage of the autoescape tag; the only (optional) argument is now the escaping strategy or false (with a BC layer) - * added a way to dynamically change the auto-escaping strategy according to the template "filename" - * changed the autoescape option to also accept a supported escaping strategy (for BC, true is equivalent to html) - * added an embed tag - -# 1.7.0 (2012-04-24) - - * fixed a PHP warning when using CIFS - * fixed template line number in some exceptions - * added an iterable test - * added an error when defining two blocks with the same name in a template - * added the preserves_safety option for filters - * fixed a PHP notice when trying to access a key on a non-object/array variable - * enhanced error reporting when the template file is an instance of SplFileInfo - * added Twig_Environment::mergeGlobals() - * added compilation checks to avoid misuses of the sandbox tag - * fixed filesystem loader freshness logic for high traffic websites - * fixed random function when charset is null - -# 1.6.5 (2012-04-11) - - * fixed a regression when a template only extends another one without defining any blocks - -# 1.6.4 (2012-04-02) - - * fixed PHP notice in Twig_Error::guessTemplateLine() introduced in 1.6.3 - * fixed performance when compiling large files - * optimized parent template creation when the template does not use dynamic inheritance - -# 1.6.3 (2012-03-22) - - * fixed usage of Z_ADDREF_P for PHP 5.2 in the C extension - * fixed compilation of numeric values used in templates when using a locale where the decimal separator is not a dot - * made the strategy used to guess the real template file name and line number in exception messages much faster and more accurate - -# 1.6.2 (2012-03-18) - - * fixed sandbox mode when used with inheritance - * added preserveKeys support for the slice filter - * fixed the date filter when a DateTime instance is passed with a specific timezone - * added a trim filter - -# 1.6.1 (2012-02-29) - - * fixed Twig C extension - * removed the creation of Twig_Markup instances when not needed - * added a way to set the default global timezone for dates - * fixed the slice filter on strings when the length is not specified - * fixed the creation of the cache directory in case of a race condition - -# 1.6.0 (2012-02-04) - - * fixed raw blocks when used with the whitespace trim option - * made a speed optimization to macro calls when imported via the "from" tag - * fixed globals, parsers, visitors, filters, tests, and functions management in Twig_Environment when a new one or new extension is added - * fixed the attribute function when passing arguments - * added slice notation support for the [] operator (syntactic sugar for the slice operator) - * added a slice filter - * added string support for the reverse filter - * fixed the empty test and the length filter for Twig_Markup instances - * added a date function to ease date comparison - * fixed unary operators precedence - * added recursive parsing support in the parser - * added string and integer handling for the random function - -# 1.5.1 (2012-01-05) - - * fixed a regression when parsing strings - -# 1.5.0 (2012-01-04) - - * added Traversable objects support for the join filter - -# 1.5.0-RC2 (2011-12-30) - - * added a way to set the default global date interval format - * fixed the date filter for DateInterval instances (setTimezone() does not exist for them) - * refactored Twig_Template::display() to ease its extension - * added a number_format filter - -# 1.5.0-RC1 (2011-12-26) - - * removed the need to quote hash keys - * allowed hash keys to be any expression - * added a do tag - * added a flush tag - * added support for dynamically named filters and functions - * added a dump function to help debugging templates - * added a nl2br filter - * added a random function - * added a way to change the default format for the date filter - * fixed the lexer when an operator ending with a letter ends a line - * added string interpolation support - * enhanced exceptions for unknown filters, functions, tests, and tags - -# 1.4.0 (2011-12-07) - - * fixed lexer when using big numbers (> PHP_INT_MAX) - * added missing preserveKeys argument to the reverse filter - * fixed macros containing filter tag calls - -# 1.4.0-RC2 (2011-11-27) - - * removed usage of Reflection in Twig_Template::getAttribute() - * added a C extension that can optionally replace Twig_Template::getAttribute() - * added negative timestamp support to the date filter - -# 1.4.0-RC1 (2011-11-20) - - * optimized variable access when using PHP 5.4 - * changed the precedence of the .. operator to be more consistent with languages that implements such a feature like Ruby - * added an Exception to Twig_Loader_Array::isFresh() method when the template does not exist to be consistent with other loaders - * added Twig_Function_Node to allow more complex functions to have their own Node class - * added Twig_Filter_Node to allow more complex filters to have their own Node class - * added Twig_Test_Node to allow more complex tests to have their own Node class - * added a better error message when a template is empty but contain a BOM - * fixed "in" operator for empty strings - * fixed the "defined" test and the "default" filter (now works with more than one call (foo.bar.foo) and for both values of the strict_variables option) - * changed the way extensions are loaded (addFilter/addFunction/addGlobal/addTest/addNodeVisitor/addTokenParser/addExtension can now be called in any order) - * added Twig_Environment::display() - * made the escape filter smarter when the encoding is not supported by PHP - * added a convert_encoding filter - * moved all node manipulations outside the compile() Node method - * made several speed optimizations - -# 1.3.0 (2011-10-08) - -no changes - -# 1.3.0-RC1 (2011-10-04) - - * added an optimization for the parent() function - * added cache reloading when auto_reload is true and an extension has been modified - * added the possibility to force the escaping of a string already marked as safe (instance of Twig_Markup) - * allowed empty templates to be used as traits - * added traits support for the "parent" function - -# 1.2.0 (2011-09-13) - -no changes - -# 1.2.0-RC1 (2011-09-10) - - * enhanced the exception when a tag remains unclosed - * added support for empty Countable objects for the "empty" test - * fixed algorithm that determines if a template using inheritance is valid (no output between block definitions) - * added better support for encoding problems when escaping a string (available as of PHP 5.4) - * added a way to ignore a missing template when using the "include" tag ({% include "foo" ignore missing %}) - * added support for an array of templates to the "include" and "extends" tags ({% include ['foo', 'bar'] %}) - * added support for bitwise operators in expressions - * added the "attribute" function to allow getting dynamic attributes on variables - * added Twig_Loader_Chain - * added Twig_Loader_Array::setTemplate() - * added an optimization for the set tag when used to capture a large chunk of static text - * changed name regex to match PHP one "[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*" (works for blocks, tags, functions, filters, and macros) - * removed the possibility to use the "extends" tag from a block - * added "if" modifier support to "for" loops - -# 1.1.2 (2011-07-30) - - * fixed json_encode filter on PHP 5.2 - * fixed regression introduced in 1.1.1 ({{ block(foo|lower) }}) - * fixed inheritance when using conditional parents - * fixed compilation of templates when the body of a child template is not empty - * fixed output when a macro throws an exception - * fixed a parsing problem when a large chunk of text is enclosed in a comment tag - * added PHPDoc for all Token parsers and Core extension functions - -# 1.1.1 (2011-07-17) - - * added a performance optimization in the Optimizer (also helps to lower the number of nested level calls) - * made some performance improvement for some edge cases - -# 1.1.0 (2011-06-28) - - * fixed json_encode filter - -# 1.1.0-RC3 (2011-06-24) - - * fixed method case-sensitivity when using the sandbox mode - * added timezone support for the date filter - * fixed possible security problems with NUL bytes - -# 1.1.0-RC2 (2011-06-16) - - * added an exception when the template passed to "use" is not a string - * made 'a.b is defined' not throw an exception if a is not defined (in strict mode) - * added {% line \d+ %} directive - -# 1.1.0-RC1 (2011-05-28) - -Flush your cache after upgrading. - - * fixed date filter when using a timestamp - * fixed the defined test for some cases - * fixed a parsing problem when a large chunk of text is enclosed in a raw tag - * added support for horizontal reuse of template blocks (see docs for more information) - * added whitespace control modifier to all tags (see docs for more information) - * added null as an alias for none (the null test is also an alias for the none test now) - * made TRUE, FALSE, NONE equivalent to their lowercase counterparts - * wrapped all compilation and runtime exceptions with Twig_Error_Runtime and added logic to guess the template name and line - * moved display() method to Twig_Template (generated templates should now use doDisplay() instead) - -# 1.0.0 (2011-03-27) - - * fixed output when using mbstring - * fixed duplicate call of methods when using the sandbox - * made the charset configurable for the escape filter - -# 1.0.0-RC2 (2011-02-21) - - * changed the way {% set %} works when capturing (the content is now marked as safe) - * added support for macro name in the endmacro tag - * make Twig_Error compatible with PHP 5.3.0 > - * fixed an infinite loop on some Windows configurations - * fixed the "length" filter for numbers - * fixed Template::getAttribute() as properties in PHP are case sensitive - * removed coupling between Twig_Node and Twig_Template - * fixed the ternary operator precedence rule - -# 1.0.0-RC1 (2011-01-09) - -Backward incompatibilities: - - * the "items" filter, which has been deprecated for quite a long time now, has been removed - * the "range" filter has been converted to a function: 0|range(10) -> range(0, 10) - * the "constant" filter has been converted to a function: {{ some_date|date('DATE_W3C'|constant) }} -> {{ some_date|date(constant('DATE_W3C')) }} - * the "cycle" filter has been converted to a function: {{ ['odd', 'even']|cycle(i) }} -> {{ cycle(['odd', 'even'], i) }} - * the "for" tag does not support "joined by" anymore - * the "autoescape" first argument is now "true"/"false" (instead of "on"/"off") - * the "parent" tag has been replaced by a "parent" function ({{ parent() }} instead of {% parent %}) - * the "display" tag has been replaced by a "block" function ({{ block('title') }} instead of {% display title %}) - * removed the grammar and simple token parser (moved to the Twig Extensions repository) - -Changes: - - * added "needs_context" option for filters and functions (the context is then passed as a first argument) - * added global variables support - * made macros return their value instead of echoing directly (fixes calling a macro in sandbox mode) - * added the "from" tag to import macros as functions - * added support for functions (a function is just syntactic sugar for a getAttribute() call) - * made macros callable when sandbox mode is enabled - * added an exception when a macro uses a reserved name - * the "default" filter now uses the "empty" test instead of just checking for null - * added the "empty" test - -# 0.9.10 (2010-12-16) - -Backward incompatibilities: - - * The Escaper extension is enabled by default, which means that all displayed - variables are now automatically escaped. You can revert to the previous - behavior by removing the extension via $env->removeExtension('escaper') - or just set the 'autoescape' option to 'false'. - * removed the "without loop" attribute for the "for" tag (not needed anymore - as the Optimizer take care of that for most cases) - * arrays and hashes have now a different syntax - * arrays keep the same syntax with square brackets: [1, 2] - * hashes now use curly braces (["a": "b"] should now be written as {"a": "b"}) - * support for "arrays with keys" and "hashes without keys" is not supported anymore ([1, "foo": "bar"] or {"foo": "bar", 1}) - * the i18n extension is now part of the Twig Extensions repository - -Changes: - - * added the merge filter - * removed 'is_escaper' option for filters (a left over from the previous version) -- you must use 'is_safe' now instead - * fixed usage of operators as method names (like is, in, and not) - * changed the order of execution for node visitors - * fixed default() filter behavior when used with strict_variables set to on - * fixed filesystem loader compatibility with PHAR files - * enhanced error messages when an unexpected token is parsed in an expression - * fixed filename not being added to syntax error messages - * added the autoescape option to enable/disable autoescaping - * removed the newline after a comment (mimics PHP behavior) - * added a syntax error exception when parent block is used on a template that does not extend another one - * made the Escaper extension enabled by default - * fixed sandbox extension when used with auto output escaping - * fixed escaper when wrapping a Twig_Node_Print (the original class must be preserved) - * added an Optimizer extension (enabled by default; optimizes "for" loops and "raw" filters) - * added priority to node visitors - -# 0.9.9 (2010-11-28) - -Backward incompatibilities: - * the self special variable has been renamed to _self - * the odd and even filters are now tests: - {{ foo|odd }} must now be written {{ foo is odd }} - * the "safe" filter has been renamed to "raw" - * in Node classes, - sub-nodes are now accessed via getNode() (instead of property access) - attributes via getAttribute() (instead of array access) - * the urlencode filter had been renamed to url_encode - * the include tag now merges the passed variables with the current context by default - (the old behavior is still possible by adding the "only" keyword) - * moved Exceptions to Twig_Error_* (Twig_SyntaxError/Twig_RuntimeError are now Twig_Error_Syntax/Twig_Error_Runtime) - * removed support for {{ 1 < i < 3 }} (use {{ i > 1 and i < 3 }} instead) - * the "in" filter has been removed ({{ a|in(b) }} should now be written {{ a in b }}) - -Changes: - * added file and line to Twig_Error_Runtime exceptions thrown from Twig_Template - * changed trans tag to accept any variable for the plural count - * fixed sandbox mode (__toString() method check was not enforced if called implicitly from complex statements) - * added the ** (power) operator - * changed the algorithm used for parsing expressions - * added the spaceless tag - * removed trim_blocks option - * added support for is*() methods for attributes (foo.bar now looks for foo->getBar() or foo->isBar()) - * changed all exceptions to extend Twig_Error - * fixed unary expressions ({{ not(1 or 0) }}) - * fixed child templates (with an extend tag) that uses one or more imports - * added support for {{ 1 not in [2, 3] }} (more readable than the current {{ not (1 in [2, 3]) }}) - * escaping has been rewritten - * the implementation of template inheritance has been rewritten - (blocks can now be called individually and still work with inheritance) - * fixed error handling for if tag when a syntax error occurs within a subparse process - * added a way to implement custom logic for resolving token parsers given a tag name - * fixed js escaper to be stricter (now uses a whilelist-based js escaper) - * added the following filers: "constant", "trans", "replace", "json_encode" - * added a "constant" test - * fixed objects with __toString() not being autoescaped - * fixed subscript expressions when calling __call() (methods now keep the case) - * added "test" feature (accessible via the "is" operator) - * removed the debug tag (should be done in an extension) - * fixed trans tag when no vars are used in plural form - * fixed race condition when writing template cache - * added the special _charset variable to reference the current charset - * added the special _context variable to reference the current context - * renamed self to _self (to avoid conflict) - * fixed Twig_Template::getAttribute() for protected properties - -# 0.9.8 (2010-06-28) - -Backward incompatibilities: - * the trans tag plural count is now attached to the plural tag: - old: `{% trans count %}...{% plural %}...{% endtrans %}` - new: `{% trans %}...{% plural count %}...{% endtrans %}` - - * added a way to translate strings coming from a variable ({% trans var %}) - * fixed trans tag when used with the Escaper extension - * fixed default cache umask - * removed Twig_Template instances from the debug tag output - * fixed objects with __isset() defined - * fixed set tag when used with a capture - * fixed type hinting for Twig_Environment::addFilter() method - -# 0.9.7 (2010-06-12) - -Backward incompatibilities: - * changed 'as' to '=' for the set tag ({% set title as "Title" %} must now be {% set title = "Title" %}) - * removed the sandboxed attribute of the include tag (use the new sandbox tag instead) - * refactored the Node system (if you have custom nodes, you will have to update them to use the new API) - - * added self as a special variable that refers to the current template (useful for importing macros from the current template) - * added Twig_Template instance support to the include tag - * added support for dynamic and conditional inheritance ({% extends some_var %} and {% extends standalone ? "minimum" : "base" %}) - * added a grammar sub-framework to ease the creation of custom tags - * fixed the for tag for large arrays (some loop variables are now only available for arrays and objects that implement the Countable interface) - * removed the Twig_Resource::resolveMissingFilter() method - * fixed the filter tag which did not apply filtering to included files - * added a bunch of unit tests - * added a bunch of phpdoc - * added a sandbox tag in the sandbox extension - * changed the date filter to support any date format supported by DateTime - * added strict_variable setting to throw an exception when an invalid variable is used in a template (disabled by default) - * added the lexer, parser, and compiler as arguments to the Twig_Environment constructor - * changed the cache option to only accepts an explicit path to a cache directory or false - * added a way to add token parsers, filters, and visitors without creating an extension - * added three interfaces: Twig_NodeInterface, Twig_TokenParserInterface, and Twig_FilterInterface - * changed the generated code to match the new coding standards - * fixed sandbox mode (__toString() method check was not enforced if called implicitly from a simple statement like {{ article }}) - * added an exception when a child template has a non-empty body (as it is always ignored when rendering) - -# 0.9.6 (2010-05-12) - - * fixed variables defined outside a loop and for which the value changes in a for loop - * fixed the test suite for PHP 5.2 and older versions of PHPUnit - * added support for __call() in expression resolution - * fixed node visiting for macros (macros are now visited by visitors as any other node) - * fixed nested block definitions with a parent call (rarely useful but nonetheless supported now) - * added the cycle filter - * fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII - * added a long-syntax for the set tag ({% set foo %}...{% endset %}) - * unit tests are now powered by PHPUnit - * added support for gettext via the `i18n` extension - * fixed twig_capitalize_string_filter() and fixed twig_length_filter() when used with UTF-8 values - * added a more useful exception if an if tag is not closed properly - * added support for escaping strategy in the autoescape tag - * fixed lexer when a template has a big chunk of text between/in a block - -# 0.9.5 (2010-01-20) - -As for any new release, don't forget to remove all cached templates after -upgrading. - -If you have defined custom filters, you MUST upgrade them for this release. To -upgrade, replace "array" with "new Twig_Filter_Function", and replace the -environment constant by the "needs_environment" option: - - // before - 'even' => array('twig_is_even_filter', false), - 'escape' => array('twig_escape_filter', true), - - // after - 'even' => new Twig_Filter_Function('twig_is_even_filter'), - 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)), - -If you have created NodeTransformer classes, you will need to upgrade them to -the new interface (please note that the interface is not yet considered -stable). - - * fixed list nodes that did not extend the Twig_NodeListInterface - * added the "without loop" option to the for tag (it disables the generation of the loop variable) - * refactored node transformers to node visitors - * fixed automatic-escaping for blocks - * added a way to specify variables to pass to an included template - * changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules) - * improved the filter system to allow object methods to be used as filters - * changed the Array and String loaders to actually make use of the cache mechanism - * included the default filter function definitions in the extension class files directly (Core, Escaper) - * added the // operator (like the floor() PHP function) - * added the .. operator (as a syntactic sugar for the range filter when the step is 1) - * added the in operator (as a syntactic sugar for the in filter) - * added the following filters in the Core extension: in, range - * added support for arrays (same behavior as in PHP, a mix between lists and dictionaries, arrays and hashes) - * enhanced some error messages to provide better feedback in case of parsing errors - -# 0.9.4 (2009-12-02) - -If you have custom loaders, you MUST upgrade them for this release: The -Twig_Loader base class has been removed, and the Twig_LoaderInterface has also -been changed (see the source code for more information or the documentation). - - * added support for DateTime instances for the date filter - * fixed loop.last when the array only has one item - * made it possible to insert newlines in tag and variable blocks - * fixed a bug when a literal '\n' were present in a template text - * fixed bug when the filename of a template contains */ - * refactored loaders - -# 0.9.3 (2009-11-11) - -This release is NOT backward compatible with the previous releases. - - The loaders do not take the cache and autoReload arguments anymore. Instead, - the Twig_Environment class has two new options: cache and auto_reload. - Upgrading your code means changing this kind of code: - - $loader = new Twig_Loader_Filesystem('/path/to/templates', '/path/to/compilation_cache', true); - $twig = new Twig_Environment($loader); - - to something like this: - - $loader = new Twig_Loader_Filesystem('/path/to/templates'); - $twig = new Twig_Environment($loader, array( - 'cache' => '/path/to/compilation_cache', - 'auto_reload' => true, - )); - - * deprecated the "items" filter as it is not needed anymore - * made cache and auto_reload options of Twig_Environment instead of arguments of Twig_Loader - * optimized template loading speed - * removed output when an error occurs in a template and render() is used - * made major speed improvements for loops (up to 300% on even the smallest loops) - * added properties as part of the sandbox mode - * added public properties support (obj.item can now be the item property on the obj object) - * extended set tag to support expression as value ({% set foo as 'foo' ~ 'bar' %} ) - * fixed bug when \ was used in HTML - -# 0.9.2 (2009-10-29) - - * made some speed optimizations - * changed the cache extension to .php - * added a js escaping strategy - * added support for short block tag - * changed the filter tag to allow chained filters - * made lexer more flexible as you can now change the default delimiters - * added set tag - * changed default directory permission when cache dir does not exist (more secure) - * added macro support - * changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance - * made Twig_Autoloader::autoload() a static method - * avoid writing template file if an error occurs - * added $ escaping when outputting raw strings - * enhanced some error messages to ease debugging - * fixed empty cache files when the template contains an error - -# 0.9.1 (2009-10-14) - - * fixed a bug in PHP 5.2.6 - * fixed numbers with one than one decimal - * added support for method calls with arguments ({{ foo.bar('a', 43) }}) - * made small speed optimizations - * made minor tweaks to allow better extensibility and flexibility - -# 0.9.0 (2009-10-12) - - * Initial release diff --git a/LICENSE b/LICENSE index 4371e423381..8711927f6d9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009-2021 by the Twig Team. +Copyright (c) 2009-2022 by the Twig Team. All rights reserved. diff --git a/README.rst b/README.rst index d896ff500a4..fbe7e9a9f83 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,7 @@ 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 is a template language for PHP. Twig uses a syntax similar to the Django and Jinja template languages which inspired the Twig runtime environment. diff --git a/composer.json b/composer.json index c344849d814..91ff912fcd4 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "symfony/polyfill-ctype": "^1.8" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", "psr/container": "^1.0" }, "autoload": { diff --git a/doc/advanced.rst b/doc/advanced.rst index f801348d115..307650f6a46 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -527,7 +527,7 @@ The ``Project_Set_Node`` class itself is quite short:: } } -The compiler implements a fluid interface and provides methods that helps the +The compiler implements a fluid interface and provides methods that help the developer generate beautiful and readable PHP code: * ``subcompile()``: Compiles a node. diff --git a/doc/api.rst b/doc/api.rst index 80dfaa584e0..3186e293d26 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -177,7 +177,7 @@ Namespaced templates can be accessed via the special $twig->render('@admin/index.html', []); -``\Twig\Loader\FilesystemLoader`` support absolute and relative paths. Using relative +``\Twig\Loader\FilesystemLoader`` supports 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):: diff --git a/doc/filters/abs.rst b/doc/filters/abs.rst index 77d5cf05495..fea11753630 100644 --- a/doc/filters/abs.rst +++ b/doc/filters/abs.rst @@ -15,4 +15,4 @@ The ``abs`` filter returns the absolute value. Internally, Twig uses the PHP `abs`_ function. -.. _`abs`: https://secure.php.net/abs +.. _`abs`: https://www.php.net/abs diff --git a/doc/filters/column.rst b/doc/filters/column.rst index 5b6769b8d62..981b8608a42 100644 --- a/doc/filters/column.rst +++ b/doc/filters/column.rst @@ -21,4 +21,4 @@ Arguments * ``name``: The column name to extract -.. _`array_column`: https://secure.php.net/array_column +.. _`array_column`: https://www.php.net/array_column diff --git a/doc/filters/convert_encoding.rst b/doc/filters/convert_encoding.rst index d977c752c8e..98645494e82 100644 --- a/doc/filters/convert_encoding.rst +++ b/doc/filters/convert_encoding.rst @@ -19,4 +19,4 @@ Arguments * ``to``: The output charset * ``from``: The input charset -.. _`iconv`: https://secure.php.net/iconv +.. _`iconv`: https://www.php.net/iconv diff --git a/doc/filters/country_name.rst b/doc/filters/country_name.rst index 166399ffce1..434b0bda7a1 100644 --- a/doc/filters/country_name.rst +++ b/doc/filters/country_name.rst @@ -25,8 +25,13 @@ By default, the filter uses the current locale. You can pass it explicitly: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/currency_name.rst b/doc/filters/currency_name.rst index 61805edf038..a35c499988d 100644 --- a/doc/filters/currency_name.rst +++ b/doc/filters/currency_name.rst @@ -28,8 +28,13 @@ By default, the filter uses the current locale. You can pass it explicitly: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/currency_symbol.rst b/doc/filters/currency_symbol.rst index 73b8e07160d..84a048ed52c 100644 --- a/doc/filters/currency_symbol.rst +++ b/doc/filters/currency_symbol.rst @@ -28,8 +28,13 @@ By default, the filter uses the current locale. You can pass it explicitly: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/data_uri.rst b/doc/filters/data_uri.rst index fadb509a8a0..e008266b346 100644 --- a/doc/filters/data_uri.rst +++ b/doc/filters/data_uri.rst @@ -1,8 +1,8 @@ ``data_uri`` ============ -The ``data_uri`` filter generates a URL using the data scheme as defined in RFC -2397: +The ``data_uri`` filter generates a URL using the data scheme as defined in +`RFC 2397`_: .. code-block:: html+twig @@ -28,8 +28,13 @@ The ``data_uri`` filter generates a URL using the data scheme as defined in RFC $ composer require twig/html-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; @@ -38,7 +43,7 @@ The ``data_uri`` filter generates a URL using the data scheme as defined in RFC .. note:: - The filter does not perform any length validation on purpose (limits depends + 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 @@ -46,3 +51,5 @@ Arguments * ``mime``: The mime type * ``parameters``: An array of parameters + +.. _RFC 2397: https://tools.ietf.org/html/rfc2397 diff --git a/doc/filters/date.rst b/doc/filters/date.rst index ec08d556324..70470f0f4d2 100644 --- a/doc/filters/date.rst +++ b/doc/filters/date.rst @@ -71,8 +71,8 @@ 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 +.. _`strtotime`: https://www.php.net/strtotime +.. _`DateTime`: https://www.php.net/DateTime +.. _`DateInterval`: https://www.php.net/DateInterval +.. _`date`: https://www.php.net/date +.. _`DateInterval::format`: https://www.php.net/DateInterval.format diff --git a/doc/filters/date_modify.rst b/doc/filters/date_modify.rst index cc5d6df160d..e091391d1c9 100644 --- a/doc/filters/date_modify.rst +++ b/doc/filters/date_modify.rst @@ -16,5 +16,5 @@ Arguments * ``modifier``: The modifier -.. _`strtotime`: https://secure.php.net/strtotime -.. _`DateTime`: https://secure.php.net/DateTime +.. _`strtotime`: https://www.php.net/strtotime +.. _`DateTime`: https://www.php.net/DateTime diff --git a/doc/filters/escape.rst b/doc/filters/escape.rst index f105d97a30f..e8b735db46b 100644 --- a/doc/filters/escape.rst +++ b/doc/filters/escape.rst @@ -114,4 +114,4 @@ Arguments * ``strategy``: The escaping strategy * ``charset``: The string charset -.. _`htmlspecialchars`: https://secure.php.net/htmlspecialchars +.. _`htmlspecialchars`: https://www.php.net/htmlspecialchars diff --git a/doc/filters/first.rst b/doc/filters/first.rst index 8d7081a53f1..e0cc7cb1c9e 100644 --- a/doc/filters/first.rst +++ b/doc/filters/first.rst @@ -19,4 +19,4 @@ a string: It also works with objects implementing the `Traversable`_ interface. -.. _`Traversable`: https://secure.php.net/manual/en/class.traversable.php +.. _`Traversable`: https://www.php.net/manual/en/class.traversable.php diff --git a/doc/filters/format.rst b/doc/filters/format.rst index 782ea75cc63..68551a3dda3 100644 --- a/doc/filters/format.rst +++ b/doc/filters/format.rst @@ -15,4 +15,4 @@ The ``format`` filter formats a given string by replacing the placeholders :doc:`replace` -.. _`sprintf`: https://secure.php.net/sprintf +.. _`sprintf`: https://www.php.net/sprintf diff --git a/doc/filters/format_currency.rst b/doc/filters/format_currency.rst index d860a1bcbfd..8b649bf5d94 100644 --- a/doc/filters/format_currency.rst +++ b/doc/filters/format_currency.rst @@ -56,8 +56,13 @@ By default, the filter uses the current locale. You can pass it explicitly: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/format_date.rst b/doc/filters/format_date.rst index 31b4550bbf0..c4a900a4360 100644 --- a/doc/filters/format_date.rst +++ b/doc/filters/format_date.rst @@ -13,8 +13,13 @@ the :doc:`format_datetime` filter, but without the time. $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; @@ -27,3 +32,5 @@ Arguments * ``locale``: The locale * ``dateFormat``: The date format * ``pattern``: A date time pattern +* ``timezone``: The date timezone +* ``calendar``: The calendar (Gregorian by default) diff --git a/doc/filters/format_datetime.rst b/doc/filters/format_datetime.rst index e08e6cdcaf4..b1b38b5b513 100644 --- a/doc/filters/format_datetime.rst +++ b/doc/filters/format_datetime.rst @@ -23,7 +23,7 @@ You can tweak the output for the date part and the time part: Supported values are: ``none``, ``short``, ``medium``, ``long``, and ``full``. -For greater flexiblity, you can even define your own pattern (see the `ICU user +For greater flexibility, you can even define your own pattern (see the `ICU user guide `_ for supported patterns). @@ -49,8 +49,13 @@ By default, the filter uses the current locale. You can pass it explicitly: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; @@ -64,3 +69,5 @@ Arguments * ``dateFormat``: The date format * ``timeFormat``: The time format * ``pattern``: A date time pattern +* ``timezone``: The date timezone +* ``calendar``: The calendar (Gregorian by default) diff --git a/doc/filters/format_number.rst b/doc/filters/format_number.rst index f6d3f965786..a1c2804ab4b 100644 --- a/doc/filters/format_number.rst +++ b/doc/filters/format_number.rst @@ -96,8 +96,13 @@ By default, the filter uses the current locale. You can pass it explicitly: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/format_time.rst b/doc/filters/format_time.rst index b1aa957233a..417b8a9c62b 100644 --- a/doc/filters/format_time.rst +++ b/doc/filters/format_time.rst @@ -13,8 +13,13 @@ the :doc:`format_datetime` filter, but without the date. $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; @@ -27,3 +32,5 @@ Arguments * ``locale``: The locale * ``timeFormat``: The time format * ``pattern``: A date time pattern +* ``timezone``: The date timezone +* ``calendar``: The calendar (Gregorian by default) diff --git a/doc/filters/html_to_markdown.rst b/doc/filters/html_to_markdown.rst index 938be15317a..58568a445c2 100644 --- a/doc/filters/html_to_markdown.rst +++ b/doc/filters/html_to_markdown.rst @@ -57,9 +57,9 @@ You can also use the filter on an entire template which you ``include``: ``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](https://github.com/erusev/parsedown) -* [thephpleague/html-to-markdown](https://github.com/thephpleague/html-to-markdown) -* [michelf/php-markdown](https://github.com/michelf/php-markdown) +* `league/html-to-markdown`_ +* `michelf/php-markdown`_ +* `erusev/parsedown`_ 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``: @@ -72,3 +72,6 @@ to the filter. Example for ``league/html-to-markdown``: {% endapply %} +.. _league/html-to-markdown: https://github.com/thephpleague/html-to-markdown +.. _michelf/php-markdown: https://github.com/michelf/php-markdown +.. _erusev/parsedown: https://github.com/erusev/parsedown diff --git a/doc/filters/inky_to_html.rst b/doc/filters/inky_to_html.rst index 5f8fe7bf039..563baba363a 100644 --- a/doc/filters/inky_to_html.rst +++ b/doc/filters/inky_to_html.rst @@ -28,8 +28,13 @@ You can also use the filter on an included file: $ composer require twig/inky-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/inline_css.rst b/doc/filters/inline_css.rst index acdb6e8bfd3..44b142626d8 100644 --- a/doc/filters/inline_css.rst +++ b/doc/filters/inline_css.rst @@ -52,8 +52,13 @@ Note that the CSS inliner works on an entire HTML document, not a fragment. $ composer require twig/cssinliner-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/json_encode.rst b/doc/filters/json_encode.rst index 434e2f17832..34e71367ed9 100644 --- a/doc/filters/json_encode.rst +++ b/doc/filters/json_encode.rst @@ -19,5 +19,5 @@ Arguments Combine constants using :ref:`bitwise operators`: ``{{ 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 +.. _`json_encode`: https://www.php.net/json_encode +.. _`json_encode options`: https://www.php.net/manual/en/json.constants.php diff --git a/doc/filters/language_name.rst b/doc/filters/language_name.rst index 96d6da13853..55c2439207a 100644 --- a/doc/filters/language_name.rst +++ b/doc/filters/language_name.rst @@ -28,8 +28,13 @@ By default, the filter uses the current locale. You can pass it explicitly: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/last.rst b/doc/filters/last.rst index 2b7c45bd167..d7ac6a533be 100644 --- a/doc/filters/last.rst +++ b/doc/filters/last.rst @@ -19,4 +19,4 @@ a string: It also works with objects implementing the `Traversable`_ interface. -.. _`Traversable`: https://secure.php.net/manual/en/class.traversable.php +.. _`Traversable`: https://www.php.net/manual/en/class.traversable.php diff --git a/doc/filters/locale_name.rst b/doc/filters/locale_name.rst index be9b8c871eb..c6d34cbc019 100644 --- a/doc/filters/locale_name.rst +++ b/doc/filters/locale_name.rst @@ -28,8 +28,13 @@ By default, the filter uses the current locale. You can pass it explicitly: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/map.rst b/doc/filters/map.rst index 1083bfe5eae..23f79e734ec 100644 --- a/doc/filters/map.rst +++ b/doc/filters/map.rst @@ -23,7 +23,7 @@ The arrow function also receives the key as a second argument: "Alice": "Dupond", } %} - {{ people|map((last, first) => "#{first} #{last}")|join(', ') }} + {{ people|map((value, key) => "#{key} #{value}")|join(', ') }} {# outputs Bob Smith, Alice Dupond #} Note that the arrow function has access to the current context. diff --git a/doc/filters/markdown_to_html.rst b/doc/filters/markdown_to_html.rst index d5cdac348b4..8e3fede00f4 100644 --- a/doc/filters/markdown_to_html.rst +++ b/doc/filters/markdown_to_html.rst @@ -29,7 +29,7 @@ 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:: @@ -41,8 +41,13 @@ You can also use the filter on an included file or a variable: $ composer require twig/markdown-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; @@ -62,6 +67,13 @@ You can also use the filter on an included file or a variable: } } }); - + 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. + +.. note:: + + If using Symfony (full-stack), ``twig/extra-bundle`` with ``league/commonmark`` as + your Markdown library you can configure CommonMark extensions. Register the desired + extension(s) as a service, then tag the service with + ``twig.markdown.league_extension``. diff --git a/doc/filters/merge.rst b/doc/filters/merge.rst index e26e51c242b..40146b5d7e5 100644 --- a/doc/filters/merge.rst +++ b/doc/filters/merge.rst @@ -45,4 +45,4 @@ overridden. 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 +.. _`array_merge`: https://www.php.net/array_merge diff --git a/doc/filters/number_format.rst b/doc/filters/number_format.rst index c44e2e3537a..047249d6718 100644 --- a/doc/filters/number_format.rst +++ b/doc/filters/number_format.rst @@ -48,4 +48,4 @@ Arguments * ``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 +.. _`number_format`: https://www.php.net/number_format diff --git a/doc/filters/reverse.rst b/doc/filters/reverse.rst index c5ceb7d0508..0c0e1ac00f9 100644 --- a/doc/filters/reverse.rst +++ b/doc/filters/reverse.rst @@ -41,4 +41,4 @@ Arguments * ``preserve_keys``: Preserve keys when reversing a mapping or a sequence. -.. _`Traversable`: https://secure.php.net/Traversable +.. _`Traversable`: https://www.php.net/Traversable diff --git a/doc/filters/slice.rst b/doc/filters/slice.rst index 9cc0ca4e814..ae83b57a063 100644 --- a/doc/filters/slice.rst +++ b/doc/filters/slice.rst @@ -62,7 +62,7 @@ Arguments * ``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 +.. _`Traversable`: https://www.php.net/manual/en/class.traversable.php +.. _`array_slice`: https://www.php.net/array_slice +.. _`mb_substr`: https://www.php.net/mb-substr +.. _`substr`: https://www.php.net/substr diff --git a/doc/filters/slug.rst b/doc/filters/slug.rst index 99cfa173e92..773a42fac26 100644 --- a/doc/filters/slug.rst +++ b/doc/filters/slug.rst @@ -30,6 +30,28 @@ argument: The ``slug`` filter uses the method by the same name in Symfony's `AsciiSlugger `_. +.. 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 --------- diff --git a/doc/filters/sort.rst b/doc/filters/sort.rst index 3dc5f4d73b7..1816c35e6fc 100644 --- a/doc/filters/sort.rst +++ b/doc/filters/sort.rst @@ -38,5 +38,5 @@ Arguments * ``arrow``: An arrow function -.. _`asort`: https://secure.php.net/asort +.. _`asort`: https://www.php.net/asort .. _`spaceship`: https://www.php.net/manual/en/language.operators.comparison.php diff --git a/doc/filters/split.rst b/doc/filters/split.rst index 38c4b7df04d..386ae3043d0 100644 --- a/doc/filters/split.rst +++ b/doc/filters/split.rst @@ -46,5 +46,5 @@ Arguments * ``delimiter``: The delimiter * ``limit``: The limit argument -.. _`explode`: https://secure.php.net/explode -.. _`str_split`: https://secure.php.net/str_split +.. _`explode`: https://www.php.net/explode +.. _`str_split`: https://www.php.net/str_split diff --git a/doc/filters/striptags.rst b/doc/filters/striptags.rst index 7a0aabddc83..d5f542b3d8b 100644 --- a/doc/filters/striptags.rst +++ b/doc/filters/striptags.rst @@ -26,4 +26,4 @@ Arguments * ``allowable_tags``: Tags which should not be stripped -.. _`strip_tags`: https://secure.php.net/strip_tags +.. _`strip_tags`: https://www.php.net/strip_tags diff --git a/doc/filters/timezone_name.rst b/doc/filters/timezone_name.rst index 0453a277fc5..dfb22818bad 100644 --- a/doc/filters/timezone_name.rst +++ b/doc/filters/timezone_name.rst @@ -27,8 +27,13 @@ By default, the filter uses the current locale. You can pass it explicitly: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/trim.rst b/doc/filters/trim.rst index 81d5e041511..a3d36ca0345 100644 --- a/doc/filters/trim.rst +++ b/doc/filters/trim.rst @@ -34,6 +34,6 @@ Arguments * ``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 +.. _`trim`: https://www.php.net/trim +.. _`ltrim`: https://www.php.net/ltrim +.. _`rtrim`: https://www.php.net/rtrim diff --git a/doc/filters/u.rst b/doc/filters/u.rst index 0f063832fa1..20bb0d5cfe8 100644 --- a/doc/filters/u.rst +++ b/doc/filters/u.rst @@ -73,8 +73,13 @@ For large strings manipulation, use the ``apply`` tag: $ composer require twig/string-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/filters/url_encode.rst b/doc/filters/url_encode.rst index 60cf365f1c8..c5919be016b 100644 --- a/doc/filters/url_encode.rst +++ b/doc/filters/url_encode.rst @@ -19,5 +19,5 @@ or an array as query string: 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 +.. _`rawurlencode`: https://www.php.net/rawurlencode +.. _`http_build_query`: https://www.php.net/http_build_query diff --git a/doc/functions/country_timezones.rst b/doc/functions/country_timezones.rst index 077cb24a95c..ecbbc1c9941 100644 --- a/doc/functions/country_timezones.rst +++ b/doc/functions/country_timezones.rst @@ -18,8 +18,13 @@ with a given country code: $ composer require twig/intl-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/functions/date.rst b/doc/functions/date.rst index 3329a56988e..16e1d48740e 100644 --- a/doc/functions/date.rst +++ b/doc/functions/date.rst @@ -41,4 +41,4 @@ Arguments * ``date``: The date * ``timezone``: The timezone -.. _`date and time formats`: https://secure.php.net/manual/en/datetime.formats.php +.. _`date and time formats`: https://www.php.net/manual/en/datetime.formats.php diff --git a/doc/functions/dump.rst b/doc/functions/dump.rst index c7264edda2a..f89a1c4b13a 100644 --- a/doc/functions/dump.rst +++ b/doc/functions/dump.rst @@ -63,4 +63,4 @@ Arguments * ``context``: The context to dump .. _`XDebug`: https://xdebug.org/docs/display -.. _`var_dump`: https://secure.php.net/var_dump +.. _`var_dump`: https://www.php.net/var_dump diff --git a/doc/functions/html_classes.rst b/doc/functions/html_classes.rst index 1be10494263..80bc796e161 100644 --- a/doc/functions/html_classes.rst +++ b/doc/functions/html_classes.rst @@ -21,8 +21,13 @@ names together: $ composer require twig/html-extra - Then, use the ``twig/extra-bundle`` on Symfony projects or add the extension - explicitly on the Twig environment:: + 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; diff --git a/doc/functions/range.rst b/doc/functions/range.rst index a1f0e7c097d..e537b11a652 100644 --- a/doc/functions/range.rst +++ b/doc/functions/range.rst @@ -55,4 +55,4 @@ Arguments * ``high``: The highest possible value of the sequence. * ``step``: The increment between elements of the sequence. -.. _`range`: https://secure.php.net/range +.. _`range`: https://www.php.net/range diff --git a/doc/internals.rst b/doc/internals.rst index 0421e7c7421..ccbb202f372 100644 --- a/doc/internals.rst +++ b/doc/internals.rst @@ -20,7 +20,7 @@ The rendering of a Twig template can be summarized into four key steps: * Then, the **parser** converts the token stream into a meaningful tree of nodes (the Abstract Syntax Tree); - * Finally, the *compiler* transforms the AST into PHP code. + * Finally, the **compiler** transforms the AST into PHP code. * **Evaluate** the template: It means calling the ``display()`` method of the compiled template and passing it the context. diff --git a/doc/recipes.rst b/doc/recipes.rst index 01c937dadda..206a52ee02a 100644 --- a/doc/recipes.rst +++ b/doc/recipes.rst @@ -528,4 +528,4 @@ include in your templates: 'tag_variable' => ['{[', ']}'], ])); -.. _callback: https://secure.php.net/manual/en/function.is-callable.php +.. _callback: https://www.php.net/manual/en/function.is-callable.php diff --git a/doc/tags/flush.rst b/doc/tags/flush.rst index 332e9825145..03d2a367717 100644 --- a/doc/tags/flush.rst +++ b/doc/tags/flush.rst @@ -11,4 +11,4 @@ The ``flush`` tag tells Twig to flush the output buffer: Internally, Twig uses the PHP `flush`_ function. -.. _`flush`: https://secure.php.net/flush +.. _`flush`: https://www.php.net/flush diff --git a/doc/templates.rst b/doc/templates.rst index 2a8f38040a3..5311ad6ace7 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -853,7 +853,7 @@ Twig can be extended. If you want to create your own extensions, read the .. _`other Twig syntax mode`: https://github.com/muxx/Twig-HTML.mode .. _`Notepad++ Twig Highlighter`: https://github.com/Banane9/notepadplusplus-twig .. _`web-mode.el`: http://web-mode.org/ -.. _`regular expressions`: https://secure.php.net/manual/en/pcre.pattern.php +.. _`regular expressions`: https://www.php.net/manual/en/pcre.pattern.php .. _`PHP-twig for atom`: https://github.com/reesef/php-twig .. _`TwigFiddle`: https://twigfiddle.com/ .. _`Twig pack`: https://marketplace.visualstudio.com/items?itemName=bajdzis.vscode-twig-pack diff --git a/extra/cache-extra/Node/CacheNode.php b/extra/cache-extra/Node/CacheNode.php index 4600fa6571f..f79873aa9a5 100644 --- a/extra/cache-extra/Node/CacheNode.php +++ b/extra/cache-extra/Node/CacheNode.php @@ -42,7 +42,7 @@ public function compile(Compiler $compiler): void if ($this->hasNode('tags')) { $compiler - ->write("\$item->tag(") + ->write('$item->tag(') ->subcompile($this->getNode('tags')) ->raw(");\n") ; diff --git a/extra/cache-extra/README.md b/extra/cache-extra/README.md index 8ad17d3c2ca..9db5195cc70 100644 --- a/extra/cache-extra/README.md +++ b/extra/cache-extra/README.md @@ -4,4 +4,6 @@ Cache Extension This package is a Twig extension that provides integration with the Symfony Cache component. -It provides a single `cache` tag that allows to cache template fragments. +It provides a single [`cache`][1] tag that allows to cache template fragments. + +[1]: https://twig.symfony.com/cache diff --git a/extra/cache-extra/Tests/FunctionalTest.php b/extra/cache-extra/Tests/FunctionalTest.php index 13730c07b4b..111026a89d9 100644 --- a/extra/cache-extra/Tests/FunctionalTest.php +++ b/extra/cache-extra/Tests/FunctionalTest.php @@ -53,7 +53,7 @@ public function testTagsNoArgs() { $twig = $this->createEnvironment(['index' => '{% cache "tags_no_args" tags() %}{% endcache %}']); $this->expectException(SyntaxError::class); - $this->expectExceptionMessage('The "ttl" modifier takes exactly one argument (0 given) in "index" at line 1.'); + $this->expectExceptionMessage('The "tags" modifier takes exactly one argument (0 given) in "index" at line 1.'); $twig->render('index'); } @@ -61,7 +61,7 @@ public function testTagsTooManyArgs() { $twig = $this->createEnvironment(['index' => '{% cache "tags_too_many_args" tags(["foo"], 1) %}{% endcache %}']); $this->expectException(SyntaxError::class); - $this->expectExceptionMessage('The "ttl" modifier takes exactly one argument (2 given) in "index" at line 1.'); + $this->expectExceptionMessage('The "tags" modifier takes exactly one argument (2 given) in "index" at line 1.'); $twig->render('index'); } @@ -78,7 +78,8 @@ public function __construct(CacheInterface $cache) $this->cache = $cache; } - public function load($class) { + public function load($class) + { if (CacheRuntime::class === $class) { return new CacheRuntime($this->cache); } diff --git a/extra/cache-extra/Tests/IntegrationTest.php b/extra/cache-extra/Tests/IntegrationTest.php index 86e847f59db..8e216aaa51e 100644 --- a/extra/cache-extra/Tests/IntegrationTest.php +++ b/extra/cache-extra/Tests/IntegrationTest.php @@ -29,8 +29,9 @@ public function getExtensions() protected function getRuntimeLoaders() { return [ - new class implements RuntimeLoaderInterface { - public function load($class) { + new class() implements RuntimeLoaderInterface { + public function load($class) + { if (CacheRuntime::class === $class) { return new CacheRuntime(new ArrayAdapter()); } diff --git a/extra/cache-extra/TokenParser/CacheTokenParser.php b/extra/cache-extra/TokenParser/CacheTokenParser.php index b986f91fafe..cb50b72d369 100644 --- a/extra/cache-extra/TokenParser/CacheTokenParser.php +++ b/extra/cache-extra/TokenParser/CacheTokenParser.php @@ -34,14 +34,14 @@ public function parse(Token $token): Node switch ($k) { case 'ttl': - if (1 !== count($args)) { - throw new SyntaxError(sprintf('The "ttl" modifier takes exactly one argument (%d given).', count($args)), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + if (1 !== \count($args)) { + throw new SyntaxError(sprintf('The "ttl" modifier takes exactly one argument (%d given).', \count($args)), $stream->getCurrent()->getLine(), $stream->getSourceContext()); } $ttl = $args->getNode(0); break; case 'tags': - if (1 !== count($args)) { - throw new SyntaxError(sprintf('The "ttl" modifier takes exactly one argument (%d given).', count($args)), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + if (1 !== \count($args)) { + throw new SyntaxError(sprintf('The "tags" modifier takes exactly one argument (%d given).', \count($args)), $stream->getCurrent()->getLine(), $stream->getSourceContext()); } $tags = $args->getNode(0); break; diff --git a/extra/cache-extra/composer.json b/extra/cache-extra/composer.json index d07a408268b..462b0f48b30 100644 --- a/extra/cache-extra/composer.json +++ b/extra/cache-extra/composer.json @@ -16,11 +16,11 @@ ], "require": { "php": ">=7.2.5", - "symfony/cache": "^5.0", + "symfony/cache": "^5.0|^6.0", "twig/twig": "^2.4|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\Cache\\" : "" }, diff --git a/extra/cssinliner-extra/LICENSE b/extra/cssinliner-extra/LICENSE index 383e7a54586..9c907a46a62 100644 --- a/extra/cssinliner-extra/LICENSE +++ b/extra/cssinliner-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2021 Fabien Potencier +Copyright (c) 2019-2022 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 diff --git a/extra/cssinliner-extra/composer.json b/extra/cssinliner-extra/composer.json index d740e516111..b1c15249b7a 100644 --- a/extra/cssinliner-extra/composer.json +++ b/extra/cssinliner-extra/composer.json @@ -17,10 +17,10 @@ "require": { "php": ">=7.1.3", "tijsverkoyen/css-to-inline-styles": "^2.0", - "twig/twig": "^2.4|^3.0" + "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\CssInliner\\" : "" }, diff --git a/extra/html-extra/LICENSE b/extra/html-extra/LICENSE index 383e7a54586..9c907a46a62 100644 --- a/extra/html-extra/LICENSE +++ b/extra/html-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2021 Fabien Potencier +Copyright (c) 2019-2022 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 diff --git a/extra/html-extra/composer.json b/extra/html-extra/composer.json index 4058da6e9cc..97d87f7e1b1 100644 --- a/extra/html-extra/composer.json +++ b/extra/html-extra/composer.json @@ -16,11 +16,11 @@ ], "require": { "php": ">=7.1.3", - "symfony/mime": "^4.3|^5.0", - "twig/twig": "^2.4|^3.0" + "symfony/mime": "^4.4|^5.0|^6.0", + "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\Html\\" : "" }, diff --git a/extra/inky-extra/LICENSE b/extra/inky-extra/LICENSE index 383e7a54586..9c907a46a62 100644 --- a/extra/inky-extra/LICENSE +++ b/extra/inky-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2021 Fabien Potencier +Copyright (c) 2019-2022 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 diff --git a/extra/inky-extra/composer.json b/extra/inky-extra/composer.json index de2bcbf144f..abd7d399aa6 100644 --- a/extra/inky-extra/composer.json +++ b/extra/inky-extra/composer.json @@ -17,10 +17,10 @@ "require": { "php": ">=7.1.3", "lorenzo/pinky": "^1.0.5", - "twig/twig": "^2.4|^3.0" + "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\Inky\\" : "" }, diff --git a/extra/intl-extra/LICENSE b/extra/intl-extra/LICENSE index 383e7a54586..9c907a46a62 100644 --- a/extra/intl-extra/LICENSE +++ b/extra/intl-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2021 Fabien Potencier +Copyright (c) 2019-2022 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 diff --git a/extra/intl-extra/composer.json b/extra/intl-extra/composer.json index f4b73e5e430..abd60d38606 100644 --- a/extra/intl-extra/composer.json +++ b/extra/intl-extra/composer.json @@ -16,11 +16,11 @@ ], "require": { "php": ">=7.1.3", - "twig/twig": "^2.4|^3.0", - "symfony/intl": "^4.3|^5.0" + "twig/twig": "^2.7|^3.0", + "symfony/intl": "^4.4|^5.0|^6.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\Intl\\" : "" }, diff --git a/extra/markdown-extra/DefaultMarkdown.php b/extra/markdown-extra/DefaultMarkdown.php index 24aad2e3454..6650a661a53 100644 --- a/extra/markdown-extra/DefaultMarkdown.php +++ b/extra/markdown-extra/DefaultMarkdown.php @@ -21,14 +21,14 @@ class DefaultMarkdown implements MarkdownInterface public function __construct() { - if (class_exists(Parsedown::class)) { - $this->converter = new ErusevMarkdown(); - } elseif (class_exists(CommonMarkConverter::class)) { + if (class_exists(CommonMarkConverter::class)) { $this->converter = new LeagueMarkdown(); } elseif (class_exists(MarkdownExtra::class)) { $this->converter = new MichelfMarkdown(); + } elseif (class_exists(Parsedown::class)) { + $this->converter = new ErusevMarkdown(); } else { - throw new \LogicException('You cannot use the "markdown_to_html" filter as no Markdown library is available; try running "composer require erusev/parsedown".'); + throw new \LogicException('You cannot use the "markdown_to_html" filter as no Markdown library is available; try running "composer require league/commonmark".'); } } diff --git a/extra/markdown-extra/LICENSE b/extra/markdown-extra/LICENSE index 383e7a54586..9c907a46a62 100644 --- a/extra/markdown-extra/LICENSE +++ b/extra/markdown-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2021 Fabien Potencier +Copyright (c) 2019-2022 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 diff --git a/extra/markdown-extra/LeagueMarkdown.php b/extra/markdown-extra/LeagueMarkdown.php index ed7e7dd329d..2390901c01e 100644 --- a/extra/markdown-extra/LeagueMarkdown.php +++ b/extra/markdown-extra/LeagueMarkdown.php @@ -16,14 +16,20 @@ class LeagueMarkdown implements MarkdownInterface { private $converter; + private $legacySupport; public function __construct(CommonMarkConverter $converter = null) { $this->converter = $converter ?: new CommonMarkConverter(); + $this->legacySupport = !method_exists($this->converter, 'convert'); } public function convert(string $body): string { - return $this->converter->convertToHtml($body); + if ($this->legacySupport) { + return $this->converter->convertToHtml($body); + } + + return $this->converter->convert($body); } } diff --git a/extra/markdown-extra/composer.json b/extra/markdown-extra/composer.json index aa62a536e79..56718f517ae 100644 --- a/extra/markdown-extra/composer.json +++ b/extra/markdown-extra/composer.json @@ -16,13 +16,13 @@ ], "require": { "php": ">=7.1.3", - "twig/twig": "^2.4|^3.0" + "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", "erusev/parsedown": "^1.7", - "league/commonmark": "^1.0", - "league/html-to-markdown": "^4.8", + "league/commonmark": "^1.0|^2.0", + "league/html-to-markdown": "^4.8|^5.0", "michelf/php-markdown": "^1.8" }, "autoload": { diff --git a/extra/string-extra/LICENSE b/extra/string-extra/LICENSE index 383e7a54586..9c907a46a62 100644 --- a/extra/string-extra/LICENSE +++ b/extra/string-extra/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2021 Fabien Potencier +Copyright (c) 2019-2022 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 diff --git a/extra/string-extra/StringExtension.php b/extra/string-extra/StringExtension.php index db88ca92270..7b5d0049204 100644 --- a/extra/string-extra/StringExtension.php +++ b/extra/string-extra/StringExtension.php @@ -12,8 +12,8 @@ namespace Twig\Extra\String; use Symfony\Component\String\AbstractUnicodeString; -use Symfony\Component\String\Slugger\SluggerInterface; use Symfony\Component\String\Slugger\AsciiSlugger; +use Symfony\Component\String\Slugger\SluggerInterface; use Symfony\Component\String\UnicodeString; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -26,7 +26,7 @@ public function __construct(SluggerInterface $slugger = null) { $this->slugger = $slugger ?: new AsciiSlugger(); } - + public function getFilters() { return [ diff --git a/extra/string-extra/composer.json b/extra/string-extra/composer.json index 061edb97cda..e422f7c0c5f 100644 --- a/extra/string-extra/composer.json +++ b/extra/string-extra/composer.json @@ -16,12 +16,12 @@ ], "require": { "php": ">=7.2.5", - "symfony/string": "^5.0", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^2.4|^3.0" + "symfony/string": "^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\String\\" : "" }, diff --git a/extra/twig-extra-bundle/.gitignore b/extra/twig-extra-bundle/.gitignore index 76367ee5bbc..0e559c535c4 100644 --- a/extra/twig-extra-bundle/.gitignore +++ b/extra/twig-extra-bundle/.gitignore @@ -1,4 +1,5 @@ vendor/ +var/ composer.lock phpunit.xml .phpunit.result.cache diff --git a/extra/twig-extra-bundle/DependencyInjection/Configuration.php b/extra/twig-extra-bundle/DependencyInjection/Configuration.php index f4043fc00b0..447e6ac76fa 100644 --- a/extra/twig-extra-bundle/DependencyInjection/Configuration.php +++ b/extra/twig-extra-bundle/DependencyInjection/Configuration.php @@ -17,6 +17,9 @@ class Configuration implements ConfigurationInterface { + /** + * @return TreeBuilder + */ public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder('twig_extra'); diff --git a/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php b/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php index b77b2e41239..0f57d71f36a 100644 --- a/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php +++ b/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php @@ -11,6 +11,7 @@ namespace Twig\Extra\TwigExtraBundle\DependencyInjection; +use League\CommonMark\CommonMarkConverter; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; @@ -35,6 +36,10 @@ public function load(array $configs, ContainerBuilder $container) foreach (array_keys(Extensions::getClasses()) as $extension) { if ($this->isConfigEnabled($container, $config[$extension])) { $loader->load($extension.'.php'); + + if ('markdown' === $extension && \class_exists(CommonMarkConverter::class)) { + $loader->load('markdown_league.php'); + } } } } diff --git a/extra/twig-extra-bundle/LICENSE b/extra/twig-extra-bundle/LICENSE index 383e7a54586..9c907a46a62 100644 --- a/extra/twig-extra-bundle/LICENSE +++ b/extra/twig-extra-bundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2021 Fabien Potencier +Copyright (c) 2019-2022 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 diff --git a/extra/twig-extra-bundle/LeagueCommonMarkConverterFactory.php b/extra/twig-extra-bundle/LeagueCommonMarkConverterFactory.php new file mode 100644 index 00000000000..a2b90a25f58 --- /dev/null +++ b/extra/twig-extra-bundle/LeagueCommonMarkConverterFactory.php @@ -0,0 +1,42 @@ +extensions = $extensions; + } + + public function __invoke(): CommonMarkConverter + { + $converter = new CommonMarkConverter(); + + foreach ($this->extensions as $extension) { + $converter->getEnvironment()->addExtension($extension); + } + + return $converter; + } +} diff --git a/extra/twig-extra-bundle/README.md b/extra/twig-extra-bundle/README.md index f7602e4fe71..439fe9df7f6 100644 --- a/extra/twig-extra-bundle/README.md +++ b/extra/twig-extra-bundle/README.md @@ -3,3 +3,8 @@ Twig Extra Bundle This package is a Symfony bundle that allows to use all "extra" extensions without any configuration. + +Installation +------------ + + composer require twig/extra-bundle diff --git a/extra/twig-extra-bundle/Resources/config/cache.php b/extra/twig-extra-bundle/Resources/config/cache.php index 6ea0cc11ad6..c6ed9d393c9 100644 --- a/extra/twig-extra-bundle/Resources/config/cache.php +++ b/extra/twig-extra-bundle/Resources/config/cache.php @@ -19,7 +19,7 @@ use Twig\Extra\Cache\CacheRuntime; return static function (ContainerConfigurator $container) { - $service = function_exists('Symfony\Component\DependencyInjection\Loader\Configurator\service') ? 'Symfony\Component\DependencyInjection\Loader\Configurator\service' : 'Symfony\Component\DependencyInjection\Loader\Configurator\ref'; + $service = \function_exists('Symfony\Component\DependencyInjection\Loader\Configurator\service') ? 'Symfony\Component\DependencyInjection\Loader\Configurator\service' : 'Symfony\Component\DependencyInjection\Loader\Configurator\ref'; $container->services() ->set('twig.extension.cache', CacheExtension::class) ->tag('twig.extension') @@ -32,7 +32,7 @@ ->set('twig.cache', TagAwareAdapter::class) ->args([ - $service('.twig.cache.inner') + $service('.twig.cache.inner'), ]) ->set('.twig.cache.inner') diff --git a/extra/twig-extra-bundle/Resources/config/markdown.php b/extra/twig-extra-bundle/Resources/config/markdown.php index dd5774d0335..665542a53ad 100644 --- a/extra/twig-extra-bundle/Resources/config/markdown.php +++ b/extra/twig-extra-bundle/Resources/config/markdown.php @@ -16,7 +16,7 @@ use Twig\Extra\Markdown\MarkdownRuntime; return static function (ContainerConfigurator $container) { - $service = function_exists('Symfony\Component\DependencyInjection\Loader\Configurator\service') ? 'Symfony\Component\DependencyInjection\Loader\Configurator\service' : 'Symfony\Component\DependencyInjection\Loader\Configurator\ref'; + $service = \function_exists('Symfony\Component\DependencyInjection\Loader\Configurator\service') ? 'Symfony\Component\DependencyInjection\Loader\Configurator\service' : 'Symfony\Component\DependencyInjection\Loader\Configurator\ref'; $container->services() ->set('twig.extension.markdown', MarkdownExtension::class) ->tag('twig.extension') diff --git a/extra/twig-extra-bundle/Resources/config/markdown_league.php b/extra/twig-extra-bundle/Resources/config/markdown_league.php new file mode 100644 index 00000000000..266f59776a2 --- /dev/null +++ b/extra/twig-extra-bundle/Resources/config/markdown_league.php @@ -0,0 +1,31 @@ +services() + ->set('twig.markdown.league_common_mark_converter_factory', LeagueCommonMarkConverterFactory::class) + ->args([new TaggedIteratorArgument('twig.markdown.league_extension')]) + + ->set('twig.markdown.league_common_mark_converter', CommonMarkConverter::class) + ->factory($service('twig.markdown.league_common_mark_converter_factory')) + + ->set('twig.markdown.default', LeagueMarkdown::class) + ->args([$service('twig.markdown.league_common_mark_converter')]) + ; +}; diff --git a/extra/twig-extra-bundle/Tests/DependencyInjection/TwigExtraExtensionTest.php b/extra/twig-extra-bundle/Tests/DependencyInjection/TwigExtraExtensionTest.php index 852162dfa26..355b794d2b1 100644 --- a/extra/twig-extra-bundle/Tests/DependencyInjection/TwigExtraExtensionTest.php +++ b/extra/twig-extra-bundle/Tests/DependencyInjection/TwigExtraExtensionTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Twig\Extra\Markdown\LeagueMarkdown; use Twig\Extra\TwigExtraBundle\DependencyInjection\TwigExtraExtension; use Twig\Extra\TwigExtraBundle\Extensions; @@ -34,5 +35,12 @@ public function testDefaultConfiguration() foreach (Extensions::getClasses() as $name => $class) { $this->assertEquals($class, $container->getDefinition('twig.extension.'.$name)->getClass()); } + + $this->assertSame(LeagueMarkdown::class, $container->getDefinition('twig.markdown.default')->getClass()); + + $commonmarkConverterFactory = $container->getDefinition('twig.markdown.league_common_mark_converter')->getFactory(); + + $this->assertSame('twig.markdown.league_common_mark_converter_factory', (string) $commonmarkConverterFactory[0]); + $this->assertSame('__invoke', $commonmarkConverterFactory[1]); } } diff --git a/extra/twig-extra-bundle/Tests/Fixture/Kernel.php b/extra/twig-extra-bundle/Tests/Fixture/Kernel.php new file mode 100644 index 00000000000..cbc9c4bd850 --- /dev/null +++ b/extra/twig-extra-bundle/Tests/Fixture/Kernel.php @@ -0,0 +1,42 @@ +loadFromExtension('framework', [ + 'secret' => 'S3CRET', + 'test' => true, + ]); + + $c->loadFromExtension('twig', [ + 'default_path' => __DIR__.'/views', + ]); + + $c->register(StrikethroughExtension::class)->addTag('twig.markdown.league_extension'); + } + + protected function configureRoutes($routes): void + { + } +} diff --git a/extra/twig-extra-bundle/Tests/Fixture/views/markdown_to_html.html.twig b/extra/twig-extra-bundle/Tests/Fixture/views/markdown_to_html.html.twig new file mode 100644 index 00000000000..76ece57dd6c --- /dev/null +++ b/extra/twig-extra-bundle/Tests/Fixture/views/markdown_to_html.html.twig @@ -0,0 +1,3 @@ +{% apply markdown_to_html %} +# Hello ~~World~~ +{% endapply %} diff --git a/extra/twig-extra-bundle/Tests/IntegrationTest.php b/extra/twig-extra-bundle/Tests/IntegrationTest.php new file mode 100644 index 00000000000..04cbbeed5a9 --- /dev/null +++ b/extra/twig-extra-bundle/Tests/IntegrationTest.php @@ -0,0 +1,18 @@ +get('twig')->render('markdown_to_html.html.twig'); + + $this->assertStringContainsString('

Hello World

', $rendered); + } +} diff --git a/extra/twig-extra-bundle/composer.json b/extra/twig-extra-bundle/composer.json index 2bc4022738c..a946ecc3441 100644 --- a/extra/twig-extra-bundle/composer.json +++ b/extra/twig-extra-bundle/composer.json @@ -15,13 +15,14 @@ } ], "require": { - "php": "^7.1.3|^8.0", - "symfony/framework-bundle": "^4.3|^5.0", - "symfony/twig-bundle": "^4.3|^5.0", - "twig/twig": "^2.4|^3.0" + "php": ">=7.2.5", + "symfony/framework-bundle": "^4.4|^5.0|^6.0", + "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9", + "league/commonmark": "^1.0|^2.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", "twig/cache-extra": "^3.0", "twig/cssinliner-extra": "^2.12|^3.0", "twig/html-extra": "^2.12|^3.0", diff --git a/extra/twig-extra-bundle/phpunit.xml.dist b/extra/twig-extra-bundle/phpunit.xml.dist index 901246f1a7d..41534858a1f 100644 --- a/extra/twig-extra-bundle/phpunit.xml.dist +++ b/extra/twig-extra-bundle/phpunit.xml.dist @@ -10,6 +10,8 @@ > + + diff --git a/src/Cache/FilesystemCache.php b/src/Cache/FilesystemCache.php index abb23580ee3..e075563aef6 100644 --- a/src/Cache/FilesystemCache.php +++ b/src/Cache/FilesystemCache.php @@ -18,7 +18,7 @@ */ class FilesystemCache implements CacheInterface { - const FORCE_BYTECODE_INVALIDATION = 1; + public const FORCE_BYTECODE_INVALIDATION = 1; private $directory; private $options; @@ -31,7 +31,7 @@ public function __construct(string $directory, int $options = 0) public function generateKey(string $name, string $className): string { - $hash = hash('sha256', $className); + $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className); return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; } @@ -63,7 +63,7 @@ public function write(string $key, string $content): void if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { // Compile cached file into bytecode cache - if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) { + if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { @opcache_invalidate($key, true); } elseif (\function_exists('apc_compile_file')) { apc_compile_file($key); diff --git a/src/Compiler.php b/src/Compiler.php index 7754c082285..95e1f183b25 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -122,14 +122,14 @@ public function string(string $value) public function repr($value) { if (\is_int($value) || \is_float($value)) { - if (false !== $locale = setlocale(LC_NUMERIC, '0')) { - setlocale(LC_NUMERIC, 'C'); + if (false !== $locale = setlocale(\LC_NUMERIC, '0')) { + setlocale(\LC_NUMERIC, 'C'); } $this->raw(var_export($value, true)); if (false !== $locale) { - setlocale(LC_NUMERIC, $locale); + setlocale(\LC_NUMERIC, $locale); } } elseif (null === $value) { $this->raw('null'); @@ -209,6 +209,6 @@ public function outdent(int $step = 1) public function getVarName(): string { - return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++)); + return sprintf('__internal_compile_%d', $this->varNameSalt++); } } diff --git a/src/Environment.php b/src/Environment.php index fae5bc9473b..ab5e5d02b8c 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -38,12 +38,12 @@ */ class Environment { - const VERSION = '3.3.0'; - const VERSION_ID = 30300; - const MAJOR_VERSION = 3; - const MINOR_VERSION = 3; - const RELEASE_VERSION = 0; - const EXTRA_VERSION = ''; + public const VERSION = '3.3.8'; + public const VERSION_ID = 30308; + public const MAJOR_VERSION = 3; + public const MINOR_VERSION = 3; + public const RELEASE_VERSION = 8; + public const EXTRA_VERSION = ''; private $charset; private $loader; @@ -235,7 +235,7 @@ public function setCache($cache) } elseif ($cache instanceof CacheInterface) { $this->originalCache = $this->cache = $cache; } else { - throw new \LogicException(sprintf('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.')); + throw new \LogicException('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.'); } } @@ -260,7 +260,7 @@ public function getTemplateClass(string $name, int $index = null): string { $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; - return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '___'.$index); + return $this->templateClassPrefix.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index); } /** @@ -382,7 +382,7 @@ public function loadTemplate(string $cls, string $name, int $index = null): Temp */ public function createTemplate(string $template, string $name = null): TemplateWrapper { - $hash = hash('sha256', $template, false); + $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $template, false); if (null !== $name) { $name = sprintf('%s (string template %s)', $name, $hash); } else { @@ -433,11 +433,20 @@ public function resolveTemplate($names): TemplateWrapper return $this->load($names); } + $count = \count($names); foreach ($names as $name) { - try { - return $this->load($name); - } catch (LoaderError $e) { + if ($name instanceof Template) { + return $name; } + if ($name instanceof TemplateWrapper) { + return $name; + } + + if (1 !== $count && !$this->getLoader()->exists($name)) { + continue; + } + + return $this->load($name); } throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); @@ -525,7 +534,7 @@ public function getLoader(): LoaderInterface public function setCharset(string $charset) { - if ('UTF8' === $charset = strtoupper($charset)) { + if ('UTF8' === $charset = null === $charset ? null : strtoupper($charset)) { // iconv on Windows requires "UTF-8" instead of "UTF8" $charset = 'UTF-8'; } @@ -548,6 +557,13 @@ public function addRuntimeLoader(RuntimeLoaderInterface $loader) $this->runtimeLoaders[] = $loader; } + /** + * @template TExtension of ExtensionInterface + * + * @param class-string $class + * + * @return TExtension + */ public function getExtension(string $class): ExtensionInterface { return $this->extensionSet->getExtension($class); @@ -556,9 +572,11 @@ public function getExtension(string $class): ExtensionInterface /** * Returns the runtime implementation of a Twig element (filter/function/tag/test). * - * @param string $class A runtime class name + * @template TRuntime of object + * + * @param class-string $class A runtime class name * - * @return object The runtime implementation + * @return TRuntime The runtime implementation * * @throws RuntimeError When the template cannot be found */ @@ -804,8 +822,8 @@ private function updateOptionsHash(): void { $this->optionsHash = implode(':', [ $this->extensionSet->getSignature(), - PHP_MAJOR_VERSION, - PHP_MINOR_VERSION, + \PHP_MAJOR_VERSION, + \PHP_MINOR_VERSION, self::VERSION, (int) $this->debug, (int) $this->strictVariables, diff --git a/src/Error/Error.php b/src/Error/Error.php index d8a30c58a22..a68be65f203 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -168,11 +168,11 @@ private function guessTemplateInfo(): void $template = null; $templateClass = null; - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT); foreach ($backtrace as $trace) { if (isset($trace['object']) && $trace['object'] instanceof Template) { $currentClass = \get_class($trace['object']); - $isEmbedContainer = 0 === strpos($templateClass, $currentClass); + $isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass); if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { $template = $trace['object']; $templateClass = \get_class($trace['object']); diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index 243c7f67210..66acddf6165 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -45,8 +45,8 @@ */ class ExpressionParser { - const OPERATOR_LEFT = 1; - const OPERATOR_RIGHT = 2; + public const OPERATOR_LEFT = 1; + public const OPERATOR_RIGHT = 2; private $parser; private $env; @@ -255,7 +255,9 @@ public function parsePrimaryExpression() $this->parser->getStream()->next(); $node = new NameExpression($token->getValue(), $token->getLine()); break; - } elseif (isset($this->unaryOperators[$token->getValue()])) { + } + + if (isset($this->unaryOperators[$token->getValue()])) { $class = $this->unaryOperators[$token->getValue()]['class']; if (!\in_array($class, [NegUnary::class, PosUnary::class])) { throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); @@ -613,7 +615,7 @@ public function parseArguments($namedArguments = false, $definition = false, $al $value = $this->parsePrimaryExpression(); if (!$this->checkConstantExpression($value)) { - throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()); + throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext()); } } else { $value = $this->parseExpression(0, $allowArrow); @@ -745,7 +747,7 @@ private function getTestNodeClass(TwigTest $test): string $src = $stream->getSourceContext(); $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine()); - @trigger_error($message, E_USER_DEPRECATED); + @trigger_error($message, \E_USER_DEPRECATED); } return $test->getNodeClass(); @@ -771,7 +773,7 @@ private function getFunctionNodeClass(string $name, int $line): string $src = $this->parser->getStream()->getSourceContext(); $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); - @trigger_error($message, E_USER_DEPRECATED); + @trigger_error($message, \E_USER_DEPRECATED); } return $function->getNodeClass(); @@ -797,7 +799,7 @@ private function getFilterNodeClass(string $name, int $line): string $src = $this->parser->getStream()->getSourceContext(); $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); - @trigger_error($message, E_USER_DEPRECATED); + @trigger_error($message, \E_USER_DEPRECATED); } return $filter->getNodeClass(); diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 5575f165a92..88cd7545842 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -177,7 +177,7 @@ public function getFilters(): array // formatting filters new TwigFilter('date', 'twig_date_format_filter', ['needs_environment' => true]), new TwigFilter('date_modify', 'twig_date_modify_filter', ['needs_environment' => true]), - new TwigFilter('format', 'sprintf'), + new TwigFilter('format', 'twig_sprintf'), new TwigFilter('replace', 'twig_replace_filter'), new TwigFilter('number_format', 'twig_number_format_filter', ['needs_environment' => true]), new TwigFilter('abs', 'abs'), @@ -193,15 +193,15 @@ public function getFilters(): array new TwigFilter('capitalize', 'twig_capitalize_string_filter', ['needs_environment' => true]), new TwigFilter('upper', 'twig_upper_filter', ['needs_environment' => true]), new TwigFilter('lower', 'twig_lower_filter', ['needs_environment' => true]), - new TwigFilter('striptags', 'strip_tags'), + new TwigFilter('striptags', 'twig_striptags'), new TwigFilter('trim', 'twig_trim_filter'), - new TwigFilter('nl2br', 'nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]), + new TwigFilter('nl2br', 'twig_nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]), new TwigFilter('spaceless', 'twig_spaceless', ['is_safe' => ['html']]), // array helpers new TwigFilter('join', 'twig_join_filter'), new TwigFilter('split', 'twig_split_filter', ['needs_environment' => true]), - new TwigFilter('sort', 'twig_sort_filter'), + new TwigFilter('sort', 'twig_sort_filter', ['needs_environment' => true]), new TwigFilter('merge', 'twig_array_merge'), new TwigFilter('batch', 'twig_array_batch'), new TwigFilter('column', 'twig_array_column'), @@ -346,7 +346,7 @@ function twig_cycle($values, $position) function twig_random(Environment $env, $values = null, $max = null) { if (null === $values) { - return null === $max ? mt_rand() : mt_rand(0, $max); + return null === $max ? mt_rand() : mt_rand(0, (int) $max); } if (\is_int($values) || \is_float($values)) { @@ -363,7 +363,7 @@ function twig_random(Environment $env, $values = null, $max = null) $max = $max; } - return mt_rand($min, $max); + return mt_rand((int) $min, (int) $max); } if (\is_string($values)) { @@ -443,6 +443,19 @@ function twig_date_modify_filter(Environment $env, $date, $modifier) return $date->modify($modifier); } +/** + * Returns a formatted string. + * + * @param string|null $format + * @param ...$values + * + * @return string + */ +function twig_sprintf($format, ...$values) +{ + return sprintf($format ?? '', ...$values); +} + /** * Converts an input to a \DateTime instance. * @@ -481,6 +494,10 @@ function twig_date_converter(Environment $env, $date = null, $timezone = null) } if (null === $date || 'now' === $date) { + if (null === $date) { + $date = 'now'; + } + return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension(CoreExtension::class)->getTimezone()); } @@ -501,7 +518,7 @@ function twig_date_converter(Environment $env, $date = null, $timezone = null) /** * Replaces strings within a string. * - * @param string $str String to replace in + * @param string|null $str String to replace in * @param array|\Traversable $from Replace values * * @return string @@ -512,20 +529,22 @@ function twig_replace_filter($str, $from) throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); } - return strtr($str, twig_to_array($from)); + return strtr($str ?? '', twig_to_array($from)); } /** * Rounds a number. * - * @param int|float $value The value to round - * @param int|float $precision The rounding precision - * @param string $method The method to use for rounding + * @param int|float|string|null $value The value to round + * @param int|float $precision The rounding precision + * @param string $method The method to use for rounding * * @return int|float The rounded number */ function twig_round($value, $precision = 0, $method = 'common') { + $value = (float) $value; + if ('common' === $method) { return round($value, $precision); } @@ -541,7 +560,7 @@ function twig_round($value, $precision = 0, $method = 'common') * Number format filter. * * All of the formatting options can be left null, in that case the defaults will - * be used. Supplying any of the parameters will override the defaults set in the + * be used. Supplying any of the parameters will override the defaults set in the * environment object. * * @param mixed $number A float/int/string of the number to format @@ -572,17 +591,17 @@ function twig_number_format_filter(Environment $env, $number, $decimal = null, $ /** * URL encodes (RFC 3986) a string as a path segment or an array as a query string. * - * @param string|array $url A URL or an array of query parameters + * @param string|array|null $url A URL or an array of query parameters * * @return string The URL encoded value */ function twig_urlencode_filter($url) { if (\is_array($url)) { - return http_build_query($url, '', '&', PHP_QUERY_RFC3986); + return http_build_query($url, '', '&', \PHP_QUERY_RFC3986); } - return rawurlencode($url); + return rawurlencode($url ?? ''); } /** @@ -644,9 +663,7 @@ function twig_slice(Environment $env, $item, $start, $length = null, $preserveKe return \array_slice($item, $start, $length, $preserveKeys); } - $item = (string) $item; - - return (string) mb_substr($item, $start, $length, $env->getCharset()); + return (string) mb_substr((string) $item, $start, $length, $env->getCharset()); } /** @@ -735,14 +752,16 @@ function twig_join_filter($value, $glue = '', $and = null) * {{ "aabbcc"|split('', 2) }} * {# returns [aa, bb, cc] #} * - * @param string $value A string - * @param string $delimiter The delimiter - * @param int $limit The limit + * @param string|null $value A string + * @param string $delimiter The delimiter + * @param int $limit The limit * * @return array The split string as an array */ function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) { + $value = $value ?? ''; + if (\strlen($delimiter) > 0) { return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); } @@ -799,8 +818,8 @@ function twig_get_array_keys_filter($array) $array = $array->getIterator(); } + $keys = []; if ($array instanceof \Iterator) { - $keys = []; $array->rewind(); while ($array->valid()) { $keys[] = $array->key(); @@ -810,7 +829,6 @@ function twig_get_array_keys_filter($array) return $keys; } - $keys = []; foreach ($array as $key => $item) { $keys[] = $key; } @@ -828,8 +846,8 @@ function twig_get_array_keys_filter($array) /** * Reverses a variable. * - * @param array|\Traversable|string $item An array, a \Traversable instance, or a string - * @param bool $preserveKeys Whether to preserve key or not + * @param array|\Traversable|string|null $item An array, a \Traversable instance, or a string + * @param bool $preserveKeys Whether to preserve key or not * * @return mixed The reversed input */ @@ -848,10 +866,10 @@ function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) $charset = $env->getCharset(); if ('UTF-8' !== $charset) { - $item = twig_convert_encoding($string, 'UTF-8', $charset); + $string = twig_convert_encoding($string, 'UTF-8', $charset); } - preg_match_all('/./us', $item, $matches); + preg_match_all('/./us', $string, $matches); $string = implode('', array_reverse($matches[0])); @@ -869,7 +887,7 @@ function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) * * @return array */ -function twig_sort_filter($array, $arrow = null) +function twig_sort_filter(Environment $env, $array, $arrow = null) { if ($array instanceof \Traversable) { $array = iterator_to_array($array); @@ -878,6 +896,8 @@ function twig_sort_filter($array, $arrow = null) } if (null !== $arrow) { + twig_check_arrow_in_sandbox($env, $arrow, 'sort', 'filter'); + uasort($array, $arrow); } else { asort($array); @@ -998,6 +1018,10 @@ function twig_compare($a, $b) /** * Returns a trimmed string. * + * @param string|null $string + * @param string|null $characterMask + * @param string $side + * * @return string * * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') @@ -1010,33 +1034,54 @@ function twig_trim_filter($string, $characterMask = null, $side = 'both') switch ($side) { case 'both': - return trim($string, $characterMask); + return trim($string ?? '', $characterMask); case 'left': - return ltrim($string, $characterMask); + return ltrim($string ?? '', $characterMask); case 'right': - return rtrim($string, $characterMask); + return rtrim($string ?? '', $characterMask); default: throw new RuntimeError('Trimming side must be "left", "right" or "both".'); } } +/** + * Inserts HTML line breaks before all newlines in a string. + * + * @param string|null $string + * + * @return string + */ +function twig_nl2br($string) +{ + return nl2br($string ?? ''); +} + /** * Removes whitespaces between HTML tags. * + * @param string|null $string + * * @return string */ function twig_spaceless($content) { - return trim(preg_replace('/>\s+<', $content)); + return trim(preg_replace('/>\s+<', $content ?? '')); } +/** + * @param string|null $string + * @param string $to + * @param string $from + * + * @return string + */ function twig_convert_encoding($string, $to, $from) { if (!\function_exists('iconv')) { throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); } - return iconv($from, $to, $string); + return iconv($from, $to, $string ?? ''); } /** @@ -1074,47 +1119,60 @@ function twig_length_filter(Environment $env, $thing) /** * Converts a string to uppercase. * - * @param string $string A string + * @param string|null $string A string * * @return string The uppercased string */ function twig_upper_filter(Environment $env, $string) { - return mb_strtoupper($string, $env->getCharset()); + return mb_strtoupper($string ?? '', $env->getCharset()); } /** * Converts a string to lowercase. * - * @param string $string A string + * @param string|null $string A string * * @return string The lowercased string */ function twig_lower_filter(Environment $env, $string) { - return mb_strtolower($string, $env->getCharset()); + return mb_strtolower($string ?? '', $env->getCharset()); +} + +/** + * Strips HTML and PHP tags from a string. + * + * @param string|null $string + * @param string[]|string|null $string + * + * @return string + */ +function twig_striptags($string, $allowable_tags = null) +{ + return strip_tags($string ?? '', $allowable_tags); } /** * Returns a titlecased string. * - * @param string $string A string + * @param string|null $string A string * * @return string The titlecased string */ function twig_title_string_filter(Environment $env, $string) { if (null !== $charset = $env->getCharset()) { - return mb_convert_case($string, MB_CASE_TITLE, $charset); + return mb_convert_case($string ?? '', \MB_CASE_TITLE, $charset); } - return ucwords(strtolower($string)); + return ucwords(strtolower($string ?? '')); } /** * Returns a capitalized string. * - * @param string $string A string + * @param string|null $string A string * * @return string The capitalized string */ @@ -1122,7 +1180,7 @@ function twig_capitalize_string_filter(Environment $env, $string) { $charset = $env->getCharset(); - return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, null, $charset), $charset); + return mb_strtoupper(mb_substr($string ?? '', 0, 1, $charset), $charset).mb_strtolower(mb_substr($string ?? '', 1, null, $charset), $charset); } /** @@ -1583,9 +1641,7 @@ function twig_array_filter(Environment $env, $array, $arrow) throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); } - if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { - throw new RuntimeError('The callable passed to "filter" filter must be a Closure in sandbox mode.'); - } + twig_check_arrow_in_sandbox($env, $arrow, 'filter', 'filter'); if (\is_array($array)) { return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); @@ -1597,9 +1653,7 @@ function twig_array_filter(Environment $env, $array, $arrow) function twig_array_map(Environment $env, $array, $arrow) { - if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { - throw new RuntimeError('The callable passed to the "map" filter must be a Closure in sandbox mode.'); - } + twig_check_arrow_in_sandbox($env, $arrow, 'map', 'filter'); $r = []; foreach ($array as $k => $v) { @@ -1611,9 +1665,7 @@ function twig_array_map(Environment $env, $array, $arrow) function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) { - if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { - throw new RuntimeError('The callable passed to the "reduce" filter must be a Closure in sandbox mode.'); - } + twig_check_arrow_in_sandbox($env, $arrow, 'reduce', 'filter'); if (!\is_array($array)) { if (!$array instanceof \Traversable) { @@ -1625,4 +1677,11 @@ function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) return array_reduce($array, $arrow, $initial); } + +function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type) +{ + if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { + throw new RuntimeError(sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type)); + } +} } diff --git a/src/Extension/EscaperExtension.php b/src/Extension/EscaperExtension.php index 642c64c9157..72795da3b2b 100644 --- a/src/Extension/EscaperExtension.php +++ b/src/Extension/EscaperExtension.php @@ -206,7 +206,7 @@ function twig_escape_filter(Environment $env, $string, $strategy = 'html', $char switch ($strategy) { case 'html': - // see https://secure.php.net/htmlspecialchars + // see https://www.php.net/htmlspecialchars // Using a static variable to avoid initializing the array // each time the function is called. Moving the declaration on the @@ -229,18 +229,18 @@ function twig_escape_filter(Environment $env, $string, $strategy = 'html', $char ]; if (isset($htmlspecialcharsCharsets[$charset])) { - return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); } if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { // cache the lowercase variant for future iterations $htmlspecialcharsCharsets[$charset] = true; - return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); } $string = twig_convert_encoding($string, 'UTF-8', $charset); - $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + $string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); return iconv('UTF-8', $charset, $string); @@ -277,7 +277,7 @@ function twig_escape_filter(Environment $env, $string, $strategy = 'html', $char return $shortMap[$char]; } - $codepoint = mb_ord($char); + $codepoint = mb_ord($char, 'UTF-8'); if (0x10000 > $codepoint) { return sprintf('\u%04X', $codepoint); } diff --git a/src/FileExtensionEscapingStrategy.php b/src/FileExtensionEscapingStrategy.php index 28579c31761..65198bbb649 100644 --- a/src/FileExtensionEscapingStrategy.php +++ b/src/FileExtensionEscapingStrategy.php @@ -41,7 +41,7 @@ public static function guess(string $name) $name = substr($name, 0, -5); } - $extension = pathinfo($name, PATHINFO_EXTENSION); + $extension = pathinfo($name, \PATHINFO_EXTENSION); switch ($extension) { case 'js': diff --git a/src/Lexer.php b/src/Lexer.php index 4fcb223dcc0..9ff028c87d9 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -35,18 +35,18 @@ class Lexer private $positions; private $currentVarBlockLine; - const STATE_DATA = 0; - const STATE_BLOCK = 1; - const STATE_VAR = 2; - const STATE_STRING = 3; - const STATE_INTERPOLATION = 4; - - const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; - const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A'; - const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; - const REGEX_DQ_STRING_DELIM = '/"/A'; - const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; - const PUNCTUATION = '()[]{}?:.,|'; + public const STATE_DATA = 0; + public const STATE_BLOCK = 1; + public const STATE_VAR = 2; + public const STATE_STRING = 3; + public const STATE_INTERPOLATION = 4; + + public const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; + public const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A'; + public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; + public const REGEX_DQ_STRING_DELIM = '/"/A'; + public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + public const PUNCTUATION = '()[]{}?:.,|'; public function __construct(Environment $env, array $options = []) { @@ -165,7 +165,7 @@ public function tokenize(Source $source): TokenStream $this->position = -1; // find all token starts in one go - preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE); + preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \PREG_OFFSET_CAPTURE); $this->positions = $matches; while ($this->cursor < $this->end) { @@ -320,7 +320,7 @@ private function lexExpression(): void // numbers elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) { $number = (float) $match[0]; // floats - if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) { + if (ctype_digit($match[0]) && $number <= \PHP_INT_MAX) { $number = (int) $match[0]; // integers lower than the maximum } $this->pushToken(/* Token::NUMBER_TYPE */ 6, $number); @@ -366,7 +366,7 @@ private function lexExpression(): void private function lexRawData(): void { - if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { + if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source); } @@ -390,7 +390,7 @@ private function lexRawData(): void private function lexComment(): void { - if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { + if (!preg_match($this->regexes['lex_comment'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source); } diff --git a/src/Loader/ArrayLoader.php b/src/Loader/ArrayLoader.php index a6164bb1aa8..5d726c35ac7 100644 --- a/src/Loader/ArrayLoader.php +++ b/src/Loader/ArrayLoader.php @@ -45,7 +45,6 @@ public function setTemplate(string $name, string $template): void public function getSourceContext(string $name): Source { - $name = (string) $name; if (!isset($this->templates[$name])) { throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); } diff --git a/src/Loader/FilesystemLoader.php b/src/Loader/FilesystemLoader.php index e55943d3574..859a898c53c 100644 --- a/src/Loader/FilesystemLoader.php +++ b/src/Loader/FilesystemLoader.php @@ -22,7 +22,7 @@ class FilesystemLoader implements LoaderInterface { /** Identifier of the main namespace. */ - const MAIN_NAMESPACE = '__main__'; + public const MAIN_NAMESPACE = '__main__'; protected $paths = []; protected $cache = []; @@ -37,7 +37,7 @@ class FilesystemLoader implements LoaderInterface public function __construct($paths = [], string $rootPath = null) { $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR; - if (false !== $realPath = realpath($rootPath)) { + if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) { $this->rootPath = $realPath.\DIRECTORY_SEPARATOR; } @@ -51,7 +51,7 @@ public function __construct($paths = [], string $rootPath = null) */ public function getPaths(string $namespace = self::MAIN_NAMESPACE): array { - return isset($this->paths[$namespace]) ? $this->paths[$namespace] : []; + return $this->paths[$namespace] ?? []; } /** @@ -277,7 +277,7 @@ private function isAbsolutePath(string $file): bool && ':' === $file[1] && strspn($file, '/\\', 2, 1) ) - || null !== parse_url($file, PHP_URL_SCHEME) + || null !== parse_url($file, \PHP_URL_SCHEME) ; } } diff --git a/src/Markup.php b/src/Markup.php index c8c5e1a174d..1788acc4f73 100644 --- a/src/Markup.php +++ b/src/Markup.php @@ -35,11 +35,16 @@ public function __toString() /** * @return int */ + #[\ReturnTypeWillChange] public function count() { return mb_strlen($this->content, $this->charset); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->content; diff --git a/src/Node/IncludeNode.php b/src/Node/IncludeNode.php index d01f1fca333..d540d6b23bf 100644 --- a/src/Node/IncludeNode.php +++ b/src/Node/IncludeNode.php @@ -29,7 +29,7 @@ public function __construct(AbstractExpression $expr, ?AbstractExpression $varia $nodes['variables'] = $variables; } - parent::__construct($nodes, ['only' => (bool) $only, 'ignore_missing' => (bool) $ignoreMissing], $lineno, $tag); + parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno, $tag); } public function compile(Compiler $compiler): void diff --git a/src/Node/MacroNode.php b/src/Node/MacroNode.php index cfe38f86f54..7f1b24d5372 100644 --- a/src/Node/MacroNode.php +++ b/src/Node/MacroNode.php @@ -21,7 +21,7 @@ */ class MacroNode extends Node { - const VARARGS_NAME = 'varargs'; + public const VARARGS_NAME = 'varargs'; public function __construct(string $name, Node $body, Node $arguments, int $lineno, string $tag = null) { diff --git a/src/Node/Node.php b/src/Node/Node.php index e974b4971ed..c0558b9afdc 100644 --- a/src/Node/Node.php +++ b/src/Node/Node.php @@ -148,6 +148,7 @@ public function removeNode(string $name): void /** * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->nodes); diff --git a/src/NodeVisitor/EscaperNodeVisitor.php b/src/NodeVisitor/EscaperNodeVisitor.php index fafceb4db83..fe56ea30741 100644 --- a/src/NodeVisitor/EscaperNodeVisitor.php +++ b/src/NodeVisitor/EscaperNodeVisitor.php @@ -196,7 +196,7 @@ private function getEscaperFilter(string $type, Node $node): FilterExpression { $line = $node->getTemplateLine(); $name = new ConstantExpression('escape', $line); - $args = new Node([new ConstantExpression((string) $type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); + $args = new Node([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); return new FilterExpression($node, $name, $args, $line); } diff --git a/src/NodeVisitor/OptimizerNodeVisitor.php b/src/NodeVisitor/OptimizerNodeVisitor.php index c167d263362..7ac75e41ad3 100644 --- a/src/NodeVisitor/OptimizerNodeVisitor.php +++ b/src/NodeVisitor/OptimizerNodeVisitor.php @@ -39,10 +39,10 @@ */ final class OptimizerNodeVisitor implements NodeVisitorInterface { - const OPTIMIZE_ALL = -1; - const OPTIMIZE_NONE = 0; - const OPTIMIZE_FOR = 2; - const OPTIMIZE_RAW_FILTER = 4; + public const OPTIMIZE_ALL = -1; + public const OPTIMIZE_NONE = 0; + public const OPTIMIZE_FOR = 2; + public const OPTIMIZE_RAW_FILTER = 4; private $loops = []; private $loopsTargets = []; diff --git a/src/Parser.php b/src/Parser.php index b8aac05f2a9..d0e77b35796 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -52,7 +52,7 @@ public function __construct(Environment $env) public function getVarName(): string { - return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->stream->getSourceContext()->getCode().$this->varNameSalt++)); + return sprintf('__internal_parse_%d', $this->varNameSalt++); } public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode @@ -193,7 +193,7 @@ public function getBlockStack(): array public function peekBlockStack() { - return isset($this->blockStack[\count($this->blockStack) - 1]) ? $this->blockStack[\count($this->blockStack) - 1] : null; + return $this->blockStack[\count($this->blockStack) - 1] ?? null; } public function popBlockStack(): void diff --git a/src/Profiler/Dumper/BlackfireDumper.php b/src/Profiler/Dumper/BlackfireDumper.php index 3fab5db1fc9..03abe0fa071 100644 --- a/src/Profiler/Dumper/BlackfireDumper.php +++ b/src/Profiler/Dumper/BlackfireDumper.php @@ -28,13 +28,13 @@ public function dump(Profile $profile): string $str = << $values) { - $str .= "{$name}//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; + $str .= "$name//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; } return $str; diff --git a/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php b/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php index bd23b208109..91abee807df 100644 --- a/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php +++ b/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php @@ -28,10 +28,12 @@ final class ProfilerNodeVisitor implements NodeVisitorInterface { private $extensionName; + private $varName; public function __construct(string $extensionName) { $this->extensionName = $extensionName; + $this->varName = sprintf('__internal_%s', hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $extensionName)); } public function enterNode(Node $node, Environment $env): Node @@ -42,33 +44,25 @@ public function enterNode(Node $node, Environment $env): Node public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof ModuleNode) { - $varName = $this->getVarName(); - $node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $varName), $node->getNode('display_start')])); - $node->setNode('display_end', new Node([new LeaveProfileNode($varName), $node->getNode('display_end')])); + $node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $this->varName), $node->getNode('display_start')])); + $node->setNode('display_end', new Node([new LeaveProfileNode($this->varName), $node->getNode('display_end')])); } elseif ($node instanceof BlockNode) { - $varName = $this->getVarName(); $node->setNode('body', new BodyNode([ - new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $varName), + new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $this->varName), $node->getNode('body'), - new LeaveProfileNode($varName), + new LeaveProfileNode($this->varName), ])); } elseif ($node instanceof MacroNode) { - $varName = $this->getVarName(); $node->setNode('body', new BodyNode([ - new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $varName), + new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $this->varName), $node->getNode('body'), - new LeaveProfileNode($varName), + new LeaveProfileNode($this->varName), ])); } return $node; } - private function getVarName(): string - { - return sprintf('__internal_%s', hash('sha256', $this->extensionName)); - } - public function getPriority(): int { return 0; diff --git a/src/Profiler/Profile.php b/src/Profiler/Profile.php index ffbe74f69fc..252ca9b0cf4 100644 --- a/src/Profiler/Profile.php +++ b/src/Profiler/Profile.php @@ -16,10 +16,10 @@ */ final class Profile implements \IteratorAggregate, \Serializable { - const ROOT = 'ROOT'; - const BLOCK = 'block'; - const TEMPLATE = 'template'; - const MACRO = 'macro'; + public const ROOT = 'ROOT'; + public const BLOCK = 'block'; + public const TEMPLATE = 'template'; + public const MACRO = 'macro'; private $template; private $name; diff --git a/src/Template.php b/src/Template.php index 14033ced891..e04bd04a63e 100644 --- a/src/Template.php +++ b/src/Template.php @@ -29,9 +29,9 @@ */ abstract class Template { - const ANY_CALL = 'any'; - const ARRAY_CALL = 'array'; - const METHOD_CALL = 'method'; + public const ANY_CALL = 'any'; + public const ARRAY_CALL = 'array'; + public const METHOD_CALL = 'method'; protected $parent; protected $parents = []; diff --git a/src/Test/IntegrationTestCase.php b/src/Test/IntegrationTestCase.php index d32587436e9..307302bb624 100644 --- a/src/Test/IntegrationTestCase.php +++ b/src/Test/IntegrationTestCase.php @@ -120,7 +120,7 @@ public function getTests($name, $legacyTests = false) $deprecation = $match[3]; $templates = self::parseTemplates($match[4]); $exception = false; - preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, PREG_SET_ORDER); + preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, \PREG_SET_ORDER); } else { throw new \InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); } @@ -186,12 +186,12 @@ protected function doIntegrationTest($file, $message, $condition, $templates, $e // avoid using the same PHP class name for different cases $p = new \ReflectionProperty($twig, 'templateClassPrefix'); $p->setAccessible(true); - $p->setValue($twig, '__TwigTemplate_'.hash('sha256', uniqid(mt_rand(), true), false).'_'); + $p->setValue($twig, '__TwigTemplate_'.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', uniqid(mt_rand(), true), false).'_'); $deprecations = []; try { $prevHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) { - if (E_USER_DEPRECATED === $type) { + if (\E_USER_DEPRECATED === $type) { $deprecations[] = $msg; return true; @@ -255,7 +255,7 @@ protected function doIntegrationTest($file, $message, $condition, $templates, $e protected static function parseTemplates($test) { $templates = []; - preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, PREG_SET_ORDER); + preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, \PREG_SET_ORDER); foreach ($matches as $match) { $templates[($match[1] ?: 'index.twig')] = $match[2]; } diff --git a/src/Test/NodeTestCase.php b/src/Test/NodeTestCase.php index 646402417e1..3b8b2c86c67 100644 --- a/src/Test/NodeTestCase.php +++ b/src/Test/NodeTestCase.php @@ -53,7 +53,7 @@ protected function getEnvironment() protected function getVariableGetter($name, $line = false) { - $line = $line > 0 ? "// line {$line}\n" : ''; + $line = $line > 0 ? "// line $line\n" : ''; return sprintf('%s($context["%s"] ?? null)', $line, $name); } diff --git a/src/Token.php b/src/Token.php index 7640fc50e75..53a6cafc350 100644 --- a/src/Token.php +++ b/src/Token.php @@ -21,20 +21,20 @@ final class Token private $type; private $lineno; - const EOF_TYPE = -1; - const TEXT_TYPE = 0; - const BLOCK_START_TYPE = 1; - const VAR_START_TYPE = 2; - const BLOCK_END_TYPE = 3; - const VAR_END_TYPE = 4; - const NAME_TYPE = 5; - const NUMBER_TYPE = 6; - const STRING_TYPE = 7; - const OPERATOR_TYPE = 8; - const PUNCTUATION_TYPE = 9; - const INTERPOLATION_START_TYPE = 10; - const INTERPOLATION_END_TYPE = 11; - const ARROW_TYPE = 12; + public const EOF_TYPE = -1; + public const TEXT_TYPE = 0; + public const BLOCK_START_TYPE = 1; + public const VAR_START_TYPE = 2; + public const BLOCK_END_TYPE = 3; + public const VAR_END_TYPE = 4; + public const NAME_TYPE = 5; + public const NUMBER_TYPE = 6; + public const STRING_TYPE = 7; + public const OPERATOR_TYPE = 8; + public const PUNCTUATION_TYPE = 9; + public const INTERPOLATION_START_TYPE = 10; + public const INTERPOLATION_END_TYPE = 11; + public const ARROW_TYPE = 12; public function __construct(int $type, $value, int $lineno) { diff --git a/src/TokenParser/ForTokenParser.php b/src/TokenParser/ForTokenParser.php index b6d3b4e41ce..bac8ba2dae8 100644 --- a/src/TokenParser/ForTokenParser.php +++ b/src/TokenParser/ForTokenParser.php @@ -52,12 +52,11 @@ public function parse(Token $token): Node $keyTarget = $targets->getNode(0); $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); $valueTarget = $targets->getNode(1); - $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); } else { $keyTarget = new AssignNameExpression('_key', $lineno); $valueTarget = $targets->getNode(0); - $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); } + $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno, $this->getTag()); } diff --git a/src/Util/DeprecationCollector.php b/src/Util/DeprecationCollector.php index 51345e0bcf4..378b666bdb8 100644 --- a/src/Util/DeprecationCollector.php +++ b/src/Util/DeprecationCollector.php @@ -57,7 +57,7 @@ public function collect(\Traversable $iterator): array { $deprecations = []; set_error_handler(function ($type, $msg) use (&$deprecations) { - if (E_USER_DEPRECATED === $type) { + if (\E_USER_DEPRECATED === $type) { $deprecations[] = $msg; } }); diff --git a/src/Util/TemplateDirIterator.php b/src/Util/TemplateDirIterator.php index c7339fd78c4..3bef14beec3 100644 --- a/src/Util/TemplateDirIterator.php +++ b/src/Util/TemplateDirIterator.php @@ -16,11 +16,19 @@ */ class TemplateDirIterator extends \IteratorIterator { + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function current() { return file_get_contents(parent::current()); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function key() { return (string) parent::key(); diff --git a/tests/Cache/FilesystemTest.php b/tests/Cache/FilesystemTest.php index 478ac355a4c..349869c26e3 100644 --- a/tests/Cache/FilesystemTest.php +++ b/tests/Cache/FilesystemTest.php @@ -23,7 +23,7 @@ class FilesystemTest extends TestCase protected function setUp(): void { - $nonce = hash('sha256', uniqid(mt_rand(), true)); + $nonce = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', uniqid(mt_rand(), true)); $this->classname = '__Twig_Tests_Cache_FilesystemTest_Template_'.$nonce; $this->directory = sys_get_temp_dir().'/twig-test'; $this->cache = new FilesystemCache($this->directory); diff --git a/tests/CompilerTest.php b/tests/CompilerTest.php index d3aa65b730c..35ffad90980 100644 --- a/tests/CompilerTest.php +++ b/tests/CompilerTest.php @@ -22,19 +22,19 @@ public function testReprNumericValueWithLocale() { $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); - $locale = setlocale(LC_NUMERIC, 0); + $locale = setlocale(\LC_NUMERIC, 0); if (false === $locale) { $this->markTestSkipped('Your platform does not support locales.'); } $required_locales = ['fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252']; - if (false === setlocale(LC_NUMERIC, $required_locales)) { + if (false === setlocale(\LC_NUMERIC, $required_locales)) { $this->markTestSkipped('Could not set any of required locales: '.implode(', ', $required_locales)); } $this->assertEquals('1.2', $compiler->repr(1.2)->getSource()); - $this->assertStringContainsString('fr', strtolower(setlocale(LC_NUMERIC, 0))); + $this->assertStringContainsString('fr', strtolower(setlocale(\LC_NUMERIC, 0))); - setlocale(LC_NUMERIC, $locale); + setlocale(\LC_NUMERIC, $locale); } } diff --git a/tests/ErrorTest.php b/tests/ErrorTest.php index b2db4f1afd3..7892be9d07b 100644 --- a/tests/ErrorTest.php +++ b/tests/ErrorTest.php @@ -285,14 +285,14 @@ public function getErroredTemplates() public function testTwigLeakOutputInDebugMode() { - $output = exec(sprintf('%s %s debug', PHP_BINARY, escapeshellarg(__DIR__.'/Fixtures/errors/leak-output.php'))); + $output = exec(sprintf('%s %s debug', \PHP_BINARY, escapeshellarg(__DIR__.'/Fixtures/errors/leak-output.php'))); $this->assertSame('Hello OOPS', $output); } public function testDoesNotTwigLeakOutput() { - $output = exec(sprintf('%s %s', PHP_BINARY, escapeshellarg(__DIR__.'/Fixtures/errors/leak-output.php'))); + $output = exec(sprintf('%s %s', \PHP_BINARY, escapeshellarg(__DIR__.'/Fixtures/errors/leak-output.php'))); $this->assertSame('', $output); } diff --git a/tests/Extension/CoreTest.php b/tests/Extension/CoreTest.php index aae0f0f52ee..29a799b8103 100644 --- a/tests/Extension/CoreTest.php +++ b/tests/Extension/CoreTest.php @@ -263,10 +263,10 @@ public function testCompare($expected, $a, $b) public function testCompareNAN() { - $this->assertSame(1, twig_compare(NAN, 'NAN')); - $this->assertSame(1, twig_compare('NAN', NAN)); - $this->assertSame(1, twig_compare(NAN, 'foo')); - $this->assertSame(1, twig_compare('foo', NAN)); + $this->assertSame(1, twig_compare(\NAN, 'NAN')); + $this->assertSame(1, twig_compare('NAN', \NAN)); + $this->assertSame(1, twig_compare(\NAN, 'foo')); + $this->assertSame(1, twig_compare('foo', \NAN)); } public function provideCompareCases() @@ -312,10 +312,10 @@ public function provideCompareCases() [-1, 42.0, 'abc42.0'], [-1, 0.0, 'abc42.0'], - [0, INF, 'INF'], - [0, -INF, '-INF'], - [0, INF, '1e1000'], - [0, -INF, '-1e1000'], + [0, \INF, 'INF'], + [0, -\INF, '-INF'], + [0, \INF, '1e1000'], + [0, -\INF, '-1e1000'], [-1, 10, 20], [-1, '10', 20], @@ -337,7 +337,7 @@ public function __construct(array $array, array $keys, $allowAccess = false, $ma $this->iterator = new CoreTestIterator($array, $keys, $allowAccess, $maxPosition); } - public function getIterator() + public function getIterator(): \Traversable { return $this->iterator; } @@ -352,7 +352,7 @@ public function __construct(array $array, array $keys, $allowValueAccess = false $this->iterator = new CoreTestIteratorAggregate($array, $keys, $allowValueAccess, $maxPosition); } - public function getIterator() + public function getIterator(): \Traversable { return $this->iterator; } @@ -375,11 +375,15 @@ public function __construct(array $values, array $keys, $allowValueAccess = fals $this->maxPosition = false === $maxPosition ? \count($values) + 1 : $maxPosition; } - public function rewind() + public function rewind(): void { $this->position = 0; } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function current() { if ($this->allowValueAccess) { @@ -389,12 +393,16 @@ public function current() throw new \LogicException('Code should only use the keys, not the values provided by iterator.'); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function key() { return $this->arrayKeys[$this->position]; } - public function next() + public function next(): void { ++$this->position; if ($this->position === $this->maxPosition) { @@ -402,7 +410,7 @@ public function next() } } - public function valid() + public function valid(): bool { return isset($this->arrayKeys[$this->position]); } diff --git a/tests/Extension/EscaperTest.php b/tests/Extension/EscaperTest.php index b7ec4065b3f..cf62dd1e2b6 100644 --- a/tests/Extension/EscaperTest.php +++ b/tests/Extension/EscaperTest.php @@ -182,6 +182,22 @@ public function testJavascriptEscapingConvertsSpecialChars() } } + public function testJavascriptEscapingConvertsSpecialCharsWithInternalEncoding() + { + $twig = new Environment($this->createMock(LoaderInterface::class)); + $previousInternalEncoding = mb_internal_encoding(); + try { + mb_internal_encoding('ISO-8859-1'); + foreach ($this->jsSpecialChars as $key => $value) { + $this->assertEquals($value, twig_escape_filter($twig, $key, 'js'), 'Failed to escape: ' . $key); + } + } finally { + if ($previousInternalEncoding !== false) { + mb_internal_encoding($previousInternalEncoding); + } + } + } + public function testJavascriptEscapingReturnsStringIfZeroLength() { $twig = new Environment($this->createMock(LoaderInterface::class)); diff --git a/tests/Extension/SandboxTest.php b/tests/Extension/SandboxTest.php index 0d9bc0afed7..e365da63280 100644 --- a/tests/Extension/SandboxTest.php +++ b/tests/Extension/SandboxTest.php @@ -390,7 +390,7 @@ public function testSandboxDisabledAfterIncludeFunctionError() public function testSandboxWithNoClosureFilter() { $this->expectException('\Twig\Error\RuntimeError'); - $this->expectExceptionMessage('The callable passed to "filter" filter must be a Closure in sandbox mode in "index" at line 1.'); + $this->expectExceptionMessage('The callable passed to the "filter" filter must be a Closure in sandbox mode in "index" at line 1.'); $twig = $this->getEnvironment(true, ['autoescape' => 'html'], ['index' => <<createMock('\Twig\Loader\LoaderInterface')); $twig->addExtension(new StringLoaderExtension()); - $this->assertSame('something', twig_include($twig, [], twig_template_from_string($twig, "something"))); + $this->assertSame('something', twig_include($twig, [], twig_template_from_string($twig, 'something'))); } } diff --git a/tests/Fixtures/filters/capitalize.test b/tests/Fixtures/filters/capitalize.test new file mode 100644 index 00000000000..bea82ba7b04 --- /dev/null +++ b/tests/Fixtures/filters/capitalize.test @@ -0,0 +1,14 @@ +--TEST-- +"capitalize" filter +--TEMPLATE-- +{{ "super helpful"|capitalize }} +{{ "a"|capitalize }} +*{{ ""|capitalize }}* +*{{ null|capitalize }}* +--DATA-- +return [] +--EXPECT-- +Super helpful +A +** +** diff --git a/tests/Fixtures/filters/convert_encoding.test b/tests/Fixtures/filters/convert_encoding.test index 6f0091f3019..a2a32485508 100644 --- a/tests/Fixtures/filters/convert_encoding.test +++ b/tests/Fixtures/filters/convert_encoding.test @@ -2,7 +2,11 @@ "convert_encoding" filter --TEMPLATE-- {{ "愛していますか?"|convert_encoding('ISO-2022-JP', 'UTF-8')|convert_encoding('UTF-8', 'ISO-2022-JP') }} +*{{ ""|convert_encoding('ISO-2022-JP', 'UTF-8')|convert_encoding('UTF-8', 'ISO-2022-JP') }}* +*{{ null|convert_encoding('ISO-2022-JP', 'UTF-8')|convert_encoding('UTF-8', 'ISO-2022-JP') }}* --DATA-- return [] --EXPECT-- 愛していますか? +** +** diff --git a/tests/Fixtures/filters/escape.test b/tests/Fixtures/filters/escape.test index 131f5b4e72b..b5b3f90f4e3 100644 --- a/tests/Fixtures/filters/escape.test +++ b/tests/Fixtures/filters/escape.test @@ -2,7 +2,11 @@ "escape" filter --TEMPLATE-- {{ "foo
"|e }} +*{{ ""|e }}* +*{{ null|e }}* --DATA-- return [] --EXPECT-- foo <br /> +** +** diff --git a/tests/Fixtures/filters/first.test b/tests/Fixtures/filters/first.test index b19f2eed7bd..6ffc1891116 100644 --- a/tests/Fixtures/filters/first.test +++ b/tests/Fixtures/filters/first.test @@ -6,7 +6,8 @@ {{ '1234'|first }} {{ arr|first }} {{ 'Ä€é'|first }} -{{ ''|first }} +*{{ ''|first }}* +*{{ null|first }}* --DATA-- return ['arr' => new \ArrayObject([1, 2, 3, 4])] --EXPECT-- @@ -15,3 +16,5 @@ return ['arr' => new \ArrayObject([1, 2, 3, 4])] 1 1 Ä +** +** diff --git a/tests/Fixtures/filters/format.test b/tests/Fixtures/filters/format.test index efaf8317a3c..dace78704cc 100644 --- a/tests/Fixtures/filters/format.test +++ b/tests/Fixtures/filters/format.test @@ -2,7 +2,11 @@ "format" filter --TEMPLATE-- {{ string|format(foo, 3) }} +*{{ ""|format(foo, 3) }}* +*{{ null|format(foo, 3) }}* --DATA-- return ['string' => '%s/%d', 'foo' => 'bar'] --EXPECT-- bar/3 +** +** diff --git a/tests/Fixtures/filters/last.test b/tests/Fixtures/filters/last.test index f71896c77f6..2fdc63b3346 100644 --- a/tests/Fixtures/filters/last.test +++ b/tests/Fixtures/filters/last.test @@ -6,7 +6,8 @@ {{ '1234'|last }} {{ arr|last }} {{ 'Ä€é'|last }} -{{ ''|last }} +*{{ ''|last }}* +*{{ null|last }}* --DATA-- return ['arr' => new \ArrayObject([1, 2, 3, 4])] --EXPECT-- @@ -15,3 +16,5 @@ return ['arr' => new \ArrayObject([1, 2, 3, 4])] 4 4 é +** +** diff --git a/tests/Fixtures/filters/length.test b/tests/Fixtures/filters/length.test index eb3f02334f3..6e0cd99275b 100644 --- a/tests/Fixtures/filters/length.test +++ b/tests/Fixtures/filters/length.test @@ -7,6 +7,7 @@ {{ to_string_able|length }} {{ countable|length }} {{ iterator_aggregate|length }} +{{ ""|length }} {{ null|length }} {{ magic|length }} {{ non_countable|length }} @@ -34,6 +35,7 @@ return [ 42 3 0 +0 1 1 2 diff --git a/tests/Fixtures/filters/lower.test b/tests/Fixtures/filters/lower.test new file mode 100644 index 00000000000..ec501e7ca12 --- /dev/null +++ b/tests/Fixtures/filters/lower.test @@ -0,0 +1,12 @@ +--TEST-- +"lower" filter +--TEMPLATE-- +{{ "I like Twig."|lower }} +*{{ ""|lower }}* +*{{ null|lower }}* +--DATA-- +return [] +--EXPECT-- +i like twig. +** +** diff --git a/tests/Fixtures/filters/nl2br.test b/tests/Fixtures/filters/nl2br.test index 524ec45f963..d6849cf0d4f 100644 --- a/tests/Fixtures/filters/nl2br.test +++ b/tests/Fixtures/filters/nl2br.test @@ -3,6 +3,8 @@ --TEMPLATE-- {{ "I like Twig.\nYou will like it too.\n\nEverybody like it!"|nl2br }} {{ text|nl2br }} +*{{ ''|nl2br }}* +*{{ null|nl2br }}* --DATA-- return ['text' => "If you have some HTML\nit will be escaped."] --EXPECT-- @@ -12,3 +14,5 @@ You will like it too.
Everybody like it! If you have some <strong>HTML</strong>
it will be escaped. +** +** diff --git a/tests/Fixtures/filters/number_format.test b/tests/Fixtures/filters/number_format.test index 7f1e2e16a28..3d12a789c46 100644 --- a/tests/Fixtures/filters/number_format.test +++ b/tests/Fixtures/filters/number_format.test @@ -7,6 +7,9 @@ {{ 20.25|number_format(2, ',') }} {{ 1020.25|number_format(2, ',') }} {{ 1020.25|number_format(2, ',', '.') }} +{{ '1020.25'|number_format(2, ',', '.') }} +{{ ''|number_format(2, ',', '.') }} +{{ null|number_format(2, ',', '.') }} --DATA-- return [] --EXPECT-- @@ -16,3 +19,6 @@ return [] 20,25 1,020,25 1.020,25 +1.020,25 +0,00 +0,00 diff --git a/tests/Fixtures/filters/replace.test b/tests/Fixtures/filters/replace.test index 1b9670a17b3..3b430cd5c47 100644 --- a/tests/Fixtures/filters/replace.test +++ b/tests/Fixtures/filters/replace.test @@ -4,9 +4,13 @@ {{ "I liké %this% and %that%."|replace({'%this%': "foo", '%that%': "bar"}) }} {{ 'I like single replace operation only %that%'|replace({'%that%' : '%that%1'}) }} {{ 'I like %this% and %that%.'|replace(traversable) }} +*{{ ''|replace(traversable) }}* +*{{ null|replace(traversable) }}* --DATA-- return ['traversable' => new \ArrayObject(['%this%' => 'foo', '%that%' => 'bar'])] --EXPECT-- I liké foo and bar. I like single replace operation only %that%1 I like foo and bar. +** +** diff --git a/tests/Fixtures/filters/reverse.test b/tests/Fixtures/filters/reverse.test index 904e5839b02..42b4dcc64b9 100644 --- a/tests/Fixtures/filters/reverse.test +++ b/tests/Fixtures/filters/reverse.test @@ -7,6 +7,8 @@ {{ {'a': 'c', 'b': 'a'}|reverse()|join(',') }} {{ {'a': 'c', 'b': 'a'}|reverse(preserveKeys=true)|join(glue=',') }} {{ {'a': 'c', 'b': 'a'}|reverse(preserve_keys=true)|join(glue=',') }} +*{{ ''|reverse }}* +*{{ null|reverse }}* --DATA-- return ['arr' => new \ArrayObject([1, 2, 3, 4])] --EXPECT-- @@ -16,3 +18,5 @@ tnemenèvé4321 a,c a,c a,c +** +** diff --git a/tests/Fixtures/filters/round.test b/tests/Fixtures/filters/round.test index 709237543a4..264b230d19f 100644 --- a/tests/Fixtures/filters/round.test +++ b/tests/Fixtures/filters/round.test @@ -9,6 +9,12 @@ {{ 21.3|round(-1)}} {{ 21.3|round(-1, 'ceil')}} {{ 21.3|round(-1, 'floor')}} +{{ '21.3'|round(-1, 'floor')}} + +{{ ''|round(-1, 'floor')}} +{{ null|round(-1, 'floor')}} +{{ null|round }} +{{ null|round(2, 'ceil') }} --DATA-- return [] --EXPECT-- @@ -20,3 +26,9 @@ return [] 20 30 20 +20 + +0 +0 +0 +0 diff --git a/tests/Fixtures/filters/spaceless.test b/tests/Fixtures/filters/spaceless.test index eadc1d4962a..166a7ea0bce 100644 --- a/tests/Fixtures/filters/spaceless.test +++ b/tests/Fixtures/filters/spaceless.test @@ -2,7 +2,11 @@ "spaceless" filter --TEMPLATE-- {{ "
foo
"|spaceless }} +*{{ ""|spaceless }}* +*{{ null|spaceless }}* --DATA-- return [] --EXPECT--
foo
+** +** diff --git a/tests/Fixtures/filters/split.test b/tests/Fixtures/filters/split.test index 2bd46afa36d..8b2a94d940c 100644 --- a/tests/Fixtures/filters/split.test +++ b/tests/Fixtures/filters/split.test @@ -9,6 +9,8 @@ {{ baz|split('', 2)|join('-') }} {{ foo|split(',', -2)|join('-') }} {{ "hello0world"|split('0')|join('-') }} +*{{ ""|split(',')|join('-') }}* +*{{ null|split(',')|join('-') }}* --DATA-- return ['foo' => "one,two,three,four,five", 'baz' => '12345',] --EXPECT-- @@ -19,4 +21,6 @@ one-two-three,four,five 1-2-3-4-5 12-34-5 one-two-three -hello-world \ No newline at end of file +hello-world +** +** diff --git a/tests/Fixtures/filters/striptags.test b/tests/Fixtures/filters/striptags.test new file mode 100644 index 00000000000..878b90bfa1c --- /dev/null +++ b/tests/Fixtures/filters/striptags.test @@ -0,0 +1,16 @@ +--TEST-- +"striptags" filter +--TEMPLATE-- +{{ "Hello, World!"|striptags }} +{{ text|striptags }} +{{ text|striptags('')|raw }} +*{{ ''|striptags }}* +*{{ null|striptags }}* +--DATA-- +return ['text' => "

Hello, World!

"] +--EXPECT-- +Hello, World! +Hello, World! +Hello, World! +** +** diff --git a/tests/Fixtures/filters/title.test b/tests/Fixtures/filters/title.test new file mode 100644 index 00000000000..6f1c0c3e85f --- /dev/null +++ b/tests/Fixtures/filters/title.test @@ -0,0 +1,12 @@ +--TEST-- +"title" filter +--TEMPLATE-- +{{ "I like Twig."|title }} +*{{ ""|title }}* +*{{ null|title }}* +--DATA-- +return [] +--EXPECT-- +I Like Twig. +** +** diff --git a/tests/Fixtures/filters/trim.test b/tests/Fixtures/filters/trim.test index 432989ff165..141f863572b 100644 --- a/tests/Fixtures/filters/trim.test +++ b/tests/Fixtures/filters/trim.test @@ -10,6 +10,12 @@ {{ "/ foo/"|trim("/", "left") }} {{ "/ foo/"|trim(character_mask="/", side="left") }} {{ " do nothing. "|trim("", "right") }} +*{{ ""|trim }}* +*{{ ""|trim("", "left") }}* +*{{ ""|trim("", "right") }}* +*{{ null|trim }}* +*{{ null|trim("", "left") }}* +*{{ null|trim("", "right") }}* --DATA-- return ['text' => " If you have some HTML it will be escaped. "] --EXPECT-- @@ -22,3 +28,9 @@ xxxI like Twig. foo/ foo/ do nothing. +** +** +** +** +** +** diff --git a/tests/Fixtures/filters/upper.test b/tests/Fixtures/filters/upper.test new file mode 100644 index 00000000000..df415075e0f --- /dev/null +++ b/tests/Fixtures/filters/upper.test @@ -0,0 +1,12 @@ +--TEST-- +"upper" filter +--TEMPLATE-- +{{ "I like Twig."|upper }} +*{{ ""|upper }}* +*{{ null|upper }}* +--DATA-- +return [] +--EXPECT-- +I LIKE TWIG. +** +** diff --git a/tests/Fixtures/filters/urlencode.test b/tests/Fixtures/filters/urlencode.test index ac2369906dd..5db2e2ded9a 100644 --- a/tests/Fixtures/filters/urlencode.test +++ b/tests/Fixtures/filters/urlencode.test @@ -5,6 +5,8 @@ {{ {foo: "bar", number: 3, "spéßi%l": "e%c0d@d", "spa ce": ""}|url_encode|raw }} {{ {}|url_encode|default("default") }} {{ 'spéßi%le%c0d@dspa ce'|url_encode }} +*{{ ''|url_encode }}* +*{{ null|url_encode }}* --DATA-- return [] --EXPECT-- @@ -12,3 +14,5 @@ foo=bar&number=3&sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&spa%20ce= foo=bar&number=3&sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&spa%20ce= default sp%C3%A9%C3%9Fi%25le%25c0d%40dspa%20ce +** +** diff --git a/tests/Fixtures/functions/template_from_string_error.test b/tests/Fixtures/functions/template_from_string_error.test index 900d238bd2b..1132296bc12 100644 --- a/tests/Fixtures/functions/template_from_string_error.test +++ b/tests/Fixtures/functions/template_from_string_error.test @@ -1,8 +1,10 @@ --TEST-- "template_from_string" function +--CONDITION-- +PHP_VERSION_ID >= 80100 --TEMPLATE-- {% include template_from_string("{{ not a Twig template ", "foo.twig") %} --DATA-- return [] --EXCEPTION-- -Twig\Error\SyntaxError: Unclosed "variable" in "foo.twig (string template 4900163d56b1af4b704c6b0afee7f98ba53418ce7a93d37a3af1882735baf9cd)" at line 1. +Twig\Error\SyntaxError: Unclosed "variable" in "foo.twig (string template 85e7b092afbbcd36f11981c2ef8f1569)" at line 1. diff --git a/tests/Fixtures/functions/template_from_string_error_php80.test b/tests/Fixtures/functions/template_from_string_error_php80.test new file mode 100644 index 00000000000..8ebe41f2cda --- /dev/null +++ b/tests/Fixtures/functions/template_from_string_error_php80.test @@ -0,0 +1,10 @@ +--TEST-- +"template_from_string" function +--CONDITION-- +PHP_VERSION_ID < 80100 +--TEMPLATE-- +{% include template_from_string("{{ not a Twig template ", "foo.twig") %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Unclosed "variable" in "foo.twig (string template 4900163d56b1af4b704c6b0afee7f98ba53418ce7a93d37a3af1882735baf9cd)" at line 1. diff --git a/tests/Fixtures/tags/for/objects.test b/tests/Fixtures/tags/for/objects.test index 134fe3fc3e9..1de7ab27d2a 100644 --- a/tests/Fixtures/tags/for/objects.test +++ b/tests/Fixtures/tags/for/objects.test @@ -19,11 +19,13 @@ class ItemsIterator implements \Iterator { protected $values = ['foo' => 'bar', 'bar' => 'foo']; + #[\ReturnTypeWillChange] public function current() { return current($this->values); } + #[\ReturnTypeWillChange] public function key() { return key($this->values); } - public function next() { return next($this->values); } - public function rewind() { return reset($this->values); } - public function valid() { return false !== current($this->values); } + public function next(): void { next($this->values); } + public function rewind(): void { reset($this->values); } + public function valid(): bool { return false !== current($this->values); } } return ['items' => new ItemsIterator()] --EXPECT-- diff --git a/tests/Fixtures/tags/for/objects_countable.test b/tests/Fixtures/tags/for/objects_countable.test index 97f1db0c540..dc215f3a623 100644 --- a/tests/Fixtures/tags/for/objects_countable.test +++ b/tests/Fixtures/tags/for/objects_countable.test @@ -20,12 +20,14 @@ class ItemsIteratorCountable implements \Iterator, \Countable { protected $values = ['foo' => 'bar', 'bar' => 'foo']; + #[\ReturnTypeWillChange] public function current() { return current($this->values); } + #[\ReturnTypeWillChange] public function key() { return key($this->values); } - public function next() { return next($this->values); } - public function rewind() { return reset($this->values); } - public function valid() { return false !== current($this->values); } - public function count() { return count($this->values); } + public function next(): void { next($this->values); } + public function rewind(): void { reset($this->values); } + public function valid(): bool { return false !== current($this->values); } + public function count(): int { return count($this->values); } } return ['items' => new ItemsIteratorCountable()] --EXPECT-- diff --git a/tests/Fixtures/tests/empty.test b/tests/Fixtures/tests/empty.test index ffcd518708d..70111352c7a 100644 --- a/tests/Fixtures/tests/empty.test +++ b/tests/Fixtures/tests/empty.test @@ -25,7 +25,7 @@ return [ 'value_null' => null, 'value_false' => false, 'value_int_zero' => 0, 'array_empty' => [], 'array_not_empty' => [1, 2], 'magically_callable' => new \Twig\Tests\MagicCallStub(), - 'countable_empty' => new \Twig\Tests\CountableStub([]), 'countable_not_empty' => new \Twig\Tests\CountableStub([1, 2]), + 'countable_empty' => new \Twig\Tests\CountableStub(0), 'countable_not_empty' => new \Twig\Tests\CountableStub(2), 'tostring_empty' => new \Twig\Tests\ToStringStub(''), 'tostring_not_empty' => new \Twig\Tests\ToStringStub('0' /* edge case of using "0" as the string */), 'markup_empty' => new \Twig\Markup('', 'UTF-8'), 'markup_not_empty' => new \Twig\Markup('test', 'UTF-8'), 'iterator' => $iter = new \ArrayIterator(['bar', 'foo']), diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 2c5f86debc9..368305251a4 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -60,7 +60,7 @@ function test_foo($value = 'foo') class TwigTestFoo implements \Iterator { - const BAR_NAME = 'bar'; + public const BAR_NAME = 'bar'; public $position = 0; public $array = [1, 2]; @@ -100,27 +100,35 @@ public function strToLower($value) return strtolower($value); } - public function rewind() + public function rewind(): void { $this->position = 0; } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function current() { return $this->array[$this->position]; } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function key() { return 'a'; } - public function next() + public function next(): void { ++$this->position; } - public function valid() + public function valid(): bool { return isset($this->array[$this->position]); } @@ -222,7 +230,7 @@ public function nl2br($value, $sep = '
') { // not secure if $value contains html tags (not only entities) // don't use - return str_replace("\n", "$sep\n", $value); + return str_replace("\n", "$sep\n", $value ?? ''); } public function dynamic_path($element, $item) @@ -328,7 +336,7 @@ public function __construct($count) $this->count = $count; } - public function count() + public function count(): int { return $this->count; } @@ -351,7 +359,7 @@ public function __construct(array $data) $this->data = $data; } - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator($this->data); } @@ -362,27 +370,35 @@ class SimpleIteratorForTesting implements \Iterator private $data = [1, 2, 3, 4, 5, 6, 7]; private $key = 0; + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function current() { return $this->key; } - public function next() + public function next(): void { ++$this->key; } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function key() { return $this->key; } - public function valid() + public function valid(): bool { return isset($this->data[$this->key]); } - public function rewind() + public function rewind(): void { $this->key = 0; } diff --git a/tests/Loader/FilesystemTest.php b/tests/Loader/FilesystemTest.php index bd8100751f0..d3bdc7b7119 100644 --- a/tests/Loader/FilesystemTest.php +++ b/tests/Loader/FilesystemTest.php @@ -203,10 +203,8 @@ public function getArrayInheritanceTests() /** * @dataProvider getArrayInheritanceTests - * - * @param $templateName string Template name with array inheritance */ - public function testArrayInheritance($templateName) + public function testArrayInheritance(string $templateName) { $loader = new FilesystemLoader([]); $loader->addPath(__DIR__.'/Fixtures/inheritance'); diff --git a/tests/TemplateTest.php b/tests/TemplateTest.php index aad7a0a5995..bd26e9d95b7 100644 --- a/tests/TemplateTest.php +++ b/tests/TemplateTest.php @@ -167,9 +167,15 @@ public function testGetAttributeOnArrayWithConfusableKey() $this->assertSame('Zero', $array[false]); $this->assertSame('One', $array[true]); - $this->assertSame('One', $array[1.5]); + if (\PHP_VERSION_ID < 80100) { + // This line will trigger a deprecation warning on PHP 8.1. + $this->assertSame('One', $array[1.5]); + } $this->assertSame('One', $array['1']); - $this->assertSame('MinusOne', $array[-1.5]); + if (\PHP_VERSION_ID < 80100) { + // This line will trigger a deprecation warning on PHP 8.1. + $this->assertSame('MinusOne', $array[-1.5]); + } $this->assertSame('FloatButString', $array['1.5']); $this->assertSame('IntegerButStringWithLeadingZeros', $array['01']); $this->assertSame('EmptyString', $array[null]); @@ -471,21 +477,25 @@ class TemplateArrayAccessObject implements \ArrayAccess '+4' => '+4', ]; - public function offsetExists($name) + public function offsetExists($name) : bool { return \array_key_exists($name, $this->attributes); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function offsetGet($name) { return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null; } - public function offsetSet($name, $value) + public function offsetSet($name, $value) : void { } - public function offsetUnset($name) + public function offsetUnset($name) : void { } } @@ -508,7 +518,7 @@ class TemplateMagicPropertyObject protected $protected = 'protected'; - public function __isset($name) + public function __isset($name): bool { return \array_key_exists($name, $this->attributes); } @@ -521,7 +531,7 @@ public function __get($name) class TemplateMagicPropertyObjectWithException { - public function __isset($key) + public function __isset($key): bool { throw new \Exception('Hey! Don\'t try to isset me!'); } @@ -542,7 +552,7 @@ class TemplatePropertyObject class TemplatePropertyObjectAndIterator extends TemplatePropertyObject implements \IteratorAggregate { - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator(['foo', 'bar']); } @@ -560,21 +570,25 @@ class TemplatePropertyObjectAndArrayAccess extends TemplatePropertyObject implem 'baf' => 'baf', ]; - public function offsetExists($offset) + public function offsetExists($offset) : bool { return \array_key_exists($offset, $this->data); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->offsetExists($offset) ? $this->data[$offset] : 'n/a'; } - public function offsetSet($offset, $value) + public function offsetSet($offset, $value) : void { } - public function offsetUnset($offset) + public function offsetUnset($offset) : void { } } @@ -708,22 +722,26 @@ class TemplateArrayAccess implements \ArrayAccess ]; private $children = []; - public function offsetExists($offset) + public function offsetExists($offset) : bool { return \array_key_exists($offset, $this->children); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->children[$offset]; } - public function offsetSet($offset, $value) + public function offsetSet($offset, $value) : void { $this->children[$offset] = $value; } - public function offsetUnset($offset) + public function offsetUnset($offset) : void { unset($this->children[$offset]); } diff --git a/tests/Util/DeprecationCollectorTest.php b/tests/Util/DeprecationCollectorTest.php index 84d95b0adc6..7b5794d83c4 100644 --- a/tests/Util/DeprecationCollectorTest.php +++ b/tests/Util/DeprecationCollectorTest.php @@ -40,7 +40,7 @@ public function deprec() class Twig_Tests_Util_Iterator implements \IteratorAggregate { - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator([ 'ok.twig' => '{{ foo }}', diff --git a/drupal_test.sh b/tests/drupal_test.sh old mode 100755 new mode 100644 similarity index 100% rename from drupal_test.sh rename to tests/drupal_test.sh