diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 189e3a55..3b17d56e 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-version: ['8.1', '8.2', '8.3'] + php-version: ['8.0', '8.1'] steps: - uses: shivammathur/setup-php@v2 with: diff --git a/.gitignore b/.gitignore index 43469c37..ea8d6aa9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /dev.php /install.php /clear_cache.php +/pimple.json /assets project_env.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..5346b623 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,75 @@ +node_js: + - "14" +php: + - 8.0 + - 8.1 + - nightly +dist: bionic +stages: + - "PHP lint tests" + - "Backoffice assets tests" +branches: + except: + - l10n_develop + +jobs: + allow_failures: + - php: nightly + + include: + - stage: "Backoffice assets tests" + language: node_js + node_js: "14" + script: sh .travis/backoffice_assets.sh + + - stage: "PHP lint tests" + language: php + sudo: required + services: + - mysql + env: + - DB=mysql + - MYSQL_VERSION=5.7 + - MYSQL_PASSWORD= + php: 7.4 + install: sh .travis/composer_install.sh + script: sh .travis/php_lint.sh + - stage: "PHP lint tests" + language: php + sudo: required + services: + - mysql + env: + - DB=mysql + - MYSQL_VERSION=5.7 + - MYSQL_PASSWORD= + php: 8.0 + install: sh .travis/composer_install.sh + script: sh .travis/php_lint.sh + - stage: "PHP lint tests" + language: php + sudo: required + services: + - mysql + env: + - DB=mysql + - MYSQL_VERSION=5.7 + - MYSQL_PASSWORD= + php: 8.1 + install: sh .travis/composer_install.sh + script: sh .travis/php_lint.sh + - stage: "PHP lint tests" + language: php + sudo: required + services: + - mysql + env: + - DB=mysql + - MYSQL_VERSION=5.7 + - MYSQL_PASSWORD= + php: nightly + install: sh .travis/composer_install.sh + script: sh .travis/php_lint.sh + + + diff --git a/.travis/backoffice_assets.sh b/.travis/backoffice_assets.sh new file mode 100644 index 00000000..92ba88c8 --- /dev/null +++ b/.travis/backoffice_assets.sh @@ -0,0 +1,5 @@ +#!/bin/sh -x +cd src || exit 1; +yarn install --pure-lockfile +yarn run install +yarn run build diff --git a/.travis/composer_install.sh b/.travis/composer_install.sh new file mode 100644 index 00000000..628aaebb --- /dev/null +++ b/.travis/composer_install.sh @@ -0,0 +1,4 @@ +#!/bin/sh -x +phpenv config-rm xdebug.ini; +curl -s http://getcomposer.org/installer | php; +php composer.phar install --dev --no-interaction; diff --git a/.travis/php_lint.sh b/.travis/php_lint.sh new file mode 100644 index 00000000..624af292 --- /dev/null +++ b/.travis/php_lint.sh @@ -0,0 +1,5 @@ +#!/bin/sh -x +vendor/bin/phpcs --report=full --report-file=./report.txt -p ./ || exit 1; +vendor/bin/phpstan analyse -c phpstan.neon || exit 1; +#vendor/bin/console lint:twig || exit 1; +#vendor/bin/console lint:twig src/Resources/views || exit 1; diff --git a/LICENSE.md b/LICENSE.md index 01cd7778..747e48b2 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright © 2024 Ambroise Maupate, Julien Blanchet +Copyright © 2023 Ambroise Maupate, Julien Blanchet Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/composer.json b/composer.json index e5bbf990..917762b9 100644 --- a/composer.json +++ b/composer.json @@ -28,48 +28,47 @@ "role": "Frontend developer" } ], - "minimum-stability": "dev", - "prefer-stable": true, "require": { - "php": ">=8.1", + "php": ">=8.0", "ext-zip": "*", - "doctrine/orm": "~2.19.0", + "doctrine/orm": "<2.17", "guzzlehttp/guzzle": "^7.2.0", "jms/serializer": "^3.9.0", "league/flysystem": "^3.0", + "pimple/pimple": "^3.3.1", "ramsey/uuid": "^4.7", - "roadiz/compat-bundle": "2.3.*", - "roadiz/core-bundle": "2.3.*", - "roadiz/doc-generator": "2.3.*", - "roadiz/documents": "2.3.*", - "roadiz/dts-generator": "2.3.*", - "roadiz/markdown": "2.3.*", - "roadiz/models": "2.3.*", + "roadiz/compat-bundle": "2.1.*", + "roadiz/core-bundle": "2.1.*", + "roadiz/doc-generator": "2.1.*", + "roadiz/documents": "2.1.*", + "roadiz/dts-generator": "2.1.*", + "roadiz/markdown": "2.1.*", + "roadiz/models": "2.1.*", "roadiz/nodetype-contracts": "~1.1.2", - "roadiz/openid": "2.3.*", - "roadiz/rozier-bundle": "2.3.*", - "symfony/asset": "6.4.*", - "symfony/filesystem": "6.4.*", - "symfony/form": "6.4.*", - "symfony/http-foundation": "6.4.*", - "symfony/http-kernel": "6.4.*", - "symfony/routing": "6.4.*", - "symfony/security-core": "6.4.*", - "symfony/security-csrf": "6.4.*", - "symfony/security-http": "6.4.*", - "symfony/translation": "6.4.*", - "symfony/validator": "6.4.*", - "symfony/workflow": "6.4.*", - "symfony/yaml": "6.4.*", + "roadiz/openid": "2.1.*", + "roadiz/rozier-bundle": "2.1.*", + "symfony/asset": "5.4.*", + "symfony/filesystem": "5.4.*", + "symfony/form": "5.4.*", + "symfony/http-foundation": "5.4.*", + "symfony/http-kernel": "5.4.*", + "symfony/routing": "5.4.*", + "symfony/security-core": "5.4.*", + "symfony/security-csrf": "5.4.*", + "symfony/security-http": "5.4.*", + "symfony/translation": "5.4.*", + "symfony/validator": "5.4.*", + "symfony/workflow": "5.4.*", + "symfony/yaml": "5.4.*", "twig/twig": "^3.1" }, "require-dev": { "php-coveralls/php-coveralls": "^2.4", "phpstan/phpstan": "^1.5.3", "phpstan/phpstan-doctrine": "^1.3", - "roadiz/entity-generator": "2.3.*", - "roadiz/random": "2.3.*", - "roadiz/jwt": "2.3.*", + "roadiz/entity-generator": "2.1.*", + "roadiz/random": "2.1.*", + "roadiz/jwt": "2.1.*", "squizlabs/php_codesniffer": "^3.5" }, "autoload": { @@ -95,8 +94,8 @@ }, "extra": { "branch-alias": { - "dev-main": "2.3.x-dev", - "dev-develop": "2.4.x-dev" + "dev-main": "2.1.x-dev", + "dev-develop": "2.2.x-dev" } } } diff --git a/phpstan.neon b/phpstan.neon index 6693cc61..353be6b9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 7 + level: 6 paths: - src excludePaths: @@ -9,8 +9,6 @@ parameters: doctrine: repositoryClass: RZ\Roadiz\Core\Repositories\EntityRepository ignoreErrors: - - identifier: missingType.iterableValue - - identifier: missingType.generics - '#Call to an undefined method RZ\\Roadiz\\CoreBundle\\Repository#' - '#Call to an undefined method RZ\\Roadiz\\UserBundle\\Repository#' - '#Call to an undefined method Doctrine\\Persistence\\ObjectRepository#' @@ -30,11 +28,10 @@ parameters: - '#Doctrine\\ORM\\Mapping\\GeneratedValue constructor expects#' - '#type mapping mismatch: property can contain Doctrine\\Common\\Collections\\Collection]+> but database expects Doctrine\\Common\\Collections\\Collection&iterable<[^\>]+>#' - '#should return Doctrine\\Common\\Collections\\Collection]+Interface> but returns Doctrine\\Common\\Collections\\Collection]+>#' - - '#but returns Doctrine\\Common\\Collections\\ReadableCollection]+>#' - - '#does not accept Doctrine\\Common\\Collections\\ReadableCollection]+>#' reportUnmatchedIgnoredErrors: false - treatPhpDocTypesAsCertain: false + checkGenericClassInNonGenericObjectType: false + checkMissingIterableValueType: false includes: - vendor/phpstan/phpstan-doctrine/extension.neon diff --git a/src/.babelrc b/src/.babelrc index b007a11d..2595c191 100644 --- a/src/.babelrc +++ b/src/.babelrc @@ -1,4 +1,4 @@ { - "presets": ["env", "stage-0"], + "presets": ["es2015", "stage-0"], "plugins": ["transform-runtime", "lodash"] } diff --git a/src/.editorconfig b/src/.editorconfig index cd986d42..c2778daa 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -1,6 +1,7 @@ # TheatreTheme editor config for contributors # Root is false as your theme is inside Roadiz filetree # http://editorconfig.org/ +root = false [*] indent_style = space diff --git a/src/.eslintrc.js b/src/.eslintrc.js index 020a951c..536f9132 100644 --- a/src/.eslintrc.js +++ b/src/.eslintrc.js @@ -11,7 +11,7 @@ module.exports = { }, // https://github.com/standard/standard/blob/master/docs/RULES-en.md //extends: 'standard', - extends: ['prettier', 'plugin:prettier/recommended', 'plugin:vue/base'], + extends: ['prettier', 'plugin:prettier/recommended', 'plugin:vue/essential'], // required to lint *.vue files plugins: ['html', 'prettier'], // add your custom rules here diff --git a/src/AjaxControllers/AbstractAjaxController.php b/src/AjaxControllers/AbstractAjaxController.php index c4633eca..2cd1c2e0 100644 --- a/src/AjaxControllers/AbstractAjaxController.php +++ b/src/AjaxControllers/AbstractAjaxController.php @@ -42,7 +42,7 @@ protected function getTranslation(Request $request): ?TranslationInterface * @param string $method * @param bool $requestCsrfToken * - * @return bool Return true if request is valid, else throw exception + * @return boolean Return true if request is valid, else throw exception */ protected function validateRequest(Request $request, string $method = 'POST', bool $requestCsrfToken = true): bool { diff --git a/src/AjaxControllers/AjaxAbstractFieldsController.php b/src/AjaxControllers/AjaxAbstractFieldsController.php index 64f3941e..0c597186 100644 --- a/src/AjaxControllers/AjaxAbstractFieldsController.php +++ b/src/AjaxControllers/AjaxAbstractFieldsController.php @@ -9,17 +9,20 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +/** + * @package Themes\Rozier\AjaxControllers + */ abstract class AjaxAbstractFieldsController extends AbstractAjaxController { - public function __construct(protected readonly HandlerFactoryInterface $handlerFactory) - { - } + private HandlerFactoryInterface $handlerFactory; - protected function findEntity(int|string $entityId): ?AbstractField + /** + * @param HandlerFactoryInterface $handlerFactory + */ + public function __construct(HandlerFactoryInterface $handlerFactory) { - return $this->em()->find($this->getEntityClass(), (int) $entityId); + $this->handlerFactory = $handlerFactory; } /** @@ -30,7 +33,7 @@ protected function findEntity(int|string $entityId): ?AbstractField * * @return null|Response */ - protected function handleFieldActions(Request $request, AbstractField $field = null): ?Response + protected function handleFieldActions(Request $request, AbstractField $field = null) { /* * Validate @@ -76,31 +79,11 @@ protected function handleFieldActions(Request $request, AbstractField $field = n */ protected function updatePosition(array $parameters, AbstractField $field = null): array { - if (!empty($parameters['afterFieldId']) && is_numeric($parameters['afterFieldId'])) { - $afterField = $this->findEntity((int) $parameters['afterFieldId']); - if (null === $afterField) { - throw new BadRequestHttpException('afterFieldId does not exist'); - } - $field->setPosition($afterField->getPosition() + 0.5); - // Apply position update before cleaning - $this->em()->flush(); - $handler = $this->handlerFactory->getHandler($field); - $handler->cleanPositions(); - $this->em()->flush(); - return [ - 'statusCode' => '200', - 'status' => 'success', - 'responseText' => $this->getTranslator()->trans('field.%name%.updated', [ - '%name%' => $field->getName(), - ]), - ]; - } - if (!empty($parameters['beforeFieldId']) && is_numeric($parameters['beforeFieldId'])) { - $beforeField = $this->findEntity((int) $parameters['beforeFieldId']); - if (null === $beforeField) { - throw new BadRequestHttpException('beforeFieldId does not exist'); - } - $field->setPosition($beforeField->getPosition() - 0.5); + /* + * First, we set the new parent + */ + if (!empty($parameters['newPosition']) && null !== $field) { + $field->setPosition((float) $parameters['newPosition']); // Apply position update before cleaning $this->em()->flush(); $handler = $this->handlerFactory->getHandler($field); @@ -114,12 +97,12 @@ protected function updatePosition(array $parameters, AbstractField $field = null ]), ]; } - - throw new BadRequestHttpException('Cannot update position for Field. Missing parameters.'); + return [ + 'statusCode' => '400', + 'status' => 'error', + 'responseText' => $this->getTranslator()->trans('field.%name%.updated', [ + '%name%' => $field->getName(), + ]), + ]; } - - /** - * @return class-string - */ - abstract protected function getEntityClass(): string; } diff --git a/src/AjaxControllers/AjaxAttributeValuesController.php b/src/AjaxControllers/AjaxAttributeValuesController.php index a4bc2108..73f5adf1 100644 --- a/src/AjaxControllers/AjaxAttributeValuesController.php +++ b/src/AjaxControllers/AjaxAttributeValuesController.php @@ -5,11 +5,10 @@ namespace Themes\Rozier\AjaxControllers; use RZ\Roadiz\CoreBundle\Entity\AttributeValue; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; +use RZ\Roadiz\CoreBundle\Entity\Node; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; final class AjaxAttributeValuesController extends AbstractAjaxController { @@ -26,75 +25,61 @@ final class AjaxAttributeValuesController extends AbstractAjaxController * * @return Response JSON response */ - public function editAction(Request $request, int $attributeValueId): Response + public function editAction(Request $request, int $attributeValueId) { /* * Validate */ $this->validateRequest($request, 'POST', false); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODE_ATTRIBUTES'); /** @var AttributeValue|null $attributeValue */ $attributeValue = $this->em()->find(AttributeValue::class, (int) $attributeValueId); - if ($attributeValue === null) { - throw $this->createNotFoundException($this->getTranslator()->trans( - 'attribute_value.%attributeValueId%.not_exists', - [ - '%attributeValueId%' => $attributeValueId - ] - )); - } - - $this->denyAccessUnlessGranted(NodeVoter::EDIT_ATTRIBUTE, $attributeValue->getAttributable()); + if ($attributeValue !== null) { + $responseArray = []; + /* + * Get the right update method against "_action" parameter + */ + switch ($request->get('_action')) { + case 'updatePosition': + $responseArray = $this->updatePosition($request->request->all(), $attributeValue); + break; + } - $responseArray = []; - /* - * Get the right update method against "_action" parameter - */ - switch ($request->get('_action')) { - case 'updatePosition': - $responseArray = $this->updatePosition($request->request->all(), $attributeValue); - break; + return new JsonResponse( + $responseArray, + Response::HTTP_PARTIAL_CONTENT + ); } - return new JsonResponse( - $responseArray, - Response::HTTP_PARTIAL_CONTENT - ); + throw $this->createNotFoundException($this->getTranslator()->trans( + 'attribute_value.%attributeValueId%.not_exists', + [ + '%attributeValueId%' => $attributeValueId + ] + )); } - protected function updatePosition(array $parameters, AttributeValue $attributeValue): array + /** + * @param array $parameters + * @param AttributeValue $attributeValue + * + * @return array + */ + protected function updatePosition($parameters, AttributeValue $attributeValue): array { $attributable = $attributeValue->getAttributable(); $details = [ '%name%' => $attributeValue->getAttribute()->getLabelOrCode(), - '%nodeName%' => $attributable->getNodeName(), + '%nodeName%' => $attributable instanceof Node ? $attributable->getNodeName() : '', ]; - - if (!empty($parameters['afterAttributeValueId']) && is_numeric($parameters['afterAttributeValueId'])) { - /** @var AttributeValue|null $afterAttributeValue */ - $afterAttributeValue = $this->em()->find(AttributeValue::class, (int) $parameters['afterAttributeValueId']); - if (null === $afterAttributeValue) { - throw new BadRequestHttpException('afterAttributeValueId does not exist'); - } - $attributeValue->setPosition($afterAttributeValue->getPosition() + 0.5); - $this->em()->flush(); - return [ - 'statusCode' => '200', - 'status' => 'success', - 'responseText' => $this->getTranslator()->trans( - 'attribute_value_translation.%name%.updated_from_node.%nodeName%', - $details - ), - ]; - } - if (!empty($parameters['beforeAttributeValueId']) && is_numeric($parameters['beforeAttributeValueId'])) { - /** @var AttributeValue|null $beforeAttributeValue */ - $beforeAttributeValue = $this->em()->find(AttributeValue::class, (int) $parameters['beforeAttributeValueId']); - if (null === $beforeAttributeValue) { - throw new BadRequestHttpException('beforeAttributeValueId does not exist'); - } - $attributeValue->setPosition($beforeAttributeValue->getPosition() - 0.5); + /* + * First, we set the new parent + */ + if (!empty($parameters['newPosition'])) { + $attributeValue->setPosition((float) $parameters['newPosition']); + // Apply position update before cleaning $this->em()->flush(); return [ 'statusCode' => '200', @@ -106,6 +91,13 @@ protected function updatePosition(array $parameters, AttributeValue $attributeVa ]; } - throw new BadRequestHttpException('Cannot update position for AttributeValue. Missing parameters.'); + return [ + 'statusCode' => '400', + 'status' => 'error', + 'responseText' => $this->getTranslator()->trans( + 'attribute_value_translation.%name%.updated_from_node.%nodeName%', + $details + ), + ]; } } diff --git a/src/AjaxControllers/AjaxCustomFormFieldsController.php b/src/AjaxControllers/AjaxCustomFormFieldsController.php index 86dc7ebc..f6075f8d 100644 --- a/src/AjaxControllers/AjaxCustomFormFieldsController.php +++ b/src/AjaxControllers/AjaxCustomFormFieldsController.php @@ -8,6 +8,9 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxCustomFormFieldsController extends AjaxAbstractFieldsController { /** @@ -15,16 +18,19 @@ class AjaxCustomFormFieldsController extends AjaxAbstractFieldsController * such as coming from widgets. * * @param Request $request - * @param int $customFormFieldId + * @param int $customFormFieldId * * @return Response JSON response */ - public function editAction(Request $request, int $customFormFieldId): Response + public function editAction(Request $request, int $customFormFieldId) { + /* + * Validate + */ $this->validateRequest($request); $this->denyAccessUnlessGranted('ROLE_ACCESS_CUSTOMFORMS_DELETE'); - $field = $this->findEntity((int) $customFormFieldId); + $field = $this->em()->find(CustomFormField::class, (int) $customFormFieldId); if (null !== $field && null !== $response = $this->handleFieldActions($request, $field)) { return $response; @@ -37,9 +43,4 @@ public function editAction(Request $request, int $customFormFieldId): Response ] )); } - - protected function getEntityClass(): string - { - return CustomFormField::class; - } } diff --git a/src/AjaxControllers/AjaxCustomFormsExplorerController.php b/src/AjaxControllers/AjaxCustomFormsExplorerController.php index 8c8707f2..901e6004 100644 --- a/src/AjaxControllers/AjaxCustomFormsExplorerController.php +++ b/src/AjaxControllers/AjaxCustomFormsExplorerController.php @@ -13,10 +13,16 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Themes\Rozier\Models\CustomFormModel; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxCustomFormsExplorerController extends AbstractAjaxController { - public function __construct(private readonly UrlGeneratorInterface $urlGenerator) + private UrlGeneratorInterface $urlGenerator; + + public function __construct(UrlGeneratorInterface $urlGenerator) { + $this->urlGenerator = $urlGenerator; } /** @@ -24,9 +30,9 @@ public function __construct(private readonly UrlGeneratorInterface $urlGenerator * * @return Response JSON response */ - public function indexAction(Request $request): Response + public function indexAction(Request $request) { - $this->denyAccessUnlessGranted('ROLE_ACCESS_CUSTOMFORMS'); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); $arrayFilter = []; /* @@ -62,15 +68,15 @@ public function indexAction(Request $request): Response * Get a CustomForm list from an array of id. * * @param Request $request - * @return Response + * @return JsonResponse */ - public function listAction(Request $request): Response + public function listAction(Request $request) { if (!$request->query->has('ids')) { throw new InvalidParameterException('Ids should be provided within an array'); } - $this->denyAccessUnlessGranted('ROLE_ACCESS_CUSTOMFORMS'); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); $cleanCustomFormsIds = array_filter($request->query->filter('ids', [], \FILTER_DEFAULT, [ 'flags' => \FILTER_FORCE_ARRAY @@ -105,7 +111,7 @@ public function listAction(Request $request): Response * @param array|\Traversable $customForms * @return array */ - private function normalizeCustomForms(iterable $customForms): array + private function normalizeCustomForms($customForms) { $customFormsArray = []; diff --git a/src/AjaxControllers/AjaxDocumentsExplorerController.php b/src/AjaxControllers/AjaxDocumentsExplorerController.php index 5936a864..c816d16a 100644 --- a/src/AjaxControllers/AjaxDocumentsExplorerController.php +++ b/src/AjaxControllers/AjaxDocumentsExplorerController.php @@ -11,18 +11,31 @@ use RZ\Roadiz\Documents\UrlGenerators\DocumentUrlGeneratorInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\InvalidParameterException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Themes\Rozier\Models\DocumentModel; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxDocumentsExplorerController extends AbstractAjaxController { + private RendererInterface $renderer; + private DocumentUrlGeneratorInterface $documentUrlGenerator; + private UrlGeneratorInterface $urlGenerator; + private EmbedFinderFactory $embedFinderFactory; + public function __construct( - private readonly RendererInterface $renderer, - private readonly DocumentUrlGeneratorInterface $documentUrlGenerator, - private readonly UrlGeneratorInterface $urlGenerator, - private readonly EmbedFinderFactory $embedFinderFactory + RendererInterface $renderer, + DocumentUrlGeneratorInterface $documentUrlGenerator, + UrlGeneratorInterface $urlGenerator, + EmbedFinderFactory $embedFinderFactory ) { + $this->renderer = $renderer; + $this->documentUrlGenerator = $documentUrlGenerator; + $this->urlGenerator = $urlGenerator; + $this->embedFinderFactory = $embedFinderFactory; } public static array $thumbnailArray = [ @@ -30,8 +43,12 @@ public function __construct( "quality" => 50, "inline" => false, ]; - - public function indexAction(Request $request): JsonResponse + /** + * @param Request $request + * + * @return Response JSON response + */ + public function indexAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_DOCUMENTS'); @@ -94,7 +111,7 @@ public function indexAction(Request $request): JsonResponse * @param Request $request * @return JsonResponse */ - public function listAction(Request $request): JsonResponse + public function listAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_DOCUMENTS'); diff --git a/src/AjaxControllers/AjaxEntitiesExplorerController.php b/src/AjaxControllers/AjaxEntitiesExplorerController.php index 9ce0e969..4557121d 100644 --- a/src/AjaxControllers/AjaxEntitiesExplorerController.php +++ b/src/AjaxControllers/AjaxEntitiesExplorerController.php @@ -18,7 +18,7 @@ use Symfony\Component\Config\Definition\Processor; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\Routing\Exception\InvalidParameterException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Yaml\Yaml; use Themes\Rozier\Explorer\ConfigurableExplorerItem; @@ -26,14 +26,26 @@ use Themes\Rozier\Explorer\SettingExplorerItem; use Themes\Rozier\Explorer\UserExplorerItem; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxEntitiesExplorerController extends AbstractAjaxController { + private RendererInterface $renderer; + private DocumentUrlGeneratorInterface $documentUrlGenerator; + private UrlGeneratorInterface $urlGenerator; + private EmbedFinderFactory $embedFinderFactory; + public function __construct( - private readonly RendererInterface $renderer, - private readonly DocumentUrlGeneratorInterface $documentUrlGenerator, - private readonly UrlGeneratorInterface $urlGenerator, - private readonly EmbedFinderFactory $embedFinderFactory + RendererInterface $renderer, + DocumentUrlGeneratorInterface $documentUrlGenerator, + UrlGeneratorInterface $urlGenerator, + EmbedFinderFactory $embedFinderFactory ) { + $this->renderer = $renderer; + $this->documentUrlGenerator = $documentUrlGenerator; + $this->urlGenerator = $urlGenerator; + $this->embedFinderFactory = $embedFinderFactory; } /** @@ -46,7 +58,7 @@ protected function getFieldConfiguration(NodeTypeField $nodeTypeField): array $nodeTypeField->getType() !== AbstractField::MANY_TO_MANY_T && $nodeTypeField->getType() !== AbstractField::MANY_TO_ONE_T ) { - throw new BadRequestHttpException('nodeTypeField is not a valid entity join.'); + throw new InvalidParameterException('nodeTypeField is not a valid entity join.'); } $configs = [ @@ -63,16 +75,11 @@ public function indexAction(Request $request): JsonResponse $this->denyAccessUnlessGranted('ROLE_BACKEND_USER'); if (!$request->query->has('nodeTypeFieldId')) { - throw new BadRequestHttpException('nodeTypeFieldId parameter is missing.'); + throw new InvalidParameterException('nodeTypeFieldId parameter is missing.'); } - /** @var NodeTypeField|null $nodeTypeField */ + /** @var NodeTypeField $nodeTypeField */ $nodeTypeField = $this->em()->find(NodeTypeField::class, $request->query->get('nodeTypeFieldId')); - - if (null === $nodeTypeField) { - throw new BadRequestHttpException('nodeTypeField does not exist.'); - } - $configuration = $this->getFieldConfiguration($nodeTypeField); /** @var class-string $className */ $className = $configuration['classname']; @@ -114,28 +121,29 @@ public function indexAction(Request $request): JsonResponse ); } + /** + * Get a Node list from an array of id. + * + * @param Request $request + * @return JsonResponse + */ public function listAction(Request $request): JsonResponse { if (!$request->query->has('nodeTypeFieldId')) { - throw new BadRequestHttpException('nodeTypeFieldId parameter is missing.'); + throw new InvalidParameterException('nodeTypeFieldId parameter is missing.'); } if (!$request->query->has('ids')) { - throw new BadRequestHttpException('Ids should be provided within an array'); + throw new InvalidParameterException('Ids should be provided within an array'); } - $this->denyAccessUnlessGranted('ROLE_BACKEND_USER'); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); /** @var EntityManager $em */ $em = $this->em(); - /** @var NodeTypeField|null $nodeTypeField */ + /** @var NodeTypeField $nodeTypeField */ $nodeTypeField = $this->em()->find(NodeTypeField::class, $request->query->get('nodeTypeFieldId')); - - if (null === $nodeTypeField) { - throw new BadRequestHttpException('nodeTypeField does not exist.'); - } - $configuration = $this->getFieldConfiguration($nodeTypeField); /** @var class-string $className */ $className = $configuration['classname']; diff --git a/src/AjaxControllers/AjaxExplorerProviderController.php b/src/AjaxControllers/AjaxExplorerProviderController.php index 3ea4fe98..b9ee2e04 100644 --- a/src/AjaxControllers/AjaxExplorerProviderController.php +++ b/src/AjaxControllers/AjaxExplorerProviderController.php @@ -4,9 +4,7 @@ namespace Themes\Rozier\AjaxControllers; -use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; -use Psr\Container\NotFoundExceptionInterface; use RZ\Roadiz\CoreBundle\Explorer\AbstractExplorerProvider; use RZ\Roadiz\CoreBundle\Explorer\ExplorerItemInterface; use RZ\Roadiz\CoreBundle\Explorer\ExplorerProviderInterface; @@ -15,17 +13,23 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\InvalidParameterException; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxExplorerProviderController extends AbstractAjaxController { - public function __construct(private readonly ContainerInterface $psrContainer) + private ContainerInterface $psrContainer; + + public function __construct(ContainerInterface $psrContainer) { + $this->psrContainer = $psrContainer; } /** - * @param class-string $providerClass + * @param class-string $providerClass * @return ExplorerProviderInterface - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface */ protected function getProvider(string $providerClass): ExplorerProviderInterface { @@ -34,47 +38,27 @@ protected function getProvider(string $providerClass): ExplorerProviderInterface } return new $providerClass(); } - /** - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface + * @param Request $request + * @return Response JSON response */ - protected function getProviderFromRequest(Request $request): ExplorerProviderInterface + public function indexAction(Request $request) { - /** @var class-string|null $providerClass */ - $providerClass = $request->query->get('providerClass'); + $this->denyAccessUnlessGranted('ROLE_BACKEND_USER'); - if (!\is_string($providerClass)) { + if (!$request->query->has('providerClass')) { throw new InvalidParameterException('providerClass parameter is missing.'); } - if (!\class_exists($providerClass)) { - throw new InvalidParameterException('providerClass is not a valid class.'); - } - $reflection = new \ReflectionClass($providerClass); - if (!$reflection->implementsInterface(ExplorerProviderInterface::class)) { - throw new InvalidParameterException('providerClass is not a valid ExplorerProviderInterface class.'); + $providerClass = $request->query->get('providerClass'); + if (!class_exists($providerClass)) { + throw new InvalidParameterException('providerClass is not a valid class.'); } $provider = $this->getProvider($providerClass); if ($provider instanceof AbstractExplorerProvider) { $provider->setContainer($this->psrContainer); } - - return $provider; - } - - /** - * @param Request $request - * @return JsonResponse - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - public function indexAction(Request $request): JsonResponse - { - $this->denyAccessUnlessGranted('ROLE_BACKEND_USER'); - - $provider = $this->getProviderFromRequest($request); $options = [ 'page' => $request->query->get('page') ?: 1, 'itemPerPage' => $request->query->get('itemPerPage') ?: 30, @@ -115,14 +99,28 @@ public function indexAction(Request $request): JsonResponse * * @param Request $request * @return JsonResponse - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface */ - public function listAction(Request $request): JsonResponse + public function listAction(Request $request) { + if (!$request->query->has('providerClass')) { + throw new InvalidParameterException('providerClass parameter is missing.'); + } + + $providerClass = $request->query->get('providerClass'); + if (!class_exists($providerClass)) { + throw new InvalidParameterException('providerClass is not a valid class.'); + } + + if (!$request->query->has('ids')) { + throw new InvalidParameterException('Ids should be provided within an array'); + } + $this->denyAccessUnlessGranted('ROLE_BACKEND_USER'); - $provider = $this->getProviderFromRequest($request); + $provider = $this->getProvider($providerClass); + if ($provider instanceof AbstractExplorerProvider) { + $provider->setContainer($this->psrContainer); + } $entitiesArray = []; $cleanNodeIds = array_filter($request->query->filter('ids', [], \FILTER_DEFAULT, [ 'flags' => \FILTER_FORCE_ARRAY diff --git a/src/AjaxControllers/AjaxFolderTreeController.php b/src/AjaxControllers/AjaxFolderTreeController.php index 1d934a54..c0c2dbe0 100644 --- a/src/AjaxControllers/AjaxFolderTreeController.php +++ b/src/AjaxControllers/AjaxFolderTreeController.php @@ -10,10 +10,16 @@ use Themes\Rozier\Widgets\FolderTreeWidget; use Themes\Rozier\Widgets\TreeWidgetFactory; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxFolderTreeController extends AbstractAjaxController { - public function __construct(private readonly TreeWidgetFactory $treeWidgetFactory) + private TreeWidgetFactory $treeWidgetFactory; + + public function __construct(TreeWidgetFactory $treeWidgetFactory) { + $this->treeWidgetFactory = $treeWidgetFactory; } public function getTreeAction(Request $request): JsonResponse diff --git a/src/AjaxControllers/AjaxFoldersController.php b/src/AjaxControllers/AjaxFoldersController.php index b0867002..3162ecc7 100644 --- a/src/AjaxControllers/AjaxFoldersController.php +++ b/src/AjaxControllers/AjaxFoldersController.php @@ -11,17 +11,28 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxFoldersController extends AbstractAjaxController { - public function __construct(private readonly HandlerFactoryInterface $handlerFactory) + private HandlerFactoryInterface $handlerFactory; + + public function __construct(HandlerFactoryInterface $handlerFactory) { + $this->handlerFactory = $handlerFactory; } - /* + /** * Handle AJAX edition requests for Folder * such as coming from tag-tree widgets. + * + * @param Request $request + * @param int $folderId + * + * @return Response JSON response */ - public function editAction(Request $request, int $folderId): JsonResponse + public function editAction(Request $request, int $folderId) { $this->validateRequest($request); $this->denyAccessUnlessGranted('ROLE_ACCESS_DOCUMENTS'); @@ -73,7 +84,7 @@ public function editAction(Request $request, int $folderId): JsonResponse * @param Request $request * @return JsonResponse */ - public function searchAction(Request $request): JsonResponse + public function searchAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_DOCUMENTS'); @@ -103,7 +114,11 @@ public function searchAction(Request $request): JsonResponse throw $this->createNotFoundException($this->getTranslator()->trans('no.folder.found')); } - protected function updatePosition(array $parameters, Folder $folder): void + /** + * @param array $parameters + * @param Folder $folder + */ + protected function updatePosition($parameters, Folder $folder): void { /* * First, we set the new parent diff --git a/src/AjaxControllers/AjaxFoldersExplorerController.php b/src/AjaxControllers/AjaxFoldersExplorerController.php index 8133a0a0..3617a29a 100644 --- a/src/AjaxControllers/AjaxFoldersExplorerController.php +++ b/src/AjaxControllers/AjaxFoldersExplorerController.php @@ -9,9 +9,17 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxFoldersExplorerController extends AbstractAjaxController { - public function indexAction(Request $request): JsonResponse + /** + * @param Request $request + * + * @return Response JSON response + */ + public function indexAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_DOCUMENTS'); diff --git a/src/AjaxControllers/AjaxNodeTreeController.php b/src/AjaxControllers/AjaxNodeTreeController.php index 313eff55..88e75a43 100644 --- a/src/AjaxControllers/AjaxNodeTreeController.php +++ b/src/AjaxControllers/AjaxNodeTreeController.php @@ -14,12 +14,15 @@ use Themes\Rozier\Widgets\NodeTreeWidget; use Themes\Rozier\Widgets\TreeWidgetFactory; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxNodeTreeController extends AbstractAjaxController { public function __construct( - private readonly NodeChrootResolver $nodeChrootResolver, - private readonly TreeWidgetFactory $treeWidgetFactory, - private readonly NodeTypes $nodeTypesBag + private NodeChrootResolver $nodeChrootResolver, + private TreeWidgetFactory $treeWidgetFactory, + private NodeTypes $nodeTypesBag ) { } @@ -93,7 +96,7 @@ public function getTreeAction(Request $request): JsonResponse $parent = $this->nodeChrootResolver->getChroot($this->getUser()); } - $nodeTree = $this->treeWidgetFactory->createRootNodeTree($parent, $translation); + $nodeTree = $this->treeWidgetFactory->createNodeTree($parent, $translation); $this->assignation['mainNodeTree'] = true; break; } diff --git a/src/AjaxControllers/AjaxNodeTypeFieldsController.php b/src/AjaxControllers/AjaxNodeTypeFieldsController.php index d825f480..e795fa5c 100644 --- a/src/AjaxControllers/AjaxNodeTypeFieldsController.php +++ b/src/AjaxControllers/AjaxNodeTypeFieldsController.php @@ -8,6 +8,9 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxNodeTypeFieldsController extends AjaxAbstractFieldsController { /** @@ -21,10 +24,13 @@ class AjaxNodeTypeFieldsController extends AjaxAbstractFieldsController */ public function editAction(Request $request, int $nodeTypeFieldId): Response { + /* + * Validate + */ $this->validateRequest($request); $this->denyAccessUnlessGranted('ROLE_ACCESS_NODEFIELDS_DELETE'); - $field = $this->findEntity($nodeTypeFieldId); + $field = $this->em()->find(NodeTypeField::class, (int) $nodeTypeFieldId); if (null !== $response = $this->handleFieldActions($request, $field)) { return $response; @@ -37,9 +43,4 @@ public function editAction(Request $request, int $nodeTypeFieldId): Response ] )); } - - protected function getEntityClass(): string - { - return NodeTypeField::class; - } } diff --git a/src/AjaxControllers/AjaxNodeTypesController.php b/src/AjaxControllers/AjaxNodeTypesController.php index e2f2a736..3f45891e 100644 --- a/src/AjaxControllers/AjaxNodeTypesController.php +++ b/src/AjaxControllers/AjaxNodeTypesController.php @@ -5,7 +5,6 @@ namespace Themes\Rozier\AjaxControllers; use Doctrine\ORM\EntityManager; -use Doctrine\ORM\Exception\NotSupported; use RZ\Roadiz\CoreBundle\Entity\NodeType; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -13,14 +12,17 @@ use Symfony\Component\Routing\Exception\InvalidParameterException; use Themes\Rozier\Models\NodeTypeModel; -class AjaxNodeTypesController extends AbstractAjaxController +/** + * @package Themes\Rozier\AjaxControllers + */ +class AjaxNodeTypesController extends AjaxAbstractFieldsController { /** * @param Request $request * * @return Response JSON response */ - public function indexAction(Request $request): Response + public function indexAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); $arrayFilter = []; @@ -57,9 +59,8 @@ public function indexAction(Request $request): Response * * @param Request $request * @return JsonResponse - * @throws NotSupported */ - public function listAction(Request $request): Response + public function listAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); @@ -100,7 +101,7 @@ public function listAction(Request $request): Response * @param array|\Traversable $nodeTypes * @return array */ - private function normalizeNodeType(iterable $nodeTypes): array + private function normalizeNodeType($nodeTypes) { $nodeTypesArray = []; diff --git a/src/AjaxControllers/AjaxNodesController.php b/src/AjaxControllers/AjaxNodesController.php index e459d81c..802fa451 100644 --- a/src/AjaxControllers/AjaxNodesController.php +++ b/src/AjaxControllers/AjaxNodesController.php @@ -5,6 +5,7 @@ namespace Themes\Rozier\AjaxControllers; use Psr\Log\LoggerInterface; +use RZ\Roadiz\CoreBundle\Security\Authorization\Chroot\NodeChrootResolver; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\Tag; use RZ\Roadiz\CoreBundle\Event\Node\NodeCreatedEvent; @@ -18,25 +19,38 @@ use RZ\Roadiz\CoreBundle\Node\NodeMover; use RZ\Roadiz\CoreBundle\Node\NodeNamePolicyInterface; use RZ\Roadiz\CoreBundle\Node\UniqueNodeGenerator; -use RZ\Roadiz\CoreBundle\Security\Authorization\Chroot\NodeChrootResolver; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Workflow\Registry; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxNodesController extends AbstractAjaxController { + private NodeNamePolicyInterface $nodeNamePolicy; + private LoggerInterface $logger; + private NodeMover $nodeMover; + private NodeChrootResolver $nodeChrootResolver; + private Registry $workflowRegistry; + private UniqueNodeGenerator $uniqueNodeGenerator; + public function __construct( - private readonly NodeNamePolicyInterface $nodeNamePolicy, - private readonly LoggerInterface $logger, - private readonly NodeMover $nodeMover, - private readonly NodeChrootResolver $nodeChrootResolver, - private readonly Registry $workflowRegistry, - private readonly UniqueNodeGenerator $uniqueNodeGenerator + NodeNamePolicyInterface $nodeNamePolicy, + LoggerInterface $logger, + NodeMover $nodeMover, + NodeChrootResolver $nodeChrootResolver, + Registry $workflowRegistry, + UniqueNodeGenerator $uniqueNodeGenerator ) { + $this->nodeNamePolicy = $nodeNamePolicy; + $this->logger = $logger; + $this->nodeMover = $nodeMover; + $this->nodeChrootResolver = $nodeChrootResolver; + $this->workflowRegistry = $workflowRegistry; + $this->uniqueNodeGenerator = $uniqueNodeGenerator; } /** @@ -44,16 +58,12 @@ public function __construct( * @param int $nodeId * @return JsonResponse */ - public function getTagsAction(Request $request, int $nodeId): JsonResponse + public function getTagsAction(Request $request, int $nodeId) { + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); $tags = []; - /** @var Node|null $node */ + /** @var Node $node */ $node = $this->em()->find(Node::class, (int) $nodeId); - if (null === $node) { - throw new NotFoundHttpException('Node not found'); - } - - $this->denyAccessUnlessGranted(NodeVoter::READ, $node); /** @var Tag $tag */ foreach ($node->getTags() as $tag) { @@ -70,77 +80,83 @@ public function getTagsAction(Request $request, int $nodeId): JsonResponse * such as coming from node-tree widgets. * * @param Request $request - * @param int|string $nodeId + * @param int $nodeId * * @return Response JSON response */ - public function editAction(Request $request, int|string $nodeId): Response + public function editAction(Request $request, $nodeId) { + /* + * Validate + */ $this->validateRequest($request); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); /** @var Node|null $node */ $node = $this->em()->find(Node::class, (int) $nodeId); - if (null === $node) { - throw $this->createNotFoundException($this->getTranslator()->trans('node.%nodeId%.not_exists', [ - '%nodeId%' => $nodeId, - ])); - } - /* - * Get the right update method against "_action" parameter - */ - switch ($request->get('_action')) { - case 'updatePosition': - $this->denyAccessUnlessGranted(NodeVoter::EDIT_SETTING, $node); - $this->updatePosition($request->request->all(), $node); + if ($node !== null) { + $responseArray = null; + + /* + * Get the right update method against "_action" parameter + */ + switch ($request->get('_action')) { + case 'updatePosition': + $this->updatePosition($request->request->all(), $node); + break; + case 'duplicate': + $duplicator = new NodeDuplicator( + $node, + $this->em(), + $this->nodeNamePolicy + ); + $newNode = $duplicator->duplicate(); + /* + * Dispatch event + */ + $this->dispatchEvent(new NodeCreatedEvent($newNode)); + $this->dispatchEvent(new NodeDuplicatedEvent($newNode)); + + $msg = $this->getTranslator()->trans('duplicated.node.%name%', [ + '%name%' => $node->getNodeName(), + ]); + $this->logger->info($msg, ['source' => $newNode->getNodeSources()->first()]); + + $responseArray = [ + 'statusCode' => '200', + 'status' => 'success', + 'responseText' => $msg, + ]; + break; + } + + if ($responseArray === null) { $responseArray = [ 'statusCode' => '200', 'status' => 'success', - 'responseText' => $this->getTranslator()->trans('node.%name%.was_moved', [ + 'responseText' => $this->getTranslator()->trans('node.%name%.updated', [ '%name%' => $node->getNodeName(), ]), ]; - break; - case 'duplicate': - $this->denyAccessUnlessGranted(NodeVoter::DUPLICATE, $node); - $duplicator = new NodeDuplicator( - $node, - $this->em(), - $this->nodeNamePolicy - ); - $newNode = $duplicator->duplicate(); - /* - * Dispatch event - */ - $this->dispatchEvent(new NodeCreatedEvent($newNode)); - $this->dispatchEvent(new NodeDuplicatedEvent($newNode)); - - $msg = $this->getTranslator()->trans('duplicated.node.%name%', [ - '%name%' => $node->getNodeName(), - ]); - $this->logger->info($msg, ['entity' => $newNode->getNodeSources()->first()]); + } - $responseArray = [ - 'statusCode' => '200', - 'status' => 'success', - 'responseText' => $msg, - ]; - break; - default: - throw new BadRequestHttpException('Action is not defined.'); + return new JsonResponse( + $responseArray, + Response::HTTP_PARTIAL_CONTENT + ); } - return new JsonResponse( - $responseArray, - Response::HTTP_PARTIAL_CONTENT - ); + throw $this->createNotFoundException($this->getTranslator()->trans('node.%nodeId%.not_exists', [ + '%nodeId%' => $nodeId, + ])); } /** * @param array $parameters * @param Node $node */ - protected function updatePosition(array $parameters, Node $node): void + protected function updatePosition($parameters, Node $node): void { if ($node->isLocked()) { throw new BadRequestHttpException('Locked node cannot be moved.'); @@ -179,11 +195,6 @@ protected function updatePosition(array $parameters, Node $node): void $this->dispatchEvent(new NodesSourcesUpdatedEvent($nodeSource)); } - $msg = $this->getTranslator()->trans('node.%name%.was_moved', [ - '%name%' => $node->getNodeName(), - ]); - $this->logger->info($msg, ['entity' => $node->getNodeSources()->first() ?: $node]); - $this->em()->flush(); } @@ -235,11 +246,15 @@ protected function parsePosition(array $parameters, float $default = 0.0): float * Update node's status. * * @param Request $request + * * @return JsonResponse + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function statusesAction(Request $request): JsonResponse { $this->validateRequest($request); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); if ($request->get('nodeId', 0) <= 0) { throw new BadRequestHttpException($this->getTranslator()->trans('node.id.not_specified')); @@ -253,8 +268,6 @@ public function statusesAction(Request $request): JsonResponse ])); } - $this->denyAccessUnlessGranted(NodeVoter::EDIT_STATUS, $node); - $availableStatuses = [ 'visible' => 'setVisible', 'locked' => 'setLocked', @@ -293,14 +306,14 @@ public function statusesAction(Request $request): JsonResponse '%name%' => $node->getNodeName(), '%visible%' => $node->isVisible() ? $this->getTranslator()->trans('visible') : $this->getTranslator()->trans('invisible'), ]); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); $this->dispatchEvent(new NodeVisibilityChangedEvent($node)); } else { $msg = $this->getTranslator()->trans('node.%name%.%field%.updated', [ '%name%' => $node->getNodeName(), '%field%' => $request->get('statusName'), ]); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); } $this->dispatchEvent(new NodeUpdatedEvent($node)); $this->em()->flush(); @@ -344,7 +357,7 @@ protected function changeNodeStatus(Node $node, string $transition): JsonRespons '%name%' => $node->getNodeName(), '%status%' => $this->getTranslator()->trans(Node::getStatusLabel($node->getStatus())), ]); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); return new JsonResponse( [ @@ -368,9 +381,9 @@ public function quickAddAction(Request $request): JsonResponse * Validate */ $this->validateRequest($request); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); try { - // Access security is handled by UniqueNodeGenerator $source = $this->uniqueNodeGenerator->generateFromRequest($request); /* diff --git a/src/AjaxControllers/AjaxNodesExplorerController.php b/src/AjaxControllers/AjaxNodesExplorerController.php index 7efcfcd4..834fc240 100644 --- a/src/AjaxControllers/AjaxNodesExplorerController.php +++ b/src/AjaxControllers/AjaxNodesExplorerController.php @@ -14,27 +14,34 @@ use RZ\Roadiz\CoreBundle\EntityApi\NodeTypeApi; use RZ\Roadiz\CoreBundle\SearchEngine\ClientRegistry; use RZ\Roadiz\CoreBundle\SearchEngine\NodeSourceSearchHandlerInterface; -use RZ\Roadiz\CoreBundle\SearchEngine\SolrSearchResultItem; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\InvalidParameterException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Bundle\SecurityBundle\Security; use Themes\Rozier\Models\NodeModel; use Themes\Rozier\Models\NodeSourceModel; -final class AjaxNodesExplorerController extends AbstractAjaxController +class AjaxNodesExplorerController extends AbstractAjaxController { + private SerializerInterface $serializer; + private ClientRegistry $clientRegistry; + private NodeSourceSearchHandlerInterface $nodeSourceSearchHandler; + private NodeTypeApi $nodeTypeApi; + private UrlGeneratorInterface $urlGenerator; + public function __construct( - private readonly SerializerInterface $serializer, - private readonly ClientRegistry $clientRegistry, - private readonly NodeSourceSearchHandlerInterface $nodeSourceSearchHandler, - private readonly NodeTypeApi $nodeTypeApi, - private readonly UrlGeneratorInterface $urlGenerator, - private readonly Security $security, + SerializerInterface $serializer, + ClientRegistry $clientRegistry, + NodeSourceSearchHandlerInterface $nodeSourceSearchHandler, + NodeTypeApi $nodeTypeApi, + UrlGeneratorInterface $urlGenerator ) { + $this->nodeSourceSearchHandler = $nodeSourceSearchHandler; + $this->nodeTypeApi = $nodeTypeApi; + $this->serializer = $serializer; + $this->urlGenerator = $urlGenerator; + $this->clientRegistry = $clientRegistry; } protected function getItemPerPage(): int @@ -54,8 +61,7 @@ protected function isSearchEngineAvailable(Request $request): bool */ public function indexAction(Request $request): Response { - // Only requires Search permission for nodes - $this->denyAccessUnlessGranted(NodeVoter::SEARCH); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); $criteria = $this->parseFilterFromRequest($request); $sorting = $this->parseSortingFromRequest($request); @@ -185,6 +191,7 @@ protected function getSolrSearchResults( $arrayFilter, $this->getItemPerPage(), true, + 2, (int) $currentPage ); $pageCount = ceil($results->getResultCount() / $this->getItemPerPage()); @@ -214,8 +221,7 @@ protected function getSolrSearchResults( */ public function listAction(Request $request): JsonResponse { - // Only requires Search permission for nodes - $this->denyAccessUnlessGranted(NodeVoter::SEARCH); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); if (!$request->query->has('ids')) { throw new InvalidParameterException('Ids should be provided within an array'); @@ -250,38 +256,32 @@ public function listAction(Request $request): JsonResponse /** * Normalize response Node list result. * - * @param iterable $nodes + * @param array|\Traversable $nodes * @return array */ - private function normalizeNodes(iterable $nodes): array + private function normalizeNodes($nodes) { $nodesArray = []; foreach ($nodes as $node) { - if ($node instanceof SolrSearchResultItem) { - $item = $node->getItem(); - if ($item instanceof NodesSources || $item instanceof Node) { - $this->normalizeItem($item, $nodesArray); + if (null !== $node) { + if ($node instanceof NodesSources) { + if (!key_exists($node->getNode()->getId(), $nodesArray)) { + $nodeModel = new NodeSourceModel($node, $this->urlGenerator); + $nodesArray[$node->getNode()->getId()] = $nodeModel->toArray(); + } + } else { + if (!key_exists($node->getId(), $nodesArray)) { + $nodeModel = new NodeModel($node, $this->urlGenerator); + $nodesArray[$node->getId()] = $nodeModel->toArray(); + } } - } else { - $this->normalizeItem($node, $nodesArray); } } return array_values($nodesArray); } - private function normalizeItem(NodesSources|Node $item, array &$nodesArray): void - { - if ($item instanceof NodesSources && !key_exists($item->getNode()->getId(), $nodesArray)) { - $nodeSourceModel = new NodeSourceModel($item, $this->urlGenerator, $this->security); - $nodesArray[$item->getNode()->getId()] = $nodeSourceModel->toArray(); - } elseif ($item instanceof Node && !key_exists($item->getId(), $nodesArray)) { - $nodeModel = new NodeModel($item, $this->urlGenerator, $this->security); - $nodesArray[$item->getId()] = $nodeModel->toArray(); - } - } - /** * @param array $data * @return JsonResponse diff --git a/src/AjaxControllers/AjaxSearchNodesSourcesController.php b/src/AjaxControllers/AjaxSearchNodesSourcesController.php index b8c34594..894c7145 100644 --- a/src/AjaxControllers/AjaxSearchNodesSourcesController.php +++ b/src/AjaxControllers/AjaxSearchNodesSourcesController.php @@ -8,35 +8,36 @@ use RZ\Roadiz\CoreBundle\Entity\NodesSourcesDocuments; use RZ\Roadiz\CoreBundle\Entity\Translation; use RZ\Roadiz\CoreBundle\SearchEngine\GlobalNodeSourceSearchHandler; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use RZ\Roadiz\Documents\UrlGenerators\DocumentUrlGeneratorInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Bundle\SecurityBundle\Security; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxSearchNodesSourcesController extends AbstractAjaxController { - public const RESULT_COUNT = 10; + public const RESULT_COUNT = 8; + private DocumentUrlGeneratorInterface $documentUrlGenerator; - public function __construct( - private readonly DocumentUrlGeneratorInterface $documentUrlGenerator, - private readonly Security $security - ) { + public function __construct(DocumentUrlGeneratorInterface $documentUrlGenerator) + { + $this->documentUrlGenerator = $documentUrlGenerator; } /** * Handle AJAX edition requests for Node - * such as coming from node-tree widgets. + * such as coming from nodetree widgets. * * @param Request $request * * @return Response JSON response */ - public function searchAction(Request $request): Response + public function searchAction(Request $request) { - $this->denyAccessUnlessGranted(NodeVoter::SEARCH); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); if (!$request->query->has('searchTerms') || $request->query->get('searchTerms') == '') { throw new BadRequestHttpException('searchTerms parameter is missing.'); @@ -45,13 +46,13 @@ public function searchAction(Request $request): Response $searchHandler = new GlobalNodeSourceSearchHandler($this->em()); $searchHandler->setDisplayNonPublishedNodes(true); - /** @var array $nodesSources */ + /** @var array $nodesSources */ $nodesSources = $searchHandler->getNodeSourcesBySearchTerm( $request->get('searchTerms'), static::RESULT_COUNT ); - if (count($nodesSources) > 0) { + if (null !== $nodesSources && count($nodesSources) > 0) { $responseArray = [ 'statusCode' => Response::HTTP_OK, 'status' => 'success', @@ -62,7 +63,6 @@ public function searchAction(Request $request): Response foreach ($nodesSources as $source) { if ( $source instanceof NodesSources && - $this->security->isGranted(NodeVoter::READ, $source) && !key_exists($source->getNode()->getId(), $responseArray['data']) ) { $responseArray['data'][$source->getNode()->getId()] = $this->getNodeSourceData($source); diff --git a/src/AjaxControllers/AjaxTagTreeController.php b/src/AjaxControllers/AjaxTagTreeController.php index 4712b013..f56f1f47 100644 --- a/src/AjaxControllers/AjaxTagTreeController.php +++ b/src/AjaxControllers/AjaxTagTreeController.php @@ -10,9 +10,12 @@ use Themes\Rozier\Widgets\TagTreeWidget; use Themes\Rozier\Widgets\TreeWidgetFactory; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxTagTreeController extends AbstractAjaxController { - public function __construct(private readonly TreeWidgetFactory $treeWidgetFactory) + public function __construct(private TreeWidgetFactory $treeWidgetFactory) { } diff --git a/src/AjaxControllers/AjaxTagsController.php b/src/AjaxControllers/AjaxTagsController.php index 6a3d5b24..6b4b0a91 100644 --- a/src/AjaxControllers/AjaxTagsController.php +++ b/src/AjaxControllers/AjaxTagsController.php @@ -19,12 +19,18 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Themes\Rozier\Models\TagModel; +/** + * @package Themes\Rozier\AjaxControllers + */ class AjaxTagsController extends AbstractAjaxController { - public function __construct( - private readonly HandlerFactoryInterface $handlerFactory, - private readonly UrlGeneratorInterface $urlGenerator - ) { + private HandlerFactoryInterface $handlerFactory; + private UrlGeneratorInterface $urlGenerator; + + public function __construct(HandlerFactoryInterface $handlerFactory, UrlGeneratorInterface $urlGenerator) + { + $this->handlerFactory = $handlerFactory; + $this->urlGenerator = $urlGenerator; } /** diff --git a/src/Controllers/AbstractAdminController.php b/src/Controllers/AbstractAdminController.php index 2d73a9ba..e6574ce3 100644 --- a/src/Controllers/AbstractAdminController.php +++ b/src/Controllers/AbstractAdminController.php @@ -8,25 +8,33 @@ use JMS\Serializer\SerializationContext; use JMS\Serializer\SerializerInterface; use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; -use RZ\Roadiz\CoreBundle\ListManager\SessionListFilters; use Symfony\Component\Form\Exception\InvalidConfigurationException; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\String\UnicodeString; use Symfony\Contracts\EventDispatcher\Event; use Themes\Rozier\RozierApp; +use Themes\Rozier\Utils\SessionListFilters; abstract class AbstractAdminController extends RozierApp { public const ITEM_PER_PAGE = 20; - public function __construct( - protected readonly SerializerInterface $serializer, - protected readonly UrlGeneratorInterface $urlGenerator - ) { + protected SerializerInterface $serializer; + protected UrlGeneratorInterface $urlGenerator; + + /** + * @param SerializerInterface $serializer + * @param UrlGeneratorInterface $urlGenerator + */ + public function __construct(SerializerInterface $serializer, UrlGeneratorInterface $urlGenerator) + { + $this->serializer = $serializer; + $this->urlGenerator = $urlGenerator; } /** @@ -60,42 +68,14 @@ protected function getRepository(): ObjectRepository return $this->em()->getRepository($this->getEntityClass()); } - /** - * @return string - */ - protected function getRequiredDeletionRole(): string - { - return $this->getRequiredRole(); - } - - protected function getRequiredListingRole(): string - { - return $this->getRequiredRole(); - } - - protected function getRequiredCreationRole(): string - { - return $this->getRequiredRole(); - } - - protected function getRequiredEditionRole(): string - { - return $this->getRequiredRole(); - } - - protected function getRequiredExportRole(): string - { - return $this->getRequiredRole(); - } - /** * @param Request $request * @return Response|null * @throws \Twig\Error\RuntimeError */ - public function defaultAction(Request $request): ?Response + public function defaultAction(Request $request) { - $this->denyAccessUnlessGranted($this->getRequiredListingRole()); + $this->denyAccessUnlessGranted($this->getRequiredRole()); $this->additionalAssignation($request); $elm = $this->createEntityListManager( @@ -124,12 +104,12 @@ public function defaultAction(Request $request): ?Response /** * @param Request $request - * @return Response|null + * @return RedirectResponse|Response|null * @throws \Twig\Error\RuntimeError */ - public function addAction(Request $request): ?Response + public function addAction(Request $request) { - $this->denyAccessUnlessGranted($this->getRequiredCreationRole()); + $this->denyAccessUnlessGranted($this->getRequiredRole()); $this->additionalAssignation($request); $item = $this->createEmptyItem($request); @@ -158,7 +138,7 @@ public function addAction(Request $request): ?Response '%namespace%' => $this->getTranslator()->trans($this->getNamespace()) ] ); - $this->publishConfirmMessage($request, $msg, $item); + $this->publishConfirmMessage($request, $msg); return $this->getPostSubmitResponse($item, true, $request); } @@ -180,9 +160,9 @@ public function addAction(Request $request): ?Response * @return Response|null * @throws \Twig\Error\RuntimeError */ - public function editAction(Request $request, $id): ?Response + public function editAction(Request $request, $id) { - $this->denyAccessUnlessGranted($this->getRequiredEditionRole()); + $this->denyAccessUnlessGranted($this->getRequiredRole()); $this->additionalAssignation($request); /** @var mixed|object|null $item */ @@ -219,7 +199,7 @@ public function editAction(Request $request, $id): ?Response '%namespace%' => $this->getTranslator()->trans($this->getNamespace()) ] ); - $this->publishConfirmMessage($request, $msg, $item); + $this->publishConfirmMessage($request, $msg); return $this->getPostSubmitResponse($item, false, $request); } @@ -237,7 +217,7 @@ public function editAction(Request $request, $id): ?Response public function exportAction(Request $request): JsonResponse { - $this->denyAccessUnlessGranted($this->getRequiredExportRole()); + $this->denyAccessUnlessGranted($this->getRequiredRole()); $this->additionalAssignation($request); $items = $this->getRepository()->findAll(); @@ -263,10 +243,10 @@ public function exportAction(Request $request): JsonResponse /** * @param Request $request * @param int|string $id Numeric ID or UUID - * @return Response|null + * @return RedirectResponse|Response|null * @throws \Twig\Error\RuntimeError */ - public function deleteAction(Request $request, $id): ?Response + public function deleteAction(Request $request, $id) { $this->denyAccessUnlessGranted($this->getRequiredDeletionRole()); $this->additionalAssignation($request); @@ -304,7 +284,7 @@ public function deleteAction(Request $request, $id): ?Response '%namespace%' => $this->getTranslator()->trans($this->getNamespace()) ] ); - $this->publishConfirmMessage($request, $msg, $item); + $this->publishConfirmMessage($request, $msg); return $this->getPostDeleteResponse($item); } @@ -348,7 +328,15 @@ abstract protected function getTemplateFolder(): string; abstract protected function getRequiredRole(): string; /** - * @return class-string + * @return string + */ + protected function getRequiredDeletionRole(): string + { + return $this->getRequiredRole(); + } + + /** + * @return class-string */ abstract protected function getEntityClass(): string; @@ -424,26 +412,14 @@ protected function getPostSubmitResponse( bool $forceDefaultEditRoute = false, ?Request $request = null ): Response { - if (null === $request) { - // Redirect to default route if no request provided - return $this->redirect($this->urlGenerator->generate( - $this->getEditRouteName(), - $this->getEditRouteParameters($item) - )); - } - - $route = $request->attributes->get('_route'); - $referrer = $request->query->get('referer'); - /* * Force redirect to avoid resending form when refreshing page */ if ( - \is_string($referrer) && - $referrer !== '' && - (new UnicodeString($referrer))->trim()->startsWith('/') + null !== $request && $request->query->has('referer') && + (new UnicodeString($request->query->get('referer')))->startsWith('/') ) { - return $this->redirect($referrer); + return $this->redirect($request->query->get('referer')); } /* @@ -451,8 +427,8 @@ protected function getPostSubmitResponse( */ if ( false === $forceDefaultEditRoute && - \is_string($route) && - $route !== '' + null !== $request && + null !== $route = $request->attributes->get('_route') ) { return $this->redirect($this->urlGenerator->generate( $route, @@ -490,27 +466,21 @@ protected function getPostDeleteResponse(PersistableInterface $item): Response } /** - * @template T of object|Event - * @param T|iterable|array|null $event - * @return T|iterable|array|null + * @param Event|Event[]|mixed|null $event + * @return object|object[]|null */ - protected function dispatchSingleOrMultipleEvent(mixed $event): object|array|null + protected function dispatchSingleOrMultipleEvent($event) { if (null === $event) { return null; } if ($event instanceof Event) { - // @phpstan-ignore-next-line return $this->dispatchEvent($event); } - if (\is_iterable($event)) { + if (is_iterable($event)) { $events = []; - /** @var T|null $singleEvent */ foreach ($event as $singleEvent) { - $returningEvent = $this->dispatchSingleOrMultipleEvent($singleEvent); - if ($returningEvent instanceof Event) { - $events[] = $returningEvent; - } + $events[] = $this->dispatchSingleOrMultipleEvent($singleEvent); } return $events; } diff --git a/src/Controllers/AbstractAdminWithBulkController.php b/src/Controllers/AbstractAdminWithBulkController.php index 1efa45a5..bbd1ed32 100644 --- a/src/Controllers/AbstractAdminWithBulkController.php +++ b/src/Controllers/AbstractAdminWithBulkController.php @@ -17,12 +17,15 @@ abstract class AbstractAdminWithBulkController extends AbstractAdminController { + protected FormFactoryInterface $formFactory; + public function __construct( - protected readonly FormFactoryInterface $formFactory, + FormFactoryInterface $formFactory, SerializerInterface $serializer, UrlGeneratorInterface $urlGenerator ) { parent::__construct($serializer, $urlGenerator); + $this->formFactory = $formFactory; } protected function additionalAssignation(Request $request): void @@ -167,7 +170,7 @@ protected function bulkAction( '%namespace%' => $this->getTranslator()->trans($this->getNamespace()) ] ); - $this->publishConfirmMessage($request, $msg, $item); + $this->publishConfirmMessage($request, $msg); } } $this->em()->flush(); diff --git a/src/Controllers/Attributes/AttributeController.php b/src/Controllers/Attributes/AttributeController.php index 49883ec6..dcfe7c0d 100644 --- a/src/Controllers/Attributes/AttributeController.php +++ b/src/Controllers/Attributes/AttributeController.php @@ -11,25 +11,26 @@ use RZ\Roadiz\CoreBundle\Form\AttributeType; use RZ\Roadiz\CoreBundle\Importer\AttributeImporter; use Symfony\Component\Form\FormError; -use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Themes\Rozier\Controllers\AbstractAdminWithBulkController; -use Twig\Error\RuntimeError; +use Themes\Rozier\Controllers\AbstractAdminController; -class AttributeController extends AbstractAdminWithBulkController +class AttributeController extends AbstractAdminController { + private AttributeImporter $attributeImporter; + public function __construct( - private readonly AttributeImporter $attributeImporter, - FormFactoryInterface $formFactory, + AttributeImporter $attributeImporter, SerializerInterface $serializer, UrlGeneratorInterface $urlGenerator ) { - parent::__construct($formFactory, $serializer, $urlGenerator); + parent::__construct($serializer, $urlGenerator); + $this->attributeImporter = $attributeImporter; } + /** * @inheritDoc */ @@ -38,11 +39,6 @@ protected function supports(PersistableInterface $item): bool return $item instanceof Attribute; } - protected function getBulkDeleteRouteName(): ?string - { - return 'attributesBulkDeletePage'; - } - /** * @inheritDoc */ @@ -107,10 +103,7 @@ protected function getFormType(): string */ protected function getDefaultOrder(Request $request): array { - return [ - 'weight' => 'DESC', - 'code' => 'ASC', - ]; + return ['code' => 'ASC']; } /** @@ -143,9 +136,8 @@ protected function getEntityName(PersistableInterface $item): string /** * @param Request $request * @return Response - * @throws RuntimeError */ - public function importAction(Request $request): Response + public function importAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_ATTRIBUTES'); @@ -157,21 +149,10 @@ public function importAction(Request $request): Response $file = $form->get('file')->getData(); if ($file->isValid()) { - $serializedData = \file_get_contents($file->getPathname()); - if (false === $serializedData) { - throw new \RuntimeException('Cannot read uploaded file.'); - } + $serializedData = file_get_contents($file->getPathname()); $this->attributeImporter->import($serializedData); $this->em()->flush(); - - $msg = $this->getTranslator()->trans( - '%namespace%.imported', - [ - '%namespace%' => $this->getTranslator()->trans($this->getNamespace()) - ] - ); - $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute('attributesHomePage'); } $form->addError(new FormError($this->getTranslator()->trans('file.not_uploaded'))); diff --git a/src/Controllers/Attributes/AttributeGroupController.php b/src/Controllers/Attributes/AttributeGroupController.php index 5fc541c3..69eeb70f 100644 --- a/src/Controllers/Attributes/AttributeGroupController.php +++ b/src/Controllers/Attributes/AttributeGroupController.php @@ -10,6 +10,9 @@ use Symfony\Component\HttpFoundation\Request; use Themes\Rozier\Controllers\AbstractAdminController; +/** + * @package Themes\Rozier\Controllers\Attributes + */ class AttributeGroupController extends AbstractAdminController { /** diff --git a/src/Controllers/CacheController.php b/src/Controllers/CacheController.php index e71f35a2..ead8047e 100644 --- a/src/Controllers/CacheController.php +++ b/src/Controllers/CacheController.php @@ -14,10 +14,15 @@ final class CacheController extends RozierApp { + private LoggerInterface $logger; + private CacheClearerInterface $cacheClearer; + public function __construct( - private readonly CacheClearerInterface $cacheClearer, - private readonly LoggerInterface $logger + CacheClearerInterface $cacheClearer, + LoggerInterface $logger ) { + $this->logger = $logger; + $this->cacheClearer = $cacheClearer; } public function deleteDoctrineCache(Request $request): Response diff --git a/src/Controllers/CustomForms/CustomFormAnswersController.php b/src/Controllers/CustomForms/CustomFormAnswersController.php index 7aea9cfe..6a015479 100644 --- a/src/Controllers/CustomForms/CustomFormAnswersController.php +++ b/src/Controllers/CustomForms/CustomFormAnswersController.php @@ -16,6 +16,9 @@ use Themes\Rozier\RozierApp; use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers + */ class CustomFormAnswersController extends RozierApp { /** @@ -84,7 +87,7 @@ public function deleteAction(Request $request, int $customFormAnswerId): Respons $this->em()->flush(); $msg = $this->getTranslator()->trans('customFormAnswer.%id%.deleted', ['%id%' => $customFormAnswer->getId()]); - $this->publishConfirmMessage($request, $msg, $customFormAnswer); + $this->publishConfirmMessage($request, $msg); /* * Redirect to update schema page */ diff --git a/src/Controllers/CustomForms/CustomFormFieldAttributesController.php b/src/Controllers/CustomForms/CustomFormFieldAttributesController.php index e5747a84..a6fac366 100644 --- a/src/Controllers/CustomForms/CustomFormFieldAttributesController.php +++ b/src/Controllers/CustomForms/CustomFormFieldAttributesController.php @@ -11,6 +11,9 @@ use Themes\Rozier\RozierApp; use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers + */ class CustomFormFieldAttributesController extends RozierApp { /** @@ -51,8 +54,8 @@ protected function getAnswersByGroups(iterable $answers): array /** @var CustomFormFieldAttribute $answer */ foreach ($answers as $answer) { $groupName = $answer->getCustomFormField()->getGroupName(); - if (\is_string($groupName) && $groupName !== '') { - if (!isset($fieldsArray[$groupName]) || !\is_array($fieldsArray[$groupName])) { + if ($groupName != '') { + if (!isset($fieldsArray[$groupName])) { $fieldsArray[$groupName] = []; } $fieldsArray[$groupName][] = $answer; diff --git a/src/Controllers/CustomForms/CustomFormFieldsController.php b/src/Controllers/CustomForms/CustomFormFieldsController.php index 66e63255..0897579d 100644 --- a/src/Controllers/CustomForms/CustomFormFieldsController.php +++ b/src/Controllers/CustomForms/CustomFormFieldsController.php @@ -18,6 +18,9 @@ use Themes\Rozier\RozierApp; use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers + */ class CustomFormFieldsController extends RozierApp { /** @@ -61,7 +64,6 @@ public function editAction(Request $request, int $customFormFieldId): Response /** @var CustomFormField|null $field */ $field = $this->em()->find(CustomFormField::class, $customFormFieldId); - if ($field === null) { throw new ResourceNotFoundException(); } @@ -75,7 +77,7 @@ public function editAction(Request $request, int $customFormFieldId): Response $this->em()->flush(); $msg = $this->getTranslator()->trans('customFormField.%name%.updated', ['%name%' => $field->getName()]); - $this->publishConfirmMessage($request, $msg, $field); + $this->publishConfirmMessage($request, $msg); /* * Redirect to update schema page @@ -106,14 +108,14 @@ public function addAction(Request $request, int $customFormId): Response { $this->denyAccessUnlessGranted('ROLE_ACCESS_CUSTOMFORMS'); + $field = new CustomFormField(); $customForm = $this->em()->find(CustomForm::class, $customFormId); + $field->setCustomForm($customForm); + if ($customForm === null) { throw new ResourceNotFoundException(); } - $field = new CustomFormField(); - $field->setCustomForm($customForm); - $this->assignation['customForm'] = $customForm; $this->assignation['field'] = $field; $form = $this->createForm(CustomFormFieldType::class, $field); @@ -128,7 +130,7 @@ public function addAction(Request $request, int $customFormId): Response 'customFormField.%name%.created', ['%name%' => $field->getName()] ); - $this->publishConfirmMessage($request, $msg, $field); + $this->publishConfirmMessage($request, $msg); /* * Redirect to update schema page @@ -141,7 +143,7 @@ public function addAction(Request $request, int $customFormId): Response ); } catch (Exception $e) { $msg = $e->getMessage(); - $this->publishErrorMessage($request, $msg, $field); + $this->publishErrorMessage($request, $msg); /* * Redirect to add page */ diff --git a/src/Controllers/CustomForms/CustomFormsController.php b/src/Controllers/CustomForms/CustomFormsController.php index 042983c8..7956bec1 100644 --- a/src/Controllers/CustomForms/CustomFormsController.php +++ b/src/Controllers/CustomForms/CustomFormsController.php @@ -6,62 +6,98 @@ use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; use RZ\Roadiz\CoreBundle\Entity\CustomForm; +use RZ\Roadiz\RozierBundle\Form\CustomFormType; use Symfony\Component\HttpFoundation\Request; -use Themes\Rozier\Controllers\AbstractAdminWithBulkController; -use Themes\Rozier\Forms\CustomFormType; +use Themes\Rozier\Controllers\AbstractAdminController; -class CustomFormsController extends AbstractAdminWithBulkController +/** + * @package Themes\Rozier\Controllers + */ +class CustomFormsController extends AbstractAdminController { + /** + * @inheritDoc + */ protected function supports(PersistableInterface $item): bool { return $item instanceof CustomForm; } + /** + * @inheritDoc + */ protected function getNamespace(): string { return 'custom-form'; } + /** + * @inheritDoc + */ protected function createEmptyItem(Request $request): PersistableInterface { return new CustomForm(); } + /** + * @inheritDoc + */ protected function getTemplateFolder(): string { return '@RoadizRozier/custom-forms'; } + /** + * @inheritDoc + */ protected function getRequiredRole(): string { return 'ROLE_ACCESS_CUSTOMFORMS'; } + /** + * @inheritDoc + */ protected function getEntityClass(): string { return CustomForm::class; } + /** + * @inheritDoc + */ protected function getFormType(): string { return CustomFormType::class; } + /** + * @inheritDoc + */ protected function getDefaultOrder(Request $request): array { return ['createdAt' => 'DESC']; } + /** + * @inheritDoc + */ protected function getDefaultRouteName(): string { return 'customFormsHomePage'; } + /** + * @inheritDoc + */ protected function getEditRouteName(): string { return 'customFormsEditPage'; } + /** + * @inheritDoc + */ protected function getEntityName(PersistableInterface $item): string { if ($item instanceof CustomForm) { @@ -69,9 +105,4 @@ protected function getEntityName(PersistableInterface $item): string } throw new \InvalidArgumentException('Item should be instance of ' . $this->getEntityClass()); } - - protected function getBulkDeleteRouteName(): ?string - { - return 'customFormsBulkDeletePage'; - } } diff --git a/src/Controllers/CustomForms/CustomFormsUtilsController.php b/src/Controllers/CustomForms/CustomFormsUtilsController.php index 5c293ccb..ba24ba04 100644 --- a/src/Controllers/CustomForms/CustomFormsUtilsController.php +++ b/src/Controllers/CustomForms/CustomFormsUtilsController.php @@ -14,10 +14,19 @@ use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Themes\Rozier\RozierApp; +/** + * @package Themes\Rozier\Controllers + */ class CustomFormsUtilsController extends RozierApp { - public function __construct(private readonly CustomFormAnswerSerializer $customFormAnswerSerializer) + private CustomFormAnswerSerializer $customFormAnswerSerializer; + + /** + * @param CustomFormAnswerSerializer $customFormAnswerSerializer + */ + public function __construct(CustomFormAnswerSerializer $customFormAnswerSerializer) { + $this->customFormAnswerSerializer = $customFormAnswerSerializer; } /** @@ -114,7 +123,7 @@ public function duplicateAction(Request $request, int $id): Response '%name%' => $existingCustomForm->getDisplayName(), ]); - $this->publishConfirmMessage($request, $msg, $newCustomForm); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute( 'customFormsEditPage', @@ -125,10 +134,9 @@ public function duplicateAction(Request $request, int $id): Response $request, $this->getTranslator()->trans("impossible.duplicate.custom.form.%name%", [ '%name%' => $existingCustomForm->getDisplayName(), - ]), - $newCustomForm + ]) ); - $this->publishErrorMessage($request, $e->getMessage(), $existingCustomForm); + $this->publishErrorMessage($request, $e->getMessage()); return $this->redirectToRoute( 'customFormsEditPage', diff --git a/src/Controllers/DashboardController.php b/src/Controllers/DashboardController.php index 5a28614c..81178887 100644 --- a/src/Controllers/DashboardController.php +++ b/src/Controllers/DashboardController.php @@ -4,12 +4,15 @@ namespace Themes\Rozier\Controllers; -use RZ\Roadiz\CoreBundle\Logger\Entity\Log; +use RZ\Roadiz\CoreBundle\Entity\Log; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Themes\Rozier\RozierApp; use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers + */ class DashboardController extends RozierApp { /** diff --git a/src/Controllers/Documents/DocumentTranslationsController.php b/src/Controllers/Documents/DocumentTranslationsController.php index e8c1138f..70e81f4d 100644 --- a/src/Controllers/Documents/DocumentTranslationsController.php +++ b/src/Controllers/Documents/DocumentTranslationsController.php @@ -12,7 +12,6 @@ use RZ\Roadiz\CoreBundle\Entity\Translation; use RZ\Roadiz\CoreBundle\Event\Document\DocumentTranslationUpdatedEvent; use Symfony\Component\Form\Extension\Core\Type\HiddenType; -use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\ResourceNotFoundException; @@ -23,19 +22,22 @@ use Themes\Rozier\Traits\VersionedControllerTrait; use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers\Documents + */ class DocumentTranslationsController extends RozierApp { use VersionedControllerTrait; /** * @param Request $request - * @param int $documentId - * @param int|null $translationId + * @param int $documentId + * @param int|null $translationId * * @return Response * @throws RuntimeError */ - public function editAction(Request $request, int $documentId, ?int $translationId = null): Response + public function editAction(Request $request, int $documentId, ?int $translationId = null) { $this->denyAccessUnlessGranted('ROLE_ACCESS_DOCUMENTS'); @@ -63,65 +65,69 @@ public function editAction(Request $request, int $documentId, ?int $translationI $documentTr = $this->createDocumentTranslation($document, $translation); } - if ($documentTr === null || $document === null) { - throw new ResourceNotFoundException(); - } - - $this->assignation['document'] = $document; - $this->assignation['translation'] = $translation; - $this->assignation['documentTr'] = $documentTr; + if ($documentTr !== null && $document !== null) { + $this->assignation['document'] = $document; + $this->assignation['translation'] = $translation; + $this->assignation['documentTr'] = $documentTr; - /** - * Versioning - */ - if ($this->isGranted('ROLE_ACCESS_VERSIONS')) { - if (null !== $response = $this->handleVersions($request, $documentTr)) { - return $response; + /** + * Versioning + */ + if ($this->isGranted('ROLE_ACCESS_VERSIONS')) { + if (null !== $response = $this->handleVersions($request, $documentTr)) { + return $response; + } } - } - /* - * Handle main form - */ - $form = $this->createForm(DocumentTranslationType::class, $documentTr, [ - 'referer' => $this->getRequest()->get('referer'), - 'disabled' => $this->isReadOnly, - ]); - $form->handleRequest($request); + /* + * Handle main form + */ + $form = $this->createForm(DocumentTranslationType::class, $documentTr, [ + 'referer' => $this->getRequest()->get('referer'), + 'disabled' => $this->isReadOnly, + ]); + $form->handleRequest($request); - if ($form->isSubmitted() && $form->isValid()) { - $this->onPostUpdate($documentTr, $request); + if ($form->isSubmitted() && $form->isValid()) { + $this->onPostUpdate($documentTr, $request); - $routeParams = [ - 'documentId' => $document->getId(), - 'translationId' => $translationId, - ]; + $routeParams = [ + 'documentId' => $document->getId(), + 'translationId' => $translationId, + ]; - if ($form->get('referer')->getData()) { - $routeParams = array_merge($routeParams, [ - 'referer' => $form->get('referer')->getData() - ]); + if ($form->get('referer')->getData()) { + $routeParams = array_merge($routeParams, [ + 'referer' => $form->get('referer')->getData() + ]); + } + + /* + * Force redirect to avoid resending form when refreshing page + */ + return $this->redirectToRoute( + 'documentsMetaPage', + $routeParams + ); } - /* - * Force redirect to avoid resending form when refreshing page - */ - return $this->redirectToRoute( - 'documentsMetaPage', - $routeParams - ); - } + $this->assignation['form'] = $form->createView(); + $this->assignation['readOnly'] = $this->isReadOnly; - $this->assignation['form'] = $form->createView(); - $this->assignation['readOnly'] = $this->isReadOnly; + return $this->render('@RoadizRozier/document-translations/edit.html.twig', $this->assignation); + } - return $this->render('@RoadizRozier/document-translations/edit.html.twig', $this->assignation); + throw new ResourceNotFoundException(); } - protected function createDocumentTranslation( - Document $document, - TranslationInterface $translation - ): DocumentTranslation { + /** + * @param Document $document + * @param TranslationInterface $translation + * + * @return DocumentTranslation + */ + protected function createDocumentTranslation(Document $document, TranslationInterface $translation) + { $dt = new DocumentTranslation(); $dt->setDocument($document); $dt->setTranslation($translation); @@ -141,7 +147,7 @@ protected function createDocumentTranslation( * @return Response * @throws RuntimeError */ - public function deleteAction(Request $request, int $documentId, int $translationId): Response + public function deleteAction(Request $request, int $documentId, int $translationId) { $this->denyAccessUnlessGranted('ROLE_ACCESS_DOCUMENTS_DELETE'); @@ -173,13 +179,13 @@ public function deleteAction(Request $request, int $documentId, int $translation 'document.translation.%name%.deleted', ['%name%' => (string) $document] ); - $this->publishConfirmMessage($request, $msg, $document); + $this->publishConfirmMessage($request, $msg); } catch (Exception $e) { $msg = $this->getTranslator()->trans( 'document.translation.%name%.cannot_delete', ['%name%' => (string) $document] ); - $this->publishErrorMessage($request, $msg, $document); + $this->publishErrorMessage($request, $msg); } /* * Force redirect to avoid resending form when refreshing page @@ -198,7 +204,12 @@ public function deleteAction(Request $request, int $documentId, int $translation throw new ResourceNotFoundException(); } - private function buildDeleteForm(DocumentTranslation $doc): FormInterface + /** + * @param DocumentTranslation $doc + * + * @return \Symfony\Component\Form\FormInterface + */ + private function buildDeleteForm(DocumentTranslation $doc) { $defaults = [ 'documentTranslationId' => $doc->getId(), @@ -228,10 +239,15 @@ protected function onPostUpdate(PersistableInterface $entity, Request $request): $msg = $this->getTranslator()->trans('document.translation.%name%.updated', [ '%name%' => (string) $entity->getDocument(), ]); - $this->publishConfirmMessage($request, $msg, $entity); + $this->publishConfirmMessage($request, $msg); } } + /** + * @param PersistableInterface $entity + * + * @return Response + */ protected function getPostUpdateRedirection(PersistableInterface $entity): ?Response { if ( diff --git a/src/Controllers/Documents/DocumentsController.php b/src/Controllers/Documents/DocumentsController.php index 2aa339c6..1acffaed 100644 --- a/src/Controllers/Documents/DocumentsController.php +++ b/src/Controllers/Documents/DocumentsController.php @@ -7,6 +7,7 @@ use GuzzleHttp\Exception\RequestException; use League\Flysystem\FilesystemException; use League\Flysystem\FilesystemOperator; +use League\Flysystem\UnableToMoveFile; use Psr\Log\LoggerInterface; use RZ\Roadiz\Core\Handlers\HandlerFactoryInterface; use RZ\Roadiz\CoreBundle\Document\DocumentFactory; @@ -19,7 +20,6 @@ use RZ\Roadiz\CoreBundle\Entity\Translation; use RZ\Roadiz\CoreBundle\EntityHandler\DocumentHandler; use RZ\Roadiz\CoreBundle\Exception\EntityAlreadyExistsException; -use RZ\Roadiz\CoreBundle\ListManager\SessionListFilters; use RZ\Roadiz\Documents\Events\DocumentCreatedEvent; use RZ\Roadiz\Documents\Events\DocumentDeletedEvent; use RZ\Roadiz\Documents\Events\DocumentFileUpdatedEvent; @@ -48,7 +48,6 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\String\UnicodeString; use Symfony\Component\Validator\Constraints\File; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; @@ -56,10 +55,23 @@ use Themes\Rozier\Forms\DocumentEmbedType; use Themes\Rozier\Models\DocumentModel; use Themes\Rozier\RozierApp; +use Themes\Rozier\Utils\SessionListFilters; use Twig\Error\RuntimeError; class DocumentsController extends RozierApp { + private array $documentPlatforms; + private DocumentFactory $documentFactory; + private HandlerFactoryInterface $handlerFactory; + private LoggerInterface $logger; + private RandomImageFinder $randomImageFinder; + private RendererInterface $renderer; + private DocumentUrlGeneratorInterface $documentUrlGenerator; + private UrlGeneratorInterface $urlGenerator; + private FilesystemOperator $documentsStorage; + private ?string $googleServerId; + private ?string $soundcloudClientId; + protected array $thumbnailFormat = [ 'quality' => 50, 'fit' => '128x128', @@ -69,21 +81,34 @@ class DocumentsController extends RozierApp 'controls' => false, 'loading' => 'lazy', ]; + private EmbedFinderFactory $embedFinderFactory; public function __construct( - private readonly array $documentPlatforms, - private readonly FilesystemOperator $documentsStorage, - private readonly HandlerFactoryInterface $handlerFactory, - private readonly LoggerInterface $logger, - private readonly RandomImageFinder $randomImageFinder, - private readonly DocumentFactory $documentFactory, - private readonly RendererInterface $renderer, - private readonly DocumentUrlGeneratorInterface $documentUrlGenerator, - private readonly UrlGeneratorInterface $urlGenerator, - private readonly EmbedFinderFactory $embedFinderFactory, - private readonly ?string $googleServerId = null, - private readonly ?string $soundcloudClientId = null + array $documentPlatforms, + FilesystemOperator $documentsStorage, + HandlerFactoryInterface $handlerFactory, + LoggerInterface $logger, + RandomImageFinder $randomImageFinder, + DocumentFactory $documentFactory, + RendererInterface $renderer, + DocumentUrlGeneratorInterface $documentUrlGenerator, + UrlGeneratorInterface $urlGenerator, + EmbedFinderFactory $embedFinderFactory, + ?string $googleServerId = null, + ?string $soundcloudClientId = null ) { + $this->documentPlatforms = $documentPlatforms; + $this->handlerFactory = $handlerFactory; + $this->logger = $logger; + $this->randomImageFinder = $randomImageFinder; + $this->documentFactory = $documentFactory; + $this->renderer = $renderer; + $this->documentUrlGenerator = $documentUrlGenerator; + $this->urlGenerator = $urlGenerator; + $this->googleServerId = $googleServerId; + $this->soundcloudClientId = $soundcloudClientId; + $this->documentsStorage = $documentsStorage; + $this->embedFinderFactory = $embedFinderFactory; } /** @@ -116,16 +141,20 @@ public function indexAction(Request $request, ?int $folderId = null): Response $this->assignation['folder'] = $folder; } - $type = $request->query->get('type', null); - if (\is_string($type) && trim($type) !== '') { - $prefilters['mimeType'] = trim($type); - $this->assignation['mimeType'] = trim($type); + if ( + $request->query->has('type') && + $request->query->get('type', '') !== '' + ) { + $prefilters['mimeType'] = trim($request->query->get('type', '')); + $this->assignation['mimeType'] = trim($request->query->get('type', '')); } - $embedPlatform = $request->query->get('embedPlatform', null); - if (\is_string($embedPlatform) && trim($embedPlatform) !== '') { - $prefilters['embedPlatform'] = trim($embedPlatform); - $this->assignation['embedPlatform'] = trim($embedPlatform); + if ( + $request->query->has('embedPlatform') && + $request->query->get('embedPlatform', '') !== '' + ) { + $prefilters['embedPlatform'] = trim($request->query->get('embedPlatform', '')); + $this->assignation['embedPlatform'] = trim($request->query->get('embedPlatform', '')); } $this->assignation['availablePlatforms'] = $this->documentPlatforms; @@ -311,13 +340,13 @@ public function editAction(Request $request, int $documentId): Response $this->dispatchEvent( new DocumentFileUpdatedEvent($document) ); - $this->publishConfirmMessage($request, $msg, $document); + $this->publishConfirmMessage($request, $msg); } $msg = $this->getTranslator()->trans('document.%name%.updated', [ '%name%' => (string) $document, ]); - $this->publishConfirmMessage($request, $msg, $document); + $this->publishConfirmMessage($request, $msg); $this->em()->flush(); // Event must be dispatched AFTER flush for async concurrency matters $this->dispatchEvent( @@ -390,13 +419,13 @@ public function deleteAction(Request $request, int $documentId): Response $msg = $this->getTranslator()->trans('document.%name%.deleted', [ '%name%' => (string) $document ]); - $this->publishConfirmMessage($request, $msg, $document); + $this->publishConfirmMessage($request, $msg); } catch (\Exception $e) { $msg = $this->getTranslator()->trans('document.%name%.cannot_delete', [ '%name%' => (string) $document ]); $this->logger->error($e->getMessage()); - $this->publishErrorMessage($request, $msg, $document); + $this->publishErrorMessage($request, $msg); } /* * Force redirect to avoid resending form when refreshing page @@ -448,7 +477,7 @@ public function bulkDeleteAction(Request $request): Response 'document.%name%.deleted', ['%name%' => (string) $document] ); - $this->publishConfirmMessage($request, $msg, $document); + $this->publishConfirmMessage($request, $msg); } $this->em()->flush(); @@ -494,18 +523,18 @@ public function embedAction(Request $request, ?int $folderId = null): Response if (is_iterable($document)) { foreach ($document as $singleDocument) { $msg = $this->getTranslator()->trans('document.%name%.uploaded', [ - '%name%' => (new UnicodeString((string) $singleDocument))->truncate(50, '...')->toString(), + '%name%' => (string) $singleDocument, ]); - $this->publishConfirmMessage($request, $msg, $singleDocument); + $this->publishConfirmMessage($request, $msg); $this->dispatchEvent( new DocumentCreatedEvent($singleDocument) ); } } else { $msg = $this->getTranslator()->trans('document.%name%.uploaded', [ - '%name%' => (new UnicodeString((string) $document))->truncate(50, '...')->toString(), + '%name%' => (string) $document, ]); - $this->publishConfirmMessage($request, $msg, $document); + $this->publishConfirmMessage($request, $msg); $this->dispatchEvent( new DocumentCreatedEvent($document) ); @@ -553,9 +582,9 @@ public function randomAction(Request $request, ?int $folderId = null): Response $document = $this->randomDocument($folderId); $msg = $this->getTranslator()->trans('document.%name%.uploaded', [ - '%name%' => (new UnicodeString((string) $document))->truncate(50, '...')->toString(), + '%name%' => (string) $document, ]); - $this->publishConfirmMessage($request, $msg, $document); + $this->publishConfirmMessage($request, $msg); $this->dispatchEvent( new DocumentCreatedEvent($document) @@ -597,31 +626,6 @@ public function downloadAction(Request $request, int $documentId): Response throw new ResourceNotFoundException(); } - /** - * Download document file inline. - * - * @param Request $request - * @param int $documentId - * @return Response - * @throws FilesystemException - */ - public function downloadInlineAction(Request $request, int $documentId): Response - { - $this->denyAccessUnlessGranted('ROLE_ACCESS_DOCUMENTS'); - - /** @var Document|null $document */ - $document = $this->em()->find(Document::class, $documentId); - - if ($document !== null) { - /** @var DocumentHandler $handler */ - $handler = $this->handlerFactory->getHandler($document); - - return $handler->getDownloadResponse(false); - } - - throw new ResourceNotFoundException(); - } - /** * @param Request $request * @param int|null $folderId @@ -651,9 +655,9 @@ public function uploadAction(Request $request, ?int $folderId = null, string $_f if (null !== $document) { $msg = $this->getTranslator()->trans('document.%name%.uploaded', [ - '%name%' => (new UnicodeString((string) $document))->truncate(50, '...')->toString(), + '%name%' => (string) $document, ]); - $this->publishConfirmMessage($request, $msg, $document); + $this->publishConfirmMessage($request, $msg); // Event must be dispatched AFTER flush for async concurrency matters $this->dispatchEvent( @@ -677,7 +681,7 @@ public function uploadAction(Request $request, ?int $folderId = null, string $_f } } else { $msg = $this->getTranslator()->trans('document.cannot_persist'); - $this->publishErrorMessage($request, $msg, $document); + $this->publishErrorMessage($request, $msg); if ($_format === 'json' || $request->isXmlHttpRequest()) { throw $this->createNotFoundException($msg); @@ -693,7 +697,6 @@ public function uploadAction(Request $request, ?int $folderId = null, string $_f /** @var Form $child */ foreach ($form as $child) { if ($child->isSubmitted() && !$child->isValid()) { - /** @var FormError $error */ foreach ($child->getErrors() as $error) { $errorPerForm[$child->getName()][] = $this->getTranslator()->trans($error->getMessage()); } diff --git a/src/Controllers/FoldersController.php b/src/Controllers/FoldersController.php index c3e866b5..44f671d1 100644 --- a/src/Controllers/FoldersController.php +++ b/src/Controllers/FoldersController.php @@ -25,8 +25,11 @@ class FoldersController extends RozierApp { - public function __construct(private readonly DocumentArchiver $documentArchiver) + private DocumentArchiver $documentArchiver; + + public function __construct(DocumentArchiver $documentArchiver) { + $this->documentArchiver = $documentArchiver; } public function indexAction(Request $request): Response @@ -83,7 +86,7 @@ public function addAction(Request $request, ?int $parentFolderId = null): Respon 'folder.%name%.created', ['%name%' => $folder->getFolderName()] ); - $this->publishConfirmMessage($request, $msg, $folder); + $this->publishConfirmMessage($request, $msg); /* * Dispatch event @@ -92,7 +95,7 @@ public function addAction(Request $request, ?int $parentFolderId = null): Respon new FolderCreatedEvent($folder) ); } catch (\RuntimeException $e) { - $this->publishErrorMessage($request, $e->getMessage(), $folder); + $this->publishErrorMessage($request, $e->getMessage()); } return $this->redirectToRoute('foldersHomePage'); @@ -134,7 +137,7 @@ public function deleteAction(Request $request, int $folderId): Response 'folder.%name%.deleted', ['%name%' => $folder->getFolderName()] ); - $this->publishConfirmMessage($request, $msg, $folder); + $this->publishConfirmMessage($request, $msg); /* * Dispatch event @@ -143,7 +146,7 @@ public function deleteAction(Request $request, int $folderId): Response new FolderDeletedEvent($folder) ); } catch (\RuntimeException $e) { - $this->publishErrorMessage($request, $e->getMessage(), $folder); + $this->publishErrorMessage($request, $e->getMessage()); } return $this->redirectToRoute('foldersHomePage'); @@ -190,7 +193,7 @@ public function editAction(Request $request, int $folderId): Response 'folder.%name%.updated', ['%name%' => $folder->getFolderName()] ); - $this->publishConfirmMessage($request, $msg, $folder); + $this->publishConfirmMessage($request, $msg); /* * Dispatch event */ @@ -198,7 +201,7 @@ public function editAction(Request $request, int $folderId): Response new FolderUpdatedEvent($folder) ); } catch (\RuntimeException $e) { - $this->publishErrorMessage($request, $e->getMessage(), $folder); + $this->publishErrorMessage($request, $e->getMessage()); } return $this->redirectToRoute('foldersEditPage', ['folderId' => $folderId]); @@ -274,7 +277,7 @@ public function editTranslationAction(Request $request, int $folderId, int $tran 'folder.%name%.updated', ['%name%' => $folder->getFolderName()] ); - $this->publishConfirmMessage($request, $msg, $folder); + $this->publishConfirmMessage($request, $msg); /* * Dispatch event */ @@ -282,7 +285,7 @@ public function editTranslationAction(Request $request, int $folderId, int $tran new FolderUpdatedEvent($folder) ); } catch (\RuntimeException $e) { - $this->publishErrorMessage($request, $e->getMessage(), $folder); + $this->publishErrorMessage($request, $e->getMessage()); } return $this->redirectToRoute('foldersEditTranslationPage', [ diff --git a/src/Controllers/GroupsController.php b/src/Controllers/GroupsController.php index c4b9e949..61cbc768 100644 --- a/src/Controllers/GroupsController.php +++ b/src/Controllers/GroupsController.php @@ -20,6 +20,9 @@ use Themes\Rozier\Forms\GroupType; use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers + */ class GroupsController extends AbstractAdminController { /** @@ -147,7 +150,7 @@ public function editRolesAction(Request $request, int $id): Response '%group%' => $item->getName(), '%role%' => $role->getRole(), ]); - $this->publishConfirmMessage($request, $msg, $role); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute( 'groupsEditRolesPage', @@ -203,7 +206,7 @@ public function removeRolesAction(Request $request, int $id, int $roleId): Respo '%role%' => $role->getRole(), '%group%' => $item->getName(), ]); - $this->publishConfirmMessage($request, $msg, $role); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute( 'groupsEditRolesPage', @@ -251,7 +254,7 @@ public function editUsersAction(Request $request, int $id): Response '%group%' => $item->getName(), '%user%' => $user->getUserName(), ]); - $this->publishConfirmMessage($request, $msg, $user); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute( 'groupsEditUsersPage', @@ -306,7 +309,7 @@ public function removeUsersAction(Request $request, int $id, int $userId): Respo '%user%' => $user->getUserName(), '%group%' => $item->getName(), ]); - $this->publishConfirmMessage($request, $msg, $user); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute( 'groupsEditUsersPage', diff --git a/src/Controllers/GroupsUtilsController.php b/src/Controllers/GroupsUtilsController.php index 25e6be3e..f15b6499 100644 --- a/src/Controllers/GroupsUtilsController.php +++ b/src/Controllers/GroupsUtilsController.php @@ -20,10 +20,17 @@ class GroupsUtilsController extends RozierApp { - public function __construct( - private readonly SerializerInterface $serializer, - private readonly GroupsImporter $groupsImporter - ) { + private SerializerInterface $serializer; + private GroupsImporter $groupsImporter; + + /** + * @param SerializerInterface $serializer + * @param GroupsImporter $groupsImporter + */ + public function __construct(SerializerInterface $serializer, GroupsImporter $groupsImporter) + { + $this->serializer = $serializer; + $this->groupsImporter = $groupsImporter; } /** @@ -113,9 +120,6 @@ public function importJsonFileAction(Request $request): Response if ($file->isValid()) { $serializedData = file_get_contents($file->getPathname()); - if (false === $serializedData) { - throw new RuntimeError('Cannot read uploaded file.'); - } if (null !== \json_decode($serializedData)) { $this->groupsImporter->import($serializedData); diff --git a/src/Controllers/HistoryController.php b/src/Controllers/HistoryController.php index acc480ca..56e7a727 100644 --- a/src/Controllers/HistoryController.php +++ b/src/Controllers/HistoryController.php @@ -4,12 +4,12 @@ namespace Themes\Rozier\Controllers; -use Monolog\Logger; +use RZ\Roadiz\CoreBundle\Entity\Log; use RZ\Roadiz\CoreBundle\Entity\User; -use RZ\Roadiz\CoreBundle\Logger\Entity\Log; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Themes\Rozier\RozierApp; use Twig\Error\RuntimeError; @@ -19,14 +19,14 @@ class HistoryController extends RozierApp { public static array $levelToHuman = [ - Logger::EMERGENCY => "emergency", - Logger::CRITICAL => "critical", - Logger::ALERT => "alert", - Logger::ERROR => "error", - Logger::WARNING => "warning", - Logger::NOTICE => "notice", - Logger::INFO => "info", - Logger::DEBUG => "debug", + Log::EMERGENCY => "emergency", + Log::CRITICAL => "critical", + Log::ALERT => "alert", + Log::ERROR => "error", + Log::WARNING => "warning", + Log::NOTICE => "notice", + Log::INFO => "info", + Log::DEBUG => "debug", ]; /** @@ -64,20 +64,20 @@ public function indexAction(Request $request): Response * List user logs action. * * @param Request $request - * @param int|string $userId + * @param int $userId * * @return Response * @throws \Doctrine\ORM\ORMException * @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\TransactionRequiredException */ - public function userAction(Request $request, int|string $userId): Response + public function userAction(Request $request, int $userId): Response { $this->denyAccessUnlessGranted(['ROLE_BACKEND_USER', 'ROLE_ACCESS_LOGS']); if ( !($this->isGranted(['ROLE_ACCESS_USERS', 'ROLE_ACCESS_LOGS']) - || ($this->getUser() instanceof User && $this->getUser()->getId() === $userId)) + || ($this->getUser() instanceof User && $this->getUser()->getId() == $userId)) ) { throw $this->createAccessDeniedException("You don't have access to this page: ROLE_ACCESS_USERS"); } @@ -94,7 +94,7 @@ public function userAction(Request $request, int|string $userId): Response */ $listManager = $this->createEntityListManager( Log::class, - ['userId' => $user->getId()], + ['user' => $user], ['datetime' => 'DESC'] ); $listManager->setDisplayingNotPublishedNodes(true); diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php index 7dea01cb..fe26ca9c 100644 --- a/src/Controllers/LoginController.php +++ b/src/Controllers/LoginController.php @@ -16,9 +16,9 @@ class LoginController extends RozierApp { public function __construct( - private readonly DocumentUrlGeneratorInterface $documentUrlGenerator, - private readonly RandomImageFinder $randomImageFinder, - private readonly Settings $settingsBag + private DocumentUrlGeneratorInterface $documentUrlGenerator, + private RandomImageFinder $randomImageFinder, + private Settings $settingsBag ) { } @@ -41,9 +41,10 @@ public function imageAction(Request $request): Response 'quality' => 80, 'sharpen' => 5, ]); - return $response->setData([ + $response->setData([ 'url' => $this->documentUrlGenerator->getUrl() ]); + return $response; } } @@ -53,8 +54,9 @@ public function imageAction(Request $request): Response if (null !== $feed) { $url = $feed['url'] ?? $feed['urls']['regular'] ?? $feed['urls']['full'] ?? $feed['urls']['raw'] ?? null; } - return $response->setData([ - 'url' => $url ?? '/themes/Rozier/static/assets/img/default_login.jpg' + $response->setData([ + 'url' => '/themes/Rozier/static/assets/img/default_login.jpg' ]); + return $response; } } diff --git a/src/Controllers/NodeTypeFieldsController.php b/src/Controllers/NodeTypeFieldsController.php index 4bdcd44b..5596ab58 100644 --- a/src/Controllers/NodeTypeFieldsController.php +++ b/src/Controllers/NodeTypeFieldsController.php @@ -21,10 +21,11 @@ class NodeTypeFieldsController extends RozierApp { - public function __construct( - private readonly bool $allowNodeTypeEdition, - private readonly MessageBusInterface $messageBus - ) { + private MessageBusInterface $messageBus; + + public function __construct(MessageBusInterface $messageBus) + { + $this->messageBus = $messageBus; } /** @@ -78,25 +79,21 @@ public function editAction(Request $request, int $nodeTypeFieldId): Response $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - if (!$this->allowNodeTypeEdition) { - $form->addError(new FormError('You cannot edit node-type fields in production.')); - } else { - $this->em()->flush(); + $this->em()->flush(); - /** @var NodeType $nodeType */ - $nodeType = $field->getNodeType(); - $this->messageBus->dispatch(new Envelope(new UpdateNodeTypeSchemaMessage($nodeType->getId()))); + /** @var NodeType $nodeType */ + $nodeType = $field->getNodeType(); + $this->messageBus->dispatch(new Envelope(new UpdateNodeTypeSchemaMessage($nodeType->getId()))); - $msg = $this->getTranslator()->trans('nodeTypeField.%name%.updated', ['%name%' => $field->getName()]); - $this->publishConfirmMessage($request, $msg, $field); + $msg = $this->getTranslator()->trans('nodeTypeField.%name%.updated', ['%name%' => $field->getName()]); + $this->publishConfirmMessage($request, $msg); - return $this->redirectToRoute( - 'nodeTypeFieldsEditPage', - [ - 'nodeTypeFieldId' => $nodeTypeFieldId, - ] - ); - } + return $this->redirectToRoute( + 'nodeTypeFieldsEditPage', + [ + 'nodeTypeFieldId' => $nodeTypeFieldId, + ] + ); } $this->assignation['form'] = $form->createView(); @@ -133,37 +130,31 @@ public function addAction(Request $request, int $nodeTypeId): Response $this->assignation['nodeType'] = $nodeType; $this->assignation['field'] = $field; - $form = $this->createForm(NodeTypeFieldType::class, $field, [ - 'disabled' => !$this->allowNodeTypeEdition - ]); + $form = $this->createForm(NodeTypeFieldType::class, $field); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - if (!$this->allowNodeTypeEdition) { - $form->addError(new FormError('You cannot add node-type fields in production.')); - } else { - try { - $this->em()->persist($field); - $this->em()->flush(); - $this->em()->refresh($nodeType); - - $this->messageBus->dispatch(new Envelope(new UpdateNodeTypeSchemaMessage($nodeType->getId()))); - - $msg = $this->getTranslator()->trans( - 'nodeTypeField.%name%.created', - ['%name%' => $field->getName()] - ); - $this->publishConfirmMessage($request, $msg, $field); - - return $this->redirectToRoute( - 'nodeTypeFieldsListPage', - [ - 'nodeTypeId' => $nodeTypeId, - ] - ); - } catch (Exception $e) { - $form->addError(new FormError($e->getMessage())); - } + try { + $this->em()->persist($field); + $this->em()->flush(); + $this->em()->refresh($nodeType); + + $this->messageBus->dispatch(new Envelope(new UpdateNodeTypeSchemaMessage($nodeType->getId()))); + + $msg = $this->getTranslator()->trans( + 'nodeTypeField.%name%.created', + ['%name%' => $field->getName()] + ); + $this->publishConfirmMessage($request, $msg); + + return $this->redirectToRoute( + 'nodeTypeFieldsListPage', + [ + 'nodeTypeId' => $nodeTypeId, + ] + ); + } catch (Exception $e) { + $form->addError(new FormError($e->getMessage())); } } @@ -194,30 +185,26 @@ public function deleteAction(Request $request, int $nodeTypeFieldId): Response $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - if (!$this->allowNodeTypeEdition) { - $form->addError(new FormError('You cannot delete node-type fields in production.')); - } else { - /** @var NodeType $nodeType */ - $nodeType = $field->getNodeType(); - $nodeTypeId = $nodeType->getId(); - $this->em()->remove($field); - $this->em()->flush(); - - $this->messageBus->dispatch(new Envelope(new UpdateNodeTypeSchemaMessage($nodeTypeId))); - - $msg = $this->getTranslator()->trans( - 'nodeTypeField.%name%.deleted', - ['%name%' => $field->getName()] - ); - $this->publishConfirmMessage($request, $msg, $field); - - return $this->redirectToRoute( - 'nodeTypeFieldsListPage', - [ - 'nodeTypeId' => $nodeTypeId, - ] - ); - } + /** @var NodeType $nodeType */ + $nodeType = $field->getNodeType(); + $nodeTypeId = $nodeType->getId(); + $this->em()->remove($field); + $this->em()->flush(); + + $this->messageBus->dispatch(new Envelope(new UpdateNodeTypeSchemaMessage($nodeTypeId))); + + $msg = $this->getTranslator()->trans( + 'nodeTypeField.%name%.deleted', + ['%name%' => $field->getName()] + ); + $this->publishConfirmMessage($request, $msg); + + return $this->redirectToRoute( + 'nodeTypeFieldsListPage', + [ + 'nodeTypeId' => $nodeTypeId, + ] + ); } $this->assignation['field'] = $field; diff --git a/src/Controllers/NodeTypes/NodeTypesController.php b/src/Controllers/NodeTypes/NodeTypesController.php index 58720914..c4de8822 100644 --- a/src/Controllers/NodeTypes/NodeTypesController.php +++ b/src/Controllers/NodeTypes/NodeTypesController.php @@ -6,7 +6,6 @@ use RZ\Roadiz\CoreBundle\Entity\NodeType; use RZ\Roadiz\CoreBundle\Exception\EntityAlreadyExistsException; -use RZ\Roadiz\CoreBundle\ListManager\SessionListFilters; use RZ\Roadiz\CoreBundle\Message\DeleteNodeTypeMessage; use RZ\Roadiz\CoreBundle\Message\UpdateNodeTypeSchemaMessage; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -17,14 +16,18 @@ use Symfony\Component\Messenger\MessageBusInterface; use Themes\Rozier\Forms\NodeTypeType; use Themes\Rozier\RozierApp; -use Twig\Error\RuntimeError; +use Themes\Rozier\Utils\SessionListFilters; +/** + * @package Themes\Rozier\Controllers\NodeTypes + */ class NodeTypesController extends RozierApp { - public function __construct( - private readonly bool $allowNodeTypeEdition, - private readonly MessageBusInterface $messageBus - ) { + private MessageBusInterface $messageBus; + + public function __construct(MessageBusInterface $messageBus) + { + $this->messageBus = $messageBus; } public function indexAction(Request $request): Response @@ -55,10 +58,12 @@ public function indexAction(Request $request): Response } /** + * Return an edition form for requested node-type. + * * @param Request $request - * @param int $nodeTypeId + * @param int $nodeTypeId + * * @return Response - * @throws RuntimeError */ public function editAction(Request $request, int $nodeTypeId): Response { @@ -77,10 +82,11 @@ public function editAction(Request $request, int $nodeTypeId): Response if ($form->isSubmitted() && $form->isValid()) { try { $this->em()->flush(); + $this->messageBus->dispatch(new Envelope(new UpdateNodeTypeSchemaMessage($nodeType->getId()))); $msg = $this->getTranslator()->trans('nodeType.%name%.updated', ['%name%' => $nodeType->getName()]); - $this->publishConfirmMessage($request, $msg, $nodeType); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute('nodeTypesEditPage', [ 'nodeTypeId' => $nodeTypeId @@ -97,40 +103,35 @@ public function editAction(Request $request, int $nodeTypeId): Response } /** + * Return an creation form for requested node-type. + * * @param Request $request * * @return Response - * @throws RuntimeError */ public function addAction(Request $request): Response { $this->denyAccessUnlessGranted('ROLE_ACCESS_NODETYPES'); $nodeType = new NodeType(); - $form = $this->createForm(NodeTypeType::class, $nodeType, [ - 'disabled' => !$this->allowNodeTypeEdition - ]); + $form = $this->createForm(NodeTypeType::class, $nodeType); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - if (!$this->allowNodeTypeEdition) { - $form->addError(new FormError('You cannot create a node-type in production mode.')); - } else { - try { - $this->em()->persist($nodeType); - $this->em()->flush(); - - $this->messageBus->dispatch(new Envelope(new UpdateNodeTypeSchemaMessage($nodeType->getId()))); - - $msg = $this->getTranslator()->trans('nodeType.%name%.created', ['%name%' => $nodeType->getName()]); - $this->publishConfirmMessage($request, $msg, $nodeType); - - return $this->redirectToRoute('nodeTypesEditPage', [ - 'nodeTypeId' => $nodeType->getId() - ]); - } catch (EntityAlreadyExistsException $e) { - $form->addError(new FormError($e->getMessage())); - } + try { + $this->em()->persist($nodeType); + $this->em()->flush(); + + $this->messageBus->dispatch(new Envelope(new UpdateNodeTypeSchemaMessage($nodeType->getId()))); + + $msg = $this->getTranslator()->trans('nodeType.%name%.created', ['%name%' => $nodeType->getName()]); + $this->publishConfirmMessage($request, $msg); + + return $this->redirectToRoute('nodeTypesEditPage', [ + 'nodeTypeId' => $nodeType->getId() + ]); + } catch (EntityAlreadyExistsException $e) { + $form->addError(new FormError($e->getMessage())); } } @@ -142,10 +143,9 @@ public function addAction(Request $request): Response /** * @param Request $request - * @param int $nodeTypeId + * @param int $nodeTypeId * * @return Response - * @throws RuntimeError */ public function deleteAction(Request $request, int $nodeTypeId): Response { @@ -162,16 +162,12 @@ public function deleteAction(Request $request, int $nodeTypeId): Response $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - if (!$this->allowNodeTypeEdition) { - $form->addError(new FormError('You cannot delete a node-type in production mode.')); - } else { - $this->messageBus->dispatch(new Envelope(new DeleteNodeTypeMessage($nodeType->getId()))); + $this->messageBus->dispatch(new Envelope(new DeleteNodeTypeMessage($nodeType->getId()))); - $msg = $this->getTranslator()->trans('nodeType.%name%.deleted', ['%name%' => $nodeType->getName()]); - $this->publishConfirmMessage($request, $msg, $nodeType); + $msg = $this->getTranslator()->trans('nodeType.%name%.deleted', ['%name%' => $nodeType->getName()]); + $this->publishConfirmMessage($request, $msg); - return $this->redirectToRoute('nodeTypesHomePage'); - } + return $this->redirectToRoute('nodeTypesHomePage'); } $this->assignation['form'] = $form->createView(); diff --git a/src/Controllers/NodeTypes/NodeTypesUtilsController.php b/src/Controllers/NodeTypes/NodeTypesUtilsController.php index 059a6b13..f0e656ca 100644 --- a/src/Controllers/NodeTypes/NodeTypesUtilsController.php +++ b/src/Controllers/NodeTypes/NodeTypesUtilsController.php @@ -29,12 +29,21 @@ class NodeTypesUtilsController extends RozierApp { + private SerializerInterface $serializer; + private NodeTypes $nodeTypesBag; + private NodeTypesImporter $nodeTypesImporter; + private MessageBusInterface $messageBus; + public function __construct( - private readonly SerializerInterface $serializer, - private readonly NodeTypes $nodeTypesBag, - private readonly NodeTypesImporter $nodeTypesImporter, - private readonly MessageBusInterface $messageBus + SerializerInterface $serializer, + NodeTypes $nodeTypesBag, + NodeTypesImporter $nodeTypesImporter, + MessageBusInterface $messageBus ) { + $this->serializer = $serializer; + $this->nodeTypesBag = $nodeTypesBag; + $this->nodeTypesImporter = $nodeTypesImporter; + $this->messageBus = $messageBus; } /** @@ -43,9 +52,9 @@ public function __construct( * @param Request $request * @param int $nodeTypeId * - * @return JsonResponse + * @return Response */ - public function exportJsonFileAction(Request $request, int $nodeTypeId): JsonResponse + public function exportJsonFileAction(Request $request, int $nodeTypeId): Response { $this->denyAccessUnlessGranted('ROLE_ACCESS_NODETYPES'); @@ -62,7 +71,7 @@ public function exportJsonFileAction(Request $request, int $nodeTypeId): JsonRes 'json', SerializationContext::create()->setGroups(['node_type', 'position']) ), - Response::HTTP_OK, + JsonResponse::HTTP_OK, [ 'Content-Disposition' => sprintf('attachment; filename="%s"', $nodeType->getName() . '.json'), ], @@ -72,8 +81,8 @@ public function exportJsonFileAction(Request $request, int $nodeTypeId): JsonRes /** * @param Request $request + * * @return BinaryFileResponse - * @throws RuntimeError */ public function exportDocumentationAction(Request $request): BinaryFileResponse { @@ -82,10 +91,6 @@ public function exportDocumentationAction(Request $request): BinaryFileResponse $documentationGenerator = new DocumentationGenerator($this->nodeTypesBag, $this->getTranslator()); $tmpfname = tempnam(sys_get_temp_dir(), date('Y-m-d-H-i-s') . '.zip'); - if (false === $tmpfname) { - throw new RuntimeError('Unable to create temporary file.'); - } - unlink($tmpfname); // Deprecated: ZipArchive::open(): Using empty file as ZipArchive is deprecated $zipArchive = new ZipArchive(); $zipArchive->open($tmpfname, ZipArchive::CREATE); @@ -142,6 +147,10 @@ public function exportTypeScriptDeclarationAction(Request $request): Response return $response; } + /** + * @param Request $request + * @return BinaryFileResponse + */ public function exportAllAction(Request $request): BinaryFileResponse { $this->denyAccessUnlessGranted('ROLE_ACCESS_NODETYPES'); @@ -152,9 +161,6 @@ public function exportAllAction(Request $request): BinaryFileResponse $zipArchive = new ZipArchive(); $tmpfname = tempnam(sys_get_temp_dir(), date('Y-m-d-H-i-s') . '.zip'); - if (false === $tmpfname) { - throw new RuntimeError('Unable to create temporary file.'); - } unlink($tmpfname); // Deprecated: ZipArchive::open(): Using empty file as ZipArchive is deprecated $zipArchive->open($tmpfname, ZipArchive::CREATE); @@ -206,9 +212,6 @@ public function importJsonFileAction(Request $request): Response if ($file->isValid()) { $serializedData = file_get_contents($file->getPathname()); - if (false === $serializedData) { - throw new RuntimeError('Unable to read uploaded file.'); - } if (null !== json_decode($serializedData)) { $this->nodeTypesImporter->import($serializedData); @@ -235,7 +238,7 @@ public function importJsonFileAction(Request $request): Response /** * @return FormInterface */ - private function buildImportJsonFileForm(): FormInterface + private function buildImportJsonFileForm() { $builder = $this->createFormBuilder() ->add('node_type_file', FileType::class, [ diff --git a/src/Controllers/Nodes/ExportController.php b/src/Controllers/Nodes/ExportController.php index 4ba71fd0..61be7980 100644 --- a/src/Controllers/Nodes/ExportController.php +++ b/src/Controllers/Nodes/ExportController.php @@ -4,11 +4,9 @@ namespace Themes\Rozier\Controllers\Nodes; -use PhpOffice\PhpSpreadsheet\Writer\Exception; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\Translation; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use RZ\Roadiz\CoreBundle\Xlsx\NodeSourceXlsxSerializer; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -17,8 +15,14 @@ class ExportController extends RozierApp { - public function __construct(private readonly NodeSourceXlsxSerializer $xlsxSerializer) + private NodeSourceXlsxSerializer $xlsxSerializer; + + /** + * @param NodeSourceXlsxSerializer $xlsxSerializer + */ + public function __construct(NodeSourceXlsxSerializer $xlsxSerializer) { + $this->xlsxSerializer = $xlsxSerializer; } /** @@ -30,10 +34,12 @@ public function __construct(private readonly NodeSourceXlsxSerializer $xlsxSeria * * @return Response * @throws \PhpOffice\PhpSpreadsheet\Exception - * @throws Exception + * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception */ public function exportAllXlsxAction(Request $request, int $translationId, ?int $parentNodeId = null): Response { + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); + /* * Get translation */ @@ -55,11 +61,8 @@ public function exportAllXlsxAction(Request $request, int $translationId, ?int $ if (null === $parentNode) { throw $this->createNotFoundException(); } - $this->denyAccessUnlessGranted(NodeVoter::READ, $parentNode); $criteria['node.parent'] = $parentNode; $filename = $parentNode->getNodeName() . '-' . date("YmdHis") . '.' . $translation->getLocale() . '.xlsx'; - } else { - $this->denyAccessUnlessGranted(NodeVoter::READ_AT_ROOT); } $sources = $this->em() diff --git a/src/Controllers/Nodes/HistoryController.php b/src/Controllers/Nodes/HistoryController.php index 6915d67e..54ef618d 100644 --- a/src/Controllers/Nodes/HistoryController.php +++ b/src/Controllers/Nodes/HistoryController.php @@ -4,19 +4,19 @@ namespace Themes\Rozier\Controllers\Nodes; -use Doctrine\ORM\QueryBuilder; +use RZ\Roadiz\CoreBundle\Entity\Log; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\Translation; -use RZ\Roadiz\CoreBundle\ListManager\QueryBuilderListManager; -use RZ\Roadiz\CoreBundle\ListManager\SessionListFilters; -use RZ\Roadiz\CoreBundle\Logger\Entity\Log; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Themes\Rozier\RozierApp; +use Themes\Rozier\Utils\SessionListFilters; use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers\Nodes + */ class HistoryController extends RozierApp { /** @@ -27,26 +27,21 @@ class HistoryController extends RozierApp */ public function historyAction(Request $request, int $nodeId): Response { + $this->denyAccessUnlessGranted(['ROLE_ACCESS_NODES', 'ROLE_ACCESS_LOGS']); /** @var Node|null $node */ $node = $this->em()->find(Node::class, $nodeId); if (null === $node) { throw new ResourceNotFoundException(); } - $this->denyAccessUnlessGranted(NodeVoter::READ_LOGS, $node); - $qb = $this->em() - ->getRepository(Log::class) - ->getAllRelatedToNodeQueryBuilder($node); - - $listManager = new QueryBuilderListManager($request, $qb, 'obj'); - $listManager->setSearchingCallable(function (QueryBuilder $queryBuilder, string $search) { - $queryBuilder->andWhere($queryBuilder->expr()->orX( - $queryBuilder->expr()->like('obj.message', ':search'), - $queryBuilder->expr()->like('obj.channel', ':search') - )); - $queryBuilder->setParameter('search', '%' . $search . '%'); - }); + $listManager = $this->createEntityListManager( + Log::class, + [ + 'nodeSource' => $node->getNodeSources()->toArray(), + ], + ['datetime' => 'DESC'] + ); $listManager->setDisplayingNotPublishedNodes(true); $listManager->setDisplayingAllNodesStatuses(true); /* diff --git a/src/Controllers/Nodes/NodesAttributesController.php b/src/Controllers/Nodes/NodesAttributesController.php index f1dfe96e..f11dad81 100644 --- a/src/Controllers/Nodes/NodesAttributesController.php +++ b/src/Controllers/Nodes/NodesAttributesController.php @@ -9,13 +9,10 @@ use RZ\Roadiz\CoreBundle\Entity\AttributeValueTranslation; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodesSources; -use RZ\Roadiz\CoreBundle\Entity\NodeType; use RZ\Roadiz\CoreBundle\Entity\Translation; use RZ\Roadiz\CoreBundle\Event\NodesSources\NodesSourcesUpdatedEvent; use RZ\Roadiz\CoreBundle\Form\AttributeValueTranslationType; use RZ\Roadiz\CoreBundle\Form\AttributeValueType; -use RZ\Roadiz\CoreBundle\Form\Error\FormErrorSerializer; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\JsonResponse; @@ -27,10 +24,14 @@ class NodesAttributesController extends RozierApp { - public function __construct( - private readonly FormFactoryInterface $formFactory, - private readonly FormErrorSerializer $formErrorSerializer - ) { + private FormFactoryInterface $formFactory; + + /** + * @param FormFactoryInterface $formFactory + */ + public function __construct(FormFactoryInterface $formFactory) + { + $this->formFactory = $formFactory; } /** @@ -43,6 +44,8 @@ public function __construct( */ public function editAction(Request $request, int $nodeId, int $translationId): Response { + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODE_ATTRIBUTES'); + /** @var Translation|null $translation */ $translation = $this->em()->find(Translation::class, $translationId); /** @var Node|null $node */ @@ -52,8 +55,6 @@ public function editAction(Request $request, int $nodeId, int $translationId): R throw $this->createNotFoundException('Node-source does not exist'); } - $this->denyAccessUnlessGranted(NodeVoter::EDIT_ATTRIBUTE, $node); - /** @var NodesSources|null $nodeSource */ $nodeSource = $this->em() ->getRepository(NodesSources::class) @@ -65,32 +66,12 @@ public function editAction(Request $request, int $nodeId, int $translationId): R throw $this->createNotFoundException('Node-source does not exist'); } - if (!$this->isAttributable($node)) { - throw $this->createNotFoundException('Node type is not attributable'); - } - if (null !== $response = $this->handleAddAttributeForm($request, $node, $translation)) { return $response; } - $isJson = - $request->isXmlHttpRequest() || - $request->getRequestFormat('html') === 'json' || - \in_array( - 'application/json', - $request->getAcceptableContentTypes() - ); - $this->assignation['attribute_value_translation_forms'] = []; - $nodeType = $node->getNodeType(); - $orderByWeight = false; - if ($nodeType instanceof NodeType) { - $orderByWeight = $nodeType->isSortingAttributesByWeight(); - } - $attributeValues = $this->em()->getRepository(AttributeValue::class)->findByAttributable( - $node, - $orderByWeight - ); + $attributeValues = $node->getAttributeValues(); /** @var AttributeValue $attributeValue */ foreach ($attributeValues as $attributeValue) { $name = $node->getNodeName() . '_attribute_' . $attributeValue->getId(); @@ -126,27 +107,27 @@ public function editAction(Request $request, int $nodeId, int $translationId): R ); $this->publishConfirmMessage($request, $msg, $nodeSource); - if ($isJson) { + if ($request->isXmlHttpRequest() || $request->getRequestFormat('html') === 'json') { return new JsonResponse([ 'status' => 'success', 'message' => $msg, - ], Response::HTTP_ACCEPTED); + ], JsonResponse::HTTP_ACCEPTED); } return $this->redirectToRoute('nodesEditAttributesPage', [ 'nodeId' => $node->getId(), 'translationId' => $translation->getId(), ]); } else { - $errors = $this->formErrorSerializer->getErrorsAsArray($attributeValueTranslationForm); + $errors = $this->getErrorsAsArray($attributeValueTranslationForm); /* * Handle errors when Ajax POST requests */ - if ($isJson) { + if ($request->isXmlHttpRequest() || $request->getRequestFormat('html') === 'json') { return new JsonResponse([ 'status' => 'fail', 'errors' => $errors, 'message' => $this->getTranslator()->trans('form_has_errors.check_you_fields'), - ], Response::HTTP_BAD_REQUEST); + ], JsonResponse::HTTP_BAD_REQUEST); } foreach ($errors as $error) { $this->publishErrorMessage($request, $error); @@ -159,7 +140,6 @@ public function editAction(Request $request, int $nodeId, int $translationId): R $this->assignation['source'] = $nodeSource; $this->assignation['translation'] = $translation; - $this->assignation['order_by_weight'] = $orderByWeight; $availableTranslations = $this->em() ->getRepository(Translation::class) ->findAvailableTranslationsForNode($node); @@ -174,15 +154,6 @@ protected function hasAttributes(): bool return $this->em()->getRepository(Attribute::class)->countBy([]) > 0; } - protected function isAttributable(Node $node): bool - { - $nodeType = $node->getNodeType(); - if ($nodeType instanceof NodeType) { - return $nodeType->isAttributable(); - } - return false; - } - /** * @param Request $request * @param Node $node @@ -192,9 +163,6 @@ protected function isAttributable(Node $node): bool */ protected function handleAddAttributeForm(Request $request, Node $node, Translation $translation): ?RedirectResponse { - if (!$this->isAttributable($node)) { - return null; - } if (!$this->hasAttributes()) { return null; } @@ -233,15 +201,16 @@ protected function handleAddAttributeForm(Request $request, Node $node, Translat /** * @param Request $request - * @param int $nodeId - * @param int $translationId - * @param int $attributeValueId + * @param int $nodeId + * @param int $translationId + * @param int $attributeValueId * * @return Response - * @throws RuntimeError */ - public function deleteAction(Request $request, int $nodeId, int $translationId, int $attributeValueId): Response + public function deleteAction(Request $request, $nodeId, $translationId, $attributeValueId): Response { + $this->denyAccessUnlessGranted('ROLE_ACCESS_ATTRIBUTES_DELETE'); + /** @var AttributeValue|null $item */ $item = $this->em()->find(AttributeValue::class, $attributeValueId); if ($item === null) { @@ -256,8 +225,6 @@ public function deleteAction(Request $request, int $nodeId, int $translationId, throw $this->createNotFoundException('Node-source does not exist'); } - $this->denyAccessUnlessGranted(NodeVoter::EDIT_ATTRIBUTE, $node); - /** @var NodesSources|null $nodeSource */ $nodeSource = $this->em() ->getRepository(NodesSources::class) @@ -284,9 +251,9 @@ public function deleteAction(Request $request, int $nodeId, int $translationId, '%nodeName%' => $nodeSource->getTitle(), ] ); - $this->publishConfirmMessage($request, $msg, $item); + $this->publishConfirmMessage($request, $msg); } catch (\RuntimeException $e) { - $this->publishErrorMessage($request, $e->getMessage(), $item); + $this->publishErrorMessage($request, $e->getMessage()); } return $this->redirectToRoute('nodesEditAttributesPage', [ @@ -310,10 +277,11 @@ public function deleteAction(Request $request, int $nodeId, int $translationId, * @param int $translationId * @param int $attributeValueId * @return Response - * @throws RuntimeError */ public function resetAction(Request $request, int $nodeId, int $translationId, int $attributeValueId): Response { + $this->denyAccessUnlessGranted('ROLE_ACCESS_ATTRIBUTES_DELETE'); + /** @var AttributeValueTranslation|null $item */ $item = $this->em() ->getRepository(AttributeValueTranslation::class) @@ -333,8 +301,6 @@ public function resetAction(Request $request, int $nodeId, int $translationId, i throw $this->createNotFoundException('Node-source does not exist'); } - $this->denyAccessUnlessGranted(NodeVoter::EDIT_ATTRIBUTE, $node); - /** @var NodesSources|null $nodeSource */ $nodeSource = $this->em() ->getRepository(NodesSources::class) @@ -361,9 +327,9 @@ public function resetAction(Request $request, int $nodeId, int $translationId, i '%nodeName%' => $nodeSource->getTitle(), ] ); - $this->publishConfirmMessage($request, $msg, $item); + $this->publishConfirmMessage($request, $msg); } catch (\RuntimeException $e) { - $this->publishErrorMessage($request, $e->getMessage(), $item); + $this->publishErrorMessage($request, $e->getMessage()); } return $this->redirectToRoute('nodesEditAttributesPage', [ diff --git a/src/Controllers/Nodes/NodesController.php b/src/Controllers/Nodes/NodesController.php index 482e097f..e304e24f 100644 --- a/src/Controllers/Nodes/NodesController.php +++ b/src/Controllers/Nodes/NodesController.php @@ -4,8 +4,6 @@ namespace Themes\Rozier\Controllers\Nodes; -use Doctrine\ORM\OptimisticLockException; -use Doctrine\ORM\ORMException; use RZ\Roadiz\Core\Handlers\HandlerFactoryInterface; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodeType; @@ -18,14 +16,11 @@ use RZ\Roadiz\CoreBundle\Event\Node\NodeUndeletedEvent; use RZ\Roadiz\CoreBundle\Event\Node\NodeUpdatedEvent; use RZ\Roadiz\CoreBundle\Exception\EntityAlreadyExistsException; -use RZ\Roadiz\CoreBundle\ListManager\SessionListFilters; use RZ\Roadiz\CoreBundle\Node\Exception\SameNodeUrlException; use RZ\Roadiz\CoreBundle\Node\NodeFactory; use RZ\Roadiz\CoreBundle\Node\NodeMover; -use RZ\Roadiz\CoreBundle\Node\NodeOffspringResolverInterface; use RZ\Roadiz\CoreBundle\Node\UniqueNodeGenerator; use RZ\Roadiz\CoreBundle\Security\Authorization\Chroot\NodeChrootResolver; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormError; @@ -37,12 +32,31 @@ use Symfony\Component\Workflow\Registry; use Themes\Rozier\RozierApp; use Themes\Rozier\Traits\NodesTrait; +use Themes\Rozier\Utils\SessionListFilters; use Twig\Error\RuntimeError; -final class NodesController extends RozierApp +/** + * @package Themes\Rozier\Controllers\Nodes + */ +class NodesController extends RozierApp { use NodesTrait; + private NodeChrootResolver $nodeChrootResolver; + private NodeMover $nodeMover; + private Registry $workflowRegistry; + private HandlerFactoryInterface $handlerFactory; + private UniqueNodeGenerator $uniqueNodeGenerator; + private NodeFactory $nodeFactory; + /** + * @var class-string + */ + private string $nodeFormTypeClass; + /** + * @var class-string + */ + private string $addNodeFormTypeClass; + /** * @param NodeChrootResolver $nodeChrootResolver * @param NodeMover $nodeMover @@ -50,21 +64,27 @@ final class NodesController extends RozierApp * @param HandlerFactoryInterface $handlerFactory * @param UniqueNodeGenerator $uniqueNodeGenerator * @param NodeFactory $nodeFactory - * @param NodeOffspringResolverInterface $nodeOffspringResolver * @param class-string $nodeFormTypeClass * @param class-string $addNodeFormTypeClass */ public function __construct( - private readonly NodeChrootResolver $nodeChrootResolver, - private readonly NodeMover $nodeMover, - private readonly Registry $workflowRegistry, - private readonly HandlerFactoryInterface $handlerFactory, - private readonly UniqueNodeGenerator $uniqueNodeGenerator, - private readonly NodeFactory $nodeFactory, - private readonly NodeOffspringResolverInterface $nodeOffspringResolver, - private readonly string $nodeFormTypeClass, - private readonly string $addNodeFormTypeClass + NodeChrootResolver $nodeChrootResolver, + NodeMover $nodeMover, + Registry $workflowRegistry, + HandlerFactoryInterface $handlerFactory, + UniqueNodeGenerator $uniqueNodeGenerator, + NodeFactory $nodeFactory, + string $nodeFormTypeClass, + string $addNodeFormTypeClass ) { + $this->nodeChrootResolver = $nodeChrootResolver; + $this->nodeMover = $nodeMover; + $this->workflowRegistry = $workflowRegistry; + $this->handlerFactory = $handlerFactory; + $this->nodeFormTypeClass = $nodeFormTypeClass; + $this->addNodeFormTypeClass = $addNodeFormTypeClass; + $this->uniqueNodeGenerator = $uniqueNodeGenerator; + $this->nodeFactory = $nodeFactory; } protected function getNodeFactory(): NodeFactory @@ -168,14 +188,14 @@ public function indexAction(Request $request, ?string $filter = null): Response */ public function editAction(Request $request, int $nodeId, ?int $translationId = null): Response { + $this->validateNodeAccessForRole('ROLE_ACCESS_NODES_SETTING', $nodeId); + /** @var Node|null $node */ $node = $this->em()->find(Node::class, $nodeId); if (null === $node) { throw new ResourceNotFoundException(sprintf('Node #%s does not exist.', $nodeId)); } - $this->denyAccessUnlessGranted(NodeVoter::EDIT_SETTING, $node); - $this->em()->refresh($node); /* * Handle StackTypes form @@ -193,7 +213,7 @@ public function editAction(Request $request, int $nodeId, ?int $translationId = '%type%' => $type->getDisplayName(), ] ); - $this->publishConfirmMessage($request, $msg, $node); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute( 'nodesEditPage', ['nodeId' => $node->getId()] @@ -234,7 +254,7 @@ public function editAction(Request $request, int $nodeId, ?int $translationId = $msg = $this->getTranslator()->trans('node.%name%.updated', [ '%name%' => $node->getNodeName(), ]); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); return $this->redirectToRoute( 'nodesEditPage', ['nodeId' => $node->getId()] @@ -269,13 +289,13 @@ public function editAction(Request $request, int $nodeId, ?int $translationId = */ public function removeStackTypeAction(Request $request, int $nodeId, int $typeId): Response { + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); + /** @var Node|null $node */ $node = $this->em()->find(Node::class, $nodeId); if (null === $node) { throw new ResourceNotFoundException(sprintf('Node #%s does not exist.', $nodeId)); } - $this->denyAccessUnlessGranted(NodeVoter::EDIT_SETTING, $node); - /** @var NodeType|null $type */ $type = $this->em()->find(NodeType::class, $typeId); if (null === $type) { @@ -292,7 +312,7 @@ public function removeStackTypeAction(Request $request, int $nodeId, int $typeId '%type%' => $type->getDisplayName(), ] ); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: null); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); return $this->redirectToRoute('nodesEditPage', ['nodeId' => $node->getId()]); } @@ -301,13 +321,12 @@ public function removeStackTypeAction(Request $request, int $nodeId, int $typeId * Handle node creation pages. * * @param Request $request - * @param int $nodeTypeId + * @param int $nodeTypeId * @param int|null $translationId * * @return Response - * @throws RuntimeError - * @throws ORMException - * @throws OptimisticLockException + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function addAction(Request $request, int $nodeTypeId, ?int $translationId = null): Response { @@ -329,9 +348,7 @@ public function addAction(Request $request, int $nodeTypeId, ?int $translationId throw new ResourceNotFoundException(sprintf('Translation #%s does not exist.', $translationId)); } - $node = new Node(); - $node->setNodeType($type); - $node->setTtl($type->getDefaultTtl()); + $node = new Node($type); $chroot = $this->nodeChrootResolver->getChroot($this->getUser()); if (null !== $chroot) { @@ -357,7 +374,7 @@ public function addAction(Request $request, int $nodeTypeId, ?int $translationId 'node.%name%.created', ['%name%' => $node->getNodeName()] ); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: null); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); return $this->redirectToRoute( 'nodesEditSourcePage', @@ -389,12 +406,15 @@ public function addAction(Request $request, int $nodeTypeId, ?int $translationId * @param int|null $translationId * * @return Response - * @throws ORMException - * @throws OptimisticLockException - * @throws RuntimeError + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + * @throws \Twig\Error\RuntimeError */ public function addChildAction(Request $request, ?int $nodeId = null, ?int $translationId = null): Response { + // include CHRoot to enable creating node in it + $this->validateNodeAccessForRole('ROLE_ACCESS_NODES', $nodeId, true); + /** @var Translation|null $translation */ $translation = $this->em()->getRepository(Translation::class)->findDefault(); @@ -408,19 +428,15 @@ public function addChildAction(Request $request, ?int $nodeId = null, ?int $tran } if (null === $translation) { - throw new ResourceNotFoundException('Translation does not exist'); + throw new ResourceNotFoundException(sprintf('Translation does not exist')); } if (null !== $nodeId && $nodeId > 0) { - /** @var Node|null $parentNode */ - $parentNode = $this->em()->find(Node::class, $nodeId); - if (null === $parentNode) { - throw new ResourceNotFoundException(sprintf('Node #%s does not exist.', $nodeId)); - } - $this->denyAccessUnlessGranted(NodeVoter::CREATE, $parentNode); + /** @var Node $parentNode */ + $parentNode = $this->em() + ->find(Node::class, $nodeId); } else { $parentNode = null; - $this->denyAccessUnlessGranted(NodeVoter::CREATE_AT_ROOT); } $node = new Node(); @@ -447,7 +463,7 @@ public function addChildAction(Request $request, ?int $nodeId = null, ?int $tran 'child_node.%name%.created', ['%name%' => $node->getNodeName()] ); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: null); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); return $this->redirectToRoute( 'nodesEditSourcePage', @@ -478,10 +494,12 @@ public function addChildAction(Request $request, ?int $nodeId = null, ?int $tran * @param int $nodeId * * @return Response - * @throws RuntimeError + * @throws \Twig\Error\RuntimeError */ public function deleteAction(Request $request, int $nodeId): Response { + $this->validateNodeAccessForRole('ROLE_ACCESS_NODES_DELETE', $nodeId); + /** @var Node|null $node */ $node = $this->em()->find(Node::class, $nodeId); @@ -489,11 +507,9 @@ public function deleteAction(Request $request, int $nodeId): Response throw new ResourceNotFoundException(sprintf('Node #%s does not exist.', $nodeId)); } - $this->denyAccessUnlessGranted(NodeVoter::DELETE, $node); - $workflow = $this->workflowRegistry->get($node); if (!$workflow->can($node, 'delete')) { - $this->publishErrorMessage($request, sprintf('Node #%s cannot be deleted.', $nodeId), $node); + $this->publishErrorMessage($request, sprintf('Node #%s cannot be deleted.', $nodeId)); return $this->redirectToRoute( 'nodesEditSourcePage', [ @@ -528,14 +544,13 @@ public function deleteAction(Request $request, int $nodeId): Response 'node.%name%.deleted', ['%name%' => $node->getNodeName()] ); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); - $referrer = $request->query->get('referer'); if ( - \is_string($referrer) && - (new UnicodeString($referrer))->trim()->startsWith('/') + $request->query->has('referer') && + (new UnicodeString($request->query->get('referer')))->startsWith('/') ) { - return $this->redirect($referrer); + return $this->redirect($request->query->get('referer')); } if (null !== $parent) { return $this->redirectToRoute( @@ -558,11 +573,11 @@ public function deleteAction(Request $request, int $nodeId): Response * @param Request $request * * @return Response - * @throws RuntimeError + * @throws \Twig\Error\RuntimeError */ public function emptyTrashAction(Request $request): Response { - $this->denyAccessUnlessGranted(NodeVoter::EMPTY_TRASH); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES_DELETE'); $form = $this->buildEmptyTrashForm(); $form->handleRequest($request); @@ -572,7 +587,10 @@ public function emptyTrashAction(Request $request): Response /** @var Node|null $chroot */ $chroot = $this->nodeChrootResolver->getChroot($this->getUser()); if ($chroot !== null) { - $criteria["parent"] = $this->nodeOffspringResolver->getAllOffspringIds($chroot); + /** @var NodeHandler $nodeHandler */ + $nodeHandler = $this->handlerFactory->getHandler($chroot); + $ids = $nodeHandler->getAllOffspringId(); + $criteria["parent"] = $ids; } $nodes = $this->em() @@ -610,10 +628,12 @@ public function emptyTrashAction(Request $request): Response * @param int $nodeId * * @return Response - * @throws RuntimeError + * @throws \Twig\Error\RuntimeError */ public function undeleteAction(Request $request, int $nodeId): Response { + $this->validateNodeAccessForRole('ROLE_ACCESS_NODES_DELETE', $nodeId); + /** @var Node|null $node */ $node = $this->em()->find(Node::class, $nodeId); @@ -621,11 +641,9 @@ public function undeleteAction(Request $request, int $nodeId): Response throw new ResourceNotFoundException(sprintf('Node #%s does not exist.', $nodeId)); } - $this->denyAccessUnlessGranted(NodeVoter::DELETE, $node); - $workflow = $this->workflowRegistry->get($node); if (!$workflow->can($node, 'undelete')) { - $this->publishErrorMessage($request, sprintf('Node #%s cannot be undeleted.', $nodeId), $node); + $this->publishErrorMessage($request, sprintf('Node #%s cannot be undeleted.', $nodeId)); return $this->redirectToRoute( 'nodesEditSourcePage', [ @@ -651,7 +669,7 @@ public function undeleteAction(Request $request, int $nodeId): Response 'node.%name%.undeleted', ['%name%' => $node->getNodeName()] ); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); /* * Force redirect to avoid resending form when refreshing page */ @@ -694,26 +712,24 @@ public function generateAndAddNodeAction(Request $request): RedirectResponse throw new ResourceNotFoundException($msg); } } - /** - * @param Request $request - * @param int $nodeId + * @param Request $request + * @param int $nodeId * @return Response - * @throws RuntimeError */ public function publishAllAction(Request $request, int $nodeId): Response { + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES_STATUS'); /** @var Node|null $node */ $node = $this->em()->find(Node::class, $nodeId); if (null === $node) { throw new ResourceNotFoundException(sprintf('Node #%s does not exist.', $nodeId)); } - $this->denyAccessUnlessGranted(NodeVoter::EDIT_STATUS, $node); $workflow = $this->workflowRegistry->get($node); if (!$workflow->can($node, 'publish')) { - $this->publishErrorMessage($request, sprintf('Node #%s cannot be published.', $nodeId), $node); + $this->publishErrorMessage($request, sprintf('Node #%s cannot be published.', $nodeId)); return $this->redirectToRoute( 'nodesEditSourcePage', [ @@ -732,13 +748,11 @@ public function publishAllAction(Request $request, int $nodeId): Response $this->em()->flush(); $msg = $this->getTranslator()->trans('node.offspring.published'); - $this->publishConfirmMessage($request, $msg, $node); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute('nodesEditSourcePage', [ 'nodeId' => $nodeId, - 'translationId' => $node->getNodeSources()->first() ? - $node->getNodeSources()->first()->getTranslation()->getId() : - null, + 'translationId' => $node->getNodeSources()->first()->getTranslation()->getId(), ]); } diff --git a/src/Controllers/Nodes/NodesSourcesController.php b/src/Controllers/Nodes/NodesSourcesController.php index 980ed5f3..84012609 100644 --- a/src/Controllers/Nodes/NodesSourcesController.php +++ b/src/Controllers/Nodes/NodesSourcesController.php @@ -13,7 +13,6 @@ use RZ\Roadiz\CoreBundle\Event\NodesSources\NodesSourcesUpdatedEvent; use RZ\Roadiz\CoreBundle\Form\Error\FormErrorSerializer; use RZ\Roadiz\CoreBundle\Routing\NodeRouter; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use RZ\Roadiz\CoreBundle\TwigExtension\JwtExtension; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\FormError; @@ -34,10 +33,13 @@ class NodesSourcesController extends RozierApp { use VersionedControllerTrait; - public function __construct( - private readonly JwtExtension $jwtExtension, - private readonly FormErrorSerializer $formErrorSerializer - ) { + private JwtExtension $jwtExtension; + private FormErrorSerializer $formErrorSerializer; + + public function __construct(JwtExtension $jwtExtension, FormErrorSerializer $formErrorSerializer) + { + $this->jwtExtension = $jwtExtension; + $this->formErrorSerializer = $formErrorSerializer; } /** @@ -52,6 +54,8 @@ public function __construct( */ public function editSourceAction(Request $request, int $nodeId, int $translationId): Response { + $this->validateNodeAccessForRole('ROLE_ACCESS_NODES', $nodeId); + /** @var Translation|null $translation */ $translation = $this->em()->find(Translation::class, $translationId); @@ -69,8 +73,6 @@ public function editSourceAction(Request $request, int $nodeId, int $translation throw new ResourceNotFoundException('Node does not exist'); } - $this->denyAccessUnlessGranted(NodeVoter::EDIT_CONTENT, $gNode); - /** @var NodesSources|null $source */ $source = $this->em() ->getRepository(NodesSources::class) @@ -107,16 +109,12 @@ public function editSourceAction(Request $request, int $nodeId, int $translation ] ); $form->handleRequest($request); - $isJsonRequest = - $request->isXmlHttpRequest() || - \in_array('application/json', $request->getAcceptableContentTypes()) - ; if ($form->isSubmitted()) { if ($form->isValid() && !$this->isReadOnly) { $this->onPostUpdate($source, $request); - if (!$isJsonRequest) { + if (!$request->isXmlHttpRequest()) { return $this->getPostUpdateRedirection($source); } @@ -169,7 +167,7 @@ public function editSourceAction(Request $request, int $nodeId, int $translation /* * Handle errors when Ajax POST requests */ - if ($isJsonRequest) { + if ($request->isXmlHttpRequest()) { $errors = $this->formErrorSerializer->getErrorsAsArray($form); return new JsonResponse([ 'status' => 'fail', @@ -209,10 +207,12 @@ public function removeAction(Request $request, int $nodeSourceId): Response if (null === $ns) { throw new ResourceNotFoundException('Node source does not exist'); } - $this->denyAccessUnlessGranted(NodeVoter::DELETE, $ns); + /** @var Node $node */ $node = $ns->getNode(); $this->em()->refresh($ns->getNode()); + $this->validateNodeAccessForRole('ROLE_ACCESS_NODES_DELETE', $node->getId()); + /* * Prevent deleting last node-source available in node. */ @@ -238,6 +238,7 @@ public function removeAction(Request $request, int $nodeSourceId): Response $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { + /** @var Node $node */ $node = $ns->getNode(); /* * Dispatch event @@ -247,18 +248,14 @@ public function removeAction(Request $request, int $nodeSourceId): Response $this->em()->remove($ns); $this->em()->flush(); - $ns = $node->getNodeSources()->first() ?: null; - - if (null === $ns) { - throw new ResourceNotFoundException('No more node-source available for this node.'); - } + $ns = $node->getNodeSources()->first(); $msg = $this->getTranslator()->trans('node_source.%node_source%.deleted.%translation%', [ '%node_source%' => $node->getNodeName(), '%translation%' => $ns->getTranslation()->getName(), ]); - $this->publishConfirmMessage($request, $msg, $node); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute( 'nodesEditSourcePage', diff --git a/src/Controllers/Nodes/NodesTagsController.php b/src/Controllers/Nodes/NodesTagsController.php new file mode 100644 index 00000000..87f69398 --- /dev/null +++ b/src/Controllers/Nodes/NodesTagsController.php @@ -0,0 +1,104 @@ +nodeFactory = $nodeFactory; + } + + protected function getNodeFactory(): NodeFactory + { + return $this->nodeFactory; + } + + /** + * Return tags form for requested node. + * + * @param Request $request + * @param int $nodeId + * + * @return Response + * @throws \Twig\Error\RuntimeError + */ + public function editTagsAction(Request $request, int $nodeId): Response + { + $this->validateNodeAccessForRole('ROLE_ACCESS_NODES', $nodeId); + + /** @var NodesSources|null $source */ + $source = $this->em() + ->getRepository(NodesSources::class) + ->setDisplayingAllNodesStatuses(true) + ->setDisplayingNotPublishedNodes(true) + ->findOneBy([ + 'node.id' => $nodeId, + 'translation' => $this->em()->getRepository(Translation::class)->findDefault() + ]); + if (null === $source) { + /** @var NodesSources|null $source */ + $source = $this->em() + ->getRepository(NodesSources::class) + ->setDisplayingAllNodesStatuses(true) + ->setDisplayingNotPublishedNodes(true) + ->findOneBy([ + 'node.id' => $nodeId, + ]); + } + + if (null !== $source) { + $node = $source->getNode(); + $form = $this->createForm(NodeTagsType::class, $node); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /* + * Dispatch event + */ + $this->dispatchEvent(new NodeTaggedEvent($node)); + $this->em()->flush(); + + $msg = $this->getTranslator()->trans('node.%node%.linked.tags', [ + '%node%' => $node->getNodeName(), + ]); + $this->publishConfirmMessage($request, $msg, $source); + + return $this->redirectToRoute( + 'nodesEditTagsPage', + ['nodeId' => $node->getId()] + ); + } + + $this->assignation['translation'] = $source->getTranslation(); + $this->assignation['node'] = $node; + $this->assignation['source'] = $source; + $this->assignation['form'] = $form->createView(); + + return $this->render('@RoadizRozier/nodes/editTags.html.twig', $this->assignation); + } + + throw new ResourceNotFoundException(); + } +} diff --git a/src/Controllers/Nodes/NodesTreesController.php b/src/Controllers/Nodes/NodesTreesController.php index 7604d5b6..0e2f157f 100644 --- a/src/Controllers/Nodes/NodesTreesController.php +++ b/src/Controllers/Nodes/NodesTreesController.php @@ -10,7 +10,6 @@ use RZ\Roadiz\CoreBundle\Entity\Translation; use RZ\Roadiz\CoreBundle\EntityHandler\NodeHandler; use RZ\Roadiz\CoreBundle\Security\Authorization\Chroot\NodeChrootResolver; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\Form\ClickableInterface; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\HiddenType; @@ -28,17 +27,37 @@ use Symfony\Component\Workflow\Registry; use Themes\Rozier\RozierApp; use Themes\Rozier\Widgets\TreeWidgetFactory; -use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers\Nodes + */ class NodesTreesController extends RozierApp { + private NodeChrootResolver $nodeChrootResolver; + private TreeWidgetFactory $treeWidgetFactory; + private FormFactoryInterface $formFactory; + private HandlerFactoryInterface $handlerFactory; + private Registry $workflowRegistry; + + /** + * @param NodeChrootResolver $nodeChrootResolver + * @param TreeWidgetFactory $treeWidgetFactory + * @param FormFactoryInterface $formFactory + * @param HandlerFactoryInterface $handlerFactory + * @param Registry $workflowRegistry + */ public function __construct( - private readonly NodeChrootResolver $nodeChrootResolver, - private readonly TreeWidgetFactory $treeWidgetFactory, - private readonly FormFactoryInterface $formFactory, - private readonly HandlerFactoryInterface $handlerFactory, - private readonly Registry $workflowRegistry + NodeChrootResolver $nodeChrootResolver, + TreeWidgetFactory $treeWidgetFactory, + FormFactoryInterface $formFactory, + HandlerFactoryInterface $handlerFactory, + Registry $workflowRegistry ) { + $this->nodeChrootResolver = $nodeChrootResolver; + $this->treeWidgetFactory = $treeWidgetFactory; + $this->formFactory = $formFactory; + $this->handlerFactory = $handlerFactory; + $this->workflowRegistry = $workflowRegistry; } /** @@ -47,29 +66,25 @@ public function __construct( * @param int|null $translationId * * @return Response - * @throws RuntimeError */ - public function treeAction(Request $request, ?int $nodeId = null, ?int $translationId = null): Response + public function treeAction(Request $request, ?int $nodeId = null, ?int $translationId = null) { if (null !== $nodeId) { + $this->validateNodeAccessForRole('ROLE_ACCESS_NODES', $nodeId, true); /** @var Node|null $node */ $node = $this->em()->find(Node::class, $nodeId); + if (null === $node) { throw new ResourceNotFoundException(); } + $this->em()->refresh($node); - } elseif (null !== $user = $this->getUser()) { - $node = $this->nodeChrootResolver->getChroot($user); + } elseif (null !== $this->getUser()) { + $node = $this->nodeChrootResolver->getChroot($this->getUser()); } else { $node = null; } - if (null !== $node) { - $this->denyAccessUnlessGranted(NodeVoter::READ, $node); - } else { - $this->denyAccessUnlessGranted(NodeVoter::READ_AT_ROOT); - } - if (null !== $translationId) { /** @var Translation $translation */ $translation = $this->em() @@ -161,131 +176,120 @@ public function treeAction(Request $request, ?int $nodeId = null, ?int $translat /** * @param Request $request * @return Response - * @throws RuntimeError */ - public function bulkDeleteAction(Request $request): Response + public function bulkDeleteAction(Request $request) { - if (empty($request->get('deleteForm')['nodesIds'])) { - throw new ResourceNotFoundException(); - } + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES_DELETE'); - $nodesIds = trim($request->get('deleteForm')['nodesIds']); - $nodesIds = explode(',', $nodesIds); - array_filter($nodesIds); + if (!empty($request->get('deleteForm')['nodesIds'])) { + $nodesIds = trim($request->get('deleteForm')['nodesIds']); + $nodesIds = explode(',', $nodesIds); + array_filter($nodesIds); - /** @var Node[] $nodes */ - $nodes = $this->em() - ->getRepository(Node::class) - ->setDisplayingNotPublishedNodes(true) - ->findBy([ - 'id' => $nodesIds, - ]); + /** @var Node[] $nodes */ + $nodes = $this->em() + ->getRepository(Node::class) + ->setDisplayingNotPublishedNodes(true) + ->findBy([ + 'id' => $nodesIds, + ]); - if (count($nodes) === 0) { - throw new ResourceNotFoundException(); - } + if (count($nodes) > 0) { + $form = $this->buildBulkDeleteForm( + $request->get('deleteForm')['referer'], + $nodesIds + ); + $form->handleRequest($request); + if ($request->get('confirm') && $form->isSubmitted() && $form->isValid()) { + $msg = $this->bulkDeleteNodes($form->getData()); + $this->publishConfirmMessage($request, $msg); + + if (!empty($form->getData()['referer'])) { + return $this->redirect($form->getData()['referer']); + } else { + return $this->redirectToRoute('nodesHomePage'); + } + } - foreach ($nodes as $node) { - $this->denyAccessUnlessGranted(NodeVoter::DELETE, $node); - } + $this->assignation['nodes'] = $nodes; + $this->assignation['form'] = $form->createView(); - $form = $this->buildBulkDeleteForm( - $request->get('deleteForm')['referer'], - $nodesIds - ); - $form->handleRequest($request); - if ($request->get('confirm') && $form->isSubmitted() && $form->isValid()) { - $msg = $this->bulkDeleteNodes($form->getData()); - $this->publishConfirmMessage($request, $msg); + if (!empty($request->get('deleteForm')['referer'])) { + $this->assignation['referer'] = $request->get('deleteForm')['referer']; + } - if (!empty($form->getData()['referer'])) { - return $this->redirect($form->getData()['referer']); - } else { - return $this->redirectToRoute('nodesHomePage'); + return $this->render('@RoadizRozier/nodes/bulkDelete.html.twig', $this->assignation); } } - $this->assignation['nodes'] = $nodes; - $this->assignation['form'] = $form->createView(); - - if (!empty($request->get('deleteForm')['referer'])) { - $this->assignation['referer'] = $request->get('deleteForm')['referer']; - } - - return $this->render('@RoadizRozier/nodes/bulkDelete.html.twig', $this->assignation); + throw new ResourceNotFoundException(); } /** * @param Request $request * * @return Response - * @throws RuntimeError */ - public function bulkStatusAction(Request $request): Response + public function bulkStatusAction(Request $request) { - if (empty($request->get('statusForm')['nodesIds'])) { - throw new ResourceNotFoundException(); - } + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES_STATUS'); - $nodesIds = trim($request->get('statusForm')['nodesIds']); - $nodesIds = explode(',', $nodesIds); - array_filter($nodesIds); + if (!empty($request->get('statusForm')['nodesIds'])) { + $nodesIds = trim($request->get('statusForm')['nodesIds']); + $nodesIds = explode(',', $nodesIds); + array_filter($nodesIds); - /** @var Node[] $nodes */ - $nodes = $this->em() - ->getRepository(Node::class) - ->setDisplayingNotPublishedNodes(true) - ->findBy([ - 'id' => $nodesIds, - ]); + /** @var Node[] $nodes */ + $nodes = $this->em() + ->getRepository(Node::class) + ->setDisplayingNotPublishedNodes(true) + ->findBy([ + 'id' => $nodesIds, + ]); + if (count($nodes) > 0) { + $form = $this->buildBulkStatusForm( + $request->get('statusForm')['referer'], + $nodesIds, + (string) $request->get('statusForm')['status'] + ); - if (count($nodes) === 0) { - throw new ResourceNotFoundException(); - } + $form->handleRequest($request); - foreach ($nodes as $node) { - $this->denyAccessUnlessGranted(NodeVoter::EDIT_STATUS, $node); - } + if ($form->isSubmitted() && $form->isValid()) { + $msg = $this->bulkStatusNodes($form->getData()); + $this->publishConfirmMessage($request, $msg); - $form = $this->buildBulkStatusForm( - $request->get('statusForm')['referer'], - $nodesIds, - (string) $request->get('statusForm')['status'] - ); + if (!empty($form->getData()['referer'])) { + return $this->redirect($form->getData()['referer']); + } else { + return $this->redirectToRoute('nodesHomePage'); + } + } - $form->handleRequest($request); + $this->assignation['nodes'] = $nodes; + $this->assignation['form'] = $form->createView(); - if ($form->isSubmitted() && $form->isValid()) { - $msg = $this->bulkStatusNodes($form->getData()); - $this->publishConfirmMessage($request, $msg); + if (!empty($request->get('statusForm')['referer'])) { + $this->assignation['referer'] = $request->get('statusForm')['referer']; + } - if (!empty($form->getData()['referer'])) { - return $this->redirect($form->getData()['referer']); - } else { - return $this->redirectToRoute('nodesHomePage'); + return $this->render('@RoadizRozier/nodes/bulkStatus.html.twig', $this->assignation); } } - $this->assignation['nodes'] = $nodes; - $this->assignation['form'] = $form->createView(); - - if (!empty($request->get('statusForm')['referer'])) { - $this->assignation['referer'] = $request->get('statusForm')['referer']; - } - - return $this->render('@RoadizRozier/nodes/bulkStatus.html.twig', $this->assignation); + throw new ResourceNotFoundException(); } /** - * @param null|string $referer + * @param false|string $referer * @param array $nodesIds * * @return FormInterface */ private function buildBulkDeleteForm( - ?string $referer = null, - array $nodesIds = [] - ): FormInterface { + $referer = false, + $nodesIds = [] + ) { /** @var FormBuilder $builder */ $builder = $this->formFactory ->createNamedBuilder('deleteForm') @@ -298,7 +302,7 @@ private function buildBulkDeleteForm( ], ]); - if (null !== $referer && (new UnicodeString($referer))->startsWith('/')) { + if (false !== $referer && (new UnicodeString($referer))->startsWith('/')) { $builder->add('referer', HiddenType::class, [ 'data' => $referer, ]); @@ -341,7 +345,12 @@ private function bulkDeleteNodes(array $data) return $this->getTranslator()->trans('wrong.request'); } - private function bulkStatusNodes(array $data): string + /** + * @param array $data + * + * @return string + */ + private function bulkStatusNodes(array $data) { if (!empty($data['nodesIds'])) { $nodesIds = trim($data['nodesIds']); @@ -370,7 +379,10 @@ private function bulkStatusNodes(array $data): string return $this->getTranslator()->trans('wrong.request'); } - private function buildBulkTagForm(): FormInterface + /** + * @return FormInterface + */ + private function buildBulkTagForm() { /** @var FormBuilder $builder */ $builder = $this->formFactory @@ -503,17 +515,17 @@ private function untagNodes(array $data) } /** - * @param null|string $referer + * @param false|string $referer * @param array $nodesIds * @param string $status * * @return FormInterface */ private function buildBulkStatusForm( - ?string $referer = null, - array $nodesIds = [], - string $status = 'reject' - ): FormInterface { + $referer = false, + $nodesIds = [], + $status = 'reject' + ) { /** @var FormBuilder $builder */ $builder = $this->formFactory ->createNamedBuilder('statusForm') @@ -541,7 +553,7 @@ private function buildBulkStatusForm( ]) ; - if (null !== $referer && (new UnicodeString($referer))->startsWith('/')) { + if (false !== $referer && (new UnicodeString($referer))->startsWith('/')) { $builder->add('referer', HiddenType::class, [ 'data' => $referer, ]); diff --git a/src/Controllers/Nodes/NodesUtilsController.php b/src/Controllers/Nodes/NodesUtilsController.php index cf73e9fc..a25fa9bd 100644 --- a/src/Controllers/Nodes/NodesUtilsController.php +++ b/src/Controllers/Nodes/NodesUtilsController.php @@ -9,15 +9,23 @@ use RZ\Roadiz\CoreBundle\Event\Node\NodeDuplicatedEvent; use RZ\Roadiz\CoreBundle\Node\NodeDuplicator; use RZ\Roadiz\CoreBundle\Node\NodeNamePolicyInterface; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Themes\Rozier\RozierApp; +/** + * @package Themes\Rozier\Controllers\Nodes + */ class NodesUtilsController extends RozierApp { - public function __construct(private readonly NodeNamePolicyInterface $nodeNamePolicy) + private NodeNamePolicyInterface $nodeNamePolicy; + + /** + * @param NodeNamePolicyInterface $nodeNamePolicy + */ + public function __construct(NodeNamePolicyInterface $nodeNamePolicy) { + $this->nodeNamePolicy = $nodeNamePolicy; } /** @@ -28,16 +36,12 @@ public function __construct(private readonly NodeNamePolicyInterface $nodeNamePo * * @return Response */ - public function duplicateAction(Request $request, int $nodeId): Response + public function duplicateAction(Request $request, int $nodeId) { - /** @var Node|null $existingNode */ - $existingNode = $this->em()->find(Node::class, $nodeId); + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); - if (null === $existingNode) { - throw $this->createNotFoundException(); - } - - $this->denyAccessUnlessGranted(NodeVoter::DUPLICATE, $existingNode); + /** @var Node $existingNode */ + $existingNode = $this->em()->find(Node::class, $nodeId); try { $duplicator = new NodeDuplicator( @@ -57,7 +61,7 @@ public function duplicateAction(Request $request, int $nodeId): Response '%name%' => $existingNode->getNodeName(), ]); - $this->publishConfirmMessage($request, $msg, $newNode->getNodeSources()->first() ?: $newNode); + $this->publishConfirmMessage($request, $msg, $newNode->getNodeSources()->first()); return $this->redirectToRoute( 'nodesEditPage', @@ -68,8 +72,7 @@ public function duplicateAction(Request $request, int $nodeId): Response $request, $this->getTranslator()->trans("impossible.duplicate.node.%name%", [ '%name%' => $existingNode->getNodeName(), - ]), - $existingNode + ]) ); return $this->redirectToRoute( diff --git a/src/Controllers/Nodes/TranstypeController.php b/src/Controllers/Nodes/TranstypeController.php index 7e3326ae..de1a7f3b 100644 --- a/src/Controllers/Nodes/TranstypeController.php +++ b/src/Controllers/Nodes/TranstypeController.php @@ -9,7 +9,7 @@ use RZ\Roadiz\CoreBundle\Event\Node\NodeUpdatedEvent; use RZ\Roadiz\CoreBundle\Event\NodesSources\NodesSourcesUpdatedEvent; use RZ\Roadiz\CoreBundle\Node\NodeTranstyper; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\ResourceNotFoundException; @@ -17,22 +17,33 @@ use Themes\Rozier\RozierApp; use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers\Nodes + */ class TranstypeController extends RozierApp { - public function __construct(private readonly NodeTranstyper $nodeTranstyper) + private NodeTranstyper $nodeTranstyper; + + /** + * @param NodeTranstyper $nodeTranstyper + */ + public function __construct(NodeTranstyper $nodeTranstyper) { + $this->nodeTranstyper = $nodeTranstyper; } /** * @param Request $request * @param int $nodeId * - * @return Response + * @return RedirectResponse|Response * @throws RuntimeError * @throws \Exception */ - public function transtypeAction(Request $request, int $nodeId): Response + public function transtypeAction(Request $request, int $nodeId) { + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); + /** @var Node|null $node */ $node = $this->em()->find(Node::class, $nodeId); $this->em()->refresh($node); @@ -41,11 +52,6 @@ public function transtypeAction(Request $request, int $nodeId): Response throw new ResourceNotFoundException(); } - /* - * Transtype is only available for higher rank users - */ - $this->denyAccessUnlessGranted(NodeVoter::EDIT_SETTING, $node); - $form = $this->createForm(TranstypeType::class, null, [ 'currentType' => $node->getNodeType(), ]); @@ -85,15 +91,13 @@ public function transtypeAction(Request $request, int $nodeId): Response '%node%' => $node->getNodeName(), '%type%' => $newNodeType->getName(), ]); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first()); return $this->redirectToRoute( 'nodesEditSourcePage', [ 'nodeId' => $node->getId(), - 'translationId' => $node->getNodeSources()->first() ? - $node->getNodeSources()->first()->getTranslation()->getId() : - null, + 'translationId' => $node->getNodeSources()->first()->getTranslation()->getId(), ] ); } diff --git a/src/Controllers/Nodes/UrlAliasesController.php b/src/Controllers/Nodes/UrlAliasesController.php new file mode 100644 index 00000000..a9966545 --- /dev/null +++ b/src/Controllers/Nodes/UrlAliasesController.php @@ -0,0 +1,380 @@ +formFactory = $formFactory; + } + + /** + * Return aliases form for requested node. + * + * @param Request $request + * @param int $nodeId + * @param int|null $translationId + * + * @return Response + * @throws RuntimeError + */ + public function editAliasesAction(Request $request, int $nodeId, ?int $translationId = null): Response + { + $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); + + if (null === $translationId || $translationId < 1) { + $translation = $this->em()->getRepository(Translation::class)->findDefault(); + } else { + $translation = $this->em()->find(Translation::class, $translationId); + } + /** @var NodesSources|null $source */ + $source = $this->em() + ->getRepository(NodesSources::class) + ->setDisplayingAllNodesStatuses(true) + ->setDisplayingNotPublishedNodes(true) + ->findOneBy(['translation' => $translation, 'node.id' => $nodeId]); + + if ($source !== null && null !== $node = $source->getNode()) { + $redirections = $this->em() + ->getRepository(Redirection::class) + ->findBy([ + 'redirectNodeSource' => $node->getNodeSources()->toArray() + ]); + $uas = $this->em() + ->getRepository(UrlAlias::class) + ->findAllFromNode($node->getId()); + $availableTranslations = $this->em() + ->getRepository(Translation::class) + ->findAvailableTranslationsForNode($node); + + $this->assignation['node'] = $node; + $this->assignation['source'] = $source; + $this->assignation['aliases'] = []; + $this->assignation['redirections'] = []; + $this->assignation['translation'] = $translation; + $this->assignation['available_translations'] = $availableTranslations; + + /* + * SEO Form + */ + $seoForm = $this->createForm(NodeSourceSeoType::class, $source); + $seoForm->handleRequest($request); + if ($seoForm->isSubmitted() && $seoForm->isValid()) { + $this->em()->flush(); + $msg = $this->getTranslator()->trans('node.seo.updated'); + $this->publishConfirmMessage($request, $msg, $source); + /* + * Dispatch event + */ + $this->dispatchEvent(new NodesSourcesUpdatedEvent($source)); + return $this->redirectToRoute( + 'nodesEditSEOPage', + ['nodeId' => $node->getId(), 'translationId' => $translationId] + ); + } + + if (null !== $response = $this->handleAddRedirection($source, $request)) { + return $response; + } + /* + * each url alias edit form + */ + /** @var UrlAlias $alias */ + foreach ($uas as $alias) { + if (null !== $response = $this->handleSingleUrlAlias($alias, $request)) { + return $response; + } + } + + /** @var Redirection $redirection */ + foreach ($redirections as $redirection) { + if (null !== $response = $this->handleSingleRedirection($redirection, $request)) { + return $response; + } + } + + /* + * Main ADD url alias form + */ + $alias = new UrlAlias(); + $addAliasForm = $this->formFactory->createNamed( + 'add_urlalias_' . $node->getId(), + UrlAliasType::class, + $alias, + [ + 'with_translation' => true + ] + ); + $addAliasForm->handleRequest($request); + if ($addAliasForm->isSubmitted() && $addAliasForm->isValid()) { + try { + $alias = $this->addNodeUrlAlias($alias, $node, $addAliasForm->get('translation')->getData()); + $msg = $this->getTranslator()->trans('url_alias.%alias%.created.%translation%', [ + '%alias%' => $alias->getAlias(), + '%translation%' => $alias->getNodeSource()->getTranslation()->getName(), + ]); + $this->publishConfirmMessage($request, $msg, $source); + /* + * Dispatch event + */ + $this->dispatchEvent(new UrlAliasCreatedEvent($alias)); + + return $this->redirect($this->generateUrl( + 'nodesEditSEOPage', + ['nodeId' => $node->getId(), 'translationId' => $translationId] + ) . '#manage-aliases'); + } catch (EntityAlreadyExistsException $e) { + $addAliasForm->addError(new FormError($e->getMessage())); + } catch (NoTranslationAvailableException $e) { + $addAliasForm->addError(new FormError($e->getMessage())); + } + } + + $this->assignation['form'] = $addAliasForm->createView(); + $this->assignation['seoForm'] = $seoForm->createView(); + + return $this->render('@RoadizRozier/nodes/editAliases.html.twig', $this->assignation); + } + + throw new ResourceNotFoundException(); + } + + /** + * @param UrlAlias $alias + * @param Node $node + * @param Translation $translation + * @return UrlAlias + */ + private function addNodeUrlAlias(UrlAlias $alias, Node $node, Translation $translation): UrlAlias + { + /** @var NodesSources|null $nodeSource */ + $nodeSource = $this->em() + ->getRepository(NodesSources::class) + ->setDisplayingAllNodesStatuses(true) + ->setDisplayingNotPublishedNodes(true) + ->findOneBy(['node' => $node, 'translation' => $translation]); + + if ($nodeSource !== null) { + $alias->setNodeSource($nodeSource); + $this->em()->persist($alias); + $this->em()->flush(); + + return $alias; + } else { + $msg = $this->getTranslator()->trans('url_alias.no_translation.%translation%', [ + '%translation%' => $translation->getName() + ]); + throw new NoTranslationAvailableException($msg); + } + } + + /** + * @param UrlAlias $alias + * @param Request $request + * + * @return RedirectResponse|null + */ + private function handleSingleUrlAlias(UrlAlias $alias, Request $request): ?RedirectResponse + { + $editForm = $this->formFactory->createNamed( + 'edit_urlalias_' . $alias->getId(), + UrlAliasType::class, + $alias + ); + $deleteForm = $this->formFactory->createNamed('delete_urlalias_' . $alias->getId()); + // Match edit + $editForm->handleRequest($request); + if ($editForm->isSubmitted() && $editForm->isValid()) { + try { + try { + $this->em()->flush(); + $msg = $this->getTranslator()->trans( + 'url_alias.%alias%.updated', + ['%alias%' => $alias->getAlias()] + ); + $this->publishConfirmMessage($request, $msg, $alias->getNodeSource()); + /* + * Dispatch event + */ + $this->dispatchEvent(new UrlAliasUpdatedEvent($alias)); + /** @var Translation $translation */ + $translation = $alias->getNodeSource()->getTranslation(); + + return $this->redirect($this->generateUrl( + 'nodesEditSEOPage', + [ + 'nodeId' => $alias->getNodeSource()->getNode()->getId(), + 'translationId' => $translation->getId() + ] + ) . '#manage-aliases'); + } catch (\RuntimeException $exception) { + $editForm->addError(new FormError($exception->getMessage())); + } + } catch (EntityAlreadyExistsException $e) { + $editForm->addError(new FormError($e->getMessage())); + } + } + + // Match delete + $deleteForm->handleRequest($request); + if ($deleteForm->isSubmitted() && $deleteForm->isValid()) { + $this->em()->remove($alias); + $this->em()->flush(); + $msg = $this->getTranslator()->trans('url_alias.%alias%.deleted', ['%alias%' => $alias->getAlias()]); + $this->publishConfirmMessage($request, $msg, $alias->getNodeSource()); + + /* + * Dispatch event + */ + $this->dispatchEvent(new UrlAliasDeletedEvent($alias)); + + /** @var Translation $translation */ + $translation = $alias->getNodeSource()->getTranslation(); + + return $this->redirect($this->generateUrl( + 'nodesEditSEOPage', + [ + 'nodeId' => $alias->getNodeSource()->getNode()->getId(), + 'translationId' => $translation->getId() + ] + ) . '#manage-aliases'); + } + + $this->assignation['aliases'][] = [ + 'alias' => $alias, + 'editForm' => $editForm->createView(), + 'deleteForm' => $deleteForm->createView(), + ]; + + return null; + } + + /** + * @param NodesSources $source + * @param Request $request + * @return RedirectResponse|null + */ + private function handleAddRedirection(NodesSources $source, Request $request): ?RedirectResponse + { + $redirection = new Redirection(); + $redirection->setRedirectNodeSource($source); + $redirection->setType(Response::HTTP_MOVED_PERMANENTLY); + + $addForm = $this->formFactory->createNamed( + 'add_redirection', + RedirectionType::class, + $redirection, + [ + 'placeholder' => $this->generateUrl($source), + 'only_query' => true + ] + ); + + $addForm->handleRequest($request); + if ($addForm->isSubmitted() && $addForm->isValid()) { + $this->em()->persist($redirection); + $this->em()->flush(); + + /** @var Translation $translation */ + $translation = $redirection->getRedirectNodeSource()->getTranslation(); + + return $this->redirect($this->generateUrl( + 'nodesEditSEOPage', + [ + 'nodeId' => $redirection->getRedirectNodeSource()->getNode()->getId(), + 'translationId' => $translation->getId() + ] + ) . '#manage-redirections'); + } + + $this->assignation['addRedirection'] = $addForm->createView(); + + return null; + } + + /** + * @param Redirection $redirection + * @param Request $request + * @return RedirectResponse|null + */ + private function handleSingleRedirection(Redirection $redirection, Request $request): ?RedirectResponse + { + $editForm = $this->formFactory->createNamed( + 'edit_redirection_' . $redirection->getId(), + RedirectionType::class, + $redirection, + [ + 'only_query' => true + ] + ); + + /** @var Translation $translation */ + $translation = $redirection->getRedirectNodeSource()->getTranslation(); + + $deleteForm = $this->formFactory->createNamed('delete_redirection_' . $redirection->getId()); + + $editForm->handleRequest($request); + if ($editForm->isSubmitted() && $editForm->isValid()) { + $this->em()->flush(); + return $this->redirect($this->generateUrl( + 'nodesEditSEOPage', + [ + 'nodeId' => $redirection->getRedirectNodeSource()->getNode()->getId(), + 'translationId' => $translation->getId() + ] + ) . '#manage-redirections'); + } + + // Match delete + $deleteForm->handleRequest($request); + if ($deleteForm->isSubmitted() && $deleteForm->isValid()) { + $this->em()->remove($redirection); + $this->em()->flush(); + return $this->redirect($this->generateUrl( + 'nodesEditSEOPage', + [ + 'nodeId' => $redirection->getRedirectNodeSource()->getNode()->getId(), + 'translationId' => $translation->getId() + ] + ) . '#manage-redirections'); + } + $this->assignation['redirections'][] = [ + 'redirection' => $redirection, + 'editForm' => $editForm->createView(), + 'deleteForm' => $deleteForm->createView(), + ]; + + return null; + } +} diff --git a/src/Controllers/PingController.php b/src/Controllers/PingController.php new file mode 100644 index 00000000..898e2a31 --- /dev/null +++ b/src/Controllers/PingController.php @@ -0,0 +1,26 @@ +denyAccessUnlessGranted('ROLE_BACKEND_USER'); + return $this->renderJson(['Pong']); + } +} diff --git a/src/Controllers/RedirectionsController.php b/src/Controllers/RedirectionsController.php index b2819ef5..cd787e20 100644 --- a/src/Controllers/RedirectionsController.php +++ b/src/Controllers/RedirectionsController.php @@ -6,14 +6,10 @@ use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; use RZ\Roadiz\CoreBundle\Entity\Redirection; -use RZ\Roadiz\CoreBundle\Event\Redirection\PostCreatedRedirectionEvent; -use RZ\Roadiz\CoreBundle\Event\Redirection\PostDeletedRedirectionEvent; -use RZ\Roadiz\CoreBundle\Event\Redirection\PostUpdatedRedirectionEvent; -use RZ\Roadiz\CoreBundle\Event\Redirection\RedirectionEvent; use Symfony\Component\HttpFoundation\Request; use Themes\Rozier\Forms\RedirectionType; -class RedirectionsController extends AbstractAdminWithBulkController +class RedirectionsController extends AbstractAdminController { /** * @inheritDoc @@ -105,33 +101,4 @@ protected function getDefaultOrder(Request $request): array { return ['query' => 'ASC']; } - - protected function createPostCreateEvent(PersistableInterface $item): RedirectionEvent - { - if (!($item instanceof Redirection)) { - throw new \InvalidArgumentException('Item should be instance of ' . Redirection::class); - } - return new PostCreatedRedirectionEvent($item); - } - - protected function createPostUpdateEvent(PersistableInterface $item): RedirectionEvent - { - if (!($item instanceof Redirection)) { - throw new \InvalidArgumentException('Item should be instance of ' . Redirection::class); - } - return new PostUpdatedRedirectionEvent($item); - } - - protected function createDeleteEvent(PersistableInterface $item): RedirectionEvent - { - if (!($item instanceof Redirection)) { - throw new \InvalidArgumentException('Item should be instance of ' . Redirection::class); - } - return new PostDeletedRedirectionEvent($item); - } - - protected function getBulkDeleteRouteName(): ?string - { - return 'redirectionsBulkDeletePage'; - } } diff --git a/src/Controllers/RolesUtilsController.php b/src/Controllers/RolesUtilsController.php index 4cc08dc7..382baaf3 100644 --- a/src/Controllers/RolesUtilsController.php +++ b/src/Controllers/RolesUtilsController.php @@ -20,10 +20,17 @@ class RolesUtilsController extends RozierApp { - public function __construct( - private readonly SerializerInterface $serializer, - private readonly RolesImporter $rolesImporter - ) { + private SerializerInterface $serializer; + private RolesImporter $rolesImporter; + + /** + * @param SerializerInterface $serializer + * @param RolesImporter $rolesImporter + */ + public function __construct(SerializerInterface $serializer, RolesImporter $rolesImporter) + { + $this->serializer = $serializer; + $this->rolesImporter = $rolesImporter; } /** @@ -83,9 +90,6 @@ public function importJsonFileAction(Request $request): Response if ($file->isValid()) { $serializedData = file_get_contents($file->getPathname()); - if (false === $serializedData) { - throw new RuntimeError('Cannot read uploaded file.'); - } if (null !== \json_decode($serializedData)) { if ($this->rolesImporter->import($serializedData)) { diff --git a/src/Controllers/SearchController.php b/src/Controllers/SearchController.php index 1cff1ec9..d6d4aecf 100644 --- a/src/Controllers/SearchController.php +++ b/src/Controllers/SearchController.php @@ -68,7 +68,7 @@ public function notBlank(mixed $var): bool * * @return array */ - protected function appendDateTimeCriteria(array &$data, string $fieldName): array + protected function appendDateTimeCriteria(array &$data, string $fieldName) { $date = $data[$fieldName]['compareDatetime']; if ($date instanceof DateTime) { @@ -86,10 +86,12 @@ protected function appendDateTimeCriteria(array &$data, string $fieldName): arra * @param string $prefix * @return mixed */ - protected function processCriteria($data, string $prefix = ""): mixed + protected function processCriteria($data, string $prefix = "") { if (!empty($data[$prefix . "nodeName"])) { - if (!isset($data[$prefix . "nodeName_exact"]) || $data[$prefix . "nodeName_exact"] !== true) { + if (isset($data[$prefix . "nodeName_exact"]) && $data[$prefix . "nodeName_exact"] === true) { + $data[$prefix . "nodeName"] = $data[$prefix . "nodeName"]; + } else { $data[$prefix . "nodeName"] = ["LIKE", "%" . $data[$prefix . "nodeName"] . "%"]; } } @@ -137,11 +139,11 @@ protected function processCriteria($data, string $prefix = ""): mixed } /** - * @param array $data + * @param array|\Traversable $data * @param NodeType $nodetype - * @return array + * @return mixed */ - protected function processCriteriaNodetype(array $data, NodeType $nodetype): array + protected function processCriteriaNodetype($data, NodeType $nodetype) { $fields = $nodetype->getFields(); foreach ($data as $key => $value) { @@ -195,7 +197,7 @@ protected function processCriteriaNodetype(array $data, NodeType $nodetype): arr * @return Response * @throws RuntimeError */ - public function searchNodeAction(Request $request): Response + public function searchNodeAction(Request $request) { $builder = $this->buildSimpleForm(''); $form = $this->addButtons($builder)->getForm(); @@ -251,12 +253,12 @@ public function searchNodeAction(Request $request): Response * @param Request $request * @param int $nodetypeId * - * @return Response + * @return null|RedirectResponse|Response * @throws Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws RuntimeError */ - public function searchNodeSourceAction(Request $request, int $nodetypeId): Response + public function searchNodeSourceAction(Request $request, int $nodetypeId) { /** @var NodeType|null $nodetype */ $nodetype = $this->em()->find(NodeType::class, $nodetypeId); @@ -344,7 +346,7 @@ protected function addButtons(FormBuilderInterface $builder, bool $exportXlsx = * * @return null|RedirectResponse */ - protected function handleNodeTypeForm(FormInterface $nodeTypeForm): ?RedirectResponse + protected function handleNodeTypeForm(FormInterface $nodeTypeForm) { if ($nodeTypeForm->isSubmitted() && $nodeTypeForm->isValid()) { if (empty($nodeTypeForm->getData()['nodetype'])) { @@ -449,7 +451,7 @@ protected function handleNodeForm(FormInterface $form, NodeType $nodetype): ?Res * @throws Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception */ - protected function getXlsxResults(NodeType $nodetype, iterable $entities): string + protected function getXlsxResults(NodeType $nodetype, $entities): string { $fields = $nodetype->getFields(); $keys = []; diff --git a/src/Controllers/SettingsController.php b/src/Controllers/SettingsController.php index 4a12ba8d..81284ef6 100644 --- a/src/Controllers/SettingsController.php +++ b/src/Controllers/SettingsController.php @@ -15,7 +15,6 @@ use RZ\Roadiz\CoreBundle\Exception\EntityAlreadyExistsException; use RZ\Roadiz\CoreBundle\Form\Error\FormErrorSerializer; use RZ\Roadiz\CoreBundle\Form\SettingType; -use RZ\Roadiz\CoreBundle\ListManager\SessionListFilters; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormFactoryInterface; @@ -24,14 +23,18 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Themes\Rozier\RozierApp; +use Themes\Rozier\Utils\SessionListFilters; use Twig\Error\RuntimeError; class SettingsController extends RozierApp { - public function __construct( - private readonly FormFactoryInterface $formFactory, - private readonly FormErrorSerializer $formErrorSerializer - ) { + private FormFactoryInterface $formFactory; + private FormErrorSerializer $formErrorSerializer; + + public function __construct(FormFactoryInterface $formFactory, FormErrorSerializer $formErrorSerializer) + { + $this->formFactory = $formFactory; + $this->formErrorSerializer = $formErrorSerializer; } /** @@ -113,13 +116,6 @@ protected function commonSettingList(Request $request, SettingGroup $settingGrou $this->assignation['filters'] = $listManager->getAssignation(); $settings = $listManager->getEntities(); $this->assignation['settings'] = []; - $isJson = - $request->isXmlHttpRequest() || - $request->getRequestFormat('html') === 'json' || - \in_array( - 'application/json', - $request->getAcceptableContentTypes() - ); /** @var Setting $setting */ foreach ($settings as $setting) { @@ -137,9 +133,9 @@ protected function commonSettingList(Request $request, SettingGroup $settingGrou 'setting.%name%.updated', ['%name%' => $setting->getName()] ); - $this->publishConfirmMessage($request, $msg, $setting); + $this->publishConfirmMessage($request, $msg); - if ($isJson) { + if ($request->isXmlHttpRequest() || $request->getRequestFormat('html') === 'json') { return new JsonResponse([ 'status' => 'success', 'message' => $msg, @@ -166,7 +162,7 @@ protected function commonSettingList(Request $request, SettingGroup $settingGrou /* * Do not publish any message, it may lead to flushing invalid form */ - if ($isJson) { + if ($request->isXmlHttpRequest() || $request->getRequestFormat('html') === 'json') { return new JsonResponse([ 'status' => 'failed', 'errors' => $errors, @@ -222,7 +218,7 @@ public function editAction(Request $request, int $settingId): Response $this->dispatchEvent(new SettingUpdatedEvent($setting)); $this->em()->flush(); $msg = $this->getTranslator()->trans('setting.%name%.updated', ['%name%' => $setting->getName()]); - $this->publishConfirmMessage($request, $msg, $setting); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page */ @@ -277,7 +273,7 @@ public function addAction(Request $request): Response $this->em()->persist($setting); $this->em()->flush(); $msg = $this->getTranslator()->trans('setting.%name%.created', ['%name%' => $setting->getName()]); - $this->publishConfirmMessage($request, $msg, $setting); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute('settingsHomePage'); } catch (EntityAlreadyExistsException $e) { @@ -323,7 +319,7 @@ public function deleteAction(Request $request, int $settingId): Response $this->em()->flush(); $msg = $this->getTranslator()->trans('setting.%name%.deleted', ['%name%' => $setting->getName()]); - $this->publishConfirmMessage($request, $msg, $setting); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page diff --git a/src/Controllers/SettingsUtilsController.php b/src/Controllers/SettingsUtilsController.php index 8d963eb4..7ddb148c 100644 --- a/src/Controllers/SettingsUtilsController.php +++ b/src/Controllers/SettingsUtilsController.php @@ -6,9 +6,9 @@ use JMS\Serializer\SerializationContext; use JMS\Serializer\SerializerInterface; +use RZ\Roadiz\CoreBundle\Importer\SettingsImporter; use RZ\Roadiz\CoreBundle\Entity\Setting; use RZ\Roadiz\CoreBundle\Entity\SettingGroup; -use RZ\Roadiz\CoreBundle\Importer\SettingsImporter; use RZ\Roadiz\Utils\StringHandler; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\FormError; @@ -21,10 +21,17 @@ class SettingsUtilsController extends RozierApp { - public function __construct( - private readonly SerializerInterface $serializer, - private readonly SettingsImporter $settingsImporter - ) { + private SerializerInterface $serializer; + private SettingsImporter $settingsImporter; + + /** + * @param SerializerInterface $serializer + * @param SettingsImporter $settingsImporter + */ + public function __construct(SerializerInterface $serializer, SettingsImporter $settingsImporter) + { + $this->serializer = $serializer; + $this->settingsImporter = $settingsImporter; } /** @@ -96,10 +103,6 @@ public function importJsonFileAction(Request $request): Response if ($file->isValid()) { $serializedData = file_get_contents($file->getPathname()); - if (!\is_string($serializedData)) { - throw new RuntimeError('Imported file is not a string.'); - } - if (null !== \json_decode($serializedData)) { if ($this->settingsImporter->import($serializedData)) { $msg = $this->getTranslator()->trans('setting.imported'); diff --git a/src/Controllers/Tags/TagMultiCreationController.php b/src/Controllers/Tags/TagMultiCreationController.php index fa8632b9..630fba1c 100644 --- a/src/Controllers/Tags/TagMultiCreationController.php +++ b/src/Controllers/Tags/TagMultiCreationController.php @@ -9,81 +9,91 @@ use RZ\Roadiz\CoreBundle\Event\Tag\TagCreatedEvent; use RZ\Roadiz\CoreBundle\Tag\TagFactory; use Symfony\Component\Form\FormError; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Themes\Rozier\Forms\MultiTagType; use Themes\Rozier\RozierApp; +/** + * @package Themes\Rozier\Controllers\Tags + */ class TagMultiCreationController extends RozierApp { - public function __construct(private readonly TagFactory $tagFactory) + private TagFactory $tagFactory; + + /** + * @param TagFactory $tagFactory + */ + public function __construct(TagFactory $tagFactory) { + $this->tagFactory = $tagFactory; } /** * @param Request $request * @param int $parentTagId - * @return Response + * @return RedirectResponse|Response|null * @throws \Twig\Error\RuntimeError */ - public function addChildAction(Request $request, int $parentTagId): Response + public function addChildAction(Request $request, int $parentTagId) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); $translation = $this->em()->getRepository(Translation::class)->findDefault(); $parentTag = $this->em()->find(Tag::class, $parentTagId); - if (null === $parentTag) { - throw new ResourceNotFoundException(); - } + if (null !== $parentTag) { + $form = $this->createForm(MultiTagType::class); + $form->handleRequest($request); - $form = $this->createForm(MultiTagType::class); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - try { - $data = $form->getData(); - $names = explode(',', $data['names']); - $names = array_map('trim', $names); - $names = array_filter($names); - $names = array_unique($names); - - /* - * Get latest position to add tags after. - */ - $latestPosition = $this->em() - ->getRepository(Tag::class) - ->findLatestPositionInParent($parentTag); - - $tagsArray = []; - foreach ($names as $name) { - $tagsArray[] = $this->tagFactory->create($name, $translation, $parentTag, $latestPosition); - $this->em()->flush(); - } + if ($form->isSubmitted() && $form->isValid()) { + try { + $data = $form->getData(); + $names = explode(',', $data['names']); + $names = array_map('trim', $names); + $names = array_filter($names); + $names = array_unique($names); - /* - * Dispatch event and msg - */ - foreach ($tagsArray as $tag) { /* - * Dispatch event + * Get latest position to add tags after. */ - $this->dispatchEvent(new TagCreatedEvent($tag)); - $msg = $this->getTranslator()->trans('child.tag.%name%.created', ['%name%' => $tag->getTagName()]); - $this->publishConfirmMessage($request, $msg, $tag); - } + $latestPosition = $this->em() + ->getRepository(Tag::class) + ->findLatestPositionInParent($parentTag); + + $tagsArray = []; + foreach ($names as $name) { + $tagsArray[] = $this->tagFactory->create($name, $translation, $parentTag, $latestPosition); + $this->em()->flush(); + } - return $this->redirectToRoute('tagsTreePage', ['tagId' => $parentTagId]); - } catch (\InvalidArgumentException $e) { - $form->addError(new FormError($e->getMessage())); + /* + * Dispatch event and msg + */ + foreach ($tagsArray as $tag) { + /* + * Dispatch event + */ + $this->dispatchEvent(new TagCreatedEvent($tag)); + $msg = $this->getTranslator()->trans('child.tag.%name%.created', ['%name%' => $tag->getTagName()]); + $this->publishConfirmMessage($request, $msg); + } + + return $this->redirectToRoute('tagsTreePage', ['tagId' => $parentTagId]); + } catch (\InvalidArgumentException $e) { + $form->addError(new FormError($e->getMessage())); + } } - } - $this->assignation['translation'] = $translation; - $this->assignation['form'] = $form->createView(); - $this->assignation['tag'] = $parentTag; + $this->assignation['translation'] = $translation; + $this->assignation['form'] = $form->createView(); + $this->assignation['tag'] = $parentTag; + + return $this->render('@RoadizRozier/tags/add-multiple.html.twig', $this->assignation); + } - return $this->render('@RoadizRozier/tags/add-multiple.html.twig', $this->assignation); + throw new ResourceNotFoundException(); } } diff --git a/src/Controllers/Tags/TagsController.php b/src/Controllers/Tags/TagsController.php index 9a766e71..dd024d97 100644 --- a/src/Controllers/Tags/TagsController.php +++ b/src/Controllers/Tags/TagsController.php @@ -15,7 +15,6 @@ use RZ\Roadiz\CoreBundle\Event\Tag\TagDeletedEvent; use RZ\Roadiz\CoreBundle\Event\Tag\TagUpdatedEvent; use RZ\Roadiz\CoreBundle\Exception\EntityAlreadyExistsException; -use RZ\Roadiz\CoreBundle\Form\Error\FormErrorSerializer; use RZ\Roadiz\CoreBundle\Repository\TranslationRepository; use RZ\Roadiz\Utils\StringHandler; use Symfony\Component\Form\Extension\Core\Type\HiddenType; @@ -36,16 +35,30 @@ use Themes\Rozier\Widgets\TreeWidgetFactory; use Twig\Error\RuntimeError; +/** + * @package Themes\Rozier\Controllers\Tags + */ class TagsController extends RozierApp { use VersionedControllerTrait; + private HandlerFactoryInterface $handlerFactory; + private FormFactoryInterface $formFactory; + private TreeWidgetFactory $treeWidgetFactory; + + /** + * @param FormFactoryInterface $formFactory + * @param HandlerFactoryInterface $handlerFactory + * @param TreeWidgetFactory $treeWidgetFactory + */ public function __construct( - private readonly FormFactoryInterface $formFactory, - private readonly FormErrorSerializer $formErrorSerializer, - private readonly HandlerFactoryInterface $handlerFactory, - private readonly TreeWidgetFactory $treeWidgetFactory + FormFactoryInterface $formFactory, + HandlerFactoryInterface $handlerFactory, + TreeWidgetFactory $treeWidgetFactory ) { + $this->handlerFactory = $handlerFactory; + $this->formFactory = $formFactory; + $this->treeWidgetFactory = $treeWidgetFactory; } /** @@ -55,7 +68,7 @@ public function __construct( * * @return Response */ - public function indexAction(Request $request): Response + public function indexAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); @@ -92,7 +105,7 @@ public function indexAction(Request $request): Response * @return Response * @throws RuntimeError */ - public function editTranslatedAction(Request $request, int $tagId, ?int $translationId = null): Response + public function editTranslatedAction(Request $request, int $tagId, ?int $translationId = null) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); @@ -153,10 +166,6 @@ public function editTranslatedAction(Request $request, int $tagId, ?int $transla 'disabled' => $this->isReadOnly, ]); $form->handleRequest($request); - $isJsonRequest = - $request->isXmlHttpRequest() || - \in_array('application/json', $request->getAcceptableContentTypes()) - ; if ($form->isSubmitted()) { if ($form->isValid()) { @@ -185,31 +194,24 @@ public function editTranslatedAction(Request $request, int $tagId, ?int $transla $msg = $this->getTranslator()->trans('tag.%name%.updated', [ '%name%' => $tagTranslation->getName(), ]); - $this->publishConfirmMessage($request, $msg, $tag); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page */ - if (!$isJsonRequest) { - return $this->getPostUpdateRedirection($tagTranslation); - } - - return new JsonResponse([ - 'status' => 'success', - 'errors' => [], - ], Response::HTTP_PARTIAL_CONTENT); + return $this->getPostUpdateRedirection($tagTranslation); } /* * Handle errors when Ajax POST requests */ - if ($isJsonRequest) { - $errors = $this->formErrorSerializer->getErrorsAsArray($form); + if ($request->isXmlHttpRequest()) { + $errors = $this->getErrorsAsArray($form); return new JsonResponse([ 'status' => 'fail', 'errors' => $errors, 'message' => $this->getTranslator()->trans('form_has_errors.check_you_fields'), - ], Response::HTTP_BAD_REQUEST); + ], JsonResponse::HTTP_BAD_REQUEST); } } /** @var TranslationRepository $translationRepository */ @@ -244,7 +246,7 @@ protected function tagNameExists(string $name): bool * @return Response * @throws RuntimeError */ - public function bulkDeleteAction(Request $request): Response + public function bulkDeleteAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS_DELETE'); @@ -294,10 +296,10 @@ public function bulkDeleteAction(Request $request): Response /** * @param Request $request + * * @return Response - * @throws RuntimeError */ - public function addAction(Request $request): Response + public function addAction(Request $request) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); @@ -331,7 +333,7 @@ public function addAction(Request $request): Response $this->dispatchEvent(new TagCreatedEvent($tag)); $msg = $this->getTranslator()->trans('tag.%name%.created', ['%name%' => $tag->getTagName()]); - $this->publishConfirmMessage($request, $msg, $tag); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page */ @@ -351,9 +353,8 @@ public function addAction(Request $request): Response * @param int $tagId * * @return Response - * @throws RuntimeError */ - public function editSettingsAction(Request $request, int $tagId): Response + public function editSettingsAction(Request $request, int $tagId) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); @@ -371,10 +372,6 @@ public function editSettingsAction(Request $request, int $tagId): Response ]); $form->handleRequest($request); - $isJsonRequest = - $request->isXmlHttpRequest() || - \in_array('application/json', $request->getAcceptableContentTypes()) - ; if ($form->isSubmitted()) { if ($form->isValid()) { @@ -385,7 +382,7 @@ public function editSettingsAction(Request $request, int $tagId): Response $this->dispatchEvent(new TagUpdatedEvent($tag)); $msg = $this->getTranslator()->trans('tag.%name%.updated', ['%name%' => $tag->getTagName()]); - $this->publishConfirmMessage($request, $msg, $tag); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page @@ -398,8 +395,8 @@ public function editSettingsAction(Request $request, int $tagId): Response /* * Handle errors when Ajax POST requests */ - if ($isJsonRequest) { - $errors = $this->formErrorSerializer->getErrorsAsArray($form); + if ($request->isXmlHttpRequest()) { + $errors = $this->getErrorsAsArray($form); return new JsonResponse([ 'status' => 'fail', 'errors' => $errors, @@ -421,9 +418,8 @@ public function editSettingsAction(Request $request, int $tagId): Response * @param int|null $translationId * * @return Response - * @throws RuntimeError */ - public function treeAction(Request $request, int $tagId, ?int $translationId = null): Response + public function treeAction(Request $request, int $tagId, ?int $translationId = null) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); @@ -453,12 +449,11 @@ public function treeAction(Request $request, int $tagId, ?int $translationId = n * Return a deletion form for requested tag. * * @param Request $request - * @param int $tagId + * @param int $tagId * * @return Response - * @throws RuntimeError */ - public function deleteAction(Request $request, int $tagId): Response + public function deleteAction(Request $request, int $tagId) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS_DELETE'); @@ -487,12 +482,8 @@ public function deleteAction(Request $request, int $tagId): Response $this->em()->remove($tag); $this->em()->flush(); - $msg = $this->getTranslator()->trans('tag.%name%.deleted', [ - '%name%' => $tag->getTranslatedTags()->first() ? - $tag->getTranslatedTags()->first()->getName() : - $tag->getTagName(), - ]); - $this->publishConfirmMessage($request, $msg, $tag); + $msg = $this->getTranslator()->trans('tag.%name%.deleted', ['%name%' => $tag->getTranslatedTags()->first()->getName()]); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page @@ -516,9 +507,8 @@ public function deleteAction(Request $request, int $tagId): Response * @param int|null $translationId * * @return Response - * @throws RuntimeError */ - public function addChildAction(Request $request, int $tagId, ?int $translationId = null): Response + public function addChildAction(Request $request, int $tagId, ?int $translationId = null) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); @@ -560,7 +550,7 @@ public function addChildAction(Request $request, int $tagId, ?int $translationId $this->dispatchEvent(new TagCreatedEvent($tag)); $msg = $this->getTranslator()->trans('child.tag.%name%.created', ['%name%' => $tag->getTagName()]); - $this->publishConfirmMessage($request, $msg, $tag); + $this->publishConfirmMessage($request, $msg); return $this->redirectToRoute( 'tagsEditPage', @@ -590,7 +580,7 @@ public function addChildAction(Request $request, int $tagId, ?int $translationId * @return Response * @throws RuntimeError */ - public function editNodesAction(Request $request, int $tagId): Response + public function editNodesAction(Request $request, int $tagId) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); @@ -628,7 +618,7 @@ public function editNodesAction(Request $request, int $tagId): Response * * @return FormInterface */ - private function buildDeleteForm(Tag $tag): FormInterface + private function buildDeleteForm(Tag $tag) { $builder = $this->createFormBuilder() ->add('tagId', HiddenType::class, [ @@ -643,15 +633,15 @@ private function buildDeleteForm(Tag $tag): FormInterface } /** - * @param null|string $referer + * @param false|string $referer * @param array $tagsIds * * @return FormInterface */ private function buildBulkDeleteForm( - ?string $referer = null, + $referer = false, array $tagsIds = [] - ): FormInterface { + ) { $builder = $this->formFactory ->createNamedBuilder('deleteForm') ->add('tagsIds', HiddenType::class, [ @@ -663,7 +653,7 @@ private function buildBulkDeleteForm( ], ]); - if (null !== $referer && (new UnicodeString($referer))->startsWith('/')) { + if (false !== $referer && (new UnicodeString($referer))->startsWith('/')) { $builder->add('referer', HiddenType::class, [ 'data' => $referer, ]); @@ -677,7 +667,7 @@ private function buildBulkDeleteForm( * * @return string */ - private function bulkDeleteTags(array $data): string + private function bulkDeleteTags(array $data) { if (!empty($data['tagsIds'])) { $tagsIds = trim($data['tagsIds']); @@ -721,7 +711,7 @@ protected function onPostUpdate(PersistableInterface $entity, Request $request): $msg = $this->getTranslator()->trans('tag.%name%.updated', [ '%name%' => $entity->getName(), ]); - $this->publishConfirmMessage($request, $msg, $entity); + $this->publishConfirmMessage($request, $msg); } } diff --git a/src/Controllers/Tags/TagsUtilsController.php b/src/Controllers/Tags/TagsUtilsController.php index 71f96693..c60cfb7a 100644 --- a/src/Controllers/Tags/TagsUtilsController.php +++ b/src/Controllers/Tags/TagsUtilsController.php @@ -12,20 +12,30 @@ use Symfony\Component\HttpFoundation\Response; use Themes\Rozier\RozierApp; +/** + * @package Themes\Rozier\Controllers\Tags + */ class TagsUtilsController extends RozierApp { - public function __construct(private readonly SerializerInterface $serializer) + private SerializerInterface $serializer; + + /** + * @param SerializerInterface $serializer + */ + public function __construct(SerializerInterface $serializer) { + $this->serializer = $serializer; } /** * Export a Tag in a Json file * * @param Request $request - * @param int $tagId - * @return JsonResponse + * @param int $tagId + * + * @return Response */ - public function exportAction(Request $request, int $tagId): JsonResponse + public function exportAction(Request $request, int $tagId) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); @@ -37,7 +47,7 @@ public function exportAction(Request $request, int $tagId): JsonResponse 'json', SerializationContext::create()->setGroups(['tag', 'position']) ), - Response::HTTP_OK, + JsonResponse::HTTP_OK, [ 'Content-Disposition' => sprintf( 'attachment; filename="%s"', @@ -54,9 +64,9 @@ public function exportAction(Request $request, int $tagId): JsonResponse * @param Request $request * @param int $tagId * - * @return JsonResponse + * @return Response */ - public function exportAllAction(Request $request, int $tagId): JsonResponse + public function exportAllAction(Request $request, int $tagId) { $this->denyAccessUnlessGranted('ROLE_ACCESS_TAGS'); @@ -70,7 +80,7 @@ public function exportAllAction(Request $request, int $tagId): JsonResponse 'json', SerializationContext::create()->setGroups(['tag', 'position']) ), - Response::HTTP_OK, + JsonResponse::HTTP_OK, [ 'Content-Disposition' => sprintf( 'attachment; filename="%s"', diff --git a/src/Controllers/TranslationsController.php b/src/Controllers/TranslationsController.php index 601c24b1..94aacc18 100644 --- a/src/Controllers/TranslationsController.php +++ b/src/Controllers/TranslationsController.php @@ -4,12 +4,12 @@ namespace Themes\Rozier\Controllers; -use RZ\Roadiz\Core\Handlers\HandlerFactoryInterface; use RZ\Roadiz\CoreBundle\Entity\Translation; -use RZ\Roadiz\CoreBundle\EntityHandler\TranslationHandler; use RZ\Roadiz\CoreBundle\Event\Translation\TranslationCreatedEvent; use RZ\Roadiz\CoreBundle\Event\Translation\TranslationDeletedEvent; use RZ\Roadiz\CoreBundle\Event\Translation\TranslationUpdatedEvent; +use RZ\Roadiz\Core\Handlers\HandlerFactoryInterface; +use RZ\Roadiz\CoreBundle\EntityHandler\TranslationHandler; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Request; @@ -23,8 +23,11 @@ class TranslationsController extends RozierApp { public const ITEM_PER_PAGE = 5; - public function __construct(private readonly HandlerFactoryInterface $handlerFactory) + private HandlerFactoryInterface $handlerFactory; + + public function __construct(HandlerFactoryInterface $handlerFactory) { + $this->handlerFactory = $handlerFactory; } /** @@ -58,7 +61,7 @@ public function indexAction(Request $request): Response $handler = $this->handlerFactory->getHandler($translation); $handler->makeDefault(); $msg = $this->getTranslator()->trans('translation.%name%.made_default', ['%name%' => $translation->getName()]); - $this->publishConfirmMessage($request, $msg, $translation); + $this->publishConfirmMessage($request, $msg); $this->dispatchEvent(new TranslationUpdatedEvent($translation)); /* * Force redirect to avoid resending form when refreshing page @@ -103,7 +106,7 @@ public function editAction(Request $request, int $translationId): Response if ($form->isSubmitted() && $form->isValid()) { $this->em()->flush(); $msg = $this->getTranslator()->trans('translation.%name%.updated', ['%name%' => $translation->getName()]); - $this->publishConfirmMessage($request, $msg, $translation); + $this->publishConfirmMessage($request, $msg); $this->dispatchEvent(new TranslationUpdatedEvent($translation)); /* @@ -141,7 +144,7 @@ public function addAction(Request $request): Response $this->em()->flush(); $msg = $this->getTranslator()->trans('translation.%name%.created', ['%name%' => $translation->getName()]); - $this->publishConfirmMessage($request, $msg, $translation); + $this->publishConfirmMessage($request, $msg); $this->dispatchEvent(new TranslationCreatedEvent($translation)); /* @@ -181,7 +184,7 @@ public function deleteAction(Request $request, int $translationId): Response $this->em()->remove($translation); $this->em()->flush(); $msg = $this->getTranslator()->trans('translation.%name%.deleted', ['%name%' => $translation->getName()]); - $this->publishConfirmMessage($request, $msg, $translation); + $this->publishConfirmMessage($request, $msg); $this->dispatchEvent(new TranslationDeletedEvent($translation)); return $this->redirectToRoute('translationsHomePage'); diff --git a/src/Controllers/Users/UsersController.php b/src/Controllers/Users/UsersController.php index a2ded208..62732744 100644 --- a/src/Controllers/Users/UsersController.php +++ b/src/Controllers/Users/UsersController.php @@ -4,311 +4,239 @@ namespace Themes\Rozier\Controllers\Users; -use JMS\Serializer\SerializerInterface; -use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; use RZ\Roadiz\CoreBundle\Entity\Role; use RZ\Roadiz\CoreBundle\Entity\User; -use Symfony\Component\Form\FormFactoryInterface; -use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Themes\Rozier\Controllers\AbstractAdminWithBulkController; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Themes\Rozier\Forms\AddUserType; use Themes\Rozier\Forms\UserDetailsType; use Themes\Rozier\Forms\UserType; +use Themes\Rozier\RozierApp; +use Themes\Rozier\Utils\SessionListFilters; use Twig\Error\RuntimeError; -class UsersController extends AbstractAdminWithBulkController +class UsersController extends RozierApp { - public function __construct( - FormFactoryInterface $formFactory, - SerializerInterface $serializer, - UrlGeneratorInterface $urlGenerator, - private readonly bool $useGravatar - ) { - parent::__construct($formFactory, $serializer, $urlGenerator); - } - - - protected function supports(PersistableInterface $item): bool - { - return $item instanceof User; - } - - protected function getNamespace(): string - { - return 'user'; - } - - protected function createEmptyItem(Request $request): User - { - $user = new User(); - $user->sendCreationConfirmationEmail(true); - return $user; - } - - protected function getTemplateFolder(): string - { - return '@RoadizRozier/users'; - } - - protected function getRequiredRole(): string - { - return 'ROLE_ACCESS_USERS'; - } - - protected function getRequiredEditionRole(): string - { - // Allow any backoffice user to access user edition before - // checking if current editing item is the same as current user. - return 'ROLE_BACKEND_USER'; - } - - protected function getRequiredDeletionRole(): string - { - return 'ROLE_ACCESS_USERS_DELETE'; - } - - protected function getEntityClass(): string - { - return User::class; - } - - protected function getFormType(): string + /** + * @param Request $request + * + * @return Response + * @throws RuntimeError + */ + public function indexAction(Request $request): Response { - return UserType::class; - } + $this->denyAccessUnlessGranted('ROLE_ACCESS_USERS'); - protected function getDefaultRouteName(): string - { - return 'usersHomePage'; - } + /* + * Manage get request to filter list + */ + $listManager = $this->createEntityListManager( + User::class, + [], + ['username' => 'ASC'] + ); + $listManager->setDisplayingNotPublishedNodes(true); + /* + * Stored in session + */ + $sessionListFilter = new SessionListFilters('user_item_per_page'); + $sessionListFilter->handleItemPerPage($request, $listManager); + $listManager->handle(); - protected function getEditRouteName(): string - { - return 'usersEditPage'; - } + $this->assignation['filters'] = $listManager->getAssignation(); + $this->assignation['users'] = $listManager->getEntities(); - protected function getBulkDeleteRouteName(): ?string - { - return 'usersBulkDeletePage'; + return $this->render('@RoadizRozier/users/list.html.twig', $this->assignation); } - protected function denyAccessUnlessItemGranted(PersistableInterface $item): void + /** + * @param Request $request + * @param int $userId + * + * @return Response + * @throws RuntimeError + */ + public function editAction(Request $request, int $userId): Response { - parent::denyAccessUnlessItemGranted($item); + $this->denyAccessUnlessGranted('ROLE_BACKEND_USER'); - if (!$item instanceof User) { - throw new \RuntimeException('Invalid item type.'); - } - $requestUser = $this->getUser(); if ( !( - $this->isGranted('ROLE_ACCESS_USERS') || - ($requestUser instanceof User && $requestUser->getId() === $item->getId()) + $this->isGranted('ROLE_ACCESS_USERS') || + ($this->getUser() instanceof User && $this->getUser()->getId() == $userId) ) ) { throw $this->createAccessDeniedException("You don't have access to this page: ROLE_ACCESS_USERS"); } - if (!$this->isGranted(Role::ROLE_SUPERADMIN) && $item->isSuperAdmin()) { + $user = $this->em()->find(User::class, $userId); + if ($user === null) { + throw new ResourceNotFoundException(); + } + if (!$this->isGranted(Role::ROLE_SUPERADMIN) && $user->isSuperAdmin()) { throw $this->createAccessDeniedException("You cannot edit a super admin."); } - } - protected function getEntityName(PersistableInterface $item): string - { - if (!$item instanceof User) { - throw new \RuntimeException('Invalid item type.'); - } - return $item->getUsername(); - } - - protected function getDefaultOrder(Request $request): array - { - return ['username' => 'ASC']; - } + $form = $this->createForm(UserType::class, $user); + $form->handleRequest($request); - protected function createUpdateEvent(PersistableInterface $item) - { - if (!$item instanceof User) { - throw new \RuntimeException('Invalid item type.'); - } - /* - * If pictureUrl is empty, use default Gravatar image. - */ - if ($item->getPictureUrl() == '' && $this->useGravatar) { - $item->setPictureUrl($item->getGravatarUrl()); + if ($form->isSubmitted() && $form->isValid()) { + $this->em()->flush(); + $msg = $this->getTranslator()->trans( + 'user.%name%.updated', + ['%name%' => $user->getUsername()] + ); + $this->publishConfirmMessage($request, $msg); + /* + * Force redirect to avoid resending form when refreshing page + */ + return $this->redirectToRoute( + 'usersEditPage', + ['userId' => $user->getId()] + ); } - return parent::createUpdateEvent($item); // TODO: Change the autogenerated stub + $this->assignation['user'] = $user; + $this->assignation['form'] = $form->createView(); + + return $this->render('@RoadizRozier/users/edit.html.twig', $this->assignation); } /** * @param Request $request - * @param int $id + * @param int $userId * * @return Response * @throws RuntimeError */ - public function editDetailsAction(Request $request, int $id): Response + public function editDetailsAction(Request $request, int $userId): Response { - $this->denyAccessUnlessGranted($this->getRequiredEditionRole()); - $this->additionalAssignation($request); + $this->denyAccessUnlessGranted('ROLE_BACKEND_USER'); - /** @var mixed|object|null $item */ - $item = $this->em()->find($this->getEntityClass(), $id); - if (!($item instanceof PersistableInterface)) { - throw $this->createNotFoundException(); + if ( + !( + $this->isGranted('ROLE_ACCESS_USERS') || + ($this->getUser() instanceof User && $this->getUser()->getId() === $userId) + ) + ) { + throw $this->createAccessDeniedException("You don't have access to this page: ROLE_ACCESS_USERS"); } + $user = $this->em()->find(User::class, $userId); - $this->prepareWorkingItem($item); - $this->denyAccessUnlessItemGranted($item); + if ($user === null) { + throw new ResourceNotFoundException(); + } + if (!$this->isGranted(Role::ROLE_SUPERADMIN) && $user->isSuperAdmin()) { + throw $this->createAccessDeniedException("You cannot edit a super admin."); + } - $form = $this->createForm(UserDetailsType::class, $item); + $form = $this->createForm(UserDetailsType::class, $user); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { /* - * Events are dispatched before entity manager is flushed - * to be able to throw exceptions before it is persisted. + * If pictureUrl is empty, use default Gravatar image. */ - $event = $this->createUpdateEvent($item); - $this->dispatchSingleOrMultipleEvent($event); - $this->em()->flush(); + if ($user->getPictureUrl() == '') { + $user->setPictureUrl($user->getGravatarUrl()); + } - /* - * Event that requires that EM is flushed - */ - $postEvent = $this->createPostUpdateEvent($item); - $this->dispatchSingleOrMultipleEvent($postEvent); + $this->em()->flush(); $msg = $this->getTranslator()->trans( - '%namespace%.%item%.was_updated', - [ - '%item%' => $this->getEntityName($item), - '%namespace%' => $this->getTranslator()->trans($this->getNamespace()) - ] + 'user.%name%.updated', + ['%name%' => $user->getUsername()] ); - $this->publishConfirmMessage($request, $msg, $item); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page */ return $this->redirectToRoute( 'usersEditDetailsPage', - ['id' => $item->getId()] + ['userId' => $user->getId()] ); } + $this->assignation['user'] = $user; $this->assignation['form'] = $form->createView(); - $this->assignation['item'] = $item; - return $this->render( - $this->getTemplateFolder() . '/editDetails.html.twig', - $this->assignation, - null, - $this->getTemplateNamespace() - ); + return $this->render('@RoadizRozier/users/editDetails.html.twig', $this->assignation); } + /** + * @param Request $request + * + * @return Response + * @throws RuntimeError + */ + public function addAction(Request $request): Response + { + $this->denyAccessUnlessGranted('ROLE_ACCESS_USERS'); + + $user = new User(); + $user->sendCreationConfirmationEmail(true); + $this->assignation['user'] = $user; + $form = $this->createForm(AddUserType::class, $user); + $form->handleRequest($request); - protected function additionalAssignation(Request $request): void - { - parent::additionalAssignation($request); + if ($form->isSubmitted() && $form->isValid()) { + $this->em()->persist($user); + $this->em()->flush(); - if (null !== $this->getBulkEnableRouteName()) { - $bulkEnableForm = $this->createEnableBulkForm(true); - $this->assignation['bulkEnableForm'] = $bulkEnableForm->createView(); - $this->assignation['hasBulkActions'] = true; - } + $msg = $this->getTranslator()->trans('user.%name%.created', ['%name%' => $user->getUsername()]); + $this->publishConfirmMessage($request, $msg); - if (null !== $this->getBulkDisableRouteName()) { - $bulkDisableForm = $this->createDisableBulkForm(true); - $this->assignation['bulkDisableForm'] = $bulkDisableForm->createView(); - $this->assignation['hasBulkActions'] = true; + return $this->redirectToRoute('usersHomePage'); } - } - /* - * User specific bulk actions - */ + $this->assignation['form'] = $form->createView(); - protected function createEnableBulkForm(bool $get = false, ?array $data = null): FormInterface - { - return $this->createBulkForm( - $this->getBulkEnableRouteName(), - 'bulk-enable', - $get, - $data - ); + return $this->render('@RoadizRozier/users/add.html.twig', $this->assignation); } - protected function createDisableBulkForm(bool $get = false, ?array $data = null): FormInterface + /** + * @param Request $request + * @param int $userId + * + * @return Response + * @throws RuntimeError + */ + public function deleteAction(Request $request, int $userId): Response { - return $this->createBulkForm( - $this->getBulkDisableRouteName(), - 'bulk-disable', - $get, - $data - ); - } + $this->denyAccessUnlessGranted('ROLE_ACCESS_USERS_DELETE'); + $user = $this->em()->find(User::class, (int) $userId); - private function getBulkEnableRouteName(): string - { - return 'usersBulkEnablePage'; - } + if ($user === null) { + throw new ResourceNotFoundException(); + } - private function getBulkDisableRouteName(): string - { - return 'usersBulkDisablePage'; - } + if (!$this->isGranted(Role::ROLE_SUPERADMIN) && $user->isSuperAdmin()) { + throw $this->createAccessDeniedException("You cannot edit a super admin."); + } - public function bulkEnableAction(Request $request): Response - { - return $this->bulkAction( - $request, - $this->getRequiredRole(), - $this->createEnableBulkForm(true), - $this->createEnableBulkForm(), - function (string $ids) { - return $this->createEnableBulkForm(false, [ - 'id' => $ids, - ]); - }, - $this->getTemplateFolder() . '/bulk_enable.html.twig', - '%namespace%.%item%.was_enabled', - function (PersistableInterface $item) { - if (!$item instanceof User) { - throw new \RuntimeException('Invalid item type.'); - } - $item->setEnabled(true); - }, - 'bulkEnableForm' - ); - } + $form = $this->createForm(FormType::class); + $form->handleRequest($request); - public function bulkDisableAction(Request $request): Response - { - return $this->bulkAction( - $request, - $this->getRequiredRole(), - $this->createDisableBulkForm(true), - $this->createDisableBulkForm(), - function (string $ids) { - return $this->createDisableBulkForm(false, [ - 'id' => $ids, - ]); - }, - $this->getTemplateFolder() . '/bulk_disable.html.twig', - '%namespace%.%item%.was_disabled', - function (PersistableInterface $item) { - if (!$item instanceof User) { - throw new \RuntimeException('Invalid item type.'); - } - $item->setEnabled(false); - }, - 'bulkDisableForm' - ); + if ($form->isSubmitted() && $form->isValid()) { + $this->em()->remove($user); + $this->em()->flush(); + $msg = $this->getTranslator()->trans( + 'user.%name%.deleted', + ['%name%' => $user->getUsername()] + ); + $this->publishConfirmMessage($request, $msg); + /* + * Force redirect to avoid resending form when refreshing page + */ + return $this->redirectToRoute('usersHomePage'); + } + + $this->assignation['user'] = $user; + $this->assignation['form'] = $form->createView(); + + return $this->render('@RoadizRozier/users/delete.html.twig', $this->assignation); } } diff --git a/src/Controllers/Users/UsersGroupsController.php b/src/Controllers/Users/UsersGroupsController.php index 62103ffc..4c5ba264 100644 --- a/src/Controllers/Users/UsersGroupsController.php +++ b/src/Controllers/Users/UsersGroupsController.php @@ -27,52 +27,53 @@ public function editGroupsAction(Request $request, int $userId): Response /** @var User|null $user */ $user = $this->em()->find(User::class, $userId); - if ($user === null) { - throw new ResourceNotFoundException(); - } - - $this->assignation['user'] = $user; - - $form = $this->buildEditGroupsForm($user); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $data = $form->getData(); - if ($data['userId'] == $user->getId()) { - if (array_key_exists('group', $data) && $data['group'][0] instanceof Group) { - $group = $data['group'][0]; - } elseif (array_key_exists('group', $data) && is_numeric($data['group'])) { - $group = $this->em()->find(Group::class, $data['group']); - } else { - $group = null; - } - if ($group !== null) { - $user->addGroup($group); - $this->em()->flush(); - - $this->dispatchEvent(new UserJoinedGroupEvent($user, $group)); - - $msg = $this->getTranslator()->trans('user.%user%.group.%group%.linked', [ - '%user%' => $user->getUserName(), - '%group%' => $group->getName(), - ]); - $this->publishConfirmMessage($request, $msg, $user); - - /* - * Force redirect to avoid resending form when refreshing page - */ - return $this->redirectToRoute( - 'usersEditGroupsPage', - ['userId' => $user->getId()] - ); + if ($user !== null) { + $this->assignation['user'] = $user; + + $form = $this->buildEditGroupsForm($user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $data = $form->getData(); + if ($data['userId'] == $user->getId()) { + if (array_key_exists('group', $data) && $data['group'][0] instanceof Group) { + $group = $data['group'][0]; + } elseif (array_key_exists('group', $data) && is_numeric($data['group'])) { + $group = $this->em()->find(Group::class, $data['group']); + } else { + $group = null; + } + + if ($group !== null) { + $user->addGroup($group); + $this->em()->flush(); + + $this->dispatchEvent(new UserJoinedGroupEvent($user, $group)); + + $msg = $this->getTranslator()->trans('user.%user%.group.%group%.linked', [ + '%user%' => $user->getUserName(), + '%group%' => $group->getName(), + ]); + $this->publishConfirmMessage($request, $msg); + + /* + * Force redirect to avoid resending form when refreshing page + */ + return $this->redirectToRoute( + 'usersEditGroupsPage', + ['userId' => $user->getId()] + ); + } } } - } - $this->assignation['form'] = $form->createView(); + $this->assignation['form'] = $form->createView(); + + return $this->render('@RoadizRozier/users/groups.html.twig', $this->assignation); + } - return $this->render('@RoadizRozier/users/groups.html.twig', $this->assignation); + throw new ResourceNotFoundException(); } public function removeGroupAction(Request $request, int $userId, int $groupId): Response @@ -84,47 +85,44 @@ public function removeGroupAction(Request $request, int $userId, int $groupId): /** @var Group|null $group */ $group = $this->em()->find(Group::class, $groupId); - if ($user === null) { - throw new ResourceNotFoundException(); - } - if ($group === null) { - throw new ResourceNotFoundException(); - } - if (!$this->isGranted($group)) { throw $this->createAccessDeniedException(); } - $this->assignation['user'] = $user; - $this->assignation['group'] = $group; + if ($user !== null) { + $this->assignation['user'] = $user; + $this->assignation['group'] = $group; - $form = $this->createForm(FormType::class); - $form->handleRequest($request); + $form = $this->createForm(FormType::class); + $form->handleRequest($request); - if ($form->isSubmitted() && $form->isValid()) { - $user->removeGroup($group); - $this->em()->flush(); + if ($form->isSubmitted() && $form->isValid()) { + $user->removeGroup($group); + $this->em()->flush(); - $this->dispatchEvent(new UserLeavedGroupEvent($user, $group)); + $this->dispatchEvent(new UserLeavedGroupEvent($user, $group)); - $msg = $this->getTranslator()->trans('user.%user%.group.%group%.removed', [ - '%user%' => $user->getUserName(), - '%group%' => $group->getName(), - ]); - $this->publishConfirmMessage($request, $msg, $user); + $msg = $this->getTranslator()->trans('user.%user%.group.%group%.removed', [ + '%user%' => $user->getUserName(), + '%group%' => $group->getName(), + ]); + $this->publishConfirmMessage($request, $msg); - /* - * Force redirect to avoid resending form when refreshing page - */ - return $this->redirectToRoute( - 'usersEditGroupsPage', - ['userId' => $user->getId()] - ); - } + /* + * Force redirect to avoid resending form when refreshing page + */ + return $this->redirectToRoute( + 'usersEditGroupsPage', + ['userId' => $user->getId()] + ); + } + + $this->assignation['form'] = $form->createView(); - $this->assignation['form'] = $form->createView(); + return $this->render('@RoadizRozier/users/removeGroup.html.twig', $this->assignation); + } - return $this->render('@RoadizRozier/users/removeGroup.html.twig', $this->assignation); + throw new ResourceNotFoundException(); } /** diff --git a/src/Controllers/Users/UsersRolesController.php b/src/Controllers/Users/UsersRolesController.php index ba80f0af..04b0a57a 100644 --- a/src/Controllers/Users/UsersRolesController.php +++ b/src/Controllers/Users/UsersRolesController.php @@ -53,7 +53,7 @@ public function editRolesAction(Request $request, int $userId): Response '%role%' => $role->getRole(), ]); - $this->publishConfirmMessage($request, $msg, $user); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page @@ -114,7 +114,7 @@ public function removeRoleAction(Request $request, int $userId, int $roleId): Re 'user.%name%.role_removed', ['%name%' => $role->getRole()] ); - $this->publishConfirmMessage($request, $msg, $user); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page diff --git a/src/Controllers/Users/UsersSecurityController.php b/src/Controllers/Users/UsersSecurityController.php index 35949690..27c178c4 100644 --- a/src/Controllers/Users/UsersSecurityController.php +++ b/src/Controllers/Users/UsersSecurityController.php @@ -45,7 +45,7 @@ public function securityAction(Request $request, int $userId): Response ['%name%' => $user->getUsername()] ); - $this->publishConfirmMessage($request, $msg, $user); + $this->publishConfirmMessage($request, $msg); /* * Force redirect to avoid resending form when refreshing page diff --git a/src/Controllers/WebhookController.php b/src/Controllers/WebhookController.php index 60bccc70..1c60d4ce 100644 --- a/src/Controllers/WebhookController.php +++ b/src/Controllers/WebhookController.php @@ -12,20 +12,21 @@ use RZ\Roadiz\CoreBundle\Webhook\WebhookDispatcher; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormError; -use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -final class WebhookController extends AbstractAdminWithBulkController +final class WebhookController extends AbstractAdminController { + private WebhookDispatcher $webhookDispatcher; + public function __construct( - private readonly WebhookDispatcher $webhookDispatcher, - FormFactoryInterface $formFactory, + WebhookDispatcher $webhookDispatcher, SerializerInterface $serializer, UrlGeneratorInterface $urlGenerator ) { - parent::__construct($formFactory, $serializer, $urlGenerator); + parent::__construct($serializer, $urlGenerator); + $this->webhookDispatcher = $webhookDispatcher; } public function triggerAction(Request $request, string $id): Response @@ -56,7 +57,7 @@ public function triggerAction(Request $request, string $id): Response '%seconds%' => $item->getThrottleSeconds(), ] ); - $this->publishConfirmMessage($request, $msg, $item); + $this->publishConfirmMessage($request, $msg); return $this->redirect($this->urlGenerator->generate( $this->getDefaultRouteName(), @@ -132,8 +133,4 @@ protected function getEntityName(PersistableInterface $item): string } return ''; } - protected function getBulkDeleteRouteName(): ?string - { - return 'webhooksBulkDeletePage'; - } } diff --git a/src/Event/UserActionsMenuEvent.php b/src/Event/UserActionsMenuEvent.php deleted file mode 100644 index f5284c61..00000000 --- a/src/Event/UserActionsMenuEvent.php +++ /dev/null @@ -1,29 +0,0 @@ -actions[] = [ - 'label' => $label, - 'path' => $path, - 'icon' => $icon, - ]; - } - - public function getActions(): array - { - return $this->actions; - } -} diff --git a/src/Explorer/ConfigurableExplorerItem.php b/src/Explorer/ConfigurableExplorerItem.php index bc86157e..4fb67bfa 100644 --- a/src/Explorer/ConfigurableExplorerItem.php +++ b/src/Explorer/ConfigurableExplorerItem.php @@ -55,12 +55,9 @@ public function getAlternativeDisplayable(): ?string { $alt = $this->configuration['classname']; if (!empty($this->configuration['alt_displayable'])) { - $altDisplayableCallable = [$this->entity, $this->configuration['alt_displayable']]; - if (\is_callable($altDisplayableCallable)) { - $alt = call_user_func($altDisplayableCallable); - if ($alt instanceof \DateTimeInterface) { - $alt = $alt->format('c'); - } + $alt = call_user_func([$this->entity, $this->configuration['alt_displayable']]); + if ($alt instanceof \DateTimeInterface) { + $alt = $alt->format('c'); } } return (new UnicodeString($alt ?? ''))->truncate(30, '…')->toString(); @@ -71,12 +68,9 @@ public function getAlternativeDisplayable(): ?string */ public function getDisplayable(): string { - $displayableCallable = [$this->entity, $this->configuration['displayable']]; - if (\is_callable($displayableCallable)) { - $displayable = call_user_func($displayableCallable); - if ($displayable instanceof \DateTimeInterface) { - $displayable = $displayable->format('c'); - } + $displayable = call_user_func([$this->entity, $this->configuration['displayable']]); + if ($displayable instanceof \DateTimeInterface) { + $displayable = $displayable->format('c'); } return (new UnicodeString($displayable ?? ''))->truncate(30, '…')->toString(); } @@ -94,14 +88,11 @@ protected function getThumbnail(): ?array /** @var DocumentInterface|null $thumbnail */ $thumbnail = null; if (!empty($this->configuration['thumbnail'])) { - $thumbnailCallable = [$this->entity, $this->configuration['thumbnail']]; - if (\is_callable($thumbnailCallable)) { - $thumbnail = call_user_func($thumbnailCallable); - if ($thumbnail instanceof Collection && $thumbnail->count() > 0 && $thumbnail->first() instanceof DocumentInterface) { - $thumbnail = $thumbnail->first(); - } elseif (is_array($thumbnail) && count($thumbnail) > 0 && $thumbnail[0] instanceof DocumentInterface) { - $thumbnail = $thumbnail[0]; - } + $thumbnail = call_user_func([$this->entity, $this->configuration['thumbnail']]); + if ($thumbnail instanceof Collection && $thumbnail->count() > 0 && $thumbnail->first() instanceof DocumentInterface) { + $thumbnail = $thumbnail->first(); + } elseif (is_array($thumbnail) && count($thumbnail) > 0 && $thumbnail[0] instanceof DocumentInterface) { + $thumbnail = $thumbnail[0]; } } diff --git a/src/Explorer/FolderExplorerItem.php b/src/Explorer/FolderExplorerItem.php index 9a85e7ae..10588ae0 100644 --- a/src/Explorer/FolderExplorerItem.php +++ b/src/Explorer/FolderExplorerItem.php @@ -35,9 +35,7 @@ public function getAlternativeDisplayable(): ?string /** @var Folder|null $parent */ $parent = $this->folder->getParent(); if (null !== $parent) { - return $parent->getTranslatedFolders()->first() ? - $parent->getTranslatedFolders()->first()->getName() : - $parent->getName(); + return $parent->getTranslatedFolders()->first()->getName(); } return ''; } @@ -47,9 +45,7 @@ public function getAlternativeDisplayable(): ?string */ public function getDisplayable(): string { - return $this->folder->getTranslatedFolders()->first() ? - $this->folder->getTranslatedFolders()->first()->getName() : - $this->folder->getName(); + return $this->folder->getTranslatedFolders()->first()->getName(); } /** diff --git a/src/Explorer/UserExplorerItem.php b/src/Explorer/UserExplorerItem.php index b7012f16..eb63dc4c 100644 --- a/src/Explorer/UserExplorerItem.php +++ b/src/Explorer/UserExplorerItem.php @@ -62,7 +62,7 @@ public function getOriginal(): User protected function getEditItemPath(): ?string { return $this->urlGenerator->generate('usersEditPage', [ - 'id' => $this->user->getId() + 'userId' => $this->user->getId() ]); } } diff --git a/src/Forms/AddUserType.php b/src/Forms/AddUserType.php index 2cbff0d7..debb57c3 100644 --- a/src/Forms/AddUserType.php +++ b/src/Forms/AddUserType.php @@ -7,6 +7,9 @@ use RZ\Roadiz\CoreBundle\Form\GroupsType; use Symfony\Component\Form\FormBuilderInterface; +/** + * @package Themes\Rozier\Forms + */ class AddUserType extends UserType { public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Forms/CustomFormFieldType.php b/src/Forms/CustomFormFieldType.php index 0fc5675a..998ad6f6 100644 --- a/src/Forms/CustomFormFieldType.php +++ b/src/Forms/CustomFormFieldType.php @@ -9,11 +9,13 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +/** + * @package Themes\Rozier\Forms + */ class CustomFormFieldType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void @@ -48,9 +50,9 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]) ->add( 'defaultValues', - TextareaType::class, + TextType::class, [ - 'label' => 'customFormField.defaultValues', + 'label' => 'defaultValues', 'required' => false, 'attr' => [ 'placeholder' => 'enter_values_comma_separated', diff --git a/src/Forms/CustomFormType.php b/src/Forms/CustomFormType.php deleted file mode 100644 index 57858f3b..00000000 --- a/src/Forms/CustomFormType.php +++ /dev/null @@ -1,121 +0,0 @@ -security = $security; - } - - public function buildForm(FormBuilderInterface $builder, array $options): void - { - $builder->add('displayName', TextType::class, [ - 'label' => 'customForm.displayName', - 'empty_data' => '', - ]) - ->add('description', MarkdownType::class, [ - 'label' => 'description', - 'required' => false, - ]) - ->add('email', TextType::class, [ - 'label' => 'email', - 'required' => false, - 'constraints' => [ - new Callback(function ($value, ExecutionContextInterface $context) { - $emails = array_filter( - array_map('trim', explode(',', $value ?? '')) - ); - foreach ($emails as $email) { - if (false === filter_var($email, FILTER_VALIDATE_EMAIL)) { - $context->buildViolation('{{ value }} is not a valid email address.') - ->setParameter('{{ value }}', $email) - ->setCode(Email::INVALID_FORMAT_ERROR) - ->addViolation(); - } - } - }), - ], - ]) - ; - if ($this->security->isGranted('ROLE_ACCESS_CUSTOMFORMS_RETENTION')) { - $builder->add('retentionTime', ChoiceType::class, [ - 'label' => 'customForm.retentionTime', - 'help' => 'customForm.retentionTime.help', - 'required' => false, - 'placeholder' => 'customForm.retentionTime.always', - 'choices' => [ - 'customForm.retentionTime.one_week' => 'P7D', - 'customForm.retentionTime.two_weeks' => 'P14D', - 'customForm.retentionTime.one_month' => 'P1M', - 'customForm.retentionTime.three_months' => 'P3M', - 'customForm.retentionTime.six_months' => 'P6M', - 'customForm.retentionTime.one_year' => 'P1Y', - 'customForm.retentionTime.two_years' => 'P2Y', - ] - ]); - } - $builder->add('open', CheckboxType::class, [ - 'label' => 'customForm.open', - 'required' => false, - ]) - ->add('closeDate', DateTimeType::class, [ - 'label' => 'customForm.closeDate', - 'required' => false, - 'date_widget' => 'single_text', - 'date_format' => 'yyyy-MM-dd', - 'attr' => [ - 'class' => 'rz-datetime-field', - ], - 'placeholder' => [ - 'hour' => 'hour', - 'minute' => 'minute', - ], - ]) - ->add('color', ColorType::class, [ - 'label' => 'customForm.color', - 'required' => false, - ]); - } - - public function getBlockPrefix(): string - { - return 'customform'; - } - - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'label' => false, - 'name' => '', - 'data_class' => CustomForm::class, - 'attr' => [ - 'class' => 'uk-form custom-form-form', - ], - ]); - $resolver->setAllowedTypes('name', 'string'); - } -} diff --git a/src/Forms/DataTransformer/TagTransformer.php b/src/Forms/DataTransformer/TagTransformer.php index f90162b2..a5519023 100644 --- a/src/Forms/DataTransformer/TagTransformer.php +++ b/src/Forms/DataTransformer/TagTransformer.php @@ -4,11 +4,16 @@ namespace Themes\Rozier\Forms\DataTransformer; +use Doctrine\Common\Collections\Collection; use Doctrine\Persistence\ObjectManager; +use RZ\Roadiz\Core\AbstractEntities\AbstractEntity; use RZ\Roadiz\CoreBundle\Entity\Tag; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; +/** + * @package Themes\Rozier\Forms\DataTransformer + */ class TagTransformer implements DataTransformerInterface { private ObjectManager $manager; diff --git a/src/Forms/DocumentEditType.php b/src/Forms/DocumentEditType.php index b016ee93..09bf4c1b 100644 --- a/src/Forms/DocumentEditType.php +++ b/src/Forms/DocumentEditType.php @@ -17,7 +17,7 @@ use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Validator\Constraints\File; use Symfony\Component\Validator\Constraints\LessThanOrEqual; use Symfony\Component\Validator\Constraints\NotBlank; @@ -118,14 +118,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]); } - if ($document->isProcessable()) { - $builder->add('imageCropAlignment', ImageCropAlignmentType::class, [ - 'label' => 'document.imageCropAlignment', - 'help' => 'document.imageCropAlignment.help', - 'required' => false, - ]); - } - /* * Display thumbnails only if current Document is original. */ diff --git a/src/Forms/DocumentTranslationType.php b/src/Forms/DocumentTranslationType.php index ea1fd10a..946c5821 100644 --- a/src/Forms/DocumentTranslationType.php +++ b/src/Forms/DocumentTranslationType.php @@ -4,8 +4,8 @@ namespace Themes\Rozier\Forms; -use RZ\Roadiz\CoreBundle\Entity\DocumentTranslation; use RZ\Roadiz\CoreBundle\Form\MarkdownType; +use RZ\Roadiz\CoreBundle\Entity\DocumentTranslation; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -29,11 +29,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, ]) ->add('copyright', TextType::class, [ - 'label' => 'document.copyrightHolder', - 'required' => false, - ]) - ->add('externalUrl', TextType::class, [ - 'label' => 'document.externalUrl', + 'label' => 'copyright', 'required' => false, ]); } diff --git a/src/Forms/DynamicType.php b/src/Forms/DynamicType.php index dbaf6999..3a65e897 100644 --- a/src/Forms/DynamicType.php +++ b/src/Forms/DynamicType.php @@ -9,6 +9,11 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\OptionsResolver\OptionsResolver; +/** + * Class DynamicType + * + * @package Themes\Rozier\Forms + */ class DynamicType extends AbstractType { /** diff --git a/src/Forms/FolderTranslationType.php b/src/Forms/FolderTranslationType.php index ba030249..16b25967 100644 --- a/src/Forms/FolderTranslationType.php +++ b/src/Forms/FolderTranslationType.php @@ -10,6 +10,9 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +/** + * @package Themes\Rozier\Forms + */ class FolderTranslationType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Forms/FolderType.php b/src/Forms/FolderType.php index d05469dc..83ab4f95 100644 --- a/src/Forms/FolderType.php +++ b/src/Forms/FolderType.php @@ -12,6 +12,9 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +/** + * @package Themes\Rozier\Forms + */ class FolderType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Forms/ImageCropAlignmentType.php b/src/Forms/ImageCropAlignmentType.php deleted file mode 100644 index 5a79bd2d..00000000 --- a/src/Forms/ImageCropAlignmentType.php +++ /dev/null @@ -1,44 +0,0 @@ -setDefaults([ - 'label' => 'image_crop_alignment', - 'required' => false, - 'placeholder' => 'image_crop_alignment.none', - 'expanded' => true, - 'choices' => [ - 'image_crop_alignment.top-left' => 'top-left', - 'image_crop_alignment.top' => 'top', - 'image_crop_alignment.top-right' => 'top-right', - 'image_crop_alignment.left' => 'left', - 'image_crop_alignment.center' => 'center', - 'image_crop_alignment.right' => 'right', - 'image_crop_alignment.bottom-left' => 'bottom-left', - 'image_crop_alignment.bottom' => 'bottom', - 'image_crop_alignment.bottom-right' => 'bottom-right', - ] - ]); - } - - public function getBlockPrefix(): string - { - return 'image_crop_alignment'; - } - - - public function getParent(): string - { - return ChoiceType::class; - } -} diff --git a/src/Forms/LoginType.php b/src/Forms/LoginType.php index 3f88811c..37b0804e 100644 --- a/src/Forms/LoginType.php +++ b/src/Forms/LoginType.php @@ -65,7 +65,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ], ]); - if ($this->requestStack->getMainRequest()?->query->has('_home')) { + if ($this->requestStack->getMasterRequest()->query->has('_home')) { $builder->add('_target_path', HiddenType::class, [ 'data' => $this->urlGenerator->generate('adminHomePage') ]); diff --git a/src/Forms/Node/AddNodeType.php b/src/Forms/Node/AddNodeType.php index ffc863f6..336d4dea 100644 --- a/src/Forms/Node/AddNodeType.php +++ b/src/Forms/Node/AddNodeType.php @@ -9,17 +9,22 @@ use RZ\Roadiz\CoreBundle\Form\DataTransformer\NodeTypeTransformer; use RZ\Roadiz\CoreBundle\Form\NodeTypesType; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Event\SubmitEvent; +use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\Event\PostSubmitEvent; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\Event\SubmitEvent; +/** + * @package Themes\Rozier\Forms\Node + */ class AddNodeType extends AbstractType { protected ManagerRegistry $managerRegistry; diff --git a/src/Forms/NodeSource/NodeSourceCustomFormType.php b/src/Forms/NodeSource/NodeSourceCustomFormType.php index 3b6fa151..b46f3a90 100644 --- a/src/Forms/NodeSource/NodeSourceCustomFormType.php +++ b/src/Forms/NodeSource/NodeSourceCustomFormType.php @@ -85,7 +85,7 @@ public function onPreSetData(FormEvent $event): void $event->setData($this->managerRegistry ->getRepository(CustomForm::class) - ->findByNodeAndFieldName($nodeSource->getNode(), $nodeTypeField->getName())); + ->findByNodeAndField($nodeSource->getNode(), $nodeTypeField)); } /** diff --git a/src/Forms/NodeSource/NodeSourceDocumentType.php b/src/Forms/NodeSource/NodeSourceDocumentType.php index 1e1aeba2..0278872f 100644 --- a/src/Forms/NodeSource/NodeSourceDocumentType.php +++ b/src/Forms/NodeSource/NodeSourceDocumentType.php @@ -88,9 +88,9 @@ public function onPreSetData(FormEvent $event): void $event->setData($this->managerRegistry ->getRepository(Document::class) - ->findByNodeSourceAndFieldName( + ->findByNodeSourceAndField( $nodeSource, - $nodeTypeField->getName() + $nodeTypeField )); } diff --git a/src/Forms/NodeSource/NodeSourceJoinType.php b/src/Forms/NodeSource/NodeSourceJoinType.php index b7810c44..27b90477 100644 --- a/src/Forms/NodeSource/NodeSourceJoinType.php +++ b/src/Forms/NodeSource/NodeSourceJoinType.php @@ -55,9 +55,8 @@ public function buildView(FormView $view, FormInterface $form, array $options): $configuration = $this->getFieldConfiguration($options); $displayableData = []; - /** @var callable $callable */ - $callable = [$options['nodeSource'], $options['nodeTypeField']->getGetterName()]; - $entities = call_user_func($callable); + + $entities = call_user_func([$options['nodeSource'], $options['nodeTypeField']->getGetterName()]); if ($entities instanceof \Traversable) { /** @var PersistableInterface $entity */ @@ -69,9 +68,8 @@ public function buildView(FormView $view, FormInterface $form, array $options): 'id' => $entity->getId(), 'classname' => $configuration['classname'], ]; - $displayableCallable = [$entity, $configuration['displayable']]; - if (\is_callable($displayableCallable)) { - $data['name'] = call_user_func($displayableCallable); + if (is_callable([$entity, $configuration['displayable']])) { + $data['name'] = call_user_func([$entity, $configuration['displayable']]); } $displayableData[] = $data; } @@ -83,9 +81,8 @@ public function buildView(FormView $view, FormInterface $form, array $options): 'id' => $entities->getId(), 'classname' => $configuration['classname'], ]; - $displayableCallable = [$entities, $configuration['displayable']]; - if (\is_callable($displayableCallable)) { - $data['name'] = call_user_func($displayableCallable); + if (is_callable([$entities, $configuration['displayable']])) { + $data['name'] = call_user_func([$entities, $configuration['displayable']]); } $displayableData[] = $data; } diff --git a/src/Forms/NodeSource/NodeSourceProviderType.php b/src/Forms/NodeSource/NodeSourceProviderType.php index 9132793c..3471cfdb 100644 --- a/src/Forms/NodeSource/NodeSourceProviderType.php +++ b/src/Forms/NodeSource/NodeSourceProviderType.php @@ -105,9 +105,7 @@ public function buildView(FormView $view, FormInterface $form, array $options): $provider = $this->getProvider($configuration, $options); $displayableData = []; - /** @var callable $callable */ - $callable = [$options['nodeSource'], $options['nodeTypeField']->getGetterName()]; - $ids = call_user_func($callable); + $ids = call_user_func([$options['nodeSource'], $options['nodeTypeField']->getGetterName()]); if (!is_array($ids)) { $entities = $provider->getItemsById([$ids]); } else { diff --git a/src/Forms/NodeSource/NodeSourceType.php b/src/Forms/NodeSource/NodeSourceType.php index 59d4fbf5..366408a4 100644 --- a/src/Forms/NodeSource/NodeSourceType.php +++ b/src/Forms/NodeSource/NodeSourceType.php @@ -18,7 +18,6 @@ use RZ\Roadiz\CoreBundle\Form\MultipleEnumerationType; use RZ\Roadiz\CoreBundle\Form\YamlType; use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeTypeFieldVoter; -use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CountryType; @@ -32,6 +31,7 @@ use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Type; diff --git a/src/Forms/NodeTagsType.php b/src/Forms/NodeTagsType.php new file mode 100644 index 00000000..cecf98ac --- /dev/null +++ b/src/Forms/NodeTagsType.php @@ -0,0 +1,63 @@ +managerRegistry = $managerRegistry; + } + + /** + * {@inheritdoc} + * + * @param FormBuilderInterface $builder + * @param array $options + */ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->add('tags', TagsType::class, [ + 'by_reference' => false + ]); + $builder->get('tags') + ->addModelTransformer(new TagTransformer($this->managerRegistry->getManager())); + } + + /** + * {@inheritdoc} + * + * @param OptionsResolver $resolver + */ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefault('data_class', Node::class); + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix(): string + { + return 'node_tags'; + } +} diff --git a/src/Forms/NodeType.php b/src/Forms/NodeType.php index 9cf491af..d3585452 100644 --- a/src/Forms/NodeType.php +++ b/src/Forms/NodeType.php @@ -5,6 +5,7 @@ namespace Themes\Rozier\Forms; use RZ\Roadiz\CoreBundle\Entity\Node; +use RZ\Roadiz\CoreBundle\Form\Constraint\UniqueNodeName; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; @@ -21,6 +22,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => 'nodeName', 'empty_data' => '', 'help' => 'node.nodeName.help', + 'constraints' => [ + new UniqueNodeName([ + 'currentValue' => $options['nodeName'], + ]), + ] ]) ->add('dynamicNodeName', CheckboxType::class, [ 'label' => 'node.dynamicNodeName', @@ -29,10 +35,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]) ; - /** @var Node|null $node */ - $node = $builder->getData(); - $isReachable = null !== $node && $node->getNodeType()->isReachable(); - if ($isReachable) { + if (null !== $builder->getData() && $builder->getData()->getNodeType()->isReachable()) { $builder->add('home', CheckboxType::class, [ 'label' => 'node.isHome', 'required' => false, @@ -53,7 +56,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]) ; - if ($isReachable) { + if (null !== $builder->getData() && $builder->getData()->getNodeType()->isReachable()) { $builder->add('ttl', IntegerType::class, [ 'label' => 'node.ttl', 'help' => 'node_time_to_live_cache_on_front_controller', @@ -77,6 +80,7 @@ public function configureOptions(OptionsResolver $resolver): void 'class' => 'uk-form node-form', ], ]); - $resolver->setAllowedTypes('nodeName', ['string', 'null']); + + $resolver->setAllowedTypes('nodeName', 'string'); } } diff --git a/src/Forms/NodeTypeFieldType.php b/src/Forms/NodeTypeFieldType.php index 2d779998..46d15fa1 100644 --- a/src/Forms/NodeTypeFieldType.php +++ b/src/Forms/NodeTypeFieldType.php @@ -13,6 +13,9 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +/** + * @package Themes\Rozier\Forms + */ class NodeTypeFieldType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void @@ -72,7 +75,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, ]) ->add('defaultValues', DynamicType::class, [ - 'label' => 'nodeTypeField.defaultValues', + 'label' => 'defaultValues', 'required' => false, 'help' => 'for_children_node_and_node_references_enter_node_type_names_comma_separated', 'attr' => [ diff --git a/src/Forms/NodeTypeType.php b/src/Forms/NodeTypeType.php index beb37c1a..3601c424 100644 --- a/src/Forms/NodeTypeType.php +++ b/src/Forms/NodeTypeType.php @@ -41,16 +41,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'help' => 'enables_published_at_field_for_time_based_publication', ]) - ->add('attributable', CheckboxType::class, [ - 'label' => 'attributable', - 'required' => false, - 'help' => 'enables_node_attributes_for_this_type', - ]) - ->add('sortingAttributesByWeight', CheckboxType::class, [ - 'label' => 'sortingAttributesByWeight', - 'required' => false, - 'help' => 'sort_attributes_by_weight_for_this_type', - ]) ->add('reachable', CheckboxType::class, [ 'label' => 'reachable', 'required' => false, diff --git a/src/Forms/RedirectionType.php b/src/Forms/RedirectionType.php index 1839e8ce..b0d22a9b 100644 --- a/src/Forms/RedirectionType.php +++ b/src/Forms/RedirectionType.php @@ -13,6 +13,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\OptionsResolver\OptionsResolver; +/** + * @package Themes\Rozier\Forms + */ class RedirectionType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Forms/TranstypeType.php b/src/Forms/TranstypeType.php index bb32d3b5..2ba0eba5 100644 --- a/src/Forms/TranstypeType.php +++ b/src/Forms/TranstypeType.php @@ -14,6 +14,9 @@ use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; +/** + * @package Themes\Rozier\Forms + */ class TranstypeType extends AbstractType { protected ManagerRegistry $managerRegistry; diff --git a/src/Forms/UserType.php b/src/Forms/UserType.php index 0f4f8c9b..65bd4b39 100644 --- a/src/Forms/UserType.php +++ b/src/Forms/UserType.php @@ -12,6 +12,9 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +/** + * @package Themes\Rozier\Forms + */ class UserType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Models/CustomFormModel.php b/src/Models/CustomFormModel.php index 7724af37..fe4f00f5 100644 --- a/src/Models/CustomFormModel.php +++ b/src/Models/CustomFormModel.php @@ -8,6 +8,9 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Contracts\Translation\TranslatorInterface; +/** + * @package Themes\Rozier\Models + */ final class CustomFormModel implements ModelInterface { private CustomForm $customForm; diff --git a/src/Models/DocumentModel.php b/src/Models/DocumentModel.php index ad4e6f98..237c4376 100644 --- a/src/Models/DocumentModel.php +++ b/src/Models/DocumentModel.php @@ -13,6 +13,9 @@ use RZ\Roadiz\Documents\UrlGenerators\DocumentUrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +/** + * @package Themes\Rozier\Models + */ final class DocumentModel implements ModelInterface { public static array $thumbnailArray = [ @@ -109,10 +112,10 @@ public function toArray(): array $editUrl = null; } - $embedFinder = $this->embedFinderFactory?->createForPlatform( + $embedFinder = $this->embedFinderFactory->createForPlatform( $this->document->getEmbedPlatform(), $this->document->getEmbedId() - ) ?? null; + ); return [ 'id' => $id, @@ -140,7 +143,7 @@ public function toArray(): array : $this->document->getShortType(), 'shortMimeType' => $this->document->getShortMimeType(), 'thumbnail_80' => $thumbnail80Url, - 'url' => $previewUrl ?? $thumbnail80Url, + 'url' => $previewUrl ?? $thumbnail80Url ?? null, ]; } } diff --git a/src/Models/ModelInterface.php b/src/Models/ModelInterface.php index 9670e57c..60726121 100644 --- a/src/Models/ModelInterface.php +++ b/src/Models/ModelInterface.php @@ -4,6 +4,9 @@ namespace Themes\Rozier\Models; +/** + * @package Themes\Rozier\Models + */ interface ModelInterface { /** diff --git a/src/Models/NodeModel.php b/src/Models/NodeModel.php index 1ff17d4d..f90bb280 100644 --- a/src/Models/NodeModel.php +++ b/src/Models/NodeModel.php @@ -9,20 +9,25 @@ use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\NodesSourcesDocuments; use RZ\Roadiz\CoreBundle\Entity\Translation; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Bundle\SecurityBundle\Security; /** + * @package Themes\Rozier\Models * @Serializer\ExclusionPolicy("all") */ final class NodeModel implements ModelInterface { - public function __construct( - private Node $node, - private UrlGeneratorInterface $urlGenerator, - private Security $security - ) { + private Node $node; + private UrlGeneratorInterface $urlGenerator; + + /** + * @param Node $node + * @param UrlGeneratorInterface $urlGenerator + */ + public function __construct(Node $node, UrlGeneratorInterface $urlGenerator) + { + $this->node = $node; + $this->urlGenerator = $urlGenerator; } public function toArray(): array @@ -31,21 +36,18 @@ public function toArray(): array $nodeSource = $this->node->getNodeSources()->first(); if (false === $nodeSource) { - $result = [ + return [ 'id' => $this->node->getId(), 'title' => $this->node->getNodeName(), 'nodeName' => $this->node->getNodeName(), 'isPublished' => $this->node->isPublished(), + 'nodesEditPage' => $this->urlGenerator->generate('nodesEditPage', [ + 'nodeId' => $this->node->getId(), + ]), 'nodeType' => [ - 'color' => $this->node->getNodeType()->getColor() ?? '#000000', + 'color' => $this->node->getNodeType()->getColor() ] ]; - if ($this->security->isGranted(NodeVoter::EDIT_SETTING, $this->node)) { - $result['nodesEditPage'] = $this->urlGenerator->generate('nodesEditPage', [ - 'nodeId' => $this->node->getId(), - ]); - } - return $result; } /** @var NodesSourcesDocuments|false $thumbnail */ @@ -59,32 +61,25 @@ public function toArray(): array 'thumbnail' => $thumbnail ? $thumbnail->getDocument() : null, 'nodeName' => $this->node->getNodeName(), 'isPublished' => $this->node->isPublished(), + 'nodesEditPage' => $this->urlGenerator->generate('nodesEditSourcePage', [ + 'nodeId' => $this->node->getId(), + 'translationId' => $translation->getId(), + ]), 'nodeType' => [ - 'color' => $this->node->getNodeType()->getColor() ?? '#000000', + 'color' => $this->node->getNodeType()->getColor() ] ]; - if ($this->security->isGranted(NodeVoter::EDIT_CONTENT, $nodeSource)) { - $result['nodesEditPage'] = $this->urlGenerator->generate('nodesEditSourcePage', [ - 'nodeId' => $this->node->getId(), - 'translationId' => $translation->getId(), - ]); - } - $parent = $this->node->getParent(); if ($parent instanceof Node) { $result['parent'] = [ - 'title' => $parent->getNodeSources()->first() ? - $parent->getNodeSources()->first()->getTitle() : - $parent->getNodeName() + 'title' => $parent->getNodeSources()->first()->getTitle() ]; $subParent = $parent->getParent(); if ($subParent instanceof Node) { $result['subparent'] = [ - 'title' => $subParent->getNodeSources()->first() ? - $subParent->getNodeSources()->first()->getTitle() : - $subParent->getNodeName() + 'title' => $subParent->getNodeSources()->first()->getTitle() ]; } } diff --git a/src/Models/NodeSourceModel.php b/src/Models/NodeSourceModel.php index c8208a4c..73fef8c1 100644 --- a/src/Models/NodeSourceModel.php +++ b/src/Models/NodeSourceModel.php @@ -8,25 +8,28 @@ use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\NodesSourcesDocuments; use RZ\Roadiz\CoreBundle\Entity\Translation; -use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Bundle\SecurityBundle\Security; /** * @Serializer\ExclusionPolicy("all") */ final class NodeSourceModel implements ModelInterface { - public function __construct( - private NodesSources $nodeSource, - private UrlGeneratorInterface $urlGenerator, - private Security $security - ) { + private NodesSources $nodeSource; + private UrlGeneratorInterface $urlGenerator; + + public function __construct(NodesSources $nodeSource, UrlGeneratorInterface $urlGenerator) + { + $this->nodeSource = $nodeSource; + $this->urlGenerator = $urlGenerator; } public function toArray(): array { $node = $this->nodeSource->getNode(); + if (null === $node) { + throw new \RuntimeException('Node-source does not have a Node.'); + } /** @var NodesSourcesDocuments|false $thumbnail */ $thumbnail = $this->nodeSource->getDocumentsByFields()->first(); @@ -39,18 +42,15 @@ public function toArray(): array 'nodeName' => $node->getNodeName(), 'thumbnail' => $thumbnail ? $thumbnail->getDocument() : null, 'isPublished' => $node->isPublished(), + 'nodesEditPage' => $this->urlGenerator->generate('nodesEditSourcePage', [ + 'nodeId' => $node->getId(), + 'translationId' => $translation->getId(), + ]), 'nodeType' => [ - 'color' => $node->getNodeType()->getColor() ?? '#000000', + 'color' => $node->getNodeType()->getColor() ] ]; - if ($this->security->isGranted(NodeVoter::EDIT_CONTENT, $node)) { - $result['nodesEditPage'] = $this->urlGenerator->generate('nodesEditSourcePage', [ - 'nodeId' => $node->getId(), - 'translationId' => $translation->getId(), - ]); - } - $parent = $this->nodeSource->getParent(); if ($parent instanceof NodesSources) { diff --git a/src/Models/NodeTypeModel.php b/src/Models/NodeTypeModel.php index 6c26967f..bf9e93b0 100644 --- a/src/Models/NodeTypeModel.php +++ b/src/Models/NodeTypeModel.php @@ -6,6 +6,9 @@ use RZ\Roadiz\CoreBundle\Entity\NodeType; +/** + * @package Themes\Rozier\Models + */ final class NodeTypeModel implements ModelInterface { private NodeType $nodeType; diff --git a/src/Models/TagModel.php b/src/Models/TagModel.php index d22864d9..10fe026c 100644 --- a/src/Models/TagModel.php +++ b/src/Models/TagModel.php @@ -7,6 +7,9 @@ use RZ\Roadiz\CoreBundle\Entity\Tag; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +/** + * @package Themes\Rozier\Models + */ final class TagModel implements ModelInterface { private Tag $tag; diff --git a/src/Resources/app/Lazyload.js b/src/Resources/app/Lazyload.js index 1b6ddfe9..0e1f3d36 100644 --- a/src/Resources/app/Lazyload.js +++ b/src/Resources/app/Lazyload.js @@ -6,8 +6,10 @@ import TagsBulk from './components/bulk-edits/TagsBulk' import DocumentUploader from './components/documents/DocumentUploader' import NodeTypeFieldsPosition from './components/node-type-fields/NodeTypeFieldsPosition' import AttributeValuePosition from './components/attribute-values/AttributeValuePosition' +import NodeTypeFieldEdit from './components/node-type-fields/NodeTypeFieldEdit' import CustomFormFieldsPosition from './components/custom-form-fields/CustomFormFieldsPosition' import NodeTreeContextActions from './components/trees/NodeTreeContextActions' +import Import from './components/import/Import' import NodeEditSource from './components/node/NodeEditSource' import InputLengthWatcher from './widgets/InputLengthWatcher' import ChildrenNodesField from './widgets/ChildrenNodesField' @@ -27,6 +29,9 @@ import MultiLeafletGeotagField from './widgets/MultiLeafletGeotagField' import TagEdit from './components/tag/TagEdit' import MainTreeTabs from './components/tabs/MainTreeTabs' +/** + * Lazyload + */ export default class Lazyload { constructor() { this.$linksSelector = null @@ -49,6 +54,7 @@ export default class Lazyload { this.attributeValuesPosition = null this.customFormFieldsPosition = null this.settingsSaveButtons = null + this.nodeTypeFieldEdit = null this.nodeEditSource = null this.tagEdit = null this.markdownEditors = [] @@ -110,10 +116,14 @@ export default class Lazyload { ) { event.preventDefault() - window.requestAnimationFrame(() => { + if (this.clickTimeout) { + clearTimeout(this.clickTimeout) + } + + this.clickTimeout = window.setTimeout(() => { window.history.pushState({}, null, $link.attr('href')) this.onPopState(null) - }) + }, 0) return false } @@ -150,10 +160,10 @@ export default class Lazyload { * Delay loading if user is click like devil */ if (this.currentTimeout) { - window.cancelAnimationFrame(this.currentTimeout) + clearTimeout(this.currentTimeout) } - this.currentTimeout = window.requestAnimationFrame(async () => { + this.currentTimeout = window.setTimeout(() => { /* * Trigger event on window to notify open * widgets to close. @@ -161,71 +171,48 @@ export default class Lazyload { let pageChangeEvent = new CustomEvent('pagechange') window.dispatchEvent(pageChangeEvent) - try { - let url = '' - const path = location.href.split('?')[0] - const params = new URLSearchParams(location.href.split('?')[1]) - if (state.headerData) { - /** - * @param {string} key - * @param {string|number|Array} value - */ - for (let [key, value] of Object.entries(state.headerData)) { - if (Array.isArray(value)) { - value.forEach((v, i) => { - params.append(key + '[' + i + ']', v) + this.currentRequest = $.ajax({ + url: location.href, + type: 'get', + dataType: 'html', + cache: false, + data: state.headerData, + beforeSend: (xhr) => { + xhr.setRequestHeader('X-Partial', true) + }, + }) + .done((data) => { + this.applyContent(data) + this.canvasLoader.hide() + let pageLoadEvent = new CustomEvent('pageload', { detail: data }) + window.dispatchEvent(pageLoadEvent) + }) + .fail((data) => { + if (typeof data.responseText !== 'undefined') { + try { + let exception = JSON.parse(data.responseText) + window.UIkit.notify({ + message: exception.message, + status: 'danger', + timeout: 3000, + pos: 'top-center', }) - } else { - params.set(key, value) + } catch (e) { + // No valid JsonResponse, need to refresh page + window.location.href = location.href } - } - } - if (params.toString() !== '') { - url = path + '?' + params.toString() - } else { - url = path - } - - const response = await fetch(url, { - method: 'GET', - headers: { - 'X-Partial': true, - Accept: 'text/html', - }, - }) - if (!response.ok) { - throw response - } - const data = await response.text() - this.applyContent(data) - let pageLoadEvent = new CustomEvent('pageload', { detail: data }) - window.dispatchEvent(pageLoadEvent) - } catch (response) { - const data = await response.text() - if (data) { - try { - let exception = JSON.parse(data) + } else { window.UIkit.notify({ - message: exception.message, + message: window.Rozier.messages.forbiddenPage, status: 'danger', timeout: 3000, pos: 'top-center', }) - } catch (e) { - // No valid JsonResponse, need to refresh page - window.location.href = location.href } - } else { - window.UIkit.notify({ - message: window.Rozier.messages.forbiddenPage, - status: 'danger', - timeout: 3000, - pos: 'top-center', - }) - } - } - this.canvasLoader.hide() - }) + + this.canvasLoader.hide() + }) + }, 100) } refreshCodemirrorEditor() { @@ -277,10 +264,10 @@ export default class Lazyload { $tempData = $container.find('.new-content-global') - $old.eq(0).fadeOut(100, () => { + $old.fadeOut(100, () => { $old.remove() this.generalBind() - $tempData.eq(0).fadeIn(200, () => { + $tempData.fadeIn(200, () => { $tempData.removeClass('new-content-global') let pageShowEndEvent = new CustomEvent('pageshowend') window.dispatchEvent(pageShowEndEvent) @@ -317,6 +304,7 @@ export default class Lazyload { this.attributeValuesPosition, this.customFormFieldsPosition, this.settingsSaveButtons, + this.nodeTypeFieldEdit, this.nodeEditSource, this.tagEdit, this.nodeTree, @@ -351,6 +339,7 @@ export default class Lazyload { this.customFormFieldsPosition = new CustomFormFieldsPosition() this.nodeTreeContextActions = new NodeTreeContextActions() this.settingsSaveButtons = new SettingsSaveButtons() + this.nodeTypeFieldEdit = new NodeTypeFieldEdit() this.nodeEditSource = new NodeEditSource() this.tagEdit = new TagEdit() this.nodeTree = new NodeTree() @@ -375,7 +364,13 @@ export default class Lazyload { // Switch checkboxes this.initBootstrapSwitches() + window.Rozier.getMessages() + + if (typeof window.Rozier.importRoutes !== 'undefined' && window.Rozier.importRoutes !== null) { + window.Rozier.import = new Import(window.Rozier.importRoutes) + window.Rozier.importRoutes = null + } } generalUnbind(objects) { diff --git a/src/Resources/app/Rozier.js b/src/Resources/app/Rozier.js index 0f6f998c..c13e2dc6 100644 --- a/src/Resources/app/Rozier.js +++ b/src/Resources/app/Rozier.js @@ -6,7 +6,6 @@ import { PointerEventsPolyfill } from './utils/plugins' import { TweenLite, Expo } from 'gsap' import NodeTreeContextActions from './components/trees/NodeTreeContextActions' import RozierMobile from './RozierMobile' -import bulkActions from './widgets/GenericBulkActions' require('gsap/ScrollToPlugin') /** @@ -21,7 +20,6 @@ export default class Rozier { this.windowHeight = null this.resizeFirst = true this.mobile = null - this.ajaxToken = null this.nodeTrees = [] this.treeTrees = [] @@ -155,14 +153,6 @@ export default class Rozier { this.refreshMainNodeTree() this.refreshMainTagTree() this.refreshMainFolderTree() - - /* - * init generic bulk actions widget - */ - bulkActions() - window.addEventListener('pageshowend', () => { - bulkActions() - }) } saveCollapsedNestableState(state = null) { @@ -233,22 +223,16 @@ export default class Rozier { bindMainTrees() { // TREES let $nodeTree = $('.nodetree-widget .root-tree') - if ($nodeTree.length) { - $nodeTree.off('change.uk.nestable') - $nodeTree.on('change.uk.nestable', this.onNestableNodeTreeChange) - } + $nodeTree.off('change.uk.nestable') + $nodeTree.on('change.uk.nestable', this.onNestableNodeTreeChange) let $tagTree = $('.tagtree-widget .root-tree') - if ($tagTree.length) { - $tagTree.off('change.uk.nestable') - $tagTree.on('change.uk.nestable', this.onNestableTagTreeChange) - } + $tagTree.off('change.uk.nestable') + $tagTree.on('change.uk.nestable', this.onNestableTagTreeChange) let $folderTree = $('.foldertree-widget .root-tree') - if ($folderTree.length) { - $folderTree.off('change.uk.nestable') - $folderTree.on('change.uk.nestable', this.onNestableFolderTreeChange) - } + $folderTree.off('change.uk.nestable') + $folderTree.on('change.uk.nestable', this.onNestableFolderTreeChange) // Tree element name this.$mainTreeElementName = this.$mainTrees.find('.tree-element-name') @@ -288,71 +272,61 @@ export default class Rozier { }) } - fetchSessionMessages() { - return new Promise(async (resolve, reject) => { - const query = new URLSearchParams({ - _action: 'messages', - _token: this.ajaxToken, - }) - const url = this.routes.ajaxSessionMessages + '?' + query.toString() - try { - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - }) - const data = await response.json() - if (!data.messages) { - reject() - } - resolve(data.messages) - } catch (e) { - reject() - } - }) - } /** * Get messages. */ - async getMessages() { - const messages = await this.fetchSessionMessages() - if (typeof messages.confirm !== 'undefined' && messages.confirm.length > 0) { - for (let i = messages.confirm.length - 1; i >= 0; i--) { - window.UIkit.notify({ - message: messages.confirm[i], - status: 'success', - timeout: 2000, - pos: 'top-center', - }) - } - } - - if (typeof messages.error !== 'undefined' && messages.error.length > 0) { - for (let j = data.messages.error.length - 1; j >= 0; j--) { - window.UIkit.notify({ - message: data.messages.error[j], - status: 'error', - timeout: 2000, - pos: 'top-center', - }) - } - } + getMessages() { + $.ajax({ + url: this.routes.ajaxSessionMessages, + type: 'GET', + dataType: 'json', + cache: false, + data: { + _action: 'messages', + _token: this.ajaxToken, + }, + }) + .done((data) => { + if (typeof data.messages !== 'undefined') { + if (typeof data.messages.confirm !== 'undefined' && data.messages.confirm.length > 0) { + for (let i = data.messages.confirm.length - 1; i >= 0; i--) { + window.UIkit.notify({ + message: data.messages.confirm[i], + status: 'success', + timeout: 2000, + pos: 'top-center', + }) + } + } + + if (typeof data.messages.error !== 'undefined' && data.messages.error.length > 0) { + for (let j = data.messages.error.length - 1; j >= 0; j--) { + window.UIkit.notify({ + message: data.messages.error[j], + status: 'error', + timeout: 2000, + pos: 'top-center', + }) + } + } + } + }) + .fail(() => { + console.log('[Rozier.getMessages] error') + }) } /** * @param translationId */ refreshAllNodeTrees(translationId) { - const promises = [] - promises.push(this.refreshMainNodeTree(translationId)) + this.refreshMainNodeTree(translationId) /* * Stack trees */ if (this.lazyload.stackNodeTrees.treeAvailable()) { - promises.push(this.lazyload.stackNodeTrees.refreshNodeTree()) + this.lazyload.stackNodeTrees.refreshNodeTree() } /* @@ -361,10 +335,9 @@ export default class Rozier { if (this.lazyload.childrenNodesFields.treeAvailable()) { for (let i = this.lazyload.childrenNodesFields.$nodeTrees.length - 1; i >= 0; i--) { let $nodeTree = this.lazyload.childrenNodesFields.$nodeTrees.eq(i) - promises.push(this.lazyload.childrenNodesFields.refreshNodeTree($nodeTree)) + this.lazyload.childrenNodesFields.refreshNodeTree($nodeTree) } } - return Promise.all(promises) } /** @@ -372,163 +345,151 @@ export default class Rozier { * * @param {Number|null|undefined} translationId */ - async refreshMainNodeTree(translationId = undefined) { - let $currentNodeTree = $('#tree-container').find('.nodetree-widget').eq(0) - if (!$currentNodeTree.length) { - console.debug('No main node-tree available.') - return - } - + refreshMainNodeTree(translationId) { + let $currentNodeTree = $('#tree-container').find('.nodetree-widget') let $currentRootTree = $currentNodeTree.find('.root-tree').eq(0) if ($currentRootTree.length && !translationId) { translationId = $currentRootTree.attr('data-translation-id') } - try { - const query = new URLSearchParams({ + if ($currentNodeTree.length) { + let postData = { _token: this.ajaxToken, _action: 'requestMainNodeTree', translationId: translationId || null, - }) - const url = this.routes.nodesTreeAjax + '?' + query.toString() - const response = await fetch(url, { - method: 'GET', - headers: { - Accept: 'application/json', - }, - }) - if (!response.ok) { - throw response - } - const data = await response.json() - if (typeof data.nodeTree !== 'undefined') { - await this.fadeOut($currentNodeTree) - $currentNodeTree.replaceWith(data.nodeTree) - $currentNodeTree = $('#tree-container').find('.nodetree-widget') - - await this.fadeIn($currentNodeTree) - this.initNestables() - this.bindMainTrees() - this.resize() - this.lazyload.bindAjaxLink() - - if (this.lazyload.nodeTreeContextActions) { - this.lazyload.nodeTreeContextActions.unbind() - } - - this.lazyload.nodeTreeContextActions = new NodeTreeContextActions() } - } catch (e) { - console.log('[Rozier.refreshMainNodeTree] Retrying in 3 seconds') - // Wait for background jobs to be done - window.setTimeout(() => { - this.refreshMainNodeTree(translationId) - }, 3000) + let url = this.routes.nodesTreeAjax + + $.ajax({ + url: url, + type: 'get', + cache: false, + dataType: 'json', + data: postData, + }) + .done((data) => { + if ($currentNodeTree.length && typeof data.nodeTree !== 'undefined') { + $currentNodeTree.fadeOut('slow', () => { + $currentNodeTree.replaceWith(data.nodeTree) + $currentNodeTree = $('#tree-container').find('.nodetree-widget') + $currentNodeTree.fadeIn() + this.initNestables() + this.bindMainTrees() + this.resize() + this.lazyload.bindAjaxLink() + + if (this.lazyload.nodeTreeContextActions) { + this.lazyload.nodeTreeContextActions.unbind() + } + + this.lazyload.nodeTreeContextActions = new NodeTreeContextActions() + }) + } + }) + .fail(() => { + console.log('[Rozier.refreshMainNodeTree] Retrying in 3 seconds') + // Wait for background jobs to be done + setTimeout(() => { + this.refreshMainNodeTree(translationId) + }, 3000) + }) + .always(() => { + this.lazyload.canvasLoader.hide() + }) + } else { + console.debug('No main node-tree available.') } - - this.lazyload.canvasLoader.hide() } /** * Refresh only main tagTree. * */ - async refreshMainTagTree() { + refreshMainTagTree() { let $currentTagTree = $('#tree-container').find('.tagtree-widget') - if (!$currentTagTree.length) { + if ($currentTagTree.length) { + let postData = { + _token: this.ajaxToken, + _action: 'requestMainTagTree', + } + + let url = this.routes.tagsTreeAjax + + $.ajax({ + url: url, + type: 'get', + cache: false, + dataType: 'json', + data: postData, + }) + .done((data) => { + if ($currentTagTree.length && typeof data.tagTree !== 'undefined') { + $currentTagTree.fadeOut('slow', () => { + $currentTagTree.replaceWith(data.tagTree) + $currentTagTree = $('#tree-container').find('.tagtree-widget') + $currentTagTree.fadeIn() + this.initNestables() + this.bindMainTrees() + this.resize() + this.lazyload.bindAjaxLink() + }) + } + }) + .always(() => { + this.lazyload.canvasLoader.hide() + }) + } else { console.debug('No main tag-tree available.') - return } - - const query = new URLSearchParams({ - _token: this.ajaxToken, - _action: 'requestMainTagTree', - }) - const url = this.routes.tagsTreeAjax + '?' + query.toString() - const response = await fetch(url, { - method: 'GET', - headers: { - Accept: 'application/json', - }, - }) - const data = await response.json() - if (typeof data.tagTree !== 'undefined') { - await this.fadeOut($currentTagTree) - $currentTagTree.replaceWith(data.tagTree) - $currentTagTree = $('#tree-container').find('.tagtree-widget') - await this.fadeIn($currentTagTree) - this.initNestables() - this.bindMainTrees() - this.resize() - this.lazyload.bindAjaxLink() - } - this.lazyload.canvasLoader.hide() } /** * Refresh only main folderTree. */ - async refreshMainFolderTree() { + refreshMainFolderTree() { let $currentFolderTree = $('#tree-container').find('.foldertree-widget') - if (!$currentFolderTree.length) { - console.debug('No main folder-tree available.') - return - } + if ($currentFolderTree.length) { + let postData = { + _token: this.ajaxToken, + _action: 'requestMainFolderTree', + } - const query = new URLSearchParams({ - _token: this.ajaxToken, - _action: 'requestMainFolderTree', - }) - const url = this.routes.foldersTreeAjax + '?' + query.toString() - const response = await fetch(url, { - method: 'GET', - headers: { - Accept: 'application/json', - }, - }) - const data = await response.json() - if (typeof data.folderTree !== 'undefined') { - await this.fadeOut($currentFolderTree) - $currentFolderTree.replaceWith(data.folderTree) - $currentFolderTree = $('#tree-container').find('.foldertree-widget') - await this.fadeIn($currentFolderTree) - this.initNestables() - this.bindMainTrees() - this.resize() - this.lazyload.bindAjaxLink() - } - - this.lazyload.canvasLoader.hide() - } + let url = this.routes.foldersTreeAjax - /** - * @param {jQuery} element - * @return {Promise} - */ - async fadeIn(element) { - return new Promise((resolve, reject) => { - element.fadeIn(() => { - resolve() + $.ajax({ + url: url, + type: 'get', + cache: false, + dataType: 'json', + data: postData, }) - }) - } - - /** - * @param {jQuery} element - * @return {Promise} - */ - async fadeOut(element) { - return new Promise((resolve, reject) => { - element.fadeOut('slow', () => { - resolve() - }) - }) + .done((data) => { + if ($currentFolderTree.length && typeof data.folderTree !== 'undefined') { + $currentFolderTree.fadeOut('slow', () => { + $currentFolderTree.replaceWith(data.folderTree) + $currentFolderTree = $('#tree-container').find('.foldertree-widget') + $currentFolderTree.fadeIn() + this.initNestables() + this.bindMainTrees() + this.resize() + this.lazyload.bindAjaxLink() + }) + } + }) + .always(() => { + this.lazyload.canvasLoader.hide() + }) + } else { + console.debug('No main folder-tree available.') + } } /** * Toggle trees panel + * @param {[type]} event [description] + * @return {[type]} [description] */ toggleTreesPanel() { $('#main-container-inner').toggleClass('trees-panel--minified') @@ -549,6 +510,8 @@ export default class Rozier { /** * Toggle user panel + * @param {[type]} event [description] + * @return {[type]} [description] */ toggleUserPanel() { $('#user-panel').toggleClass('minified') @@ -602,12 +565,12 @@ export default class Rozier { /** * @param event - * @param {HTMLElement} rootEl - * @param {HTMLElement} el - * @param {string|null|undefined} status - * @returns {false|undefined} + * @param rootEl + * @param el + * @param status + * @returns {boolean} */ - async onNestableNodeTreeChange(event, rootEl, el, status) { + onNestableNodeTreeChange(event, rootEl, el, status) { let element = $(el) /* * If node removed, do not do anything, the other change.uk.nestable nodeTree will be triggered @@ -643,7 +606,7 @@ export default class Rozier { return false } - const postData = { + let postData = { _token: this.ajaxToken, _action: 'updatePosition', nodeId: nodeId, @@ -659,43 +622,39 @@ export default class Rozier { postData.prevNodeId = parseInt(element.prev().attr('data-node-id')) } - try { - const response = await fetch(this.routes.nodeAjaxEdit.replace('%nodeId%', nodeId), { - method: 'POST', - headers: { - Accept: 'application/json', - }, - body: new URLSearchParams(postData), - }) - if (!response.ok) { - throw response - } - const data = await response.json() - window.UIkit.notify({ - message: data.responseText || data.detail, - status: data.status, - timeout: 3000, - pos: 'top-center', + $.ajax({ + url: this.routes.nodeAjaxEdit.replace('%nodeId%', nodeId), + type: 'POST', + dataType: 'json', + data: postData, + }) + .done((data) => { + window.UIkit.notify({ + message: data.responseText, + status: data.status, + timeout: 3000, + pos: 'top-center', + }) }) - } catch (response) { - const data = await response.json() - window.UIkit.notify({ - message: data.error_message || data.detail, - status: 'danger', - timeout: 3000, - pos: 'top-center', + .fail((data) => { + data = JSON.parse(data.responseText) + window.UIkit.notify({ + message: data.error_message, + status: 'danger', + timeout: 3000, + pos: 'top-center', + }) }) - } } /** * @param event - * @param {HTMLElement} rootEl - * @param {HTMLElement} el - * @param {string|null|undefined} status - * @returns {false|undefined} + * @param rootEl + * @param el + * @param status + * @returns {boolean} */ - async onNestableTagTreeChange(event, rootEl, el, status) { + onNestableTagTreeChange(event, rootEl, el, status) { let element = $(el) /* @@ -747,43 +706,29 @@ export default class Rozier { postData.prevTagId = parseInt(element.prev().attr('data-tag-id')) } - try { - const response = await fetch(this.routes.tagAjaxEdit.replace('%tagId%', tagId), { - method: 'POST', - headers: { - Accept: 'application/json', - }, - body: new URLSearchParams(postData), - }) - if (!response.ok) { - throw response - } - const data = await response.json() + $.ajax({ + url: this.routes.tagAjaxEdit.replace('%tagId%', tagId), + type: 'POST', + dataType: 'json', + data: postData, + }).done((data) => { window.UIkit.notify({ message: data.responseText, status: data.status, timeout: 3000, pos: 'top-center', }) - } catch (response) { - const data = await response.json() - window.UIkit.notify({ - message: data.error_message || data.detail, - status: 'danger', - timeout: 3000, - pos: 'top-center', - }) - } + }) } /** + * * @param event - * @param {HTMLElement} rootEl - * @param {HTMLElement} el - * @param {string|null|undefined} status - * @returns {false|undefined} + * @param element + * @param status + * @returns {boolean} */ - async onNestableFolderTreeChange(event, rootEl, el, status) { + onNestableFolderTreeChange(event, rootEl, el, status) { let element = $(el) /* * If folder removed, do not do anything, the other folderTree will be triggered @@ -836,33 +781,19 @@ export default class Rozier { postData.prevFolderId = parseInt(element.prev().attr('data-folder-id')) } - try { - const response = await fetch(this.routes.folderAjaxEdit.replace('%folderId%', folderId), { - method: 'POST', - headers: { - Accept: 'application/json', - }, - body: new URLSearchParams(postData), - }) - if (!response.ok) { - throw response - } - const data = await response.json() + $.ajax({ + url: this.routes.folderAjaxEdit.replace('%folderId%', folderId), + type: 'POST', + dataType: 'json', + data: postData, + }).done((data) => { window.UIkit.notify({ message: data.responseText, status: data.status, timeout: 3000, pos: 'top-center', }) - } catch (response) { - const data = await response.json() - window.UIkit.notify({ - message: data.error_message || data.detail, - status: 'danger', - timeout: 3000, - pos: 'top-center', - }) - } + }) } /** @@ -886,9 +817,9 @@ export default class Rozier { if (this.windowWidth >= 768 && this.windowWidth <= 1200 && this.$mainTrees.length && this.resizeFirst) { this.$mainTrees[0].style.display = 'none' this.$minifyTreePanelButton.trigger('click') - window.requestAnimationFrame(() => { + window.setTimeout(() => { this.$mainTrees[0].style.display = 'table-cell' - }) + }, 1000) } // Check if mobile @@ -935,6 +866,9 @@ export default class Rozier { this.lazyload.resize() this.entriesPanel.replaceSubNavs() + // Documents list + // if(this.lazyload !== null && !this.resizeFirst) this.lazyload.documentsList.resize(); + // Set resize first to false if (this.resizeFirst) this.resizeFirst = false } diff --git a/src/Resources/app/api/DocumentApi.js b/src/Resources/app/api/DocumentApi.js index e76828c9..9729e172 100644 --- a/src/Resources/app/api/DocumentApi.js +++ b/src/Resources/app/api/DocumentApi.js @@ -29,6 +29,7 @@ export function getDocumentsByIds({ ids = [] }) { } }) .catch((error) => { + // TODO // Log request error or display a message throw new Error(error.response.data.humanMessage) }) @@ -76,6 +77,7 @@ export function getDocuments({ searchTerms, filters, filterExplorerSelection, mo } }) .catch((error) => { + // TODO // Log request error or display a message throw new Error(error) }) diff --git a/src/Resources/app/components/Dropzone.vue b/src/Resources/app/components/Dropzone.vue index 0babde35..d6b06dc3 100644 --- a/src/Resources/app/components/Dropzone.vue +++ b/src/Resources/app/components/Dropzone.vue @@ -183,7 +183,7 @@ export default { */ if (file.previewElement) { let $preview = $(file.previewElement) - window.setTimeout(function () { + setTimeout(function () { $preview.fadeOut(500) }, 3000) } diff --git a/src/Resources/app/components/RzTextarea.vue b/src/Resources/app/components/RzTextarea.vue index 5638efae..ec8ea488 100644 --- a/src/Resources/app/components/RzTextarea.vue +++ b/src/Resources/app/components/RzTextarea.vue @@ -1,5 +1,5 @@ + - + diff --git a/src/Resources/views/partials/simple-js-inject.html.twig b/src/Resources/views/partials/simple-js-inject.html.twig index 89eb5041..c77fcff7 100644 --- a/src/Resources/views/partials/simple-js-inject.html.twig +++ b/src/Resources/views/partials/simple-js-inject.html.twig @@ -1,3 +1,3 @@ - + diff --git a/src/Resources/views/redirections/add.html.twig b/src/Resources/views/redirections/add.html.twig index 39253b55..de268d6b 100644 --- a/src/Resources/views/redirections/add.html.twig +++ b/src/Resources/views/redirections/add.html.twig @@ -24,7 +24,7 @@
{% apply spaceless %} -
- {{ form_end(form) }} - - {% endif %} -{% endblock %} diff --git a/src/Resources/views/redirections/edit.html.twig b/src/Resources/views/redirections/edit.html.twig index 891a55bd..214bd60a 100644 --- a/src/Resources/views/redirections/edit.html.twig +++ b/src/Resources/views/redirections/edit.html.twig @@ -24,7 +24,7 @@
{% apply spaceless %} diff --git a/src/Resources/views/roles/edit.html.twig b/src/Resources/views/roles/edit.html.twig index 9ce227af..d9efcc6c 100644 --- a/src/Resources/views/roles/edit.html.twig +++ b/src/Resources/views/roles/edit.html.twig @@ -20,7 +20,7 @@ {{ form_widget(form) }}
{% apply spaceless %} - diff --git a/src/Resources/views/roles/list.html.twig b/src/Resources/views/roles/list.html.twig index ff21a251..110b8321 100644 --- a/src/Resources/views/roles/list.html.twig +++ b/src/Resources/views/roles/list.html.twig @@ -69,7 +69,7 @@ href="{{ path('rolesExportPage', { id: item.getId }) }}" title="{% trans %}export{% endtrans %}" data-uk-tooltip="{animation:true}"> - diff --git a/src/Resources/views/settingGroups/add.html.twig b/src/Resources/views/settingGroups/add.html.twig index 8c2109b2..6a043446 100644 --- a/src/Resources/views/settingGroups/add.html.twig +++ b/src/Resources/views/settingGroups/add.html.twig @@ -22,7 +22,7 @@ {{ form_start(form, { attr: { id: 'add-settingGroup-form'}}) }} {{ form_widget(form) }}
- diff --git a/src/Resources/views/settingGroups/edit.html.twig b/src/Resources/views/settingGroups/edit.html.twig index a3bca2b3..f95a60fe 100644 --- a/src/Resources/views/settingGroups/edit.html.twig +++ b/src/Resources/views/settingGroups/edit.html.twig @@ -25,7 +25,7 @@ {{ form_widget(form) }}
diff --git a/src/Resources/views/settings/edit.html.twig b/src/Resources/views/settings/edit.html.twig index 0f51429e..d8e390b8 100644 --- a/src/Resources/views/settings/edit.html.twig +++ b/src/Resources/views/settings/edit.html.twig @@ -19,7 +19,7 @@ {{ form_start(form, { attr: { id: 'edit-setting-form'}}) }} {{ form_widget(form) }}
- diff --git a/src/Resources/views/settings/list.html.twig b/src/Resources/views/settings/list.html.twig index 5df80eb1..824ed098 100644 --- a/src/Resources/views/settings/list.html.twig +++ b/src/Resources/views/settings/list.html.twig @@ -57,7 +57,7 @@

- {{ setting.setting.name|trans|inlineMarkdown -}} + {%- if setting.setting.Encrypted -%} {% endif %}{{ setting.setting.name|trans|inlineMarkdown -}}

{% if setting.setting.description %} @@ -76,9 +76,9 @@ {% apply spaceless %} - + - + {% endapply %} diff --git a/src/Resources/views/tags/add.html.twig b/src/Resources/views/tags/add.html.twig index 9dc9062f..3ddf468e 100644 --- a/src/Resources/views/tags/add.html.twig +++ b/src/Resources/views/tags/add.html.twig @@ -19,7 +19,7 @@ {{ form_widget(form) }}
{% apply spaceless %} - diff --git a/src/Resources/views/tags/edit.html.twig b/src/Resources/views/tags/edit.html.twig index 82ff225f..5fc466f9 100644 --- a/src/Resources/views/tags/edit.html.twig +++ b/src/Resources/views/tags/edit.html.twig @@ -51,7 +51,7 @@
{% if not readOnly %} {% apply spaceless %} - diff --git a/src/Resources/views/tags/list.html.twig b/src/Resources/views/tags/list.html.twig index 0079b274..5bdbe3a7 100644 --- a/src/Resources/views/tags/list.html.twig +++ b/src/Resources/views/tags/list.html.twig @@ -54,7 +54,7 @@ {% endif %} {% if is_granted('ROLE_ACCESS_TAGS_DELETE') and not tag.locked %} - + {% endif %} @@ -75,7 +75,7 @@ {% if is_granted('ROLE_ACCESS_TAGS_DELETE') and not tag.locked %} - + {% endif %} diff --git a/src/Resources/views/tags/settings.html.twig b/src/Resources/views/tags/settings.html.twig index 416465d1..d738357b 100644 --- a/src/Resources/views/tags/settings.html.twig +++ b/src/Resources/views/tags/settings.html.twig @@ -21,7 +21,7 @@ {{ form_widget(form) }}
{% apply spaceless %} - @@ -32,15 +32,15 @@