diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 01ad0225..1becfa1c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -50,14 +50,14 @@ jobs: - name: Install Node.js uses: actions/setup-node@v3 with: - node-version: '16' + node-version: '22' - name: Install and build assets run: | cd tests/App yarn install yarn dev - name: Run test suite on PHP ${{ matrix.php }} and Symfony ${{ matrix.symfony }} - run: vendor/bin/simple-phpunit + run: vendor/bin/phpunit - name: Run ECS run: vendor/bin/ecs - name: Run PHPStan diff --git a/composer.json b/composer.json index 31f9178a..79435e19 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,10 @@ "symfony/runtime": "^6.4|^7.0", "phpstan/phpstan": "^1.5", "mhujer/breadcrumbs-bundle": "^1.5", - "whatwedo/twig-bootstrap-icons": "^1.0" + "whatwedo/twig-bootstrap-icons": "^1.0", + "slevomat/coding-standard": "8.22.1", + "phpunit/phpunit": "^10", + "symfony/test-pack": "^1.0" }, "suggest": { "mhujer/breadcrumbs-bundle": "Allows creation of breadcrumbs" diff --git a/src/DataCollector/CrudDataCollector.php b/src/DataCollector/CrudDataCollector.php index a6ff3648..af10f510 100644 --- a/src/DataCollector/CrudDataCollector.php +++ b/src/DataCollector/CrudDataCollector.php @@ -19,7 +19,7 @@ public function __construct( ) { } - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $definitionClass = null; $definition = null; diff --git a/src/Definition/AbstractDefinition.php b/src/Definition/AbstractDefinition.php index 5729f6c4..cbd3e057 100644 --- a/src/Definition/AbstractDefinition.php +++ b/src/Definition/AbstractDefinition.php @@ -297,7 +297,7 @@ public function getLongTitle(?PageInterface $route = null, mixed $entity = null, }; } - public function getMetaTitle(PageInterface $route = null, $entity = null) + public function getMetaTitle(?PageInterface $route = null, $entity = null) { $add = $this->translator->trans('araise_crud.add'); $delete = $this->translator->trans('araise_crud.delete'); @@ -381,7 +381,7 @@ public function getBuilder(): DefinitionBuilder return $this->builder ?? throw new \RuntimeException('Please call DefinitionInterface::createView before accessing the builder'); } - public function createView(PageInterface $route, object $data = null): DefinitionView + public function createView(PageInterface $route, ?object $data = null): DefinitionView { $this->builder = $this->getDefinitionBuilder($data); diff --git a/src/Definition/DefinitionInterface.php b/src/Definition/DefinitionInterface.php index f1f513fc..558b6689 100644 --- a/src/Definition/DefinitionInterface.php +++ b/src/Definition/DefinitionInterface.php @@ -43,7 +43,7 @@ public function getTitle(mixed $entity = null): string; public function getLongTitle(?PageInterface $route = null, mixed $entity = null): string; - public function getMetaTitle(PageInterface $route = null, $entity = null); + public function getMetaTitle(?PageInterface $route = null, $entity = null); public function getFormAccessorPrefix(): string; diff --git a/src/Form/ChoiceLoader/AjaxDoctrineChoiceLoader.php b/src/Form/ChoiceLoader/AjaxDoctrineChoiceLoader.php index 1ad12cb9..51b9a12b 100644 --- a/src/Form/ChoiceLoader/AjaxDoctrineChoiceLoader.php +++ b/src/Form/ChoiceLoader/AjaxDoctrineChoiceLoader.php @@ -32,17 +32,17 @@ public function onFormPostSetData(FormEvent $event): void } } - public function loadChoiceList(callable $value = null): ChoiceListInterface + public function loadChoiceList(?callable $value = null): ChoiceListInterface { return new ArrayChoiceList($this->selected, $value); } - public function loadChoicesForValues(array $values, callable $value = null): array + public function loadChoicesForValues(array $values, ?callable $value = null): array { return $this->doctrineChoiceLoader->loadChoicesForValues($values, $value); } - public function loadValuesForChoices(array $choices, callable $value = null): array + public function loadValuesForChoices(array $choices, ?callable $value = null): array { return $this->doctrineChoiceLoader->loadValuesForChoices($choices, $value); } diff --git a/src/Formatter/CrudDefaultFormatter.php b/src/Formatter/CrudDefaultFormatter.php index 644df498..ca388186 100644 --- a/src/Formatter/CrudDefaultFormatter.php +++ b/src/Formatter/CrudDefaultFormatter.php @@ -9,6 +9,7 @@ use araise\CrudBundle\Enums\Page; use araise\CrudBundle\Manager\DefinitionManager; use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Routing\RouterInterface; class CrudDefaultFormatter extends DefaultFormatter @@ -31,7 +32,7 @@ public function getHtml(mixed $value): string $this->router->generate($definition::getRoute(Page::SHOW), [ 'id' => $value->getId(), ]), - StringConverter::toString($value) + $this->escapeHTML(StringConverter::toString($value)), ); } } catch (\InvalidArgumentException $e) { @@ -41,4 +42,10 @@ public function getHtml(mixed $value): string return parent::getHtml($value); } + + protected function configureOptions(OptionsResolver $resolver): void + { + parent::configureOptions($resolver); + $resolver->setDefault(self::OPT_HTML_SAFE, true); + } } diff --git a/src/Normalizer/BaseObjectNormalizer.php b/src/Normalizer/BaseObjectNormalizer.php index 54570455..10a7982d 100644 --- a/src/Normalizer/BaseObjectNormalizer.php +++ b/src/Normalizer/BaseObjectNormalizer.php @@ -40,7 +40,7 @@ abstract class BaseObjectNormalizer extends AbstractObjectNormalizer private readonly \Closure $objectClassResolver; - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = []) + public function __construct(?ClassMetadataFactoryInterface $classMetadataFactory = null, ?NameConverterInterface $nameConverter = null, ?PropertyAccessorInterface $propertyAccessor = null, ?PropertyTypeExtractorInterface $propertyTypeExtractor = null, ?ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, ?callable $objectClassResolver = null, array $defaultContext = []) { if (!class_exists(PropertyAccess::class)) { throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.'); @@ -70,7 +70,7 @@ public function hasCacheableSupportsMethod(): bool return __CLASS__ === static::class; } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { if (\stdClass::class === $object::class) { return array_keys((array) $object); @@ -133,7 +133,7 @@ protected function extractAttributes(object $object, string $format = null, arra return array_keys($attributes); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { $cacheKey = $object::class; if (!\array_key_exists($cacheKey, $this->discriminatorCache)) { @@ -147,7 +147,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute); } - protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void { try { $this->propertyAccessor->setValue($object, $attribute, $value); diff --git a/src/Resources/views/includes/layout/_content.html.twig b/src/Resources/views/includes/layout/_content.html.twig index 0ab1b9c8..71a22e06 100644 --- a/src/Resources/views/includes/layout/_content.html.twig +++ b/src/Resources/views/includes/layout/_content.html.twig @@ -37,7 +37,8 @@ {% endif %} {% else %} - {{ wwd_crud_render_content_value(content) }} + {% set contentValue = wwd_crud_render_content_value(content) %} + {{ wwd_is_html_safe(content) ? contentValue|raw : contentValue }} {% endif %} diff --git a/src/Test/AbstractCrudTest.php b/src/Test/AbstractCrudTestBase.php similarity index 99% rename from src/Test/AbstractCrudTest.php rename to src/Test/AbstractCrudTestBase.php index dd45f912..cf03dc24 100644 --- a/src/Test/AbstractCrudTest.php +++ b/src/Test/AbstractCrudTestBase.php @@ -49,7 +49,7 @@ use Symfony\Component\DomCrawler\Form; use Symfony\Component\Routing\RouterInterface; -abstract class AbstractCrudTest extends WebTestCase +abstract class AbstractCrudTestBase extends WebTestCase { protected ?KernelBrowser $client = null; diff --git a/src/Twig/CrudRenderExtension.php b/src/Twig/CrudRenderExtension.php index a4a16b66..48f6917c 100644 --- a/src/Twig/CrudRenderExtension.php +++ b/src/Twig/CrudRenderExtension.php @@ -31,19 +31,20 @@ public function __construct( public function getFunctions(): array { - $options = [ + $options = $noSafeOptions = [ 'needs_context' => true, 'is_safe' => ['html'], 'is_safe_callback' => true, 'blockName' => 'blockName', ]; + $noSafeOptions['is_safe'] = []; return [ new TwigFunction('wwd_crud_render_block', fn ($context, Block $block, DefinitionView $view, PageInterface $page, ?FormView $form = null) => $this->renderBlock($context, $block, $view, $page, $form), $options), new TwigFunction('wwd_definition_block_render', fn ($context, DefinitionBlock $definitionBlock) => $this->renderDefinitionBlock($context, $definitionBlock), $options), new TwigFunction('wwd_crud_render_content', fn ($context, $content, Block $block, DefinitionView $view, ?FormView $form = null) => $this->renderContent($context, $content, $block, $view, $form), $options), new TwigFunction('wwd_crud_render_action', fn ($context, Action $action, DefinitionView $view, ?FormView $form = null) => $this->renderAction($context, $action, $view), $options), - new TwigFunction('wwd_crud_render_content_value', fn ($context, AbstractContent $content) => $this->renderContentValue($context, $content), $options), + new TwigFunction('wwd_crud_render_content_value', fn ($context, AbstractContent $content) => $this->renderContentValue($context, $content), $noSafeOptions), ]; } diff --git a/tests/App/Entity/Category.php b/tests/App/Entity/Category.php index 7d2c0e5c..7f0c7339 100644 --- a/tests/App/Entity/Category.php +++ b/tests/App/Entity/Category.php @@ -78,7 +78,7 @@ public function getLevel(): int return $this->lvl; } - public function setParent(self $parent = null): void + public function setParent(?self $parent = null): void { $this->parent = $parent; } diff --git a/tests/Crud/AbstractCrudTest.php b/tests/Crud/AbstractCrudTestBase.php similarity index 93% rename from tests/Crud/AbstractCrudTest.php rename to tests/Crud/AbstractCrudTestBase.php index e4288a6e..0ee5adc1 100644 --- a/tests/Crud/AbstractCrudTest.php +++ b/tests/Crud/AbstractCrudTestBase.php @@ -29,11 +29,11 @@ namespace araise\CrudBundle\Tests\Crud; -use araise\CrudBundle\Test\AbstractCrudTest as BaseAbstractCrudTest; +use araise\CrudBundle\Test\AbstractCrudTestBase as BaseAbstractCrudTestBase; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Component\Security\Core\User\UserProviderInterface; -abstract class AbstractCrudTest extends BaseAbstractCrudTest +abstract class AbstractCrudTestBase extends BaseAbstractCrudTestBase { protected ?KernelBrowser $client = null; diff --git a/tests/Crud/CRUDTest.php b/tests/Crud/CRUDTest.php index 64a57548..192044ea 100644 --- a/tests/Crud/CRUDTest.php +++ b/tests/Crud/CRUDTest.php @@ -36,7 +36,7 @@ use araise\CrudBundle\Tests\App\Entity\Company; use Doctrine\ORM\EntityManagerInterface; -class CRUDTest extends AbstractCrudTest +class CRUDTest extends AbstractCrudTestBase { public function getTestData(): array { diff --git a/tests/Crud/CrudDefaultFormatterTest.php b/tests/Crud/CrudDefaultFormatterTest.php index a3f06638..e3b81b36 100644 --- a/tests/Crud/CrudDefaultFormatterTest.php +++ b/tests/Crud/CrudDefaultFormatterTest.php @@ -13,7 +13,7 @@ use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\Security\Core\User\UserProviderInterface; -class CrudDefaultFormatterTest extends AbstractCrudTest +class CrudDefaultFormatterTest extends AbstractCrudTestBase { public function getTestData(): array { diff --git a/tests/Crud/ExportTest.php b/tests/Crud/ExportTest.php index a02f235c..78464a49 100644 --- a/tests/Crud/ExportTest.php +++ b/tests/Crud/ExportTest.php @@ -40,7 +40,7 @@ use Symfony\Component\DomCrawler\Crawler; use Zenstruck\Foundry\Test\Factories; -class ExportTest extends AbstractCrudTest +class ExportTest extends AbstractCrudTestBase { use Factories; diff --git a/tests/Crud/FormOptionsTest.php b/tests/Crud/FormOptionsTest.php index 0c5b7d5f..ceefc566 100644 --- a/tests/Crud/FormOptionsTest.php +++ b/tests/Crud/FormOptionsTest.php @@ -35,7 +35,7 @@ use araise\CrudBundle\Tests\App\Definition\PersonDefinition; use araise\CrudBundle\Tests\App\Factory\PersonFactory; -class FormOptionsTest extends AbstractCrudTest +class FormOptionsTest extends AbstractCrudTestBase { public function getTestData(): array {