From 0f3fb0b5b0651908488f739068d2310d828437f7 Mon Sep 17 00:00:00 2001 From: Brandon Staab Date: Sat, 14 Feb 2015 21:48:17 -0500 Subject: [PATCH 01/61] Fixed the JSON for composer.json The extra "," is not valid JSON, This is and extremely small issue but I guess someone should point it out. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b085310c..9afece55 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Require the bundle in your composer.json file: ``` { "require": { - "exercise/htmlpurifier-bundle": "*", + "exercise/htmlpurifier-bundle": "*" } } ``` From e97612b5ce8a61d4a230b073099d9d56203c21c5 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Wed, 31 Dec 2014 12:05:13 -0600 Subject: [PATCH 02/61] use PSR-4 autoloading. --- composer.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 16ec5844..9c36b2f4 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,6 @@ } }, "autoload": { - "psr-0": { "Exercise\\HTMLPurifierBundle": "" } - }, - "target-dir": "Exercise/HTMLPurifierBundle" + "psr-4": { "Exercise\\HTMLPurifierBundle\\": "" } + } } From 5220e69480fa62d3aaef9754892c63573dbf0bf9 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Sat, 17 Oct 2015 16:02:57 +0200 Subject: [PATCH 03/61] minor --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e9a55e24..45790fd5 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Require the bundle in your composer.json file: Install the bundle: ``` -$ composer update exercise/htmlpurifier-bundle +$ composer require exercise/htmlpurifier-bundle ``` Register the bundle: From f259a3f32b08500269bda5d12781d2b168d1cb5d Mon Sep 17 00:00:00 2001 From: Christoph Hautzinger Date: Tue, 1 Dec 2015 17:42:29 +0100 Subject: [PATCH 04/61] made bundle ready for symfony3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9c36b2f4..65719a62 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ ], "require": { "php": ">=5.3.2", - "symfony/framework-bundle": "~2.0", + "symfony/framework-bundle": "~2.0|~3.0", "ezyang/htmlpurifier": "~4.0" }, "require-dev": { From 05cd8cc3e8978aa6e0af28f24c02ddce24448f5f Mon Sep 17 00:00:00 2001 From: Alister Bulman Date: Sun, 21 Feb 2016 16:56:47 +0000 Subject: [PATCH 05/61] Run purify to build cache within the CacheWarmer --- .gitignore | 2 ++ CacheWarmer/SerializerCacheWarmer.php | 6 +++++- Resources/config/html_purifier.xml | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ce3aa652..5de5676c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ phpunit.xml +composer.lock +vendor/ diff --git a/CacheWarmer/SerializerCacheWarmer.php b/CacheWarmer/SerializerCacheWarmer.php index 365eb6a4..965321a9 100644 --- a/CacheWarmer/SerializerCacheWarmer.php +++ b/CacheWarmer/SerializerCacheWarmer.php @@ -3,6 +3,7 @@ namespace Exercise\HTMLPurifierBundle\CacheWarmer; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use HTMLPurifier; /** * Cache warmer for creating HTMLPurifier's cache directory. @@ -18,9 +19,10 @@ class SerializerCacheWarmer implements CacheWarmerInterface * * @param array $paths */ - public function __construct(array $paths) + public function __construct(array $paths, HTMLPurifier $htmlPurifier) { $this->paths = $paths; + $this->htmlPurifier = $htmlPurifier; } /** @@ -37,6 +39,8 @@ public function warmUp($cacheDir) throw new \RuntimeException(sprintf('The HTMLPurifier Serializer cache directory "%s" is not writeable for the current system user.', $path)); } } + $this->htmlPurifier->purify('
-2
'); + $this->htmlPurifier->purify('
'); } /** diff --git a/Resources/config/html_purifier.xml b/Resources/config/html_purifier.xml index 63f8bc5a..8143161e 100644 --- a/Resources/config/html_purifier.xml +++ b/Resources/config/html_purifier.xml @@ -15,6 +15,7 @@ %exercise_html_purifier.cache_warmer.serializer.paths% + From ceb90762e0b4af2919f4216af1f32c7a643adb3a Mon Sep 17 00:00:00 2001 From: Alister Bulman Date: Mon, 21 Mar 2016 14:35:55 +0000 Subject: [PATCH 06/61] Issue #22, build with htmlpurifier for tests --- Tests/CacheWarmer/SerializerCacheWarmerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/CacheWarmer/SerializerCacheWarmerTest.php b/Tests/CacheWarmer/SerializerCacheWarmerTest.php index 6697fbd6..6068363d 100644 --- a/Tests/CacheWarmer/SerializerCacheWarmerTest.php +++ b/Tests/CacheWarmer/SerializerCacheWarmerTest.php @@ -8,7 +8,7 @@ class SerializerCacheWarmerTest extends \PHPUnit_Framework_TestCase { public function testShouldBeRequired() { - $cacheWarmer = new SerializerCacheWarmer(array()); + $cacheWarmer = new SerializerCacheWarmer(array(), new \HTMLPurifier()); $this->assertFalse($cacheWarmer->isOptional()); } @@ -20,7 +20,7 @@ public function testShouldCreatePaths() $path = sys_get_temp_dir() . '/' . uniqid('htmlpurifierbundle'); - $cacheWarmer = new SerializerCacheWarmer(array($path)); + $cacheWarmer = new SerializerCacheWarmer(array($path), new \HTMLPurifier()); $cacheWarmer->warmUp(null); $this->assertTrue(is_dir($path)); From cb72bcb1c201813ca034c9b32eb746a5688fbaae Mon Sep 17 00:00:00 2001 From: Alister Bulman Date: Tue, 23 Aug 2016 09:50:33 +0100 Subject: [PATCH 07/61] Add explanations as to what. Fixes issue #22 --- CacheWarmer/SerializerCacheWarmer.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CacheWarmer/SerializerCacheWarmer.php b/CacheWarmer/SerializerCacheWarmer.php index 965321a9..398972da 100644 --- a/CacheWarmer/SerializerCacheWarmer.php +++ b/CacheWarmer/SerializerCacheWarmer.php @@ -6,14 +6,21 @@ use HTMLPurifier; /** - * Cache warmer for creating HTMLPurifier's cache directory. + * Cache warmer for creating HTMLPurifier's cache directory and contents. * + * Run purify() with various contents to have the caches built here, and not + * on first use, as the owning user may be different then, causing problems + * with file ownership when deleting the cached files later. + * * @author Henrik Bjornskov */ class SerializerCacheWarmer implements CacheWarmerInterface { private $paths; + /** @var HTMLPurifier used to build cache within bundle runtime */ + private $htmlPurifier; + /** * Constructor. * @@ -39,6 +46,8 @@ public function warmUp($cacheDir) throw new \RuntimeException(sprintf('The HTMLPurifier Serializer cache directory "%s" is not writeable for the current system user.', $path)); } } + + // build htmlPurifier cache for HTML/CSS & URIs with the other Symfony cache warmups. Fixes issue #22 $this->htmlPurifier->purify('
-2
'); $this->htmlPurifier->purify('
'); } From 28f7a56d8f4bdd492089c447ac96dfbe82997347 Mon Sep 17 00:00:00 2001 From: Victor Bocharsky Date: Thu, 25 Aug 2016 15:29:08 +0300 Subject: [PATCH 08/61] Specify language for code blocks in README It allows to highlight code according to the specified lang and improve readability --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4dec9c10..fd234b4f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This bundle integrates [HTMLPurifier][] into Symfony2. Require the bundle in your composer.json file: -``` +``` json { "require": { "exercise/htmlpurifier-bundle": "*" @@ -20,7 +20,7 @@ Require the bundle in your composer.json file: Install the bundle: -``` +``` bash $ composer require exercise/htmlpurifier-bundle ``` @@ -44,7 +44,7 @@ public function registerBundles() Add HTMLPurifier and this bundle to your `vendor/` directory: -``` +``` bash $ git submodule add git://github.com/Exercise/HTMLPurifierBundle.git vendor/bundles/Exercise/HTMLPurifierBundle $ git submodule add git://github.com/ezyang/htmlpurifier.git vendor/htmlpurifier ``` @@ -54,7 +54,7 @@ $ git submodule add git://github.com/ezyang/htmlpurifier.git vendor/htmlpurifier Register "HTMLPurifier" and the "Exercise" namespace prefix in your project's `autoload.php`: -``` +``` php # app/autoload.php $loader->registerNamespaces(array( @@ -71,7 +71,7 @@ $loader->registerPrefixes(array( Add HTMLPurifierBundle to the `registerBundles()` method of your application kernel: -``` +``` php # app/AppKernel.php public function registerBundles() @@ -90,7 +90,7 @@ If you do not explicitly configure this bundle, an HTMLPurifier service will be defined as `exercise_html_purifier.default`. This behavior is the same as if you had specified the following configuration: -``` +``` yaml # app/config.yml exercise_html_purifier: @@ -102,7 +102,7 @@ The `default` profile is special in that it is used as the configuration for the `exercise_html_purifier.default` service as well as the base configuration for other profiles you might define. -``` +``` yaml # app/config.yml exercise_html_purifier: From 124a6fc4d6f4f0afe395a410fbd5fc601dc035d8 Mon Sep 17 00:00:00 2001 From: Andreas Lemke Date: Sat, 7 Jan 2017 08:52:41 +0100 Subject: [PATCH 09/61] add twig/twig version 2.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 65719a62..09e37bf6 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ }, "require-dev": { "symfony/form": "~2.0", - "twig/twig": "~1.3" + "twig/twig": "~1.3|~2.0" }, "extra": { "branch-alias": { From d91ce32e6fdd3b52bf6ea1efbf50be3123a86a7b Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 20 Feb 2018 11:21:40 +0100 Subject: [PATCH 10/61] Add support for Symfony 4 This also drops support for Symfony <2.7 as these are long EOLed and this allows simplifying the code. --- CacheWarmer/SerializerCacheWarmer.php | 6 +-- .../ExerciseHTMLPurifierExtension.php | 43 ++++++------------- .../ExerciseHTMLPurifierExtensionTest.php | 10 +---- Twig/HTMLPurifierExtension.php | 2 +- composer.json | 6 +-- 5 files changed, 23 insertions(+), 44 deletions(-) diff --git a/CacheWarmer/SerializerCacheWarmer.php b/CacheWarmer/SerializerCacheWarmer.php index 398972da..bc9e9e2b 100644 --- a/CacheWarmer/SerializerCacheWarmer.php +++ b/CacheWarmer/SerializerCacheWarmer.php @@ -11,7 +11,7 @@ * Run purify() with various contents to have the caches built here, and not * on first use, as the owning user may be different then, causing problems * with file ownership when deleting the cached files later. - * + * * @author Henrik Bjornskov */ class SerializerCacheWarmer implements CacheWarmerInterface @@ -33,7 +33,7 @@ public function __construct(array $paths, HTMLPurifier $htmlPurifier) } /** - * @see Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface::warmUp() + * @see \Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface::warmUp() */ public function warmUp($cacheDir) { @@ -53,7 +53,7 @@ public function warmUp($cacheDir) } /** - * @see Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface::isOptional() + * @see \Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface::isOptional() */ public function isOptional() { diff --git a/DependencyInjection/ExerciseHTMLPurifierExtension.php b/DependencyInjection/ExerciseHTMLPurifierExtension.php index ab208509..b3ca00c8 100644 --- a/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -37,42 +37,27 @@ public function load(array $configs, ContainerBuilder $container) foreach ($configs as $name => $config) { $configDefinition = new Definition('%exercise_html_purifier.config.class%'); - - // Handle Symfony >= 2.7 - if (method_exists($configDefinition, 'setFactory')) { - if ('default' === $name) { - $configDefinition - ->setFactory(array('%exercise_html_purifier.config.class%', 'create')) - ->addArgument($config); - } else { - $configDefinition - ->setFactory(array('%exercise_html_purifier.config.class%', 'inherit')) - ->addArgument(new Reference('exercise_html_purifier.config.default')) - ->addMethodCall('loadArray', array($config)); - } - } - // Handle Symfony < 2.7 - else { - $configDefinition->setFactoryClass('%exercise_html_purifier.config.class%'); - - if ('default' === $name) { - $configDefinition - ->setFactoryMethod('create') - ->addArgument($config); - } else { - $configDefinition - ->setFactoryMethod('inherit') - ->addArgument(new Reference('exercise_html_purifier.config.default')) - ->addMethodCall('loadArray', array($config)); - } + + if ('default' === $name) { + $configDefinition + ->setFactory(array('%exercise_html_purifier.config.class%', 'create')) + ->addArgument($config); + } else { + $configDefinition + ->setFactory(array('%exercise_html_purifier.config.class%', 'inherit')) + ->addArgument(new Reference('exercise_html_purifier.config.default')) + ->addMethodCall('loadArray', array($config)); } $configId = 'exercise_html_purifier.config.' . $name; $container->setDefinition($configId, $configDefinition); + $purifierDefinition = new Definition('%exercise_html_purifier.class%', array(new Reference($configId))); + $purifierDefinition->setPublic(true); + $container->setDefinition( 'exercise_html_purifier.' . $name, - new Definition('%exercise_html_purifier.class%', array(new Reference($configId))) + $purifierDefinition ); if (isset($config['Cache.SerializerPath'])) { diff --git a/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php index 71faeb08..26769e00 100644 --- a/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php +++ b/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php @@ -8,12 +8,12 @@ class ExerciseHTMLPurifierExtensionTest extends \PHPUnit_Framework_TestCase { /** - * @var Symfony\Component\DependencyInjection\ContainerBuilder + * @var \Symfony\Component\DependencyInjection\ContainerBuilder */ private $container; /** - * @var Exercise\HTMLPurifierBundle\DependencyInjection\ExerciseHTMLPurifierExtension + * @var \Exercise\HTMLPurifierBundle\DependencyInjection\ExerciseHTMLPurifierExtension */ private $extension; @@ -74,9 +74,6 @@ public function testShouldNotDeepMergeOptions() public function testShouldLoadCustomConfiguration() { - $container = new ContainerBuilder(); - $extension = new ExerciseHTMLPurifierExtension(); - $config = array( 'default' => array( 'AutoFormat.AutoParagraph' => true, @@ -108,9 +105,6 @@ public function testShouldLoadCustomConfiguration() public function testShouldResolveServices() { - $container = new ContainerBuilder; - $extension = new ExerciseHTMLPurifierExtension(); - $config = array( 'simple' => array( 'AutoFormat.Custom' => array('@service_container'), diff --git a/Twig/HTMLPurifierExtension.php b/Twig/HTMLPurifierExtension.php index db404bd1..84ed0f08 100644 --- a/Twig/HTMLPurifierExtension.php +++ b/Twig/HTMLPurifierExtension.php @@ -13,7 +13,7 @@ class HTMLPurifierExtension extends \Twig_Extension /** * Constructor. * - * @param \HTMLPurifier $purifier + * @param ContainerInterface $container */ public function __construct(ContainerInterface $container) { diff --git a/composer.json b/composer.json index 09e37bf6..be3216e9 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "exercise/htmlpurifier-bundle", "type": "symfony-bundle", - "description": "HTMLPurifier integration for your Symfony2 project", + "description": "HTMLPurifier integration for your Symfony project", "keywords": ["htmlpurifier"], "homepage": "https://github.com/Exercise/HTMLPurifierBundle", "license": "MIT", @@ -10,11 +10,11 @@ ], "require": { "php": ">=5.3.2", - "symfony/framework-bundle": "~2.0|~3.0", + "symfony/framework-bundle": "^2.7 || ^3.0 || ^4.0", "ezyang/htmlpurifier": "~4.0" }, "require-dev": { - "symfony/form": "~2.0", + "symfony/form": "^2.7 || ^3.0 || ^4.0", "twig/twig": "~1.3|~2.0" }, "extra": { From 92683fc3c9c7fc557fe04f6e23427052afb63a56 Mon Sep 17 00:00:00 2001 From: Serhii Polishchuk Date: Sat, 28 Jul 2018 14:13:49 +0300 Subject: [PATCH 11/61] Added phpunit, ignore bin dir --- .gitignore | 1 + composer.json | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5de5676c..fdd38a01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/bin/ phpunit.xml composer.lock vendor/ diff --git a/composer.json b/composer.json index be3216e9..67f311e1 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ }, "require-dev": { "symfony/form": "^2.7 || ^3.0 || ^4.0", - "twig/twig": "~1.3|~2.0" + "twig/twig": "~1.3|~2.0", + "phpunit/phpunit": "^7.2" }, "extra": { "branch-alias": { @@ -24,5 +25,8 @@ }, "autoload": { "psr-4": { "Exercise\\HTMLPurifierBundle\\": "" } + }, + "config": { + "bin-dir": "bin" } } From b08e1f6eb82924db195ef747b0f19d264e2b83c4 Mon Sep 17 00:00:00 2001 From: Serhii Polishchuk Date: Sat, 28 Jul 2018 14:14:04 +0300 Subject: [PATCH 12/61] Updated tests --- Tests/CacheWarmer/SerializerCacheWarmerTest.php | 3 ++- .../ExerciseHTMLPurifierExtensionTest.php | 11 ++++++----- Tests/Form/HTMLPurifierTransformerTest.php | 3 ++- Tests/Twig/HTMLPurifierExtensionTest.php | 5 +++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Tests/CacheWarmer/SerializerCacheWarmerTest.php b/Tests/CacheWarmer/SerializerCacheWarmerTest.php index 6068363d..f6ac743b 100644 --- a/Tests/CacheWarmer/SerializerCacheWarmerTest.php +++ b/Tests/CacheWarmer/SerializerCacheWarmerTest.php @@ -3,8 +3,9 @@ namespace Exercise\HTMLPurifierBundle\Tests\CacheWarmer; use Exercise\HTMLPurifierBundle\CacheWarmer\SerializerCacheWarmer; +use PHPUnit\Framework\TestCase; -class SerializerCacheWarmerTest extends \PHPUnit_Framework_TestCase +class SerializerCacheWarmerTest extends TestCase { public function testShouldBeRequired() { diff --git a/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php index 26769e00..4e2d798f 100644 --- a/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php +++ b/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php @@ -3,9 +3,10 @@ namespace Exercise\HTMLPurifierBundle\Tests\DependencyInjection; use Exercise\HTMLPurifierBundle\DependencyInjection\ExerciseHTMLPurifierExtension; +use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; -class ExerciseHTMLPurifierExtensionTest extends \PHPUnit_Framework_TestCase +class ExerciseHTMLPurifierExtensionTest extends TestCase { /** * @var \Symfony\Component\DependencyInjection\ContainerBuilder @@ -137,8 +138,8 @@ private function assertConfigDefinition($name, array $config) $definition = $this->container->getDefinition('exercise_html_purifier.config.' . $name); $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getClass()); - $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getFactoryClass()); - $this->assertEquals('inherit', $definition->getFactoryMethod()); + $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getFactory()[0]); + $this->assertEquals('inherit', $definition->getFactory()[1]); $this->assertEquals(1, count($definition->getArguments())); $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $definition->getArgument(0)); @@ -161,8 +162,8 @@ private function assertDefaultConfigDefinition(array $config) $definition = $this->container->getDefinition('exercise_html_purifier.config.default'); $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getClass()); - $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getFactoryClass()); - $this->assertEquals('create', $definition->getFactoryMethod()); + $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getFactory()[0]); + $this->assertEquals('create', $definition->getFactory()[1]); $this->assertEquals(array($config), $definition->getArguments()); } diff --git a/Tests/Form/HTMLPurifierTransformerTest.php b/Tests/Form/HTMLPurifierTransformerTest.php index b1189166..44aa0197 100644 --- a/Tests/Form/HTMLPurifierTransformerTest.php +++ b/Tests/Form/HTMLPurifierTransformerTest.php @@ -3,8 +3,9 @@ namespace Exercise\HTMLPurifierBundle\Tests\Form; use Exercise\HTMLPurifierBundle\Form\HTMLPurifierTransformer; +use PHPUnit\Framework\TestCase; -class HTMLPurifierTransformerTest extends \PHPUnit_Framework_TestCase +class HTMLPurifierTransformerTest extends TestCase { public function testShouldPurifyOnlyDuringReverseTransform() { diff --git a/Tests/Twig/HTMLPurifierExtensionTest.php b/Tests/Twig/HTMLPurifierExtensionTest.php index 7103f0d8..10aa65e3 100644 --- a/Tests/Twig/HTMLPurifierExtensionTest.php +++ b/Tests/Twig/HTMLPurifierExtensionTest.php @@ -3,8 +3,9 @@ namespace Exercise\HTMLPurifierBundle\Tests\Twig; use Exercise\HTMLPurifierBundle\Twig\HTMLPurifierExtension; +use PHPUnit\Framework\TestCase; -class HTMLPurifierExtensionTest extends \PHPUnit_Framework_TestCase +class HTMLPurifierExtensionTest extends TestCase { /** * @dataProvider providePurifierProfiles @@ -23,7 +24,7 @@ public function testPurifyFilter($profile) ->with($input) ->will($this->returnValue($purifiedInput)); - $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container = $this->createMock('Symfony\Component\DependencyInjection\ContainerInterface'); $container->expects($this->once()) ->method('get') From 0307dcc0b6bd66d8523327449093a65bb95910e7 Mon Sep 17 00:00:00 2001 From: Serhii Polishchuk Date: Sat, 28 Jul 2018 16:16:14 +0300 Subject: [PATCH 13/61] Remove bin directory --- .gitignore | 1 - composer.json | 3 --- 2 files changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index fdd38a01..5de5676c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/bin/ phpunit.xml composer.lock vendor/ diff --git a/composer.json b/composer.json index 67f311e1..9c9accf5 100644 --- a/composer.json +++ b/composer.json @@ -25,8 +25,5 @@ }, "autoload": { "psr-4": { "Exercise\\HTMLPurifierBundle\\": "" } - }, - "config": { - "bin-dir": "bin" } } From 5d96bbcb10ece97027e04a1de9f4ecb03b7beb53 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Tue, 12 Dec 2017 20:25:13 +0100 Subject: [PATCH 14/61] Upgraded to v2 - Require PHP 5.5 or 7.0 and Symfony 3.4 minimum - Refactored extension and enabled autowiring - [BC Break] Removed classes parameters - [BC Break] Removed the form data transformer - added a new text form type extension with a purifier listener to purify submitted data in all text based fields, using opt-in and custom profile thanks to dedicated options - added a new "exercise.html_purifier" tag to make custom purifier implementations available as profile through form options and Twig filter - added a purifiers registry to lazy load purifiers everywhere - added a Twig HTMLPurifierRuntime for better performances - upgraded the LICENSE and README files --- CacheWarmer/SerializerCacheWarmer.php | 17 +- .../Compiler/HTMLPurifierPass.php | 56 +++++ DependencyInjection/Configuration.php | 3 + .../ExerciseHTMLPurifierExtension.php | 92 ++++--- ExerciseHTMLPurifierBundle.php | 9 + Form/HTMLPurifierTransformer.php | 36 --- Form/Listener/HTMLPurifierListener.php | 56 +++++ .../HTMLPurifierTextTypeExtension.php | 72 ++++++ HTMLPurifiersRegistry.php | 31 +++ HTMLPurifiersRegistryInterface.php | 20 ++ LICENSE | 19 ++ README.md | 224 +++++++++--------- Resources/config/html_purifier.xml | 30 ++- .../Compiler/HTMLPurifierPassTest.php | 80 +++++++ .../ExerciseHTMLPurifierExtensionTest.php | 177 +++++++------- Tests/Form/HTMLPurifierTransformerTest.php | 29 --- .../Listener/HTMLPurifierListenerTest.php | 83 +++++++ .../HTMLPurifierTextTypeExtensionTest.php | 127 ++++++++++ Tests/HTMLPurifiersRegistryTest.php | 69 ++++++ ...onTest.php => HTMLPurifierRuntimeTest.php} | 22 +- Twig/HTMLPurifierExtension.php | 65 +---- Twig/HTMLPurifierRuntime.php | 43 ++++ composer.json | 17 +- 23 files changed, 961 insertions(+), 416 deletions(-) create mode 100644 DependencyInjection/Compiler/HTMLPurifierPass.php delete mode 100644 Form/HTMLPurifierTransformer.php create mode 100644 Form/Listener/HTMLPurifierListener.php create mode 100644 Form/TypeExtension/HTMLPurifierTextTypeExtension.php create mode 100644 HTMLPurifiersRegistry.php create mode 100644 HTMLPurifiersRegistryInterface.php create mode 100644 LICENSE create mode 100644 Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php delete mode 100644 Tests/Form/HTMLPurifierTransformerTest.php create mode 100644 Tests/Form/Listener/HTMLPurifierListenerTest.php create mode 100644 Tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php create mode 100644 Tests/HTMLPurifiersRegistryTest.php rename Tests/Twig/{HTMLPurifierExtensionTest.php => HTMLPurifierRuntimeTest.php} (59%) create mode 100644 Twig/HTMLPurifierRuntime.php diff --git a/CacheWarmer/SerializerCacheWarmer.php b/CacheWarmer/SerializerCacheWarmer.php index bc9e9e2b..bee1fd53 100644 --- a/CacheWarmer/SerializerCacheWarmer.php +++ b/CacheWarmer/SerializerCacheWarmer.php @@ -3,7 +3,6 @@ namespace Exercise\HTMLPurifierBundle\CacheWarmer; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; -use HTMLPurifier; /** * Cache warmer for creating HTMLPurifier's cache directory and contents. @@ -17,23 +16,20 @@ class SerializerCacheWarmer implements CacheWarmerInterface { private $paths; - - /** @var HTMLPurifier used to build cache within bundle runtime */ private $htmlPurifier; /** - * Constructor. - * - * @param array $paths + * @param string[] $paths + * @param \HTMLPurifier $htmlPurifier Used to build cache within bundle runtime */ - public function __construct(array $paths, HTMLPurifier $htmlPurifier) + public function __construct(array $paths, \HTMLPurifier $htmlPurifier) { $this->paths = $paths; $this->htmlPurifier = $htmlPurifier; } /** - * @see \Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface::warmUp() + * {@inheritdoc} */ public function warmUp($cacheDir) { @@ -47,13 +43,14 @@ public function warmUp($cacheDir) } } - // build htmlPurifier cache for HTML/CSS & URIs with the other Symfony cache warmups. Fixes issue #22 + // build htmlPurifier cache for HTML/CSS & URIs with the other Symfony cache warmups. + // see https://github.com/Exercise/HTMLPurifierBundle/issues/22 $this->htmlPurifier->purify('
-2
'); $this->htmlPurifier->purify('
'); } /** - * @see \Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface::isOptional() + * {@inheritdoc} */ public function isOptional() { diff --git a/DependencyInjection/Compiler/HTMLPurifierPass.php b/DependencyInjection/Compiler/HTMLPurifierPass.php new file mode 100644 index 00000000..ce744e84 --- /dev/null +++ b/DependencyInjection/Compiler/HTMLPurifierPass.php @@ -0,0 +1,56 @@ +hasAlias(HTMLPurifiersRegistryInterface::class)) { + return; + } + + try { + $registry = $container->findDefinition(HTMLPurifiersRegistryInterface::class); + } catch (ServiceNotFoundException $e) { + return; + } + + $purifiers = []; + + foreach ($container->findTaggedServiceIds(self::PURIFIER_TAG) as $id => $tags) { + if (empty($tags[0]['profile'])) { + throw new InvalidConfigurationException(sprintf('Tag "%s" must define a "profile" attribute.', self::PURIFIER_TAG)); + } + + $profile = $tags[0]['profile']; + $purifier = $container->getDefinition($id); + + if (empty($purifier->getArguments())) { + $configId = "exercise_html_purifier.config.$profile"; + $config = $container->hasDefinition($configId) ? $configId : 'exercise_html_purifier.config.default'; + + $purifier->addArgument(new Reference($config)); + } + + $purifiers[$profile] = new Reference($id); + } + + $registry->setArguments([ + ServiceLocatorTagPass::register($container, $purifiers), + ]); + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index dc068210..7696c2f6 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -7,6 +7,9 @@ class Configuration implements ConfigurationInterface { + /** + * {@inheritdoc} + */ public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); diff --git a/DependencyInjection/ExerciseHTMLPurifierExtension.php b/DependencyInjection/ExerciseHTMLPurifierExtension.php index b3ca00c8..a85a0e62 100644 --- a/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -2,19 +2,24 @@ namespace Exercise\HTMLPurifierBundle\DependencyInjection; +use Exercise\HTMLPurifierBundle\DependencyInjection\Compiler\HTMLPurifierPass; +use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistry; +use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\DependencyInjection\ContainerInterface; class ExerciseHTMLPurifierExtension extends Extension { + /** + * {@inheritdoc} + */ public function load(array $configs, ContainerBuilder $container) { $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('html_purifier.xml'); /* Prepend the default configuration. This cannot be defined within the @@ -25,77 +30,62 @@ public function load(array $configs, ContainerBuilder $container) * configuration (relying on canBeUnset() on the prototype node) or * setting the "Cache.SerializerPath" option to null. */ - array_unshift($configs, array( - 'default' => array( + array_unshift($configs, [ + 'default' => [ 'Cache.SerializerPath' => '%kernel.cache_dir%/htmlpurifier', - ), - )); + ], + ]); $configs = $this->processConfiguration(new Configuration(), $configs); - $configs = array_map(array($this, 'resolveServices'), $configs); - $paths = array(); + + $serializerPaths = []; foreach ($configs as $name => $config) { - $configDefinition = new Definition('%exercise_html_purifier.config.class%'); + $configId = "exercise_html_purifier.config.$name"; + $configDefinition = $container->register($configId, \HTMLPurifier_Config::class) + ->setPublic(false) + ; if ('default' === $name) { $configDefinition - ->setFactory(array('%exercise_html_purifier.config.class%', 'create')) - ->addArgument($config); + ->setFactory([\HTMLPurifier_Config::class, 'create']) + ->addArgument($config) + ; } else { $configDefinition - ->setFactory(array('%exercise_html_purifier.config.class%', 'inherit')) + ->setFactory([\HTMLPurifier_Config::class, 'inherit']) ->addArgument(new Reference('exercise_html_purifier.config.default')) - ->addMethodCall('loadArray', array($config)); + ->addMethodCall('loadArray', [$config]) + ; } - $configId = 'exercise_html_purifier.config.' . $name; - $container->setDefinition($configId, $configDefinition); - - $purifierDefinition = new Definition('%exercise_html_purifier.class%', array(new Reference($configId))); - $purifierDefinition->setPublic(true); - - $container->setDefinition( - 'exercise_html_purifier.' . $name, - $purifierDefinition - ); + $container->register("exercise_html_purifier.$name", \HTMLPurifier::class) + ->addArgument(new Reference($configId)) + ->addTag(HTMLPurifierPass::PURIFIER_TAG, ['profile' => $name]) + ; if (isset($config['Cache.SerializerPath'])) { - $paths[] = $config['Cache.SerializerPath']; + $serializerPaths[] = $config['Cache.SerializerPath']; } } - $container->setParameter('exercise_html_purifier.cache_warmer.serializer.paths', array_unique($paths)); + $container->register('exercise_html_purifier.purifiers_registry', HTMLPurifiersRegistry::class) + ->setPublic(false) + ; + $container->setAlias(HTMLPurifiersRegistryInterface::class, 'exercise_html_purifier.purifiers_registry') + ->setPublic(false) + ; + $container->setAlias(\HTMLPurifier::class, 'exercise_html_purifier.default') + ->setPublic(false) + ; + $container->setParameter('exercise_html_purifier.cache_warmer.serializer.paths', array_unique($serializerPaths)); } + /** + * {@inheritdoc} + */ public function getAlias() { return 'exercise_html_purifier'; } - - private function resolveServices($value) - { - if (is_array($value)) { - $value = array_map(array($this, 'resolveServices'), $value); - } else if (is_string($value) && 0 === strpos($value, '@')) { - if (0 === strpos($value, '@?')) { - $value = substr($value, 2); - $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; - } else { - $value = substr($value, 1); - $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; - } - - if ('=' === substr($value, -1)) { - $value = substr($value, 0, -1); - $strict = false; - } else { - $strict = true; - } - - $value = new Reference($value, $invalidBehavior, $strict); - } - - return $value; - } } diff --git a/ExerciseHTMLPurifierBundle.php b/ExerciseHTMLPurifierBundle.php index 1cb01644..31966ee9 100644 --- a/ExerciseHTMLPurifierBundle.php +++ b/ExerciseHTMLPurifierBundle.php @@ -2,8 +2,17 @@ namespace Exercise\HTMLPurifierBundle; +use Exercise\HTMLPurifierBundle\DependencyInjection\Compiler\HTMLPurifierPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class ExerciseHTMLPurifierBundle extends Bundle { + /** + * {@inheritdoc} + */ + public function build(ContainerBuilder $container) + { + $container->addCompilerPass(new HTMLPurifierPass()); + } } diff --git a/Form/HTMLPurifierTransformer.php b/Form/HTMLPurifierTransformer.php deleted file mode 100644 index 31c486e4..00000000 --- a/Form/HTMLPurifierTransformer.php +++ /dev/null @@ -1,36 +0,0 @@ -purifier = $purifier; - } - - /** - * @see Symfony\Component\Form\DataTransformerInterface::transform() - */ - public function transform($value) - { - return $value; - } - - /** - * @see Symfony\Component\Form\DataTransformerInterface::reverseTransform() - */ - public function reverseTransform($value) - { - return $this->purifier->purify($value); - } -} diff --git a/Form/Listener/HTMLPurifierListener.php b/Form/Listener/HTMLPurifierListener.php new file mode 100644 index 00000000..4ff2fabc --- /dev/null +++ b/Form/Listener/HTMLPurifierListener.php @@ -0,0 +1,56 @@ +registry = $registry; + $this->profile = $profile; + } + + public function purifySubmittedData(FormEvent $event) + { + if (!is_scalar($data = $event->getData())) { + // Hope there is a view transformer, otherwise an error might happen + return; // because we don't want to handle it here + } + + if (0 === strlen($submittedData = trim($data))) { + return; + } + + $event->setData($this->getPurifier()->purify($submittedData)); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + FormEvents::PRE_SUBMIT => ['purifySubmittedData', /* as soon as possible */ 1000000], + ]; + } + + /** + * @return \HTMLPurifier + */ + private function getPurifier() + { + return $this->registry->get($this->profile); + } +} diff --git a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php new file mode 100644 index 00000000..7f6d1aff --- /dev/null +++ b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php @@ -0,0 +1,72 @@ +purifiersRegistry = $registry; + } + + /** + * {@inheritdoc} + */ + public function getExtendedType() + { + return TextType::class; + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefaults([ + 'purify_html' => false, + 'purify_html_profile' => 'default', + ]) + ->setAllowedTypes('purify_html', 'bool') + ->setAllowedTypes('purify_html_profile', 'string') + ->setNormalizer('purify_html_profile', function (Options $options, $profile) { + if (!$options['purify_html']) { + return null; + } + + if ($this->purifiersRegistry->has($profile)) { + return $profile; + } + + throw new InvalidOptionsException(sprintf('The profile "%s" is not registered.', $profile)); + }) + ->setNormalizer('trim', function (Options $options, $trim) { + // trim is done in the HTMLPurifierListener + return $options['purify_html'] ? false : $trim; + }) + ; + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + if ($options['purify_html']) { + $builder->addEventSubscriber( + new HTMLPurifierListener($this->purifiersRegistry, $options['purify_html_profile']) + ); + } + } +} diff --git a/HTMLPurifiersRegistry.php b/HTMLPurifiersRegistry.php new file mode 100644 index 00000000..a96917ce --- /dev/null +++ b/HTMLPurifiersRegistry.php @@ -0,0 +1,31 @@ +purifiersLocator = $purifiersLocator; + } + + /** + * {@inheritdoc} + */ + public function has($profile) + { + return $this->purifiersLocator->has($profile); + } + + /** + * {@inheritdoc} + */ + public function get($profile) + { + return $this->purifiersLocator->get($profile); + } +} diff --git a/HTMLPurifiersRegistryInterface.php b/HTMLPurifiersRegistryInterface.php new file mode 100644 index 00000000..06136d49 --- /dev/null +++ b/HTMLPurifiersRegistryInterface.php @@ -0,0 +1,20 @@ +registerNamespaces(array( - 'Exercise' => __DIR__ . '/../vendor/bundles', -)); - -$loader->registerPrefixes(array( - 'HTMLPurifier' => __DIR__ . '/../vendor//htmlpurifier/library', -)); -``` - -### Application Kernel - -Add HTMLPurifierBundle to the `registerBundles()` method of your application -kernel: - -``` php -# app/AppKernel.php - -public function registerBundles() -{ - return array( - // ... - new Exercise\HTMLPurifierBundle\ExerciseHTMLPurifierBundle(), - // ... - ); -} -``` - -## Configuration +## Configuration in Symfony 3 without Symfony Flex If you do not explicitly configure this bundle, an HTMLPurifier service will be defined as `exercise_html_purifier.default`. This behavior is the same as if you had specified the following configuration: -``` yaml +```yaml # app/config.yml exercise_html_purifier: @@ -102,7 +56,7 @@ The `default` profile is special in that it is used as the configuration for the `exercise_html_purifier.default` service as well as the base configuration for other profiles you might define. -``` yaml +```yaml # app/config.yml exercise_html_purifier: @@ -122,84 +76,91 @@ option to suppress the default path. [configuration documentation]: http://htmlpurifier.org/live/configdoc/plain.html -## Cache Warming ## +## Configuration using Symfony Flex + +If you do not explicitly configure this bundle, an HTMLPurifier service will be +defined as `exercise_html_purifier.default`. This behavior is the same as if you +had specified the following configuration: + +```yaml +# config/packages/exercise_html_purifier.yaml + +exercise_html_purifier: + default: + Cache.SerializerPath: '%kernel.cache_dir%/htmlpurifier' +``` + +The `default` profile is special in that it is used as the configuration for the +`exercise_html_purifier.default` service as well as the base configuration for +other profiles you might define. + +```yaml +# config/packages/exercise_html_purifier.yaml -When a path is supplied for HTMLPurifier's `Cache.SerializerPath` configuration -option, an error is raised if the directory is not writable. This bundle defines -a cache warmer service that will collect all `Cache.SerializerPath` options and -ensure those directories exist and are writeable. +exercise_html_purifier: + default: + Cache.SerializerPath: '%kernel.cache_dir%/htmlpurifier' + custom: + Core.Encoding: 'ISO-8859-1' +``` + +## Autowiring + +By default type hinting `\HtmlPurifier` in your services will autowire +the `exercise_html_purifier.default` service. +To override it and use your own config as default autowired services just add +this in you `app/config/services.yml` or `config/services.yaml`: + +```yaml +services: + # ... + + \HTMLPurifier: + alias: exercise_html_purifier.custom + + # or the equivalent as of Symfony 3.3 + \HTMLPurifier: '@exercise_html_purifier.custom' +``` -## Form Data Transformer +## Form Type Extension -This bundles provides a data transformer class for filtering form fields with -HTMLPurifier. Purification is done during the `reverseTransform()` method, which -means that client data will be filtered during binding to the form. +This bundles provides a form type extension for filtering form fields with +HTMLPurifier. Purification is done during the PRE_SUBMIT event, which +means that client data will be filtered before binding to the form. The following example demonstrates one possible way to integrate an HTMLPurifier transformer into a form by way of a custom field type: -``` php +```php purifierTransformer = $purifierTransformer; - } - public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->addViewTransformer($this->purifierTransformer); - } - - public function getParent() - { - return 'textarea'; - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'compound' => false, - )); - } - - public function getName() - { - return 'purified_textarea'; + $builder + ->add('content', TextareaType::class, ['purify_html' => 'true']) // will use default profile + ->add('sneek_peak', TextType::class, ['purify_html' => 'true', 'purify_html_profile' => 'sneak_peak']) + // ... + ; } + + // ... } ``` -Then define both the field type and transformer in the service container: +Every type extending `TextType` (i.e: `TextareaType`) inherit these options. +It also means that if you use a type such as [CKEditorType][], you will benefit +from these options without configuring anything. -``` xml - - - - - - - - - - -``` - -Additional documentation on data transformers may be found in the -[Symfony2 documentation][]. - - [Symfony2 documentation]: http://symfony.com/doc/current/cookbook/form/data_transformers.html + [CKEDitorType]: https://github.com/egeloen/IvoryCKEditorBundle/blob/master/Form/Type/CKEditorType.php#L570 ## Twig Filter @@ -214,3 +175,42 @@ as follows: {# Filters text's value through the "custom" HTMLPurifier service #} {{ text|purify('custom') }} ``` + +## Purifiers Registry + +A `Exercise\HtmlPurifierBundle\HtmlPurifiersRegistry` class is registered by default +as a service. To add your custom instance of purifier, and make it available to +the form type and Twig extensions through its profile name, you can use the tag +`exercise.html_purifier` as follow: + +```yaml +# config/services.yaml + +services: + # ... + + App\HtmlPurifier\CustomPurifier: + tags: + - name: exercise.html_purifier + profile: custom +``` + +Now your purifier can be used when: + +```php +// In a form type +$builder + ->add('content', TextareaType::class, [ + 'purify_html' => 'true', + 'purify_html_profile' => 'custom', + ]) + // ... +``` + +```jinja +{# in a template #} +{{ html_string|purify('custom') }} +``` + +Your class will inherit the default config or the one from the same profile +used in the tag. diff --git a/Resources/config/html_purifier.xml b/Resources/config/html_purifier.xml index 8143161e..d7934a56 100644 --- a/Resources/config/html_purifier.xml +++ b/Resources/config/html_purifier.xml @@ -2,26 +2,24 @@ - - - HTMLPurifier - HTMLPurifier_Config - Exercise\HTMLPurifierBundle\CacheWarmer\SerializerCacheWarmer - Exercise\HTMLPurifierBundle\Twig\HTMLPurifierExtension - - - - - - %exercise_html_purifier.cache_warmer.serializer.paths% - - + + %exercise_html_purifier.cache_warmer.serializer.paths% + - - + + + + + + + + + + + diff --git a/Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php b/Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php new file mode 100644 index 00000000..870cf009 --- /dev/null +++ b/Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php @@ -0,0 +1,80 @@ +createMock(ContainerBuilder::class); + $container->expects($this->once()) + ->method('hasAlias') + ->with(HTMLPurifiersRegistryInterface::class) + ->willReturn(false) + ; + $container->expects($this->never()) + ->method('findDefinition') + ; + + $pass = new HTMLPurifierPass(); + + $pass->process($container); + } + + public function testProcess() + { + $container = new ContainerBuilder(); + $purifier = $container->register(DummyPurifier::class) + ->addTag('exercise.html_purifier', ['profile' => 'test']) + ; + $registry = $container->register('exercise_html_purifier.purifiers_registry', HTMLPurifiersRegistry::class); + + $container->setAlias(HTMLPurifiersRegistryInterface::class, 'exercise_html_purifier.purifiers_registry'); + + $pass = new HTMLPurifierPass(); + $pass->process($container); + + $this->assertInstanceOf(Reference::class, $config = $purifier->getArgument(0)); + $this->assertSame('exercise_html_purifier.config.default', (string) $config); + $this->assertInstanceOf(Definition::class, $locator = $container->findDefinition($registry->getArgument(0))); + $this->assertArrayHasKey('test', $map = $locator->getArgument(0)); + $this->assertInstanceOf(ServiceClosureArgument::class, $map['test']); + $this->assertSame(DummyPurifier::class, (string) $map['test']->getValues()[0]); + } + + public function testProcessDoNothingIfRegistryIsNotDefined() + { + $container = $this->createMock(ContainerBuilder::class); + $container + ->expects($this->once()) + ->method('hasAlias') + ->with(HTMLPurifiersRegistryInterface::class) + ->willReturn(true) + ; + $container + ->expects($this->once()) + ->method('findDefinition') + ->with(HTMLPurifiersRegistryInterface::class) + ->willThrowException($this->createMock(ServiceNotFoundException::class)) + ; + $container + ->expects($this->never()) + ->method('findTaggedServiceIds') + ; + + $pass = new HTMLPurifierPass(); + $pass->process($container); + } +} + +class DummyPurifier extends \HTMLPurifier {} diff --git a/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php index 4e2d798f..c5b3b642 100644 --- a/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php +++ b/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php @@ -2,131 +2,120 @@ namespace Exercise\HTMLPurifierBundle\Tests\DependencyInjection; +use Exercise\HTMLPurifierBundle\DependencyInjection\Compiler\HTMLPurifierPass; use Exercise\HTMLPurifierBundle\DependencyInjection\ExerciseHTMLPurifierExtension; +use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistry; +use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; class ExerciseHTMLPurifierExtensionTest extends TestCase { /** - * @var \Symfony\Component\DependencyInjection\ContainerBuilder + * @var ContainerBuilder */ private $container; /** - * @var \Exercise\HTMLPurifierBundle\DependencyInjection\ExerciseHTMLPurifierExtension + * @var ExerciseHTMLPurifierExtension */ private $extension; + /** + * @var array + */ private $defaultConfig; public function setUp() { $this->container = new ContainerBuilder(); $this->extension = new ExerciseHTMLPurifierExtension(); - - $this->defaultConfig = array( + $this->defaultConfig = [ 'Cache.SerializerPath' => '%kernel.cache_dir%/htmlpurifier', - ); + ]; } public function testShouldLoadDefaultConfiguration() { - $this->extension->load(array(), $this->container); + $this->extension->load([], $this->container); $this->assertDefaultConfigDefinition($this->defaultConfig); - $this->assertCacheWarmerSerializerPaths(array('%kernel.cache_dir%/htmlpurifier')); + $this->assertCacheWarmerSerializerPaths(['%kernel.cache_dir%/htmlpurifier']); + $this->assertRegistryHasProfiles(['default']); } public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPath() { - $config = array( - 'default' => array( + $config = [ + 'default' => [ 'AutoFormat.AutoParagraph' => true, - 'Cache.SerializerPath' => null, - ), - ); + 'Cache.SerializerPath' => null, + ], + ]; - $this->extension->load(array($config), $this->container); + $this->extension->load([$config], $this->container); $this->assertDefaultConfigDefinition($config['default']); - $this->assertCacheWarmerSerializerPaths(array()); + $this->assertCacheWarmerSerializerPaths([]); + $this->assertRegistryHasProfiles(['default']); } public function testShouldNotDeepMergeOptions() { - $configs = array( - array('default' => array( - 'Core.HiddenElements' => array('script' => true), + $configs = [ + ['default' => [ + 'Core.HiddenElements' => ['script' => true], 'Cache.SerializerPath' => null, - )), - array('default' => array( - 'Core.HiddenElements' => array('style' => true), - )), - ); + ]], + ['default' => [ + 'Core.HiddenElements' => ['style' => true], + ]], + ]; $this->extension->load($configs, $this->container); - $this->assertDefaultConfigDefinition(array( - 'Core.HiddenElements' => array('style' => true), + $this->assertDefaultConfigDefinition([ + 'Core.HiddenElements' => ['style' => true], 'Cache.SerializerPath' => null, - )); + ]); + $this->assertCacheWarmerSerializerPaths([]); + $this->assertRegistryHasProfiles(['default']); } public function testShouldLoadCustomConfiguration() { - $config = array( - 'default' => array( - 'AutoFormat.AutoParagraph' => true, - ), - 'simple' => array( - 'Cache.DefinitionImpl' => null, - 'Cache.SerializerPath' => '%kernel.cache_dir%/htmlpurifier-simple', - 'AutoFormat.Linkify' => true, - 'AutoFormat.RemoveEmpty' => true, + $config = [ + 'default' => [ + 'AutoFormat.AutoParagraph' => true, + ], + 'simple' => [ + 'Cache.DefinitionImpl' => null, + 'Cache.SerializerPath' => '%kernel.cache_dir%/htmlpurifier-simple', + 'AutoFormat.Linkify' => true, + 'AutoFormat.RemoveEmpty' => true, 'AutoFormat.RemoveEmpty.RemoveNbsp' => true, - 'HTML.Allowed' => "a[href],strong,em,p,li,ul,ol", - ), - 'advanced' => array( - 'Cache.DefinitionImpl' => null, - ), - ); + 'HTML.Allowed' => "a[href],strong,em,p,li,ul,ol", + ], + 'advanced' => [ + 'Cache.DefinitionImpl' => null, + ], + ]; - $this->extension->load(array($config), $this->container); + $this->extension->load([$config], $this->container); $this->assertDefaultConfigDefinition(array_replace($this->defaultConfig, $config['default'])); $this->assertConfigDefinition('simple', $config['simple']); $this->assertConfigDefinition('advanced', $config['advanced']); - - $this->assertCacheWarmerSerializerPaths(array( + $this->assertCacheWarmerSerializerPaths([ '%kernel.cache_dir%/htmlpurifier', '%kernel.cache_dir%/htmlpurifier-simple', - )); - } - - public function testShouldResolveServices() - { - $config = array( - 'simple' => array( - 'AutoFormat.Custom' => array('@service_container'), - ), - ); - - $this->extension->load(array($config), $this->container); - - $definition = $this->container->getDefinition('exercise_html_purifier.config.simple'); - $calls = $definition->getMethodCalls(); - - $call = $calls[0]; - $this->assertSame('loadArray', $call[0]); - - $args = $call[1]; - - $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $args[0]['AutoFormat.Custom'][0]); + ]); + $this->assertRegistryHasProfiles(['default', 'simple', 'advanced']); } /** - * Assert that the named config definition extends the default profile and + * Asserts that the named config definition extends the default profile and * loads the given options. * * @param string $name @@ -137,22 +126,17 @@ private function assertConfigDefinition($name, array $config) $this->assertTrue($this->container->hasDefinition('exercise_html_purifier.config.' . $name)); $definition = $this->container->getDefinition('exercise_html_purifier.config.' . $name); - $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getClass()); - $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getFactory()[0]); - $this->assertEquals('inherit', $definition->getFactory()[1]); - - $this->assertEquals(1, count($definition->getArguments())); - $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $definition->getArgument(0)); - $this->assertEquals('exercise_html_purifier.config.default', (string) $definition->getArgument(0)); - - $calls = $definition->getMethodCalls(); - $this->assertEquals(1, count($calls)); - $this->assertEquals('loadArray', $calls[0][0]); - $this->assertEquals(array($config), $calls[0][1]); + + $this->assertSame([\HTMLPurifier_Config::class, 'inherit'], $definition->getFactory()); + + $args = $definition->getArguments(); + + $this->assertCount(1, $args); + $this->assertEquals([$config], $definition->getMethodCalls()[0][1]); } /** - * Assert that the default config definition loads the given options. + * Asserts that the default config definition loads the given options. * * @param array $config */ @@ -161,10 +145,37 @@ private function assertDefaultConfigDefinition(array $config) $this->assertTrue($this->container->hasDefinition('exercise_html_purifier.config.default')); $definition = $this->container->getDefinition('exercise_html_purifier.config.default'); - $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getClass()); - $this->assertEquals('%exercise_html_purifier.config.class%', $definition->getFactory()[0]); - $this->assertEquals('create', $definition->getFactory()[1]); - $this->assertEquals(array($config), $definition->getArguments()); + $this->assertEquals([\HTMLPurifier_Config::class, 'create'], $definition->getFactory()); + $this->assertEquals([$config], $definition->getArguments()); + } + + /** + * Asserts that the registry has profiles. + * + * @param string[] $profiles + */ + private function assertRegistryHasProfiles(array $profiles) + { + $this->assertTrue($this->container->hasAlias(HTMLPurifiersRegistryInterface::class), "The registry interface alias must exist."); + + try { + $registry = $this->container->findDefinition(HTMLPurifiersRegistryInterface::class); + } catch (ServiceNotFoundException $e) { + $this->fail(sprintf('Alias %s does not target a valid id: %s.', HTMLPurifiersRegistryInterface::class, $e->getMessage())); + } + + $this->assertSame(HTMLPurifiersRegistry::class, $registry->getClass()); + + foreach ($profiles as $profile) { + $purifierId = "exercise_html_purifier.$profile"; + + $this->assertTrue($this->container->has($purifierId), "The service $purifierId should be registered."); + + $tag = ['profile' => $profile]; + $purifier = $this->container->findDefinition($purifierId); + + $this->assertSame([HTMLPurifierPass::PURIFIER_TAG => [$tag]], $purifier->getTags()); + } } /** diff --git a/Tests/Form/HTMLPurifierTransformerTest.php b/Tests/Form/HTMLPurifierTransformerTest.php deleted file mode 100644 index 44aa0197..00000000 --- a/Tests/Form/HTMLPurifierTransformerTest.php +++ /dev/null @@ -1,29 +0,0 @@ -text

'; - - $purifier = $this->getMockBuilder('HTMLPurifier') - ->disableOriginalConstructor() - ->getMock(); - - $purifier->expects($this->once()) - ->method('purify') - ->with($input) - ->will($this->returnValue($purifiedInput)); - - $transformer = new HTMLPurifierTransformer($purifier); - - $this->assertEquals($purifiedInput, $transformer->reverseTransform($input)); - $this->assertEquals($purifiedInput, $transformer->transform($purifiedInput)); - } -} diff --git a/Tests/Form/Listener/HTMLPurifierListenerTest.php b/Tests/Form/Listener/HTMLPurifierListenerTest.php new file mode 100644 index 00000000..1ad90dd3 --- /dev/null +++ b/Tests/Form/Listener/HTMLPurifierListenerTest.php @@ -0,0 +1,83 @@ +text

'; + + $purifier = $this->createMock('HTMLPurifier'); + $purifier + ->expects($this->once()) + ->method('purify') + ->with($input) + ->willReturn($purifiedInput) + ; + + $profile = 'test'; + $registry = $this->createMock(HTMLPurifiersRegistryInterface::class); + $registry + ->expects($this->once()) + ->method('get') + ->with($profile) + ->willReturn($purifier) + ; + + $listener = new HTMLPurifierListener($registry, $profile); + + $event = $this->getFormEvent($input); + + $listener->purifySubmittedData($event); + + $this->assertSame($purifiedInput, $event->getData()); + } + + /** + * @dataProvider provideInvalidInput + */ + public function testPurifyDoNothingForEmptyOrNonScalarData($input) + { + $registry = $this->createMock(HTMLPurifiersRegistryInterface::class); + $registry + ->expects($this->never()) + ->method('get') + ; + + $listener = new HTMLPurifierListener($registry, 'test'); + + $event = $this->createMock(FormEvent::class); + $event + ->expects($this->once()) + ->method('getData') + ->willReturn($input) + ; + $event + ->expects($this->never()) + ->method('setData') + ; + + $listener->purifySubmittedData($event); + } + + public function provideInvalidInput() + { + yield ['']; + yield [' ']; + yield [[]]; + yield [new \stdClass()]; + } + + private function getFormEvent($data) + { + return new FormEvent($this->createMock(FormInterface::class), $data); + } +} diff --git a/Tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php b/Tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php new file mode 100644 index 00000000..73e49574 --- /dev/null +++ b/Tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php @@ -0,0 +1,127 @@ +registry = $this->createMock(HTMLPurifiersRegistryInterface::class); + + parent::setUp(); + } + + protected function getTypeExtensions() + { + return [ + new HTMLPurifierTextTypeExtension($this->registry), + ]; + } + + public function testDefaultOptions() + { + $this->registry + ->expects($this->never()) + ->method('has') + ; + $this->registry + ->expects($this->never()) + ->method('get') + ; + + $form = $this->factory->create(TextType::class); + $options = $form->getConfig()->getOptions(); + + $this->assertFalse($options['purify_html']); + $this->assertNull($options['purify_html_profile']); + $this->assertTrue($options['trim']); + $this->assertFalse($this->hasPurifierListener($form)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The profile "default" is not registered. + */ + public function testPurifyOptionsNeedDefaultProfile() + { + $this->registry + ->expects($this->once()) + ->method('has') + ->with('default') + ->willReturn(false) + ; + $this->registry + ->expects($this->never()) + ->method('get'); + ; + + $this->factory->create(TextType::class, null, ['purify_html' => true]); + } + + public function testDefaultOptionsWhenPurifyIsTrue() + { + $this->registry + ->expects($this->once()) + ->method('has') + ->with('default') + ->willReturn(true) + ; + + $form = $this->factory->create(TextType::class, null, ['purify_html' => true]); + $options = $form->getConfig()->getOptions(); + + $this->assertTrue($options['purify_html']); + $this->assertSame('default', $options['purify_html_profile']); + $this->assertFalse($options['trim']); + $this->assertTrue($this->hasPurifierListener($form)); + } + + /** + * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException + * @expectedExceptionMessage The profile "test" is not registered. + */ + public function testInvalidProfile() + { + $this->registry + ->expects($this->once()) + ->method('has') + ->with('test') + ->willReturn(false) + ; + $this->registry + ->expects($this->never()) + ->method('get') + ; + + $this->factory->create(TextType::class, null, [ + 'purify_html' => true, + 'purify_html_profile' => 'test', + ]); + } + + /** + * @param FormInterface $form + * + * @return bool + */ + private function hasPurifierListener(FormInterface $form) + { + foreach ($form->getConfig()->getEventDispatcher()->getListeners(FormEvents::PRE_SUBMIT) as $listener) { + if ($listener[0] instanceof HTMLPurifierListener) { + return true; + } + } + + return false; + } +} diff --git a/Tests/HTMLPurifiersRegistryTest.php b/Tests/HTMLPurifiersRegistryTest.php new file mode 100644 index 00000000..8c727cb4 --- /dev/null +++ b/Tests/HTMLPurifiersRegistryTest.php @@ -0,0 +1,69 @@ +locator = $this->createMock(ContainerInterface::class); + $this->registry = new HTMLPurifiersRegistry($this->locator); + } + + public function provideProfiles() + { + yield ['default']; + yield ['test']; + } + + /** + * @dataProvider provideProfiles + */ + public function testHas($profile) + { + $this->locator->expects($this->once()) + ->method('has') + ->with($profile) + ->willReturn(true) + ; + + $this->assertTrue($this->registry->has($profile)); + } + + /** + * @dataProvider provideProfiles + */ + public function testHasNot($profile) + { + $this->locator->expects($this->once()) + ->method('has') + ->with($profile) + ->willReturn(false) + ; + + $this->assertFalse($this->registry->has($profile)); + } + + /** + * @dataProvider provideProfiles + */ + public function testGet($profile) + { + $purifier = $this->createMock(\HTMLPurifier::class); + + $this->locator->expects($this->once()) + ->method('get') + ->with($profile) + ->willReturn($purifier) + ; + + $this->assertSame($purifier, $this->registry->get($profile)); + } +} diff --git a/Tests/Twig/HTMLPurifierExtensionTest.php b/Tests/Twig/HTMLPurifierRuntimeTest.php similarity index 59% rename from Tests/Twig/HTMLPurifierExtensionTest.php rename to Tests/Twig/HTMLPurifierRuntimeTest.php index 10aa65e3..7910e491 100644 --- a/Tests/Twig/HTMLPurifierExtensionTest.php +++ b/Tests/Twig/HTMLPurifierRuntimeTest.php @@ -2,10 +2,11 @@ namespace Exercise\HTMLPurifierBundle\Tests\Twig; -use Exercise\HTMLPurifierBundle\Twig\HTMLPurifierExtension; +use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; +use Exercise\HTMLPurifierBundle\Twig\HTMLPurifierRuntime; use PHPUnit\Framework\TestCase; -class HTMLPurifierExtensionTest extends TestCase +class HTMLPurifierRuntimeTest extends TestCase { /** * @dataProvider providePurifierProfiles @@ -24,23 +25,22 @@ public function testPurifyFilter($profile) ->with($input) ->will($this->returnValue($purifiedInput)); - $container = $this->createMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $registry = $this->createMock(HTMLPurifiersRegistryInterface::class); - $container->expects($this->once()) + $registry->expects($this->once()) ->method('get') - ->with('exercise_html_purifier.' . $profile) - ->will($this->returnValue($purifier)); + ->with($profile) + ->will($this->returnValue($purifier)) + ; - $extension = new HTMLPurifierExtension($container); + $extension = new HTMLPurifierRuntime($registry); $this->assertEquals($purifiedInput, $extension->purify($input, $profile)); } public function providePurifierProfiles() { - return array( - array('default'), - array('custom'), - ); + yield ['default']; + yield ['custom']; } } diff --git a/Twig/HTMLPurifierExtension.php b/Twig/HTMLPurifierExtension.php index 84ed0f08..a0e945fd 100644 --- a/Twig/HTMLPurifierExtension.php +++ b/Twig/HTMLPurifierExtension.php @@ -2,73 +2,18 @@ namespace Exercise\HTMLPurifierBundle\Twig; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; -class HTMLPurifierExtension extends \Twig_Extension +class HTMLPurifierExtension extends AbstractExtension { - private $container; - - private $purifiers = array(); - - /** - * Constructor. - * - * @param ContainerInterface $container - */ - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - /** - * @see Twig_Extension::getFilters() + * {@inheritdoc} */ public function getFilters() { return array( - new \Twig_SimpleFilter('purify', array($this, 'purify'), array('is_safe' => array('html'))), + new TwigFilter('purify', [HTMLPurifierRuntime::class, 'purify'], ['is_safe' => ['html']]), ); } - - /** - * Filter the input through an HTMLPurifier service. - * - * @param string $string - * @param string $profile - * @return string - */ - public function purify($string, $profile = 'default') - { - return $this->getHTMLPurifierForProfile($profile)->purify($string); - } - - /** - * Get the HTMLPurifier service corresponding to the given profile. - * - * @param string $profile - * @return \HTMLPurifier - * @throws \RuntimeException - */ - private function getHTMLPurifierForProfile($profile) - { - if (!isset($this->purifiers[$profile])) { - $purifier = $this->container->get('exercise_html_purifier.' . $profile); - - if (!$purifier instanceof \HTMLPurifier) { - throw new \RuntimeException(sprintf('Service "exercise_html_purifier.%s" is not an HTMLPurifier instance.', $profile)); - } - - $this->purifiers[$profile] = $purifier; - } - - return $this->purifiers[$profile]; - } - - /** - * @see Twig_ExtensionInterface::getName() - */ - public function getName() - { - return 'html_purifier'; - } } diff --git a/Twig/HTMLPurifierRuntime.php b/Twig/HTMLPurifierRuntime.php new file mode 100644 index 00000000..025cf0e7 --- /dev/null +++ b/Twig/HTMLPurifierRuntime.php @@ -0,0 +1,43 @@ +purifiersRegistry = $registry; + } + + /** + * Filters the input through an \HTMLPurifier service. + * + * @param string $string The html string to purify + * @param string $profile A configuration profile name + * + * @return string The purified html string + */ + public function purify($string, $profile = 'default') + { + return $this->getHTMLPurifierForProfile($profile)->purify($string); + } + + /** + * Gets the HTMLPurifier service corresponding to the given profile. + * + * @param string $profile + * + * @return \HTMLPurifier + * + * @throws \InvalidArgumentException If the profile does not exist + */ + private function getHTMLPurifierForProfile($profile) + { + return $this->purifiersRegistry->get($profile); + } +} diff --git a/composer.json b/composer.json index 9c9accf5..1d362b41 100644 --- a/composer.json +++ b/composer.json @@ -2,25 +2,26 @@ "name": "exercise/htmlpurifier-bundle", "type": "symfony-bundle", "description": "HTMLPurifier integration for your Symfony project", - "keywords": ["htmlpurifier"], + "keywords": ["htmlpurifier", "html", "purifier", "symfony"], "homepage": "https://github.com/Exercise/HTMLPurifierBundle", "license": "MIT", "authors": [ { "name": "contributors", "homepage": "https://github.com/Exercise/HTMLPurifierBundle/contributors" } ], "require": { - "php": ">=5.3.2", - "symfony/framework-bundle": "^2.7 || ^3.0 || ^4.0", - "ezyang/htmlpurifier": "~4.0" + "php": "^5.5.9|>=7.0.8", + "ezyang/htmlpurifier": "~4.0", + "symfony/dependency-injection": "~3.4.1 || ^4.0.1", + "symfony/http-kernel": "~3.4.1 || ^4.0.1" }, "require-dev": { - "symfony/form": "^2.7 || ^3.0 || ^4.0", - "twig/twig": "~1.3|~2.0", - "phpunit/phpunit": "^7.2" + "phpunit/phpunit": "^7.2", + "symfony/form": "~3.4.1 || ^4.0.1", + "twig/twig": "^1.34.4 || ^2.4.3" }, "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { From 951b101f6c779d640362b5e69c3d8684cea42b82 Mon Sep 17 00:00:00 2001 From: Serhii Polishchuk Date: Sun, 29 Jul 2018 21:51:29 +0300 Subject: [PATCH 15/61] Added travis-ci config --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..1a34b607 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: php +php: + - '7.1' +install: composer install +script: php vendor/bin/phpunit From 7165c3e7fa45900051812e52c15ec1cc18504b75 Mon Sep 17 00:00:00 2001 From: Serhii Polishchuk Date: Sun, 29 Jul 2018 22:13:10 +0300 Subject: [PATCH 16/61] Added missing dependencies --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1d362b41..4abb290d 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "php": "^5.5.9|>=7.0.8", "ezyang/htmlpurifier": "~4.0", "symfony/dependency-injection": "~3.4.1 || ^4.0.1", - "symfony/http-kernel": "~3.4.1 || ^4.0.1" + "symfony/http-kernel": "~3.4.1 || ^4.0.1", + "symfony/config": "~3.3 || ~4.0" }, "require-dev": { "phpunit/phpunit": "^7.2", From 5283bd4a019b048d7dbf536ccbf2ce8b560794ce Mon Sep 17 00:00:00 2001 From: Oscar Reimer Date: Fri, 3 Aug 2018 14:09:57 +0200 Subject: [PATCH 17/61] Update HTMLPurifierTextTypeExtension.php Fixes exception "The option "purify_html_profile" with value null is expected to be of type "string", but is of type "NULL"" when using TextType without specifying any purify options. --- Form/TypeExtension/HTMLPurifierTextTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php index 7f6d1aff..6e700f0f 100644 --- a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php +++ b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php @@ -39,7 +39,7 @@ public function configureOptions(OptionsResolver $resolver) 'purify_html_profile' => 'default', ]) ->setAllowedTypes('purify_html', 'bool') - ->setAllowedTypes('purify_html_profile', 'string') + ->setAllowedTypes('purify_html_profile', ['string', 'null']) ->setNormalizer('purify_html_profile', function (Options $options, $profile) { if (!$options['purify_html']) { return null; From b7a05dcee4b65b6698db88380ccd5ad96afdf5aa Mon Sep 17 00:00:00 2001 From: Alister Bulman Date: Sun, 5 Aug 2018 19:41:14 +0100 Subject: [PATCH 18/61] kernel.cache_warmer & fixing service definition see: #54 --- Resources/config/html_purifier.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Resources/config/html_purifier.xml b/Resources/config/html_purifier.xml index d7934a56..f3e2bc63 100644 --- a/Resources/config/html_purifier.xml +++ b/Resources/config/html_purifier.xml @@ -4,8 +4,9 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - %exercise_html_purifier.cache_warmer.serializer.paths% + %exercise_html_purifier.cache_warmer.serializer.paths% + From 683c0687b8852924f4dcebabdd6c756050505f2e Mon Sep 17 00:00:00 2001 From: Top Web Studio Date: Sat, 11 Aug 2018 13:44:54 +0300 Subject: [PATCH 19/61] Update README.md Fix for "The option "purify_html" with value "true" is expected to be of type "bool", but is of type "string" --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2f70f110..68261171 100644 --- a/README.md +++ b/README.md @@ -146,8 +146,8 @@ class ArticleType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('content', TextareaType::class, ['purify_html' => 'true']) // will use default profile - ->add('sneek_peak', TextType::class, ['purify_html' => 'true', 'purify_html_profile' => 'sneak_peak']) + ->add('content', TextareaType::class, ['purify_html' => true]) // will use default profile + ->add('sneek_peak', TextType::class, ['purify_html' => true, 'purify_html_profile' => 'sneak_peak']) // ... ; } @@ -201,7 +201,7 @@ Now your purifier can be used when: // In a form type $builder ->add('content', TextareaType::class, [ - 'purify_html' => 'true', + 'purify_html' => true, 'purify_html_profile' => 'custom', ]) // ... From ed374a581d9746b7974156a22f1dc838e7d2d672 Mon Sep 17 00:00:00 2001 From: Serhii Polishchuk Date: Sat, 11 Aug 2018 20:41:03 +0300 Subject: [PATCH 20/61] Added Travis-Ci label --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 68261171..5f88b2d6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ExerciseHTMLPurifierBundle +# ExerciseHTMLPurifierBundle [![Build Status](https://travis-ci.org/Exercise/HTMLPurifierBundle.svg?branch=master)](https://travis-ci.org/Exercise/HTMLPurifierBundle) This bundle integrates [HTMLPurifier][] into Symfony. From 61da369aafcc8e7ee325bf0d1fea0c0206c3075c Mon Sep 17 00:00:00 2001 From: Mathieu Piot Date: Sun, 23 Dec 2018 16:04:07 +0100 Subject: [PATCH 21/61] Implement getExtendedTypes in place of getExtendedType --- Form/TypeExtension/HTMLPurifierTextTypeExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php index 6e700f0f..dd209fae 100644 --- a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php +++ b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php @@ -23,9 +23,9 @@ public function __construct(HTMLPurifiersRegistryInterface $registry) /** * {@inheritdoc} */ - public function getExtendedType() + public static function getExtendedTypes(): iterable { - return TextType::class; + return [TextType::class]; } /** From c1061f32aaa4dd5868bc1a35b81676ae5ec9c433 Mon Sep 17 00:00:00 2001 From: Mathieu Piot Date: Sun, 23 Dec 2018 16:19:47 +0100 Subject: [PATCH 22/61] Fix Tree root deprecation --- DependencyInjection/Configuration.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 7696c2f6..13a4f4ac 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -12,8 +12,14 @@ class Configuration implements ConfigurationInterface */ public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('exercise_html_purifier'); + $treeBuilder = new TreeBuilder('exercise_html_purifier'); + + if (method_exists($treeBuilder, 'getRootNode')) { + $rootNode = $treeBuilder->getRootNode(); + } else { + // BC layer for symfony/config 4.1 and older + $rootNode = $treeBuilder->root('exercise_html_purifier'); + } $rootNode ->useAttributeAsKey('name') From ee6735f7182c54e2ed17fc8e9611298f922e0a99 Mon Sep 17 00:00:00 2001 From: Mathieu Piot Date: Thu, 31 Jan 2019 21:51:09 +0100 Subject: [PATCH 23/61] Keep the previous method, and minimum PHP version update --- Form/TypeExtension/HTMLPurifierTextTypeExtension.php | 8 ++++++++ composer.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php index dd209fae..0b88e03e 100644 --- a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php +++ b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php @@ -20,6 +20,14 @@ public function __construct(HTMLPurifiersRegistryInterface $registry) $this->purifiersRegistry = $registry; } + /** + * {@inheritdoc} + */ + public function getExtendedType() + { + return TextType::class; + } + /** * {@inheritdoc} */ diff --git a/composer.json b/composer.json index 4abb290d..bbd7b95f 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ { "name": "contributors", "homepage": "https://github.com/Exercise/HTMLPurifierBundle/contributors" } ], "require": { - "php": "^5.5.9|>=7.0.8", + "php": "^7.1.3", "ezyang/htmlpurifier": "~4.0", "symfony/dependency-injection": "~3.4.1 || ^4.0.1", "symfony/http-kernel": "~3.4.1 || ^4.0.1", From ec4c80e818796601b93150f077c45900ded894d5 Mon Sep 17 00:00:00 2001 From: Mathieu Piot Date: Wed, 20 Feb 2019 14:40:52 +0100 Subject: [PATCH 24/61] Remove iterable return type --- Form/TypeExtension/HTMLPurifierTextTypeExtension.php | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php index 0b88e03e..77f24c37 100644 --- a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php +++ b/Form/TypeExtension/HTMLPurifierTextTypeExtension.php @@ -31,7 +31,7 @@ public function getExtendedType() /** * {@inheritdoc} */ - public static function getExtendedTypes(): iterable + public static function getExtendedTypes() { return [TextType::class]; } diff --git a/composer.json b/composer.json index bbd7b95f..4abb290d 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ { "name": "contributors", "homepage": "https://github.com/Exercise/HTMLPurifierBundle/contributors" } ], "require": { - "php": "^7.1.3", + "php": "^5.5.9|>=7.0.8", "ezyang/htmlpurifier": "~4.0", "symfony/dependency-injection": "~3.4.1 || ^4.0.1", "symfony/http-kernel": "~3.4.1 || ^4.0.1", From 2ddf3b698d9d693c06092141e7ce95657de93cfc Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sat, 14 Dec 2019 01:00:53 +0100 Subject: [PATCH 25/61] Updated travis.yaml to rely on PHPUnit bridge and PHP CS Fixer --- .gitignore | 2 + .php_cs.dist | 13 ++++ .travis.yml | 69 ++++++++++++++++-- DependencyInjection/Configuration.php | 2 +- .../ExerciseHTMLPurifierExtension.php | 2 +- Form/Listener/HTMLPurifierListener.php | 3 +- .../ForwardCompatTypeExtensionTrait.php | 47 ++++++++++++ .../HTMLPurifierTextTypeExtension.php | 7 +- .../CacheWarmer/SerializerCacheWarmerTest.php | 22 +++++- .../Compiler/HTMLPurifierPassTest.php | 42 ++++++++--- .../ExerciseHTMLPurifierExtensionTest.php | 25 ++++--- .../HTMLPurifierTextTypeExtensionTest.php | 30 ++++---- Tests/ForwardCompatTestTrait.php | 72 +++++++++++++++++++ Tests/HTMLPurifiersRegistryTest.php | 10 ++- Twig/HTMLPurifierExtension.php | 4 +- composer.json | 27 ++++--- phpunit.xml.dist | 3 +- 17 files changed, 319 insertions(+), 61 deletions(-) create mode 100644 .php_cs.dist create mode 100644 Form/TypeExtension/ForwardCompatTypeExtensionTrait.php create mode 100644 Tests/ForwardCompatTestTrait.php diff --git a/.gitignore b/.gitignore index 5de5676c..3714b9b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.php_cs.cache +.phpunit.result.cache phpunit.xml composer.lock vendor/ diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 00000000..ac731140 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,13 @@ +exclude('Resources') + ->in(__DIR__) +; + +return PhpCsFixer\Config::create() + ->setRules([ + '@Symfony' => true, + ]) + ->setFinder($finder) +; diff --git a/.travis.yml b/.travis.yml index 1a34b607..edd425ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,66 @@ language: php -php: - - '7.1' -install: composer install -script: php vendor/bin/phpunit + +sudo: false + +dist: xenial + +env: + global: + - COMPOSER_MEMORY_LIMIT=-1 + - SYMFONY_PHPUNIT_DIR=$HOME/.phpunit-bridge + +cache: + directories: + - $HOME/.composer/cache + - $HOME/.phpunit-bridge + +jobs: + include: + # Lowest + - php: 5.5 + dist: trusty + env: NO_FLEX=1 COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak + - php: 7.1 + env: SYMFONY_REQUIRE="4.3.*" COMPOSER_FLAGS="--prefer-lowest" + + # Stable + - php: 7.2 + env: SYMFONY_REQUIRE="3.4.*" COMPOSER_FLAGS="--prefer-stable" + - php: 7.3 + env: SYMFONY_REQUIRE="4.4.*" COMPOSER_FLAGS="--prefer-stable" + - php: 7.4 + env: SYMFONY_REQUIRE="5.0.*" COMPOSER_FLAGS="--prefer-stable" + + # Dev + - php: 7.4 + env: STABILITY=dev + + # QA + - stage: QA + name: PHP CS Fixer + php: 7.4 + script: vendor/bin/php-cs-fixer fix --dry-run --diff + - name: Coverage + php: 7.4 + before_script: + - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,} + - if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi + script: + - ./vendor/bin/simple-phpunit -v --coverage-text + + allow_failures: + - env: STABILITY=dev + +before_install: + - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available" + - composer self-update + - if [[ -z $NO_FLEX ]]; then composer global require --no-progress --no-scripts --no-plugins symfony/flex; fi; + +install: + - composer update --prefer-dist --no-interaction $COMPOSER_FLAGS + - vendor/bin/simple-phpunit install + +script: + - if [[ -v $STABILITY ]]; then composer config minimum-stability $STABILITY; fi; + - composer validate --strict --no-check-lock + - vendor/bin/simple-phpunit -v diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 13a4f4ac..f30b6a76 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -2,8 +2,8 @@ namespace Exercise\HTMLPurifierBundle\DependencyInjection; -use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; class Configuration implements ConfigurationInterface { diff --git a/DependencyInjection/ExerciseHTMLPurifierExtension.php b/DependencyInjection/ExerciseHTMLPurifierExtension.php index a85a0e62..c8aae1b6 100644 --- a/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -7,8 +7,8 @@ use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class ExerciseHTMLPurifierExtension extends Extension diff --git a/Form/Listener/HTMLPurifierListener.php b/Form/Listener/HTMLPurifierListener.php index 4ff2fabc..25398cb1 100644 --- a/Form/Listener/HTMLPurifierListener.php +++ b/Form/Listener/HTMLPurifierListener.php @@ -13,8 +13,7 @@ class HTMLPurifierListener implements EventSubscriberInterface private $profile; /** - * @param HTMLPurifiersRegistryInterface $registry - * @param string $profile + * @param string $profile */ public function __construct(HTMLPurifiersRegistryInterface $registry, $profile) { diff --git a/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php b/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php new file mode 100644 index 00000000..4f1f1eeb --- /dev/null +++ b/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php @@ -0,0 +1,47 @@ +assertFalse($cacheWarmer->isOptional()); } + public function testFailsWhenNotWriteable() + { + $path = sys_get_temp_dir().'/'.uniqid('htmlpurifierbundle_fails'); + + if (false === @mkdir($path, 0000)) { + $this->markTestSkipped('Tmp dir is not writeable.'); + } + + $this->expectException('RuntimeException'); + + $cacheWarmer = new SerializerCacheWarmer([$path], new \HTMLPurifier()); + $cacheWarmer->warmUp(null); + + @rmdir($path); + } + public function testShouldCreatePaths() { if (!is_writable(sys_get_temp_dir())) { $this->markTestSkipped(sprintf('The system temp directory "%s" is not writeable for the current system user.', sys_get_temp_dir())); } - $path = sys_get_temp_dir() . '/' . uniqid('htmlpurifierbundle'); + $path = sys_get_temp_dir().'/'.uniqid('htmlpurifierbundle'); - $cacheWarmer = new SerializerCacheWarmer(array($path), new \HTMLPurifier()); + $cacheWarmer = new SerializerCacheWarmer([$path], new \HTMLPurifier()); $cacheWarmer->warmUp(null); $this->assertTrue(is_dir($path)); diff --git a/Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php b/Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php index 870cf009..b7c88e4a 100644 --- a/Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php +++ b/Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php @@ -5,6 +5,8 @@ use Exercise\HTMLPurifierBundle\DependencyInjection\Compiler\HTMLPurifierPass; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistry; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; +use Exercise\HTMLPurifierBundle\Tests\ForwardCompatTestTrait; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -14,21 +16,40 @@ class HTMLPurifierPassTest extends TestCase { + use ForwardCompatTestTrait; + + /** @var ContainerBuilder|MockObject */ + private $container; + + private function doSetUp() + { + $this->container = $this->createPartialMock(ContainerBuilder::class, [ + 'hasAlias', + 'findDefinition', + 'findTaggedServiceIds', + 'getDefinition', + ]); + } + + private function doTearDown() + { + $this->container = null; + } + public function testProcessOnlyIfRegistryInterfaceIsDefined() { - $container = $this->createMock(ContainerBuilder::class); - $container->expects($this->once()) + $this->container->expects($this->once()) ->method('hasAlias') ->with(HTMLPurifiersRegistryInterface::class) ->willReturn(false) ; - $container->expects($this->never()) + $this->container->expects($this->never()) ->method('findDefinition') ; $pass = new HTMLPurifierPass(); - $pass->process($container); + $pass->process($this->container); } public function testProcess() @@ -54,27 +75,28 @@ public function testProcess() public function testProcessDoNothingIfRegistryIsNotDefined() { - $container = $this->createMock(ContainerBuilder::class); - $container + $this->container ->expects($this->once()) ->method('hasAlias') ->with(HTMLPurifiersRegistryInterface::class) ->willReturn(true) ; - $container + $this->container ->expects($this->once()) ->method('findDefinition') ->with(HTMLPurifiersRegistryInterface::class) ->willThrowException($this->createMock(ServiceNotFoundException::class)) ; - $container + $this->container ->expects($this->never()) ->method('findTaggedServiceIds') ; $pass = new HTMLPurifierPass(); - $pass->process($container); + $pass->process($this->container); } } -class DummyPurifier extends \HTMLPurifier {} +class DummyPurifier extends \HTMLPurifier +{ +} diff --git a/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php index c5b3b642..5e445548 100644 --- a/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php +++ b/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php @@ -6,12 +6,15 @@ use Exercise\HTMLPurifierBundle\DependencyInjection\ExerciseHTMLPurifierExtension; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistry; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; +use Exercise\HTMLPurifierBundle\Tests\ForwardCompatTestTrait; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; class ExerciseHTMLPurifierExtensionTest extends TestCase { + use ForwardCompatTestTrait; + /** * @var ContainerBuilder */ @@ -27,7 +30,7 @@ class ExerciseHTMLPurifierExtensionTest extends TestCase */ private $defaultConfig; - public function setUp() + private function doSetUp() { $this->container = new ContainerBuilder(); $this->extension = new ExerciseHTMLPurifierExtension(); @@ -36,6 +39,13 @@ public function setUp() ]; } + private function doTearDown() + { + $this->defaultConfig = null; + $this->extension = null; + $this->container = null; + } + public function testShouldLoadDefaultConfiguration() { $this->extension->load([], $this->container); @@ -95,7 +105,7 @@ public function testShouldLoadCustomConfiguration() 'AutoFormat.Linkify' => true, 'AutoFormat.RemoveEmpty' => true, 'AutoFormat.RemoveEmpty.RemoveNbsp' => true, - 'HTML.Allowed' => "a[href],strong,em,p,li,ul,ol", + 'HTML.Allowed' => 'a[href],strong,em,p,li,ul,ol', ], 'advanced' => [ 'Cache.DefinitionImpl' => null, @@ -119,13 +129,12 @@ public function testShouldLoadCustomConfiguration() * loads the given options. * * @param string $name - * @param array $config */ private function assertConfigDefinition($name, array $config) { - $this->assertTrue($this->container->hasDefinition('exercise_html_purifier.config.' . $name)); + $this->assertTrue($this->container->hasDefinition('exercise_html_purifier.config.'.$name)); - $definition = $this->container->getDefinition('exercise_html_purifier.config.' . $name); + $definition = $this->container->getDefinition('exercise_html_purifier.config.'.$name); $this->assertSame([\HTMLPurifier_Config::class, 'inherit'], $definition->getFactory()); @@ -137,8 +146,6 @@ private function assertConfigDefinition($name, array $config) /** * Asserts that the default config definition loads the given options. - * - * @param array $config */ private function assertDefaultConfigDefinition(array $config) { @@ -156,7 +163,7 @@ private function assertDefaultConfigDefinition(array $config) */ private function assertRegistryHasProfiles(array $profiles) { - $this->assertTrue($this->container->hasAlias(HTMLPurifiersRegistryInterface::class), "The registry interface alias must exist."); + $this->assertTrue($this->container->hasAlias(HTMLPurifiersRegistryInterface::class), 'The registry interface alias must exist.'); try { $registry = $this->container->findDefinition(HTMLPurifiersRegistryInterface::class); @@ -180,8 +187,6 @@ private function assertRegistryHasProfiles(array $profiles) /** * Assert that the cache warmer serializer paths equal the given array. - * - * @param array $paths */ private function assertCacheWarmerSerializerPaths(array $paths) { diff --git a/Tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php b/Tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php index 73e49574..d8a71df2 100644 --- a/Tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php +++ b/Tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php @@ -5,6 +5,7 @@ use Exercise\HTMLPurifierBundle\Form\Listener\HTMLPurifierListener; use Exercise\HTMLPurifierBundle\Form\TypeExtension\HTMLPurifierTextTypeExtension; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; +use Exercise\HTMLPurifierBundle\Tests\ForwardCompatTestTrait; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; @@ -12,15 +13,24 @@ class HTMLPurifierTextTypeExtensionTest extends FormIntegrationTestCase { + use ForwardCompatTestTrait; + private $registry; - protected function setUp() + private function doSetUp() { $this->registry = $this->createMock(HTMLPurifiersRegistryInterface::class); parent::setUp(); } + private function doTearDown() + { + parent::tearDown(); + + $this->registry = null; + } + protected function getTypeExtensions() { return [ @@ -48,10 +58,6 @@ public function testDefaultOptions() $this->assertFalse($this->hasPurifierListener($form)); } - /** - * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException - * @expectedExceptionMessage The profile "default" is not registered. - */ public function testPurifyOptionsNeedDefaultProfile() { $this->registry @@ -62,9 +68,12 @@ public function testPurifyOptionsNeedDefaultProfile() ; $this->registry ->expects($this->never()) - ->method('get'); + ->method('get') ; + $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); + $this->expectExceptionMessage('The profile "default" is not registered.'); + $this->factory->create(TextType::class, null, ['purify_html' => true]); } @@ -86,10 +95,6 @@ public function testDefaultOptionsWhenPurifyIsTrue() $this->assertTrue($this->hasPurifierListener($form)); } - /** - * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException - * @expectedExceptionMessage The profile "test" is not registered. - */ public function testInvalidProfile() { $this->registry @@ -103,6 +108,9 @@ public function testInvalidProfile() ->method('get') ; + $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); + $this->expectExceptionMessage('The profile "test" is not registered.'); + $this->factory->create(TextType::class, null, [ 'purify_html' => true, 'purify_html_profile' => 'test', @@ -110,8 +118,6 @@ public function testInvalidProfile() } /** - * @param FormInterface $form - * * @return bool */ private function hasPurifierListener(FormInterface $form) diff --git a/Tests/ForwardCompatTestTrait.php b/Tests/ForwardCompatTestTrait.php new file mode 100644 index 00000000..3870ed3f --- /dev/null +++ b/Tests/ForwardCompatTestTrait.php @@ -0,0 +1,72 @@ +hasReturnType()) { + eval(' + namespace Exercise\HTMLPurifierBundle\Tests; + + /** + * @internal + */ + trait ForwardCompatTestTrait + { + private function doSetUp(): void + { + } + + private function doTearDown(): void + { + } + + protected function setUp(): void + { + $this->doSetUp(); + } + protected function tearDown(): void + { + $this->doTearDown(); + } + } +'); +} else { + /** + * @internal + */ + trait ForwardCompatTestTrait + { + /** + * @return void + */ + private function doSetUp() + { + } + + /** + * @return void + */ + private function doTearDown() + { + } + + /** + * @return void + */ + protected function setUp() + { + $this->doSetUp(); + } + + /** + * @return void + */ + protected function tearDown() + { + $this->doTearDown(); + } + } +} diff --git a/Tests/HTMLPurifiersRegistryTest.php b/Tests/HTMLPurifiersRegistryTest.php index 8c727cb4..378e73d8 100644 --- a/Tests/HTMLPurifiersRegistryTest.php +++ b/Tests/HTMLPurifiersRegistryTest.php @@ -8,15 +8,23 @@ class HTMLPurifiersRegistryTest extends TestCase { + use ForwardCompatTestTrait; + private $locator; private $registry; - protected function setUp() + private function doSetUp() { $this->locator = $this->createMock(ContainerInterface::class); $this->registry = new HTMLPurifiersRegistry($this->locator); } + private function doTearDown() + { + $this->registry = null; + $this->locator = null; + } + public function provideProfiles() { yield ['default']; diff --git a/Twig/HTMLPurifierExtension.php b/Twig/HTMLPurifierExtension.php index a0e945fd..713596d8 100644 --- a/Twig/HTMLPurifierExtension.php +++ b/Twig/HTMLPurifierExtension.php @@ -12,8 +12,8 @@ class HTMLPurifierExtension extends AbstractExtension */ public function getFilters() { - return array( + return [ new TwigFilter('purify', [HTMLPurifierRuntime::class, 'purify'], ['is_safe' => ['html']]), - ); + ]; } } diff --git a/composer.json b/composer.json index 4abb290d..ff9517f6 100644 --- a/composer.json +++ b/composer.json @@ -6,26 +6,33 @@ "homepage": "https://github.com/Exercise/HTMLPurifierBundle", "license": "MIT", "authors": [ - { "name": "contributors", "homepage": "https://github.com/Exercise/HTMLPurifierBundle/contributors" } + { + "name": "contributors", + "homepage": "https://github.com/Exercise/HTMLPurifierBundle/contributors" + } ], "require": { "php": "^5.5.9|>=7.0.8", "ezyang/htmlpurifier": "~4.0", - "symfony/dependency-injection": "~3.4.1 || ^4.0.1", - "symfony/http-kernel": "~3.4.1 || ^4.0.1", - "symfony/config": "~3.3 || ~4.0" + "symfony/config": "~3.4 || ~4.0 || ^5.0", + "symfony/dependency-injection": "~3.4.1 || ^4.0.1 || ^5.0", + "symfony/http-kernel": "~3.4.1 || ^4.0.1 || ^5.0" }, "require-dev": { - "phpunit/phpunit": "^7.2", - "symfony/form": "~3.4.1 || ^4.0.1", - "twig/twig": "^1.34.4 || ^2.4.3" + "friendsofphp/php-cs-fixer": "^2.0", + "symfony/form": "~3.4.1 || ^4.0.1 || ^5.0", + "symfony/phpunit-bridge": "4.4.*", + "twig/twig": "^1.35.0 || ^2.4.4 || ^3.0" + }, + "autoload": { + "psr-4": { "Exercise\\HTMLPurifierBundle\\": "" } + }, + "config": { + "sort-packages": true }, "extra": { "branch-alias": { "dev-master": "2.0.x-dev" } - }, - "autoload": { - "psr-4": { "Exercise\\HTMLPurifierBundle\\": "" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b2618a68..d9c986e2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,10 +16,11 @@ - ./ + ./ ./Resources ./Tests + ./vendor From ebaed2162be0f1576f12fd26d6a374bed32e033c Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Thu, 12 Dec 2019 22:12:07 +0100 Subject: [PATCH 26/61] Fixed README and added CHANGELOG --- CHANGELOG | 14 ++++++++++++++ README.md | 34 ++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 00000000..74331c35 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,14 @@ +## Version 2.0 (08/2018) + + * Added compatibility for Symfony 5 and Twig 3 + * Updated minimum requirement of Twig to 1.35 and 2.4 to support runtime + * [BC break] Dropped support for Symfony 2. Symfony 3.4 minimum required. + * [BC break] Removed classes parameters. + * [BC break] Removed the form data transformer. + * Added an `HTMLPurifierTextTypeExtension` to add `purify_html` and + `purify_html_profile` options to all `TextType` children. + * Added an `HTMLPurifierListener` to purify submitted form data. + * Added an `HTMLPurifiersRegistryInterface` to lazy load purifiers by profile. + * Added a Twig `HTMLPurifierRuntime` to lazy load purifiers in templates. + * Added a pass to use custom `\HTMLPurifier` classes as custom profiles using + a new `exercise.html_purifier` tag. diff --git a/README.md b/README.md index 5f88b2d6..0249313b 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,10 @@ exercise_html_purifier: Cache.SerializerPath: '%kernel.cache_dir%/htmlpurifier' ``` -The `default` profile is special in that it is used as the configuration for the -`exercise_html_purifier.default` service as well as the base configuration for -other profiles you might define. +The `default` profile is special, it is *always* defined and its configuration +is inherited by all custom profiles. +`exercise_html_purifier.default` is the default service using the base +configuration. ```yaml # config/packages/exercise_html_purifier.yaml @@ -115,13 +116,24 @@ this in you `app/config/services.yml` or `config/services.yaml`: services: # ... - \HTMLPurifier: - alias: exercise_html_purifier.custom - - # or the equivalent as of Symfony 3.3 - \HTMLPurifier: '@exercise_html_purifier.custom' + exercise_html_purifier.default: '@exercise_html_purifier.custom' +``` + +## Using a custom purifier class as default + +If you want to use your own class as default purifier, define a new alias: + +```yaml +# config/services.yaml +services: + # ... + + exercise_html_purifier.default: '@App\Html\CustomHtmlPurifier' ``` +In such case, the custom purifier will use its own defined configuration, +ignoring the bundle configuration. + ## Form Type Extension This bundles provides a form type extension for filtering form fields with @@ -212,5 +224,7 @@ $builder {{ html_string|purify('custom') }} ``` -Your class will inherit the default config or the one from the same profile -used in the tag. +## Contributing + +PRs are welcomed :). Please target the `2.0` branch for bug fixes and `master` +for new features. From 43b2af521e4fdf0320803813a4bcf28be8214f5b Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Wed, 11 Dec 2019 22:30:27 +0100 Subject: [PATCH 27/61] Fixed trimming in HTMLPurifierListener --- Form/Listener/HTMLPurifierListener.php | 4 +++ .../Listener/HTMLPurifierListenerTest.php | 27 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Form/Listener/HTMLPurifierListener.php b/Form/Listener/HTMLPurifierListener.php index 25398cb1..846151fe 100644 --- a/Form/Listener/HTMLPurifierListener.php +++ b/Form/Listener/HTMLPurifierListener.php @@ -29,6 +29,10 @@ public function purifySubmittedData(FormEvent $event) } if (0 === strlen($submittedData = trim($data))) { + if ($submittedData !== $data) { + $event->setData($submittedData); + } + return; } diff --git a/Tests/Form/Listener/HTMLPurifierListenerTest.php b/Tests/Form/Listener/HTMLPurifierListenerTest.php index 1ad90dd3..fd079c50 100644 --- a/Tests/Form/Listener/HTMLPurifierListenerTest.php +++ b/Tests/Form/Listener/HTMLPurifierListenerTest.php @@ -41,6 +41,32 @@ public function testPurify() $this->assertSame($purifiedInput, $event->getData()); } + public function testPurifyTrimEmptyValues() + { + $input = ' '; + $trimmedInput = ''; + + $purifier = $this->createMock('HTMLPurifier'); + $purifier + ->expects($this->never()) + ->method('purify') + ; + + $registry = $this->createMock(HTMLPurifiersRegistryInterface::class); + $registry + ->expects($this->never()) + ->method('get') + ; + + $listener = new HTMLPurifierListener($registry, 'test'); + + $event = $this->getFormEvent($input); + + $listener->purifySubmittedData($event); + + $this->assertSame($trimmedInput, $event->getData()); + } + /** * @dataProvider provideInvalidInput */ @@ -71,7 +97,6 @@ public function testPurifyDoNothingForEmptyOrNonScalarData($input) public function provideInvalidInput() { yield ['']; - yield [' ']; yield [[]]; yield [new \stdClass()]; } From 01a9f8a23a051d1238cabc7ade0dd5d63f54b9b2 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sat, 14 Dec 2019 12:37:01 +0100 Subject: [PATCH 28/61] Changed directory structure and added "autoload-dev" --- composer.json | 5 ++++- phpunit.xml.dist | 6 ++---- {CacheWarmer => src/CacheWarmer}/SerializerCacheWarmer.php | 0 .../DependencyInjection}/Compiler/HTMLPurifierPass.php | 0 .../DependencyInjection}/Configuration.php | 0 .../DependencyInjection}/ExerciseHTMLPurifierExtension.php | 0 .../ExerciseHTMLPurifierBundle.php | 0 {Form => src/Form}/Listener/HTMLPurifierListener.php | 0 .../Form}/TypeExtension/ForwardCompatTypeExtensionTrait.php | 0 .../Form}/TypeExtension/HTMLPurifierTextTypeExtension.php | 0 HTMLPurifiersRegistry.php => src/HTMLPurifiersRegistry.php | 0 .../HTMLPurifiersRegistryInterface.php | 0 {Resources => src/Resources}/config/html_purifier.xml | 0 {Twig => src/Twig}/HTMLPurifierExtension.php | 0 {Twig => src/Twig}/HTMLPurifierRuntime.php | 0 {Tests => tests}/CacheWarmer/SerializerCacheWarmerTest.php | 0 .../DependencyInjection/Compiler/HTMLPurifierPassTest.php | 0 .../ExerciseHTMLPurifierExtensionTest.php | 0 {Tests => tests}/Form/Listener/HTMLPurifierListenerTest.php | 2 +- .../TypeExtension/HTMLPurifierTextTypeExtensionTest.php | 0 {Tests => tests}/ForwardCompatTestTrait.php | 0 {Tests => tests}/HTMLPurifiersRegistryTest.php | 0 {Tests => tests}/Twig/HTMLPurifierRuntimeTest.php | 0 23 files changed, 7 insertions(+), 6 deletions(-) rename {CacheWarmer => src/CacheWarmer}/SerializerCacheWarmer.php (100%) rename {DependencyInjection => src/DependencyInjection}/Compiler/HTMLPurifierPass.php (100%) rename {DependencyInjection => src/DependencyInjection}/Configuration.php (100%) rename {DependencyInjection => src/DependencyInjection}/ExerciseHTMLPurifierExtension.php (100%) rename ExerciseHTMLPurifierBundle.php => src/ExerciseHTMLPurifierBundle.php (100%) rename {Form => src/Form}/Listener/HTMLPurifierListener.php (100%) rename {Form => src/Form}/TypeExtension/ForwardCompatTypeExtensionTrait.php (100%) rename {Form => src/Form}/TypeExtension/HTMLPurifierTextTypeExtension.php (100%) rename HTMLPurifiersRegistry.php => src/HTMLPurifiersRegistry.php (100%) rename HTMLPurifiersRegistryInterface.php => src/HTMLPurifiersRegistryInterface.php (100%) rename {Resources => src/Resources}/config/html_purifier.xml (100%) rename {Twig => src/Twig}/HTMLPurifierExtension.php (100%) rename {Twig => src/Twig}/HTMLPurifierRuntime.php (100%) rename {Tests => tests}/CacheWarmer/SerializerCacheWarmerTest.php (100%) rename {Tests => tests}/DependencyInjection/Compiler/HTMLPurifierPassTest.php (100%) rename {Tests => tests}/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php (100%) rename {Tests => tests}/Form/Listener/HTMLPurifierListenerTest.php (97%) rename {Tests => tests}/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php (100%) rename {Tests => tests}/ForwardCompatTestTrait.php (100%) rename {Tests => tests}/HTMLPurifiersRegistryTest.php (100%) rename {Tests => tests}/Twig/HTMLPurifierRuntimeTest.php (100%) diff --git a/composer.json b/composer.json index ff9517f6..6c0bd3e5 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,10 @@ "twig/twig": "^1.35.0 || ^2.4.4 || ^3.0" }, "autoload": { - "psr-4": { "Exercise\\HTMLPurifierBundle\\": "" } + "psr-4": { "Exercise\\HTMLPurifierBundle\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "Exercise\\HTMLPurifierBundle\\Tests\\": "tests/" } }, "config": { "sort-packages": true diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d9c986e2..ad6b264b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,17 +10,15 @@ --> - ./Tests + ./tests - ./ + ./src ./Resources - ./Tests - ./vendor diff --git a/CacheWarmer/SerializerCacheWarmer.php b/src/CacheWarmer/SerializerCacheWarmer.php similarity index 100% rename from CacheWarmer/SerializerCacheWarmer.php rename to src/CacheWarmer/SerializerCacheWarmer.php diff --git a/DependencyInjection/Compiler/HTMLPurifierPass.php b/src/DependencyInjection/Compiler/HTMLPurifierPass.php similarity index 100% rename from DependencyInjection/Compiler/HTMLPurifierPass.php rename to src/DependencyInjection/Compiler/HTMLPurifierPass.php diff --git a/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php similarity index 100% rename from DependencyInjection/Configuration.php rename to src/DependencyInjection/Configuration.php diff --git a/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php similarity index 100% rename from DependencyInjection/ExerciseHTMLPurifierExtension.php rename to src/DependencyInjection/ExerciseHTMLPurifierExtension.php diff --git a/ExerciseHTMLPurifierBundle.php b/src/ExerciseHTMLPurifierBundle.php similarity index 100% rename from ExerciseHTMLPurifierBundle.php rename to src/ExerciseHTMLPurifierBundle.php diff --git a/Form/Listener/HTMLPurifierListener.php b/src/Form/Listener/HTMLPurifierListener.php similarity index 100% rename from Form/Listener/HTMLPurifierListener.php rename to src/Form/Listener/HTMLPurifierListener.php diff --git a/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php b/src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php similarity index 100% rename from Form/TypeExtension/ForwardCompatTypeExtensionTrait.php rename to src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php diff --git a/Form/TypeExtension/HTMLPurifierTextTypeExtension.php b/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php similarity index 100% rename from Form/TypeExtension/HTMLPurifierTextTypeExtension.php rename to src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php diff --git a/HTMLPurifiersRegistry.php b/src/HTMLPurifiersRegistry.php similarity index 100% rename from HTMLPurifiersRegistry.php rename to src/HTMLPurifiersRegistry.php diff --git a/HTMLPurifiersRegistryInterface.php b/src/HTMLPurifiersRegistryInterface.php similarity index 100% rename from HTMLPurifiersRegistryInterface.php rename to src/HTMLPurifiersRegistryInterface.php diff --git a/Resources/config/html_purifier.xml b/src/Resources/config/html_purifier.xml similarity index 100% rename from Resources/config/html_purifier.xml rename to src/Resources/config/html_purifier.xml diff --git a/Twig/HTMLPurifierExtension.php b/src/Twig/HTMLPurifierExtension.php similarity index 100% rename from Twig/HTMLPurifierExtension.php rename to src/Twig/HTMLPurifierExtension.php diff --git a/Twig/HTMLPurifierRuntime.php b/src/Twig/HTMLPurifierRuntime.php similarity index 100% rename from Twig/HTMLPurifierRuntime.php rename to src/Twig/HTMLPurifierRuntime.php diff --git a/Tests/CacheWarmer/SerializerCacheWarmerTest.php b/tests/CacheWarmer/SerializerCacheWarmerTest.php similarity index 100% rename from Tests/CacheWarmer/SerializerCacheWarmerTest.php rename to tests/CacheWarmer/SerializerCacheWarmerTest.php diff --git a/Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php b/tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php similarity index 100% rename from Tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php rename to tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php diff --git a/Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php similarity index 100% rename from Tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php rename to tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php diff --git a/Tests/Form/Listener/HTMLPurifierListenerTest.php b/tests/Form/Listener/HTMLPurifierListenerTest.php similarity index 97% rename from Tests/Form/Listener/HTMLPurifierListenerTest.php rename to tests/Form/Listener/HTMLPurifierListenerTest.php index fd079c50..a712df69 100644 --- a/Tests/Form/Listener/HTMLPurifierListenerTest.php +++ b/tests/Form/Listener/HTMLPurifierListenerTest.php @@ -1,6 +1,6 @@ Date: Sat, 14 Dec 2019 12:52:08 +0100 Subject: [PATCH 29/61] Added badges in README --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0249313b..1a1694f5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ -# ExerciseHTMLPurifierBundle [![Build Status](https://travis-ci.org/Exercise/HTMLPurifierBundle.svg?branch=master)](https://travis-ci.org/Exercise/HTMLPurifierBundle) +[![Total Downloads](https://poser.pugx.org/exercise/htmlpurifier-bundle/downloads)](https://packagist.org/packages/exercise/htmlpurifier-bundle) +[![Latest Stable Version](https://poser.pugx.org/exercise/htmlpurifier-bundle/v/stable)](https://packagist.org/packages/exercise/htmlpurifier-bundle) +[![License](https://poser.pugx.org/exercise/htmlpurifier-bundle/license)](https://packagist.org/packages/exercise/htmlpurifier-bundle) +[![Build Status](https://travis-ci.org/Exercise/HTMLPurifierBundle.svg?branch=2.0)](https://travis-ci.org/Exercise/HTMLPurifierBundle) + +# ExerciseHTMLPurifierBundle This bundle integrates [HTMLPurifier][] into Symfony. From cee7c1aae5c10f21167a655455d2e5eb55bce9a9 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sat, 7 Dec 2019 12:18:41 +0100 Subject: [PATCH 30/61] Introducing v3 --- .travis.yml | 9 +- CHANGELOG | 27 ++ README.md | 196 ++++++++- Tests/HTMLPurifierConfigFactoryTest.php | 67 +++ composer.json | 4 +- phpunit.xml.dist | 7 - src/CacheWarmer/SerializerCacheWarmer.php | 43 +- src/DependencyInjection/Configuration.php | 76 +++- .../ExerciseHTMLPurifierExtension.php | 112 +++-- src/Form/Listener/HTMLPurifierListener.php | 12 +- .../ForwardCompatTypeExtensionTrait.php | 47 -- .../HTMLPurifierTextTypeExtension.php | 7 +- src/HTMLPurifierConfigFactory.php | 88 ++++ src/HTMLPurifiersRegistry.php | 4 +- src/HTMLPurifiersRegistryInterface.php | 14 +- src/Resources/config/html_purifier.xml | 6 +- src/Twig/HTMLPurifierRuntime.php | 8 +- .../CacheWarmer/SerializerCacheWarmerTest.php | 56 ++- .../Compiler/HTMLPurifierPassTest.php | 24 +- .../ExerciseHTMLPurifierExtensionTest.php | 410 +++++++++++++++--- .../Listener/HTMLPurifierListenerTest.php | 4 +- .../HTMLPurifierTextTypeExtensionTest.php | 17 +- tests/ForwardCompatTestTrait.php | 72 --- tests/HTMLPurifiersRegistryTest.php | 8 +- tests/Twig/HTMLPurifierRuntimeTest.php | 2 +- 25 files changed, 953 insertions(+), 367 deletions(-) create mode 100644 Tests/HTMLPurifierConfigFactoryTest.php delete mode 100644 src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php create mode 100644 src/HTMLPurifierConfigFactory.php delete mode 100644 tests/ForwardCompatTestTrait.php diff --git a/.travis.yml b/.travis.yml index edd425ba..0324b0a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,11 +17,10 @@ cache: jobs: include: # Lowest - - php: 5.5 - dist: trusty - env: NO_FLEX=1 COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak - php: 7.1 - env: SYMFONY_REQUIRE="4.3.*" COMPOSER_FLAGS="--prefer-lowest" + env: COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak + - php: 7.1 + env: SYMFONY_REQUIRE="4.3.*" COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak # Stable - php: 7.2 @@ -54,7 +53,7 @@ jobs: before_install: - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available" - composer self-update - - if [[ -z $NO_FLEX ]]; then composer global require --no-progress --no-scripts --no-plugins symfony/flex; fi; + - composer global require --no-progress --no-scripts --no-plugins symfony/flex install: - composer update --prefer-dist --no-interaction $COMPOSER_FLAGS diff --git a/CHANGELOG b/CHANGELOG index 74331c35..62e68f86 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,30 @@ +## Version 3.0 (12/2019) + + * [BC break] Dropped support for PHP 5.x. PHP 7.1 minimum required. + * [BC break] Added type hints for scalar and return type hints where possible. + * [BC Break] The bundle configuration has changed: + ```yaml + # Before + exercise_html_purifier: + default: + Cache.SerializerPath: '%kernel.cache_dir%/htmlpurifier' + # ... + custom: + Core.Encoding: 'ISO-8859-1' + + # After + exercise_html_purifier: + default_cache_serializer_path: '%kernel.cache_dir%/htmlpurifier' + html_profiles: + default: + # ... + custom: + config: + Core.Encoding: 'ISO-8859-1' + ``` + * Added an `HTMLPurifierConfigFactory` to handle cache and custom definitions. + * Refactored `SerializerCacheWarmer` to preload each profile configuration + ## Version 2.0 (08/2018) * Added compatibility for Symfony 5 and Twig 3 diff --git a/README.md b/README.md index 1a1694f5..40e40a9f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Total Downloads](https://poser.pugx.org/exercise/htmlpurifier-bundle/downloads)](https://packagist.org/packages/exercise/htmlpurifier-bundle) [![Latest Stable Version](https://poser.pugx.org/exercise/htmlpurifier-bundle/v/stable)](https://packagist.org/packages/exercise/htmlpurifier-bundle) [![License](https://poser.pugx.org/exercise/htmlpurifier-bundle/license)](https://packagist.org/packages/exercise/htmlpurifier-bundle) -[![Build Status](https://travis-ci.org/Exercise/HTMLPurifierBundle.svg?branch=2.0)](https://travis-ci.org/Exercise/HTMLPurifierBundle) +[![Build Status](https://travis-ci.org/Exercise/HTMLPurifierBundle.svg?branch=master)](https://travis-ci.org/Exercise/HTMLPurifierBundle) # ExerciseHTMLPurifierBundle @@ -36,14 +36,14 @@ Register the bundle in Symfony 3: public function registerBundles() { - return array( - new Exercise\HTMLPurifierBundle\ExerciseHTMLPurifierBundle(), + return [ // ... - ); + new Exercise\HTMLPurifierBundle\ExerciseHTMLPurifierBundle(), + ]; } ``` -## Configuration in Symfony 3 without Symfony Flex +## Configuration in Symfony 3 If you do not explicitly configure this bundle, an HTMLPurifier service will be defined as `exercise_html_purifier.default`. This behavior is the same as if you @@ -53,8 +53,7 @@ had specified the following configuration: # app/config.yml exercise_html_purifier: - default: - Cache.SerializerPath: '%kernel.cache_dir%/htmlpurifier' + default_cache_serializer_path: '%kernel.cache_dir%/htmlpurifier' ``` The `default` profile is special in that it is used as the configuration for the @@ -65,10 +64,11 @@ other profiles you might define. # app/config.yml exercise_html_purifier: - default: - Cache.SerializerPath: '%kernel.cache_dir%/htmlpurifier' - custom: - Core.Encoding: 'ISO-8859-1' + default_cache_serializer_path: '%kernel.cache_dir%/htmlpurifier' + html_profiles: + custom: + config: + Core.Encoding: 'ISO-8859-1' ``` In this example, a `exercise_html_purifier.custom` service will also be defined, @@ -81,7 +81,7 @@ option to suppress the default path. [configuration documentation]: http://htmlpurifier.org/live/configdoc/plain.html -## Configuration using Symfony Flex +## Configuration in Symfony 4 and up If you do not explicitly configure this bundle, an HTMLPurifier service will be defined as `exercise_html_purifier.default`. This behavior is the same as if you @@ -91,8 +91,7 @@ had specified the following configuration: # config/packages/exercise_html_purifier.yaml exercise_html_purifier: - default: - Cache.SerializerPath: '%kernel.cache_dir%/htmlpurifier' + default_cache_serializer_path: '%kernel.cache_dir%/htmlpurifier' ``` The `default` profile is special, it is *always* defined and its configuration @@ -104,27 +103,33 @@ configuration. # config/packages/exercise_html_purifier.yaml exercise_html_purifier: - default: - Cache.SerializerPath: '%kernel.cache_dir%/htmlpurifier' - custom: - Core.Encoding: 'ISO-8859-1' + default_cache_serializer_path: 'tmp/htmlpurifier' + html_profiles: + default: + config: + Cache.SerializerPermissions: 777 + custom: + config: + Core.Encoding: 'ISO-8859-1' ``` - + ## Autowiring By default type hinting `\HtmlPurifier` in your services will autowire the `exercise_html_purifier.default` service. To override it and use your own config as default autowired services just add -this in you `app/config/services.yml` or `config/services.yaml`: +this in you `app/config/services.yml` in you use symfony 3 or `config/services.yaml` +if you use symfony 4: ```yaml +# config/services.yaml services: - # ... - + #... + exercise_html_purifier.default: '@exercise_html_purifier.custom' ``` -## Using a custom purifier class as default +### Using a custom purifier class as default If you want to use your own class as default purifier, define a new alias: @@ -139,6 +144,29 @@ services: In such case, the custom purifier will use its own defined configuration, ignoring the bundle configuration. +### Argument binding + +The bundle also leverages the alias argument binding for each profile. So the +following config: + +```yaml + html_profiles: + blog: + # ... + gallery: + # ... +``` + +will register the following binding: + +```php + // default config is bound whichever argument name is used +public function __construct(\HTMLPurifier $purifier) {} +public function __construct(\HTMLPurifier $htmlPurifier) {} +public function __construct(\HTMLPurifier $blogPurifier) {} // blog config +public function __construct(\HTMLPurifier $galleryPurifier) {} // gallery config +``` + ## Form Type Extension This bundles provides a form type extension for filtering form fields with @@ -229,6 +257,128 @@ $builder {{ html_string|purify('custom') }} ``` +## How to Customize a Config Definition + +# Custom Attributes + +In some case, you might want to set some rules for a specific tag. +This is what the following config is about: + +```yaml +# config/packages/exercise_html_purifier.yaml +exercise_html_purifier: + html_profiles: + default: + config: + HTML.Allowed: < + *[id|class|name], + a[href|title|rel|target], + img[src|alt|height|width], + br,div,embed,object,u,em,ul,ol,li,strong,span + attributes: + img: + # attribute name, type (Integer, Color, ...) + data-id: ID + data-image-size: Text + span: + data-link: URI +``` + +See [HTMLPurifier_AttrTypes][] for more options. + + [HTMLPurifier_AttrTypes]: https://github.com/ezyang/htmlpurifier/blob/master/library/HTMLPurifier/AttrTypes.php + +# Custom Elements + +In some case, you might want to set some rules for a specific tag. +This is what the following config is about: + +```yaml +# config/packages/exercise_html_purifier.yaml +exercise_html_purifier: + html_profiles: + default: + # ... + elements: + video: + - Block + - 'Optional: (source, Flow) | (Flow, source) | Flow' + - Common # allows a set of common attributes + # The 4th and 5th arguments are optional + - src: URI # list of type rules by attributes + type: Text + width: Length + height: Length + poster: URI + preload: 'Enum#auto,metadata,none' + controls: Bool + source: + - Block + - Flow + - Common + - { src: URI, type: Text } + - [style] # list of forbidden attributes +``` + +Would be equivalent to: + +```php +$def = $config->getHTMLDefintion(true); +$def->addElement('video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [ + 'src' => 'URI', + 'type' => 'Text', + 'width' => 'Length', + 'height' => 'Length', + 'poster' => 'URI', + 'preload' => 'Enum#auto,metadata,none', + 'controls' => 'Bool', +]); +$source = $def->addElement('source', 'Block', 'Flow', 'Common', [ + 'src' => 'URI', + 'type' => 'Text', +]); +$source->excludes = ['style' => true]; +``` + +See [HTMLPurifier documentation][] for more details. + + [HTMLPurifier documentation]: http://htmlpurifier.org/docs/enduser-customize.html + +# Blank Elements + +It might happen that you need a tag clean from any attributes. +Then just add it to the list: + +```yaml +# config/packages/exercise_html_purifier.yaml +exercise_html_purifier: + html_profiles: + default: + # ... + blank_elements: [legend, figcaption] +``` + +## How to Reuse Profiles + +What can really convenient is to reuse some profile definition +to build other custom definitions. + +```yaml +# config/packages/exercise_html_purifier.yaml +exercise_html_purifier: + html_profiles: + base: + # ... + video: + # ... + all: + parents: [base, video] +``` + +In this example the profile named "all" will inherit the "default" profile, +then the two custom ones. The order is important as each profile overrides the +previous, and "all" could define its own rules too. + ## Contributing PRs are welcomed :). Please target the `2.0` branch for bug fixes and `master` diff --git a/Tests/HTMLPurifierConfigFactoryTest.php b/Tests/HTMLPurifierConfigFactoryTest.php new file mode 100644 index 00000000..e0a129d6 --- /dev/null +++ b/Tests/HTMLPurifierConfigFactoryTest.php @@ -0,0 +1,67 @@ +mkdir(self::$cacheDir); + } + + public static function tearDownAfterClass(): void + { + (new Filesystem())->remove(self::$cacheDir); + } + + public function testCreateUseDoesNotBuildDefinitionByDefault() + { + TestHTMLPurifierConfigFactory::create('default', []); + + $this->assertSame(0, TestHTMLPurifierConfigFactory::$calledBuild); + } + + public function testCreateUseSerializedCache() + { + $configArgs = [ + 'test', /* profile */ + [/* config array */ + 'Cache.SerializerPath' => self::$cacheDir, + 'HTML.Nofollow' => true, + ], + null, /* default config */ + [], /* parents */ + ['a' => ['href' => 'URI']], /* attributes */ + ]; + + (new \HTMLPurifier( + TestHTMLPurifierConfigFactory::create(...$configArgs) + ))->purify('
test
'); + + TestHTMLPurifierConfigFactory::create(...$configArgs); + + $this->assertSame(1, TestHTMLPurifierConfigFactory::$calledBuild); + } +} + +class TestHTMLPurifierConfigFactory extends HTMLPurifierConfigFactory +{ + public static $calledBuild = 0; + + public static function buildHTMLDefinition( + \HTMLPurifier_Definition $def, + array $attributes, + array $elements, + array $blankElements + ): void { + ++self::$calledBuild; + parent::buildHTMLDefinition($def, $attributes, $elements, $blankElements); + } +} diff --git a/composer.json b/composer.json index 6c0bd3e5..c9aee97b 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", + "php": "^7.1.3", "ezyang/htmlpurifier": "~4.0", "symfony/config": "~3.4 || ~4.0 || ^5.0", "symfony/dependency-injection": "~3.4.1 || ^4.0.1 || ^5.0", @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ad6b264b..60a44c95 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,6 @@ - ./tests diff --git a/src/CacheWarmer/SerializerCacheWarmer.php b/src/CacheWarmer/SerializerCacheWarmer.php index bee1fd53..a0f9c762 100644 --- a/src/CacheWarmer/SerializerCacheWarmer.php +++ b/src/CacheWarmer/SerializerCacheWarmer.php @@ -2,30 +2,40 @@ namespace Exercise\HTMLPurifierBundle\CacheWarmer; +use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; /** * Cache warmer for creating HTMLPurifier's cache directory and contents. * - * Run purify() with various contents to have the caches built here, and not - * on first use, as the owning user may be different then, causing problems - * with file ownership when deleting the cached files later. + * Create all purifiers to generate their caches here, and not on first use, as + * the owning user may be different then, causing problems with file ownership + * when deleting the cached files later. + * + * See https://github.com/Exercise/HTMLPurifierBundle/issues/22 * * @author Henrik Bjornskov + * @author Jules Pietri */ class SerializerCacheWarmer implements CacheWarmerInterface { private $paths; - private $htmlPurifier; + private $profiles; + private $registry; + private $filesystem; /** - * @param string[] $paths - * @param \HTMLPurifier $htmlPurifier Used to build cache within bundle runtime + * @param string[] $paths + * @param string[] $profiles + * @param HTMLPurifiersRegistryInterface $registry Used to build cache within bundle runtime */ - public function __construct(array $paths, \HTMLPurifier $htmlPurifier) + public function __construct(array $paths, array $profiles, HTMLPurifiersRegistryInterface $registry, Filesystem $filesystem) { $this->paths = $paths; - $this->htmlPurifier = $htmlPurifier; + $this->profiles = $profiles; + $this->registry = $registry; + $this->filesystem = $filesystem; } /** @@ -34,19 +44,14 @@ public function __construct(array $paths, \HTMLPurifier $htmlPurifier) public function warmUp($cacheDir) { foreach ($this->paths as $path) { - if (!is_dir($path)) { - if (false === @mkdir($path, 0777, true)) { - throw new \RuntimeException(sprintf('Unable to create the HTMLPurifier Serializer cache directory "%s".', $path)); - } - } elseif (!is_writable($path)) { - throw new \RuntimeException(sprintf('The HTMLPurifier Serializer cache directory "%s" is not writeable for the current system user.', $path)); - } + $this->filesystem->remove($path); // clean previous cache + $this->filesystem->mkdir($path); } - // build htmlPurifier cache for HTML/CSS & URIs with the other Symfony cache warmups. - // see https://github.com/Exercise/HTMLPurifierBundle/issues/22 - $this->htmlPurifier->purify('
-2
'); - $this->htmlPurifier->purify('
'); + foreach ($this->profiles as $profile) { + // Will build the configuration + $this->registry->get($profile)->purify("
"); + } } /** diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index f30b6a76..8ecee7ad 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -4,6 +4,7 @@ use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; class Configuration implements ConfigurationInterface { @@ -22,10 +23,77 @@ public function getConfigTreeBuilder() } $rootNode - ->useAttributeAsKey('name') - ->prototype('array') - ->useAttributeAsKey('name') - ->prototype('variable') + ->children() + ->scalarNode('default_cache_serializer_path') + ->defaultValue('%kernel.cache_dir%/htmlpurifier') + ->end() + ->arrayNode('html_profiles') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->validate() + ->always(function ($profiles) { + foreach ($profiles as $profile => $definition) { + foreach ($definition['parents'] as $parent) { + if (!isset($profiles[$parent])) { + throw new InvalidConfigurationException(sprintf('Invalid parent "%s" is not defined for profile "%s".', $parent, $profile)); + } + } + } + + return $profiles; + }) + ->end() + ->arrayPrototype() + ->children() + ->arrayNode('config') + ->defaultValue([]) + ->info('An array of parameters.') + ->useAttributeAsKey('parameter') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->arrayNode('attributes') + ->defaultValue([]) + ->info('Every key is a tag name, with arrays for rules') + ->normalizeKeys(false) + ->useAttributeAsKey('tag_name') + ->arrayPrototype() + ->info('Every key is an attribute name for a rule like "Text"') + ->useAttributeAsKey('attribute_name') + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->end() + ->end() + ->arrayNode('elements') + ->defaultValue([]) + ->info('Every key is a tag name, with an array of four values as definition. The fourth is an optional array of attributes rules.') + ->normalizeKeys(false) + ->useAttributeAsKey('tag_name') + ->info('An array represents a definition, with three required elements: a type ("Inline", "Block", ...), a content type ("Empty", "Optional: #PCDATA", ...), an attributes set ("Core", "Common", ...), a fourth optional may define attributes rules as array, and fifth for forbidden attributes.') + ->arrayPrototype() + ->validate() + ->ifTrue(function ($array) { + $count = count($array); + + return 3 > $count || $count > 5; + }) + ->thenInvalid('An element definition must define three to five elements: a type ("Inline", "Block", ...), a content type ("Empty", "Optional: #PCDATA", ...), an attributes set ("Core", "Common", ...), and a fourth optional may define attributes rules as array, and fifth for forbidden attributes.') + ->end() + ->variablePrototype()->end() + ->end() + ->end() + ->arrayNode('blank_elements') + ->defaultValue([]) + ->info('An array of tag names that should purify everything.') + ->scalarPrototype()->end() + ->end() + ->arrayNode('parents') + ->defaultValue([]) + ->info('An array of config names that should be inherited.') + ->scalarPrototype()->end() + ->end() + ->end() + ->end() ->end() ->end() ; diff --git a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php index c8aae1b6..76965aaf 100644 --- a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -3,6 +3,7 @@ namespace Exercise\HTMLPurifierBundle\DependencyInjection; use Exercise\HTMLPurifierBundle\DependencyInjection\Compiler\HTMLPurifierPass; +use Exercise\HTMLPurifierBundle\HTMLPurifierConfigFactory; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistry; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; use Symfony\Component\Config\FileLocator; @@ -13,59 +14,61 @@ class ExerciseHTMLPurifierExtension extends Extension { - /** - * {@inheritdoc} - */ public function load(array $configs, ContainerBuilder $container) { $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('html_purifier.xml'); - /* Prepend the default configuration. This cannot be defined within the - * Configuration class, since the root node's children are array - * prototypes. - * - * This cache path may be suppressed by either unsetting the "default" - * configuration (relying on canBeUnset() on the prototype node) or - * setting the "Cache.SerializerPath" option to null. - */ - array_unshift($configs, [ - 'default' => [ - 'Cache.SerializerPath' => '%kernel.cache_dir%/htmlpurifier', - ], - ]); - $configs = $this->processConfiguration(new Configuration(), $configs); + // Set default serializer cache path, while ensuring a default profile is defined + $configs['html_profiles']['default']['config']['Cache.SerializerPath'] = $configs['default_cache_serializer_path']; + $serializerPaths = []; + // Drop when require Symfony > 3.4 + $registerAlias = method_exists($container, 'registerAliasForArgument'); - foreach ($configs as $name => $config) { + foreach ($configs['html_profiles'] as $name => $definition) { $configId = "exercise_html_purifier.config.$name"; - $configDefinition = $container->register($configId, \HTMLPurifier_Config::class) - ->setPublic(false) - ; + $default = null; + $parents = []; // stores inherited configs - if ('default' === $name) { - $configDefinition - ->setFactory([\HTMLPurifier_Config::class, 'create']) - ->addArgument($config) - ; - } else { - $configDefinition - ->setFactory([\HTMLPurifier_Config::class, 'inherit']) - ->addArgument(new Reference('exercise_html_purifier.config.default')) - ->addMethodCall('loadArray', [$config]) - ; + if ('default' !== $name) { + $default = new Reference('exercise_html_purifier.config.default'); + $parentNames = $definition['parents']; + + unset($parentNames['default']); // default is always inherited + foreach ($parentNames as $parentName) { + self::resolveProfileInheritance($parentName, $configs['html_profiles'], $parents); + } } - $container->register("exercise_html_purifier.$name", \HTMLPurifier::class) - ->addArgument(new Reference($configId)) + $container->register($configId, \HTMLPurifier_Config::class) + ->setFactory([HTMLPurifierConfigFactory::class, 'create']) + ->setArguments([ + $name, + $definition['config'], + $default, + self::getResolvedConfig('config', $parents), + self::getResolvedConfig('attributes', $parents, $definition), + self::getResolvedConfig('elements', $parents, $definition), + self::getResolvedConfig('blank_elements', $parents, $definition), + ]) + ; + + $id = "exercise_html_purifier.$name"; + $container->register($id, \HTMLPurifier::class) + ->setArguments([new Reference($configId)]) ->addTag(HTMLPurifierPass::PURIFIER_TAG, ['profile' => $name]) ; - if (isset($config['Cache.SerializerPath'])) { - $serializerPaths[] = $config['Cache.SerializerPath']; + if (isset($definition['config']['Cache.SerializerPath'])) { + $serializerPaths[] = $definition['config']['Cache.SerializerPath']; + } + + if ($registerAlias && $default) { + $container->registerAliasForArgument($id, \HTMLPurifier::class, "$name.purifier"); } } @@ -78,14 +81,43 @@ public function load(array $configs, ContainerBuilder $container) $container->setAlias(\HTMLPurifier::class, 'exercise_html_purifier.default') ->setPublic(false) ; - $container->setParameter('exercise_html_purifier.cache_warmer.serializer.paths', array_unique($serializerPaths)); + $container->getDefinition('exercise_html_purifier.cache_warmer.serializer') + ->setArgument(0, array_unique($serializerPaths)) + ->setArgument(1, array_keys($configs['html_profiles'])) + ; } - /** - * {@inheritdoc} - */ public function getAlias() { return 'exercise_html_purifier'; } + + private static function resolveProfileInheritance(string $parent, array $configs, array &$resolved): void + { + if (isset($resolved[$parent])) { + // Another profile already inherited this config, skip + return; + } + + foreach ($configs[$parent]['parents'] as $grandParent) { + self::resolveProfileInheritance($grandParent, $configs, $resolved); + } + + $resolved[$parent]['config'] = $configs[$parent]['config']; + $resolved[$parent]['attributes'] = $configs[$parent]['attributes']; + $resolved[$parent]['elements'] = $configs[$parent]['elements']; + $resolved[$parent]['blank_elements'] = $configs[$parent]['blank_elements']; + } + + private static function getResolvedConfig(string $parameter, array $parents, array $definition = null): array + { + if (null !== $definition) { + return array_filter(array_merge( + array_column($parents, $parameter), + isset($definition[$parameter]) ? $definition[$parameter] : [] + )); + } + + return array_filter(array_column($parents, $parameter)); + } } diff --git a/src/Form/Listener/HTMLPurifierListener.php b/src/Form/Listener/HTMLPurifierListener.php index 846151fe..98d86dd1 100644 --- a/src/Form/Listener/HTMLPurifierListener.php +++ b/src/Form/Listener/HTMLPurifierListener.php @@ -12,16 +12,13 @@ class HTMLPurifierListener implements EventSubscriberInterface private $registry; private $profile; - /** - * @param string $profile - */ - public function __construct(HTMLPurifiersRegistryInterface $registry, $profile) + public function __construct(HTMLPurifiersRegistryInterface $registry, string $profile) { $this->registry = $registry; $this->profile = $profile; } - public function purifySubmittedData(FormEvent $event) + public function purifySubmittedData(FormEvent $event): void { if (!is_scalar($data = $event->getData())) { // Hope there is a view transformer, otherwise an error might happen @@ -49,10 +46,7 @@ public static function getSubscribedEvents() ]; } - /** - * @return \HTMLPurifier - */ - private function getPurifier() + private function getPurifier(): \HTMLPurifier { return $this->registry->get($this->profile); } diff --git a/src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php b/src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php deleted file mode 100644 index 4f1f1eeb..00000000 --- a/src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php +++ /dev/null @@ -1,47 +0,0 @@ - ['src' => 'URI', 'data-type' => Text']] ] + * @param array $elements An array of arrays by element to add or override, arrays must + * hold a type ("Inline, "Block", ...), a content type ("Empty", + * "Optional: #PCDATA", ...), an attributes set ("Core", "Common", + * ...), a fourth optional may define attributes rules as array, and + * a fifth to list forbidden attributes + * @param array $blankElements An array of tag names that should not have any attributes + */ + public static function create( + string $profile, + array $configArray, + \HTMLPurifier_Config $defaultConfig = null, + array $parents = [], + array $attributes = [], + array $elements = [], + array $blankElements = [] + ): \HTMLPurifier_Config { + if ($defaultConfig) { + $config = \HTMLPurifier_Config::inherit($defaultConfig); + } else { + $config = \HTMLPurifier_Config::createDefault(); + } + + foreach ($parents as $parent) { + $config->loadArray($parent); + } + + $config->loadArray($configArray); + + // Make the config unique + $config->set('HTML.DefinitionID', $profile); + $config->set('HTML.DefinitionRev', 1); + + $def = $config->maybeGetRawHTMLDefinition(); + + // If the definition is not cached, build it + if ($def && ($attributes || $elements || $blankElements)) { + static::buildHTMLDefinition($def, $attributes, $elements, $blankElements); + } + + return $config; + } + + /** + * Builds a config definition from the given parameters. + * + * This build should never happen on runtime, since purifiers cache should + * be generated during warm up. + */ + public static function buildHTMLDefinition(\HTMLPurifier_Definition $def, array $attributes, array $elements, array $blankElements): void + { + foreach ($attributes as $elementName => $rule) { + foreach ($rule as $attributeName => $definition) { + /* @see \HTMLPurifier_AttrTypes */ + $def->addAttribute($elementName, $attributeName, $definition); + } + } + + foreach ($elements as $elementName => $config) { + /* @see \HTMLPurifier_HTMLModule::addElement() */ + $el = $def->addElement($elementName, $config[0], $config[1], $config[2], isset($config[3]) ? $config[3] : []); + + if (isset($config[4])) { + $el->excludes = array_fill_keys($config[4], true); + } + } + + foreach ($blankElements as $blankElement) { + /* @see \HTMLPurifier_HTMLModule::addBlankElement() */ + $def->addBlankElement($blankElement); + } + } +} diff --git a/src/HTMLPurifiersRegistry.php b/src/HTMLPurifiersRegistry.php index a96917ce..ec8f39c8 100644 --- a/src/HTMLPurifiersRegistry.php +++ b/src/HTMLPurifiersRegistry.php @@ -16,7 +16,7 @@ public function __construct(ContainerInterface $purifiersLocator) /** * {@inheritdoc} */ - public function has($profile) + public function has(string $profile): bool { return $this->purifiersLocator->has($profile); } @@ -24,7 +24,7 @@ public function has($profile) /** * {@inheritdoc} */ - public function get($profile) + public function get(string $profile): \HTMLPurifier { return $this->purifiersLocator->get($profile); } diff --git a/src/HTMLPurifiersRegistryInterface.php b/src/HTMLPurifiersRegistryInterface.php index 06136d49..0f484a72 100644 --- a/src/HTMLPurifiersRegistryInterface.php +++ b/src/HTMLPurifiersRegistryInterface.php @@ -4,17 +4,7 @@ interface HTMLPurifiersRegistryInterface { - /** - * @param string $profile - * - * @return bool - */ - public function has($profile); + public function has(string $profile): bool; - /** - * @param string $profile - * - * @return \HTMLPurifier - */ - public function get($profile); + public function get(string $profile): \HTMLPurifier; } diff --git a/src/Resources/config/html_purifier.xml b/src/Resources/config/html_purifier.xml index f3e2bc63..d3bfac09 100644 --- a/src/Resources/config/html_purifier.xml +++ b/src/Resources/config/html_purifier.xml @@ -4,8 +4,10 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - %exercise_html_purifier.cache_warmer.serializer.paths% - + + + + diff --git a/src/Twig/HTMLPurifierRuntime.php b/src/Twig/HTMLPurifierRuntime.php index 025cf0e7..8c152528 100644 --- a/src/Twig/HTMLPurifierRuntime.php +++ b/src/Twig/HTMLPurifierRuntime.php @@ -22,7 +22,7 @@ public function __construct(HTMLPurifiersRegistryInterface $registry) * * @return string The purified html string */ - public function purify($string, $profile = 'default') + public function purify(string $string, string $profile = 'default'): string { return $this->getHTMLPurifierForProfile($profile)->purify($string); } @@ -30,13 +30,9 @@ public function purify($string, $profile = 'default') /** * Gets the HTMLPurifier service corresponding to the given profile. * - * @param string $profile - * - * @return \HTMLPurifier - * * @throws \InvalidArgumentException If the profile does not exist */ - private function getHTMLPurifierForProfile($profile) + private function getHTMLPurifierForProfile(string $profile): \HTMLPurifier { return $this->purifiersRegistry->get($profile); } diff --git a/tests/CacheWarmer/SerializerCacheWarmerTest.php b/tests/CacheWarmer/SerializerCacheWarmerTest.php index fa20dd62..42856753 100644 --- a/tests/CacheWarmer/SerializerCacheWarmerTest.php +++ b/tests/CacheWarmer/SerializerCacheWarmerTest.php @@ -3,46 +3,60 @@ namespace Exercise\HTMLPurifierBundle\Tests\CacheWarmer; use Exercise\HTMLPurifierBundle\CacheWarmer\SerializerCacheWarmer; +use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; use PHPUnit\Framework\TestCase; +use Symfony\Component\Filesystem\Filesystem; class SerializerCacheWarmerTest extends TestCase { public function testShouldBeRequired() { - $cacheWarmer = new SerializerCacheWarmer([], new \HTMLPurifier()); + $cacheWarmer = new SerializerCacheWarmer([], [], $this->createMock(HTMLPurifiersRegistryInterface::class), new Filesystem()); + $this->assertFalse($cacheWarmer->isOptional()); } - public function testFailsWhenNotWriteable() + public function testWarmUpShouldCreatePaths() { - $path = sys_get_temp_dir().'/'.uniqid('htmlpurifierbundle_fails'); + $fs = new Filesystem(); + $path = sys_get_temp_dir().DIRECTORY_SEPARATOR.'html_purifier'; - if (false === @mkdir($path, 0000)) { - $this->markTestSkipped('Tmp dir is not writeable.'); + if ($fs->exists($path)) { + $fs->remove($path); } - $this->expectException('RuntimeException'); + $this->assertFalse($fs->exists($path)); - $cacheWarmer = new SerializerCacheWarmer([$path], new \HTMLPurifier()); + $cacheWarmer = new SerializerCacheWarmer([$path], [], $this->createMock(HTMLPurifiersRegistryInterface::class), $fs); $cacheWarmer->warmUp(null); - @rmdir($path); + $this->assertTrue($fs->exists($path)); + + $fs->remove($path); } - public function testShouldCreatePaths() + public function testWarmUpShouldCallPurifyForEachProfile() { - if (!is_writable(sys_get_temp_dir())) { - $this->markTestSkipped(sprintf('The system temp directory "%s" is not writeable for the current system user.', sys_get_temp_dir())); - } - - $path = sys_get_temp_dir().'/'.uniqid('htmlpurifierbundle'); - - $cacheWarmer = new SerializerCacheWarmer([$path], new \HTMLPurifier()); + $purifier = $this->createMock(\HTMLPurifier::class); + $purifier->expects($this->exactly(2)) + ->method('purify') + ; + + $registry = $this->createMock(HTMLPurifiersRegistryInterface::class); + $registry->expects($this->exactly(2)) + ->method('get') + ->willReturn($purifier) + ; + $registry->expects($this->at(0)) + ->method('get') + ->with('first') + ; + $registry->expects($this->at(1)) + ->method('get') + ->with('second') + ; + + $cacheWarmer = new SerializerCacheWarmer([], ['first', 'second'], $registry, new Filesystem()); $cacheWarmer->warmUp(null); - - $this->assertTrue(is_dir($path)); - $this->assertTrue(is_writeable($path)); - - rmdir($path); } } diff --git a/tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php b/tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php index b7c88e4a..7c484f2f 100644 --- a/tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php +++ b/tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php @@ -5,9 +5,9 @@ use Exercise\HTMLPurifierBundle\DependencyInjection\Compiler\HTMLPurifierPass; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistry; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; -use Exercise\HTMLPurifierBundle\Tests\ForwardCompatTestTrait; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -16,12 +16,10 @@ class HTMLPurifierPassTest extends TestCase { - use ForwardCompatTestTrait; - /** @var ContainerBuilder|MockObject */ private $container; - private function doSetUp() + protected function setUp(): void { $this->container = $this->createPartialMock(ContainerBuilder::class, [ 'hasAlias', @@ -31,7 +29,7 @@ private function doSetUp() ]); } - private function doTearDown() + protected function tearDown(): void { $this->container = null; } @@ -95,6 +93,22 @@ public function testProcessDoNothingIfRegistryIsNotDefined() $pass = new HTMLPurifierPass(); $pass->process($this->container); } + + public function testProcessFailsIfTaggedServiceMissesProfileName() + { + $container = new ContainerBuilder(); + $container->register(DummyPurifier::class) + ->addTag('exercise.html_purifier') + ; + $container->register('exercise_html_purifier.purifiers_registry', HTMLPurifiersRegistry::class); + $container->setAlias(HTMLPurifiersRegistryInterface::class, 'exercise_html_purifier.purifiers_registry'); + + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('Tag "exercise.html_purifier" must define a "profile" attribute.'); + + $pass = new HTMLPurifierPass(); + $pass->process($container); + } } class DummyPurifier extends \HTMLPurifier diff --git a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php index 5e445548..274e5431 100644 --- a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php +++ b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php @@ -4,16 +4,17 @@ use Exercise\HTMLPurifierBundle\DependencyInjection\Compiler\HTMLPurifierPass; use Exercise\HTMLPurifierBundle\DependencyInjection\ExerciseHTMLPurifierExtension; -use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistry; +use Exercise\HTMLPurifierBundle\HTMLPurifierConfigFactory; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; -use Exercise\HTMLPurifierBundle\Tests\ForwardCompatTestTrait; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; class ExerciseHTMLPurifierExtensionTest extends TestCase { - use ForwardCompatTestTrait; + private const DEFAULT_CACHE_PATH = '%kernel.cache_dir%/htmlpurifier'; /** * @var ContainerBuilder @@ -30,16 +31,17 @@ class ExerciseHTMLPurifierExtensionTest extends TestCase */ private $defaultConfig; - private function doSetUp() + public function setUp(): void { $this->container = new ContainerBuilder(); + $this->container->setParameter('kernel.cache_dir', '/tmp'); $this->extension = new ExerciseHTMLPurifierExtension(); $this->defaultConfig = [ - 'Cache.SerializerPath' => '%kernel.cache_dir%/htmlpurifier', + 'Cache.SerializerPath' => self::DEFAULT_CACHE_PATH, ]; } - private function doTearDown() + public function tearDown(): void { $this->defaultConfig = null; $this->extension = null; @@ -51,77 +53,321 @@ public function testShouldLoadDefaultConfiguration() $this->extension->load([], $this->container); $this->assertDefaultConfigDefinition($this->defaultConfig); - $this->assertCacheWarmerSerializerPaths(['%kernel.cache_dir%/htmlpurifier']); + $this->assertCacheWarmerSerializerArgs([self::DEFAULT_CACHE_PATH], ['default']); $this->assertRegistryHasProfiles(['default']); } + public function testInvalidParent() + { + $config = [ + 'html_profiles' => [ + 'custom' => [ + 'config' => ['AutoFormat.AutoParagraph' => true], + ], + 'custom_2' => [ + 'config' => ['AutoFormat.Linkify' => true], + 'parents' => ['custom', 'unknown'], + ], + ], + ]; + + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('Invalid parent "unknown" is not defined for profile "custom_2".'); + + $this->extension->load([$config], $this->container); + } + + /** + * @dataProvider provideInvalidElementDefinitions + */ + public function testInvalidElements(array $elementDefinition) + { + $config = [ + 'html_profiles' => [ + 'default' => [ + 'elements' => ['a' => []], + ], + ], + ]; + + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('Invalid configuration for path "exercise_html_purifier.html_profiles.default.elements.a": An element definition must define three to five elements: a type ("Inline", "Block", ...), a content type ("Empty", "Optional: #PCDATA", ...), an attributes set ("Core", "Common", ...), and a fourth optional may define attributes rules as array, and fifth for forbidden attributes.'); + + $this->extension->load([$config], $this->container); + } + + public function provideInvalidElementDefinitions(): iterable + { + yield 'empty array' => [[]]; + yield 'only one argument' => [['']]; + yield 'only two arguments' => [['', '']]; + yield 'too many arguments' => [['', '', '', [], [], 'extra argument']]; + } + public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPath() { $config = [ - 'default' => [ - 'AutoFormat.AutoParagraph' => true, - 'Cache.SerializerPath' => null, + 'default_cache_serializer_path' => null, + 'html_profiles' => [ + 'default' => [ + 'config' => [ + 'AutoFormat.AutoParagraph' => true, + ], + ], ], ]; $this->extension->load([$config], $this->container); - $this->assertDefaultConfigDefinition($config['default']); - $this->assertCacheWarmerSerializerPaths([]); + $this->assertDefaultConfigDefinition(array_merge($config['html_profiles']['default']['config'], [ + 'Cache.SerializerPath' => null, + ])); + $this->assertCacheWarmerSerializerArgs([], ['default']); $this->assertRegistryHasProfiles(['default']); } public function testShouldNotDeepMergeOptions() { $configs = [ - ['default' => [ - 'Core.HiddenElements' => ['script' => true], - 'Cache.SerializerPath' => null, + ['html_profiles' => [ + 'default' => [ + 'config' => [ + 'Core.HiddenElements' => ['script' => true], + ], + ], ]], - ['default' => [ - 'Core.HiddenElements' => ['style' => true], + ['html_profiles' => [ + 'default' => [ + 'config' => [ + 'Core.HiddenElements' => ['style' => true], + ], + ], ]], ]; $this->extension->load($configs, $this->container); - $this->assertDefaultConfigDefinition([ + $this->assertDefaultConfigDefinition(array_merge([ 'Core.HiddenElements' => ['style' => true], - 'Cache.SerializerPath' => null, - ]); - $this->assertCacheWarmerSerializerPaths([]); + ], $this->defaultConfig)); + $this->assertCacheWarmerSerializerArgs([self::DEFAULT_CACHE_PATH], ['default']); $this->assertRegistryHasProfiles(['default']); } public function testShouldLoadCustomConfiguration() { $config = [ - 'default' => [ - 'AutoFormat.AutoParagraph' => true, + 'html_profiles' => [ + 'default' => [ + 'config' => [ + 'AutoFormat.AutoParagraph' => true, + ], + ], + 'simple' => [ + 'config' => [ + 'Cache.DefinitionImpl' => null, + 'Cache.SerializerPath' => '%kernel.cache_dir%/htmlpurifier-simple', + 'AutoFormat.Linkify' => true, + 'AutoFormat.RemoveEmpty' => true, + 'AutoFormat.RemoveEmpty.RemoveNbsp' => true, + 'HTML.Allowed' => 'a[href],strong,em,p,li,ul,ol', + ], + ], + 'advanced' => [ + 'config' => [ + 'Cache.DefinitionImpl' => null, + ], + ], + ], + ]; + + $this->extension->load([$config], $this->container); + + $profiles = ['default', 'simple', 'advanced']; + + $this->assertDefaultConfigDefinition(array_merge($config['html_profiles']['default']['config'], $this->defaultConfig)); + $this->assertConfigDefinition('simple', $config['html_profiles']['simple']['config']); + $this->assertConfigDefinition('advanced', $config['html_profiles']['advanced']['config']); + $this->assertCacheWarmerSerializerArgs([ + self::DEFAULT_CACHE_PATH, + self::DEFAULT_CACHE_PATH.'-simple', + ], $profiles); + $this->assertRegistryHasProfiles($profiles); + } + + public function testShouldLoadComplexCustomConfiguration() + { + $defaultConfig = [ + 'AutoFormat.AutoParagraph' => true, + ]; + $defaultAttributes = [ + 'a' => ['href' => 'URI'], + 'span' => ['data-link' => 'URI'], + ]; + $defaultBlankElements = [ + 'figcaption', + 'legend', + ]; + $simpleConfig = [ + 'AutoFormat.Linkify' => true, + 'AutoFormat.RemoveEmpty' => true, + 'AutoFormat.RemoveEmpty.RemoveNbsp' => true, + 'HTML.Allowed' => 'a[href],strong,em,p,li,ul,ol', + ]; + $videoElements = [ + 'video' => [ + 'Block', + 'Optional: (source, Flow) | (Flow, source) | Flow', + 'Common', + [ + 'src' => 'URI', + 'type' => 'Text', + 'width' => 'Length', + 'height' => 'Length', + 'poster' => 'URI', + 'preload' => 'Enum#auto,metadata,none', + 'controls' => 'Bool', + ], ], - 'simple' => [ - 'Cache.DefinitionImpl' => null, - 'Cache.SerializerPath' => '%kernel.cache_dir%/htmlpurifier-simple', - 'AutoFormat.Linkify' => true, - 'AutoFormat.RemoveEmpty' => true, - 'AutoFormat.RemoveEmpty.RemoveNbsp' => true, - 'HTML.Allowed' => 'a[href],strong,em,p,li,ul,ol', + ]; + $advancedConfig = [ + 'Core.HiddenElements' => ['script' => true], + ]; + $allParents = ['simple', 'video', 'advanced']; + + $config = [ + 'html_profiles' => [ + 'default' => [ + 'config' => $defaultConfig, + 'attributes' => $defaultAttributes, + 'blank_elements' => $defaultBlankElements, + ], + 'simple' => [ + 'config' => $simpleConfig, + ], + 'video' => [ + 'elements' => $videoElements, + ], + 'advanced' => [ + 'config' => $advancedConfig, + ], + 'all' => [ + 'parents' => $allParents, + ], ], - 'advanced' => [ - 'Cache.DefinitionImpl' => null, + ]; + + $this->extension->load([$config], $this->container); + + $profiles = ['default', 'simple', 'video', 'advanced', 'all']; + + $this->assertDefaultConfigDefinition( + array_merge($defaultConfig, $this->defaultConfig), + $defaultAttributes, + [], + $defaultBlankElements + ); + $this->assertConfigDefinition('simple', $simpleConfig); + $this->assertConfigDefinition( + 'video', + [/* config */], + [/* parents */], + [/* attributes */], + $videoElements + ); + $this->assertConfigDefinition('advanced', $advancedConfig); + $this->assertConfigDefinition( + 'all', + [/* config */], + [$simpleConfig, /* video config is filtered */ 2 => $advancedConfig], + [/* attributes */], + [/* simple elements are filtered */ 1 => $videoElements], + [/* blank elements */] + ); + $this->assertCacheWarmerSerializerArgs([self::DEFAULT_CACHE_PATH], $profiles); + $this->assertRegistryHasProfiles($profiles); + } + + public function testShouldRegisterAliases() + { + if (!method_exists($this->container, 'registerAliasForArgument')) { + $this->markTestSkipped('Alias arguments binding is not available.'); + } + + $config = [ + 'html_profiles' => [ + 'default' => [ + 'config' => [ + 'AutoFormat.AutoParagraph' => true, + ], + ], + 'simple' => [ + 'config' => [ + 'HTML.Allowed' => 'a[href],strong,em,p,li,ul,ol', + ], + ], + 'advanced' => [ + 'config' => [ + 'Core.HiddenElements' => ['script' => true], + ], + ], ], ]; $this->extension->load([$config], $this->container); - $this->assertDefaultConfigDefinition(array_replace($this->defaultConfig, $config['default'])); - $this->assertConfigDefinition('simple', $config['simple']); - $this->assertConfigDefinition('advanced', $config['advanced']); - $this->assertCacheWarmerSerializerPaths([ - '%kernel.cache_dir%/htmlpurifier', - '%kernel.cache_dir%/htmlpurifier-simple', - ]); - $this->assertRegistryHasProfiles(['default', 'simple', 'advanced']); + $this->container->register(ServiceWithDefaultConfig::class) + ->setAutowired(true) + ->setPublic(true) + ; + $this->container->register(ServiceWithDefaultConfig2::class) + ->setAutowired(true) + ->setPublic(true) + ; + $this->container->register(ServiceWithSimpleConfig::class) + ->setAutowired(true) + ->setPublic(true) + ; + $this->container->register(ServiceWithAdvancedConfig::class) + ->setAutowired(true) + ->setPublic(true) + ; + + $this->container->compile(); + + $defaultConfigArgument1 = $this->container->findDefinition(ServiceWithDefaultConfig::class) + ->getArgument(0) + ; + + $this->assertInstanceOf(Reference::class, $defaultConfigArgument1); + $this->assertSame('exercise_html_purifier.default', (string) $defaultConfigArgument1); + + $defaultConfigArgument2 = $this->container->findDefinition(ServiceWithDefaultConfig2::class) + ->getArgument(0) + ; + + $this->assertInstanceOf(Reference::class, $defaultConfigArgument2); + $this->assertSame('exercise_html_purifier.default', (string) $defaultConfigArgument2); + + $simpleConfigArgument = $this->container->findDefinition(ServiceWithSimpleConfig::class) + ->getArgument(0) + ; + + $this->assertInstanceOf(Definition::class, $simpleConfigArgument); + $this->assertSame( + 'simple', + $simpleConfigArgument->getTag(HTMLPurifierPass::PURIFIER_TAG)[0]['profile'] ?? '' + ); + + $advancedConfigArgument = $this->container->findDefinition(ServiceWithAdvancedConfig::class) + ->getArgument(0) + ; + + $this->assertInstanceOf(Definition::class, $advancedConfigArgument); + $this->assertSame( + 'advanced', + $advancedConfigArgument->getTag(HTMLPurifierPass::PURIFIER_TAG)[0]['profile'] ?? '' + ); } /** @@ -130,30 +376,39 @@ public function testShouldLoadCustomConfiguration() * * @param string $name */ - private function assertConfigDefinition($name, array $config) + private function assertConfigDefinition($name, array $config, array $parents = [], array $attributes = [], array $elements = [], array $blankElements = []) { $this->assertTrue($this->container->hasDefinition('exercise_html_purifier.config.'.$name)); $definition = $this->container->getDefinition('exercise_html_purifier.config.'.$name); - $this->assertSame([\HTMLPurifier_Config::class, 'inherit'], $definition->getFactory()); + $this->assertEquals([new Reference(HTMLPurifierConfigFactory::class), 'create'], $definition->getFactory()); $args = $definition->getArguments(); - - $this->assertCount(1, $args); - $this->assertEquals([$config], $definition->getMethodCalls()[0][1]); + $defaultConfig = $definition->getArgument(2); + + $this->assertCount(7, $args); + $this->assertSame($name, $definition->getArgument(0)); + $this->assertSame($config, $definition->getArgument(1)); + $this->assertInstanceOf(Reference::class, $defaultConfig); + $this->assertSame('exercise_html_purifier.config.default', (string) $defaultConfig); + $this->assertSame($parents, $definition->getArgument(3)); + $this->assertSame($attributes, $definition->getArgument(4)); + $this->assertSame($elements, $definition->getArgument(5)); + $this->assertSame($blankElements, $definition->getArgument(6)); } /** * Asserts that the default config definition loads the given options. */ - private function assertDefaultConfigDefinition(array $config) + private function assertDefaultConfigDefinition(array $config, array $attributes = [], array $elements = [], array $blankElements = []): void { $this->assertTrue($this->container->hasDefinition('exercise_html_purifier.config.default')); $definition = $this->container->getDefinition('exercise_html_purifier.config.default'); - $this->assertEquals([\HTMLPurifier_Config::class, 'create'], $definition->getFactory()); - $this->assertEquals([$config], $definition->getArguments()); + + $this->assertEquals([new Reference(HTMLPurifierConfigFactory::class), 'create'], $definition->getFactory()); + $this->assertSame(['default', $config, null, [], $attributes, $elements, $blankElements], $definition->getArguments(), 'Default config is invalid.'); } /** @@ -161,35 +416,52 @@ private function assertDefaultConfigDefinition(array $config) * * @param string[] $profiles */ - private function assertRegistryHasProfiles(array $profiles) + private function assertRegistryHasProfiles(array $profiles): void { - $this->assertTrue($this->container->hasAlias(HTMLPurifiersRegistryInterface::class), 'The registry interface alias must exist.'); - - try { - $registry = $this->container->findDefinition(HTMLPurifiersRegistryInterface::class); - } catch (ServiceNotFoundException $e) { - $this->fail(sprintf('Alias %s does not target a valid id: %s.', HTMLPurifiersRegistryInterface::class, $e->getMessage())); + foreach ($profiles as $profile) { + $this->assertTrue($this->container->hasDefinition("exercise_html_purifier.$profile")); + $this->assertTrue($this->container->hasDefinition("exercise_html_purifier.config.$profile")); } + } - $this->assertSame(HTMLPurifiersRegistry::class, $registry->getClass()); + /** + * Assert that the cache warmer serializer paths equal the given array. + */ + private function assertCacheWarmerSerializerArgs(array $paths, array $profiles): void + { + $serializer = $this->container->getDefinition('exercise_html_purifier.cache_warmer.serializer'); - foreach ($profiles as $profile) { - $purifierId = "exercise_html_purifier.$profile"; + $this->assertSame($serializer->getArgument(0), $paths); + $this->assertSame($serializer->getArgument(1), $profiles); + $this->assertSame((string) $serializer->getArgument(2), HTMLPurifiersRegistryInterface::class); + $this->assertSame((string) $serializer->getArgument(3), 'filesystem'); + } +} - $this->assertTrue($this->container->has($purifierId), "The service $purifierId should be registered."); +class ServiceWithDefaultConfig +{ + public function __construct(\HTMLPurifier $purifier) + { + } +} - $tag = ['profile' => $profile]; - $purifier = $this->container->findDefinition($purifierId); +class ServiceWithDefaultConfig2 +{ + public function __construct(\HTMLPurifier $htmlPurifier) + { + } +} - $this->assertSame([HTMLPurifierPass::PURIFIER_TAG => [$tag]], $purifier->getTags()); - } +class ServiceWithSimpleConfig +{ + public function __construct(\HTMLPurifier $simplePurifier) + { } +} - /** - * Assert that the cache warmer serializer paths equal the given array. - */ - private function assertCacheWarmerSerializerPaths(array $paths) +class ServiceWithAdvancedConfig +{ + public function __construct(\HTMLPurifier $advancedPurifier) { - $this->assertEquals($paths, $this->container->getParameter('exercise_html_purifier.cache_warmer.serializer.paths')); } } diff --git a/tests/Form/Listener/HTMLPurifierListenerTest.php b/tests/Form/Listener/HTMLPurifierListenerTest.php index a712df69..14447a60 100644 --- a/tests/Form/Listener/HTMLPurifierListenerTest.php +++ b/tests/Form/Listener/HTMLPurifierListenerTest.php @@ -94,14 +94,14 @@ public function testPurifyDoNothingForEmptyOrNonScalarData($input) $listener->purifySubmittedData($event); } - public function provideInvalidInput() + public function provideInvalidInput(): iterable { yield ['']; yield [[]]; yield [new \stdClass()]; } - private function getFormEvent($data) + private function getFormEvent($data): FormEvent { return new FormEvent($this->createMock(FormInterface::class), $data); } diff --git a/tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php b/tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php index d8a71df2..1bddef62 100644 --- a/tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php +++ b/tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php @@ -5,26 +5,24 @@ use Exercise\HTMLPurifierBundle\Form\Listener\HTMLPurifierListener; use Exercise\HTMLPurifierBundle\Form\TypeExtension\HTMLPurifierTextTypeExtension; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; -use Exercise\HTMLPurifierBundle\Tests\ForwardCompatTestTrait; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Test\FormIntegrationTestCase; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; class HTMLPurifierTextTypeExtensionTest extends FormIntegrationTestCase { - use ForwardCompatTestTrait; - private $registry; - private function doSetUp() + protected function setUp(): void { $this->registry = $this->createMock(HTMLPurifiersRegistryInterface::class); parent::setUp(); } - private function doTearDown() + protected function tearDown(): void { parent::tearDown(); @@ -71,7 +69,7 @@ public function testPurifyOptionsNeedDefaultProfile() ->method('get') ; - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); + $this->expectException(InvalidOptionsException::class); $this->expectExceptionMessage('The profile "default" is not registered.'); $this->factory->create(TextType::class, null, ['purify_html' => true]); @@ -108,7 +106,7 @@ public function testInvalidProfile() ->method('get') ; - $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); + $this->expectException(InvalidOptionsException::class); $this->expectExceptionMessage('The profile "test" is not registered.'); $this->factory->create(TextType::class, null, [ @@ -117,10 +115,7 @@ public function testInvalidProfile() ]); } - /** - * @return bool - */ - private function hasPurifierListener(FormInterface $form) + private function hasPurifierListener(FormInterface $form): bool { foreach ($form->getConfig()->getEventDispatcher()->getListeners(FormEvents::PRE_SUBMIT) as $listener) { if ($listener[0] instanceof HTMLPurifierListener) { diff --git a/tests/ForwardCompatTestTrait.php b/tests/ForwardCompatTestTrait.php deleted file mode 100644 index 3870ed3f..00000000 --- a/tests/ForwardCompatTestTrait.php +++ /dev/null @@ -1,72 +0,0 @@ -hasReturnType()) { - eval(' - namespace Exercise\HTMLPurifierBundle\Tests; - - /** - * @internal - */ - trait ForwardCompatTestTrait - { - private function doSetUp(): void - { - } - - private function doTearDown(): void - { - } - - protected function setUp(): void - { - $this->doSetUp(); - } - protected function tearDown(): void - { - $this->doTearDown(); - } - } -'); -} else { - /** - * @internal - */ - trait ForwardCompatTestTrait - { - /** - * @return void - */ - private function doSetUp() - { - } - - /** - * @return void - */ - private function doTearDown() - { - } - - /** - * @return void - */ - protected function setUp() - { - $this->doSetUp(); - } - - /** - * @return void - */ - protected function tearDown() - { - $this->doTearDown(); - } - } -} diff --git a/tests/HTMLPurifiersRegistryTest.php b/tests/HTMLPurifiersRegistryTest.php index 378e73d8..d58d6df0 100644 --- a/tests/HTMLPurifiersRegistryTest.php +++ b/tests/HTMLPurifiersRegistryTest.php @@ -8,24 +8,22 @@ class HTMLPurifiersRegistryTest extends TestCase { - use ForwardCompatTestTrait; - private $locator; private $registry; - private function doSetUp() + protected function setUp(): void { $this->locator = $this->createMock(ContainerInterface::class); $this->registry = new HTMLPurifiersRegistry($this->locator); } - private function doTearDown() + protected function tearDown(): void { $this->registry = null; $this->locator = null; } - public function provideProfiles() + public function provideProfiles(): iterable { yield ['default']; yield ['test']; diff --git a/tests/Twig/HTMLPurifierRuntimeTest.php b/tests/Twig/HTMLPurifierRuntimeTest.php index 7910e491..e6b03349 100644 --- a/tests/Twig/HTMLPurifierRuntimeTest.php +++ b/tests/Twig/HTMLPurifierRuntimeTest.php @@ -38,7 +38,7 @@ public function testPurifyFilter($profile) $this->assertEquals($purifiedInput, $extension->purify($input, $profile)); } - public function providePurifierProfiles() + public function providePurifierProfiles(): iterable { yield ['default']; yield ['custom']; From 7719770ac84bd8187a2256f369a29de609623f4e Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sat, 14 Dec 2019 16:58:42 +0100 Subject: [PATCH 31/61] Fixed README formating --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 40e40a9f..cd30044d 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,7 @@ $builder ## How to Customize a Config Definition -# Custom Attributes +### Whitelist Attributes In some case, you might want to set some rules for a specific tag. This is what the following config is about: @@ -288,7 +288,7 @@ See [HTMLPurifier_AttrTypes][] for more options. [HTMLPurifier_AttrTypes]: https://github.com/ezyang/htmlpurifier/blob/master/library/HTMLPurifier/AttrTypes.php -# Custom Elements +### Whitelist Elements In some case, you might want to set some rules for a specific tag. This is what the following config is about: @@ -344,7 +344,7 @@ See [HTMLPurifier documentation][] for more details. [HTMLPurifier documentation]: http://htmlpurifier.org/docs/enduser-customize.html -# Blank Elements +### Blank Elements It might happen that you need a tag clean from any attributes. Then just add it to the list: From 97a716731eac4b9721a7b3b259614380ef85e14c Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sat, 14 Dec 2019 17:01:21 +0100 Subject: [PATCH 32/61] Fixed case sensitive tests folder after rebase --- {Tests => tests}/HTMLPurifierConfigFactoryTest.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {Tests => tests}/HTMLPurifierConfigFactoryTest.php (100%) diff --git a/Tests/HTMLPurifierConfigFactoryTest.php b/tests/HTMLPurifierConfigFactoryTest.php similarity index 100% rename from Tests/HTMLPurifierConfigFactoryTest.php rename to tests/HTMLPurifierConfigFactoryTest.php From 95ac61e29f7667f2654b6659b8d41d217e1f7383 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sun, 8 Mar 2020 16:47:14 +0100 Subject: [PATCH 33/61] Minor improvements in README --- README.md | 70 ++++++++++++++++--------------------------------------- 1 file changed, 20 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index cd30044d..4e3a1c17 100644 --- a/README.md +++ b/README.md @@ -45,23 +45,29 @@ public function registerBundles() ## Configuration in Symfony 3 +The configuration is the same as the following section, but the path should be +`app/config.yml` instead. + +## Configuration in Symfony 4 and up + If you do not explicitly configure this bundle, an HTMLPurifier service will be defined as `exercise_html_purifier.default`. This behavior is the same as if you had specified the following configuration: ```yaml -# app/config.yml +# config/packages/exercise_html_purifier.yaml exercise_html_purifier: default_cache_serializer_path: '%kernel.cache_dir%/htmlpurifier' ``` -The `default` profile is special in that it is used as the configuration for the -`exercise_html_purifier.default` service as well as the base configuration for -other profiles you might define. +The `default` profile is special, it is *always* defined and its configuration +is inherited by all custom profiles. +`exercise_html_purifier.default` is the default service using the base +configuration. ```yaml -# app/config.yml +# config/packages/exercise_html_purifier.yaml exercise_html_purifier: default_cache_serializer_path: '%kernel.cache_dir%/htmlpurifier' @@ -81,45 +87,12 @@ option to suppress the default path. [configuration documentation]: http://htmlpurifier.org/live/configdoc/plain.html -## Configuration in Symfony 4 and up - -If you do not explicitly configure this bundle, an HTMLPurifier service will be -defined as `exercise_html_purifier.default`. This behavior is the same as if you -had specified the following configuration: - -```yaml -# config/packages/exercise_html_purifier.yaml - -exercise_html_purifier: - default_cache_serializer_path: '%kernel.cache_dir%/htmlpurifier' -``` - -The `default` profile is special, it is *always* defined and its configuration -is inherited by all custom profiles. -`exercise_html_purifier.default` is the default service using the base -configuration. - -```yaml -# config/packages/exercise_html_purifier.yaml - -exercise_html_purifier: - default_cache_serializer_path: 'tmp/htmlpurifier' - html_profiles: - default: - config: - Cache.SerializerPermissions: 777 - custom: - config: - Core.Encoding: 'ISO-8859-1' -``` - ## Autowiring By default type hinting `\HtmlPurifier` in your services will autowire the `exercise_html_purifier.default` service. To override it and use your own config as default autowired services just add -this in you `app/config/services.yml` in you use symfony 3 or `config/services.yaml` -if you use symfony 4: +this configuration: ```yaml # config/services.yaml @@ -131,7 +104,8 @@ services: ### Using a custom purifier class as default -If you want to use your own class as default purifier, define a new alias: +If you want to use your own class as default purifier, define the new alias as +below: ```yaml # config/services.yaml @@ -141,10 +115,7 @@ services: exercise_html_purifier.default: '@App\Html\CustomHtmlPurifier' ``` -In such case, the custom purifier will use its own defined configuration, -ignoring the bundle configuration. - -### Argument binding +### Argument binding (Symfony >= 4.4) The bundle also leverages the alias argument binding for each profile. So the following config: @@ -170,11 +141,10 @@ public function __construct(\HTMLPurifier $galleryPurifier) {} // gallery config ## Form Type Extension This bundles provides a form type extension for filtering form fields with -HTMLPurifier. Purification is done during the PRE_SUBMIT event, which -means that client data will be filtered before binding to the form. +HTMLPurifier. Purification is done early during the PRE_SUBMIT event, which +means that client data will be filtered before being bound to the form. -The following example demonstrates one possible way to integrate an HTMLPurifier -transformer into a form by way of a custom field type: +Two options are automatically available in all `TextType` based types: ```php Date: Mon, 9 Mar 2020 18:40:38 +0100 Subject: [PATCH 34/61] Replace eval by require_once for ForwardCompatTypeExtensionTrait --- .../ForwardCompatTypeExtensionTrait.php | 19 +------------------ .../forward_compat_trait.inc.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 src/Form/TypeExtension/forward_compat_trait.inc.php diff --git a/src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php b/src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php index 4f1f1eeb..4e0707d9 100644 --- a/src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php +++ b/src/Form/TypeExtension/ForwardCompatTypeExtensionTrait.php @@ -5,24 +5,7 @@ use Symfony\Component\Form\FormTypeExtensionInterface; if (method_exists(FormTypeExtensionInterface::class, 'getExtendedTypes')) { - eval(' - namespace Exercise\HTMLPurifierBundle\Form\TypeExtension; - - /** - * @internal - */ - trait ForwardCompatTypeExtensionTrait - { - private static function doGetExtendedTypes(): iterable - { - } - - public static function getExtendedTypes(): iterable - { - return self::doGetExtendedTypes(); - } - } -'); + require_once __DIR__.'/forward_compat_trait.inc.php'; } else { /** * @internal diff --git a/src/Form/TypeExtension/forward_compat_trait.inc.php b/src/Form/TypeExtension/forward_compat_trait.inc.php new file mode 100644 index 00000000..326b0070 --- /dev/null +++ b/src/Form/TypeExtension/forward_compat_trait.inc.php @@ -0,0 +1,18 @@ + Date: Wed, 16 Sep 2020 10:11:21 +0200 Subject: [PATCH 35/61] #76: Allow null input in Twig extension --- src/Twig/HTMLPurifierRuntime.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Twig/HTMLPurifierRuntime.php b/src/Twig/HTMLPurifierRuntime.php index 8c152528..46e098d5 100644 --- a/src/Twig/HTMLPurifierRuntime.php +++ b/src/Twig/HTMLPurifierRuntime.php @@ -17,13 +17,17 @@ public function __construct(HTMLPurifiersRegistryInterface $registry) /** * Filters the input through an \HTMLPurifier service. * - * @param string $string The html string to purify + * @param string|null $string The html string to purify * @param string $profile A configuration profile name * * @return string The purified html string */ - public function purify(string $string, string $profile = 'default'): string + public function purify(?string $string, string $profile = 'default'): string { + if (null === $string) { + return ''; + } + return $this->getHTMLPurifierForProfile($profile)->purify($string); } From 412fa43155cf9041beee85cbbc585cd3581bab9e Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 16 Sep 2020 12:58:14 +0200 Subject: [PATCH 36/61] Fix CS --- src/Twig/HTMLPurifierRuntime.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Twig/HTMLPurifierRuntime.php b/src/Twig/HTMLPurifierRuntime.php index 46e098d5..f1ba5df4 100644 --- a/src/Twig/HTMLPurifierRuntime.php +++ b/src/Twig/HTMLPurifierRuntime.php @@ -18,7 +18,7 @@ public function __construct(HTMLPurifiersRegistryInterface $registry) * Filters the input through an \HTMLPurifier service. * * @param string|null $string The html string to purify - * @param string $profile A configuration profile name + * @param string $profile A configuration profile name * * @return string The purified html string */ @@ -27,7 +27,7 @@ public function purify(?string $string, string $profile = 'default'): string if (null === $string) { return ''; } - + return $this->getHTMLPurifierForProfile($profile)->purify($string); } From f697c34c0896fb2fe2b62dd9f12f9efabd6891d0 Mon Sep 17 00:00:00 2001 From: Lukas Ribi Date: Sat, 26 Dec 2020 19:37:21 +0100 Subject: [PATCH 37/61] Extended the configuration example in the README (#80) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e3a1c17..25af43e4 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,12 @@ exercise_html_purifier: custom: config: Core.Encoding: 'ISO-8859-1' + HTML.Allowed: 'a[href|target],p,br' + Attr.AllowedFrameTargets: '_blank' ``` In this example, a `exercise_html_purifier.custom` service will also be defined, -which includes both the cache and encoding options. Available configuration +which includes cache, encoding, HTML tags and attributes options. Available configuration options may be found in HTMLPurifier's [configuration documentation][]. **Note:** If you define a `default` profile but omit `Cache.SerializerPath`, it From 9bfd06e42b409cce01e1e5345dceb9d1dc8e7fe6 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Mon, 28 Dec 2020 14:07:04 +0100 Subject: [PATCH 38/61] Declare PHP 8 support --- .travis.yml | 2 ++ composer.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0324b0a5..3ab56941 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,8 @@ jobs: env: SYMFONY_REQUIRE="4.4.*" COMPOSER_FLAGS="--prefer-stable" - php: 7.4 env: SYMFONY_REQUIRE="5.0.*" COMPOSER_FLAGS="--prefer-stable" + - php: 8.0 + env: SYMFONY_REQUIRE="5.0.*" COMPOSER_FLAGS="--prefer-stable" SYMFONY_DEPRECATIONS_HELPER=weak # Dev - php: 7.4 diff --git a/composer.json b/composer.json index c9aee97b..dddff0ff 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "php": "^7.1.3", + "php": "^7.1.3 || ^8.0.0", "ezyang/htmlpurifier": "~4.0", "symfony/config": "~3.4 || ~4.0 || ^5.0", "symfony/dependency-injection": "~3.4.1 || ^4.0.1 || ^5.0", From 45fc253f341e0664cfeffdc4d9e55989d67545e5 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Mon, 28 Dec 2020 19:29:08 +0100 Subject: [PATCH 39/61] Fix Xdebug coverage --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3ab56941..e0dc3bd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,6 +45,7 @@ jobs: php: 7.4 before_script: - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,} + - echo "xdebug.mode = coverage" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini - if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi script: - ./vendor/bin/simple-phpunit -v --coverage-text From 8d43fbe32af83c86f783aa5333d3cda2dbea116f Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sun, 17 Apr 2022 13:10:49 +0200 Subject: [PATCH 40/61] fix compatibility with Symfony 6.x --- composer.json | 12 ++++++------ src/CacheWarmer/SerializerCacheWarmer.php | 6 ++++-- .../Compiler/HTMLPurifierPass.php | 2 +- src/DependencyInjection/Configuration.php | 2 +- .../ExerciseHTMLPurifierExtension.php | 2 +- src/ExerciseHTMLPurifierBundle.php | 2 +- src/Form/Listener/HTMLPurifierListener.php | 2 +- .../TypeExtension/HTMLPurifierTextTypeExtension.php | 4 ++-- src/Twig/HTMLPurifierExtension.php | 2 +- tests/CacheWarmer/SerializerCacheWarmerTest.php | 4 ++-- 10 files changed, 20 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index dddff0ff..89b385f9 100644 --- a/composer.json +++ b/composer.json @@ -14,14 +14,14 @@ "require": { "php": "^7.1.3 || ^8.0.0", "ezyang/htmlpurifier": "~4.0", - "symfony/config": "~3.4 || ~4.0 || ^5.0", - "symfony/dependency-injection": "~3.4.1 || ^4.0.1 || ^5.0", - "symfony/http-kernel": "~3.4.1 || ^4.0.1 || ^5.0" + "symfony/config": "~3.4 || ~4.0 || ^5.0 || ^6.0", + "symfony/dependency-injection": "~3.4.1 || ^4.0.1 || ^5.0 || ^6.0", + "symfony/http-kernel": "~3.4.1 || ^4.0.1 || ^5.0 || ^6.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", - "symfony/form": "~3.4.1 || ^4.0.1 || ^5.0", - "symfony/phpunit-bridge": "4.4.*", + "symfony/form": "~3.4.1 || ^4.0.1 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^6.0", "twig/twig": "^1.35.0 || ^2.4.4 || ^3.0" }, "autoload": { @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.x-dev" } } } diff --git a/src/CacheWarmer/SerializerCacheWarmer.php b/src/CacheWarmer/SerializerCacheWarmer.php index a0f9c762..3256503b 100644 --- a/src/CacheWarmer/SerializerCacheWarmer.php +++ b/src/CacheWarmer/SerializerCacheWarmer.php @@ -41,7 +41,7 @@ public function __construct(array $paths, array $profiles, HTMLPurifiersRegistry /** * {@inheritdoc} */ - public function warmUp($cacheDir) + public function warmUp(string $cacheDir): array { foreach ($this->paths as $path) { $this->filesystem->remove($path); // clean previous cache @@ -52,12 +52,14 @@ public function warmUp($cacheDir) // Will build the configuration $this->registry->get($profile)->purify("
"); } + + return []; } /** * {@inheritdoc} */ - public function isOptional() + public function isOptional(): bool { return false; } diff --git a/src/DependencyInjection/Compiler/HTMLPurifierPass.php b/src/DependencyInjection/Compiler/HTMLPurifierPass.php index ce744e84..f3c276c0 100644 --- a/src/DependencyInjection/Compiler/HTMLPurifierPass.php +++ b/src/DependencyInjection/Compiler/HTMLPurifierPass.php @@ -17,7 +17,7 @@ class HTMLPurifierPass implements CompilerPassInterface /** * {@inheritdoc} */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasAlias(HTMLPurifiersRegistryInterface::class)) { return; diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 8ecee7ad..9bc5d5c9 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -11,7 +11,7 @@ class Configuration implements ConfigurationInterface /** * {@inheritdoc} */ - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('exercise_html_purifier'); diff --git a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php index 76965aaf..48105196 100644 --- a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -87,7 +87,7 @@ public function load(array $configs, ContainerBuilder $container) ; } - public function getAlias() + public function getAlias(): string { return 'exercise_html_purifier'; } diff --git a/src/ExerciseHTMLPurifierBundle.php b/src/ExerciseHTMLPurifierBundle.php index 31966ee9..f22d1b0b 100644 --- a/src/ExerciseHTMLPurifierBundle.php +++ b/src/ExerciseHTMLPurifierBundle.php @@ -11,7 +11,7 @@ class ExerciseHTMLPurifierBundle extends Bundle /** * {@inheritdoc} */ - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { $container->addCompilerPass(new HTMLPurifierPass()); } diff --git a/src/Form/Listener/HTMLPurifierListener.php b/src/Form/Listener/HTMLPurifierListener.php index 98d86dd1..3a3eebd5 100644 --- a/src/Form/Listener/HTMLPurifierListener.php +++ b/src/Form/Listener/HTMLPurifierListener.php @@ -39,7 +39,7 @@ public function purifySubmittedData(FormEvent $event): void /** * {@inheritdoc} */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ FormEvents::PRE_SUBMIT => ['purifySubmittedData', /* as soon as possible */ 1000000], diff --git a/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php b/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php index 0b88e03e..72090132 100644 --- a/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php +++ b/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php @@ -39,7 +39,7 @@ public static function getExtendedTypes(): iterable /** * {@inheritdoc} */ - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver ->setDefaults([ @@ -69,7 +69,7 @@ public function configureOptions(OptionsResolver $resolver) /** * {@inheritdoc} */ - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { if ($options['purify_html']) { $builder->addEventSubscriber( diff --git a/src/Twig/HTMLPurifierExtension.php b/src/Twig/HTMLPurifierExtension.php index 713596d8..aceafb77 100644 --- a/src/Twig/HTMLPurifierExtension.php +++ b/src/Twig/HTMLPurifierExtension.php @@ -10,7 +10,7 @@ class HTMLPurifierExtension extends AbstractExtension /** * {@inheritdoc} */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('purify', [HTMLPurifierRuntime::class, 'purify'], ['is_safe' => ['html']]), diff --git a/tests/CacheWarmer/SerializerCacheWarmerTest.php b/tests/CacheWarmer/SerializerCacheWarmerTest.php index 42856753..34493f17 100644 --- a/tests/CacheWarmer/SerializerCacheWarmerTest.php +++ b/tests/CacheWarmer/SerializerCacheWarmerTest.php @@ -28,7 +28,7 @@ public function testWarmUpShouldCreatePaths() $this->assertFalse($fs->exists($path)); $cacheWarmer = new SerializerCacheWarmer([$path], [], $this->createMock(HTMLPurifiersRegistryInterface::class), $fs); - $cacheWarmer->warmUp(null); + $cacheWarmer->warmUp(''); $this->assertTrue($fs->exists($path)); @@ -57,6 +57,6 @@ public function testWarmUpShouldCallPurifyForEachProfile() ; $cacheWarmer = new SerializerCacheWarmer([], ['first', 'second'], $registry, new Filesystem()); - $cacheWarmer->warmUp(null); + $cacheWarmer->warmUp(''); } } From c86c72dee965713b941dfc8c14b8125a5cfcf054 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sun, 17 Apr 2022 13:14:32 +0200 Subject: [PATCH 41/61] drop support for Symfony < 4.4 --- composer.json | 8 ++++---- src/DependencyInjection/Configuration.php | 8 +------- src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php | 8 -------- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index 89b385f9..d7f87898 100644 --- a/composer.json +++ b/composer.json @@ -14,13 +14,13 @@ "require": { "php": "^7.1.3 || ^8.0.0", "ezyang/htmlpurifier": "~4.0", - "symfony/config": "~3.4 || ~4.0 || ^5.0 || ^6.0", - "symfony/dependency-injection": "~3.4.1 || ^4.0.1 || ^5.0 || ^6.0", - "symfony/http-kernel": "~3.4.1 || ^4.0.1 || ^5.0 || ^6.0" + "symfony/config": "~4.4 || ^5.0 || ^6.0", + "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0", + "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", - "symfony/form": "~3.4.1 || ^4.0.1 || ^5.0 || ^6.0", + "symfony/form": "^4.4 || ^5.0 || ^6.0", "symfony/phpunit-bridge": "^6.0", "twig/twig": "^1.35.0 || ^2.4.4 || ^3.0" }, diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 9bc5d5c9..4b534279 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -14,13 +14,7 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('exercise_html_purifier'); - - if (method_exists($treeBuilder, 'getRootNode')) { - $rootNode = $treeBuilder->getRootNode(); - } else { - // BC layer for symfony/config 4.1 and older - $rootNode = $treeBuilder->root('exercise_html_purifier'); - } + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() diff --git a/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php b/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php index 72090132..90815dd7 100644 --- a/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php +++ b/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php @@ -20,14 +20,6 @@ public function __construct(HTMLPurifiersRegistryInterface $registry) $this->purifiersRegistry = $registry; } - /** - * {@inheritdoc} - */ - public function getExtendedType() - { - return TextType::class; - } - /** * {@inheritdoc} */ From 40f520605ad1deb3de290ae5442eb27f6d8522d7 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sun, 17 Apr 2022 13:16:51 +0200 Subject: [PATCH 42/61] update README --- README.md | 37 +++---------------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 25af43e4..ae39ef4f 100644 --- a/README.md +++ b/README.md @@ -11,44 +11,13 @@ This bundle integrates [HTMLPurifier][] into Symfony. ## Installation -## Symfony 3.4 and above (using Composer) - -Require the bundle in your composer.json file: - -```json -{ - "require": { - "exercise/htmlpurifier-bundle": "*" - } -} -``` - Install the bundle: ```bash $ composer require exercise/htmlpurifier-bundle ``` -Register the bundle in Symfony 3: - -```php -// app/AppKernel.php - -public function registerBundles() -{ - return [ - // ... - new Exercise\HTMLPurifierBundle\ExerciseHTMLPurifierBundle(), - ]; -} -``` - -## Configuration in Symfony 3 - -The configuration is the same as the following section, but the path should be -`app/config.yml` instead. - -## Configuration in Symfony 4 and up +## Configuration If you do not explicitly configure this bundle, an HTMLPurifier service will be defined as `exercise_html_purifier.default`. This behavior is the same as if you @@ -117,7 +86,7 @@ services: exercise_html_purifier.default: '@App\Html\CustomHtmlPurifier' ``` -### Argument binding (Symfony >= 4.4) +### Argument binding The bundle also leverages the alias argument binding for each profile. So the following config: @@ -353,5 +322,5 @@ previous, and "all" could define its own rules too. ## Contributing -PRs are welcomed :). Please target the `2.0` branch for bug fixes and `master` +PRs are welcomed :). Please target the `3.x` branch for bug fixes and `master` for new features. From a56a9eccaf6a4739a5c6d4e8a6bf8151538c0561 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sun, 17 Apr 2022 13:19:05 +0200 Subject: [PATCH 43/61] fix PHPUnit 10 compatibility --- tests/CacheWarmer/SerializerCacheWarmerTest.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/CacheWarmer/SerializerCacheWarmerTest.php b/tests/CacheWarmer/SerializerCacheWarmerTest.php index 34493f17..3429e5fc 100644 --- a/tests/CacheWarmer/SerializerCacheWarmerTest.php +++ b/tests/CacheWarmer/SerializerCacheWarmerTest.php @@ -45,16 +45,9 @@ public function testWarmUpShouldCallPurifyForEachProfile() $registry = $this->createMock(HTMLPurifiersRegistryInterface::class); $registry->expects($this->exactly(2)) ->method('get') + ->withConsecutive(['first'], ['second']) ->willReturn($purifier) ; - $registry->expects($this->at(0)) - ->method('get') - ->with('first') - ; - $registry->expects($this->at(1)) - ->method('get') - ->with('second') - ; $cacheWarmer = new SerializerCacheWarmer([], ['first', 'second'], $registry, new Filesystem()); $cacheWarmer->warmUp(''); From da6c9f8ead3f8e15a797d1de1f0c593c23e15d07 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sun, 17 Apr 2022 13:24:54 +0200 Subject: [PATCH 44/61] update CHANGELOG --- CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 62e68f86..308e9413 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +## Version 4.0 (04/2022) + + * [BC Break] Drop support for Symfony < 4.4 + * add support for Symfony 6.x + * add support for PHPUnit 10.x + ## Version 3.0 (12/2019) * [BC break] Dropped support for PHP 5.x. PHP 7.1 minimum required. From 25e25369cdc78ddbe28e78f28abb8a438a59ba76 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sun, 17 Apr 2022 13:31:07 +0200 Subject: [PATCH 45/61] update php-cs-fixer --- .php-cs-fixer.cache | 1 + .php_cs.dist => .php-cs-fixer.dist.php | 4 ++-- composer.json | 2 +- src/DependencyInjection/Compiler/HTMLPurifierPass.php | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .php-cs-fixer.cache rename .php_cs.dist => .php-cs-fixer.dist.php (65%) diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache new file mode 100644 index 00000000..8b92120c --- /dev/null +++ b/.php-cs-fixer.cache @@ -0,0 +1 @@ +{"php":"8.1.4","version":"3.8.0:v3.8.0#cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3","indent":" ","lineEnding":"\n","rules":{"array_syntax":true,"backtick_to_shell_exec":true,"binary_operator_spaces":true,"blank_line_before_statement":{"statements":["return"]},"braces":{"allow_single_line_anonymous_class_with_empty_body":true,"allow_single_line_closure":true},"cast_spaces":true,"class_attributes_separation":{"elements":{"method":"one"}},"class_definition":{"single_line":true},"class_reference_name_casing":true,"clean_namespace":true,"concat_space":true,"echo_tag_syntax":true,"empty_loop_body":{"style":"braces"},"empty_loop_condition":true,"fully_qualified_strict_types":true,"function_typehint_space":true,"general_phpdoc_tag_rename":{"replacements":{"inheritDocs":"inheritDoc"}},"include":true,"increment_style":true,"integer_literal_case":true,"lambda_not_used_import":true,"linebreak_after_opening_tag":true,"magic_constant_casing":true,"magic_method_casing":true,"method_argument_space":{"on_multiline":"ignore"},"native_function_casing":true,"native_function_type_declaration_casing":true,"no_alias_language_construct_call":true,"no_alternative_syntax":true,"no_binary_string":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_blank_lines":{"tokens":["case","continue","curly_brace_block","default","extra","parenthesis_brace_block","square_brace_block","switch","throw","use"]},"no_leading_namespace_whitespace":true,"no_mixed_echo_print":true,"no_multiline_whitespace_around_double_arrow":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_around_offset":true,"no_superfluous_phpdoc_tags":{"allow_mixed":true,"allow_unused_params":true},"no_trailing_comma_in_list_call":true,"no_trailing_comma_in_singleline_array":true,"no_trailing_comma_in_singleline_function_call":true,"no_unneeded_control_parentheses":{"statements":["break","clone","continue","echo_print","return","switch_case","yield","yield_from"]},"no_unneeded_curly_braces":{"namespaces":true},"no_unneeded_import_alias":true,"no_unset_cast":true,"no_unused_imports":true,"no_whitespace_before_comma_in_array":true,"normalize_index_brace":true,"object_operator_without_whitespace":true,"ordered_imports":true,"php_unit_fqcn_annotation":true,"php_unit_method_casing":true,"phpdoc_align":true,"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag_normalizer":true,"phpdoc_no_access":true,"phpdoc_no_alias_tag":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_tag_type":{"tags":{"inheritDoc":"inline"}},"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_trim_consecutive_blank_line_separation":true,"phpdoc_types":true,"phpdoc_types_order":{"null_adjustment":"always_last","sort_algorithm":"none"},"phpdoc_var_without_name":true,"protected_to_private":true,"semicolon_after_instruction":true,"single_class_element_per_statement":true,"single_line_comment_spacing":true,"single_line_comment_style":{"comment_types":["hash"]},"single_line_throw":true,"single_quote":true,"single_space_after_construct":true,"space_after_semicolon":{"remove_in_empty_for_expressions":true},"standardize_increment":true,"standardize_not_equals":true,"switch_continue_to_break":true,"trailing_comma_in_multiline":true,"trim_array_spaces":true,"types_spaces":true,"unary_operator_spaces":true,"whitespace_after_comma_in_array":true,"yoda_style":true,"blank_line_after_opening_tag":true,"compact_nullable_typehint":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"return_type_declaration":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"no_break_comment":true,"no_closing_tag":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"src\/HTMLPurifiersRegistryInterface.php":187845934,"src\/Form\/Listener\/HTMLPurifierListener.php":4116418424,"src\/Form\/TypeExtension\/HTMLPurifierTextTypeExtension.php":608289064,"src\/HTMLPurifiersRegistry.php":795094810,"src\/CacheWarmer\/SerializerCacheWarmer.php":2733650958,"src\/HTMLPurifierConfigFactory.php":1012979259,"src\/Twig\/HTMLPurifierExtension.php":3905834270,"src\/Twig\/HTMLPurifierRuntime.php":1281382522,"src\/DependencyInjection\/Configuration.php":2513854708,"src\/DependencyInjection\/ExerciseHTMLPurifierExtension.php":1265155058,"src\/DependencyInjection\/Compiler\/HTMLPurifierPass.php":1538180982,"src\/ExerciseHTMLPurifierBundle.php":2699336334,"tests\/HTMLPurifierConfigFactoryTest.php":969987042,"tests\/Form\/Listener\/HTMLPurifierListenerTest.php":1185658046,"tests\/Form\/TypeExtension\/HTMLPurifierTextTypeExtensionTest.php":1496463765,"tests\/CacheWarmer\/SerializerCacheWarmerTest.php":3561678071,"tests\/HTMLPurifiersRegistryTest.php":2606387570,"tests\/Twig\/HTMLPurifierRuntimeTest.php":887512979,"tests\/DependencyInjection\/ExerciseHTMLPurifierExtensionTest.php":2900253984,"tests\/DependencyInjection\/Compiler\/HTMLPurifierPassTest.php":1485032584}} \ No newline at end of file diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 65% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index ac731140..7de6fa49 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -1,11 +1,11 @@ exclude('Resources') ->in(__DIR__) ; -return PhpCsFixer\Config::create() +return (new PhpCsFixer\Config()) ->setRules([ '@Symfony' => true, ]) diff --git a/composer.json b/composer.json index d7f87898..6bb8e97a 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", "symfony/form": "^4.4 || ^5.0 || ^6.0", "symfony/phpunit-bridge": "^6.0", "twig/twig": "^1.35.0 || ^2.4.4 || ^3.0" diff --git a/src/DependencyInjection/Compiler/HTMLPurifierPass.php b/src/DependencyInjection/Compiler/HTMLPurifierPass.php index f3c276c0..16ed073a 100644 --- a/src/DependencyInjection/Compiler/HTMLPurifierPass.php +++ b/src/DependencyInjection/Compiler/HTMLPurifierPass.php @@ -12,7 +12,7 @@ class HTMLPurifierPass implements CompilerPassInterface { - const PURIFIER_TAG = 'exercise.html_purifier'; + public const PURIFIER_TAG = 'exercise.html_purifier'; /** * {@inheritdoc} From 6c3ab4384d15fc41cf733783a32852d838a64054 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Mon, 18 Apr 2022 19:17:54 +0200 Subject: [PATCH 46/61] update QA from travis to Github actions --- .github/workflows/qa.yaml | 119 ++++++++++++++++++ .gitignore | 2 +- .php-cs-fixer.cache | 1 - .travis.yml | 68 ---------- composer.json | 2 +- src/CacheWarmer/SerializerCacheWarmer.php | 7 +- .../ExerciseHTMLPurifierExtension.php | 5 +- src/Form/Listener/HTMLPurifierListener.php | 2 +- src/HTMLPurifierConfigFactory.php | 2 +- .../CacheWarmer/SerializerCacheWarmerTest.php | 6 +- .../Compiler/HTMLPurifierPassTest.php | 10 +- .../ExerciseHTMLPurifierExtensionTest.php | 28 ++--- .../Listener/HTMLPurifierListenerTest.php | 11 +- .../HTMLPurifierTextTypeExtensionTest.php | 12 +- tests/HTMLPurifierConfigFactoryTest.php | 8 +- tests/HTMLPurifiersRegistryTest.php | 9 +- tests/Twig/HTMLPurifierRuntimeTest.php | 2 +- 17 files changed, 178 insertions(+), 116 deletions(-) create mode 100644 .github/workflows/qa.yaml delete mode 100644 .php-cs-fixer.cache delete mode 100644 .travis.yml diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml new file mode 100644 index 00000000..55d48e32 --- /dev/null +++ b/.github/workflows/qa.yaml @@ -0,0 +1,119 @@ +name: Quality and Assurance + +on: + push: + pull_request: + +jobs: + build: + name: Build + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php-versions: ['7.1', '8.1'] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, extensions and composer with shivammathur/setup-php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, xml, ctype, iconv, intl, pdo, pdo_mysql, dom, filter, gd, iconv, json, mbstring, pdo + env: + update: true + + - name: Validate composer.json + run: composer validate --strict + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Install Lowest Composer dependencies + if: "matrix.php-versions < '8.1'" + run: composer update --prefer-lowest --no-progress --prefer-dist --optimize-autoloader + + - name: Install PHPUnit + run: vendor/bin/simple-phpunit install + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + php-version: ${{ matrix.php-versions }} + + qa: + name: Quality + needs: build + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php-versions: ['8.1'] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, extensions and composer with shivammathur/setup-php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, xml, ctype, intl, dom, filter + tools: symfony + env: + update: true + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: PHP-CS-Fixer + run: symfony php ./vendor/bin/php-cs-fixer fix --dry-run --using-cache=no --verbose --diff + + test: + name: Tests + needs: build + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php-versions: ['7.1', '8.1'] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, extensions and composer with shivammathur/setup-php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, xml, ctype, intl, dom, filter + tools: symfony + env: + update: true + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Install Composer dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Install Lowest Composer dependencies + if: "matrix.php-versions < '8.1'" + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Execute unit tests + run: symfony php ./vendor/bin/simple-phpunit diff --git a/.gitignore b/.gitignore index 3714b9b6..55240f9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.php_cs.cache +.php-cs-fixer.cache .phpunit.result.cache phpunit.xml composer.lock diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache deleted file mode 100644 index 8b92120c..00000000 --- a/.php-cs-fixer.cache +++ /dev/null @@ -1 +0,0 @@ -{"php":"8.1.4","version":"3.8.0:v3.8.0#cbad1115aac4b5c3c5540e7210d3c9fba2f81fa3","indent":" ","lineEnding":"\n","rules":{"array_syntax":true,"backtick_to_shell_exec":true,"binary_operator_spaces":true,"blank_line_before_statement":{"statements":["return"]},"braces":{"allow_single_line_anonymous_class_with_empty_body":true,"allow_single_line_closure":true},"cast_spaces":true,"class_attributes_separation":{"elements":{"method":"one"}},"class_definition":{"single_line":true},"class_reference_name_casing":true,"clean_namespace":true,"concat_space":true,"echo_tag_syntax":true,"empty_loop_body":{"style":"braces"},"empty_loop_condition":true,"fully_qualified_strict_types":true,"function_typehint_space":true,"general_phpdoc_tag_rename":{"replacements":{"inheritDocs":"inheritDoc"}},"include":true,"increment_style":true,"integer_literal_case":true,"lambda_not_used_import":true,"linebreak_after_opening_tag":true,"magic_constant_casing":true,"magic_method_casing":true,"method_argument_space":{"on_multiline":"ignore"},"native_function_casing":true,"native_function_type_declaration_casing":true,"no_alias_language_construct_call":true,"no_alternative_syntax":true,"no_binary_string":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_blank_lines":{"tokens":["case","continue","curly_brace_block","default","extra","parenthesis_brace_block","square_brace_block","switch","throw","use"]},"no_leading_namespace_whitespace":true,"no_mixed_echo_print":true,"no_multiline_whitespace_around_double_arrow":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_around_offset":true,"no_superfluous_phpdoc_tags":{"allow_mixed":true,"allow_unused_params":true},"no_trailing_comma_in_list_call":true,"no_trailing_comma_in_singleline_array":true,"no_trailing_comma_in_singleline_function_call":true,"no_unneeded_control_parentheses":{"statements":["break","clone","continue","echo_print","return","switch_case","yield","yield_from"]},"no_unneeded_curly_braces":{"namespaces":true},"no_unneeded_import_alias":true,"no_unset_cast":true,"no_unused_imports":true,"no_whitespace_before_comma_in_array":true,"normalize_index_brace":true,"object_operator_without_whitespace":true,"ordered_imports":true,"php_unit_fqcn_annotation":true,"php_unit_method_casing":true,"phpdoc_align":true,"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag_normalizer":true,"phpdoc_no_access":true,"phpdoc_no_alias_tag":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_tag_type":{"tags":{"inheritDoc":"inline"}},"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_trim_consecutive_blank_line_separation":true,"phpdoc_types":true,"phpdoc_types_order":{"null_adjustment":"always_last","sort_algorithm":"none"},"phpdoc_var_without_name":true,"protected_to_private":true,"semicolon_after_instruction":true,"single_class_element_per_statement":true,"single_line_comment_spacing":true,"single_line_comment_style":{"comment_types":["hash"]},"single_line_throw":true,"single_quote":true,"single_space_after_construct":true,"space_after_semicolon":{"remove_in_empty_for_expressions":true},"standardize_increment":true,"standardize_not_equals":true,"switch_continue_to_break":true,"trailing_comma_in_multiline":true,"trim_array_spaces":true,"types_spaces":true,"unary_operator_spaces":true,"whitespace_after_comma_in_array":true,"yoda_style":true,"blank_line_after_opening_tag":true,"compact_nullable_typehint":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"return_type_declaration":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"no_break_comment":true,"no_closing_tag":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"src\/HTMLPurifiersRegistryInterface.php":187845934,"src\/Form\/Listener\/HTMLPurifierListener.php":4116418424,"src\/Form\/TypeExtension\/HTMLPurifierTextTypeExtension.php":608289064,"src\/HTMLPurifiersRegistry.php":795094810,"src\/CacheWarmer\/SerializerCacheWarmer.php":2733650958,"src\/HTMLPurifierConfigFactory.php":1012979259,"src\/Twig\/HTMLPurifierExtension.php":3905834270,"src\/Twig\/HTMLPurifierRuntime.php":1281382522,"src\/DependencyInjection\/Configuration.php":2513854708,"src\/DependencyInjection\/ExerciseHTMLPurifierExtension.php":1265155058,"src\/DependencyInjection\/Compiler\/HTMLPurifierPass.php":1538180982,"src\/ExerciseHTMLPurifierBundle.php":2699336334,"tests\/HTMLPurifierConfigFactoryTest.php":969987042,"tests\/Form\/Listener\/HTMLPurifierListenerTest.php":1185658046,"tests\/Form\/TypeExtension\/HTMLPurifierTextTypeExtensionTest.php":1496463765,"tests\/CacheWarmer\/SerializerCacheWarmerTest.php":3561678071,"tests\/HTMLPurifiersRegistryTest.php":2606387570,"tests\/Twig\/HTMLPurifierRuntimeTest.php":887512979,"tests\/DependencyInjection\/ExerciseHTMLPurifierExtensionTest.php":2900253984,"tests\/DependencyInjection\/Compiler\/HTMLPurifierPassTest.php":1485032584}} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e0dc3bd7..00000000 --- a/.travis.yml +++ /dev/null @@ -1,68 +0,0 @@ -language: php - -sudo: false - -dist: xenial - -env: - global: - - COMPOSER_MEMORY_LIMIT=-1 - - SYMFONY_PHPUNIT_DIR=$HOME/.phpunit-bridge - -cache: - directories: - - $HOME/.composer/cache - - $HOME/.phpunit-bridge - -jobs: - include: - # Lowest - - php: 7.1 - env: COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak - - php: 7.1 - env: SYMFONY_REQUIRE="4.3.*" COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak - - # Stable - - php: 7.2 - env: SYMFONY_REQUIRE="3.4.*" COMPOSER_FLAGS="--prefer-stable" - - php: 7.3 - env: SYMFONY_REQUIRE="4.4.*" COMPOSER_FLAGS="--prefer-stable" - - php: 7.4 - env: SYMFONY_REQUIRE="5.0.*" COMPOSER_FLAGS="--prefer-stable" - - php: 8.0 - env: SYMFONY_REQUIRE="5.0.*" COMPOSER_FLAGS="--prefer-stable" SYMFONY_DEPRECATIONS_HELPER=weak - - # Dev - - php: 7.4 - env: STABILITY=dev - - # QA - - stage: QA - name: PHP CS Fixer - php: 7.4 - script: vendor/bin/php-cs-fixer fix --dry-run --diff - - name: Coverage - php: 7.4 - before_script: - - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,} - - echo "xdebug.mode = coverage" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini - - if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi - script: - - ./vendor/bin/simple-phpunit -v --coverage-text - - allow_failures: - - env: STABILITY=dev - -before_install: - - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available" - - composer self-update - - composer global require --no-progress --no-scripts --no-plugins symfony/flex - -install: - - composer update --prefer-dist --no-interaction $COMPOSER_FLAGS - - vendor/bin/simple-phpunit install - -script: - - if [[ -v $STABILITY ]]; then composer config minimum-stability $STABILITY; fi; - - composer validate --strict --no-check-lock - - vendor/bin/simple-phpunit -v diff --git a/composer.json b/composer.json index 6bb8e97a..e2c50218 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ ], "require": { "php": "^7.1.3 || ^8.0.0", - "ezyang/htmlpurifier": "~4.0", + "ezyang/htmlpurifier": "~4.14", "symfony/config": "~4.4 || ^5.0 || ^6.0", "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0", "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0" diff --git a/src/CacheWarmer/SerializerCacheWarmer.php b/src/CacheWarmer/SerializerCacheWarmer.php index 3256503b..bfa42f13 100644 --- a/src/CacheWarmer/SerializerCacheWarmer.php +++ b/src/CacheWarmer/SerializerCacheWarmer.php @@ -26,9 +26,8 @@ class SerializerCacheWarmer implements CacheWarmerInterface private $filesystem; /** - * @param string[] $paths - * @param string[] $profiles - * @param HTMLPurifiersRegistryInterface $registry Used to build cache within bundle runtime + * @param string[] $paths + * @param string[] $profiles */ public function __construct(array $paths, array $profiles, HTMLPurifiersRegistryInterface $registry, Filesystem $filesystem) { @@ -41,7 +40,7 @@ public function __construct(array $paths, array $profiles, HTMLPurifiersRegistry /** * {@inheritdoc} */ - public function warmUp(string $cacheDir): array + public function warmUp($cacheDir): array { foreach ($this->paths as $path) { $this->filesystem->remove($path); // clean previous cache diff --git a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php index 48105196..74b067e4 100644 --- a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -14,7 +14,10 @@ class ExerciseHTMLPurifierExtension extends Extension { - public function load(array $configs, ContainerBuilder $container) + /** + * {@inheritdoc} + */ + public function load(array $configs, ContainerBuilder $container): void { $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); diff --git a/src/Form/Listener/HTMLPurifierListener.php b/src/Form/Listener/HTMLPurifierListener.php index 3a3eebd5..dfbf7420 100644 --- a/src/Form/Listener/HTMLPurifierListener.php +++ b/src/Form/Listener/HTMLPurifierListener.php @@ -25,7 +25,7 @@ public function purifySubmittedData(FormEvent $event): void return; // because we don't want to handle it here } - if (0 === strlen($submittedData = trim($data))) { + if (0 === strlen($submittedData = trim((string) $data))) { if ($submittedData !== $data) { $event->setData($submittedData); } diff --git a/src/HTMLPurifierConfigFactory.php b/src/HTMLPurifierConfigFactory.php index ce0657b2..9ba5d1aa 100644 --- a/src/HTMLPurifierConfigFactory.php +++ b/src/HTMLPurifierConfigFactory.php @@ -62,7 +62,7 @@ public static function create( * This build should never happen on runtime, since purifiers cache should * be generated during warm up. */ - public static function buildHTMLDefinition(\HTMLPurifier_Definition $def, array $attributes, array $elements, array $blankElements): void + public static function buildHTMLDefinition(\HTMLPurifier_HTMLDefinition $def, array $attributes, array $elements, array $blankElements): void { foreach ($attributes as $elementName => $rule) { foreach ($rule as $attributeName => $definition) { diff --git a/tests/CacheWarmer/SerializerCacheWarmerTest.php b/tests/CacheWarmer/SerializerCacheWarmerTest.php index 3429e5fc..b21ec241 100644 --- a/tests/CacheWarmer/SerializerCacheWarmerTest.php +++ b/tests/CacheWarmer/SerializerCacheWarmerTest.php @@ -9,14 +9,14 @@ class SerializerCacheWarmerTest extends TestCase { - public function testShouldBeRequired() + public function testShouldBeRequired(): void { $cacheWarmer = new SerializerCacheWarmer([], [], $this->createMock(HTMLPurifiersRegistryInterface::class), new Filesystem()); $this->assertFalse($cacheWarmer->isOptional()); } - public function testWarmUpShouldCreatePaths() + public function testWarmUpShouldCreatePaths(): void { $fs = new Filesystem(); $path = sys_get_temp_dir().DIRECTORY_SEPARATOR.'html_purifier'; @@ -35,7 +35,7 @@ public function testWarmUpShouldCreatePaths() $fs->remove($path); } - public function testWarmUpShouldCallPurifyForEachProfile() + public function testWarmUpShouldCallPurifyForEachProfile(): void { $purifier = $this->createMock(\HTMLPurifier::class); $purifier->expects($this->exactly(2)) diff --git a/tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php b/tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php index 7c484f2f..f5ef7090 100644 --- a/tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php +++ b/tests/DependencyInjection/Compiler/HTMLPurifierPassTest.php @@ -16,7 +16,7 @@ class HTMLPurifierPassTest extends TestCase { - /** @var ContainerBuilder|MockObject */ + /** @var ContainerBuilder|MockObject|null */ private $container; protected function setUp(): void @@ -34,7 +34,7 @@ protected function tearDown(): void $this->container = null; } - public function testProcessOnlyIfRegistryInterfaceIsDefined() + public function testProcessOnlyIfRegistryInterfaceIsDefined(): void { $this->container->expects($this->once()) ->method('hasAlias') @@ -50,7 +50,7 @@ public function testProcessOnlyIfRegistryInterfaceIsDefined() $pass->process($this->container); } - public function testProcess() + public function testProcess(): void { $container = new ContainerBuilder(); $purifier = $container->register(DummyPurifier::class) @@ -71,7 +71,7 @@ public function testProcess() $this->assertSame(DummyPurifier::class, (string) $map['test']->getValues()[0]); } - public function testProcessDoNothingIfRegistryIsNotDefined() + public function testProcessDoNothingIfRegistryIsNotDefined(): void { $this->container ->expects($this->once()) @@ -94,7 +94,7 @@ public function testProcessDoNothingIfRegistryIsNotDefined() $pass->process($this->container); } - public function testProcessFailsIfTaggedServiceMissesProfileName() + public function testProcessFailsIfTaggedServiceMissesProfileName(): void { $container = new ContainerBuilder(); $container->register(DummyPurifier::class) diff --git a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php index 274e5431..ffaa3e2e 100644 --- a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php +++ b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php @@ -17,17 +17,17 @@ class ExerciseHTMLPurifierExtensionTest extends TestCase private const DEFAULT_CACHE_PATH = '%kernel.cache_dir%/htmlpurifier'; /** - * @var ContainerBuilder + * @var ContainerBuilder|null */ private $container; /** - * @var ExerciseHTMLPurifierExtension + * @var ExerciseHTMLPurifierExtension|null */ private $extension; /** - * @var array + * @var array|null */ private $defaultConfig; @@ -48,7 +48,7 @@ public function tearDown(): void $this->container = null; } - public function testShouldLoadDefaultConfiguration() + public function testShouldLoadDefaultConfiguration(): void { $this->extension->load([], $this->container); @@ -57,7 +57,7 @@ public function testShouldLoadDefaultConfiguration() $this->assertRegistryHasProfiles(['default']); } - public function testInvalidParent() + public function testInvalidParent(): void { $config = [ 'html_profiles' => [ @@ -80,12 +80,12 @@ public function testInvalidParent() /** * @dataProvider provideInvalidElementDefinitions */ - public function testInvalidElements(array $elementDefinition) + public function testInvalidElements(array $elementDefinition): void { $config = [ 'html_profiles' => [ 'default' => [ - 'elements' => ['a' => []], + 'elements' => ['a' => $elementDefinition], ], ], ]; @@ -104,7 +104,7 @@ public function provideInvalidElementDefinitions(): iterable yield 'too many arguments' => [['', '', '', [], [], 'extra argument']]; } - public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPath() + public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPath(): void { $config = [ 'default_cache_serializer_path' => null, @@ -126,7 +126,7 @@ public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPath $this->assertRegistryHasProfiles(['default']); } - public function testShouldNotDeepMergeOptions() + public function testShouldNotDeepMergeOptions(): void { $configs = [ ['html_profiles' => [ @@ -154,7 +154,7 @@ public function testShouldNotDeepMergeOptions() $this->assertRegistryHasProfiles(['default']); } - public function testShouldLoadCustomConfiguration() + public function testShouldLoadCustomConfiguration(): void { $config = [ 'html_profiles' => [ @@ -195,7 +195,7 @@ public function testShouldLoadCustomConfiguration() $this->assertRegistryHasProfiles($profiles); } - public function testShouldLoadComplexCustomConfiguration() + public function testShouldLoadComplexCustomConfiguration(): void { $defaultConfig = [ 'AutoFormat.AutoParagraph' => true, @@ -288,7 +288,7 @@ public function testShouldLoadComplexCustomConfiguration() $this->assertRegistryHasProfiles($profiles); } - public function testShouldRegisterAliases() + public function testShouldRegisterAliases(): void { if (!method_exists($this->container, 'registerAliasForArgument')) { $this->markTestSkipped('Alias arguments binding is not available.'); @@ -373,10 +373,8 @@ public function testShouldRegisterAliases() /** * Asserts that the named config definition extends the default profile and * loads the given options. - * - * @param string $name */ - private function assertConfigDefinition($name, array $config, array $parents = [], array $attributes = [], array $elements = [], array $blankElements = []) + private function assertConfigDefinition(string $name, array $config, array $parents = [], array $attributes = [], array $elements = [], array $blankElements = []): void { $this->assertTrue($this->container->hasDefinition('exercise_html_purifier.config.'.$name)); diff --git a/tests/Form/Listener/HTMLPurifierListenerTest.php b/tests/Form/Listener/HTMLPurifierListenerTest.php index 14447a60..04dc8240 100644 --- a/tests/Form/Listener/HTMLPurifierListenerTest.php +++ b/tests/Form/Listener/HTMLPurifierListenerTest.php @@ -10,7 +10,7 @@ class HTMLPurifierListenerTest extends TestCase { - public function testPurify() + public function testPurify(): void { $input = 'text'; $purifiedInput = '

text

'; @@ -41,7 +41,7 @@ public function testPurify() $this->assertSame($purifiedInput, $event->getData()); } - public function testPurifyTrimEmptyValues() + public function testPurifyTrimEmptyValues(): void { $input = ' '; $trimmedInput = ''; @@ -69,8 +69,10 @@ public function testPurifyTrimEmptyValues() /** * @dataProvider provideInvalidInput + * + * @param mixed $input */ - public function testPurifyDoNothingForEmptyOrNonScalarData($input) + public function testPurifyDoNothingForEmptyOrNonScalarData($input): void { $registry = $this->createMock(HTMLPurifiersRegistryInterface::class); $registry @@ -101,6 +103,9 @@ public function provideInvalidInput(): iterable yield [new \stdClass()]; } + /** + * @param mixed $data + */ private function getFormEvent($data): FormEvent { return new FormEvent($this->createMock(FormInterface::class), $data); diff --git a/tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php b/tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php index 1bddef62..84682181 100644 --- a/tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php +++ b/tests/Form/TypeExtension/HTMLPurifierTextTypeExtensionTest.php @@ -5,6 +5,7 @@ use Exercise\HTMLPurifierBundle\Form\Listener\HTMLPurifierListener; use Exercise\HTMLPurifierBundle\Form\TypeExtension\HTMLPurifierTextTypeExtension; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; @@ -13,6 +14,7 @@ class HTMLPurifierTextTypeExtensionTest extends FormIntegrationTestCase { + /** @var HTMLPurifiersRegistryInterface|MockObject|null */ private $registry; protected function setUp(): void @@ -29,14 +31,14 @@ protected function tearDown(): void $this->registry = null; } - protected function getTypeExtensions() + protected function getTypeExtensions(): array { return [ new HTMLPurifierTextTypeExtension($this->registry), ]; } - public function testDefaultOptions() + public function testDefaultOptions(): void { $this->registry ->expects($this->never()) @@ -56,7 +58,7 @@ public function testDefaultOptions() $this->assertFalse($this->hasPurifierListener($form)); } - public function testPurifyOptionsNeedDefaultProfile() + public function testPurifyOptionsNeedDefaultProfile(): void { $this->registry ->expects($this->once()) @@ -75,7 +77,7 @@ public function testPurifyOptionsNeedDefaultProfile() $this->factory->create(TextType::class, null, ['purify_html' => true]); } - public function testDefaultOptionsWhenPurifyIsTrue() + public function testDefaultOptionsWhenPurifyIsTrue(): void { $this->registry ->expects($this->once()) @@ -93,7 +95,7 @@ public function testDefaultOptionsWhenPurifyIsTrue() $this->assertTrue($this->hasPurifierListener($form)); } - public function testInvalidProfile() + public function testInvalidProfile(): void { $this->registry ->expects($this->once()) diff --git a/tests/HTMLPurifierConfigFactoryTest.php b/tests/HTMLPurifierConfigFactoryTest.php index e0a129d6..70dfec9c 100644 --- a/tests/HTMLPurifierConfigFactoryTest.php +++ b/tests/HTMLPurifierConfigFactoryTest.php @@ -8,6 +8,7 @@ class HTMLPurifierConfigFactoryTest extends TestCase { + /** @var string */ private static $cacheDir; public static function setUpBeforeClass(): void @@ -21,14 +22,14 @@ public static function tearDownAfterClass(): void (new Filesystem())->remove(self::$cacheDir); } - public function testCreateUseDoesNotBuildDefinitionByDefault() + public function testCreateUseDoesNotBuildDefinitionByDefault(): void { TestHTMLPurifierConfigFactory::create('default', []); $this->assertSame(0, TestHTMLPurifierConfigFactory::$calledBuild); } - public function testCreateUseSerializedCache() + public function testCreateUseSerializedCache(): void { $configArgs = [ 'test', /* profile */ @@ -53,10 +54,11 @@ public function testCreateUseSerializedCache() class TestHTMLPurifierConfigFactory extends HTMLPurifierConfigFactory { + /** @var int */ public static $calledBuild = 0; public static function buildHTMLDefinition( - \HTMLPurifier_Definition $def, + \HTMLPurifier_HTMLDefinition $def, array $attributes, array $elements, array $blankElements diff --git a/tests/HTMLPurifiersRegistryTest.php b/tests/HTMLPurifiersRegistryTest.php index d58d6df0..5dcfacea 100644 --- a/tests/HTMLPurifiersRegistryTest.php +++ b/tests/HTMLPurifiersRegistryTest.php @@ -3,12 +3,15 @@ namespace Exercise\HTMLPurifierBundle\Tests; use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistry; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; class HTMLPurifiersRegistryTest extends TestCase { + /** @var MockObject|ContainerInterface|null */ private $locator; + /** @var HTMLPurifiersRegistry|null */ private $registry; protected function setUp(): void @@ -32,7 +35,7 @@ public function provideProfiles(): iterable /** * @dataProvider provideProfiles */ - public function testHas($profile) + public function testHas(string $profile): void { $this->locator->expects($this->once()) ->method('has') @@ -46,7 +49,7 @@ public function testHas($profile) /** * @dataProvider provideProfiles */ - public function testHasNot($profile) + public function testHasNot(string $profile): void { $this->locator->expects($this->once()) ->method('has') @@ -60,7 +63,7 @@ public function testHasNot($profile) /** * @dataProvider provideProfiles */ - public function testGet($profile) + public function testGet(string $profile): void { $purifier = $this->createMock(\HTMLPurifier::class); diff --git a/tests/Twig/HTMLPurifierRuntimeTest.php b/tests/Twig/HTMLPurifierRuntimeTest.php index e6b03349..21594ddb 100644 --- a/tests/Twig/HTMLPurifierRuntimeTest.php +++ b/tests/Twig/HTMLPurifierRuntimeTest.php @@ -11,7 +11,7 @@ class HTMLPurifierRuntimeTest extends TestCase /** * @dataProvider providePurifierProfiles */ - public function testPurifyFilter($profile) + public function testPurifyFilter(string $profile): void { $input = 'text'; $purifiedInput = '

text

'; From 9712ab74579222d58f5e20a12afe8ff0648b0ed3 Mon Sep 17 00:00:00 2001 From: Andrii Afanasiev Date: Fri, 1 Jul 2022 08:07:43 +0200 Subject: [PATCH 47/61] Default Cache.SerializerPermissions support --- README.md | 2 ++ src/DependencyInjection/Configuration.php | 4 +++ .../ExerciseHTMLPurifierExtension.php | 1 + .../ExerciseHTMLPurifierExtensionTest.php | 27 +++++++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/README.md b/README.md index ae39ef4f..454fdb59 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ had specified the following configuration: exercise_html_purifier: default_cache_serializer_path: '%kernel.cache_dir%/htmlpurifier' + # 493 int => ocl "0755" + default_cache_serializer_permissions: 493 ``` The `default` profile is special, it is *always* defined and its configuration diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 4b534279..f79205e4 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -21,6 +21,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('default_cache_serializer_path') ->defaultValue('%kernel.cache_dir%/htmlpurifier') ->end() + ->scalarNode('default_cache_serializer_permissions') + // (format "%#o" 493) "0755" + ->defaultValue(493) + ->end() ->arrayNode('html_profiles') ->useAttributeAsKey('name') ->normalizeKeys(false) diff --git a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php index 74b067e4..3383d1c2 100644 --- a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -27,6 +27,7 @@ public function load(array $configs, ContainerBuilder $container): void // Set default serializer cache path, while ensuring a default profile is defined $configs['html_profiles']['default']['config']['Cache.SerializerPath'] = $configs['default_cache_serializer_path']; + $configs['html_profiles']['default']['config']['Cache.SerializerPermissions'] = $configs['default_cache_serializer_permissions']; $serializerPaths = []; // Drop when require Symfony > 3.4 diff --git a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php index ffaa3e2e..d78ec06f 100644 --- a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php +++ b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php @@ -15,6 +15,7 @@ class ExerciseHTMLPurifierExtensionTest extends TestCase { private const DEFAULT_CACHE_PATH = '%kernel.cache_dir%/htmlpurifier'; + private const DEFAULT_CACHE_PERMISSIONS = 493; /** * @var ContainerBuilder|null @@ -38,6 +39,7 @@ public function setUp(): void $this->extension = new ExerciseHTMLPurifierExtension(); $this->defaultConfig = [ 'Cache.SerializerPath' => self::DEFAULT_CACHE_PATH, + 'Cache.SerializerPermissions' => self::DEFAULT_CACHE_PERMISSIONS, ]; } @@ -121,6 +123,31 @@ public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPath $this->assertDefaultConfigDefinition(array_merge($config['html_profiles']['default']['config'], [ 'Cache.SerializerPath' => null, + 'Cache.SerializerPermissions' => 493, + ])); + $this->assertCacheWarmerSerializerArgs([], ['default']); + $this->assertRegistryHasProfiles(['default']); + } + + public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPermissions(): void + { + $config = [ + 'default_cache_serializer_path' => null, + 'default_cache_serializer_permissions' => 511, + 'html_profiles' => [ + 'default' => [ + 'config' => [ + 'AutoFormat.AutoParagraph' => true, + ], + ], + ], + ]; + + $this->extension->load([$config], $this->container); + + $this->assertDefaultConfigDefinition(array_merge($config['html_profiles']['default']['config'], [ + 'Cache.SerializerPath' => null, + 'Cache.SerializerPermissions' => 511, ])); $this->assertCacheWarmerSerializerArgs([], ['default']); $this->assertRegistryHasProfiles(['default']); From 4eeaa2bcb96af502bf323abfe1f270bb42db7e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 25 Aug 2022 14:44:28 +0200 Subject: [PATCH 48/61] Rename CHANGELOG to CHANGELOG.md --- CHANGELOG => CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CHANGELOG => CHANGELOG.md (100%) diff --git a/CHANGELOG b/CHANGELOG.md similarity index 100% rename from CHANGELOG rename to CHANGELOG.md From ee8bc6bd91c8f1cc51de22b8ea467b9c5b61630b Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sun, 28 Aug 2022 17:40:31 +0200 Subject: [PATCH 49/61] update CHANGELOG for 4.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 308e9413..cde3b1ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Version 4.1 (08/2022) + + * Add support of the `Cache.SerializerPermissions` for default profile + ## Version 4.0 (04/2022) * [BC Break] Drop support for Symfony < 4.4 From 32ee71c37188461cc87b4c67b4726be7fc9d0405 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Sun, 28 Aug 2022 17:46:07 +0200 Subject: [PATCH 50/61] fixup CS --- tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php index d78ec06f..8a5937f3 100644 --- a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php +++ b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php @@ -134,7 +134,7 @@ public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPerm $config = [ 'default_cache_serializer_path' => null, 'default_cache_serializer_permissions' => 511, - 'html_profiles' => [ + 'html_profiles' => [ 'default' => [ 'config' => [ 'AutoFormat.AutoParagraph' => true, From 948bd5e0d47d9c2a6bc7b6ad6ace81d5f808f98a Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 29 Aug 2022 11:03:18 +0200 Subject: [PATCH 51/61] fix issue with overwriting default config on default profile --- .../ExerciseHTMLPurifierExtension.php | 5 +++- .../ExerciseHTMLPurifierExtensionTest.php | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php index 3383d1c2..f6a5686f 100644 --- a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -27,7 +27,10 @@ public function load(array $configs, ContainerBuilder $container): void // Set default serializer cache path, while ensuring a default profile is defined $configs['html_profiles']['default']['config']['Cache.SerializerPath'] = $configs['default_cache_serializer_path']; - $configs['html_profiles']['default']['config']['Cache.SerializerPermissions'] = $configs['default_cache_serializer_permissions']; + + if (!isset($configs['html_profiles']['default']['config']['Cache.SerializerPermissions'])) { + $configs['html_profiles']['default']['config']['Cache.SerializerPermissions'] = $configs['default_cache_serializer_permissions']; + } $serializerPaths = []; // Drop when require Symfony > 3.4 diff --git a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php index 8a5937f3..fde6d8f8 100644 --- a/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php +++ b/tests/DependencyInjection/ExerciseHTMLPurifierExtensionTest.php @@ -153,6 +153,30 @@ public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPerm $this->assertRegistryHasProfiles(['default']); } + public function testShouldAllowOverridingDefaultConfigurationCacheSerializerPermissionsOnDefaultProfile(): void + { + $config = [ + 'default_cache_serializer_path' => null, + 'html_profiles' => [ + 'default' => [ + 'config' => [ + 'AutoFormat.AutoParagraph' => true, + 'Cache.SerializerPermissions' => 511, + ], + ], + ], + ]; + + $this->extension->load([$config], $this->container); + + $this->assertDefaultConfigDefinition(array_merge($config['html_profiles']['default']['config'], [ + 'Cache.SerializerPath' => null, + 'Cache.SerializerPermissions' => 511, + ])); + $this->assertCacheWarmerSerializerArgs([], ['default']); + $this->assertRegistryHasProfiles(['default']); + } + public function testShouldNotDeepMergeOptions(): void { $configs = [ From 53873ea3286d9bb8ca046c8d370f654a864f3a09 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Tue, 4 Jul 2023 20:46:13 +0200 Subject: [PATCH 52/61] run php cs fixer --- src/CacheWarmer/SerializerCacheWarmer.php | 6 ------ src/DependencyInjection/Compiler/HTMLPurifierPass.php | 3 --- src/DependencyInjection/Configuration.php | 3 --- .../ExerciseHTMLPurifierExtension.php | 3 --- src/ExerciseHTMLPurifierBundle.php | 3 --- src/Form/Listener/HTMLPurifierListener.php | 3 --- src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php | 9 --------- src/HTMLPurifiersRegistry.php | 6 ------ src/Twig/HTMLPurifierExtension.php | 3 --- tests/Form/Listener/HTMLPurifierListenerTest.php | 5 ----- 10 files changed, 44 deletions(-) diff --git a/src/CacheWarmer/SerializerCacheWarmer.php b/src/CacheWarmer/SerializerCacheWarmer.php index bfa42f13..0f9e3d66 100644 --- a/src/CacheWarmer/SerializerCacheWarmer.php +++ b/src/CacheWarmer/SerializerCacheWarmer.php @@ -37,9 +37,6 @@ public function __construct(array $paths, array $profiles, HTMLPurifiersRegistry $this->filesystem = $filesystem; } - /** - * {@inheritdoc} - */ public function warmUp($cacheDir): array { foreach ($this->paths as $path) { @@ -55,9 +52,6 @@ public function warmUp($cacheDir): array return []; } - /** - * {@inheritdoc} - */ public function isOptional(): bool { return false; diff --git a/src/DependencyInjection/Compiler/HTMLPurifierPass.php b/src/DependencyInjection/Compiler/HTMLPurifierPass.php index 16ed073a..1008648a 100644 --- a/src/DependencyInjection/Compiler/HTMLPurifierPass.php +++ b/src/DependencyInjection/Compiler/HTMLPurifierPass.php @@ -14,9 +14,6 @@ class HTMLPurifierPass implements CompilerPassInterface { public const PURIFIER_TAG = 'exercise.html_purifier'; - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container): void { if (!$container->hasAlias(HTMLPurifiersRegistryInterface::class)) { diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index f79205e4..ec18707b 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -8,9 +8,6 @@ class Configuration implements ConfigurationInterface { - /** - * {@inheritdoc} - */ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('exercise_html_purifier'); diff --git a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php index f6a5686f..fc55d169 100644 --- a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -14,9 +14,6 @@ class ExerciseHTMLPurifierExtension extends Extension { - /** - * {@inheritdoc} - */ public function load(array $configs, ContainerBuilder $container): void { $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); diff --git a/src/ExerciseHTMLPurifierBundle.php b/src/ExerciseHTMLPurifierBundle.php index f22d1b0b..395a5196 100644 --- a/src/ExerciseHTMLPurifierBundle.php +++ b/src/ExerciseHTMLPurifierBundle.php @@ -8,9 +8,6 @@ class ExerciseHTMLPurifierBundle extends Bundle { - /** - * {@inheritdoc} - */ public function build(ContainerBuilder $container): void { $container->addCompilerPass(new HTMLPurifierPass()); diff --git a/src/Form/Listener/HTMLPurifierListener.php b/src/Form/Listener/HTMLPurifierListener.php index dfbf7420..2377acf7 100644 --- a/src/Form/Listener/HTMLPurifierListener.php +++ b/src/Form/Listener/HTMLPurifierListener.php @@ -36,9 +36,6 @@ public function purifySubmittedData(FormEvent $event): void $event->setData($this->getPurifier()->purify($submittedData)); } - /** - * {@inheritdoc} - */ public static function getSubscribedEvents(): array { return [ diff --git a/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php b/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php index 90815dd7..4af73ec1 100644 --- a/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php +++ b/src/Form/TypeExtension/HTMLPurifierTextTypeExtension.php @@ -20,17 +20,11 @@ public function __construct(HTMLPurifiersRegistryInterface $registry) $this->purifiersRegistry = $registry; } - /** - * {@inheritdoc} - */ public static function getExtendedTypes(): iterable { return [TextType::class]; } - /** - * {@inheritdoc} - */ public function configureOptions(OptionsResolver $resolver): void { $resolver @@ -58,9 +52,6 @@ public function configureOptions(OptionsResolver $resolver): void ; } - /** - * {@inheritdoc} - */ public function buildForm(FormBuilderInterface $builder, array $options): void { if ($options['purify_html']) { diff --git a/src/HTMLPurifiersRegistry.php b/src/HTMLPurifiersRegistry.php index ec8f39c8..9e4cef1e 100644 --- a/src/HTMLPurifiersRegistry.php +++ b/src/HTMLPurifiersRegistry.php @@ -13,17 +13,11 @@ public function __construct(ContainerInterface $purifiersLocator) $this->purifiersLocator = $purifiersLocator; } - /** - * {@inheritdoc} - */ public function has(string $profile): bool { return $this->purifiersLocator->has($profile); } - /** - * {@inheritdoc} - */ public function get(string $profile): \HTMLPurifier { return $this->purifiersLocator->get($profile); diff --git a/src/Twig/HTMLPurifierExtension.php b/src/Twig/HTMLPurifierExtension.php index aceafb77..2c922d88 100644 --- a/src/Twig/HTMLPurifierExtension.php +++ b/src/Twig/HTMLPurifierExtension.php @@ -7,9 +7,6 @@ class HTMLPurifierExtension extends AbstractExtension { - /** - * {@inheritdoc} - */ public function getFilters(): array { return [ diff --git a/tests/Form/Listener/HTMLPurifierListenerTest.php b/tests/Form/Listener/HTMLPurifierListenerTest.php index 04dc8240..bf5e73c6 100644 --- a/tests/Form/Listener/HTMLPurifierListenerTest.php +++ b/tests/Form/Listener/HTMLPurifierListenerTest.php @@ -69,8 +69,6 @@ public function testPurifyTrimEmptyValues(): void /** * @dataProvider provideInvalidInput - * - * @param mixed $input */ public function testPurifyDoNothingForEmptyOrNonScalarData($input): void { @@ -103,9 +101,6 @@ public function provideInvalidInput(): iterable yield [new \stdClass()]; } - /** - * @param mixed $data - */ private function getFormEvent($data): FormEvent { return new FormEvent($this->createMock(FormInterface::class), $data); From d9539c656e16e87a540cd2f4b3bbaaa92c4bf158 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Tue, 4 Jul 2023 20:42:36 +0200 Subject: [PATCH 53/61] add .gitattributes to exclude files from dist --- .gitattributes | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a1e85ac6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +.gitattributes export-ignore +.gitignore export-ignore +.github export-ignore +tests/ export-ignore +.php-cs-fixer.dist.php export-ignore +phpunit.xml.dist export-ignore From 70e4021c03954a1b13917d654c17bae0ab16b641 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Thu, 16 Nov 2023 12:24:17 +0100 Subject: [PATCH 54/61] Allow Symfony 7 --- .github/workflows/qa.yaml | 103 ++++++++-------------- README.md | 2 +- composer.json | 20 ++--- qa/php-cs-fixer/composer.json | 5 ++ src/CacheWarmer/SerializerCacheWarmer.php | 2 +- 5 files changed, 53 insertions(+), 79 deletions(-) create mode 100644 qa/php-cs-fixer/composer.json diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index 55d48e32..4abccde0 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -5,59 +5,14 @@ on: pull_request: jobs: - build: - name: Build - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - php-versions: ['7.1', '8.1'] - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP, extensions and composer with shivammathur/setup-php - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: mbstring, xml, ctype, iconv, intl, pdo, pdo_mysql, dom, filter, gd, iconv, json, mbstring, pdo - env: - update: true - - - name: Validate composer.json - run: composer validate --strict - - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Install Composer dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader - - - name: Install Lowest Composer dependencies - if: "matrix.php-versions < '8.1'" - run: composer update --prefer-lowest --no-progress --prefer-dist --optimize-autoloader - - - name: Install PHPUnit - run: vendor/bin/simple-phpunit install - - - name: Cache composer dependencies - uses: actions/cache@v1 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - php-version: ${{ matrix.php-versions }} - qa: name: Quality - needs: build runs-on: ubuntu-latest strategy: fail-fast: true matrix: - php-versions: ['8.1'] + php-versions: + - 8.2 steps: - name: Checkout @@ -68,28 +23,38 @@ jobs: with: php-version: ${{ matrix.php-versions }} extensions: mbstring, xml, ctype, intl, dom, filter - tools: symfony env: update: true - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Install Composer dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader + run: composer install --working-dir=qa/php-cs-fixer - name: PHP-CS-Fixer - run: symfony php ./vendor/bin/php-cs-fixer fix --dry-run --using-cache=no --verbose --diff + run: ./qa/php-cs-fixer/vendor/bin/php-cs-fixer fix --dry-run --using-cache=no --verbose --diff test: name: Tests - needs: build runs-on: ubuntu-latest strategy: fail-fast: true matrix: - php-versions: ['7.1', '8.1'] + php-versions: + - 8.1 + - 8.2 + symfony-versions: + - 5.4.* + - 6.3.* + - 6.4.* + - 7.0.* + dependencies: + - '' + include: + - php-versions: '8.1' + symfony-versions: '5.4.*' + dependencies: 'lowest' + exclude: + - php-versions: '8.1' + symfony-versions: '7.0.*' steps: - name: Checkout @@ -99,21 +64,25 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - extensions: mbstring, xml, ctype, intl, dom, filter - tools: symfony + extensions: mbstring, xml, ctype, iconv, intl, pdo, pdo_mysql, dom, filter, gd, json + tools: flex env: update: true - - name: Get composer cache directory - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Install Composer dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader + - name: Install Composer + if: "matrix.dependencies == ''" + run: composer update --no-progress --ansi --prefer-stable + env: + SYMFONY_REQUIRE: ${{ matrix.symfony-versions }} - name: Install Lowest Composer dependencies - if: "matrix.php-versions < '8.1'" - run: composer install --no-progress --prefer-dist --optimize-autoloader + if: "matrix.dependencies == 'lowest'" + run: composer update --no-progress --ansi --prefer-stable --prefer-lowest + env: + SYMFONY_REQUIRE: ${{ matrix.symfony-versions }} + + - name: Validate composer + run: composer validate --strict --no-check-lock - name: Execute unit tests - run: symfony php ./vendor/bin/simple-phpunit + run: ./vendor/bin/simple-phpunit diff --git a/README.md b/README.md index 454fdb59..8a24ba2a 100644 --- a/README.md +++ b/README.md @@ -324,5 +324,5 @@ previous, and "all" could define its own rules too. ## Contributing -PRs are welcomed :). Please target the `3.x` branch for bug fixes and `master` +PRs are welcomed :). Please target the `4.x` branch for bug fixes and `master` for new features. diff --git a/composer.json b/composer.json index e2c50218..66c7335c 100644 --- a/composer.json +++ b/composer.json @@ -12,17 +12,16 @@ } ], "require": { - "php": "^7.1.3 || ^8.0.0", + "php": "^8.1", "ezyang/htmlpurifier": "~4.14", - "symfony/config": "~4.4 || ^5.0 || ^6.0", - "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0", - "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0" + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.0", - "symfony/form": "^4.4 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^6.0", - "twig/twig": "^1.35.0 || ^2.4.4 || ^3.0" + "symfony/form": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^7.0", + "twig/twig": "^2.4.4 || ^3.0" }, "autoload": { "psr-4": { "Exercise\\HTMLPurifierBundle\\": "src/" } @@ -35,7 +34,8 @@ }, "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } - } + }, + "minimum-stability": "dev" } diff --git a/qa/php-cs-fixer/composer.json b/qa/php-cs-fixer/composer.json new file mode 100644 index 00000000..2fbac5dd --- /dev/null +++ b/qa/php-cs-fixer/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "friendsofphp/php-cs-fixer": "^3.38" + } +} diff --git a/src/CacheWarmer/SerializerCacheWarmer.php b/src/CacheWarmer/SerializerCacheWarmer.php index 0f9e3d66..4e7fe7cf 100644 --- a/src/CacheWarmer/SerializerCacheWarmer.php +++ b/src/CacheWarmer/SerializerCacheWarmer.php @@ -37,7 +37,7 @@ public function __construct(array $paths, array $profiles, HTMLPurifiersRegistry $this->filesystem = $filesystem; } - public function warmUp($cacheDir): array + public function warmUp($cacheDir, string $buildDir = null): array { foreach ($this->paths as $path) { $this->filesystem->remove($path); // clean previous cache From 0272e33728fa8ef330250f8182294d3f8cce25a2 Mon Sep 17 00:00:00 2001 From: Marc Heying Date: Fri, 8 Mar 2024 13:39:57 +0100 Subject: [PATCH 55/61] Apply codestyle from current config with friendsofphp/php-cs-fixer:v3.51.0 --- src/CacheWarmer/SerializerCacheWarmer.php | 2 +- src/DependencyInjection/ExerciseHTMLPurifierExtension.php | 2 +- src/HTMLPurifierConfigFactory.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CacheWarmer/SerializerCacheWarmer.php b/src/CacheWarmer/SerializerCacheWarmer.php index 4e7fe7cf..932bd9b0 100644 --- a/src/CacheWarmer/SerializerCacheWarmer.php +++ b/src/CacheWarmer/SerializerCacheWarmer.php @@ -37,7 +37,7 @@ public function __construct(array $paths, array $profiles, HTMLPurifiersRegistry $this->filesystem = $filesystem; } - public function warmUp($cacheDir, string $buildDir = null): array + public function warmUp($cacheDir, ?string $buildDir = null): array { foreach ($this->paths as $path) { $this->filesystem->remove($path); // clean previous cache diff --git a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php index fc55d169..861feca3 100644 --- a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -113,7 +113,7 @@ private static function resolveProfileInheritance(string $parent, array $configs $resolved[$parent]['blank_elements'] = $configs[$parent]['blank_elements']; } - private static function getResolvedConfig(string $parameter, array $parents, array $definition = null): array + private static function getResolvedConfig(string $parameter, array $parents, ?array $definition = null): array { if (null !== $definition) { return array_filter(array_merge( diff --git a/src/HTMLPurifierConfigFactory.php b/src/HTMLPurifierConfigFactory.php index 9ba5d1aa..6eac59fb 100644 --- a/src/HTMLPurifierConfigFactory.php +++ b/src/HTMLPurifierConfigFactory.php @@ -24,7 +24,7 @@ class HTMLPurifierConfigFactory public static function create( string $profile, array $configArray, - \HTMLPurifier_Config $defaultConfig = null, + ?\HTMLPurifier_Config $defaultConfig = null, array $parents = [], array $attributes = [], array $elements = [], From d3a203c6179469e5f15b8dd200fa960e4ab9a5a5 Mon Sep 17 00:00:00 2001 From: Marc Heying Date: Fri, 8 Mar 2024 13:39:57 +0100 Subject: [PATCH 56/61] Fix composer lowest build relates to https://github.com/symfony/symfony/pull/52846 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 66c7335c..95e829f4 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "symfony/form": "^5.4 || ^6.0 || ^7.0", - "symfony/phpunit-bridge": "^7.0", + "symfony/phpunit-bridge": "^7.0.1", "twig/twig": "^2.4.4 || ^3.0" }, "autoload": { From cbeb56acb55b4bee64263a57c7cfa15205df6425 Mon Sep 17 00:00:00 2001 From: Marc Heying Date: Fri, 8 Mar 2024 13:50:05 +0100 Subject: [PATCH 57/61] update to latest action version and get rid of node deprecations warnings like > The following actions uses node12 which is deprecated and will be forced to run on node16 [...] --- .github/workflows/qa.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index 4abccde0..15519dc3 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP, extensions and composer with shivammathur/setup-php uses: shivammathur/setup-php@v2 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP, extensions and composer with shivammathur/setup-php uses: shivammathur/setup-php@v2 From 133a80b3013468ee8cc8c1b8fc73f519729e1430 Mon Sep 17 00:00:00 2001 From: Marc Heying Date: Fri, 8 Mar 2024 17:24:58 +0100 Subject: [PATCH 58/61] Add php 8.3 to build jobs --- .github/workflows/qa.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index 4abccde0..9171a736 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -41,6 +41,7 @@ jobs: php-versions: - 8.1 - 8.2 + - 8.3 symfony-versions: - 5.4.* - 6.3.* From 6e5d2ba5c7affb28205b2ab4c8593a9f4b3bfc8d Mon Sep 17 00:00:00 2001 From: Evgeny Zhukov Date: Sun, 29 Dec 2024 20:42:08 +0300 Subject: [PATCH 59/61] fix for Symfony > 7.1 --- src/DependencyInjection/ExerciseHTMLPurifierExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php index 861feca3..8dd3fa37 100644 --- a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -10,7 +10,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\DependencyInjection\Extension\Extension; class ExerciseHTMLPurifierExtension extends Extension { From 5aa8abe863458e128948336353e8e9b1c5149680 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 6 Oct 2025 20:22:55 +0200 Subject: [PATCH 60/61] move to php format for Symfony DI config --- .../ExerciseHTMLPurifierExtension.php | 12 +++--- src/HTMLPurifierConfigFactory.php | 2 +- src/Resources/config/html_purifier.xml | 28 ------------- src/Resources/config/services.php | 40 +++++++++++++++++++ tests/HTMLPurifierConfigFactoryTest.php | 2 +- 5 files changed, 47 insertions(+), 37 deletions(-) delete mode 100644 src/Resources/config/html_purifier.xml create mode 100644 src/Resources/config/services.php diff --git a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php index 8dd3fa37..061a4043 100644 --- a/src/DependencyInjection/ExerciseHTMLPurifierExtension.php +++ b/src/DependencyInjection/ExerciseHTMLPurifierExtension.php @@ -8,17 +8,17 @@ use Exercise\HTMLPurifierBundle\HTMLPurifiersRegistryInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Reference; class ExerciseHTMLPurifierExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('html_purifier.xml'); + $loader->load('services.php'); $configs = $this->processConfiguration(new Configuration(), $configs); @@ -30,8 +30,6 @@ public function load(array $configs, ContainerBuilder $container): void } $serializerPaths = []; - // Drop when require Symfony > 3.4 - $registerAlias = method_exists($container, 'registerAliasForArgument'); foreach ($configs['html_profiles'] as $name => $definition) { $configId = "exercise_html_purifier.config.$name"; @@ -71,7 +69,7 @@ public function load(array $configs, ContainerBuilder $container): void $serializerPaths[] = $definition['config']['Cache.SerializerPath']; } - if ($registerAlias && $default) { + if ($default) { $container->registerAliasForArgument($id, \HTMLPurifier::class, "$name.purifier"); } } diff --git a/src/HTMLPurifierConfigFactory.php b/src/HTMLPurifierConfigFactory.php index 6eac59fb..e8e88e4e 100644 --- a/src/HTMLPurifierConfigFactory.php +++ b/src/HTMLPurifierConfigFactory.php @@ -28,7 +28,7 @@ public static function create( array $parents = [], array $attributes = [], array $elements = [], - array $blankElements = [] + array $blankElements = [], ): \HTMLPurifier_Config { if ($defaultConfig) { $config = \HTMLPurifier_Config::inherit($defaultConfig); diff --git a/src/Resources/config/html_purifier.xml b/src/Resources/config/html_purifier.xml deleted file mode 100644 index d3bfac09..00000000 --- a/src/Resources/config/html_purifier.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php new file mode 100644 index 00000000..dad69f2d --- /dev/null +++ b/src/Resources/config/services.php @@ -0,0 +1,40 @@ +services() + ->set('exercise_html_purifier.cache_warmer.serializer', SerializerCacheWarmer::class) + ->tag('kernel.cache_warmer') + ->args([ + abstract_arg('cache paths'), + abstract_arg('profiles'), + service(HTMLPurifiersRegistryInterface::class), + service('filesystem'), + ]) + + ->set('exercise_html_purifier.form.text_type_extension', HTMLPurifierTextTypeExtension::class) + ->tag('form.type_extension', ['extended_type' => TextType::class]) + ->args([ + service(HTMLPurifiersRegistryInterface::class), + ]) + + ->set('exercise_html_purifier.twig.extension', HTMLPurifierExtension::class) + ->tag('twig.extension') + + ->set('exercise_html_purifier.twig.runtime', HTMLPurifierRuntime::class) + ->tag('twig.runtime') + ->args([ + service(HTMLPurifiersRegistryInterface::class), + ]); +}; diff --git a/tests/HTMLPurifierConfigFactoryTest.php b/tests/HTMLPurifierConfigFactoryTest.php index 70dfec9c..ff780bfe 100644 --- a/tests/HTMLPurifierConfigFactoryTest.php +++ b/tests/HTMLPurifierConfigFactoryTest.php @@ -61,7 +61,7 @@ public static function buildHTMLDefinition( \HTMLPurifier_HTMLDefinition $def, array $attributes, array $elements, - array $blankElements + array $blankElements, ): void { ++self::$calledBuild; parent::buildHTMLDefinition($def, $attributes, $elements, $blankElements); From 8da1314ed5d7c80f060f40461116508cfaceb850 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Wed, 5 Nov 2025 20:03:41 +0100 Subject: [PATCH 61/61] Add support for Symfony 8 --- .github/workflows/qa.yaml | 10 +++++++--- composer.json | 10 +++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index b77d233c..059a2544 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -42,20 +42,24 @@ jobs: - 8.1 - 8.2 - 8.3 + - 8.4 symfony-versions: - 5.4.* - - 6.3.* - 6.4.* - - 7.0.* + - 7.3.* dependencies: - '' include: - php-versions: '8.1' symfony-versions: '5.4.*' dependencies: 'lowest' + - php-versions: '8.4' + symfony-versions: '7.4.*' + - php-versions: '8.4' + symfony-versions: '8.0.*' exclude: - php-versions: '8.1' - symfony-versions: '7.0.*' + symfony-versions: '7.3.*' steps: - name: Checkout diff --git a/composer.json b/composer.json index 95e829f4..b96a1952 100644 --- a/composer.json +++ b/composer.json @@ -14,13 +14,13 @@ "require": { "php": "^8.1", "ezyang/htmlpurifier": "~4.14", - "symfony/config": "^5.4 || ^6.0 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", - "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0" + "symfony/config": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "symfony/form": "^5.4 || ^6.0 || ^7.0", - "symfony/phpunit-bridge": "^7.0.1", + "symfony/form": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/phpunit-bridge": "^7.3", "twig/twig": "^2.4.4 || ^3.0" }, "autoload": {