From 7e388f6241dbd1b231bd5d574080c42e06851254 Mon Sep 17 00:00:00 2001 From: roadiz-ci Date: Fri, 21 Jun 2024 09:44:14 +0000 Subject: [PATCH] chore: Bumped --- .env | 1 - .env.test | 6 - LICENSE.md | 2 +- composer.json | 93 ++--- config/api_resources/attribute.yml | 8 +- config/api_resources/attribute_value.yml | 28 +- config/api_resources/common_content.yml | 57 ++- config/api_resources/custom_form.yml | 220 +++++------ config/api_resources/document.yml | 30 +- config/api_resources/folder.yml | 26 +- config/api_resources/node.yml | 16 +- config/api_resources/nodes_sources.yml | 132 +++---- config/api_resources/realm.yml | 28 +- config/api_resources/tag.yml | 36 +- config/api_resources/translation.yml | 34 +- config/api_resources/web_response.yml | 44 ++- config/fixtures/settings.json | 37 ++ config/packages/roadiz_core.yaml | 3 + config/packages/security.yaml | 2 +- config/routing.yaml | 8 +- config/services.yaml | 20 +- migrations/Version20240214143213.php | 31 -- migrations/Version20240214143849.php | 31 -- migrations/Version20240214145403.php | 31 -- migrations/Version20240305124809.php | 31 -- migrations/Version20240305125653.php | 31 -- migrations/Version20240305132609.php | 31 -- migrations/Version20240305133122.php | 31 -- migrations/Version20240305133641.php | 31 -- migrations/Version20240305134734.php | 37 -- migrations/Version20240305142243.php | 62 ---- migrations/Version20240305143443.php | 31 -- migrations/Version20240318184555.php | 34 -- migrations/Version20240318184556.php | 68 ---- migrations/Version20240318204224.php | 44 --- phpstan.neon | 11 +- phpunit.xml.dist | 67 ---- .../NodesSourcesBreadcrumbsFactory.php | 1 + .../GetWebResponseByPathController.php | 105 +++--- .../TranslationAwareControllerTrait.php | 5 - .../WebResponseDataTransformerInterface.php | 18 +- .../WebResponseOutputDataTransformer.php | 90 +++-- .../AttributeValueRealmExtension.php | 2 +- src/Api/ListManager/SolrSearchListManager.php | 4 +- .../Model/BlocksAwareWebResponseInterface.php | 8 +- src/Api/Model/NodesSourcesHead.php | 52 ++- src/Api/Model/NodesSourcesHeadFactory.php | 23 +- .../Model/RealmsAwareWebResponseInterface.php | 8 +- src/Api/Model/WebResponse.php | 95 ++++- src/Api/Model/WebResponseInterface.php | 18 - src/Api/Model/WebResponseTrait.php | 149 -------- src/Api/OpenApi/WebResponseDecorator.php | 4 - src/Bag/NodeTypes.php | 12 +- src/Bag/Roles.php | 19 +- src/Bag/Settings.php | 19 +- src/Cache/Clearer/FileClearer.php | 4 +- .../Clearer/NodesSourcesUrlsCacheClearer.php | 5 +- src/Cache/CloudflareProxyCache.php | 35 +- src/Cache/ReverseProxyCache.php | 28 +- src/Cache/ReverseProxyCacheLocator.php | 14 +- src/Console/AppInstallCommand.php | 43 ++- src/Console/AppMigrateCommand.php | 13 +- src/Console/CustomFormAnswerPurgeCommand.php | 13 +- src/Console/DecodePrivateKeyCommand.php | 49 +++ src/Console/EncodePrivateKeyCommand.php | 50 +++ src/Console/FilesCommandTrait.php | 6 +- src/Console/FilesExportCommand.php | 19 +- src/Console/FilesImportCommand.php | 18 +- src/Console/GenerateApiResourceCommand.php | 14 +- .../GenerateNodeSourceEntitiesCommand.php | 29 +- src/Console/GeneratePrivateKeyCommand.php | 47 +++ src/Console/GetCronLastExecDateCommand.php | 43 --- src/Console/InstallCommand.php | 31 +- src/Console/LogsCleanupCommand.php | 16 +- src/Console/MailerTestCommand.php | 17 +- .../NodeApplyUniversalFieldsCommand.php | 20 +- src/Console/NodeClearTagCommand.php | 29 +- src/Console/NodeTypesCommand.php | 19 +- src/Console/NodeTypesCreationCommand.php | 22 +- src/Console/NodeTypesDefaultValuesCommand.php | 15 +- src/Console/NodeTypesDeleteCommand.php | 24 +- src/Console/NodesCleanNamesCommand.php | 19 +- src/Console/NodesCommand.php | 19 +- src/Console/NodesCreationCommand.php | 133 +++++++ src/Console/NodesDetailsCommand.php | 16 +- src/Console/NodesEmptyTrashCommand.php | 20 +- src/Console/NodesOrphansCommand.php | 19 +- src/Console/PrivateKeyCommand.php | 57 +++ .../RegisterCronLastExecDateCommand.php | 46 --- src/Console/SolrCommand.php | 18 +- src/Console/SolrOptimizeCommand.php | 21 +- src/Console/SolrReindexCommand.php | 17 +- src/Console/SolrResetCommand.php | 21 +- src/Console/TranslationsCommand.php | 17 +- src/Console/TranslationsCreationCommand.php | 15 +- src/Console/TranslationsDeleteCommand.php | 15 +- src/Console/TranslationsDisableCommand.php | 15 +- src/Console/TranslationsEnableCommand.php | 15 +- src/Console/UsersCommand.php | 12 +- src/Console/UsersCreationCommand.php | 3 + src/Console/UsersDeleteCommand.php | 3 + src/Console/UsersDisableCommand.php | 3 + src/Console/UsersEnableCommand.php | 3 + src/Console/UsersPasswordCommand.php | 19 +- src/Console/UsersRolesCommand.php | 16 +- src/Console/VersionsPurgeCommand.php | 14 +- src/Controller/CustomFormController.php | 112 +++++- src/Crypto/UniqueKeyEncoderFactory.php | 55 +++ src/DependencyInjection/Configuration.php | 20 +- .../RoadizCoreExtension.php | 36 +- .../SettingLifeCycleSubscriber.php | 125 +++++++ .../UserLifeCycleSubscriber.php | 32 +- src/Doctrine/ORM/Filter/ANodesFilter.php | 50 ++- src/Doctrine/ORM/Filter/BNodesFilter.php | 8 + src/Doctrine/ORM/SimpleQueryBuilder.php | 16 +- src/Entity/AbstractDateTimedPositioned.php | 42 --- src/Entity/AttributeDocuments.php | 44 ++- src/Entity/AttributeValue.php | 24 +- src/Entity/CustomForm.php | 11 +- src/Entity/CustomFormAnswer.php | 7 +- src/Entity/CustomFormField.php | 23 +- src/Entity/CustomFormFieldAttribute.php | 33 +- src/Entity/Document.php | 39 -- src/Entity/DocumentTranslation.php | 18 +- src/Entity/FieldAwareEntityTrait.php | 44 --- src/Entity/Folder.php | 3 +- src/Entity/FolderTranslation.php | 14 +- src/Entity/Node.php | 122 +++---- src/Entity/NodeType.php | 19 +- src/Entity/NodeTypeField.php | 76 +++- src/Entity/NodesCustomForms.php | 65 +++- src/Entity/NodesSources.php | 95 +++-- src/Entity/NodesSourcesDocuments.php | 77 ++-- src/Entity/NodesToNodes.php | 85 +++-- src/Entity/Realm.php | 2 +- src/Entity/RealmNode.php | 12 +- src/Entity/Setting.php | 87 ++++- src/Entity/Tag.php | 51 +-- src/Entity/TagTranslation.php | 82 ++++- src/Entity/TagTranslationDocuments.php | 40 +- src/Entity/Translation.php | 30 +- src/Entity/UrlAlias.php | 25 +- src/EntityHandler/CustomFormFieldHandler.php | 29 +- src/EntityHandler/CustomFormHandler.php | 13 +- src/EntityHandler/DocumentHandler.php | 12 +- src/EntityHandler/FolderHandler.php | 52 ++- src/EntityHandler/GroupHandler.php | 2 +- src/EntityHandler/HandlerFactory.php | 10 +- src/EntityHandler/NodeHandler.php | 100 ++--- src/EntityHandler/NodeTypeFieldHandler.php | 12 +- src/EntityHandler/NodeTypeHandler.php | 40 +- src/EntityHandler/NodesSourcesHandler.php | 69 ++-- src/EntityHandler/TagHandler.php | 135 ++++++- src/EntityHandler/TranslationHandler.php | 17 +- .../NodesSourcesPathGeneratingEvent.php | 56 ++- .../AssetsCacheEventSubscriber.php | 17 +- .../AutomaticWebhookSubscriber.php | 30 +- .../CloudflareCacheEventSubscriber.php | 47 ++- src/EventSubscriber/LocaleSubscriber.php | 104 ++---- src/EventSubscriber/LoggableSubscriber.php | 23 +- .../NodeDuplicationSubscriber.php | 18 +- src/EventSubscriber/NodeNameSubscriber.php | 1 + .../NodeRedirectionSubscriber.php | 22 +- .../NodeSourcePathSubscriber.php | 17 +- .../NodesSourcesAddHeadersSubscriber.php | 86 ----- .../NodesSourcesLinkHeaderEventSubscriber.php | 67 ++-- .../NodesSourcesUniversalSubscriber.php | 1 + .../NodesSourcesUrlsCacheEventSubscriber.php | 11 + .../OPCacheEventSubscriber.php | 1 + .../RealmNodeInheritanceSubscriber.php | 4 +- .../ReverseProxyCacheEventSubscriber.php | 2 + src/EventSubscriber/RoleSubscriber.php | 3 + src/EventSubscriber/SignatureSubscriber.php | 18 +- .../TagTimestampSubscriber.php | 5 +- src/EventSubscriber/TranslationSubscriber.php | 10 +- src/EventSubscriber/UserLocaleSubscriber.php | 21 +- src/Form/DataListTextType.php | 43 --- src/Form/RoleEntityType.php | 2 +- src/Form/SettingType.php | 4 + src/ListManager/SessionListFilters.php | 53 --- src/Logger/DoctrineHandler.php | 4 +- src/Mailer/ContactFormManager.php | 48 ++- src/Mailer/EmailManager.php | 59 ++- ...pplyRealmNodeInheritanceMessageHandler.php | 4 +- .../Handler/DeleteNodeTypeMessageHandler.php | 14 +- .../Handler/HttpRequestMessageHandler.php | 18 +- .../PurgeReverseProxyCacheMessageHandler.php | 23 +- ...archRealmNodeInheritanceMessageHandler.php | 21 +- .../UpdateDoctrineSchemaMessageHandler.php | 5 +- .../UpdateNodeTypeSchemaMessageHandler.php | 14 +- src/Model/AttributeGroupTrait.php | 11 +- src/Model/AttributeGroupTranslationTrait.php | 20 +- src/Model/AttributeTrait.php | 8 +- src/Model/AttributeTranslationTrait.php | 15 +- src/Model/AttributeValueTrait.php | 7 +- src/Model/AttributeValueTranslationTrait.php | 30 +- src/Node/NodeDuplicator.php | 9 +- src/Node/NodeFactory.php | 27 +- src/Node/NodeTranstyper.php | 10 +- src/Node/UniqueNodeGenerator.php | 5 +- src/Node/UniversalDataDuplicator.php | 19 +- src/NodeType/ApiResourceGenerator.php | 262 ++++--------- .../ApiResourceOperationNameGenerator.php | 28 -- .../EventSubscriber/PreviewBarSubscriber.php | 10 +- .../EventSubscriber/PreviewModeSubscriber.php | 37 +- .../Exception/PreviewNotAllowedException.php | 2 +- src/Preview/RequestPreviewRevolver.php | 2 +- src/Preview/User/PreviewUserProvider.php | 2 +- src/Realm/RealmResolver.php | 34 +- src/Realm/RealmResolverInterface.php | 13 - src/Repository/CustomFormRepository.php | 28 +- src/Repository/DocumentRepository.php | 32 +- src/Repository/NodeRepository.php | 88 +---- src/Repository/NodesCustomFormsRepository.php | 35 +- .../NodesSourcesDocumentsRepository.php | 27 +- src/Repository/NodesSourcesRepository.php | 343 +----------------- src/Repository/NodesToNodesRepository.php | 30 +- src/Repository/RealmRepository.php | 9 - src/Repository/StatusAwareRepository.php | 2 +- src/Repository/TagRepository.php | 22 +- src/Routing/DeferredRouteCollection.php | 29 ++ src/Routing/DynamicUrlMatcher.php | 20 +- src/Routing/NodeRouter.php | 79 +++- src/Routing/NodeUrlMatcher.php | 46 ++- src/Routing/NodesSourcesPathResolver.php | 24 +- src/Routing/NodesSourcesUrlGenerator.php | 27 +- src/Routing/NullLoader.php | 28 +- ...timizedNodesSourcesGraphPathAggregator.php | 23 +- src/Routing/RedirectionMatcher.php | 9 +- src/Routing/RedirectionPathResolver.php | 13 +- src/Routing/RedirectionRouter.php | 27 +- src/Routing/StaticRouter.php | 52 +++ src/SearchEngine/AbstractSearchHandler.php | 55 +-- src/SearchEngine/DocumentSearchHandler.php | 4 +- .../GlobalNodeSourceSearchHandler.php | 69 ++-- src/SearchEngine/Indexer/AbstractIndexer.php | 14 +- src/SearchEngine/Indexer/DocumentIndexer.php | 13 +- .../Indexer/NodesSourcesIndexer.php | 13 +- src/SearchEngine/NodeSourceSearchHandler.php | 4 +- src/SearchEngine/SearchHandlerInterface.php | 4 + src/SearchEngine/SearchResultsInterface.php | 6 - src/SearchEngine/SolrSearchResultItem.php | 48 --- src/SearchEngine/SolrSearchResults.php | 83 +++-- .../DefaultNodesSourcesIndexingSubscriber.php | 5 +- .../Authentication/RoadizAuthenticator.php | 2 +- .../Authorization/Voter/GroupVoter.php | 12 +- .../Voter/NodeTypeFieldVoter.php | 2 +- .../Authorization/Voter/NodeVoter.php | 9 +- .../Authorization/Voter/RealmVoter.php | 23 +- .../Authorization/Voter/RoleArrayVoter.php | 6 +- .../Voter/SuperAdminRoleHierarchyVoter.php | 14 +- src/Serializer/CircularReferenceHandler.php | 9 +- .../Normalizer/AbstractPathNormalizer.php | 24 +- .../Normalizer/AttributeValueNormalizer.php | 2 +- .../Normalizer/CustomFormNormalizer.php | 2 +- .../Normalizer/DocumentNormalizer.php | 11 +- .../Normalizer/DocumentSourcesNormalizer.php | 7 +- .../Normalizer/FolderNormalizer.php | 2 +- .../Normalizer/NodesSourcesPathNormalizer.php | 2 +- .../RealmSerializationGroupNormalizer.php | 50 ++- src/Serializer/Normalizer/TagNormalizer.php | 33 +- .../Normalizer/TranslationAwareNormalizer.php | 60 ++- .../AbstractTypedObjectConstructor.php | 17 +- .../AttributeObjectConstructor.php | 55 --- .../ChainDoctrineObjectConstructor.php | 24 +- .../GroupObjectConstructor.php | 4 +- .../NodeObjectConstructor.php | 4 +- .../NodeTypeFieldObjectConstructor.php | 64 ++++ .../NodeTypeObjectConstructor.php | 42 ++- .../RoleObjectConstructor.php | 4 +- .../SettingGroupObjectConstructor.php | 4 +- .../SettingObjectConstructor.php | 4 +- .../TagObjectConstructor.php | 4 +- .../TranslationObjectConstructor.php | 11 +- .../TranslationAwareContextBuilder.php | 63 ++-- .../NodeType/ApiResourceGeneratorTest.php | 152 -------- src/TwigExtension/AttributesExtension.php | 14 +- src/TwigExtension/BlockRenderExtension.php | 12 +- .../CentralTruncateExtension.php | 2 +- src/TwigExtension/DocumentUrlExtension.php | 17 +- src/TwigExtension/HandlerExtension.php | 14 +- src/TwigExtension/JwtExtension.php | 14 +- src/TwigExtension/LogExtension.php | 13 +- src/TwigExtension/NodesSourcesExtension.php | 113 +++--- src/TwigExtension/RoadizExtension.php | 35 +- src/TwigExtension/RoutingExtension.php | 19 +- src/TwigExtension/TransChoiceExtension.php | 8 +- src/TwigExtension/TranslationExtension.php | 2 +- .../TranslationMenuExtension.php | 17 +- .../Event/NodeStatusGuardListener.php | 2 +- src/Xlsx/NodeSourceXlsxSerializer.php | 1 + tests/bootstrap.php | 11 - tests/expected_api_resources/nssecondtest.yml | 35 -- tests/expected_api_resources/nstest.yml | 35 -- tests/expected_api_resources/web_response.yml | 32 -- .../web_response_multiple.yml | 61 ---- tests/object-manager.php | 11 - translations/validators.ar.xlf | 1 - translations/validators.en.xlf | 10 +- translations/validators.es.xlf | 1 - translations/validators.fr.xlf | 9 +- translations/validators.id.xlf | 1 - translations/validators.it.xlf | 1 - translations/validators.ru.xlf | 1 - translations/validators.sr.xlf | 1 - translations/validators.tr.xlf | 1 - 306 files changed, 4726 insertions(+), 4587 deletions(-) delete mode 100644 .env delete mode 100644 .env.test delete mode 100644 migrations/Version20240214143213.php delete mode 100644 migrations/Version20240214143849.php delete mode 100644 migrations/Version20240214145403.php delete mode 100644 migrations/Version20240305124809.php delete mode 100644 migrations/Version20240305125653.php delete mode 100644 migrations/Version20240305132609.php delete mode 100644 migrations/Version20240305133122.php delete mode 100644 migrations/Version20240305133641.php delete mode 100644 migrations/Version20240305134734.php delete mode 100644 migrations/Version20240305142243.php delete mode 100644 migrations/Version20240305143443.php delete mode 100644 migrations/Version20240318184555.php delete mode 100644 migrations/Version20240318184556.php delete mode 100644 migrations/Version20240318204224.php delete mode 100644 phpunit.xml.dist delete mode 100644 src/Api/Model/WebResponseTrait.php create mode 100644 src/Console/DecodePrivateKeyCommand.php create mode 100644 src/Console/EncodePrivateKeyCommand.php create mode 100644 src/Console/GeneratePrivateKeyCommand.php delete mode 100644 src/Console/GetCronLastExecDateCommand.php create mode 100644 src/Console/NodesCreationCommand.php create mode 100644 src/Console/PrivateKeyCommand.php delete mode 100644 src/Console/RegisterCronLastExecDateCommand.php create mode 100644 src/Crypto/UniqueKeyEncoderFactory.php create mode 100644 src/Doctrine/EventSubscriber/SettingLifeCycleSubscriber.php delete mode 100644 src/Entity/AbstractDateTimedPositioned.php delete mode 100644 src/Entity/FieldAwareEntityTrait.php delete mode 100644 src/EventSubscriber/NodesSourcesAddHeadersSubscriber.php delete mode 100644 src/Form/DataListTextType.php delete mode 100644 src/ListManager/SessionListFilters.php delete mode 100644 src/NodeType/ApiResourceOperationNameGenerator.php create mode 100644 src/Routing/DeferredRouteCollection.php create mode 100644 src/Routing/StaticRouter.php delete mode 100644 src/SearchEngine/SolrSearchResultItem.php delete mode 100644 src/Serializer/ObjectConstructor/AttributeObjectConstructor.php create mode 100644 src/Serializer/ObjectConstructor/NodeTypeFieldObjectConstructor.php delete mode 100644 src/Test/NodeType/ApiResourceGeneratorTest.php delete mode 100644 tests/bootstrap.php delete mode 100644 tests/expected_api_resources/nssecondtest.yml delete mode 100644 tests/expected_api_resources/nstest.yml delete mode 100644 tests/expected_api_resources/web_response.yml delete mode 100644 tests/expected_api_resources/web_response_multiple.yml delete mode 100644 tests/object-manager.php diff --git a/.env b/.env deleted file mode 100644 index 0e52df3d..00000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -# default env file diff --git a/.env.test b/.env.test deleted file mode 100644 index 9e7162f0..00000000 --- a/.env.test +++ /dev/null @@ -1,6 +0,0 @@ -# define your env variables for the test env here -KERNEL_CLASS='App\Kernel' -APP_SECRET='$ecretf0rt3st' -SYMFONY_DEPRECATIONS_HELPER=999999 -PANTHER_APP_ENV=panther -PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots diff --git a/LICENSE.md b/LICENSE.md index 8e18fa63..d4d8a009 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright © 2024 Ambroise Maupate +Copyright © 2023 Ambroise Maupate 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 5dc81bc4..e9dcb335 100644 --- a/composer.json +++ b/composer.json @@ -23,71 +23,72 @@ "ext-iconv": "*", "ext-zip": "*", "ext-json": "*", - "api-platform/core": "~3.2.14", + "api-platform/core": "~2.7.0", "doctrine/annotations": "^1.0", "doctrine/doctrine-bundle": "^2.8.1", "doctrine/doctrine-migrations-bundle": "^3.1", - "doctrine/orm": "~2.19.0", + "doctrine/orm": "~2.17.0", "gedmo/doctrine-extensions": "^3.10.0", "inlinestyle/inlinestyle": "~1.2.7", "james-heinrich/getid3": "^1.9", "jms/serializer": "^3.9.0", - "jms/serializer-bundle": "^4.2.0", + "jms/serializer-bundle": "^3.10.0", "league/flysystem": "^3.0", "league/flysystem-bundle": "^3.0", "lexik/jwt-authentication-bundle": "^2.19", "phpdocumentor/reflection-docblock": "^5.2", "phpoffice/phpspreadsheet": "^1.15", "ramsey/uuid": "^4.7", + "rezozero/crypto": "^1.0.0", "rezozero/intervention-request-bundle": "~3.0.0", "rezozero/liform-bundle": "^0.19", "rezozero/tree-walker": "^1.3.0", - "roadiz/doc-generator": "2.3.*", - "roadiz/documents": "2.3.*", - "roadiz/dts-generator": "2.3.*", - "roadiz/entity-generator": "2.3.*", - "roadiz/jwt": "2.3.*", - "roadiz/markdown": "2.3.*", - "roadiz/models": "2.3.*", + "roadiz/doc-generator": "2.2.*", + "roadiz/documents": "2.2.*", + "roadiz/dts-generator": "2.2.*", + "roadiz/entity-generator": "2.2.*", + "roadiz/jwt": "2.2.*", + "roadiz/markdown": "2.2.*", + "roadiz/models": "2.2.*", "roadiz/nodetype-contracts": "~1.1.2", - "roadiz/random": "2.3.*", + "roadiz/random": "2.2.*", "rollerworks/password-common-list": "^0.2.0", "rollerworks/password-strength-bundle": "^2.2", "scienta/doctrine-json-functions": "^4.2", "sensio/framework-extra-bundle": "^6.1", "solarium/solarium": "^6.0.4", - "symfony-cmf/routing-bundle": "^3.0.2", - "symfony/asset": "6.4.*", - "symfony/cache": "6.4.*", - "symfony/console": "6.4.*", - "symfony/dotenv": "6.4.*", - "symfony/expression-language": "6.4.*", + "symfony-cmf/routing": "^2.3.3", + "symfony-cmf/routing-bundle": "^2.5", + "symfony/asset": "5.4.*", + "symfony/cache": "5.4.*", + "symfony/console": "5.4.*", + "symfony/dotenv": "5.4.*", + "symfony/expression-language": "5.4.*", "symfony/flex": "^2.2.3", - "symfony/form": "6.4.*", - "symfony/framework-bundle": "6.4.*", - "symfony/http-client": "6.4.*", - "symfony/intl": "6.4.*", - "symfony/lock": "6.4.*", - "symfony/mailer": "6.4.*", - "symfony/messenger": "6.4.*", - "symfony/mime": "6.4.*", + "symfony/form": "5.4.*", + "symfony/framework-bundle": "5.4.*", + "symfony/http-client": "5.4.*", + "symfony/intl": "5.4.*", + "symfony/mailer": "5.4.*", + "symfony/messenger": "5.4.*", + "symfony/mime": "5.4.*", "symfony/monolog-bundle": "^3.1", - "symfony/notifier": "6.4.*", - "symfony/process": "6.4.*", - "symfony/property-access": "6.4.*", - "symfony/property-info": "6.4.*", - "symfony/proxy-manager-bridge": "6.4.*", - "symfony/rate-limiter": "6.4.*", - "symfony/runtime": "6.4.*", - "symfony/security-core": "6.4.*", - "symfony/serializer": "6.4.*", - "symfony/string": "6.4.*", - "symfony/translation": "6.4.*", - "symfony/twig-bundle": "6.4.*", - "symfony/validator": "6.4.*", - "symfony/web-link": "6.4.*", - "symfony/workflow": "6.4.*", - "symfony/yaml": "6.4.*", + "symfony/notifier": "5.4.*", + "symfony/process": "5.4.*", + "symfony/property-access": "5.4.*", + "symfony/property-info": "5.4.*", + "symfony/proxy-manager-bridge": "5.4.*", + "symfony/rate-limiter": "5.4.*", + "symfony/runtime": "5.4.*", + "symfony/security-core": "5.4.*", + "symfony/serializer": "5.4.*", + "symfony/string": "5.4.*", + "symfony/translation": "5.4.*", + "symfony/twig-bundle": "5.4.*", + "symfony/validator": "5.4.*", + "symfony/web-link": "5.4.*", + "symfony/workflow": "5.4.*", + "symfony/yaml": "5.4.*", "twig/extra-bundle": "^3.0", "twig/intl-extra": "*", "twig/string-extra": "*", @@ -102,9 +103,9 @@ "phpstan/phpstan-doctrine": "^1.3", "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.5", - "symfony/browser-kit": "6.4.*", - "symfony/phpunit-bridge": "^7.0", - "symfony/stopwatch": "6.4.*" + "symfony/browser-kit": "5.4.*", + "symfony/phpunit-bridge": "5.4.*", + "symfony/stopwatch": "5.4.*" }, "config": { "optimize-autoloader": true, @@ -131,8 +132,8 @@ }, "extra": { "branch-alias": { - "dev-main": "2.3.x-dev", - "dev-develop": "2.4.x-dev" + "dev-main": "2.2.x-dev", + "dev-develop": "2.3.x-dev" } } } diff --git a/config/api_resources/attribute.yml b/config/api_resources/attribute.yml index 07ccee13..1b48bcc9 100644 --- a/config/api_resources/attribute.yml +++ b/config/api_resources/attribute.yml @@ -1,4 +1,4 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\Attribute: -# operations: [] -# +--- +RZ\Roadiz\CoreBundle\Entity\Attribute: + operations: [] + diff --git a/config/api_resources/attribute_value.yml b/config/api_resources/attribute_value.yml index 50fd10fe..23eee57d 100644 --- a/config/api_resources/attribute_value.yml +++ b/config/api_resources/attribute_value.yml @@ -1,14 +1,14 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\AttributeValue: -# operations: -# ApiPlatform\Metadata\GetCollection: -# method: "GET" -# normalizationContext: -# groups: ["urls", "attribute", "document_display", "attribute_node", "attribute_documents"] -# enable_max_depth: true -# ApiPlatform\Metadata\Get: -# method: 'GET' -# normalizationContext: -# groups: ["urls", "attribute", "document_display", "attribute_node", "attribute_documents"] -# enable_max_depth: true -# +--- +RZ\Roadiz\CoreBundle\Entity\AttributeValue: + operations: + ApiPlatform\Metadata\GetCollection: + method: "GET" + normalizationContext: + groups: ["urls", "attribute", "document_display", "attribute_node", "attribute_documents"] + enable_max_depth: true + ApiPlatform\Metadata\Get: + method: 'GET' + normalizationContext: + groups: ["urls", "attribute", "document_display", "attribute_node", "attribute_documents"] + enable_max_depth: true + diff --git a/config/api_resources/common_content.yml b/config/api_resources/common_content.yml index a02a765a..cb452fe8 100644 --- a/config/api_resources/common_content.yml +++ b/config/api_resources/common_content.yml @@ -1,29 +1,28 @@ -#resources: -# App\Api\Model\CommonContent: -# operations: -# getCommonContent: -# class: ApiPlatform\Metadata\Get -# method: 'GET' -# uriTemplate: '/common_content' -# read: false -# controller: App\Controller\GetCommonContentController -# pagination_enabled: false -# normalizationContext: -# enable_max_depth: true -# pagination_enabled: false -# groups: -# - get -# - common_content -# - web_response -# - walker -# - walker_level -# - children -# - children_count -# - nodes_sources_base -# - nodes_sources_default -# - urls -# - blocks_urls -# - tag_base -# - translation_base -# - document_display -# - document_folders +App\Api\Model\CommonContent: + operations: + getCommonContent: + class: ApiPlatform\Metadata\Get + method: 'GET' + uriTemplate: '/common_content' + read: false + controller: App\Controller\GetCommonContentController + pagination_enabled: false + normalizationContext: + enable_max_depth: true + pagination_enabled: false + groups: + - get + - common_content + - web_response + - walker + - walker_level + - children + - children_count + - nodes_sources_base + - nodes_sources_default + - urls + - blocks_urls + - tag_base + - translation_base + - document_display + - document_folders diff --git a/config/api_resources/custom_form.yml b/config/api_resources/custom_form.yml index 0a4f26de..fbb79c5f 100644 --- a/config/api_resources/custom_form.yml +++ b/config/api_resources/custom_form.yml @@ -1,110 +1,110 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\CustomForm: -# operations: -# ApiPlatform\Metadata\GetCollection: -# method: "GET" -# normalizationContext: -# enable_max_depth: true -# -# ApiPlatform\Metadata\Get: -# method: 'GET' -# normalizationContext: -# enable_max_depth: true -# -# api_custom_forms_item_post: -# method: 'POST' -# class: ApiPlatform\Metadata\Post -# routeName: api_custom_forms_item_post -# normalizationContext: -# enable_max_depth: true -# openapiContext: -# summary: Post a user custom form -# description: | -# Post a user custom form -# requestBody: -# content: -# multipart/form-data: -# schema: -# type: object -# properties: -# custom_form_slug[email]: -# type: string -# example: test@test.test -# custom_form_slug[first_name]: -# type: string -# example: John -# custom_form_slug[last_name]: -# type: string -# example: Doe -# responses: -# 201: ~ -# 400: -# description: Posted custom form has errors -# content: -# application/json: -# schema: -# type: object -# properties: -# email: -# type: object -# example: -# email: This value is not a valid email address. -# 202: -# description: Posted custom form was accepted -# content: -# application/json: -# schema: -# type: object -# properties: { } -# -# api_custom_forms_item_definition: -# method: 'GET' -# class: ApiPlatform\Metadata\Get -# routeName: api_custom_forms_item_definition -# normalizationContext: -# enable_max_depth: true -# openapiContext: -# summary: Get a custom form definition for frontend -# description: | -# Get a custom form definition for frontend -# responses: -# 200: -# description: Custom form definition object -# content: -# application/json: -# schema: -# type: object -# properties: -# title: -# type: string -# description: Form inputs prefix -# example: reiciendis_natus_ducimus_nostrum -# type: -# type: string -# description: Form definition type -# example: object -# properties: -# type: object -# description: Form definition fields -# example: -# email: -# type: string -# title: Email -# attr: -# data-group: null -# placeholder: null -# widget: email -# propertyOrder: 1 -# first_name: -# type: string -# title: Firstname -# attr: -# data-group: null -# placeholder: null -# widget: string -# propertyOrder: 2 -# required: -# type: array -# description: Required fields names -# example: -# - 'email' +--- +RZ\Roadiz\CoreBundle\Entity\CustomForm: + operations: + ApiPlatform\Metadata\GetCollection: + method: "GET" + normalizationContext: + enable_max_depth: true + + ApiPlatform\Metadata\Get: + method: 'GET' + normalizationContext: + enable_max_depth: true + + api_custom_forms_item_post: + method: 'POST' + class: ApiPlatform\Metadata\Post + routeName: api_custom_forms_item_post + normalizationContext: + enable_max_depth: true + openapiContext: + summary: Post a user custom form + description: | + Post a user custom form + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + custom_form_slug[email]: + type: string + example: test@test.test + custom_form_slug[first_name]: + type: string + example: John + custom_form_slug[last_name]: + type: string + example: Doe + responses: + 201: ~ + 400: + description: Posted custom form has errors + content: + application/json: + schema: + type: object + properties: + email: + type: object + example: + email: This value is not a valid email address. + 202: + description: Posted custom form was accepted + content: + application/json: + schema: + type: object + properties: { } + + api_custom_forms_item_definition: + method: 'GET' + class: ApiPlatform\Metadata\Get + routeName: api_custom_forms_item_definition + normalizationContext: + enable_max_depth: true + openapiContext: + summary: Get a custom form definition for frontend + description: | + Get a custom form definition for frontend + responses: + 200: + description: Custom form definition object + content: + application/json: + schema: + type: object + properties: + title: + type: string + description: Form inputs prefix + example: reiciendis_natus_ducimus_nostrum + type: + type: string + description: Form definition type + example: object + properties: + type: object + description: Form definition fields + example: + email: + type: string + title: Email + attr: + data-group: null + placeholder: null + widget: email + propertyOrder: 1 + first_name: + type: string + title: Firstname + attr: + data-group: null + placeholder: null + widget: string + propertyOrder: 2 + required: + type: array + description: Required fields names + example: + - 'email' diff --git a/config/api_resources/document.yml b/config/api_resources/document.yml index db91f92b..d875d9f0 100644 --- a/config/api_resources/document.yml +++ b/config/api_resources/document.yml @@ -1,15 +1,15 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\Document: -# operations: -# ApiPlatform\Metadata\GetCollection: -# method: "GET" -# normalizationContext: -# groups: ["urls", "document_display", "document_folders", "document_folders_all", "document_display_sources"] -# enable_max_depth: true -# -# ApiPlatform\Metadata\Get: -# method: 'GET' -# normalizationContext: -# groups: ["urls", "document", "document_display", "document_folders", "document_folders_all", "document_display_sources"] -# enable_max_depth: true -# +--- +RZ\Roadiz\CoreBundle\Entity\Document: + operations: + ApiPlatform\Metadata\GetCollection: + method: "GET" + normalizationContext: + groups: ["urls", "document_display", "document_folders", "document_folders_all", "document_display_sources"] + enable_max_depth: true + + ApiPlatform\Metadata\Get: + method: 'GET' + normalizationContext: + groups: ["urls", "document", "document_display", "document_folders", "document_folders_all", "document_display_sources"] + enable_max_depth: true + diff --git a/config/api_resources/folder.yml b/config/api_resources/folder.yml index 01016890..38dafc52 100644 --- a/config/api_resources/folder.yml +++ b/config/api_resources/folder.yml @@ -1,13 +1,13 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\Folder: -# operations: -# ApiPlatform\Metadata\GetCollection: -# method: "GET" -# normalizationContext: -# groups: [ "folder" ] -# enable_max_depth: true -# ApiPlatform\Metadata\Get: -# method: "GET" -# normalizationContext: -# groups: [ "folder" ] -# enable_max_depth: true +--- +RZ\Roadiz\CoreBundle\Entity\Folder: + operations: + ApiPlatform\Metadata\GetCollection: + method: "GET" + normalizationContext: + groups: [ "folder" ] + enable_max_depth: true + ApiPlatform\Metadata\Get: + method: "GET" + normalizationContext: + groups: [ "folder" ] + enable_max_depth: true diff --git a/config/api_resources/node.yml b/config/api_resources/node.yml index ce20501e..94e6cf1b 100644 --- a/config/api_resources/node.yml +++ b/config/api_resources/node.yml @@ -1,8 +1,8 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\Node: -# operations: -# ApiPlatform\Metadata\Get: -# method: 'GET' -# normalizationContext: -# groups: ["node", "document_display"] -# enable_max_depth: true +--- +RZ\Roadiz\CoreBundle\Entity\Node: + operations: + ApiPlatform\Metadata\Get: + method: 'GET' + normalizationContext: + groups: ["node", "document_display"] + enable_max_depth: true diff --git a/config/api_resources/nodes_sources.yml b/config/api_resources/nodes_sources.yml index 9b379c70..602794b7 100644 --- a/config/api_resources/nodes_sources.yml +++ b/config/api_resources/nodes_sources.yml @@ -1,66 +1,66 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\NodesSources: -# operations: -# ApiPlatform\Metadata\GetCollection: -# method: "GET" -# normalizationContext: -# groups: -# - nodes_sources_base -# - nodes_sources_default -# - user -# - urls -# - tag_base -# - translation_base -# - document_display -# -# api_nodes_sources_archives: -# class: ApiPlatform\Metadata\GetCollection -# method: 'GET' -# uriTemplate: '/nodes_sources/archives' -# pagination_enabled: false -# pagination_client_enabled: false -# extraProperties: -# archive_enabled: true -# archive_publication_field_name: publishedAt -# normalizationContext: -# groups: -# - get -# - archives -# openapiContext: -# summary: Get available NodesSources archives -# parameters: ~ -# description: | -# Get available NodesSources archives (years and months) based on their `publishedAt` field -# -# api_nodes_sources_search: -# class: ApiPlatform\Metadata\GetCollection -# method: 'GET' -# uriTemplate: '/nodes_sources/search' -# controller: RZ\Roadiz\CoreBundle\Api\Controller\NodesSourcesSearchController -# read: false -# normalizationContext: -# groups: -# - get -# - nodes_sources_base -# - nodes_sources_default -# - urls -# - tag_base -# - translation_base -# - document_display -# openapiContext: -# summary: Search NodesSources resources -# description: | -# Search all website NodesSources resources using **Solr** full-text search engine -# parameters: -# - type: string -# name: search -# in: query -# required: true -# description: Search pattern -# schema: -# type: string -# -# ApiPlatform\Metadata\Get: -# method: 'GET' -# normalizationContext: -# groups: ["nodes_sources", "urls", "tag_base", "translation_base", "document_display"] +--- +RZ\Roadiz\CoreBundle\Entity\NodesSources: + operations: + ApiPlatform\Metadata\GetCollection: + method: "GET" + normalizationContext: + groups: + - nodes_sources_base + - nodes_sources_default + - user + - urls + - tag_base + - translation_base + - document_display + + api_nodes_sources_archives: + class: ApiPlatform\Metadata\GetCollection + method: 'GET' + uriTemplate: '/nodes_sources/archives' + pagination_enabled: false + pagination_client_enabled: false + extraProperties: + archive_enabled: true + archive_publication_field_name: publishedAt + normalizationContext: + groups: + - get + - archives + openapiContext: + summary: Get available NodesSources archives + parameters: ~ + description: | + Get available NodesSources archives (years and months) based on their `publishedAt` field + + api_nodes_sources_search: + class: ApiPlatform\Metadata\GetCollection + method: 'GET' + uriTemplate: '/nodes_sources/search' + controller: RZ\Roadiz\CoreBundle\Api\Controller\NodesSourcesSearchController + read: false + normalizationContext: + groups: + - get + - nodes_sources_base + - nodes_sources_default + - urls + - tag_base + - translation_base + - document_display + openapiContext: + summary: Search NodesSources resources + description: | + Search all website NodesSources resources using **Solr** full-text search engine + parameters: + - type: string + name: search + in: query + required: true + description: Search pattern + schema: + type: string + + ApiPlatform\Metadata\Get: + method: 'GET' + normalizationContext: + groups: ["nodes_sources", "urls", "tag_base", "translation_base", "document_display"] diff --git a/config/api_resources/realm.yml b/config/api_resources/realm.yml index f0bc2faa..50dbdd8b 100644 --- a/config/api_resources/realm.yml +++ b/config/api_resources/realm.yml @@ -1,14 +1,14 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\Realm: -# operations: -# ApiPlatform\Metadata\GetCollection: -# method: "GET" -# normalizationContext: -# groups: [ "realm" ] -# enable_max_depth: true -# -# ApiPlatform\Metadata\Get: -# method: "GET" -# normalizationContext: -# groups: [ "realm" ] -# enable_max_depth: true +--- +RZ\Roadiz\CoreBundle\Entity\Realm: + operations: + ApiPlatform\Metadata\GetCollection: + method: "GET" + normalizationContext: + groups: [ "realm" ] + enable_max_depth: true + + ApiPlatform\Metadata\Get: + method: "GET" + normalizationContext: + groups: [ "realm" ] + enable_max_depth: true diff --git a/config/api_resources/tag.yml b/config/api_resources/tag.yml index e9b64d16..3f35d12b 100644 --- a/config/api_resources/tag.yml +++ b/config/api_resources/tag.yml @@ -1,18 +1,18 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\Tag: -# operations: -# ApiPlatform\Metadata\GetCollection: -# method: "GET" -# normalizationContext: -# enable_max_depth: true -# groups: -# - tag_base -# -# ApiPlatform\Metadata\Get: -# method: 'GET' -# normalizationContext: -# enable_max_depth: true -# groups: -# - tag -# - tag_base -# - tag_parent +--- +RZ\Roadiz\CoreBundle\Entity\Tag: + operations: + ApiPlatform\Metadata\GetCollection: + method: "GET" + normalizationContext: + enable_max_depth: true + groups: + - tag_base + + ApiPlatform\Metadata\Get: + method: 'GET' + normalizationContext: + enable_max_depth: true + groups: + - tag + - tag_base + - tag_parent diff --git a/config/api_resources/translation.yml b/config/api_resources/translation.yml index c0d56c1a..ad19c01c 100644 --- a/config/api_resources/translation.yml +++ b/config/api_resources/translation.yml @@ -1,17 +1,17 @@ -#resources: -# RZ\Roadiz\CoreBundle\Entity\Translation: -# operations: -# ApiPlatform\Metadata\GetCollection: -# method: "GET" -# normalizationContext: -# enable_max_depth: true -# groups: -# - translation_base -# -# ApiPlatform\Metadata\Get: -# method: 'GET' -# normalizationContext: -# enable_max_depth: true -# groups: -# - translation_base -# +--- +RZ\Roadiz\CoreBundle\Entity\Translation: + operations: + ApiPlatform\Metadata\GetCollection: + method: "GET" + normalizationContext: + enable_max_depth: true + groups: + - translation_base + + ApiPlatform\Metadata\Get: + method: 'GET' + normalizationContext: + enable_max_depth: true + groups: + - translation_base + diff --git a/config/api_resources/web_response.yml b/config/api_resources/web_response.yml index 1c2a1d41..cf9513c9 100644 --- a/config/api_resources/web_response.yml +++ b/config/api_resources/web_response.yml @@ -1,3 +1,41 @@ -#resources: -# RZ\Roadiz\CoreBundle\Api\Model\WebResponse: -# operations: [] +RZ\Roadiz\CoreBundle\Api\Model\WebResponse: + operations: + getByPath: + class: ApiPlatform\Metadata\Get + method: 'GET' + uriTemplate: '/web_response_by_path' + read: false + controller: RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController + pagination_enabled: false + normalizationContext: + enable_max_depth: true + pagination_enabled: false + groups: + - get + - web_response + - position + - walker + - walker_level + - meta + - children + - children_count + - nodes_sources + - node_listing + - urls + - tag_base + - translation_base + - document_display + - node_attributes + - document_display_sources + openapiContext: + summary: Get a resource by its path wrapped in a WebResponse object + description: | + Get a resource by its path wrapped in a WebResponse + parameters: + - type: string + name: path + in: query + required: true + description: Resource path, or `/` for home page + schema: + type: string diff --git a/config/fixtures/settings.json b/config/fixtures/settings.json index ee93f247..137890ad 100644 --- a/config/fixtures/settings.json +++ b/config/fixtures/settings.json @@ -2,6 +2,7 @@ { "name": "force_locale", "visible": true, + "encrypted": false, "description": "Force displaying translation locale in every node’ paths. This should be *ON* if you redirect users based on their language on homepage.", "setting_group": { "name": "Development", @@ -14,6 +15,7 @@ "name": "force_locale_with_urlaliases", "description": "force_locale_with_urlaliases.help", "visible": true, + "encrypted": false, "setting_group": { "name": "Development", "in_menu": true @@ -23,6 +25,7 @@ { "name": "leaflet_map_tile_url", "value": "https:\/\/{s}.tile.osm.org\/{z}\/{x}\/{y}.png", + "encrypted": false, "description": "Default maps tiles layout when using *Leaflet*.", "visible": true, "setting_group": { @@ -35,6 +38,7 @@ { "name": "maps_default_location", "value": "{\"lat\":45.769785, \"lng\":4.833967, \"zoom\":14}", + "encrypted": false, "description": "Default maps marker location.", "visible": true, "setting_group": { @@ -48,6 +52,7 @@ "name": "openid_button_label", "description": "openid_button_label.help", "visible": true, + "encrypted": false, "setting_group": { "name": "OpenId", "in_menu": true @@ -57,6 +62,7 @@ { "name": "support_email_address", "visible": true, + "encrypted": false, "description": "Support email address, used in every system emails footer", "setting_group": { "name": "Emailings", @@ -68,6 +74,7 @@ { "name": "email_sender", "visible": true, + "encrypted": false, "description": "Default sender email, used as origin for every system email sent. This email **must be allowed by your SMTP server.**", "setting_group": { "name": "Emailings", @@ -79,6 +86,7 @@ { "name": "email_sender_name", "visible": true, + "encrypted": false, "setting_group": { "name": "Emailings", "in_menu": true @@ -89,6 +97,7 @@ { "name": "universal_analytics_id", "visible": true, + "encrypted": false, "setting_group": { "name": "APIs", "in_menu": true @@ -99,6 +108,18 @@ { "name": "google_tag_manager_id", "visible": true, + "encrypted": false, + "setting_group": { + "name": "APIs", + "in_menu": true + }, + "type": 0, + "default_values": "" + }, + { + "name": "instagram_access_token", + "visible": true, + "encrypted": true, "setting_group": { "name": "APIs", "in_menu": true @@ -109,6 +130,7 @@ { "name": "seo_description", "visible": true, + "encrypted": false, "setting_group": { "name": "Site information", "in_menu": true @@ -119,6 +141,7 @@ { "name": "site_name", "visible": true, + "encrypted": false, "setting_group": { "name": "Site information", "in_menu": true @@ -129,6 +152,7 @@ { "name": "maintenance_mode", "visible": true, + "encrypted": false, "description": "Switch maintenance mode. Only login page will be available for public requests.", "setting_group": { "name": "Site information", @@ -140,6 +164,7 @@ { "name": "site_copyright", "visible": true, + "encrypted": false, "setting_group": { "name": "Site information", "in_menu": true @@ -149,6 +174,7 @@ }, { "name": "main_color", + "encrypted": false, "visible": true, "setting_group": { "name": "Site information", @@ -160,6 +186,7 @@ { "name": "admin_image", "visible": true, + "encrypted": false, "setting_group": { "name": "Site information", "in_menu": true @@ -170,6 +197,7 @@ { "name": "login_image", "visible": true, + "encrypted": false, "description": "Replace random *Splashbase* login images with your own.", "setting_group": { "name": "Site information", @@ -181,6 +209,7 @@ { "name": "facebook_url", "visible": true, + "encrypted": false, "setting_group": { "name": "Social networks", "in_menu": true @@ -191,6 +220,7 @@ { "name": "instagram_url", "visible": true, + "encrypted": false, "setting_group": { "name": "Social networks", "in_menu": true @@ -201,6 +231,7 @@ { "name": "pinterest_url", "visible": true, + "encrypted": false, "setting_group": { "name": "Social networks", "in_menu": true @@ -211,6 +242,7 @@ { "name": "twitter_url", "visible": true, + "encrypted": false, "setting_group": { "name": "Social networks", "in_menu": true @@ -221,6 +253,7 @@ { "name": "linkedin_url", "visible": true, + "encrypted": false, "setting_group": { "name": "Social networks", "in_menu": true @@ -231,6 +264,7 @@ { "name": "youtube_url", "visible": true, + "encrypted": false, "setting_group": { "name": "Social networks", "in_menu": true @@ -242,6 +276,7 @@ "name": "custom_preview_scheme", "description": "custom_preview_scheme.help", "visible": true, + "encrypted": false, "setting_group": { "name": "Site information", "in_menu": true @@ -252,6 +287,7 @@ "name": "custom_public_scheme", "description": "custom_public_scheme.help", "visible": true, + "encrypted": false, "setting_group": { "name": "Site information", "in_menu": true @@ -262,6 +298,7 @@ "name": "dashboard_iframe", "description": "dashboard_iframe.help", "visible": true, + "encrypted": false, "setting_group": { "name": "Site information", "in_menu": true diff --git a/config/packages/roadiz_core.yaml b/config/packages/roadiz_core.yaml index 1f5bf32d..818584c3 100644 --- a/config/packages/roadiz_core.yaml +++ b/config/packages/roadiz_core.yaml @@ -11,6 +11,9 @@ roadiz_core: # Be careful if you are using a reverse-proxy cache, YOU MUST vary on Accept-Language header and normalize it. # @see https://varnish-cache.org/docs/6.3/users-guide/increasing-your-hitrate.html#http-vary useAcceptLanguageHeader: '%env(bool:APP_USE_ACCEPT_LANGUAGE_HEADER)%' + security: + private_key_name: default + themes: [] medias: unsplash_client_id: '%env(string:APP_UNSPLASH_CLIENT_ID)%' soundcloud_client_id: '%env(string:APP_SOUNDCLOUD_CLIENT_ID)%' diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 8324dc43..840b2f1d 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -25,8 +25,8 @@ security: # https://symfony.com/bundles/LexikJWTAuthenticationBundle/current/index.html#configure-application-routing api_login: - stateless: true pattern: ^/api/token + stateless: true provider: all_users login_throttling: max_attempts: 3 diff --git a/config/routing.yaml b/config/routing.yaml index e3a597bc..361fab01 100644 --- a/config/routing.yaml +++ b/config/routing.yaml @@ -5,14 +5,12 @@ api_custom_forms_item_definition: methods: [GET] path: /api/custom_forms/{id}/definition controller: RZ\Roadiz\CoreBundle\Controller\CustomFormController::definitionAction - stateless: true requirements: id: "[0-9]+" api_custom_forms_item_post: methods: [POST] path: /api/custom_forms/{id}/post controller: RZ\Roadiz\CoreBundle\Controller\CustomFormController::postAction - stateless: true requirements: id: "[0-9]+" @@ -30,10 +28,12 @@ customFormSentAction: healthCheckAction: methods: [GET] path: /health-check - stateless: true controller: RZ\Roadiz\CoreBundle\Controller\HealthCheckController +roadiz_core_themes: + resource: . + type: themes + api_login_check: methods: [POST] - stateless: true path: /api/token diff --git a/config/services.yaml b/config/services.yaml index e4120845..dda11297 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,6 +1,6 @@ --- parameters: - roadiz_core.cms_version: '2.3.14' + roadiz_core.cms_version: '2.2.26' roadiz_core.cms_version_prefix: 'main' env(APP_NAMESPACE): "roadiz" env(APP_VERSION): "0.1.0" @@ -27,19 +27,20 @@ services: $cmsVersion: '%roadiz_core.cms_version%' $appVersion: '%roadiz_core.app_version%' $cmsVersionPrefix: '%roadiz_core.cms_version_prefix%' + $staticDomain: '%roadiz_core.static_domain_name%' $hideRoadizVersion: '%roadiz_core.hide_roadiz_version%' $inheritanceType: '%roadiz_core.inheritance_type%' $maxPixelSize: '%rz_intervention_request.max_pixel_size%' $appNamespace: '%roadiz_core.app_namespace%' $projectDir: '%kernel.project_dir%' $exportDir: '%kernel.project_dir%/var/export' + $privateKeyName: '%roadiz_core.private_key_name%' $generatedEntitiesDir: '%roadiz_core.generated_entities_dir%' $serializedNodeTypesDir: '%roadiz_core.serialized_node_types_dir%' $importFilesConfigPath: '%roadiz_core.import_files_config_path%' $kernelProjectDir: '%kernel.project_dir%' $apiResourcesDir: '%kernel.project_dir%/config/api_resources' $debug: '%kernel.debug%' - $kernelEnvironment: '%kernel.environment%' $defaultControllerClass: '%roadiz_core.default_node_source_controller%' $defaultLocale: '%kernel.default_locale%' $webhookMessageTypes: '%roadiz_core.webhook.message_types%' @@ -50,8 +51,6 @@ services: $maxVersionsShowed: '%roadiz_core.max_versions_showed%' $recaptchaPublicKey: '%roadiz_core.medias.recaptcha_public_key%' $recaptchaPrivateKey: '%roadiz_core.medias.recaptcha_private_key%' - $webResponseClass: '%roadiz_core.web_response_class%' - $useGravatar: '%roadiz_core.use_gravatar%' RZ\Roadiz\CoreBundle\: resource: '../src/' @@ -437,6 +436,12 @@ services: RZ\Roadiz\Random\PasswordGenerator: ~ + RZ\Crypto\KeyChain\KeyChainInterface: + alias: RZ\Crypto\KeyChain\AsymmetricFilesystemKeyChain + + RZ\Crypto\KeyChain\AsymmetricFilesystemKeyChain: + arguments: ['%kernel.project_dir%/var/secret', true] + JMS\Serializer\Construction\ObjectConstructorInterface: alias: RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\ObjectConstructor @@ -447,10 +452,10 @@ services: - '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\ObjectConstructor' - [ '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\TranslationObjectConstructor', - '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\AttributeObjectConstructor', '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\TagObjectConstructor', '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\NodeObjectConstructor', '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\NodeTypeObjectConstructor', + '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\NodeTypeFieldObjectConstructor', '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\RoleObjectConstructor', '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\GroupObjectConstructor', '@RZ\Roadiz\CoreBundle\Serializer\ObjectConstructor\SettingObjectConstructor', @@ -528,9 +533,9 @@ services: RZ\Roadiz\Documents\Renderer\PdfRenderer: tags: [ 'roadiz_core.document_renderer' ] RZ\Roadiz\Documents\Renderer\SvgRenderer: - tags: [ { name: 'roadiz_core.document_renderer', priority: 10 } ] + tags: [ 'roadiz_core.document_renderer' ] RZ\Roadiz\Documents\Renderer\InlineSvgRenderer: - tags: [ { name: 'roadiz_core.document_renderer', priority: 11 } ] + tags: [ 'roadiz_core.document_renderer' ] RZ\Roadiz\Documents\Renderer\EmbedRenderer: tags: [ { name: 'roadiz_core.document_renderer', priority: -128 } ] RZ\Roadiz\Documents\Renderer\ThumbnailRenderer: @@ -546,6 +551,7 @@ services: alias: RZ\Roadiz\CoreBundle\Filesystem\RoadizFileDirectories public: true + RZ\Roadiz\Documents\Packages: ~ RZ\Roadiz\Documents\DownscaleImageManager: ~ RZ\Roadiz\Documents\DocumentArchiver: ~ # diff --git a/migrations/Version20240214143213.php b/migrations/Version20240214143213.php deleted file mode 100644 index c1fae99a..00000000 --- a/migrations/Version20240214143213.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE nodes_sources DROP meta_keywords'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE nodes_sources ADD meta_keywords LONGTEXT NOT NULL'); - } -} diff --git a/migrations/Version20240214143849.php b/migrations/Version20240214143849.php deleted file mode 100644 index 677a88f2..00000000 --- a/migrations/Version20240214143849.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE nodes DROP priority'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE nodes ADD priority NUMERIC(2, 1) NOT NULL'); - } -} diff --git a/migrations/Version20240214145403.php b/migrations/Version20240214145403.php deleted file mode 100644 index a20f4e63..00000000 --- a/migrations/Version20240214145403.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE settings DROP encrypted'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE settings ADD encrypted TINYINT(1) DEFAULT 0 NOT NULL'); - } -} diff --git a/migrations/Version20240305124809.php b/migrations/Version20240305124809.php deleted file mode 100644 index 8f7e1c46..00000000 --- a/migrations/Version20240305124809.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE realms_nodes CHANGE realm_id realm_id INT NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE realms_nodes CHANGE realm_id realm_id INT DEFAULT NULL'); - } -} diff --git a/migrations/Version20240305125653.php b/migrations/Version20240305125653.php deleted file mode 100644 index 254249fe..00000000 --- a/migrations/Version20240305125653.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE nodes_sources_documents CHANGE ns_id ns_id INT NOT NULL, CHANGE document_id document_id INT NOT NULL, CHANGE node_type_field_id node_type_field_id INT NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE nodes_sources_documents CHANGE ns_id ns_id INT DEFAULT NULL, CHANGE document_id document_id INT DEFAULT NULL, CHANGE node_type_field_id node_type_field_id INT DEFAULT NULL'); - } -} diff --git a/migrations/Version20240305132609.php b/migrations/Version20240305132609.php deleted file mode 100644 index 13ccd8a8..00000000 --- a/migrations/Version20240305132609.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE nodes_to_nodes CHANGE node_a_id node_a_id INT NOT NULL, CHANGE node_b_id node_b_id INT NOT NULL, CHANGE node_type_field_id node_type_field_id INT NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE nodes_to_nodes CHANGE node_a_id node_a_id INT DEFAULT NULL, CHANGE node_b_id node_b_id INT DEFAULT NULL, CHANGE node_type_field_id node_type_field_id INT DEFAULT NULL'); - } -} diff --git a/migrations/Version20240305133122.php b/migrations/Version20240305133122.php deleted file mode 100644 index f21faee5..00000000 --- a/migrations/Version20240305133122.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE nodes_custom_forms CHANGE node_id node_id INT NOT NULL, CHANGE custom_form_id custom_form_id INT NOT NULL, CHANGE node_type_field_id node_type_field_id INT NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE nodes_custom_forms CHANGE node_id node_id INT DEFAULT NULL, CHANGE custom_form_id custom_form_id INT DEFAULT NULL, CHANGE node_type_field_id node_type_field_id INT DEFAULT NULL'); - } -} diff --git a/migrations/Version20240305133641.php b/migrations/Version20240305133641.php deleted file mode 100644 index ba74548a..00000000 --- a/migrations/Version20240305133641.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE nodes_sources CHANGE node_id node_id INT NOT NULL, CHANGE translation_id translation_id INT NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE nodes_sources CHANGE node_id node_id INT DEFAULT NULL, CHANGE translation_id translation_id INT DEFAULT NULL'); - } -} diff --git a/migrations/Version20240305134734.php b/migrations/Version20240305134734.php deleted file mode 100644 index 0bb9c334..00000000 --- a/migrations/Version20240305134734.php +++ /dev/null @@ -1,37 +0,0 @@ -addSql('ALTER TABLE documents_translations CHANGE translation_id translation_id INT NOT NULL, CHANGE document_id document_id INT NOT NULL'); - $this->addSql('ALTER TABLE folders_translations CHANGE folder_id folder_id INT NOT NULL, CHANGE translation_id translation_id INT NOT NULL'); - $this->addSql('ALTER TABLE tags_translations CHANGE tag_id tag_id INT NOT NULL, CHANGE translation_id translation_id INT NOT NULL'); - $this->addSql('ALTER TABLE tags_translations_documents CHANGE tag_translation_id tag_translation_id INT NOT NULL, CHANGE document_id document_id INT NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE documents_translations CHANGE translation_id translation_id INT DEFAULT NULL, CHANGE document_id document_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE folders_translations CHANGE folder_id folder_id INT DEFAULT NULL, CHANGE translation_id translation_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE tags_translations CHANGE tag_id tag_id INT DEFAULT NULL, CHANGE translation_id translation_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE tags_translations_documents CHANGE tag_translation_id tag_translation_id INT DEFAULT NULL, CHANGE document_id document_id INT DEFAULT NULL'); - } -} diff --git a/migrations/Version20240305142243.php b/migrations/Version20240305142243.php deleted file mode 100644 index 9a8f36a5..00000000 --- a/migrations/Version20240305142243.php +++ /dev/null @@ -1,62 +0,0 @@ -addSql('ALTER TABLE attribute_group_translations CHANGE attribute_group_id attribute_group_id INT NOT NULL, CHANGE translation_id translation_id INT NOT NULL'); - $this->addSql('ALTER TABLE attribute_translations CHANGE attribute_id attribute_id INT NOT NULL, CHANGE translation_id translation_id INT NOT NULL'); - $this->addSql('ALTER TABLE attribute_value_translations CHANGE translation_id translation_id INT NOT NULL, CHANGE attribute_value attribute_value INT NOT NULL'); - $this->addSql('ALTER TABLE attribute_values CHANGE attribute_id attribute_id INT NOT NULL, CHANGE node_id node_id INT NOT NULL'); - $this->addSql('ALTER TABLE attributes_documents CHANGE attribute_id attribute_id INT NOT NULL, CHANGE document_id document_id INT NOT NULL'); - $this->addSql('ALTER TABLE custom_form_answers CHANGE custom_form_id custom_form_id INT NOT NULL'); - $this->addSql('ALTER TABLE custom_form_field_attributes CHANGE custom_form_answer_id custom_form_answer_id INT NOT NULL, CHANGE custom_form_field_id custom_form_field_id INT NOT NULL'); - $this->addSql('ALTER TABLE custom_form_fields CHANGE custom_form_id custom_form_id INT NOT NULL'); - - // Remove all node_type_fields where node_type_id is null before changing it to not-nullable - $this->addSql('DELETE FROM node_type_fields WHERE node_type_id IS NULL'); - $this->addSql('ALTER TABLE node_type_fields CHANGE node_type_id node_type_id INT NOT NULL'); - - // Remove all nodes where nodeType_id is null before changing it to not-nullable - $this->addSql('DELETE FROM nodes WHERE nodeType_id IS NULL'); - $this->addSql('ALTER TABLE nodes CHANGE nodeType_id nodeType_id INT NOT NULL'); - - // Remove all url_aliases where ns_id is null before changing it to not-nullable - $this->addSql('DELETE FROM url_aliases WHERE ns_id IS NULL'); - $this->addSql('ALTER TABLE url_aliases DROP FOREIGN KEY FK_E261ED65AA2D61'); - $this->addSql('ALTER TABLE url_aliases CHANGE ns_id ns_id INT NOT NULL'); - $this->addSql('ALTER TABLE url_aliases ADD CONSTRAINT FK_E261ED65AA2D61 FOREIGN KEY (ns_id) REFERENCES nodes_sources (id) ON DELETE CASCADE'); - } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE attribute_group_translations CHANGE translation_id translation_id INT DEFAULT NULL, CHANGE attribute_group_id attribute_group_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE attribute_translations CHANGE translation_id translation_id INT DEFAULT NULL, CHANGE attribute_id attribute_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE attribute_value_translations CHANGE translation_id translation_id INT DEFAULT NULL, CHANGE attribute_value attribute_value INT DEFAULT NULL'); - $this->addSql('ALTER TABLE attribute_values CHANGE node_id node_id INT DEFAULT NULL, CHANGE attribute_id attribute_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE attributes_documents CHANGE attribute_id attribute_id INT DEFAULT NULL, CHANGE document_id document_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE custom_form_answers CHANGE custom_form_id custom_form_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE custom_form_field_attributes CHANGE custom_form_answer_id custom_form_answer_id INT DEFAULT NULL, CHANGE custom_form_field_id custom_form_field_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE custom_form_fields CHANGE custom_form_id custom_form_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE node_type_fields CHANGE node_type_id node_type_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE nodes CHANGE nodeType_id nodeType_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE url_aliases DROP FOREIGN KEY FK_E261ED65AA2D61'); - $this->addSql('ALTER TABLE url_aliases CHANGE ns_id ns_id INT DEFAULT NULL'); - $this->addSql('ALTER TABLE url_aliases ADD CONSTRAINT FK_E261ED65AA2D61 FOREIGN KEY (ns_id) REFERENCES nodes_sources (id) ON UPDATE NO ACTION ON DELETE NO ACTION'); - } -} diff --git a/migrations/Version20240305143443.php b/migrations/Version20240305143443.php deleted file mode 100644 index f2469a84..00000000 --- a/migrations/Version20240305143443.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('CREATE INDEX node_parent_position ON nodes (parent_node_id, position)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP INDEX node_parent_position ON nodes'); - } -} diff --git a/migrations/Version20240318184555.php b/migrations/Version20240318184555.php deleted file mode 100644 index e03fcf8c..00000000 --- a/migrations/Version20240318184555.php +++ /dev/null @@ -1,34 +0,0 @@ -addSql('ALTER TABLE nodes_custom_forms ADD field_name VARCHAR(250)'); - $this->addSql('ALTER TABLE nodes_sources_documents ADD field_name VARCHAR(250)'); - $this->addSql('ALTER TABLE nodes_to_nodes ADD field_name VARCHAR(250)'); - } - - public function down(Schema $schema): void - { - $this->throwIrreversibleMigrationException('Cannot convert node-type fields name back to their identifiers'); - } -} diff --git a/migrations/Version20240318184556.php b/migrations/Version20240318184556.php deleted file mode 100644 index 69d87323..00000000 --- a/migrations/Version20240318184556.php +++ /dev/null @@ -1,68 +0,0 @@ -connection->beginTransaction(); - $this->connection->executeStatement('UPDATE nodes_custom_forms SET field_name = (SELECT name FROM node_type_fields WHERE id = node_type_field_id)'); - $this->connection->executeStatement('UPDATE nodes_sources_documents SET field_name = (SELECT name FROM node_type_fields WHERE id = node_type_field_id)'); - $this->connection->executeStatement('UPDATE nodes_to_nodes SET field_name = (SELECT name FROM node_type_fields WHERE id = node_type_field_id)'); - $this->connection->commit(); - - $this->addSql('ALTER TABLE nodes_custom_forms DROP FOREIGN KEY FK_4D401A0C47705282'); - $this->addSql('DROP INDEX IDX_4D401A0C47705282 ON nodes_custom_forms'); - $this->addSql('DROP INDEX customform_node_field_position ON nodes_custom_forms'); - $this->addSql('CREATE INDEX customform_node_field_position ON nodes_custom_forms (node_id, field_name, position)'); - $this->addSql('ALTER TABLE nodes_sources_documents DROP FOREIGN KEY FK_1CD104F747705282'); - $this->addSql('DROP INDEX IDX_1CD104F747705282 ON nodes_sources_documents'); - $this->addSql('DROP INDEX nsdoc_field ON nodes_sources_documents'); - $this->addSql('DROP INDEX nsdoc_field_position ON nodes_sources_documents'); - $this->addSql('CREATE INDEX nsdoc_field ON nodes_sources_documents (ns_id, field_name)'); - $this->addSql('CREATE INDEX nsdoc_field_position ON nodes_sources_documents (ns_id, field_name, position)'); - $this->addSql('ALTER TABLE nodes_to_nodes DROP FOREIGN KEY FK_761F9A9147705282'); - $this->addSql('DROP INDEX IDX_761F9A9147705282 ON nodes_to_nodes'); - $this->addSql('DROP INDEX node_a_field ON nodes_to_nodes'); - $this->addSql('DROP INDEX node_a_field_position ON nodes_to_nodes'); - $this->addSql('DROP INDEX node_b_field ON nodes_to_nodes'); - $this->addSql('DROP INDEX node_b_field_position ON nodes_to_nodes'); - $this->addSql('CREATE INDEX node_a_field ON nodes_to_nodes (node_a_id, field_name)'); - $this->addSql('CREATE INDEX node_a_field_position ON nodes_to_nodes (node_a_id, field_name, position)'); - $this->addSql('CREATE INDEX node_b_field ON nodes_to_nodes (node_b_id, field_name)'); - $this->addSql('CREATE INDEX node_b_field_position ON nodes_to_nodes (node_b_id, field_name, position)'); - - /* - * DESTRUCTIVE OPERATIONS - */ - $this->addSql('ALTER TABLE nodes_custom_forms CHANGE field_name field_name VARCHAR(250) NOT NULL'); - $this->addSql('ALTER TABLE nodes_sources_documents CHANGE field_name field_name VARCHAR(250) NOT NULL'); - $this->addSql('ALTER TABLE nodes_to_nodes CHANGE field_name field_name VARCHAR(250) NOT NULL'); - - $this->addSql('ALTER TABLE nodes_custom_forms DROP node_type_field_id'); - $this->addSql('ALTER TABLE nodes_sources_documents DROP node_type_field_id'); - $this->addSql('ALTER TABLE nodes_to_nodes DROP node_type_field_id'); - } - - public function down(Schema $schema): void - { - $this->throwIrreversibleMigrationException('Cannot convert node-type fields name back to their identifiers'); - } -} diff --git a/migrations/Version20240318204224.php b/migrations/Version20240318204224.php deleted file mode 100644 index 0897dc0b..00000000 --- a/migrations/Version20240318204224.php +++ /dev/null @@ -1,44 +0,0 @@ -connection->executeQuery('SELECT max(length(name)) FROM `node_type_fields`'); - $maxLength = $result->fetchOne(); - - $this->skipIf(!is_numeric($maxLength), 'Cannot find node_type_fields name maximum length.'); - $this->skipIf($maxLength >= 50, 'You have at least on node_type_field name that exceed 50 characters long.'); - - // this up() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE node_type_fields CHANGE name name VARCHAR(50) NOT NULL'); - $this->addSql('CREATE INDEX ntf_name ON node_type_fields (name)'); - $this->addSql('ALTER TABLE nodes_custom_forms CHANGE field_name field_name VARCHAR(50) NOT NULL'); - $this->addSql('ALTER TABLE nodes_sources_documents CHANGE field_name field_name VARCHAR(50) NOT NULL'); - $this->addSql('ALTER TABLE nodes_to_nodes CHANGE field_name field_name VARCHAR(50) NOT NULL'); - } - - public function down(Schema $schema): void - { - $this->addSql('DROP INDEX ntf_name ON node_type_fields'); - $this->addSql('ALTER TABLE node_type_fields CHANGE name name VARCHAR(250) NOT NULL'); - $this->addSql('ALTER TABLE nodes_custom_forms CHANGE field_name field_name VARCHAR(250) NOT NULL'); - $this->addSql('ALTER TABLE nodes_sources_documents CHANGE field_name field_name VARCHAR(250) NOT NULL'); - $this->addSql('ALTER TABLE nodes_to_nodes CHANGE field_name field_name VARCHAR(250) NOT NULL'); - } -} diff --git a/phpstan.neon b/phpstan.neon index 882156d4..a032d3d1 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,17 +1,12 @@ parameters: - level: 7 + level: 6 paths: - src excludePaths: - */node_modules/* - */bower_components/* - */static/* - doctrine: - repositoryClass: RZ\Roadiz\CoreBundle\Repository\EntityRepository - objectManagerLoader: ./tests/object-manager.php 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#' @@ -35,7 +30,7 @@ parameters: - '#does not accept Doctrine\\Common\\Collections\\ReadableCollection]+>#' reportUnmatchedIgnoredErrors: false - treatPhpDocTypesAsCertain: false + checkGenericClassInNonGenericObjectType: false + checkMissingIterableValueType: false includes: - vendor/phpstan/phpstan-doctrine/extension.neon - - vendor/phpstan/phpstan-doctrine/rules.neon diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index c0f0120e..00000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - src/Test - - - - - - src - - - - - - - - - - - - diff --git a/src/Api/Breadcrumbs/NodesSourcesBreadcrumbsFactory.php b/src/Api/Breadcrumbs/NodesSourcesBreadcrumbsFactory.php index 6f02c639..07fcf193 100644 --- a/src/Api/Breadcrumbs/NodesSourcesBreadcrumbsFactory.php +++ b/src/Api/Breadcrumbs/NodesSourcesBreadcrumbsFactory.php @@ -20,6 +20,7 @@ public function create(?PersistableInterface $entity): ?BreadcrumbsInterface } if ( + null === $entity->getNode()->getNodeType() || !$entity->isReachable() ) { return null; diff --git a/src/Api/Controller/GetWebResponseByPathController.php b/src/Api/Controller/GetWebResponseByPathController.php index 2e924a4f..8d9905bd 100644 --- a/src/Api/Controller/GetWebResponseByPathController.php +++ b/src/Api/Controller/GetWebResponseByPathController.php @@ -6,94 +6,70 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Metadata\Exception\OperationNotFoundException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use Psr\Log\LoggerInterface; use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; use RZ\Roadiz\CoreBundle\Api\DataTransformer\WebResponseDataTransformerInterface; use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface; -use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\Redirection; -use RZ\Roadiz\CoreBundle\NodeType\ApiResourceOperationNameGenerator; use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; use RZ\Roadiz\CoreBundle\Routing\PathResolverInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\String\UnicodeString; final class GetWebResponseByPathController extends AbstractController { + private RequestStack $requestStack; + private PathResolverInterface $pathResolver; + private WebResponseDataTransformerInterface $webResponseDataTransformer; + private IriConverterInterface $iriConverter; + private PreviewResolverInterface $previewResolver; + public function __construct( - private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, - private readonly PathResolverInterface $pathResolver, - private readonly WebResponseDataTransformerInterface $webResponseDataTransformer, - private readonly IriConverterInterface $iriConverter, - private readonly PreviewResolverInterface $previewResolver, - private readonly ApiResourceOperationNameGenerator $apiResourceOperationNameGenerator, - private readonly LoggerInterface $logger + RequestStack $requestStack, + PathResolverInterface $pathResolver, + WebResponseDataTransformerInterface $webResponseDataTransformer, + IriConverterInterface $iriConverter, + PreviewResolverInterface $previewResolver ) { + $this->requestStack = $requestStack; + $this->pathResolver = $pathResolver; + $this->webResponseDataTransformer = $webResponseDataTransformer; + $this->iriConverter = $iriConverter; + $this->previewResolver = $previewResolver; } - public function __invoke(?Request $request): ?WebResponseInterface + public function __invoke(): ?WebResponseInterface { try { if ( - null === $request || - empty($request->query->get('path')) + null === $this->requestStack->getMainRequest() || + empty($this->requestStack->getMainRequest()->query->get('path')) ) { throw new InvalidArgumentException('path query parameter is mandatory'); } $resource = $this->normalizeResourcePath( - $request, - (string) $request->query->get('path') + (string) $this->requestStack->getMainRequest()->query->get('path') ); - $request->attributes->set('id', $resource->getId()); - $request->attributes->set('path', (string) $request->query->get('path')); - $request->attributes->set('_route_params', [ - ...$request->attributes->get('_route_params', []), - 'path' => (string) $request->query->get('path'), - ]); - - try { - /* - * Force API Platform to look for real resource configuration and serialization - * context. You must define "%entity%_get_by_path" operation for your WebResponse resource configuration. - * It should be generated automatically by Roadiz when you create new reachable NodeTypes. - */ - $resourceClass = get_class($resource); - $operationName = $this->apiResourceOperationNameGenerator->generateGetByPath($resourceClass); - $webResponseClass = $request->attributes->get('_api_resource_class'); - $operation = $this->resourceMetadataCollectionFactory - ->create($webResponseClass) - ->getOperation($operationName); - $request->attributes->set('_api_operation', $operation); - $request->attributes->set('_web_response_item_class', $resourceClass); - $request->attributes->set('_api_operation_name', $operationName); - } catch (OperationNotFoundException $exception) { - // Do not fail if operation is not found - // But warn in logs about missing operation configuration for this resource - $this->logger->warning($exception->getMessage()); - } - - $request->attributes->set('_stateless', true); - - if ($resource instanceof NodesSources) { - $request->attributes->set('_translation', $resource->getTranslation()); - $request->attributes->set('_locale', $resource->getTranslation()->getPreferredLocale()); - } - - $data = $this->webResponseDataTransformer->transform($resource, WebResponseInterface::class); - $request->attributes->set('data', $data); - - return $data; - } catch (ResourceNotFoundException | ResourceClassNotFoundException $exception) { - throw $this->createNotFoundException($exception->getMessage(), $exception); + $this->requestStack->getMainRequest()->attributes->set('data', $resource); + $this->requestStack->getMainRequest()->attributes->set('id', $resource->getId()); + /* + * Force API Platform to look for real resource configuration and serialization + * context. You must define "itemOperations.getByPath" for your API resource configuration. + */ + $this->requestStack->getMainRequest()->attributes->set('_api_resource_class', get_class($resource)); + return $this->webResponseDataTransformer->transform($resource, WebResponseInterface::class); + } catch (ResourceNotFoundException $exception) { + throw new NotFoundHttpException($exception->getMessage(), $exception); } } - protected function normalizeResourcePath(?Request $request, string $path): PersistableInterface + /** + * @param string $path + * @return PersistableInterface + */ + protected function normalizeResourcePath(string $path): PersistableInterface { /* * Serve any PersistableInterface Resource by implementing @@ -128,11 +104,11 @@ protected function normalizeResourcePath(?Request $request, string $path): Persi * Recursive call to normalize path coming from Redirection if redirected path * is internal (starting with /) */ - return $this->normalizeResourcePath($request, $resource->getRedirectUri()); + return $this->normalizeResourcePath($resource->getRedirectUri()); } } - $this->addResourceToCacheTags($request, $resource); + $this->addResourceToCacheTags($resource); /* * Or plain entity @@ -140,8 +116,9 @@ protected function normalizeResourcePath(?Request $request, string $path): Persi return $resource; } - protected function addResourceToCacheTags(?Request $request, PersistableInterface $resource): void + protected function addResourceToCacheTags(PersistableInterface $resource): void { + $request = $this->requestStack->getMainRequest(); if (null !== $request) { $iri = $this->iriConverter->getIriFromResource($resource); $request->attributes->set('_resources', $request->attributes->get('_resources', []) + [ $iri => $iri ]); diff --git a/src/Api/Controller/TranslationAwareControllerTrait.php b/src/Api/Controller/TranslationAwareControllerTrait.php index 47533414..de21fd9e 100644 --- a/src/Api/Controller/TranslationAwareControllerTrait.php +++ b/src/Api/Controller/TranslationAwareControllerTrait.php @@ -23,11 +23,6 @@ abstract protected function getPreviewResolver(): PreviewResolverInterface; protected function getTranslation(Request $request): TranslationInterface { $locale = $request->query->get('_locale'); - $requestTranslation = $request->attributes->get('_translation'); - if ($requestTranslation instanceof TranslationInterface) { - return $requestTranslation; - } - /** @var TranslationRepository $repository */ $repository = $this->getManagerRegistry()->getRepository(TranslationInterface::class); if (!\is_string($locale) || $locale === '') { diff --git a/src/Api/DataTransformer/WebResponseDataTransformerInterface.php b/src/Api/DataTransformer/WebResponseDataTransformerInterface.php index 424d02a3..a6d97c0f 100644 --- a/src/Api/DataTransformer/WebResponseDataTransformerInterface.php +++ b/src/Api/DataTransformer/WebResponseDataTransformerInterface.php @@ -4,19 +4,25 @@ namespace RZ\Roadiz\CoreBundle\Api\DataTransformer; -use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface; interface WebResponseDataTransformerInterface { /** - * @template T of PersistableInterface - * @param T $object + * Transforms the given object to something else, usually another object. + * This must return the original object if no transformations have been done. + * + * @param object $object * @param string $to * @param array $context - * @return WebResponseInterface|null + * @return WebResponseInterface|null */ - public function transform(PersistableInterface $object, string $to, array $context = []): ?WebResponseInterface; + public function transform($object, string $to, array $context = []): ?WebResponseInterface; - public function createWebResponse(): WebResponseInterface; + /** + * Checks whether the transformation is supported for a given data and context. + * + * @param object|array $data object on normalize / array on denormalize + */ + public function supportsTransformation($data, string $to, array $context = []): bool; } diff --git a/src/Api/DataTransformer/WebResponseOutputDataTransformer.php b/src/Api/DataTransformer/WebResponseOutputDataTransformer.php index 7cd9a1e1..a91c4b04 100644 --- a/src/Api/DataTransformer/WebResponseOutputDataTransformer.php +++ b/src/Api/DataTransformer/WebResponseOutputDataTransformer.php @@ -8,9 +8,8 @@ use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; use RZ\Roadiz\CoreBundle\Api\Breadcrumbs\BreadcrumbsFactoryInterface; -use RZ\Roadiz\CoreBundle\Api\Model\BlocksAwareWebResponseInterface; use RZ\Roadiz\CoreBundle\Api\Model\NodesSourcesHeadFactoryInterface; -use RZ\Roadiz\CoreBundle\Api\Model\RealmsAwareWebResponseInterface; +use RZ\Roadiz\CoreBundle\Api\Model\WebResponse; use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface; use RZ\Roadiz\CoreBundle\Api\TreeWalker\AutoChildrenNodeSourceWalker; use RZ\Roadiz\CoreBundle\Api\TreeWalker\TreeWalkerGenerator; @@ -26,26 +25,30 @@ class WebResponseOutputDataTransformer implements WebResponseDataTransformerInte use BlocksAwareWebResponseOutputDataTransformerTrait; use RealmsAwareWebResponseOutputDataTransformerTrait; - /** - * @param NodesSourcesHeadFactoryInterface $nodesSourcesHeadFactory - * @param BreadcrumbsFactoryInterface $breadcrumbsFactory - * @param WalkerContextInterface $walkerContext - * @param CacheItemPoolInterface $cacheItemPool - * @param UrlGeneratorInterface $urlGenerator - * @param RealmResolverInterface $realmResolver - * @param TreeWalkerGenerator $treeWalkerGenerator - * @param class-string $webResponseClass - */ + private NodesSourcesHeadFactoryInterface $nodesSourcesHeadFactory; + private BreadcrumbsFactoryInterface $breadcrumbsFactory; + private WalkerContextInterface $walkerContext; + private CacheItemPoolInterface $cacheItemPool; + private UrlGeneratorInterface $urlGenerator; + private RealmResolverInterface $realmResolver; + private TreeWalkerGenerator $treeWalkerGenerator; + public function __construct( - protected readonly NodesSourcesHeadFactoryInterface $nodesSourcesHeadFactory, - protected readonly BreadcrumbsFactoryInterface $breadcrumbsFactory, - protected readonly WalkerContextInterface $walkerContext, - protected readonly CacheItemPoolInterface $cacheItemPool, - protected readonly UrlGeneratorInterface $urlGenerator, - protected readonly RealmResolverInterface $realmResolver, - protected readonly TreeWalkerGenerator $treeWalkerGenerator, - private readonly string $webResponseClass + NodesSourcesHeadFactoryInterface $nodesSourcesHeadFactory, + BreadcrumbsFactoryInterface $breadcrumbsFactory, + WalkerContextInterface $walkerContext, + CacheItemPoolInterface $cacheItemPool, + UrlGeneratorInterface $urlGenerator, + RealmResolverInterface $realmResolver, + TreeWalkerGenerator $treeWalkerGenerator ) { + $this->nodesSourcesHeadFactory = $nodesSourcesHeadFactory; + $this->breadcrumbsFactory = $breadcrumbsFactory; + $this->walkerContext = $walkerContext; + $this->cacheItemPool = $cacheItemPool; + $this->urlGenerator = $urlGenerator; + $this->realmResolver = $realmResolver; + $this->treeWalkerGenerator = $treeWalkerGenerator; } protected function getWalkerContext(): WalkerContextInterface @@ -81,33 +84,40 @@ protected function getRealmResolver(): RealmResolverInterface return $this->realmResolver; } - public function createWebResponse(): WebResponseInterface - { - return new ($this->webResponseClass)(); - } - - public function transform(PersistableInterface $object, string $to, array $context = []): ?WebResponseInterface + /** + * @inheritDoc + */ + public function transform($object, string $to, array $context = []): ?WebResponseInterface { - $output = $this->createWebResponse(); - $output->setItem($object); + if (!$object instanceof PersistableInterface) { + throw new \InvalidArgumentException( + 'Data to transform must be instance of ' . + PersistableInterface::class + ); + } + $output = new WebResponse(); + $output->item = $object; if ($object instanceof NodesSources) { - if ($output instanceof RealmsAwareWebResponseInterface) { - $this->injectRealms($output, $object); - } - if ($output instanceof BlocksAwareWebResponseInterface) { - $this->injectBlocks($output, $object); - } + $this->injectRealms($output, $object); + $this->injectBlocks($output, $object); - $output->setPath($this->urlGenerator->generate(RouteObjectInterface::OBJECT_BASED_ROUTE_NAME, [ + $output->path = $this->urlGenerator->generate(RouteObjectInterface::OBJECT_BASED_ROUTE_NAME, [ RouteObjectInterface::ROUTE_OBJECT => $object - ], UrlGeneratorInterface::ABSOLUTE_PATH)); - $output->setHead($this->nodesSourcesHeadFactory->createForNodeSource($object)); - $output->setBreadcrumbs($this->breadcrumbsFactory->create($object)); - $output->setMaxAge($object->getNode()->getTtl() * 60); + ], UrlGeneratorInterface::ABSOLUTE_PATH); + $output->head = $this->nodesSourcesHeadFactory->createForNodeSource($object); + $output->breadcrumbs = $this->breadcrumbsFactory->create($object); } if ($object instanceof TranslationInterface) { - $output->setHead($this->nodesSourcesHeadFactory->createForTranslation($object)); + $output->head = $this->nodesSourcesHeadFactory->createForTranslation($object); } return $output; } + + /** + * @inheritDoc + */ + public function supportsTransformation($data, string $to, array $context = []): bool + { + return WebResponseInterface::class === $to && $data instanceof PersistableInterface; + } } diff --git a/src/Api/Extension/AttributeValueRealmExtension.php b/src/Api/Extension/AttributeValueRealmExtension.php index fb8f06c9..a43c17fd 100644 --- a/src/Api/Extension/AttributeValueRealmExtension.php +++ b/src/Api/Extension/AttributeValueRealmExtension.php @@ -12,7 +12,7 @@ use RZ\Roadiz\CoreBundle\Entity\AttributeValue; use RZ\Roadiz\CoreBundle\Model\RealmInterface; use RZ\Roadiz\CoreBundle\Realm\RealmResolverInterface; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; final class AttributeValueRealmExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface { diff --git a/src/Api/ListManager/SolrSearchListManager.php b/src/Api/ListManager/SolrSearchListManager.php index d4c6e75f..a79c575a 100644 --- a/src/Api/ListManager/SolrSearchListManager.php +++ b/src/Api/ListManager/SolrSearchListManager.php @@ -50,6 +50,7 @@ public function handle(bool $disabled = false) $this->criteria, # a simple criteria array to filter search results $this->getItemPerPage(), # result count $this->searchInTags, # Search in tags too, + 1, $this->getPage() ); } else { @@ -58,6 +59,7 @@ public function handle(bool $disabled = false) $this->criteria, # a simple criteria array to filter search results $this->getItemPerPage(), # result count $this->searchInTags, # Search in tags too, + 2, $this->getPage() ); } @@ -83,7 +85,7 @@ public function getItemCount(): int /** * @inheritDoc */ - public function getEntities(): array + public function getEntities() { if (null !== $this->searchResults) { return $this->searchResults->getResultItems(); diff --git a/src/Api/Model/BlocksAwareWebResponseInterface.php b/src/Api/Model/BlocksAwareWebResponseInterface.php index bdf356fe..e94917f7 100644 --- a/src/Api/Model/BlocksAwareWebResponseInterface.php +++ b/src/Api/Model/BlocksAwareWebResponseInterface.php @@ -10,13 +10,13 @@ interface BlocksAwareWebResponseInterface extends WebResponseInterface { /** - * @return WalkerInterface[]|null + * @return Collection|null */ - public function getBlocks(): ?array; + public function getBlocks(): ?Collection; /** * @param Collection|null $blocks - * @return $this + * @return BlocksAwareWebResponseInterface */ - public function setBlocks(?Collection $blocks): self; + public function setBlocks(?Collection $blocks): BlocksAwareWebResponseInterface; } diff --git a/src/Api/Model/NodesSourcesHead.php b/src/Api/Model/NodesSourcesHead.php index c0f52e2b..ef94ded5 100644 --- a/src/Api/Model/NodesSourcesHead.php +++ b/src/Api/Model/NodesSourcesHead.php @@ -113,6 +113,7 @@ public function getMatomoSiteId(): ?string #[Serializer\Groups(["web_response", "nodes_sources_single", "walker"])] public function getSiteName(): ?string { + // site_name return $this->settingsBag->get('site_name', null) ?? null; } @@ -172,6 +173,9 @@ public function isNoIndex(): bool return false; } + /** + * @return string|null + */ #[Serializer\Groups(["web_response", "nodes_sources_single", "walker"])] public function getPolicyUrl(): ?string { @@ -195,60 +199,63 @@ public function getPolicyUrl(): ?string return null; } + /** + * @return string|null + */ #[Serializer\Groups(["web_response", "nodes_sources_single", "walker"])] public function getMainColor(): ?string { return $this->settingsBag->get('main_color', null) ?? null; } + /** + * @return string|null + */ #[Serializer\Groups(["web_response", "nodes_sources_single", "walker"])] public function getFacebookUrl(): ?string { return $this->settingsBag->get('facebook_url', null) ?? null; } + /** + * @return string|null + */ #[Serializer\Groups(["web_response", "nodes_sources_single", "walker"])] public function getInstagramUrl(): ?string { return $this->settingsBag->get('instagram_url', null) ?? null; } + /** + * @return string|null + */ #[Serializer\Groups(["web_response", "nodes_sources_single", "walker"])] public function getTwitterUrl(): ?string { return $this->settingsBag->get('twitter_url', null) ?? null; } + /** + * @return string|null + */ #[Serializer\Groups(["web_response", "nodes_sources_single", "walker"])] public function getYoutubeUrl(): ?string { return $this->settingsBag->get('youtube_url', null) ?? null; } + /** + * @return string|null + */ #[Serializer\Groups(["nodes_sources_single", "walker"])] public function getLinkedinUrl(): ?string { return $this->settingsBag->get('linkedin_url', null) ?? null; } - #[Serializer\Groups(["nodes_sources_single", "walker"])] - public function getSpotifyUrl(): ?string - { - return $this->settingsBag->get('spotify_url', null) ?? null; - } - - #[Serializer\Groups(["nodes_sources_single", "walker"])] - public function getSoundcloudUrl(): ?string - { - return $this->settingsBag->get('soundcloud_url', null) ?? null; - } - - #[Serializer\Groups(["nodes_sources_single", "walker"])] - public function getTikTokUrl(): ?string - { - return $this->settingsBag->get('tiktok_url', null) ?? null; - } - + /** + * @return string|null + */ #[Serializer\Groups(["web_response", "nodes_sources_single", "walker"])] public function getHomePageUrl(): ?string { @@ -261,6 +268,9 @@ public function getHomePageUrl(): ?string return null; } + /** + * @return DocumentInterface|null + */ #[Serializer\Groups(["web_response", "nodes_sources_single"])] public function getShareImage(): ?DocumentInterface { @@ -281,6 +291,9 @@ public function getShareImage(): ?DocumentInterface return $this->settingsBag->getDocument('share_image') ?? null; } + /** + * @return TranslationInterface + */ #[Serializer\Ignore()] public function getTranslation(): TranslationInterface { @@ -290,6 +303,9 @@ public function getTranslation(): TranslationInterface return $this->defaultTranslation; } + /** + * @return NodesSources|null + */ #[Serializer\Ignore()] public function getHomePage(): ?NodesSources { diff --git a/src/Api/Model/NodesSourcesHeadFactory.php b/src/Api/Model/NodesSourcesHeadFactory.php index 9cae9098..d84fc468 100644 --- a/src/Api/Model/NodesSourcesHeadFactory.php +++ b/src/Api/Model/NodesSourcesHeadFactory.php @@ -13,12 +13,27 @@ final class NodesSourcesHeadFactory implements NodesSourcesHeadFactoryInterface { + private Settings $settingsBag; + private UrlGeneratorInterface $urlGenerator; + private NodeSourceApi $nodeSourceApi; + private HandlerFactoryInterface $handlerFactory; + + /** + * @param Settings $settingsBag + * @param UrlGeneratorInterface $urlGenerator + * @param NodeSourceApi $nodeSourceApi + * @param HandlerFactoryInterface $handlerFactory + */ public function __construct( - private readonly Settings $settingsBag, - private readonly UrlGeneratorInterface $urlGenerator, - private readonly NodeSourceApi $nodeSourceApi, - private readonly HandlerFactoryInterface $handlerFactory + Settings $settingsBag, + UrlGeneratorInterface $urlGenerator, + NodeSourceApi $nodeSourceApi, + HandlerFactoryInterface $handlerFactory ) { + $this->settingsBag = $settingsBag; + $this->urlGenerator = $urlGenerator; + $this->nodeSourceApi = $nodeSourceApi; + $this->handlerFactory = $handlerFactory; } public function createForNodeSource(NodesSources $nodesSources): NodesSourcesHeadInterface diff --git a/src/Api/Model/RealmsAwareWebResponseInterface.php b/src/Api/Model/RealmsAwareWebResponseInterface.php index 3962bfe0..92e78e05 100644 --- a/src/Api/Model/RealmsAwareWebResponseInterface.php +++ b/src/Api/Model/RealmsAwareWebResponseInterface.php @@ -15,9 +15,9 @@ public function getRealms(): ?array; /** * @param RealmInterface[]|null $realms - * @return $this + * @return RealmsAwareWebResponseInterface */ - public function setRealms(?array $realms): self; + public function setRealms(?array $realms): RealmsAwareWebResponseInterface; /** * @return bool @@ -26,7 +26,7 @@ public function isHidingBlocks(): bool; /** * @param bool $hidingBlocks - * @return $this + * @return RealmsAwareWebResponseInterface */ - public function setHidingBlocks(bool $hidingBlocks): self; + public function setHidingBlocks(bool $hidingBlocks): RealmsAwareWebResponseInterface; } diff --git a/src/Api/Model/WebResponse.php b/src/Api/Model/WebResponse.php index d8558dce..4d78feab 100644 --- a/src/Api/Model/WebResponse.php +++ b/src/Api/Model/WebResponse.php @@ -4,7 +4,100 @@ namespace RZ\Roadiz\CoreBundle\Api\Model; +use ApiPlatform\Metadata\ApiProperty; +use Doctrine\Common\Collections\Collection; +use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; +use RZ\Roadiz\CoreBundle\Api\Breadcrumbs\BreadcrumbsInterface; +use RZ\Roadiz\CoreBundle\Model\RealmInterface; +use RZ\TreeWalker\WalkerInterface; +use Symfony\Component\Serializer\Annotation as Serializer; + final class WebResponse implements WebResponseInterface, BlocksAwareWebResponseInterface, RealmsAwareWebResponseInterface { - use WebResponseTrait; + #[ApiProperty(identifier: true)] + public ?string $path = null; + + #[Serializer\Groups(["web_response"])] + public ?PersistableInterface $item = null; + + #[Serializer\Groups(["web_response"])] + public ?BreadcrumbsInterface $breadcrumbs = null; + + #[Serializer\Groups(["web_response"])] + public ?NodesSourcesHeadInterface $head = null; + /** + * @var Collection|null + */ + #[Serializer\Groups(["web_response"])] + private ?Collection $blocks = null; + /** + * @var array|null + */ + #[Serializer\Groups(["web_response"])] + private ?array $realms = null; + + #[Serializer\Groups(["web_response"])] + private bool $hidingBlocks = false; + + /** + * @return PersistableInterface|null + */ + public function getItem(): ?PersistableInterface + { + return $this->item; + } + + /** + * @return Collection|null + */ + public function getBlocks(): ?Collection + { + return $this->blocks; + } + + /** + * @param Collection|null $blocks + * @return WebResponse + */ + public function setBlocks(?Collection $blocks): WebResponse + { + $this->blocks = $blocks; + return $this; + } + + /** + * @return RealmInterface[]|null + */ + public function getRealms(): ?array + { + return $this->realms; + } + + /** + * @param RealmInterface[]|null $realms + * @return WebResponse + */ + public function setRealms(?array $realms): WebResponse + { + $this->realms = $realms; + return $this; + } + + /** + * @return bool + */ + public function isHidingBlocks(): bool + { + return $this->hidingBlocks; + } + + /** + * @param bool $hidingBlocks + * @return WebResponse + */ + public function setHidingBlocks(bool $hidingBlocks): WebResponse + { + $this->hidingBlocks = $hidingBlocks; + return $this; + } } diff --git a/src/Api/Model/WebResponseInterface.php b/src/Api/Model/WebResponseInterface.php index 61ff1727..589b7983 100644 --- a/src/Api/Model/WebResponseInterface.php +++ b/src/Api/Model/WebResponseInterface.php @@ -5,26 +5,8 @@ namespace RZ\Roadiz\CoreBundle\Api\Model; use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; -use RZ\Roadiz\CoreBundle\Api\Breadcrumbs\BreadcrumbsInterface; -/** - * @template T of PersistableInterface - */ interface WebResponseInterface { - public function setHead(?NodesSourcesHeadInterface $head): self; - public function setBreadcrumbs(?BreadcrumbsInterface $breadcrumbs): self; - - /** - * @param T|null $item - * @return self - */ - public function setItem(?PersistableInterface $item): self; - public function setPath(?string $path): self; - /** - * @return T|null - */ public function getItem(): ?PersistableInterface; - public function getMaxAge(): ?int; - public function setMaxAge(?int $maxAge): self; } diff --git a/src/Api/Model/WebResponseTrait.php b/src/Api/Model/WebResponseTrait.php deleted file mode 100644 index 8acf455f..00000000 --- a/src/Api/Model/WebResponseTrait.php +++ /dev/null @@ -1,149 +0,0 @@ -|null - */ - #[Serializer\Groups(["web_response"])] - private ?Collection $blocks = null; - /** - * @var array|null - */ - #[Serializer\Groups(["web_response"])] - private ?array $realms = null; - - #[Serializer\Groups(["web_response"])] - private bool $hidingBlocks = false; - - /** - * @var int|null WebResponse item maximum age in seconds - */ - #[Serializer\Groups(["web_response"])] - private ?int $maxAge = null; - - public function getMaxAge(): ?int - { - return $this->maxAge; - } - - public function setMaxAge(?int $maxAge): self - { - $this->maxAge = $maxAge; - return $this; - } - - public function setPath(?string $path): self - { - $this->path = $path; - return $this; - } - - public function setItem(?PersistableInterface $item): self - { - $this->item = $item; - return $this; - } - - /** - * @return PersistableInterface|null - */ - public function getItem(): ?PersistableInterface - { - return $this->item; - } - - /** - * @return WalkerInterface[]|null - */ - public function getBlocks(): ?array - { - return $this->blocks?->getValues(); - } - - /** - * @param Collection|null $blocks - * @return $this - */ - public function setBlocks(?Collection $blocks): self - { - $this->blocks = $blocks; - return $this; - } - - /** - * @return RealmInterface[]|null - */ - public function getRealms(): ?array - { - return $this->realms; - } - - /** - * @param RealmInterface[]|null $realms - * @return $this - */ - public function setRealms(?array $realms): self - { - $this->realms = $realms; - return $this; - } - - /** - * @return bool - */ - public function isHidingBlocks(): bool - { - return $this->hidingBlocks; - } - - /** - * @param bool $hidingBlocks - * @return $this - */ - public function setHidingBlocks(bool $hidingBlocks): self - { - $this->hidingBlocks = $hidingBlocks; - return $this; - } - - public function setBreadcrumbs(?BreadcrumbsInterface $breadcrumbs): self - { - $this->breadcrumbs = $breadcrumbs; - return $this; - } - - public function setHead(?NodesSourcesHeadInterface $head): self - { - $this->head = $head; - return $this; - } -} diff --git a/src/Api/OpenApi/WebResponseDecorator.php b/src/Api/OpenApi/WebResponseDecorator.php index 21131a1e..da54e849 100644 --- a/src/Api/OpenApi/WebResponseDecorator.php +++ b/src/Api/OpenApi/WebResponseDecorator.php @@ -22,10 +22,6 @@ public function __invoke(array $context = []): OpenApi { $openApi = ($this->decorated)($context); $pathItem = $openApi->getPaths()->getPath('/api/web_response_by_path'); - if (null === $pathItem) { - return $openApi; - } - $operation = $pathItem->getGet(); $openApi->getPaths()->addPath('/api/web_response_by_path', $pathItem->withGet( diff --git a/src/Bag/NodeTypes.php b/src/Bag/NodeTypes.php index fbaf9dd5..44147c4a 100644 --- a/src/Bag/NodeTypes.php +++ b/src/Bag/NodeTypes.php @@ -10,15 +10,23 @@ use RZ\Roadiz\CoreBundle\Entity\NodeType; use RZ\Roadiz\CoreBundle\Repository\NodeTypeRepository; -final class NodeTypes extends LazyParameterBag implements NodeTypeResolverInterface +class NodeTypes extends LazyParameterBag implements NodeTypeResolverInterface { private ?NodeTypeRepository $repository = null; + private ManagerRegistry $managerRegistry; - public function __construct(private readonly ManagerRegistry $managerRegistry) + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) { parent::__construct(); + $this->managerRegistry = $managerRegistry; } + /** + * @return NodeTypeRepository + */ public function getRepository(): NodeTypeRepository { if (null === $this->repository) { diff --git a/src/Bag/Roles.php b/src/Bag/Roles.php index e98d7e14..4d13624c 100644 --- a/src/Bag/Roles.php +++ b/src/Bag/Roles.php @@ -9,15 +9,23 @@ use RZ\Roadiz\CoreBundle\Entity\Role; use RZ\Roadiz\CoreBundle\Repository\RoleRepository; -final class Roles extends LazyParameterBag +class Roles extends LazyParameterBag { + private ManagerRegistry $managerRegistry; private ?RoleRepository $repository = null; - public function __construct(private readonly ManagerRegistry $managerRegistry) + /** + * @param ManagerRegistry $managerRegistry; + */ + public function __construct(ManagerRegistry $managerRegistry) { parent::__construct(); + $this->managerRegistry = $managerRegistry; } + /** + * @return RoleRepository + */ public function getRepository(): RoleRepository { if (null === $this->repository) { @@ -49,15 +57,14 @@ protected function populateParameters(): void * * @return Role */ - public function get(string $key, $default = null): Role + public function get($key, $default = null): Role { $role = parent::get($key, $default); if (null === $role) { $role = new Role($key); - $roleManager = $this->managerRegistry->getManagerForClass(Role::class); - $roleManager->persist($role); - $roleManager->flush(); + $this->managerRegistry->getManagerForClass(Role::class)->persist($role); + $this->managerRegistry->getManagerForClass(Role::class)->flush(); } return $role; diff --git a/src/Bag/Settings.php b/src/Bag/Settings.php index 4760721b..4586ae26 100644 --- a/src/Bag/Settings.php +++ b/src/Bag/Settings.php @@ -13,15 +13,20 @@ class Settings extends LazyParameterBag { + private ManagerRegistry $managerRegistry; private ?SettingRepository $repository = null; + private Stopwatch $stopwatch; - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly Stopwatch $stopwatch - ) { + public function __construct(ManagerRegistry $managerRegistry, Stopwatch $stopwatch) + { parent::__construct(); + $this->managerRegistry = $managerRegistry; + $this->stopwatch = $stopwatch; } + /** + * @return SettingRepository + */ public function getRepository(): SettingRepository { if (null === $this->repository) { @@ -50,9 +55,9 @@ protected function populateParameters(): void /** * @param string $key * @param mixed $default - * @return mixed + * @return bool|mixed */ - public function get(string $key, $default = false): mixed + public function get($key, $default = false) { return parent::get($key, $default); } @@ -63,7 +68,7 @@ public function get(string $key, $default = false): mixed * @param string $key * @return Document|null */ - public function getDocument(string $key): ?Document + public function getDocument($key): ?Document { try { $id = $this->getInt($key); diff --git a/src/Cache/Clearer/FileClearer.php b/src/Cache/Clearer/FileClearer.php index 955e348f..0cb90568 100644 --- a/src/Cache/Clearer/FileClearer.php +++ b/src/Cache/Clearer/FileClearer.php @@ -7,9 +7,11 @@ abstract class FileClearer implements ClearerInterface { protected ?string $output = null; + protected string $cacheDir; - public function __construct(protected readonly string $cacheDir) + public function __construct(string $cacheDir) { + $this->cacheDir = $cacheDir; } public function clear(): bool diff --git a/src/Cache/Clearer/NodesSourcesUrlsCacheClearer.php b/src/Cache/Clearer/NodesSourcesUrlsCacheClearer.php index a7018fe0..f59f919b 100644 --- a/src/Cache/Clearer/NodesSourcesUrlsCacheClearer.php +++ b/src/Cache/Clearer/NodesSourcesUrlsCacheClearer.php @@ -8,9 +8,12 @@ final class NodesSourcesUrlsCacheClearer extends FileClearer { - public function __construct(private readonly CacheItemPoolInterface $cacheProvider) + private CacheItemPoolInterface $cacheProvider; + + public function __construct(CacheItemPoolInterface $cacheProvider) { parent::__construct(''); + $this->cacheProvider = $cacheProvider; } public function clear(): bool diff --git a/src/Cache/CloudflareProxyCache.php b/src/Cache/CloudflareProxyCache.php index 93485e54..15e63174 100644 --- a/src/Cache/CloudflareProxyCache.php +++ b/src/Cache/CloudflareProxyCache.php @@ -6,15 +6,32 @@ final class CloudflareProxyCache { - public function __construct( - private readonly string $name, - private readonly string $zone, - private readonly string $version, - private readonly string $bearer, - private readonly string $email, - private readonly string $key, - private readonly int $timeout - ) { + protected string $name; + protected string $zone; + protected string $version; + protected string $bearer; + protected string $email; + protected string $key; + protected int $timeout; + + /** + * @param string $name + * @param string $zone + * @param string $version + * @param string $bearer + * @param string $email + * @param string $key + * @param int $timeout + */ + public function __construct(string $name, string $zone, string $version, string $bearer, string $email, string $key, int $timeout) + { + $this->name = $name; + $this->zone = $zone; + $this->version = $version; + $this->bearer = $bearer; + $this->email = $email; + $this->key = $key; + $this->timeout = $timeout; } /** diff --git a/src/Cache/ReverseProxyCache.php b/src/Cache/ReverseProxyCache.php index 8aa36611..80900a63 100644 --- a/src/Cache/ReverseProxyCache.php +++ b/src/Cache/ReverseProxyCache.php @@ -6,12 +6,23 @@ final class ReverseProxyCache { - public function __construct( - private readonly string $name, - private readonly string $host, - private readonly string $domainName, - private readonly int $timeout - ) { + protected string $name; + protected string $host; + protected string $domainName; + protected int $timeout; + + /** + * @param string $name + * @param string $host + * @param string $domainName + * @param int $timeout + */ + public function __construct(string $name, string $host, string $domainName, int $timeout) + { + $this->name = $name; + $this->host = $host; + $this->domainName = $domainName; + $this->timeout = $timeout; } /** @@ -37,9 +48,4 @@ public function getDomainName(): string { return $this->domainName; } - - public function getTimeout(): int - { - return $this->timeout; - } } diff --git a/src/Cache/ReverseProxyCacheLocator.php b/src/Cache/ReverseProxyCacheLocator.php index b3f4b963..b6a4a3f6 100644 --- a/src/Cache/ReverseProxyCacheLocator.php +++ b/src/Cache/ReverseProxyCacheLocator.php @@ -6,14 +6,20 @@ final class ReverseProxyCacheLocator { + /** + * @var ReverseProxyCache[] + */ + private array $frontends; + private ?CloudflareProxyCache $cloudflareProxyCache; + /** * @param ReverseProxyCache[] $frontends * @param CloudflareProxyCache|null $cloudflareProxyCache */ - public function __construct( - private readonly array $frontends, - private readonly ?CloudflareProxyCache $cloudflareProxyCache = null - ) { + public function __construct(array $frontends, ?CloudflareProxyCache $cloudflareProxyCache = null) + { + $this->frontends = $frontends; + $this->cloudflareProxyCache = $cloudflareProxyCache; } /** diff --git a/src/Console/AppInstallCommand.php b/src/Console/AppInstallCommand.php index e1023ba7..11354079 100644 --- a/src/Console/AppInstallCommand.php +++ b/src/Console/AppInstallCommand.php @@ -5,6 +5,7 @@ namespace RZ\Roadiz\CoreBundle\Console; use Doctrine\Persistence\ManagerRegistry; +use RZ\Roadiz\CompatBundle\Theme\ThemeGenerator; use RZ\Roadiz\CoreBundle\Exception\EntityAlreadyExistsException; use RZ\Roadiz\CoreBundle\Importer\AttributeImporter; use RZ\Roadiz\CoreBundle\Importer\EntityImporterInterface; @@ -21,23 +22,45 @@ use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\Yaml\Yaml; +/** + * Command line utils for managing themes from terminal. + */ final class AppInstallCommand extends Command { - private SymfonyStyle $io; + protected SymfonyStyle $io; private bool $dryRun = false; + protected string $projectDir; + protected ThemeGenerator $themeGenerator; + protected NodeTypesImporter $nodeTypesImporter; + protected TagsImporter $tagsImporter; + protected SettingsImporter $settingsImporter; + protected RolesImporter $rolesImporter; + protected GroupsImporter $groupsImporter; + protected AttributeImporter $attributeImporter; + protected ManagerRegistry $managerRegistry; public function __construct( - private readonly string $projectDir, - private readonly ManagerRegistry $managerRegistry, - private readonly NodeTypesImporter $nodeTypesImporter, - private readonly TagsImporter $tagsImporter, - private readonly SettingsImporter $settingsImporter, - private readonly RolesImporter $rolesImporter, - private readonly GroupsImporter $groupsImporter, - private readonly AttributeImporter $attributeImporter, - ?string $name = null + string $projectDir, + ManagerRegistry $managerRegistry, + ThemeGenerator $themeGenerator, + NodeTypesImporter $nodeTypesImporter, + TagsImporter $tagsImporter, + SettingsImporter $settingsImporter, + RolesImporter $rolesImporter, + GroupsImporter $groupsImporter, + AttributeImporter $attributeImporter, + string $name = null ) { parent::__construct($name); + $this->projectDir = $projectDir; + $this->themeGenerator = $themeGenerator; + $this->nodeTypesImporter = $nodeTypesImporter; + $this->tagsImporter = $tagsImporter; + $this->settingsImporter = $settingsImporter; + $this->rolesImporter = $rolesImporter; + $this->groupsImporter = $groupsImporter; + $this->attributeImporter = $attributeImporter; + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/AppMigrateCommand.php b/src/Console/AppMigrateCommand.php index e8949486..9aef6a7a 100644 --- a/src/Console/AppMigrateCommand.php +++ b/src/Console/AppMigrateCommand.php @@ -6,6 +6,7 @@ use RZ\Roadiz\CoreBundle\Doctrine\SchemaUpdater; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -15,12 +16,14 @@ final class AppMigrateCommand extends Command { - public function __construct( - private readonly SchemaUpdater $schemaUpdater, - private readonly string $projectDir, - ?string $name = null - ) { + protected string $projectDir; + private SchemaUpdater $schemaUpdater; + + public function __construct(SchemaUpdater $schemaUpdater, string $projectDir, ?string $name = null) + { parent::__construct($name); + $this->projectDir = $projectDir; + $this->schemaUpdater = $schemaUpdater; } protected function configure(): void diff --git a/src/Console/CustomFormAnswerPurgeCommand.php b/src/Console/CustomFormAnswerPurgeCommand.php index 07270a01..5363c503 100644 --- a/src/Console/CustomFormAnswerPurgeCommand.php +++ b/src/Console/CustomFormAnswerPurgeCommand.php @@ -19,13 +19,20 @@ final class CustomFormAnswerPurgeCommand extends Command { + private ManagerRegistry $managerRegistry; + private EventDispatcherInterface $eventDispatcher; + private LoggerInterface $logger; + public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly EventDispatcherInterface $eventDispatcher, - private readonly LoggerInterface $logger, + ManagerRegistry $managerRegistry, + EventDispatcherInterface $eventDispatcher, + LoggerInterface $logger, string $name = null ) { parent::__construct($name); + $this->managerRegistry = $managerRegistry; + $this->eventDispatcher = $eventDispatcher; + $this->logger = $logger; } protected function configure(): void diff --git a/src/Console/DecodePrivateKeyCommand.php b/src/Console/DecodePrivateKeyCommand.php new file mode 100644 index 00000000..e213f591 --- /dev/null +++ b/src/Console/DecodePrivateKeyCommand.php @@ -0,0 +1,49 @@ +keyChain = $keyChain; + $this->uniqueKeyEncoderFactory = $uniqueKeyEncoderFactory; + } + + protected function configure(): void + { + $this->setName('crypto:private-key:decode') + ->addArgument('key-name', InputArgument::REQUIRED) + ->addArgument('data', InputArgument::REQUIRED) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $keyName = $input->getArgument('key-name'); + $encoder = $this->uniqueKeyEncoderFactory->getEncoder($keyName); + $encoded = $encoder->decode($input->getArgument('data')); + + $io->note($encoded->getString()); + return 0; + } +} diff --git a/src/Console/EncodePrivateKeyCommand.php b/src/Console/EncodePrivateKeyCommand.php new file mode 100644 index 00000000..763b2ffa --- /dev/null +++ b/src/Console/EncodePrivateKeyCommand.php @@ -0,0 +1,50 @@ +keyChain = $keyChain; + $this->uniqueKeyEncoderFactory = $uniqueKeyEncoderFactory; + } + + protected function configure(): void + { + $this->setName('crypto:private-key:encode') + ->addArgument('key-name', InputArgument::REQUIRED) + ->addArgument('data', InputArgument::REQUIRED) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $keyName = $input->getArgument('key-name'); + $encoder = $this->uniqueKeyEncoderFactory->getEncoder($keyName); + $encoded = $encoder->encode(new HiddenString($input->getArgument('data'))); + + $io->note($encoded); + return 0; + } +} diff --git a/src/Console/FilesCommandTrait.php b/src/Console/FilesCommandTrait.php index 15d73514..e215fe70 100644 --- a/src/Console/FilesCommandTrait.php +++ b/src/Console/FilesCommandTrait.php @@ -9,7 +9,7 @@ trait FilesCommandTrait /** * @return string */ - protected function getPublicFolderName(): string + protected function getPublicFolderName() { return '/exported_public'; } @@ -17,7 +17,7 @@ protected function getPublicFolderName(): string /** * @return string */ - protected function getPrivateFolderName(): string + protected function getPrivateFolderName() { return '/exported_private'; } @@ -25,7 +25,7 @@ protected function getPrivateFolderName(): string /** * @return string */ - protected function getFontsFolderName(): string + protected function getFontsFolderName() { return '/exported_fonts'; } diff --git a/src/Console/FilesExportCommand.php b/src/Console/FilesExportCommand.php index adf93e2d..29fac777 100644 --- a/src/Console/FilesExportCommand.php +++ b/src/Console/FilesExportCommand.php @@ -14,17 +14,20 @@ use Symfony\Component\String\Slugger\AsciiSlugger; use ZipArchive; -final class FilesExportCommand extends Command +class FilesExportCommand extends Command { use FilesCommandTrait; - public function __construct( - private readonly FileAwareInterface $fileAware, - private readonly string $exportDir, - private readonly string $appNamespace, - ?string $name = null - ) { - parent::__construct($name); + protected FileAwareInterface $fileAware; + protected string $exportDir; + protected string $appNamespace; + + public function __construct(FileAwareInterface $fileAware, string $exportDir, string $appNamespace) + { + parent::__construct(); + $this->fileAware = $fileAware; + $this->exportDir = $exportDir; + $this->appNamespace = $appNamespace; } protected function configure(): void diff --git a/src/Console/FilesImportCommand.php b/src/Console/FilesImportCommand.php index 4b910575..55c0b07f 100644 --- a/src/Console/FilesImportCommand.php +++ b/src/Console/FilesImportCommand.php @@ -15,16 +15,20 @@ use Symfony\Component\String\Slugger\AsciiSlugger; use ZipArchive; -final class FilesImportCommand extends Command +class FilesImportCommand extends Command { use FilesCommandTrait; - public function __construct( - private readonly FileAwareInterface $fileAware, - private readonly string $appNamespace, - ?string $name = null - ) { - parent::__construct($name); + protected FileAwareInterface $fileAware; + protected string $exportDir; + protected string $appNamespace; + + public function __construct(FileAwareInterface $fileAware, string $exportDir, string $appNamespace) + { + parent::__construct(); + $this->fileAware = $fileAware; + $this->exportDir = $exportDir; + $this->appNamespace = $appNamespace; } protected function configure(): void diff --git a/src/Console/GenerateApiResourceCommand.php b/src/Console/GenerateApiResourceCommand.php index 87ae30e3..7bfda603 100644 --- a/src/Console/GenerateApiResourceCommand.php +++ b/src/Console/GenerateApiResourceCommand.php @@ -12,14 +12,18 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -final class GenerateApiResourceCommand extends Command +class GenerateApiResourceCommand extends Command { + protected ManagerRegistry $managerRegistry; + protected ApiResourceGenerator $apiResourceGenerator; + public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly ApiResourceGenerator $apiResourceGenerator, - ?string $name = null + ManagerRegistry $managerRegistry, + ApiResourceGenerator $apiResourceGenerator ) { - parent::__construct($name); + parent::__construct(); + $this->managerRegistry = $managerRegistry; + $this->apiResourceGenerator = $apiResourceGenerator; } protected function configure(): void diff --git a/src/Console/GenerateNodeSourceEntitiesCommand.php b/src/Console/GenerateNodeSourceEntitiesCommand.php index 04c3b8dc..1a630456 100644 --- a/src/Console/GenerateNodeSourceEntitiesCommand.php +++ b/src/Console/GenerateNodeSourceEntitiesCommand.php @@ -5,8 +5,6 @@ namespace RZ\Roadiz\CoreBundle\Console; use Doctrine\Persistence\ManagerRegistry; -use Psr\Container\ContainerExceptionInterface; -use Psr\Container\NotFoundExceptionInterface; use RZ\Roadiz\CoreBundle\Entity\NodeType; use RZ\Roadiz\CoreBundle\EntityHandler\HandlerFactory; use RZ\Roadiz\CoreBundle\EntityHandler\NodeTypeHandler; @@ -15,14 +13,23 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -final class GenerateNodeSourceEntitiesCommand extends Command +/** + * Command line utils for managing node-types from terminal. + */ +class GenerateNodeSourceEntitiesCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly HandlerFactory $handlerFactory, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + protected HandlerFactory $handlerFactory; + + /** + * @param ManagerRegistry $managerRegistry + * @param HandlerFactory $handlerFactory + */ + public function __construct(ManagerRegistry $managerRegistry, HandlerFactory $handlerFactory) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; + $this->handlerFactory = $handlerFactory; } protected function configure(): void @@ -31,10 +38,6 @@ protected function configure(): void ->setDescription('Generate node-sources entities PHP classes.'); } - /** - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); diff --git a/src/Console/GeneratePrivateKeyCommand.php b/src/Console/GeneratePrivateKeyCommand.php new file mode 100644 index 00000000..3f46c280 --- /dev/null +++ b/src/Console/GeneratePrivateKeyCommand.php @@ -0,0 +1,47 @@ +keyChain = $keyChain; + $this->privateKeyName = $privateKeyName; + } + + protected function configure(): void + { + $this->setName('crypto:private-key:generate') + ->setDescription('Generate a default private key to encode data in your database.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $this->keyChain->generate($this->privateKeyName); + $io->success(sprintf('Private key has been generated: %s', $this->privateKeyName)); + return 0; + } +} diff --git a/src/Console/GetCronLastExecDateCommand.php b/src/Console/GetCronLastExecDateCommand.php deleted file mode 100644 index e090c16d..00000000 --- a/src/Console/GetCronLastExecDateCommand.php +++ /dev/null @@ -1,43 +0,0 @@ -settingRepository->findOneByName('cron_last_exec_date'); - if (!($setting instanceof Setting)) { - $io->warning('Last execution date of cron job has not been persisted yet.'); - return Command::FAILURE; - } - - $io->success(sprintf( - 'Last execution date of cron job is %s.', - $setting->getRawValue() - )); - - return Command::SUCCESS; - } -} diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 913de1ca..79291b84 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -18,16 +18,33 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Yaml\Yaml; -final class InstallCommand extends Command +/** + * Command line utils for installing RZ-CMS v3 from terminal. + */ +class InstallCommand extends Command { + protected ManagerRegistry $managerRegistry; + protected RolesImporter $rolesImporter; + protected GroupsImporter $groupsImporter; + protected SettingsImporter $settingsImporter; + + /** + * @param ManagerRegistry $managerRegistry + * @param RolesImporter $rolesImporter + * @param GroupsImporter $groupsImporter + * @param SettingsImporter $settingsImporter + */ public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly RolesImporter $rolesImporter, - private readonly GroupsImporter $groupsImporter, - private readonly SettingsImporter $settingsImporter, - ?string $name = null + ManagerRegistry $managerRegistry, + RolesImporter $rolesImporter, + GroupsImporter $groupsImporter, + SettingsImporter $settingsImporter ) { - parent::__construct($name); + parent::__construct(); + $this->managerRegistry = $managerRegistry; + $this->rolesImporter = $rolesImporter; + $this->groupsImporter = $groupsImporter; + $this->settingsImporter = $settingsImporter; } protected function configure(): void diff --git a/src/Console/LogsCleanupCommand.php b/src/Console/LogsCleanupCommand.php index 56fd662a..d9b94f3c 100644 --- a/src/Console/LogsCleanupCommand.php +++ b/src/Console/LogsCleanupCommand.php @@ -15,13 +15,17 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -final class LogsCleanupCommand extends Command +class LogsCleanupCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/MailerTestCommand.php b/src/Console/MailerTestCommand.php index 83a66962..f7c43949 100644 --- a/src/Console/MailerTestCommand.php +++ b/src/Console/MailerTestCommand.php @@ -13,15 +13,20 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Mime\Address; -final class MailerTestCommand extends Command +class MailerTestCommand extends Command { - public function __construct( - private readonly EmailManager $emailManager, - ?string $name = null - ) { - parent::__construct($name); + protected EmailManager $emailManager; + + /** + * @param EmailManager $emailManager + */ + public function __construct(EmailManager $emailManager) + { + parent::__construct(); + $this->emailManager = $emailManager; } + protected function configure(): void { $this->setName('mailer:send:test') diff --git a/src/Console/NodeApplyUniversalFieldsCommand.php b/src/Console/NodeApplyUniversalFieldsCommand.php index 942e2541..a136ac45 100644 --- a/src/Console/NodeApplyUniversalFieldsCommand.php +++ b/src/Console/NodeApplyUniversalFieldsCommand.php @@ -15,14 +15,20 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; -final class NodeApplyUniversalFieldsCommand extends Command +class NodeApplyUniversalFieldsCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly UniversalDataDuplicator $universalDataDuplicator, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + protected UniversalDataDuplicator $universalDataDuplicator; + + /** + * @param ManagerRegistry $managerRegistry + * @param UniversalDataDuplicator $universalDataDuplicator + */ + public function __construct(ManagerRegistry $managerRegistry, UniversalDataDuplicator $universalDataDuplicator) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; + $this->universalDataDuplicator = $universalDataDuplicator; } protected function configure(): void diff --git a/src/Console/NodeClearTagCommand.php b/src/Console/NodeClearTagCommand.php index e7e775f7..7ba65879 100644 --- a/src/Console/NodeClearTagCommand.php +++ b/src/Console/NodeClearTagCommand.php @@ -15,13 +15,18 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; -final class NodeClearTagCommand extends Command +class NodeClearTagCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($name); + protected SymfonyStyle $io; + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; } protected function configure(): void @@ -43,7 +48,7 @@ protected function getNodeQueryBuilder(Tag $tag): QueryBuilder protected function execute(InputInterface $input, OutputInterface $output): int { $em = $this->managerRegistry->getManagerForClass(Node::class); - $io = new SymfonyStyle($input, $output); + $this->io = new SymfonyStyle($input, $output); $tagId = (int) $input->getArgument('tagId'); if ($tagId <= 0) { @@ -64,12 +69,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int ->getSingleScalarResult(); if ($count <= 0) { - $io->warning('No nodes were found linked with this tag.'); + $this->io->warning('No nodes were found linked with this tag.'); return 0; } if ( - $io->askQuestion(new ConfirmationQuestion( + $this->io->askQuestion(new ConfirmationQuestion( sprintf('Are you sure to delete permanently %d nodes?', $count), false )) @@ -79,7 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ->getQuery() ->getResult(); - $io->progressStart($count); + $this->io->progressStart($count); /** @var Node $node */ foreach ($results as $node) { $em->remove($node); @@ -87,10 +92,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $em->flush(); // Executes all updates. } ++$i; - $io->progressAdvance(); + $this->io->progressAdvance(); } $em->flush(); - $io->progressFinish(); + $this->io->progressFinish(); } return 0; diff --git a/src/Console/NodeTypesCommand.php b/src/Console/NodeTypesCommand.php index 262bcaa6..23107664 100644 --- a/src/Console/NodeTypesCommand.php +++ b/src/Console/NodeTypesCommand.php @@ -13,13 +13,20 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -final class NodeTypesCommand extends Command +/** + * Command line utils for managing node-types from terminal. + */ +class NodeTypesCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/NodeTypesCreationCommand.php b/src/Console/NodeTypesCreationCommand.php index 25788fec..812e9d0d 100644 --- a/src/Console/NodeTypesCreationCommand.php +++ b/src/Console/NodeTypesCreationCommand.php @@ -23,13 +23,21 @@ */ class NodeTypesCreationCommand extends Command { - public function __construct( - protected readonly ManagerRegistry $managerRegistry, - protected readonly HandlerFactory $handlerFactory, - protected readonly SchemaUpdater $schemaUpdater, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + protected HandlerFactory $handlerFactory; + protected SchemaUpdater $schemaUpdater; + + /** + * @param ManagerRegistry $managerRegistry + * @param HandlerFactory $handlerFactory + * @param SchemaUpdater $schemaUpdater + */ + public function __construct(ManagerRegistry $managerRegistry, HandlerFactory $handlerFactory, SchemaUpdater $schemaUpdater) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; + $this->handlerFactory = $handlerFactory; + $this->schemaUpdater = $schemaUpdater; } protected function configure(): void diff --git a/src/Console/NodeTypesDefaultValuesCommand.php b/src/Console/NodeTypesDefaultValuesCommand.php index 2c4c3ec7..bf59585e 100644 --- a/src/Console/NodeTypesDefaultValuesCommand.php +++ b/src/Console/NodeTypesDefaultValuesCommand.php @@ -5,6 +5,7 @@ namespace RZ\Roadiz\CoreBundle\Console; use Doctrine\Persistence\ManagerRegistry; +use RZ\Roadiz\Core\AbstractEntities\AbstractEntity; use RZ\Roadiz\Core\AbstractEntities\AbstractField; use RZ\Roadiz\CoreBundle\Entity\NodeTypeField; use RZ\Roadiz\EntityGenerator\Field\DefaultValuesResolverInterface; @@ -14,14 +15,16 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -final class NodeTypesDefaultValuesCommand extends Command +class NodeTypesDefaultValuesCommand extends Command { - public function __construct( - private readonly DefaultValuesResolverInterface $defaultValuesResolver, - private readonly ManagerRegistry $managerRegistry, - string $name = null - ) { + private DefaultValuesResolverInterface $defaultValuesResolver; + private ManagerRegistry $managerRegistry; + + public function __construct(DefaultValuesResolverInterface $defaultValuesResolver, ManagerRegistry $managerRegistry, string $name = null) + { parent::__construct($name); + $this->defaultValuesResolver = $defaultValuesResolver; + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/NodeTypesDeleteCommand.php b/src/Console/NodeTypesDeleteCommand.php index 6e9d47c0..e4bfb545 100644 --- a/src/Console/NodeTypesDeleteCommand.php +++ b/src/Console/NodeTypesDeleteCommand.php @@ -19,15 +19,23 @@ /** * Command line utils for managing node-types from terminal. */ -final class NodeTypesDeleteCommand extends Command +class NodeTypesDeleteCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly HandlerFactory $handlerFactory, - private readonly SchemaUpdater $schemaUpdater, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + protected HandlerFactory $handlerFactory; + protected SchemaUpdater $schemaUpdater; + + /** + * @param ManagerRegistry $managerRegistry + * @param HandlerFactory $handlerFactory + * @param SchemaUpdater $schemaUpdater + */ + public function __construct(ManagerRegistry $managerRegistry, HandlerFactory $handlerFactory, SchemaUpdater $schemaUpdater) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; + $this->handlerFactory = $handlerFactory; + $this->schemaUpdater = $schemaUpdater; } protected function configure(): void diff --git a/src/Console/NodesCleanNamesCommand.php b/src/Console/NodesCleanNamesCommand.php index 394a55de..bee548c7 100644 --- a/src/Console/NodesCleanNamesCommand.php +++ b/src/Console/NodesCleanNamesCommand.php @@ -15,14 +15,21 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * @package RZ\Roadiz\CoreBundle\Console + */ final class NodesCleanNamesCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly NodeNamePolicyInterface $nodeNamePolicy, - ?string $name = null - ) { - parent::__construct($name); + protected NodeNamePolicyInterface $nodeNamePolicy; + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/NodesCommand.php b/src/Console/NodesCommand.php index cd5e56a0..3442ea0c 100644 --- a/src/Console/NodesCommand.php +++ b/src/Console/NodesCommand.php @@ -13,13 +13,20 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -final class NodesCommand extends Command +/** + * Command line utils for managing nodes from terminal. + */ +class NodesCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/NodesCreationCommand.php b/src/Console/NodesCreationCommand.php new file mode 100644 index 00000000..98e3ce6b --- /dev/null +++ b/src/Console/NodesCreationCommand.php @@ -0,0 +1,133 @@ +managerRegistry = $managerRegistry; + $this->nodeFactory = $nodeFactory; + } + + protected function configure(): void + { + $this->setName('nodes:create') + ->setDescription('Create a new node') + ->addArgument( + 'node-name', + InputArgument::REQUIRED, + 'Node name' + ) + ->addArgument( + 'node-type', + InputArgument::REQUIRED, + 'Node-type name' + ) + ->addArgument( + 'locale', + InputArgument::OPTIONAL, + 'Translation locale' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $nodeName = $input->getArgument('node-name'); + $typeName = $input->getArgument('node-type'); + $locale = $input->getArgument('locale'); + $this->io = new SymfonyStyle($input, $output); + + $existingNode = $this->managerRegistry + ->getRepository(Node::class) + ->setDisplayingNotPublishedNodes(true) + ->findOneByNodeName($nodeName); + + if (null === $existingNode) { + $type = $this->managerRegistry + ->getRepository(NodeType::class) + ->findOneByName($typeName); + + if (null !== $type) { + $translation = null; + + if ($locale) { + $translation = $this->managerRegistry + ->getRepository(TranslationInterface::class) + ->findOneBy(['locale' => $locale]); + } + + if ($translation === null) { + $translation = $this->managerRegistry + ->getRepository(TranslationInterface::class) + ->findDefault(); + } + + $this->executeNodeCreation($input->getArgument('node-name'), $type, $translation); + } else { + $this->io->error('"' . $typeName . '" node type does not exist.'); + return 1; + } + return 0; + } else { + $this->io->error($existingNode->getNodeName() . ' node already exists.'); + return 1; + } + } + + /** + * @param string $nodeName + * @param NodeType $type + * @param TranslationInterface $translation + */ + private function executeNodeCreation( + string $nodeName, + NodeType $type, + TranslationInterface $translation + ): void { + $node = $this->nodeFactory->create($nodeName, $type, $translation); + $source = $node->getNodeSources()->first() ?: null; + if (null === $source) { + throw new \InvalidArgumentException('Node source is null'); + } + $fields = $type->getFields(); + + foreach ($fields as $field) { + if (!$field->isVirtual()) { + $question = new Question('[Field ' . $field->getLabel() . '] : ', null); + $fValue = $this->io->askQuestion($question); + $setterName = $field->getSetterName(); + $source->$setterName($fValue); + } + } + + $this->managerRegistry->getManagerForClass(Node::class)->flush(); + $this->io->success('Node “' . $nodeName . '” created at root level.'); + } +} diff --git a/src/Console/NodesDetailsCommand.php b/src/Console/NodesDetailsCommand.php index 53515526..8b373a43 100644 --- a/src/Console/NodesDetailsCommand.php +++ b/src/Console/NodesDetailsCommand.php @@ -14,13 +14,17 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -final class NodesDetailsCommand extends Command +class NodesDetailsCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/NodesEmptyTrashCommand.php b/src/Console/NodesEmptyTrashCommand.php index 8e21028b..d9078595 100644 --- a/src/Console/NodesEmptyTrashCommand.php +++ b/src/Console/NodesEmptyTrashCommand.php @@ -15,14 +15,20 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; -final class NodesEmptyTrashCommand extends Command +class NodesEmptyTrashCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly HandlerFactory $handlerFactory, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + protected HandlerFactory $handlerFactory; + + /** + * @param ManagerRegistry $managerRegistry + * @param HandlerFactory $handlerFactory + */ + public function __construct(ManagerRegistry $managerRegistry, HandlerFactory $handlerFactory) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; + $this->handlerFactory = $handlerFactory; } protected function configure(): void diff --git a/src/Console/NodesOrphansCommand.php b/src/Console/NodesOrphansCommand.php index e3a68598..60fcf003 100644 --- a/src/Console/NodesOrphansCommand.php +++ b/src/Console/NodesOrphansCommand.php @@ -13,13 +13,20 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -final class NodesOrphansCommand extends Command +/** + * @package RZ\Roadiz\CoreBundle\Console + */ +class NodesOrphansCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/PrivateKeyCommand.php b/src/Console/PrivateKeyCommand.php new file mode 100644 index 00000000..9e368498 --- /dev/null +++ b/src/Console/PrivateKeyCommand.php @@ -0,0 +1,57 @@ +keyChain = $keyChain; + } + + protected function configure(): void + { + $this->setName('crypto:private-key:info') + ->addArgument('key-name', InputArgument::REQUIRED) + ->setDescription('Get a private or public key information') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $keyName = $input->getArgument('key-name'); + $key = $this->keyChain->get($keyName); + + $io->table([ + 'name', + 'type', + 'derivation', + 'usage', + 'base64', + ], [[ + $keyName, + $key->isAsymmetricKey() ? 'asymmetric' : 'symmetric', + $key->isPublicKey() ? 'public' : 'private', + $key->isSigningKey() ? 'signing' : 'encryption', + base64_encode($key->getRawKeyMaterial()) + ]]); + return 0; + } +} diff --git a/src/Console/RegisterCronLastExecDateCommand.php b/src/Console/RegisterCronLastExecDateCommand.php deleted file mode 100644 index 20bed441..00000000 --- a/src/Console/RegisterCronLastExecDateCommand.php +++ /dev/null @@ -1,46 +0,0 @@ -managerRegistry->getManager(); - $parameter = $this->settingRepository->findOneByName('cron_last_exec_date'); - if (null === $parameter) { - $parameter = new Setting(); - $parameter->setName('cron_last_exec_date'); - $manager->persist($parameter); - } - - $parameter->setValue(new \DateTimeImmutable()); - $manager->flush(); - $io->success('Last execution date of cron job has been persisted.'); - - return Command::SUCCESS; - } -} diff --git a/src/Console/SolrCommand.php b/src/Console/SolrCommand.php index 32396f2b..6e930a20 100644 --- a/src/Console/SolrCommand.php +++ b/src/Console/SolrCommand.php @@ -10,15 +10,21 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Command line utils for managing nodes from terminal. + */ class SolrCommand extends Command { protected ?SymfonyStyle $io = null; + protected ClientRegistry $clientRegistry; - public function __construct( - protected readonly ClientRegistry $clientRegistry, - ?string $name = null - ) { - parent::__construct($name); + /** + * @param ClientRegistry $clientRegistry + */ + public function __construct(ClientRegistry $clientRegistry) + { + parent::__construct(); + $this->clientRegistry = $clientRegistry; } protected function configure(): void @@ -51,7 +57,7 @@ protected function displayBasicConfig(): void if (null !== $this->io) { $this->io->error('No Solr search engine server has been configured…'); $this->io->note(<<indexerFactory = $indexerFactory; } protected function configure(): void diff --git a/src/Console/SolrReindexCommand.php b/src/Console/SolrReindexCommand.php index 041a22fb..eb582902 100644 --- a/src/Console/SolrReindexCommand.php +++ b/src/Console/SolrReindexCommand.php @@ -12,20 +12,31 @@ use RZ\Roadiz\CoreBundle\SearchEngine\Indexer\IndexerFactoryInterface; use RZ\Roadiz\CoreBundle\SearchEngine\SolariumDocumentTranslation; use RZ\Roadiz\CoreBundle\SearchEngine\SolariumNodeSource; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Stopwatch\Stopwatch; +/** + * Command line utils for managing nodes from terminal. + */ class SolrReindexCommand extends SolrCommand implements ThemeAwareCommandInterface { + protected ?QuestionHelper $questionHelper = null; + protected IndexerFactoryInterface $indexerFactory; + + /** + * @param ClientRegistry $clientRegistry + * @param IndexerFactoryInterface $indexerFactory + */ public function __construct( - protected readonly IndexerFactoryInterface $indexerFactory, ClientRegistry $clientRegistry, - ?string $name = null + IndexerFactoryInterface $indexerFactory ) { - parent::__construct($clientRegistry, $name); + parent::__construct($clientRegistry); + $this->indexerFactory = $indexerFactory; } protected function configure(): void diff --git a/src/Console/SolrResetCommand.php b/src/Console/SolrResetCommand.php index ac48d1f3..6cd34a0f 100644 --- a/src/Console/SolrResetCommand.php +++ b/src/Console/SolrResetCommand.php @@ -13,14 +13,21 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; -final class SolrResetCommand extends SolrCommand +/** + * Command line utils for managing nodes from terminal. + */ +class SolrResetCommand extends SolrCommand { - public function __construct( - private readonly IndexerFactoryInterface $indexerFactory, - ClientRegistry $clientRegistry, - ?string $name = null - ) { - parent::__construct($clientRegistry, $name); + protected IndexerFactoryInterface $indexerFactory; + + /** + * @param ClientRegistry $clientRegistry + * @param IndexerFactoryInterface $indexerFactory + */ + public function __construct(ClientRegistry $clientRegistry, IndexerFactoryInterface $indexerFactory) + { + parent::__construct($clientRegistry); + $this->indexerFactory = $indexerFactory; } protected function configure(): void diff --git a/src/Console/TranslationsCommand.php b/src/Console/TranslationsCommand.php index e3781525..58217357 100644 --- a/src/Console/TranslationsCommand.php +++ b/src/Console/TranslationsCommand.php @@ -11,13 +11,20 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Command line utils for managing translations from terminal. + */ class TranslationsCommand extends Command { - public function __construct( - protected readonly ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/TranslationsCreationCommand.php b/src/Console/TranslationsCreationCommand.php index 9d0f8b90..a4ac9e5d 100644 --- a/src/Console/TranslationsCreationCommand.php +++ b/src/Console/TranslationsCreationCommand.php @@ -4,7 +4,9 @@ namespace RZ\Roadiz\CoreBundle\Console; +use Doctrine\Persistence\ManagerRegistry; use RZ\Roadiz\CoreBundle\Entity\Translation; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -14,8 +16,19 @@ /** * Command line utils for managing translations */ -final class TranslationsCreationCommand extends TranslationsCommand +class TranslationsCreationCommand extends Command { + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; + } + protected function configure(): void { $this->setName('translations:create') diff --git a/src/Console/TranslationsDeleteCommand.php b/src/Console/TranslationsDeleteCommand.php index 60f54562..eb3e5a9d 100644 --- a/src/Console/TranslationsDeleteCommand.php +++ b/src/Console/TranslationsDeleteCommand.php @@ -4,7 +4,9 @@ namespace RZ\Roadiz\CoreBundle\Console; +use Doctrine\Persistence\ManagerRegistry; use RZ\Roadiz\CoreBundle\Entity\Translation; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -14,8 +16,19 @@ /** * Command line utils for managing translations. */ -final class TranslationsDeleteCommand extends TranslationsCommand +class TranslationsDeleteCommand extends Command { + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; + } + protected function configure(): void { $this->setName('translations:delete') diff --git a/src/Console/TranslationsDisableCommand.php b/src/Console/TranslationsDisableCommand.php index 164b7422..abf6317b 100644 --- a/src/Console/TranslationsDisableCommand.php +++ b/src/Console/TranslationsDisableCommand.php @@ -4,7 +4,9 @@ namespace RZ\Roadiz\CoreBundle\Console; +use Doctrine\Persistence\ManagerRegistry; use RZ\Roadiz\CoreBundle\Entity\Translation; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -14,8 +16,19 @@ /** * Command line utils for managing translations. */ -final class TranslationsDisableCommand extends TranslationsCommand +class TranslationsDisableCommand extends Command { + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; + } + protected function configure(): void { $this->setName('translations:disable') diff --git a/src/Console/TranslationsEnableCommand.php b/src/Console/TranslationsEnableCommand.php index f6656694..f7fe0530 100644 --- a/src/Console/TranslationsEnableCommand.php +++ b/src/Console/TranslationsEnableCommand.php @@ -4,7 +4,9 @@ namespace RZ\Roadiz\CoreBundle\Console; +use Doctrine\Persistence\ManagerRegistry; use RZ\Roadiz\CoreBundle\Entity\Translation; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -14,8 +16,19 @@ /** * Command line utils for managing translations. */ -final class TranslationsEnableCommand extends TranslationsCommand +class TranslationsEnableCommand extends Command { + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; + } + protected function configure(): void { $this->setName('translations:enable') diff --git a/src/Console/UsersCommand.php b/src/Console/UsersCommand.php index 2309ff8d..f61d19ef 100644 --- a/src/Console/UsersCommand.php +++ b/src/Console/UsersCommand.php @@ -14,13 +14,17 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Command line utils for managing users from terminal. + */ class UsersCommand extends Command { - public function __construct( - protected readonly ManagerRegistry $managerRegistry, - string $name = null - ) { + protected ManagerRegistry $managerRegistry; + + public function __construct(ManagerRegistry $managerRegistry, string $name = null) + { parent::__construct($name); + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Console/UsersCreationCommand.php b/src/Console/UsersCreationCommand.php index 2373093e..bb33ff51 100644 --- a/src/Console/UsersCreationCommand.php +++ b/src/Console/UsersCreationCommand.php @@ -15,6 +15,9 @@ use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Command line utils for managing users from terminal. + */ final class UsersCreationCommand extends UsersCommand { protected function configure(): void diff --git a/src/Console/UsersDeleteCommand.php b/src/Console/UsersDeleteCommand.php index 2e8c3062..9e98959f 100644 --- a/src/Console/UsersDeleteCommand.php +++ b/src/Console/UsersDeleteCommand.php @@ -11,6 +11,9 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Command line utils for managing users from terminal. + */ final class UsersDeleteCommand extends UsersCommand { protected function configure(): void diff --git a/src/Console/UsersDisableCommand.php b/src/Console/UsersDisableCommand.php index 97b0062a..11cf7db2 100644 --- a/src/Console/UsersDisableCommand.php +++ b/src/Console/UsersDisableCommand.php @@ -11,6 +11,9 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Command line utils for managing users from terminal. + */ final class UsersDisableCommand extends UsersCommand { protected function configure(): void diff --git a/src/Console/UsersEnableCommand.php b/src/Console/UsersEnableCommand.php index b9092656..f277a779 100644 --- a/src/Console/UsersEnableCommand.php +++ b/src/Console/UsersEnableCommand.php @@ -11,6 +11,9 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Command line utils for managing users from terminal. + */ final class UsersEnableCommand extends UsersCommand { protected function configure(): void diff --git a/src/Console/UsersPasswordCommand.php b/src/Console/UsersPasswordCommand.php index 9d265e1f..ccab60ae 100644 --- a/src/Console/UsersPasswordCommand.php +++ b/src/Console/UsersPasswordCommand.php @@ -13,14 +13,21 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Command line utils for managing users from terminal. + */ final class UsersPasswordCommand extends UsersCommand { - public function __construct( - private readonly PasswordGenerator $passwordGenerator, - ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($managerRegistry, $name); + private PasswordGenerator $passwordGenerator; + + /** + * @param ManagerRegistry $managerRegistry + * @param PasswordGenerator $passwordGenerator + */ + public function __construct(ManagerRegistry $managerRegistry, PasswordGenerator $passwordGenerator) + { + parent::__construct($managerRegistry); + $this->passwordGenerator = $passwordGenerator; } protected function configure(): void diff --git a/src/Console/UsersRolesCommand.php b/src/Console/UsersRolesCommand.php index 047a2d5b..0c9fc369 100644 --- a/src/Console/UsersRolesCommand.php +++ b/src/Console/UsersRolesCommand.php @@ -20,12 +20,16 @@ */ final class UsersRolesCommand extends UsersCommand { - public function __construct( - private readonly Roles $rolesBag, - ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($managerRegistry, $name); + private Roles $rolesBag; + + /** + * @param ManagerRegistry $managerRegistry + * @param Roles $rolesBag + */ + public function __construct(ManagerRegistry $managerRegistry, Roles $rolesBag) + { + parent::__construct($managerRegistry); + $this->rolesBag = $rolesBag; } protected function configure(): void diff --git a/src/Console/VersionsPurgeCommand.php b/src/Console/VersionsPurgeCommand.php index 8891e472..0f83df79 100644 --- a/src/Console/VersionsPurgeCommand.php +++ b/src/Console/VersionsPurgeCommand.php @@ -18,11 +18,15 @@ final class VersionsPurgeCommand extends Command { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - ?string $name = null - ) { - parent::__construct($name); + protected ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) + { + parent::__construct(); + $this->managerRegistry = $managerRegistry; } protected function configure(): void diff --git a/src/Controller/CustomFormController.php b/src/Controller/CustomFormController.php index b044580a..c22ac946 100644 --- a/src/Controller/CustomFormController.php +++ b/src/Controller/CustomFormController.php @@ -9,12 +9,15 @@ use League\Flysystem\FilesystemException; use Limenius\Liform\LiformInterface; use Psr\Log\LoggerInterface; +use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; use RZ\Roadiz\CoreBundle\Bag\Settings; use RZ\Roadiz\CoreBundle\CustomForm\CustomFormHelperFactory; use RZ\Roadiz\CoreBundle\CustomForm\Message\CustomFormAnswerNotifyMessage; use RZ\Roadiz\CoreBundle\Entity\CustomForm; use RZ\Roadiz\CoreBundle\Exception\EntityAlreadyExistsException; use RZ\Roadiz\CoreBundle\Form\Error\FormErrorSerializerInterface; +use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; +use RZ\Roadiz\CoreBundle\Repository\TranslationRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; @@ -29,22 +32,47 @@ use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\RateLimiter\RateLimiterFactory; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; final class CustomFormController extends AbstractController { + private Settings $settingsBag; + private LoggerInterface $logger; + private TranslatorInterface $translator; + private CustomFormHelperFactory $customFormHelperFactory; + private LiformInterface $liform; + private SerializerInterface $serializer; + private FormErrorSerializerInterface $formErrorSerializer; + private ManagerRegistry $registry; + private RateLimiterFactory $customFormLimiter; + private PreviewResolverInterface $previewResolver; + private MessageBusInterface $messageBus; + public function __construct( - private readonly Settings $settingsBag, - private readonly LoggerInterface $logger, - private readonly TranslatorInterface $translator, - private readonly CustomFormHelperFactory $customFormHelperFactory, - private readonly LiformInterface $liform, - private readonly SerializerInterface $serializer, - private readonly FormErrorSerializerInterface $formErrorSerializer, - private readonly ManagerRegistry $registry, - private readonly RateLimiterFactory $customFormLimiter, - private readonly MessageBusInterface $messageBus, + Settings $settingsBag, + LoggerInterface $logger, + TranslatorInterface $translator, + CustomFormHelperFactory $customFormHelperFactory, + LiformInterface $liform, + SerializerInterface $serializer, + FormErrorSerializerInterface $formErrorSerializer, + ManagerRegistry $registry, + RateLimiterFactory $customFormLimiter, + PreviewResolverInterface $previewResolver, + MessageBusInterface $messageBus, ) { + $this->settingsBag = $settingsBag; + $this->logger = $logger; + $this->translator = $translator; + $this->customFormHelperFactory = $customFormHelperFactory; + $this->liform = $liform; + $this->serializer = $serializer; + $this->formErrorSerializer = $formErrorSerializer; + $this->registry = $registry; + $this->customFormLimiter = $customFormLimiter; + $this->previewResolver = $previewResolver; + $this->messageBus = $messageBus; } protected function validateCustomForm(?CustomForm $customForm): void @@ -57,6 +85,38 @@ protected function validateCustomForm(?CustomForm $customForm): void } } + protected function getTranslationFromRequest(?Request $request): TranslationInterface + { + $locale = null; + + if (null !== $request) { + $locale = $request->query->get('_locale'); + + /* + * If no _locale query param is defined check Accept-Language header + */ + if (null === $locale) { + $locale = $request->getPreferredLanguage($this->getTranslationRepository()->getAllLocales()); + } + } + /* + * Then fallback to default CMS locale + */ + if (null === $locale) { + $translation = $this->getTranslationRepository()->findDefault(); + } elseif ($this->previewResolver->isPreview()) { + $translation = $this->getTranslationRepository() + ->findOneByLocaleOrOverrideLocale((string) $locale); + } else { + $translation = $this->getTranslationRepository() + ->findOneAvailableByLocaleOrOverrideLocale((string) $locale); + } + if (null === $translation) { + throw new NotFoundHttpException('No translation for locale ' . $locale); + } + return $translation; + } + /** * @param Request $request * @param int $id @@ -69,6 +129,11 @@ public function definitionAction(Request $request, int $id): JsonResponse $this->validateCustomForm($customForm); $helper = $this->customFormHelperFactory->createHelper($customForm); + $translation = $this->getTranslationFromRequest($request); + $request->setLocale($translation->getPreferredLocale()); + if ($this->translator instanceof LocaleAwareInterface) { + $this->translator->setLocale($translation->getPreferredLocale()); + } $schema = json_encode($this->liform->transform($helper->getForm($request, false, false))); return new JsonResponse( @@ -104,6 +169,12 @@ public function postAction(Request $request, int $id): Response $customForm = $this->registry->getRepository(CustomForm::class)->find($id); $this->validateCustomForm($customForm); + $translation = $this->getTranslationFromRequest($request); + $request->setLocale($translation->getPreferredLocale()); + if ($this->translator instanceof LocaleAwareInterface) { + $this->translator->setLocale($translation->getPreferredLocale()); + } + $mixed = $this->prepareAndHandleCustomFormAssignation( $request, $customForm, @@ -251,13 +322,10 @@ public function prepareAndHandleCustomFormAssignation( ['%name%' => $customFormsEntity->getDisplayName()] ); - if (!$request->attributes->getBoolean('_stateless') && $request->hasPreviousSession()) { - $session = $request->getSession(); - if ($session instanceof Session) { - $session->getFlashBag()->add('confirm', $msg); - } + $session = $request->getSession(); + if ($session instanceof Session) { + $session->getFlashBag()->add('confirm', $msg); } - $this->logger->info($msg); return $response; @@ -270,4 +338,16 @@ public function prepareAndHandleCustomFormAssignation( $assignation['formObject'] = $form; return $assignation; } + + protected function getTranslationRepository(): TranslationRepository + { + $repository = $this->registry->getRepository(TranslationInterface::class); + if (!$repository instanceof TranslationRepository) { + throw new \RuntimeException( + 'Translation repository must be instance of ' . + TranslationRepository::class + ); + } + return $repository; + } } diff --git a/src/Crypto/UniqueKeyEncoderFactory.php b/src/Crypto/UniqueKeyEncoderFactory.php new file mode 100644 index 00000000..9747ed07 --- /dev/null +++ b/src/Crypto/UniqueKeyEncoderFactory.php @@ -0,0 +1,55 @@ +keyChain = $keyChain; + $this->defaultKeyName = $defaultKeyName; + } + + public function getEncoder(?string $keyName = null): UniqueKeyEncoderInterface + { + try { + $keyName = $keyName ?? $this->defaultKeyName; + $key = $this->keyChain->get($keyName); + + if ($key instanceof EncryptionSecretKey) { + $publicKey = $key->derivePublicKey(); + return new AsymmetricUniqueKeyEncoder( + $publicKey, + $key + ); + } elseif ($key instanceof EncryptionKey) { + return new SymmetricUniqueKeyEncoder($key); + } + } catch (\Exception $exception) { + throw new InvalidKey( + sprintf('Key %s is not a valid encryption key', $keyName), + 0, + $exception + ); + } + + throw new InvalidKey(sprintf('Key %s is not a valid encryption key', $keyName)); + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 7f5e8948..4240e7ab 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -4,7 +4,6 @@ namespace RZ\Roadiz\CoreBundle\DependencyInjection; -use RZ\Roadiz\CoreBundle\Api\Model\WebResponse; use RZ\Roadiz\CoreBundle\Controller\DefaultNodeSourceController; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeDefinition; @@ -45,18 +44,12 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('defaultNodeSourceController') ->defaultValue(DefaultNodeSourceController::class) ->end() - ->scalarNode('webResponseClass') - ->defaultValue(WebResponse::class) - ->end() ->booleanNode('useNativeJsonColumnType') ->defaultValue(true) ->end() ->booleanNode('hideRoadizVersion') ->defaultValue(false) ->end() - ->booleanNode('useGravatar') - ->defaultTrue() - ->end() ->scalarNode('documentsLibDir')->defaultValue( 'vendor/roadiz/documents/src' )->info('Relative path to Roadiz Documents lib sources from project directory.')->end() @@ -76,6 +69,19 @@ public function getConfigTreeBuilder(): TreeBuilder their node-type to avoid name conflicts with reachable nodes (pages). EOT) ->end() + ->arrayNode('security') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('private_key_dir') + ->defaultValue('%kernel.project_dir%/var/secret') + ->info('Asymmetric cryptographic key directory.') + ->end() + ->scalarNode('private_key_name') + ->defaultValue('default') + ->info('Asymmetric cryptographic key name.') + ->end() + ->end() + ->end() ->append($this->addSolrNode()) ->append($this->addInheritanceNode()) ->append($this->addReverseProxyCacheNode()) diff --git a/src/DependencyInjection/RoadizCoreExtension.php b/src/DependencyInjection/RoadizCoreExtension.php index dce12f58..3bf9a7ac 100644 --- a/src/DependencyInjection/RoadizCoreExtension.php +++ b/src/DependencyInjection/RoadizCoreExtension.php @@ -6,9 +6,12 @@ use League\CommonMark\Environment\Environment; use League\CommonMark\MarkdownConverter; +use RZ\Crypto\KeyChain\AsymmetricFilesystemKeyChain; +use RZ\Crypto\KeyChain\KeyChainInterface; use RZ\Roadiz\CoreBundle\Cache\CloudflareProxyCache; use RZ\Roadiz\CoreBundle\Cache\ReverseProxyCache; use RZ\Roadiz\CoreBundle\Cache\ReverseProxyCacheLocator; +use RZ\Roadiz\CoreBundle\Crypto\UniqueKeyEncoderFactory; use RZ\Roadiz\CoreBundle\Entity\CustomForm; use RZ\Roadiz\CoreBundle\Entity\Document; use RZ\Roadiz\CoreBundle\Entity\Node; @@ -57,17 +60,21 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('roadiz_core.app_namespace', $config['appNamespace']); $container->setParameter('roadiz_core.app_version', $config['appVersion']); - $container->setParameter('roadiz_core.use_gravatar', $config['useGravatar']); $container->setParameter('roadiz_core.health_check_token', $config['healthCheckToken']); $container->setParameter('roadiz_core.inheritance_type', $config['inheritance']['type']); $container->setParameter('roadiz_core.max_versions_showed', $config['maxVersionsShowed']); $container->setParameter('roadiz_core.static_domain_name', $config['staticDomainName'] ?? ''); + $container->setParameter('roadiz_core.private_key_name', $config['security']['private_key_name']); + $container->setParameter('roadiz_core.private_key_dir', $config['security']['private_key_dir']); + $container->setParameter( + 'roadiz_core.private_key_path', + $config['security']['private_key_dir'] . DIRECTORY_SEPARATOR . $config['security']['private_key_name'] + ); $container->setParameter('roadiz_core.default_node_source_controller', $config['defaultNodeSourceController']); $container->setParameter('roadiz_core.use_native_json_column_type', $config['useNativeJsonColumnType']); $container->setParameter('roadiz_core.use_typed_node_names', $config['useTypedNodeNames']); $container->setParameter('roadiz_core.hide_roadiz_version', $config['hideRoadizVersion']); $container->setParameter('roadiz_core.use_accept_language_header', $config['useAcceptLanguageHeader']); - $container->setParameter('roadiz_core.web_response_class', $config['webResponseClass']); $container->setParameter('roadiz_core.preview_required_role_name', $config['previewRequiredRoleName']); /* @@ -125,6 +132,31 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerReverseProxyCache($config, $container); $this->registerSolr($config, $container); $this->registerMarkdown($config, $container); + $this->registerCrypto($config, $container); + } + + private function registerCrypto(array $config, ContainerBuilder $container): void + { + $container->setDefinition( + UniqueKeyEncoderFactory::class, + (new Definition()) + ->setClass(UniqueKeyEncoderFactory::class) + ->setPublic(true) + ->setArguments([ + new Reference(KeyChainInterface::class), + $container->getParameter('roadiz_core.private_key_name') + ]) + ); + + $container->setDefinition( + KeyChainInterface::class, + (new Definition()) + ->setClass(AsymmetricFilesystemKeyChain::class) + ->setPublic(true) + ->setArguments([ + $container->getParameter('roadiz_core.private_key_dir') + ]) + ); } private function registerReverseProxyCache(array $config, ContainerBuilder $container): void diff --git a/src/Doctrine/EventSubscriber/SettingLifeCycleSubscriber.php b/src/Doctrine/EventSubscriber/SettingLifeCycleSubscriber.php new file mode 100644 index 00000000..acc18d84 --- /dev/null +++ b/src/Doctrine/EventSubscriber/SettingLifeCycleSubscriber.php @@ -0,0 +1,125 @@ +uniqueKeyEncoderFactory = $uniqueKeyEncoderFactory; + $this->privateKeyName = $privateKeyName; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function getSubscribedEvents(): array + { + return [ + Events::preUpdate, + Events::postLoad + ]; + } + + /** + * @param PreUpdateEventArgs $event + * @throws InvalidKey + */ + public function preUpdate(PreUpdateEventArgs $event): void + { + $setting = $event->getObject(); + if ($setting instanceof Setting) { + if ( + $event->hasChangedField('encrypted') && + $event->getNewValue('encrypted') === false && + null !== $setting->getRawValue() + ) { + /* + * Set raw value and do not encode it if setting is not encrypted anymore. + */ + $setting->setValue($setting->getRawValue()); + } elseif ( + $event->hasChangedField('encrypted') && + $event->getNewValue('encrypted') === true && + null !== $setting->getRawValue() + ) { + /* + * Encode value for the first time. + */ + $setting->setValue($this->getEncoder()->encode(new HiddenString($setting->getRawValue()))); + } elseif ( + $setting->isEncrypted() && + $event->hasChangedField('value') && + null !== $event->getNewValue('value') + ) { + /* + * Encode setting if value has changed + */ + $event->setNewValue('value', $this->getEncoder()->encode(new HiddenString($event->getNewValue('value')))); + $setting->setClearValue($event->getNewValue('value')); + } + } + } + + /** + * @param LifecycleEventArgs $event + */ + public function postLoad(LifecycleEventArgs $event): void + { + $setting = $event->getObject(); + if ( + $setting instanceof Setting && + $setting->isEncrypted() && + null !== $setting->getRawValue() + ) { + try { + $setting->setClearValue($this->getEncoder()->decode($setting->getRawValue())->getString()); + } catch (InvalidKey $exception) { + $this->logger->error( + sprintf('Failed to decode "%s" setting value', $setting->getName()), + [ + 'exception_message' => $exception->getMessage(), + 'trace' => $exception->getTraceAsString(), + 'entity' => $setting + ] + ); + } catch (InvalidMessage $exception) { + $this->logger->error( + sprintf('Failed to decode "%s" setting value', $setting->getName()), + [ + 'exception_message' => $exception->getMessage(), + 'trace' => $exception->getTraceAsString(), + 'entity' => $setting + ] + ); + } + } + } + + /** + * @throws InvalidKey + */ + protected function getEncoder(): UniqueKeyEncoderInterface + { + return $this->uniqueKeyEncoderFactory->getEncoder($this->privateKeyName); + } +} diff --git a/src/Doctrine/EventSubscriber/UserLifeCycleSubscriber.php b/src/Doctrine/EventSubscriber/UserLifeCycleSubscriber.php index 0e3b98f8..b7c59b99 100644 --- a/src/Doctrine/EventSubscriber/UserLifeCycleSubscriber.php +++ b/src/Doctrine/EventSubscriber/UserLifeCycleSubscriber.php @@ -24,13 +24,27 @@ final class UserLifeCycleSubscriber implements EventSubscriber { + private UserViewer $userViewer; + private EventDispatcherInterface $dispatcher; + private PasswordHasherFactoryInterface $passwordHasherFactory; + private LoggerInterface $logger; + + /** + * @param UserViewer $userViewer + * @param EventDispatcherInterface $dispatcher + * @param PasswordHasherFactoryInterface $passwordHasherFactory + * @param LoggerInterface $logger + */ public function __construct( - private readonly UserViewer $userViewer, - private readonly EventDispatcherInterface $dispatcher, - private readonly PasswordHasherFactoryInterface $passwordHasherFactory, - private readonly LoggerInterface $logger, - private readonly bool $useGravatar + UserViewer $userViewer, + EventDispatcherInterface $dispatcher, + PasswordHasherFactoryInterface $passwordHasherFactory, + LoggerInterface $logger ) { + $this->userViewer = $userViewer; + $this->dispatcher = $dispatcher; + $this->logger = $logger; + $this->passwordHasherFactory = $passwordHasherFactory; } /** @@ -80,11 +94,9 @@ public function preUpdate(PreUpdateEventArgs $event): void $user->setPictureUrl($url); } catch (\Exception $e) { $user->setFacebookName(''); - if ($this->useGravatar) { - $user->setPictureUrl($user->getGravatarUrl()); - } + $user->setPictureUrl($user->getGravatarUrl()); } - } elseif ($this->useGravatar) { + } else { $user->setPictureUrl($user->getGravatarUrl()); } } @@ -189,7 +201,7 @@ public function prePersist(LifecycleEventArgs $event): void /* * Force a Gravatar image if not defined */ - if (empty($user->getPictureUrl()) && $this->useGravatar) { + if (empty($user->getPictureUrl())) { $user->setPictureUrl($user->getGravatarUrl()); } } diff --git a/src/Doctrine/ORM/Filter/ANodesFilter.php b/src/Doctrine/ORM/Filter/ANodesFilter.php index ebfcb49e..8548be74 100644 --- a/src/Doctrine/ORM/Filter/ANodesFilter.php +++ b/src/Doctrine/ORM/Filter/ANodesFilter.php @@ -40,6 +40,14 @@ protected function getNodeJoinAlias(): string return 'a_n'; } + /** + * @return string + */ + protected function getNodeFieldJoinAlias(): string + { + return 'a_n_f'; + } + /** * @param QueryBuilderBuildEvent $event */ @@ -64,9 +72,24 @@ public function onNodeQueryBuilderBuild(QueryBuilderBuildEvent $event): void $this->getNodeJoinAlias() ); } - - $prefix = $this->getNodeJoinAlias() . '.'; - $key = str_replace($this->getProperty() . '.', '', $event->getProperty()); + if (str_contains($event->getProperty(), $this->getProperty() . '.field.')) { + if ( + !$simpleQB->joinExists( + $simpleQB->getRootAlias(), + $this->getNodeFieldJoinAlias() + ) + ) { + $qb->innerJoin( + $this->getNodeJoinAlias() . '.field', + $this->getNodeFieldJoinAlias() + ); + } + $prefix = $this->getNodeFieldJoinAlias() . '.'; + $key = str_replace($this->getProperty() . '.field.', '', $event->getProperty()); + } else { + $prefix = $this->getNodeJoinAlias() . '.'; + $key = str_replace($this->getProperty() . '.', '', $event->getProperty()); + } $qb->andWhere($simpleQB->buildExpressionWithoutBinding($event->getValue(), $prefix, $key, $baseKey)); } @@ -109,9 +132,24 @@ public function onNodesSourcesQueryBuilderBuild(QueryBuilderNodesSourcesBuildEve $this->getNodeJoinAlias() ); } - - $prefix = $this->getNodeJoinAlias() . '.'; - $key = str_replace('node.' . $this->getProperty() . '.', '', $event->getProperty()); + if (str_contains($event->getProperty(), 'node.' . $this->getProperty() . '.field.')) { + if ( + !$simpleQB->joinExists( + $simpleQB->getRootAlias(), + $this->getNodeFieldJoinAlias() + ) + ) { + $qb->innerJoin( + $this->getNodeJoinAlias() . '.field', + $this->getNodeFieldJoinAlias() + ); + } + $prefix = $this->getNodeFieldJoinAlias() . '.'; + $key = str_replace('node.' . $this->getProperty() . '.field.', '', $event->getProperty()); + } else { + $prefix = $this->getNodeJoinAlias() . '.'; + $key = str_replace('node.' . $this->getProperty() . '.', '', $event->getProperty()); + } $qb->andWhere($simpleQB->buildExpressionWithoutBinding($event->getValue(), $prefix, $key, $baseKey)); } diff --git a/src/Doctrine/ORM/Filter/BNodesFilter.php b/src/Doctrine/ORM/Filter/BNodesFilter.php index 8cb6f894..032b9e7a 100644 --- a/src/Doctrine/ORM/Filter/BNodesFilter.php +++ b/src/Doctrine/ORM/Filter/BNodesFilter.php @@ -35,4 +35,12 @@ protected function getNodeJoinAlias(): string { return 'b_n'; } + + /** + * @return string + */ + protected function getNodeFieldJoinAlias(): string + { + return 'b_n_f'; + } } diff --git a/src/Doctrine/ORM/SimpleQueryBuilder.php b/src/Doctrine/ORM/SimpleQueryBuilder.php index 90c7713b..15e3204b 100644 --- a/src/Doctrine/ORM/SimpleQueryBuilder.php +++ b/src/Doctrine/ORM/SimpleQueryBuilder.php @@ -55,7 +55,7 @@ public function buildExpressionWithBinding($value, string $prefix, string $key): * * @return Comparison|Func|string */ - public function buildExpressionWithoutBinding(mixed $value, string $prefix, string $key, string $baseKey = null) + public function buildExpressionWithoutBinding($value, string $prefix, string $key, string $baseKey = null) { if (\mb_strlen($prefix) > 0 && \mb_substr($prefix, -\mb_strlen('.')) !== '.') { $prefix .= '.'; @@ -127,11 +127,14 @@ public function buildExpressionWithoutBinding(mixed $value, string $prefix, stri if ($value instanceof PersistableInterface) { return $this->queryBuilder->expr()->eq($prefix . $key, ':' . $baseKey); } + if (isset($value)) { + return $this->queryBuilder->expr()->eq($prefix . $key, ':' . $baseKey); + } if (null === $value) { return $this->queryBuilder->expr()->isNull($prefix . $key); } - - return $this->queryBuilder->expr()->eq($prefix . $key, ':' . $baseKey); + // @phpstan-ignore-next-line + throw new \InvalidArgumentException('Value is not supported for expression.'); } /** @@ -178,11 +181,14 @@ public function bindValue(string $key, $value): QueryBuilder if ($value instanceof PersistableInterface) { return $this->queryBuilder->setParameter($key, $value->getId()); } + if (isset($value)) { + return $this->queryBuilder->setParameter($key, $value); + } if (null === $value) { return $this->queryBuilder; } - - return $this->queryBuilder->setParameter($key, $value); + // @phpstan-ignore-next-line + throw new \InvalidArgumentException('Value is not supported for binding.'); } /** diff --git a/src/Entity/AbstractDateTimedPositioned.php b/src/Entity/AbstractDateTimedPositioned.php deleted file mode 100644 index 9f33830a..00000000 --- a/src/Entity/AbstractDateTimedPositioned.php +++ /dev/null @@ -1,42 +0,0 @@ -document = $document; $this->attribute = $attribute; } + /** + * + */ public function __clone() { if ($this->id) { $this->id = null; + $this->attribute = null; } } - public function getDocument(): Document + /** + * Gets the value of document. + * + * @return Document|null + */ + public function getDocument(): ?Document { return $this->document; } - public function setDocument(Document $document): AttributeDocuments + /** + * Sets the value of document. + * + * @param Document|null $document the document + * + * @return AttributeDocuments + */ + public function setDocument(?Document $document): AttributeDocuments { $this->document = $document; + return $this; } - public function getAttribute(): Attribute + /** + * @return Attribute|null + */ + public function getAttribute(): ?Attribute { return $this->attribute; } - public function setAttribute(Attribute $attribute): AttributeDocuments + /** + * @param Attribute|null $attribute + * @return AttributeDocuments + */ + public function setAttribute(?Attribute $attribute): AttributeDocuments { $this->attribute = $attribute; return $this; diff --git a/src/Entity/AttributeValue.php b/src/Entity/AttributeValue.php index f5df8d02..641fd2fa 100644 --- a/src/Entity/AttributeValue.php +++ b/src/Entity/AttributeValue.php @@ -35,9 +35,12 @@ class AttributeValue extends AbstractPositioned implements AttributeValueInterfa { use AttributeValueTrait; + /** + * @var Node|null + */ #[ ORM\ManyToOne(targetEntity: Node::class, inversedBy: "attributeValues"), - ORM\JoinColumn(name: "node_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(name: "node_id", onDelete: "CASCADE"), Serializer\Groups(["attribute_node"]), SymfonySerializer\Groups(["attribute_node"]), SymfonySerializer\MaxDepth(1), @@ -52,7 +55,7 @@ class AttributeValue extends AbstractPositioned implements AttributeValueInterfa "node.visible" ]) ] - protected Node $node; + protected ?Node $node = null; #[ORM\ManyToOne(targetEntity: Realm::class)] #[ORM\JoinColumn( @@ -82,7 +85,10 @@ public function getPosition(): float return $this->position; } - public function getAttributable(): Node + /** + * @inheritDoc + */ + public function getAttributable(): ?AttributableInterface { return $this->node; } @@ -99,12 +105,20 @@ public function setAttributable(?AttributableInterface $attributable) throw new \InvalidArgumentException('Attributable have to be an instance of Node.'); } - public function getNode(): Node + /** + * @return Node|null + */ + public function getNode(): ?Node { return $this->node; } - public function setNode(Node $node): AttributeValue + /** + * @param Node|null $node + * + * @return AttributeValue + */ + public function setNode(?Node $node): AttributeValue { $this->node = $node; diff --git a/src/Entity/CustomForm.php b/src/Entity/CustomForm.php index 9f91c796..61befe69 100644 --- a/src/Entity/CustomForm.php +++ b/src/Entity/CustomForm.php @@ -108,12 +108,7 @@ class CustomForm extends AbstractDateTimed * @var Collection */ #[ - ORM\OneToMany( - mappedBy: "customForm", - targetEntity: CustomFormField::class, - cascade: ["ALL"], - orphanRemoval: true - ), + ORM\OneToMany(mappedBy: "customForm", targetEntity: CustomFormField::class, cascade: ["ALL"]), ORM\OrderBy(["position" => "ASC"]), Serializer\Groups(["custom_form"]), SymfonySerializer\Groups(["custom_form"]), @@ -128,8 +123,7 @@ class CustomForm extends AbstractDateTimed ORM\OneToMany( mappedBy: "customForm", targetEntity: CustomFormAnswer::class, - cascade: ["ALL"], - orphanRemoval: true + cascade: ["ALL"] ), Serializer\Exclude, SymfonySerializer\Ignore @@ -345,6 +339,7 @@ public function removeField(CustomFormField $field): CustomForm { if ($this->getFields()->contains($field)) { $this->getFields()->removeElement($field); + $field->setCustomForm(null); } return $this; diff --git a/src/Entity/CustomFormAnswer.php b/src/Entity/CustomFormAnswer.php index f73abec6..40121188 100644 --- a/src/Entity/CustomFormAnswer.php +++ b/src/Entity/CustomFormAnswer.php @@ -44,8 +44,7 @@ class CustomFormAnswer extends AbstractEntity ORM\OneToMany( mappedBy: "customFormAnswer", targetEntity: CustomFormFieldAttribute::class, - cascade: ["ALL"], - orphanRemoval: true + cascade: ["ALL"] ), Serializer\Groups(["custom_form_answer"]), SymfonySerializer\Groups(["custom_form_answer"]) @@ -57,11 +56,11 @@ class CustomFormAnswer extends AbstractEntity targetEntity: CustomForm::class, inversedBy: "customFormAnswers" ), - ORM\JoinColumn(name: "custom_form_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(name: "custom_form_id", referencedColumnName: "id", onDelete: "CASCADE"), Serializer\Exclude, SymfonySerializer\Ignore ] - private CustomForm $customForm; + private ?CustomForm $customForm = null; public function __construct() { diff --git a/src/Entity/CustomFormField.php b/src/Entity/CustomFormField.php index d1c6ee20..f7b90f28 100644 --- a/src/Entity/CustomFormField.php +++ b/src/Entity/CustomFormField.php @@ -50,11 +50,11 @@ class CustomFormField extends AbstractField #[ ORM\ManyToOne(targetEntity: CustomForm::class, inversedBy: "fields"), - ORM\JoinColumn(name: "custom_form_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(name: "custom_form_id", referencedColumnName: "id", onDelete: "CASCADE"), Serializer\Exclude, SymfonySerializer\Ignore ] - private CustomForm $customForm; + private ?CustomForm $customForm = null; /** * @var Collection @@ -84,7 +84,7 @@ public function __construct() * * @return $this */ - public function setLabel($label): CustomFormField + public function setLabel($label) { parent::setLabel($label); $this->setName($label); @@ -92,15 +92,25 @@ public function setLabel($label): CustomFormField return $this; } - public function getCustomForm(): CustomForm + /** + * @return CustomForm|null + */ + public function getCustomForm(): ?CustomForm { return $this->customForm; } - public function setCustomForm(CustomForm $customForm): CustomFormField + /** + * @param CustomForm|null $customForm + * + * @return $this + */ + public function setCustomForm(CustomForm $customForm = null): CustomFormField { $this->customForm = $customForm; - $this->customForm->addField($this); + if (null !== $customForm) { + $this->customForm->addField($this); + } return $this; } @@ -152,6 +162,7 @@ public function __clone() { if ($this->id) { $this->id = null; + $this->customForm = null; $this->customFormFieldAttributes = new ArrayCollection(); } } diff --git a/src/Entity/CustomFormFieldAttribute.php b/src/Entity/CustomFormFieldAttribute.php index 65ec6285..63c441d1 100644 --- a/src/Entity/CustomFormFieldAttribute.php +++ b/src/Entity/CustomFormFieldAttribute.php @@ -24,15 +24,15 @@ class CustomFormFieldAttribute extends AbstractEntity { #[ ORM\ManyToOne(targetEntity: CustomFormAnswer::class, inversedBy: "answerFields"), - ORM\JoinColumn(name: "custom_form_answer_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE") + ORM\JoinColumn(name: "custom_form_answer_id", referencedColumnName: "id", onDelete: "CASCADE") ] - protected CustomFormAnswer $customFormAnswer; + protected ?CustomFormAnswer $customFormAnswer = null; #[ ORM\ManyToOne(targetEntity: CustomFormField::class, inversedBy: "customFormFieldAttributes"), - ORM\JoinColumn(name: "custom_form_field_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE") + ORM\JoinColumn(name: "custom_form_field_id", referencedColumnName: "id", onDelete: "CASCADE") ] - protected CustomFormField $customFormField; + protected ?CustomFormField $customFormField = null; /** * @var Collection @@ -83,11 +83,23 @@ public function setValue(?string $value): CustomFormFieldAttribute return $this; } - public function getCustomFormAnswer(): CustomFormAnswer + /** + * Gets the value of customFormAnswer. + * + * @return CustomFormAnswer|null + */ + public function getCustomFormAnswer(): ?CustomFormAnswer { return $this->customFormAnswer; } + /** + * Sets the value of customFormAnswer. + * + * @param CustomFormAnswer $customFormAnswer the custom form answer + * + * @return self + */ public function setCustomFormAnswer(CustomFormAnswer $customFormAnswer): CustomFormFieldAttribute { $this->customFormAnswer = $customFormAnswer; @@ -104,11 +116,20 @@ public function __toString(): string return $this->getValue() ?? ''; } - public function getCustomFormField(): CustomFormField + /** + * @return CustomFormField|null + */ + public function getCustomFormField(): ?CustomFormField { return $this->customFormField; } + /** + * Sets the value of customFormField. + * + * @param CustomFormField $customFormField the custom form field + * @return self + */ public function setCustomFormField(CustomFormField $customFormField): CustomFormFieldAttribute { $this->customFormField = $customFormField; diff --git a/src/Entity/Document.php b/src/Entity/Document.php index 2f8db769..073815c5 100644 --- a/src/Entity/Document.php +++ b/src/Entity/Document.php @@ -6,7 +6,6 @@ use ApiPlatform\Doctrine\Orm\Filter as BaseFilter; use ApiPlatform\Metadata\ApiFilter; -use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Serializer\Filter\PropertyFilter; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -80,9 +79,6 @@ class Document extends AbstractDateTimed implements AdvancedDocumentInterface, H #[ORM\Column(name: 'copyright_valid_since', type: 'datetime', nullable: true)] #[SymfonySerializer\Groups(['document_copyright'])] #[Serializer\Groups(['document_copyright'])] - #[ApiProperty( - description: 'Document copyright starting date', - )] protected ?\DateTime $copyrightValidSince = null; /** @@ -91,9 +87,6 @@ class Document extends AbstractDateTimed implements AdvancedDocumentInterface, H #[ORM\Column(name: 'copyright_valid_until', type: 'datetime', nullable: true)] #[SymfonySerializer\Groups(['document_copyright'])] #[Serializer\Groups(['document_copyright'])] - #[ApiProperty( - description: 'Document copyright expiry date', - )] protected ?\DateTime $copyrightValidUntil = null; /** @@ -150,10 +143,6 @@ class Document extends AbstractDateTimed implements AdvancedDocumentInterface, H #[SymfonySerializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Type("string")] - #[ApiProperty( - description: 'Embed ID on external platforms', - example: 'FORSwsjtQSE', - )] #[Assert\Length(max: 250)] protected ?string $embedId = null; @@ -178,10 +167,6 @@ class Document extends AbstractDateTimed implements AdvancedDocumentInterface, H #[Serializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Type('string')] #[Assert\Length(max: 100)] - #[ApiProperty( - description: 'Embed platform name', - example: 'youtube', - )] protected ?string $embedPlatform = null; /** * @var Collection @@ -250,10 +235,6 @@ class Document extends AbstractDateTimed implements AdvancedDocumentInterface, H #[Serializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Type('string')] #[Assert\Length(max: 255)] - #[ApiProperty( - description: 'Document file mime type', - example: 'image/jpeg', - )] private ?string $mimeType = null; /** * @var Collection @@ -286,10 +267,6 @@ class Document extends AbstractDateTimed implements AdvancedDocumentInterface, H #[SymfonySerializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Type('int')] - #[ApiProperty( - description: 'When document has visual size: width in pixels', - example: '1280', - )] private int $imageWidth = 0; /** * @var integer @@ -298,10 +275,6 @@ class Document extends AbstractDateTimed implements AdvancedDocumentInterface, H #[SymfonySerializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Type('int')] - #[ApiProperty( - description: 'When document has visual size: height in pixels', - example: '800', - )] private int $imageHeight = 0; /** * @var integer @@ -310,10 +283,6 @@ class Document extends AbstractDateTimed implements AdvancedDocumentInterface, H #[SymfonySerializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Type('int')] - #[ApiProperty( - description: 'When document is audio or video: duration in seconds', - example: '300', - )] private int $mediaDuration = 0; /** * @var string|null @@ -323,10 +292,6 @@ class Document extends AbstractDateTimed implements AdvancedDocumentInterface, H #[Serializer\Groups(['document', 'document_display', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Type('string')] #[Assert\Length(max: 7)] - #[ApiProperty( - description: 'When document is image: average color in hexadecimal format', - example: '#ffffff' - )] private ?string $imageAverageColor = null; /** * @var int|null The filesize in bytes. @@ -654,10 +619,6 @@ public function setFilesize(?int $filesize): static Serializer\SerializedName("alt"), SymfonySerializer\Groups(["document", "document_display", "nodes_sources", "tag", "attribute"]), SymfonySerializer\SerializedName("alt"), - ApiProperty( - description: 'Document alternative text, for img HTML tag.', - writable: false, - ) ] public function getAlternativeText(): string { diff --git a/src/Entity/DocumentTranslation.php b/src/Entity/DocumentTranslation.php index e7ac1609..858d529a 100644 --- a/src/Entity/DocumentTranslation.php +++ b/src/Entity/DocumentTranslation.php @@ -43,16 +43,16 @@ class DocumentTranslation extends AbstractEntity implements Loggable protected ?string $externalUrl = null; #[ORM\ManyToOne(targetEntity: Translation::class, fetch: 'EXTRA_LAZY', inversedBy: 'documentTranslations')] - #[ORM\JoinColumn(name: 'translation_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'translation_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Groups(['document', 'nodes_sources', 'tag', 'attribute'])] #[Serializer\Groups(['document', 'nodes_sources', 'tag', 'attribute'])] - protected TranslationInterface $translation; + protected ?TranslationInterface $translation = null; #[ORM\ManyToOne(targetEntity: Document::class, fetch: 'EXTRA_LAZY', inversedBy: 'documentTranslations')] - #[ORM\JoinColumn(name: 'document_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'document_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Ignore] #[Serializer\Exclude] - protected DocumentInterface $document; + protected ?DocumentInterface $document; #[ORM\Column(type: 'text', nullable: true)] #[SymfonySerializer\Groups(['document', 'nodes_sources', 'tag', 'attribute'])] @@ -79,11 +79,19 @@ public function setName(?string $name): DocumentTranslation return $this; } + /** + * @return string + */ public function getDescription(): ?string { return $this->description; } + /** + * @param string|null $description + * + * @return $this + */ public function setDescription(?string $description): DocumentTranslation { $this->description = $description; @@ -159,7 +167,7 @@ public function getDocument(): DocumentInterface * @param DocumentInterface $document * @return $this */ - public function setDocument(DocumentInterface $document): DocumentTranslation + public function setDocument(DocumentInterface $document) { $this->document = $document; return $this; diff --git a/src/Entity/FieldAwareEntityTrait.php b/src/Entity/FieldAwareEntityTrait.php deleted file mode 100644 index be0611eb..00000000 --- a/src/Entity/FieldAwareEntityTrait.php +++ /dev/null @@ -1,44 +0,0 @@ -fieldName; - } - - public function setFieldName(string $fieldName): self - { - $this->fieldName = $fieldName; - return $this; - } - - /** - * @deprecated Use setFieldName method instead - */ - public function setField(NodeTypeFieldInterface $field): self - { - $this->fieldName = $field->getName(); - return $this; - } - - protected function initializeFieldAwareEntityTrait(?NodeTypeFieldInterface $nodeTypeField = null): void - { - if (null === $nodeTypeField) { - return; - } - $this->fieldName = $nodeTypeField->getName(); - } -} diff --git a/src/Entity/Folder.php b/src/Entity/Folder.php index bcb5834d..abb50bd2 100644 --- a/src/Entity/Folder.php +++ b/src/Entity/Folder.php @@ -5,13 +5,14 @@ namespace RZ\Roadiz\CoreBundle\Entity; use ApiPlatform\Doctrine\Orm\Filter as BaseFilter; -use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Serializer\Filter\PropertyFilter; +use ApiPlatform\Metadata\ApiFilter; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; use JMS\Serializer\Annotation as Serializer; +use RZ\Roadiz\Core\AbstractEntities\AbstractDateTimedPositioned; use RZ\Roadiz\Core\AbstractEntities\LeafInterface; use RZ\Roadiz\Core\AbstractEntities\LeafTrait; use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; diff --git a/src/Entity/FolderTranslation.php b/src/Entity/FolderTranslation.php index 76a78b84..b827d27e 100644 --- a/src/Entity/FolderTranslation.php +++ b/src/Entity/FolderTranslation.php @@ -33,17 +33,21 @@ class FolderTranslation extends AbstractEntity protected string $name = ''; #[ORM\ManyToOne(targetEntity: Folder::class, inversedBy: 'translatedFolders')] - #[ORM\JoinColumn(name: 'folder_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'folder_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Ignore] #[Serializer\Exclude] - protected Folder $folder; + protected ?Folder $folder = null; #[ORM\ManyToOne(targetEntity: Translation::class, fetch: 'EXTRA_LAZY', inversedBy: 'folderTranslations')] - #[ORM\JoinColumn(name: 'translation_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'translation_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Groups(['folder', 'document'])] #[Serializer\Groups(['folder', 'document'])] - protected TranslationInterface $translation; + protected ?TranslationInterface $translation = null; + /** + * @param Folder $original + * @param TranslationInterface $translation + */ public function __construct(Folder $original, TranslationInterface $translation) { $this->setFolder($original); @@ -63,7 +67,7 @@ public function getName(): string * @param string $name * @return $this */ - public function setName(string $name): FolderTranslation + public function setName(string $name) { $this->name = $name; return $this; diff --git a/src/Entity/Node.php b/src/Entity/Node.php index 18b73384..d47d4fd5 100644 --- a/src/Entity/Node.php +++ b/src/Entity/Node.php @@ -5,9 +5,8 @@ namespace RZ\Roadiz\CoreBundle\Entity; use ApiPlatform\Doctrine\Orm\Filter as BaseFilter; -use ApiPlatform\Metadata\ApiFilter; -use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Serializer\Filter\PropertyFilter; +use ApiPlatform\Metadata\ApiFilter; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; @@ -15,8 +14,8 @@ use Gedmo\Loggable\Loggable; use Gedmo\Mapping\Annotation as Gedmo; use JMS\Serializer\Annotation as Serializer; -use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\Contracts\NodeType\NodeTypeInterface; +use RZ\Roadiz\Core\AbstractEntities\AbstractDateTimedPositioned; use RZ\Roadiz\Core\AbstractEntities\LeafInterface; use RZ\Roadiz\Core\AbstractEntities\LeafTrait; use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; @@ -45,7 +44,6 @@ ORM\Index(columns: ["created_at"]), ORM\Index(columns: ["updated_at"]), ORM\Index(columns: ["hide_children"]), - ORM\Index(columns: ["home"]), ORM\Index(columns: ["node_name", "status"]), ORM\Index(columns: ["visible", "status"]), ORM\Index(columns: ["visible", "status", "parent_node_id"], name: "node_visible_status_parent"), @@ -53,9 +51,9 @@ ORM\Index(columns: ["nodeType_id", "status", "parent_node_id"], name: "node_nodetype_status_parent"), ORM\Index(columns: ["nodeType_id", "status", "parent_node_id", "position"], name: "node_nodetype_status_parent_position"), ORM\Index(columns: ["visible", "parent_node_id"], name: "node_visible_parent"), - ORM\Index(columns: ["parent_node_id", "position"], name: "node_parent_position"), ORM\Index(columns: ["visible", "parent_node_id", "position"], name: "node_visible_parent_position"), ORM\Index(columns: ["status", "visible", "parent_node_id", "position"], name: "node_status_visible_parent_position"), + ORM\Index(columns: ["home"]), ORM\HasLifecycleCallbacks, Gedmo\Loggable(logEntryClass: UserLogEntry::class), // Need to override repository method to see all nodes @@ -94,10 +92,6 @@ class Node extends AbstractDateTimedPositioned implements LeafInterface, Attribu #[Assert\NotNull] #[Assert\NotBlank] #[Assert\Length(max: 255)] - #[ApiProperty( - description: 'Unique node name (slug) used to build content URL', - example: 'this-is-a-node-name', - )] private string $nodeName = ''; #[ORM\Column(name: 'dynamic_node_name', type: 'boolean', nullable: false, options: ['default' => true])] @@ -113,10 +107,6 @@ class Node extends AbstractDateTimedPositioned implements LeafInterface, Attribu #[SymfonySerializer\Groups(['nodes_sources_base', 'nodes_sources', 'node'])] #[Serializer\Groups(['nodes_sources_base', 'nodes_sources', 'node'])] #[Gedmo\Versioned] - #[ApiProperty( - description: 'Is this node visible in website navigation?', - example: 'true', - )] private bool $visible = true; /** @@ -129,41 +119,36 @@ class Node extends AbstractDateTimedPositioned implements LeafInterface, Attribu #[ORM\Column(type: 'integer', nullable: false, options: ['default' => 0])] #[Assert\GreaterThanOrEqual(value: 0)] - #[Assert\NotNull] #[SymfonySerializer\Ignore] #[Serializer\Exclude] #[Gedmo\Versioned] - // @phpstan-ignore-next-line - private ?int $ttl = 0; + private int $ttl = 0; #[ORM\Column(type: 'boolean', nullable: false, options: ['default' => false])] #[SymfonySerializer\Groups(['node'])] #[Serializer\Groups(['node'])] #[Gedmo\Versioned] - #[ApiProperty( - description: 'Is this node locked to prevent deletion and renaming?', - example: 'false', - )] private bool $locked = false; + /** + * @var float|string|int + */ + #[ORM\Column(type: 'decimal', precision: 2, scale: 1)] + #[SymfonySerializer\Groups(['node'])] + #[Serializer\Groups(['node'])] + #[Gedmo\Versioned] + private string|float|int $priority = 0.8; + #[ORM\Column(name: 'hide_children', type: 'boolean', nullable: false, options: ['default' => false])] #[SymfonySerializer\Groups(['node'])] #[Serializer\Groups(['node'])] #[Gedmo\Versioned] - #[ApiProperty( - description: 'Does this node act as a container for other nodes?', - example: 'false', - )] private bool $hideChildren = false; #[ORM\Column(type: 'boolean', nullable: false, options: ['default' => false])] #[SymfonySerializer\Groups(['node'])] #[Serializer\Groups(['node'])] #[Gedmo\Versioned] - #[ApiProperty( - description: 'Can this node hold other nodes inside?', - example: 'false', - )] private bool $sterile = false; #[ORM\Column(name: 'children_order', type: 'string', length: 50)] @@ -171,40 +156,24 @@ class Node extends AbstractDateTimedPositioned implements LeafInterface, Attribu #[Serializer\Groups(['node', 'node_listing'])] #[Assert\Length(max: 50)] #[Gedmo\Versioned] - #[ApiProperty( - description: 'This node children will be sorted by a given field', - example: 'position', - schema: [ - 'type' => 'string', - 'enum' => ['position', 'nodeName', 'createdAt', 'updatedAt', 'publishedAt'], - 'example' => 'position' - ], - )] private string $childrenOrder = 'position'; #[ORM\Column(name: 'children_order_direction', type: 'string', length: 4)] #[SymfonySerializer\Groups(['node', 'node_listing'])] #[Serializer\Groups(['node', 'node_listing'])] #[Assert\Length(max: 4)] - #[Assert\Choice(choices: ['ASC', 'DESC'])] #[Gedmo\Versioned] - #[ApiProperty( - description: 'This node children will be sorted ascendant or descendant', - example: 'ASC', - schema: [ - 'type' => 'string', - 'enum' => ['ASC', 'DESC'], - 'example' => 'ASC' - ], - )] private string $childrenOrderDirection = 'ASC'; + /** + * @var NodeTypeInterface|null + */ #[ORM\ManyToOne(targetEntity: NodeTypeInterface::class)] - #[ORM\JoinColumn(name: 'nodeType_id', referencedColumnName: 'id', nullable:false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'nodeType_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Groups(['node'])] #[Serializer\Groups(['node'])] #[SymfonySerializer\Ignore] - private NodeTypeInterface $nodeType; + private ?NodeTypeInterface $nodeType = null; /** * @var Node|null @@ -319,7 +288,7 @@ class Node extends AbstractDateTimedPositioned implements LeafInterface, Attribu /** * Create a new empty Node according to given node-type. */ - public function __construct() + public function __construct(NodeTypeInterface $nodeType = null) { $this->nodesTags = new ArrayCollection(); $this->children = new ArrayCollection(); @@ -329,6 +298,8 @@ public function __construct() $this->aNodes = new ArrayCollection(); $this->bNodes = new ArrayCollection(); $this->attributeValues = new ArrayCollection(); + + $this->setNodeType($nodeType); $this->initAbstractDateTimed(); } @@ -419,15 +390,15 @@ public function setStatus(int|string $status): Node */ public function getTtl(): int { - return $this->ttl ?? 0; + return $this->ttl; } /** - * @param int|null $ttl + * @param int $ttl * * @return Node */ - public function setTtl(?int $ttl): Node + public function setTtl(int $ttl): Node { $this->ttl = $ttl; return $this; @@ -483,6 +454,24 @@ public function setLocked(bool $locked): static return $this; } + /** + * @return float|string + */ + public function getPriority() + { + return $this->priority; + } + + /** + * @param float|string $priority + * @return $this + */ + public function setPriority($priority): static + { + $this->priority = $priority; + return $this; + } + /** * @return bool */ @@ -796,10 +785,10 @@ public function addNodeSources(NodesSources $ns): static * @return Collection */ #[SymfonySerializer\Ignore] - public function getBNodesByField(NodeTypeFieldInterface $field): Collection + public function getBNodesByField(NodeTypeField $field): Collection { $criteria = Criteria::create(); - $criteria->andWhere(Criteria::expr()->eq('fieldName', $field->getName())); + $criteria->andWhere(Criteria::expr()->eq('field', $field)); $criteria->orderBy(['position' => 'ASC']); return $this->getBNodes()->matching($criteria); } @@ -820,6 +809,9 @@ public function getBNodes(): Collection */ public function setBNodes(Collection $bNodes): static { + foreach ($this->bNodes as $bNode) { + $bNode->setNodeA(null); + } $this->bNodes->clear(); foreach ($bNodes as $bNode) { if (!$this->hasBNode($bNode)) { @@ -834,7 +826,7 @@ public function hasBNode(NodesToNodes $bNode): bool return $this->getBNodes()->exists(function ($key, NodesToNodes $element) use ($bNode) { return $bNode->getNodeB()->getId() !== null && $element->getNodeB()->getId() === $bNode->getNodeB()->getId() && - $element->getFieldName() === $bNode->getFieldName(); + $element->getField()->getId() === $bNode->getField()->getId(); }); } @@ -851,14 +843,15 @@ public function addBNode(NodesToNodes $bNode): static return $this; } - public function clearBNodesForField(NodeTypeFieldInterface $field): Node + public function clearBNodesForField(NodeTypeField $nodeTypeField): Node { - $toRemoveCollection = $this->getBNodes()->filter(function (NodesToNodes $element) use ($field) { - return $element->getFieldName() === $field->getName(); + $toRemoveCollection = $this->getBNodes()->filter(function (NodesToNodes $element) use ($nodeTypeField) { + return $element->getField()->getId() === $nodeTypeField->getId(); }); /** @var NodesToNodes $toRemove */ foreach ($toRemoveCollection as $toRemove) { $this->getBNodes()->removeElement($toRemove); + $toRemove->setNodeA(null); } return $this; } @@ -901,12 +894,19 @@ public function setNodeName(string $nodeName): static return $this; } - public function getNodeType(): NodeTypeInterface + /** + * @return NodeTypeInterface|null + */ + public function getNodeType(): ?NodeTypeInterface { return $this->nodeType; } - public function setNodeType(NodeTypeInterface $nodeType): Node + /** + * @param NodeTypeInterface|null $nodeType + * @return $this + */ + public function setNodeType(?NodeTypeInterface $nodeType = null): static { $this->nodeType = $nodeType; return $this; diff --git a/src/Entity/NodeType.php b/src/Entity/NodeType.php index 53ab01ff..8e0375b4 100644 --- a/src/Entity/NodeType.php +++ b/src/Entity/NodeType.php @@ -139,12 +139,7 @@ class NodeType extends AbstractEntity implements NodeTypeInterface * @var Collection */ #[ - ORM\OneToMany( - mappedBy: "nodeType", - targetEntity: NodeTypeField::class, - cascade: ["all"], - orphanRemoval: true - ), + ORM\OneToMany(mappedBy: "nodeType", targetEntity: NodeTypeField::class, cascade: ["all"]), ORM\OrderBy(["position" => "ASC"]), Serializer\Groups(["node_type"]), SymfonySerializer\Groups(["node_type"]), @@ -157,11 +152,9 @@ class NodeType extends AbstractEntity implements NodeTypeInterface Serializer\Groups(["node_type"]), SymfonySerializer\Groups(["node_type"]), Serializer\Type("int"), - Assert\GreaterThanOrEqual(value: 0), - Assert\NotNull + Assert\GreaterThanOrEqual(value: 0) ] - // @phpstan-ignore-next-line - private ?int $defaultTtl = 0; + private int $defaultTtl = 0; /** * Define if this node-type title will be indexed during its parent indexation. */ @@ -354,15 +347,15 @@ public function setColor(?string $color): NodeType */ public function getDefaultTtl(): int { - return $this->defaultTtl ?? 0; + return $this->defaultTtl; } /** - * @param int|null $defaultTtl + * @param int $defaultTtl * * @return NodeType */ - public function setDefaultTtl(?int $defaultTtl): NodeType + public function setDefaultTtl(int $defaultTtl): NodeType { $this->defaultTtl = $defaultTtl; diff --git a/src/Entity/NodeTypeField.php b/src/Entity/NodeTypeField.php index 07dd18bf..34d6fed3 100644 --- a/src/Entity/NodeTypeField.php +++ b/src/Entity/NodeTypeField.php @@ -29,7 +29,6 @@ ORM\Index(columns: ["group_name"]), ORM\Index(columns: ["group_name_canonical"]), ORM\Index(columns: ["type"]), - ORM\Index(columns: ["name"], name: 'ntf_name'), ORM\Index(columns: ["universal"]), ORM\Index(columns: ["node_type_id", "position"], name: "ntf_type_position"), ORM\UniqueConstraint(columns: ["name", "node_type_id"]), @@ -40,11 +39,11 @@ class NodeTypeField extends AbstractField implements NodeTypeFieldInterface, SerializableInterface { #[ - ORM\Column(type: "string", length: 50), + ORM\Column(type: "string", length: 250), Serializer\Expose, Serializer\Groups(["node_type", "setting"]), SymfonySerializer\Groups(["node_type", "setting"]), - Assert\Length(max: 50), + Assert\Length(max: 250), Serializer\Type("string"), RoadizAssert\NonSqlReservedWord(), RoadizAssert\SimpleLatinString() @@ -75,11 +74,11 @@ class NodeTypeField extends AbstractField implements NodeTypeFieldInterface, Ser #[ ORM\ManyToOne(targetEntity: NodeType::class, inversedBy: "fields"), - ORM\JoinColumn(name: "node_type_id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(name: "node_type_id", onDelete: "CASCADE"), Serializer\Exclude(), SymfonySerializer\Ignore ] - private NodeTypeInterface $nodeType; + private ?NodeTypeInterface $nodeType = null; #[ Serializer\Groups(["node_type"]), @@ -146,32 +145,53 @@ class NodeTypeField extends AbstractField implements NodeTypeFieldInterface, Ser private bool $visible = true; #[ + Serializer\VirtualProperty(), + Serializer\Type("string"), + Serializer\Groups(["node_type"]), SymfonySerializer\Groups(["node_type"]) ] public function getNodeTypeName(): string { - return $this->getNodeType()->getName(); + return $this->getNodeType() ? $this->getNodeType()->getName() : ''; } - public function getNodeType(): NodeTypeInterface + /** + * @return NodeTypeInterface|null + */ + public function getNodeType(): ?NodeTypeInterface { return $this->nodeType; } - public function setNodeType(NodeTypeInterface $nodeType): NodeTypeField + /** + * @param NodeTypeInterface|null $nodeType + * + * @return $this + */ + public function setNodeType(?NodeTypeInterface $nodeType) { $this->nodeType = $nodeType; + return $this; } + /** + * @return int|null + */ public function getMinLength(): ?int { return $this->minLength; } - public function setMinLength(?int $minLength): NodeTypeField + /** + * @param int|null $minLength + * + * @return $this + */ + public function setMinLength(?int $minLength) { $this->minLength = $minLength; + return $this; } @@ -183,9 +203,15 @@ public function getMaxLength(): ?int return $this->maxLength; } - public function setMaxLength(?int $maxLength): NodeTypeField + /** + * @param int|null $maxLength + * + * @return $this + */ + public function setMaxLength(?int $maxLength) { $this->maxLength = $maxLength; + return $this; } @@ -203,7 +229,7 @@ public function isSearchable(): bool * @return string */ #[SymfonySerializer\Ignore] - public function getOneLineSummary(): string + public function getOneLineSummary() { return $this->getId() . " — " . $this->getLabel() . ' [' . $this->getName() . ']' . ' - ' . $this->getTypeName() . @@ -220,7 +246,11 @@ public function isIndexed(): bool return $this->indexed && $this->getDoctrineType() !== 'json'; } - public function setIndexed(bool $indexed): NodeTypeField + /** + * @param bool $indexed + * @return $this + */ + public function setIndexed(bool $indexed) { $this->indexed = $indexed; return $this; @@ -234,9 +264,14 @@ public function isVisible(): bool return $this->visible; } - public function setVisible(bool $visible): NodeTypeField + /** + * @param bool $visible + * @return $this + */ + public function setVisible(bool $visible) { $this->visible = $visible; + return $this; } @@ -261,7 +296,7 @@ public function getUniversal(): bool * @param bool $universal * @return NodeTypeField */ - public function setUniversal(bool $universal): NodeTypeField + public function setUniversal(bool $universal) { $this->universal = $universal; return $this; @@ -283,18 +318,29 @@ public function getExcludeFromSearch(): bool return $this->excludeFromSearch; } + /** + * @return bool + */ public function isExcludeFromSearch(): bool { return $this->getExcludeFromSearch(); } - public function setExcludeFromSearch(bool $excludeFromSearch): NodeTypeField + /** + * @param bool $excludeFromSearch + * + * @return NodeTypeField + */ + public function setExcludeFromSearch(bool $excludeFromSearch) { $this->excludeFromSearch = $excludeFromSearch; return $this; } + /** + * @return string|null + */ public function getSerializationExclusionExpression(): ?string { return $this->serializationExclusionExpression; diff --git a/src/Entity/NodesCustomForms.php b/src/Entity/NodesCustomForms.php index 8f7d4a39..97335583 100644 --- a/src/Entity/NodesCustomForms.php +++ b/src/Entity/NodesCustomForms.php @@ -18,50 +18,62 @@ ORM\Table(name: "nodes_custom_forms"), ORM\Index(columns: ["position"]), ORM\Index(columns: ["node_id", "position"], name: "customform_node_position"), - ORM\Index(columns: ["node_id", "field_name", "position"], name: "customform_node_field_position") + ORM\Index(columns: ["node_id", "node_type_field_id", "position"], name: "customform_node_field_position") ] class NodesCustomForms extends AbstractPositioned { - use FieldAwareEntityTrait; - + /** + * @var Node|null + */ #[ORM\ManyToOne(targetEntity: Node::class, fetch: 'EAGER', inversedBy: 'customForms')] - #[ORM\JoinColumn(name: 'node_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] - protected Node $node; + #[ORM\JoinColumn(name: 'node_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + protected ?Node $node = null; + /** + * @var CustomForm|null + */ #[ORM\ManyToOne(targetEntity: CustomForm::class, fetch: 'EAGER', inversedBy: 'nodes')] - #[ORM\JoinColumn(name: 'custom_form_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] - protected CustomForm $customForm; + #[ORM\JoinColumn(name: 'custom_form_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + protected ?CustomForm $customForm = null; + + /** + * @var NodeTypeField|null + */ + #[ORM\ManyToOne(targetEntity: NodeTypeField::class)] + #[ORM\JoinColumn(name: 'node_type_field_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + protected ?NodeTypeField $field = null; /** * Create a new relation between a Node, a CustomForm and a NodeTypeField. * - * @param Node $node - * @param CustomForm $customForm - * @param NodeTypeFieldInterface|null $field NodeTypeField + * @param Node $node + * @param CustomForm $customForm + * @param NodeTypeFieldInterface $field NodeTypeField */ - public function __construct(Node $node, CustomForm $customForm, ?NodeTypeFieldInterface $field = null) + public function __construct(Node $node, CustomForm $customForm, NodeTypeFieldInterface $field) { if (!$field instanceof NodeTypeField) { throw new \InvalidArgumentException('NodesCustomForms only accept NodeTypeField'); } $this->node = $node; $this->customForm = $customForm; - $this->initializeFieldAwareEntityTrait($field); + $this->field = $field; } public function __clone() { if ($this->id) { $this->id = null; + $this->node = null; } } /** * Gets the value of node. * - * @return Node + * @return Node|null */ - public function getNode(): Node + public function getNode(): ?Node { return $this->node; } @@ -99,6 +111,31 @@ public function getCustomForm(): CustomForm public function setCustomForm(CustomForm $customForm): NodesCustomForms { $this->customForm = $customForm; + + return $this; + } + + /** + * Gets the value of field. + * + * @return NodeTypeField + */ + public function getField(): NodeTypeField + { + return $this->field; + } + + /** + * Sets the value of field. + * + * @param NodeTypeField $field the field + * + * @return self + */ + public function setField(NodeTypeField $field): NodesCustomForms + { + $this->field = $field; + return $this; } } diff --git a/src/Entity/NodesSources.php b/src/Entity/NodesSources.php index 8ebc470e..ca91b957 100644 --- a/src/Entity/NodesSources.php +++ b/src/Entity/NodesSources.php @@ -5,9 +5,8 @@ namespace RZ\Roadiz\CoreBundle\Entity; use ApiPlatform\Doctrine\Orm\Filter as BaseFilter; -use ApiPlatform\Metadata\ApiFilter; -use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Serializer\Filter\PropertyFilter; +use ApiPlatform\Metadata\ApiFilter; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; @@ -16,7 +15,7 @@ use Gedmo\Loggable\Loggable; use Gedmo\Mapping\Annotation as Gedmo; use JMS\Serializer\Annotation as Serializer; -use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; +use RuntimeException; use RZ\Roadiz\Core\AbstractEntities\AbstractEntity; use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; use RZ\Roadiz\CoreBundle\Api\Filter as RoadizFilter; @@ -74,10 +73,6 @@ class NodesSources extends AbstractEntity implements Loggable #[Serializer\Groups(['nodes_sources', 'nodes_sources_base', 'log_sources'])] #[Gedmo\Versioned] #[Assert\Length(max: 250)] - #[ApiProperty( - description: 'Content title', - example: 'This is a title', - )] protected ?string $title = null; #[ApiFilter(BaseFilter\DateFilter::class)] @@ -87,9 +82,6 @@ class NodesSources extends AbstractEntity implements Loggable #[SymfonySerializer\Groups(['nodes_sources', 'nodes_sources_base'])] #[Serializer\Groups(['nodes_sources', 'nodes_sources_base'])] #[Gedmo\Versioned] - #[ApiProperty( - description: 'Content publication date and time', - )] protected ?\DateTime $publishedAt = null; #[ApiFilter(BaseFilter\SearchFilter::class, strategy: "partial")] @@ -98,21 +90,19 @@ class NodesSources extends AbstractEntity implements Loggable #[Serializer\Groups(['nodes_sources'])] #[Gedmo\Versioned] #[Assert\Length(max: 150)] - #[ApiProperty( - description: 'Title for search engine optimization, used in HTML title tag', - example: 'This is a title', - )] protected string $metaTitle = ''; + #[ORM\Column(name: 'meta_keywords', type: 'text')] + #[SymfonySerializer\Groups(['nodes_sources'])] + #[Serializer\Groups(['nodes_sources'])] + #[Gedmo\Versioned] + protected string $metaKeywords = ''; + #[ApiFilter(BaseFilter\SearchFilter::class, strategy: "partial")] #[ORM\Column(name: 'meta_description', type: 'text')] #[SymfonySerializer\Groups(['nodes_sources'])] #[Serializer\Groups(['nodes_sources'])] #[Gedmo\Versioned] - #[ApiProperty( - description: 'Description for search engine optimization, used in HTML meta description tag', - example: 'This is a description', - )] protected string $metaDescription = ''; #[ApiFilter(BaseFilter\BooleanFilter::class)] @@ -120,10 +110,6 @@ class NodesSources extends AbstractEntity implements Loggable #[SymfonySerializer\Groups(['nodes_sources'])] #[Serializer\Groups(['nodes_sources'])] #[Gedmo\Versioned] - #[ApiProperty( - description: 'Do not allow robots to index this content, used in HTML meta robots tag', - example: 'false', - )] protected bool $noIndex = false; #[ApiFilter(BaseFilter\SearchFilter::class, properties: [ @@ -168,23 +154,21 @@ class NodesSources extends AbstractEntity implements Loggable "node.nodesTags.tag.tagName", ])] #[ORM\ManyToOne(targetEntity: Node::class, cascade: ['persist'], fetch: 'EAGER', inversedBy: 'nodeSources')] - #[ORM\JoinColumn(name: 'node_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'node_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Groups(['nodes_sources', 'nodes_sources_base', 'log_sources'])] #[Serializer\Groups(['nodes_sources', 'nodes_sources_base', 'log_sources'])] #[Assert\Valid] - #[Assert\NotNull] - private Node $node; + private ?Node $node = null; #[ApiFilter(BaseFilter\SearchFilter::class, properties: [ "translation.id" => "exact", "translation.locale" => "exact", ])] #[ORM\ManyToOne(targetEntity: Translation::class, inversedBy: 'nodeSources')] - #[ORM\JoinColumn(name: 'translation_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'translation_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Groups(['translation_base'])] #[Serializer\Groups(['translation_base'])] - #[Assert\NotNull] - private TranslationInterface $translation; + private ?TranslationInterface $translation = null; /** * @var Collection @@ -238,11 +222,22 @@ public function preUpdate(): void $this->getNode()->setUpdatedAt(new \DateTime("now")); } + /** + * @return Node + */ public function getNode(): Node { + if (null === $this->node) { + throw new \BadMethodCallException('NodeSource node should never be null.'); + } return $this->node; } + /** + * @param Node $node + * + * @return $this + */ public function setNode(Node $node): NodesSources { $this->node = $node; @@ -265,16 +260,17 @@ public function addUrlAlias(UrlAlias $urlAlias): NodesSources return $this; } - public function clearDocumentsByFields(NodeTypeFieldInterface $field): NodesSources + public function clearDocumentsByFields(NodeTypeField $nodeTypeField): NodesSources { $toRemoveCollection = $this->getDocumentsByFields()->filter( - function (NodesSourcesDocuments $element) use ($field) { - return $element->getFieldName() === $field->getName(); + function (NodesSourcesDocuments $element) use ($nodeTypeField) { + return $element->getField()->getId() === $nodeTypeField->getId(); } ); /** @var NodesSourcesDocuments $toRemove */ foreach ($toRemoveCollection as $toRemove) { $this->getDocumentsByFields()->removeElement($toRemove); + $toRemove->setNodeSource(null); } return $this; @@ -298,8 +294,7 @@ public function getOneDisplayableDocument(): ?DocumentInterface { return $this->getDocumentsByFields()->filter(function (NodesSourcesDocuments $nsd) { return null !== $nsd->getDocument() && - !$nsd->getDocument()->isPrivate() && - ($nsd->getDocument()->isImage() || $nsd->getDocument()->isSvg()) && + $nsd->getDocument()->isImage() && $nsd->getDocument()->isProcessable(); })->map(function (NodesSourcesDocuments $nsd) { return $nsd->getDocument(); @@ -313,6 +308,9 @@ public function getOneDisplayableDocument(): ?DocumentInterface */ public function setDocumentsByFields(Collection $documentsByFields): NodesSources { + foreach ($this->documentsByFields as $documentsByField) { + $documentsByField->setNodeSource(null); + } $this->documentsByFields->clear(); foreach ($documentsByFields as $documentsByField) { if (!$this->hasNodesSourcesDocuments($documentsByField)) { @@ -334,7 +332,7 @@ public function hasNodesSourcesDocuments(NodesSourcesDocuments $nodesSourcesDocu function ($key, NodesSourcesDocuments $element) use ($nodesSourcesDocuments) { return $nodesSourcesDocuments->getDocument()->getId() !== null && $element->getDocument()->getId() === $nodesSourcesDocuments->getDocument()->getId() && - $element->getFieldName() === $nodesSourcesDocuments->getFieldName(); + $element->getField()->getId() === $nodesSourcesDocuments->getField()->getId(); } ); } @@ -360,14 +358,14 @@ public function addDocumentsByFields(NodesSourcesDocuments $nodesSourcesDocument * * @return Document[] */ - public function getDocumentsByFieldsWithField(NodeTypeFieldInterface $field): array + public function getDocumentsByFieldsWithField(NodeTypeField $field): array { $criteria = Criteria::create(); $criteria->orderBy(['position' => 'ASC']); return $this->getDocumentsByFields() ->matching($criteria) ->filter(function (NodesSourcesDocuments $element) use ($field) { - return $element->getFieldName() === $field->getName(); + return $element->getField() === $field; }) ->map(function (NodesSourcesDocuments $nodesSourcesDocuments) { return $nodesSourcesDocuments->getDocument(); @@ -387,7 +385,7 @@ public function getDocumentsByFieldsWithName(string $fieldName): array return $this->getDocumentsByFields() ->matching($criteria) ->filter(function (NodesSourcesDocuments $element) use ($fieldName) { - return $element->getFieldName() === $fieldName; + return $element->getField()->getName() === $fieldName; }) ->map(function (NodesSourcesDocuments $nodesSourcesDocuments) { return $nodesSourcesDocuments->getDocument(); @@ -452,6 +450,26 @@ public function setMetaTitle(?string $metaTitle): NodesSources return $this; } + /** + * @return string + */ + public function getMetaKeywords(): string + { + return $this->metaKeywords; + } + + /** + * @param string|null $metaKeywords + * + * @return $this + */ + public function setMetaKeywords(?string $metaKeywords): NodesSources + { + $this->metaKeywords = null !== $metaKeywords ? trim($metaKeywords) : ''; + + return $this; + } + /** * @return string */ @@ -567,6 +585,9 @@ public function setTitle(?string $title): NodesSources */ public function getTranslation(): TranslationInterface { + if (null === $this->translation) { + throw new RuntimeException('Node source translation cannot be null.'); + } return $this->translation; } diff --git a/src/Entity/NodesSourcesDocuments.php b/src/Entity/NodesSourcesDocuments.php index 6842bba9..4e3fadc3 100644 --- a/src/Entity/NodesSourcesDocuments.php +++ b/src/Entity/NodesSourcesDocuments.php @@ -8,7 +8,6 @@ use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\Core\AbstractEntities\AbstractPositioned; use RZ\Roadiz\CoreBundle\Repository\NodesSourcesDocumentsRepository; -use Symfony\Component\Validator\Constraints as Assert; /** * Describes a complex ManyToMany relation @@ -18,15 +17,13 @@ ORM\Entity(repositoryClass: NodesSourcesDocumentsRepository::class), ORM\Table(name: "nodes_sources_documents"), ORM\Index(columns: ["position"]), - ORM\Index(columns: ["ns_id", "field_name"], name: "nsdoc_field"), - ORM\Index(columns: ["ns_id", "field_name", "position"], name: "nsdoc_field_position") + ORM\Index(columns: ["ns_id", "node_type_field_id"], name: "nsdoc_field"), + ORM\Index(columns: ["ns_id", "node_type_field_id", "position"], name: "nsdoc_field_position") ] class NodesSourcesDocuments extends AbstractPositioned { - use FieldAwareEntityTrait; - /** - * @var NodesSources + * @var NodesSources|null */ #[ORM\ManyToOne( targetEntity: NodesSources::class, @@ -34,12 +31,11 @@ class NodesSourcesDocuments extends AbstractPositioned fetch: 'EAGER', inversedBy: 'documentsByFields' )] - #[Assert\NotNull] - #[ORM\JoinColumn(name: 'ns_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] - protected NodesSources $nodeSource; + #[ORM\JoinColumn(name: 'ns_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + protected ?NodesSources $nodeSource; /** - * @var Document + * @var Document|null */ #[ORM\ManyToOne( targetEntity: Document::class, @@ -47,40 +43,47 @@ class NodesSourcesDocuments extends AbstractPositioned fetch: 'EAGER', inversedBy: 'nodesSourcesByFields' )] - #[Assert\NotNull] - #[ORM\JoinColumn(name: 'document_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] - protected Document $document; + #[ORM\JoinColumn(name: 'document_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + protected ?Document $document; + + /** + * @var NodeTypeField|null + */ + #[ORM\ManyToOne(targetEntity: NodeTypeField::class)] + #[ORM\JoinColumn(name: 'node_type_field_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + protected ?NodeTypeField $field; /** * Create a new relation between NodeSource, a Document and a NodeTypeField. * * @param NodesSources $nodeSource NodesSources and inherited types - * @param Document $document Document to link - * @param NodeTypeFieldInterface|null $field NodeTypeField + * @param Document $document Document to link + * @param NodeTypeFieldInterface $field NodeTypeField */ - public function __construct(NodesSources $nodeSource, Document $document, ?NodeTypeFieldInterface $field = null) + public function __construct(NodesSources $nodeSource, Document $document, NodeTypeFieldInterface $field) { if (!$field instanceof NodeTypeField) { throw new \InvalidArgumentException('NodesSourcesDocuments field must be a NodeTypeField instance.'); } $this->nodeSource = $nodeSource; $this->document = $document; - $this->initializeFieldAwareEntityTrait($field); + $this->field = $field; } public function __clone() { if ($this->id) { $this->id = null; + $this->nodeSource = null; } } /** * Gets the value of nodeSource. * - * @return NodesSources + * @return NodesSources|null */ - public function getNodeSource(): NodesSources + public function getNodeSource(): ?NodesSources { return $this->nodeSource; } @@ -88,11 +91,11 @@ public function getNodeSource(): NodesSources /** * Sets the value of nodeSource. * - * @param NodesSources $nodeSource the node source + * @param NodesSources|null $nodeSource the node source * * @return self */ - public function setNodeSource(NodesSources $nodeSource): NodesSourcesDocuments + public function setNodeSource(?NodesSources $nodeSource): NodesSourcesDocuments { $this->nodeSource = $nodeSource; @@ -102,9 +105,9 @@ public function setNodeSource(NodesSources $nodeSource): NodesSourcesDocuments /** * Gets the value of document. * - * @return Document + * @return Document|null */ - public function getDocument(): Document + public function getDocument(): ?Document { return $this->document; } @@ -112,14 +115,38 @@ public function getDocument(): Document /** * Sets the value of document. * - * @param Document $document the document + * @param Document|null $document the document * * @return self */ - public function setDocument(Document $document): NodesSourcesDocuments + public function setDocument(?Document $document): NodesSourcesDocuments { $this->document = $document; return $this; } + + /** + * Gets the value of field. + * + * @return NodeTypeField|null + */ + public function getField(): ?NodeTypeField + { + return $this->field; + } + + /** + * Sets the value of field. + * + * @param NodeTypeField|null $field the field + * + * @return self + */ + public function setField(?NodeTypeField $field): NodesSourcesDocuments + { + $this->field = $field; + + return $this; + } } diff --git a/src/Entity/NodesToNodes.php b/src/Entity/NodesToNodes.php index b80836c9..ebcb4b6c 100644 --- a/src/Entity/NodesToNodes.php +++ b/src/Entity/NodesToNodes.php @@ -5,7 +5,6 @@ namespace RZ\Roadiz\CoreBundle\Entity; use Doctrine\ORM\Mapping as ORM; -use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\Core\AbstractEntities\AbstractPositioned; use RZ\Roadiz\CoreBundle\Repository\NodesToNodesRepository; @@ -17,43 +16,62 @@ ORM\Entity(repositoryClass: NodesToNodesRepository::class), ORM\Table(name: "nodes_to_nodes"), ORM\Index(columns: ["position"]), - ORM\Index(columns: ["node_a_id", "field_name"], name: "node_a_field"), - ORM\Index(columns: ["node_a_id", "field_name", "position"], name: "node_a_field_position"), - ORM\Index(columns: ["node_b_id", "field_name"], name: "node_b_field"), - ORM\Index(columns: ["node_b_id", "field_name", "position"], name: "node_b_field_position") + ORM\Index(columns: ["node_a_id", "node_type_field_id"], name: "node_a_field"), + ORM\Index(columns: ["node_a_id", "node_type_field_id", "position"], name: "node_a_field_position"), + ORM\Index(columns: ["node_b_id", "node_type_field_id"], name: "node_b_field"), + ORM\Index(columns: ["node_b_id", "node_type_field_id", "position"], name: "node_b_field_position") ] class NodesToNodes extends AbstractPositioned { - use FieldAwareEntityTrait; - + /** + * @var Node|null + */ #[ORM\ManyToOne(targetEntity: Node::class, cascade: ['persist'], fetch: 'EAGER', inversedBy: 'bNodes')] - #[ORM\JoinColumn(name: 'node_a_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] - protected Node $nodeA; + #[ORM\JoinColumn(name: 'node_a_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + protected ?Node $nodeA; + /** + * @var Node|null + */ #[ORM\ManyToOne(targetEntity: Node::class, cascade: ['persist'], fetch: 'EAGER', inversedBy: 'aNodes')] - #[ORM\JoinColumn(name: 'node_b_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] - protected Node $nodeB; + #[ORM\JoinColumn(name: 'node_b_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + protected ?Node $nodeB; + + /** + * @var NodeTypeField|null + */ + #[ORM\ManyToOne(targetEntity: NodeTypeField::class)] + #[ORM\JoinColumn(name: 'node_type_field_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + protected ?NodeTypeField $field; - public function __construct(Node $nodeA, Node $nodeB, ?NodeTypeFieldInterface $field = null) + /** + * Create a new relation between two Nodes and a NodeTypeField. + * + * @param Node $nodeA + * @param Node $nodeB + * @param NodeTypeField $field NodeTypeField + */ + public function __construct(Node $nodeA, Node $nodeB, NodeTypeField $field) { $this->nodeA = $nodeA; $this->nodeB = $nodeB; - $this->initializeFieldAwareEntityTrait($field); + $this->field = $field; } public function __clone() { if ($this->id) { $this->id = null; + $this->nodeA = null; } } /** * Gets the value of nodeA. * - * @return Node + * @return Node|null */ - public function getNodeA(): Node + public function getNodeA(): ?Node { return $this->nodeA; } @@ -61,11 +79,11 @@ public function getNodeA(): Node /** * Sets the value of nodeA. * - * @param Node $nodeA the node + * @param Node|null $nodeA the node * * @return self */ - public function setNodeA(Node $nodeA): NodesToNodes + public function setNodeA(?Node $nodeA): NodesToNodes { $this->nodeA = $nodeA; @@ -75,9 +93,9 @@ public function setNodeA(Node $nodeA): NodesToNodes /** * Gets the value of nodeB. * - * @return Node + * @return Node|null */ - public function getNodeB(): Node + public function getNodeB(): ?Node { return $this->nodeB; } @@ -85,13 +103,38 @@ public function getNodeB(): Node /** * Sets the value of nodeB. * - * @param Node $nodeB the node + * @param Node|null $nodeB the node * * @return self */ - public function setNodeB(Node $nodeB): NodesToNodes + public function setNodeB(?Node $nodeB): NodesToNodes { $this->nodeB = $nodeB; + + return $this; + } + + /** + * Gets the value of field. + * + * @return NodeTypeField|null + */ + public function getField(): ?NodeTypeField + { + return $this->field; + } + + /** + * Sets the value of field. + * + * @param NodeTypeField|null $field the field + * + * @return self + */ + public function setField(?NodeTypeField $field): NodesToNodes + { + $this->field = $field; + return $this; } } diff --git a/src/Entity/Realm.php b/src/Entity/Realm.php index 9cf68e49..69ba3bce 100644 --- a/src/Entity/Realm.php +++ b/src/Entity/Realm.php @@ -150,7 +150,7 @@ public function setSerializationGroup(?string $serializationGroup): Realm { $this->serializationGroup = null !== $serializationGroup ? (new AsciiSlugger())->slug($serializationGroup, '_')->lower()->toString() : - null; + (new AsciiSlugger())->slug($this->getName(), '_')->lower()->toString(); return $this; } diff --git a/src/Entity/RealmNode.php b/src/Entity/RealmNode.php index 8007a309..07f262a6 100644 --- a/src/Entity/RealmNode.php +++ b/src/Entity/RealmNode.php @@ -42,12 +42,12 @@ class RealmNode extends AbstractEntity name: 'realm_id', referencedColumnName: 'id', unique: false, - nullable: false, + nullable: true, onDelete: 'CASCADE' )] #[SymfonySerializer\Ignore] #[Serializer\Exclude] - private Realm $realm; + private ?Realm $realm = null; #[ORM\Column(name: 'inheritance_type', type: 'string', length: 10, nullable: false)] #[SymfonySerializer\Ignore] @@ -74,18 +74,18 @@ public function setNode(Node $node): RealmNode } /** - * @return Realm + * @return Realm|null */ - public function getRealm(): Realm + public function getRealm(): ?Realm { return $this->realm; } /** - * @param Realm $realm + * @param Realm|null $realm * @return RealmNode */ - public function setRealm(Realm $realm): RealmNode + public function setRealm(?Realm $realm): RealmNode { $this->realm = $realm; return $this; diff --git a/src/Entity/Setting.php b/src/Entity/Setting.php index 2670a463..8b62f98e 100644 --- a/src/Entity/Setting.php +++ b/src/Entity/Setting.php @@ -71,11 +71,25 @@ class Setting extends AbstractEntity #[Serializer\Groups(['setting', 'nodes_sources'])] private ?string $value = null; + /** + * Holds clear setting value after value is decoded by postLoad Doctrine event. + * + * READ ONLY: Not persisted value to hold clear value if setting is encrypted. + */ + #[SymfonySerializer\Ignore] + #[Serializer\Exclude] + private ?string $clearValue = null; + #[ORM\Column(type: 'boolean', nullable: false, options: ['default' => true])] #[SymfonySerializer\Groups(['setting'])] #[Serializer\Groups(['setting'])] private bool $visible = true; + #[ORM\Column(type: 'boolean', nullable: false, options: ['default' => false])] + #[SymfonySerializer\Groups(['setting'])] + #[Serializer\Groups(['setting'])] + private bool $encrypted = false; + #[ORM\ManyToOne( targetEntity: SettingGroup::class, cascade: ['persist', 'merge'], @@ -118,7 +132,7 @@ public function getName(): string * * @return $this */ - public function setName(?string $name): self + public function setName(?string $name) { $this->name = trim(\mb_strtolower($name ?? '')); $this->name = (new UnicodeString($this->name)) @@ -158,38 +172,49 @@ public function getRawValue(): ?string } /** + * Getter for setting value OR clear value, if encrypted. + * * @return string|bool|\DateTime|int|null * @throws \Exception */ #[SymfonySerializer\Ignore] - public function getValue(): string|bool|\DateTime|int|null + public function getValue() { + if ($this->isEncrypted()) { + $value = $this->clearValue; + } else { + $value = $this->value; + } + if ($this->getType() == AbstractField::BOOLEAN_T) { - return (bool) $this->value; + return (bool) $value; } - if (null !== $this->value) { + if (null !== $value) { if ($this->getType() == AbstractField::DATETIME_T) { - return new \DateTime($this->value); + return new \DateTime($value); } if ($this->getType() == AbstractField::DOCUMENTS_T) { - return (int) $this->value; + return (int) $value; } } - return $this->value; + return $value; } /** - * @param mixed $value + * @param null|mixed $value * * @return $this */ - public function setValue(mixed $value): self + public function setValue($value) { if (null === $value) { $this->value = null; - } elseif ($value instanceof \DateTimeInterface) { + } elseif ( + ($this->getType() === AbstractField::DATETIME_T || $this->getType() === AbstractField::DATE_T) && + $value instanceof \DateTimeInterface + ) { $this->value = $value->format('c'); // $value is instance of \DateTime } else { $this->value = (string) $value; @@ -198,6 +223,26 @@ public function setValue(mixed $value): self return $this; } + /** + * @return bool + */ + public function isEncrypted(): bool + { + return $this->encrypted; + } + + /** + * @param bool $encrypted + * + * @return Setting + */ + public function setEncrypted(bool $encrypted): Setting + { + $this->encrypted = $encrypted; + + return $this; + } + /** * @return int */ @@ -211,13 +256,27 @@ public function getType(): int * * @return $this */ - public function setType(int $type): self + public function setType(int $type) { $this->type = $type; return $this; } + /** + * Holds clear setting value after value is decoded by postLoad Doctrine event. + * + * @param string|null $clearValue + * + * @return Setting + */ + public function setClearValue(?string $clearValue): Setting + { + $this->clearValue = $clearValue; + + return $this; + } + /** * @return boolean */ @@ -231,7 +290,7 @@ public function isVisible(): bool * * @return $this */ - public function setVisible(bool $visible): self + public function setVisible(bool $visible) { $this->visible = $visible; @@ -251,7 +310,7 @@ public function getSettingGroup(): ?SettingGroup * * @return $this */ - public function setSettingGroup(?SettingGroup $settingGroup): self + public function setSettingGroup(?SettingGroup $settingGroup) { $this->settingGroup = $settingGroup; @@ -271,7 +330,7 @@ public function getDefaultValues(): ?string * * @return Setting */ - public function setDefaultValues(?string $defaultValues): self + public function setDefaultValues(?string $defaultValues) { $this->defaultValues = $defaultValues; diff --git a/src/Entity/Tag.php b/src/Entity/Tag.php index fc4f8661..ebbf8da5 100644 --- a/src/Entity/Tag.php +++ b/src/Entity/Tag.php @@ -5,13 +5,13 @@ namespace RZ\Roadiz\CoreBundle\Entity; use ApiPlatform\Doctrine\Orm\Filter as BaseFilter; -use ApiPlatform\Metadata\ApiFilter; -use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Serializer\Filter\PropertyFilter; +use ApiPlatform\Metadata\ApiFilter; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use JMS\Serializer\Annotation as Serializer; +use RZ\Roadiz\Core\AbstractEntities\AbstractDateTimedPositioned; use RZ\Roadiz\Core\AbstractEntities\LeafInterface; use RZ\Roadiz\Core\AbstractEntities\LeafTrait; use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; @@ -61,10 +61,6 @@ class Tag extends AbstractDateTimedPositioned implements LeafInterface #[SymfonySerializer\Groups(['tag', 'tag_color', 'color'])] #[Serializer\Groups(['tag', 'tag_color', 'color'])] #[Assert\Length(max: 7)] - #[ApiProperty( - description: 'Tag color in hexadecimal format.', - example: '#ff0000', - )] protected string $color = '#000000'; /** @@ -121,10 +117,6 @@ class Tag extends AbstractDateTimedPositioned implements LeafInterface #[Assert\NotNull] #[Assert\NotBlank] #[Assert\Length(max: 250)] - #[ApiProperty( - description: 'Unique tag name (slug) used to build content URL or filter queries.', - example: 'this-is-a-tag-name', - )] private string $tagName = ''; #[SymfonySerializer\Ignore] @@ -135,50 +127,24 @@ class Tag extends AbstractDateTimedPositioned implements LeafInterface #[ORM\Column(type: 'boolean', nullable: false, options: ['default' => true])] #[SymfonySerializer\Groups(['tag', 'tag_base', 'node', 'nodes_sources'])] #[Serializer\Groups(['tag', 'tag_base', 'node', 'nodes_sources'])] - #[ApiProperty( - description: 'Is this tag visible in website?', - example: 'true', - )] private bool $visible = true; #[ORM\Column(name: 'children_order', type: 'string', length: 60, options: ['default' => 'position'])] #[SymfonySerializer\Ignore] #[Serializer\Groups(["tag", "tag_children_order"])] #[Assert\Length(max: 60)] - #[ApiProperty( - description: 'This tag children will be sorted by a given field', - example: 'position', - schema: [ - 'type' => 'string', - 'enum' => ['position', 'tagName', 'createdAt', 'updatedAt', 'publishedAt'], - 'example' => 'position' - ], - )] private string $childrenOrder = 'position'; #[ORM\Column(name: 'children_order_direction', type: 'string', length: 4, options: ['default' => 'ASC'])] #[SymfonySerializer\Ignore] #[Serializer\Groups(["tag", "tag_children_order"])] #[Assert\Length(max: 4)] - #[ApiProperty( - description: 'This tag children will be sorted ascendant or descendant', - example: 'ASC', - schema: [ - 'type' => 'string', - 'enum' => ['ASC', 'DESC'], - 'example' => 'ASC' - ], - )] private string $childrenOrderDirection = 'ASC'; #[ApiFilter(BaseFilter\BooleanFilter::class)] #[ORM\Column(type: 'boolean', nullable: false, options: ['default' => false])] #[SymfonySerializer\Ignore] #[Serializer\Groups(["tag"])] - #[ApiProperty( - description: 'Is this tag locked to prevent deletion and renaming?', - example: 'false', - )] private bool $locked = false; /** @@ -502,17 +468,4 @@ public function setParent(?LeafInterface $parent = null): static return $this; } - - - #[ApiProperty( - description: 'Unique tag name (slug) used to build content URL or filter queries.', - example: 'this-is-a-tag-name', - )] - #[SymfonySerializer\SerializedName('slug')] - #[SymfonySerializer\Groups(['tag', 'tag_base', 'node', 'nodes_sources'])] - #[Serializer\Groups(['tag', 'tag_base', 'node', 'nodes_sources'])] - public function getSlug(): string - { - return $this->getTagName(); - } } diff --git a/src/Entity/TagTranslation.php b/src/Entity/TagTranslation.php index 36017fe1..b8aa78f1 100644 --- a/src/Entity/TagTranslation.php +++ b/src/Entity/TagTranslation.php @@ -45,16 +45,16 @@ class TagTranslation extends AbstractEntity protected ?string $description = null; #[ORM\ManyToOne(targetEntity: Tag::class, inversedBy: 'translatedTags')] - #[ORM\JoinColumn(name: 'tag_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'tag_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Ignore] #[Serializer\Exclude] - protected Tag $tag; + protected ?Tag $tag = null; #[ORM\ManyToOne(targetEntity: Translation::class, fetch: 'EXTRA_LAZY', inversedBy: 'tagTranslations')] - #[ORM\JoinColumn(name: 'translation_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'translation_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Groups(['tag', 'node', 'nodes_sources'])] #[Serializer\Groups(['tag', 'node', 'nodes_sources'])] - protected TranslationInterface $translation; + protected ?TranslationInterface $translation = null; /** * @var Collection @@ -73,22 +73,33 @@ class TagTranslation extends AbstractEntity /** * Create a new TagTranslation with its origin Tag and Translation. * - * @param Tag $original - * @param TranslationInterface $translation + * @param Tag|null $original + * @param TranslationInterface|null $translation */ - public function __construct(Tag $original, TranslationInterface $translation) + public function __construct(Tag $original = null, TranslationInterface $translation = null) { $this->setTag($original); $this->setTranslation($translation); $this->tagTranslationDocuments = new ArrayCollection(); - $this->name = $original->getDirtyTagName() != '' ? $original->getDirtyTagName() : $original->getTagName(); + + if (null !== $original) { + $this->name = $original->getDirtyTagName() != '' ? $original->getDirtyTagName() : $original->getTagName(); + } } + /** + * @return string + */ public function getName(): string { return $this->name; } + /** + * @param string|null $name + * + * @return $this + */ public function setName(?string $name): TagTranslation { $this->name = $name ?? ''; @@ -96,11 +107,19 @@ public function setName(?string $name): TagTranslation return $this; } + /** + * @return string + */ public function getDescription(): ?string { return $this->description; } + /** + * @param string|null $description + * + * @return $this + */ public function setDescription(?string $description): TagTranslation { $this->description = $description; @@ -108,23 +127,48 @@ public function setDescription(?string $description): TagTranslation return $this; } - public function getTag(): Tag + /** + * Gets the value of tag. + * + * @return Tag + */ + public function getTag(): ?Tag { return $this->tag; } - public function setTag(Tag $tag): TagTranslation + /** + * Sets the value of tag. + * + * @param Tag|null $tag the tag + * + * @return self + */ + public function setTag(?Tag $tag): TagTranslation { $this->tag = $tag; + return $this; } - public function getTranslation(): TranslationInterface + /** + * Gets the value of translation. + * + * @return TranslationInterface|null + */ + public function getTranslation(): ?TranslationInterface { return $this->translation; } - public function setTranslation(TranslationInterface $translation): TagTranslation + /** + * Sets the value of translation. + * + * @param TranslationInterface|null $translation the translation + * + * @return self + */ + public function setTranslation(?TranslationInterface $translation): TagTranslation { $this->translation = $translation; @@ -142,12 +186,14 @@ public function __clone() if ($this->id) { $this->id = null; $documents = $this->getDocuments(); - $this->tagTranslationDocuments = new ArrayCollection(); - /** @var TagTranslationDocuments $document */ - foreach ($documents as $document) { - $cloneDocument = clone $document; - $this->tagTranslationDocuments->add($cloneDocument); - $cloneDocument->setTagTranslation($this); + if ($documents !== null) { + $this->tagTranslationDocuments = new ArrayCollection(); + /** @var TagTranslationDocuments $document */ + foreach ($documents as $document) { + $cloneDocument = clone $document; + $this->tagTranslationDocuments->add($cloneDocument); + $cloneDocument->setTagTranslation($this); + } } } } diff --git a/src/Entity/TagTranslationDocuments.php b/src/Entity/TagTranslationDocuments.php index d06683da..ca765fb0 100644 --- a/src/Entity/TagTranslationDocuments.php +++ b/src/Entity/TagTranslationDocuments.php @@ -28,10 +28,10 @@ class TagTranslationDocuments extends AbstractPositioned fetch: 'EAGER', inversedBy: 'tagTranslationDocuments' )] - #[ORM\JoinColumn(name: 'tag_translation_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'tag_translation_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Ignore] #[Serializer\Exclude] - protected TagTranslation $tagTranslation; + protected ?TagTranslation $tagTranslation = null; #[ORM\ManyToOne( targetEntity: Document::class, @@ -39,18 +39,18 @@ class TagTranslationDocuments extends AbstractPositioned fetch: 'EAGER', inversedBy: 'tagTranslations' )] - #[ORM\JoinColumn(name: 'document_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'document_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[SymfonySerializer\Groups(['tag'])] #[Serializer\Groups(['tag'])] - protected Document $document; + protected ?Document $document = null; /** * Create a new relation between NodeSource, a Document and a NodeTypeField. * - * @param TagTranslation $tagTranslation - * @param Document $document + * @param TagTranslation|null $tagTranslation + * @param Document|null $document */ - public function __construct(TagTranslation $tagTranslation, Document $document) + public function __construct(TagTranslation $tagTranslation = null, Document $document = null) { $this->document = $document; $this->tagTranslation = $tagTranslation; @@ -60,26 +60,44 @@ public function __clone() { if ($this->id) { $this->id = null; + $this->tagTranslation = null; } } - public function getDocument(): Document + /** + * Gets the value of document. + * + * @return Document|null + */ + public function getDocument(): ?Document { return $this->document; } - public function setDocument(Document $document): TagTranslationDocuments + /** + * Sets the value of document. + * + * @param Document|null $document the document + * + * @return self + */ + public function setDocument(?Document $document): TagTranslationDocuments { $this->document = $document; + return $this; } - public function getTagTranslation(): TagTranslation + public function getTagTranslation(): ?TagTranslation { return $this->tagTranslation; } - public function setTagTranslation(TagTranslation $tagTranslation): TagTranslationDocuments + /** + * @param TagTranslation|null $tagTranslation + * @return TagTranslationDocuments + */ + public function setTagTranslation(?TagTranslation $tagTranslation): TagTranslationDocuments { $this->tagTranslation = $tagTranslation; return $this; diff --git a/src/Entity/Translation.php b/src/Entity/Translation.php index 549360e8..f4b2e95f 100644 --- a/src/Entity/Translation.php +++ b/src/Entity/Translation.php @@ -4,7 +4,6 @@ namespace RZ\Roadiz\CoreBundle\Entity; -use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Serializer\Filter\PropertyFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Doctrine\Orm\Filter as BaseFilter; @@ -544,24 +543,19 @@ class Translation extends AbstractDateTimed implements TranslationInterface protected Collection $folderTranslations; /** - * ISO 639-1 Language locale. + * Language locale * * fr or en for example * * @var string - * @see https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes * @Serializer\Groups({"translation", "document", "nodes_sources", "tag", "attribute", "folder", "log_sources"}) * @Serializer\Type("string") */ - #[ORM\Column(type: 'string', length: 10, unique: true, nullable: false)] + #[ORM\Column(type: 'string', length: 10, unique: true)] #[SymfonySerializer\Ignore] #[Assert\NotBlank] #[Assert\NotNull] #[Assert\Length(max: 10)] - #[ApiProperty( - description: 'Translation ISO 639-1 locale. See https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes', - example: 'fr', - )] private string $locale = ''; /** @@ -572,10 +566,6 @@ class Translation extends AbstractDateTimed implements TranslationInterface #[ORM\Column(name: 'override_locale', type: 'string', length: 10, unique: true, nullable: true)] #[SymfonySerializer\Ignore] #[Assert\Length(max: 10)] - #[ApiProperty( - description: 'Override standard locale with an other one (for example, `uk` instead of `en`)', - example: 'uk', - )] private ?string $overrideLocale = null; /** @@ -588,10 +578,6 @@ class Translation extends AbstractDateTimed implements TranslationInterface #[Assert\NotNull] #[Assert\NotBlank] #[Assert\Length(max: 250)] - #[ApiProperty( - description: 'Translation display name', - example: 'French', - )] private string $name = ''; /** @@ -601,10 +587,6 @@ class Translation extends AbstractDateTimed implements TranslationInterface */ #[ORM\Column(name: 'default_translation', type: 'boolean', nullable: false, options: ['default' => false])] #[SymfonySerializer\Groups(['translation', 'translation_base'])] - #[ApiProperty( - description: 'Is translation default one?', - example: 'true', - )] private bool $defaultTranslation = false; /** @@ -614,10 +596,6 @@ class Translation extends AbstractDateTimed implements TranslationInterface */ #[ORM\Column(type: 'boolean', nullable: false, options: ['default' => true])] #[SymfonySerializer\Groups(['translation', 'translation_base'])] - #[ApiProperty( - description: 'Is translation available publicly?', - example: 'true', - )] private bool $available = true; /** @@ -816,10 +794,6 @@ public function setOverrideLocale(?string $overrideLocale): Translation */ #[SymfonySerializer\SerializedName('locale')] #[SymfonySerializer\Groups(['translation_base'])] - #[ApiProperty( - description: 'Translation ISO 639-1 locale. See https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes', - example: 'fr', - )] public function getPreferredLocale(): string { return !empty($this->overrideLocale) ? $this->overrideLocale : $this->locale; diff --git a/src/Entity/UrlAlias.php b/src/Entity/UrlAlias.php index 9f713fef..8298c4de 100644 --- a/src/Entity/UrlAlias.php +++ b/src/Entity/UrlAlias.php @@ -32,10 +32,20 @@ class UrlAlias extends AbstractEntity private string $alias = ''; #[ORM\ManyToOne(targetEntity: NodesSources::class, inversedBy: 'urlAliases')] - #[ORM\JoinColumn(name: 'ns_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'ns_id', referencedColumnName: 'id')] #[SymfonySerializer\Ignore] #[Serializer\Exclude] - private NodesSources $nodeSource; + private ?NodesSources $nodeSource = null; + + /** + * Create a new UrlAlias linked to a NodeSource. + * + * @param NodesSources|null $nodeSource + */ + public function __construct(?NodesSources $nodeSource = null) + { + $this->setNodeSource($nodeSource); + } /** * @return string @@ -56,12 +66,19 @@ public function setAlias(string $alias): UrlAlias return $this; } - public function getNodeSource(): NodesSources + /** + * @return NodesSources|null + */ + public function getNodeSource(): ?NodesSources { return $this->nodeSource; } - public function setNodeSource(NodesSources $nodeSource): UrlAlias + /** + * @param NodesSources|null $nodeSource + * @return $this + */ + public function setNodeSource(?NodesSources $nodeSource): UrlAlias { $this->nodeSource = $nodeSource; return $this; diff --git a/src/EntityHandler/CustomFormFieldHandler.php b/src/EntityHandler/CustomFormFieldHandler.php index e7b8cbd5..368bb8fd 100644 --- a/src/EntityHandler/CustomFormFieldHandler.php +++ b/src/EntityHandler/CustomFormFieldHandler.php @@ -11,29 +11,38 @@ /** * Handle operations with customForms fields entities. */ -final class CustomFormFieldHandler extends AbstractHandler +class CustomFormFieldHandler extends AbstractHandler { private ?CustomFormField $customFormField = null; + private CustomFormHandler $customFormHandler; + /** + * @return CustomFormField + */ + public function getCustomFormField(): ?CustomFormField + { + return $this->customFormField; + } /** * @param CustomFormField $customFormField * @return $this */ - public function setCustomFormField(CustomFormField $customFormField): self + public function setCustomFormField(CustomFormField $customFormField) { $this->customFormField = $customFormField; return $this; } /** + * Create a new custom-form-field handler with custom-form-field to handle. + * * @param ObjectManager $objectManager * @param CustomFormHandler $customFormHandler */ - public function __construct( - ObjectManager $objectManager, - private readonly CustomFormHandler $customFormHandler - ) { + public function __construct(ObjectManager $objectManager, CustomFormHandler $customFormHandler) + { parent::__construct($objectManager); + $this->customFormHandler = $customFormHandler; } /** @@ -48,7 +57,11 @@ public function cleanPositions(bool $setPositions = true): float throw new \BadMethodCallException('CustomForm is null'); } - $this->customFormHandler->setCustomForm($this->customFormField->getCustomForm()); - return $this->customFormHandler->cleanFieldsPositions($setPositions); + if ($this->customFormField->getCustomForm() !== null) { + $this->customFormHandler->setCustomForm($this->customFormField->getCustomForm()); + return $this->customFormHandler->cleanFieldsPositions($setPositions); + } + + return 1; } } diff --git a/src/EntityHandler/CustomFormHandler.php b/src/EntityHandler/CustomFormHandler.php index 721e121a..d77dd59e 100644 --- a/src/EntityHandler/CustomFormHandler.php +++ b/src/EntityHandler/CustomFormHandler.php @@ -11,11 +11,20 @@ /** * Handle operations with node-type entities. */ -final class CustomFormHandler extends AbstractHandler +class CustomFormHandler extends AbstractHandler { protected ?CustomForm $customForm = null; - public function setCustomForm(CustomForm $customForm): self + public function getCustomForm(): ?CustomForm + { + return $this->customForm; + } + + /** + * @param CustomForm $customForm + * @return $this + */ + public function setCustomForm(CustomForm $customForm) { $this->customForm = $customForm; return $this; diff --git a/src/EntityHandler/DocumentHandler.php b/src/EntityHandler/DocumentHandler.php index 44e9d885..106ccf87 100644 --- a/src/EntityHandler/DocumentHandler.php +++ b/src/EntityHandler/DocumentHandler.php @@ -21,13 +21,15 @@ /** * Handle operations with documents entities. */ -final class DocumentHandler extends AbstractHandler +class DocumentHandler extends AbstractHandler { - private ?DocumentInterface $document = null; + protected ?DocumentInterface $document = null; + private FilesystemOperator $documentStorage; - public function __construct(ObjectManager $objectManager, private readonly FilesystemOperator $documentStorage) + public function __construct(ObjectManager $objectManager, FilesystemOperator $documentStorage) { parent::__construct($objectManager); + $this->documentStorage = $documentStorage; } /** @@ -93,9 +95,9 @@ public function getDocument(): ?DocumentInterface /** * @param DocumentInterface $document - * @return $this + * @return DocumentHandler */ - public function setDocument(DocumentInterface $document): self + public function setDocument(DocumentInterface $document): DocumentHandler { $this->document = $document; return $this; diff --git a/src/EntityHandler/FolderHandler.php b/src/EntityHandler/FolderHandler.php index 5cb16fdb..2b01d6af 100644 --- a/src/EntityHandler/FolderHandler.php +++ b/src/EntityHandler/FolderHandler.php @@ -6,12 +6,13 @@ use Doctrine\Common\Collections\Criteria; use RZ\Roadiz\Core\Handlers\AbstractHandler; +use RZ\Roadiz\Core\AbstractEntities\LeafInterface; use RZ\Roadiz\CoreBundle\Entity\Folder; /** * Handle operations with folders entities. */ -final class FolderHandler extends AbstractHandler +class FolderHandler extends AbstractHandler { protected ?Folder $folder = null; @@ -27,7 +28,7 @@ public function getFolder(): Folder * @param Folder $folder * @return $this */ - public function setFolder(Folder $folder): self + public function setFolder(Folder $folder) { $this->folder = $folder; return $this; @@ -38,7 +39,7 @@ public function setFolder(Folder $folder): self * * @return $this */ - private function removeChildren(): self + private function removeChildren() { /** @var Folder $folder */ foreach ($this->getFolder()->getChildren() as $folder) { @@ -56,7 +57,7 @@ private function removeChildren(): self * * @return $this */ - public function removeWithChildrenAndAssociations(): self + public function removeWithChildrenAndAssociations() { $this->removeChildren(); $this->objectManager->remove($this->getFolder()); @@ -68,6 +69,49 @@ public function removeWithChildrenAndAssociations(): self return $this; } + /** + * Return every folder’s parents. + * + * @deprecated Use directly Folder::getParents method. + * @return array + */ + public function getParents(): array + { + $parentsArray = []; + $parent = $this->getFolder(); + + do { + $parent = $parent->getParent(); + if ($parent !== null) { + $parentsArray[] = $parent; + } else { + break; + } + } while ($parent !== null); + + return array_reverse($parentsArray); + } + + /** + * Get folder full path using folder names. + * + * @deprecated Use directly Folder::getFullPath method. + * @return string + */ + public function getFullPath(): string + { + $parents = $this->getParents(); + $path = []; + + foreach ($parents as $parent) { + $path[] = $parent->getFolderName(); + } + + $path[] = $this->getFolder()->getFolderName(); + + return implode('/', $path); + } + /** * Clean position for current folder siblings. * diff --git a/src/EntityHandler/GroupHandler.php b/src/EntityHandler/GroupHandler.php index 9fb78ea9..b4cfe577 100644 --- a/src/EntityHandler/GroupHandler.php +++ b/src/EntityHandler/GroupHandler.php @@ -11,7 +11,7 @@ /** * Handle operations with Group entities. */ -final class GroupHandler extends AbstractHandler +class GroupHandler extends AbstractHandler { private ?Group $group = null; diff --git a/src/EntityHandler/HandlerFactory.php b/src/EntityHandler/HandlerFactory.php index 860b941e..8975ba83 100644 --- a/src/EntityHandler/HandlerFactory.php +++ b/src/EntityHandler/HandlerFactory.php @@ -20,10 +20,16 @@ use RZ\Roadiz\CoreBundle\Entity\Tag; use RZ\Roadiz\CoreBundle\Entity\Translation; -final class HandlerFactory implements HandlerFactoryInterface +class HandlerFactory implements HandlerFactoryInterface { - public function __construct(private readonly ContainerInterface $container) + private ContainerInterface $container; + + /** + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container) { + $this->container = $container; } /** diff --git a/src/EntityHandler/NodeHandler.php b/src/EntityHandler/NodeHandler.php index fced6244..42e55676 100644 --- a/src/EntityHandler/NodeHandler.php +++ b/src/EntityHandler/NodeHandler.php @@ -5,9 +5,7 @@ namespace RZ\Roadiz\CoreBundle\EntityHandler; use Doctrine\Common\Collections\Criteria; -use Doctrine\ORM\NonUniqueResultException; use Doctrine\Persistence\ObjectManager; -use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\Core\Handlers\AbstractHandler; use RZ\Roadiz\CoreBundle\Entity\CustomForm; use RZ\Roadiz\CoreBundle\Entity\Node; @@ -22,22 +20,34 @@ use RZ\Roadiz\CoreBundle\Security\Authorization\Chroot\NodeChrootResolver; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Workflow\Registry; -use Symfony\Component\Workflow\WorkflowInterface; +use Symfony\Component\Workflow\Workflow; /** * Handle operations with nodes entities. */ -final class NodeHandler extends AbstractHandler +class NodeHandler extends AbstractHandler { + protected NodeChrootResolver $chrootResolver; + private Registry $registry; private ?Node $node = null; + private NodeNamePolicyInterface $nodeNamePolicy; + /** + * @param ObjectManager $objectManager + * @param Registry $registry + * @param NodeChrootResolver $chrootResolver + * @param NodeNamePolicyInterface $nodeNamePolicy + */ final public function __construct( ObjectManager $objectManager, - private readonly Registry $registry, - private readonly NodeChrootResolver $chrootResolver, - private readonly NodeNamePolicyInterface $nodeNamePolicy + Registry $registry, + NodeChrootResolver $chrootResolver, + NodeNamePolicyInterface $nodeNamePolicy ) { parent::__construct($objectManager); + $this->registry = $registry; + $this->chrootResolver = $chrootResolver; + $this->nodeNamePolicy = $nodeNamePolicy; } protected function createSelf(): self @@ -63,9 +73,9 @@ public function getNode(): Node /** * @param Node $node - * @return $this + * @return NodeHandler */ - public function setNode(Node $node): self + public function setNode(Node $node) { $this->node = $node; return $this; @@ -78,11 +88,11 @@ public function setNode(Node $node): self * @param bool $flush * @return $this */ - public function cleanCustomFormsFromField(NodeTypeFieldInterface $field, bool $flush = true): self + public function cleanCustomFormsFromField(NodeTypeField $field, bool $flush = true) { $nodesCustomForms = $this->objectManager ->getRepository(NodesCustomForms::class) - ->findBy(['node' => $this->getNode(), 'fieldName' => $field->getName()]); + ->findBy(['node' => $this->getNode(), 'field' => $field]); foreach ($nodesCustomForms as $ncf) { $this->objectManager->remove($ncf); @@ -106,16 +116,16 @@ public function cleanCustomFormsFromField(NodeTypeFieldInterface $field, bool $f */ public function addCustomFormForField( CustomForm $customForm, - NodeTypeFieldInterface $field, + NodeTypeField $field, bool $flush = true, ?float $position = null - ): self { + ) { $ncf = new NodesCustomForms($this->getNode(), $customForm, $field); if (null === $position) { $latestPosition = $this->objectManager ->getRepository(NodesCustomForms::class) - ->getLatestPositionForFieldName($this->getNode(), $field->getName()); + ->getLatestPosition($this->getNode(), $field); $ncf->setPosition($latestPosition + 1); } else { $ncf->setPosition($position); @@ -140,9 +150,9 @@ public function getCustomFormsFromFieldName(string $fieldName): array { return $this->objectManager ->getRepository(CustomForm::class) - ->findByNodeAndFieldName( + ->findByNodeAndField( $this->getNode(), - $fieldName + $this->getNode()->getNodeType()->getFieldByName($fieldName) ); } @@ -153,7 +163,7 @@ public function getCustomFormsFromFieldName(string $fieldName): array * @param bool $flush * @return $this */ - public function cleanNodesFromField(NodeTypeFieldInterface $field, bool $flush = true): self + public function cleanNodesFromField(NodeTypeField $field, bool $flush = true) { $this->node->clearBNodesForField($field); @@ -173,7 +183,7 @@ public function cleanNodesFromField(NodeTypeFieldInterface $field, bool $flush = * @param null|float $position * @return $this */ - public function addNodeForField(Node $node, NodeTypeFieldInterface $field, bool $flush = true, ?float $position = null): self + public function addNodeForField(Node $node, NodeTypeField $field, bool $flush = true, ?float $position = null) { $ntn = new NodesToNodes($this->getNode(), $node, $field); @@ -181,7 +191,7 @@ public function addNodeForField(Node $node, NodeTypeFieldInterface $field, bool if (null === $position) { $latestPosition = $this->objectManager ->getRepository(NodesToNodes::class) - ->getLatestPositionForFieldName($this->getNode(), $field->getName()); + ->getLatestPosition($this->getNode(), $field); $ntn->setPosition($latestPosition + 1); } else { $ntn->setPosition($position); @@ -240,7 +250,6 @@ public function getReverseNodesFromFieldName(string $fieldName): array * @param Translation $translation * * @return null|NodesSources - * @deprecated Use Node::getNodeSourcesByTranslation() instead. */ public function getNodeSourceByTranslation($translation): ?NodesSources { @@ -254,7 +263,7 @@ public function getNodeSourceByTranslation($translation): ?NodesSources * * @return $this */ - private function removeChildren(): self + private function removeChildren() { /** @var Node $node */ foreach ($this->getNode()->getChildren() as $node) { @@ -270,7 +279,7 @@ private function removeChildren(): self * * @return $this */ - public function removeAssociations(): self + public function removeAssociations() { /** @var NodesSources $ns */ foreach ($this->getNode()->getNodeSources() as $ns) { @@ -287,7 +296,7 @@ public function removeAssociations(): self * * @return $this */ - public function removeWithChildrenAndAssociations(): self + public function removeWithChildrenAndAssociations() { $this->removeChildren(); $this->removeAssociations(); @@ -297,9 +306,9 @@ public function removeWithChildrenAndAssociations(): self } /** - * @return WorkflowInterface + * @return Workflow */ - private function getWorkflow(): WorkflowInterface + private function getWorkflow(): Workflow { return $this->registry->get($this->getNode()); } @@ -311,7 +320,7 @@ private function getWorkflow(): WorkflowInterface * * @return $this */ - public function softRemoveWithChildren(): self + public function softRemoveWithChildren() { $workflow = $this->getWorkflow(); if ($workflow->can($this->getNode(), 'delete')) { @@ -335,7 +344,7 @@ public function softRemoveWithChildren(): self * * @return $this */ - public function softUnremoveWithChildren(): self + public function softUnremoveWithChildren() { $workflow = $this->getWorkflow(); if ($workflow->can($this->getNode(), 'undelete')) { @@ -359,7 +368,7 @@ public function softUnremoveWithChildren(): self * * @return $this */ - public function publishWithChildren(): self + public function publishWithChildren() { $workflow = $this->getWorkflow(); if ($workflow->can($this->getNode(), 'publish')) { @@ -382,7 +391,7 @@ public function publishWithChildren(): self * * @return $this */ - public function archiveWithChildren(): self + public function archiveWithChildren() { $workflow = $this->getWorkflow(); if ($workflow->can($this->getNode(), 'archive')) { @@ -399,6 +408,17 @@ public function archiveWithChildren(): self return $this; } + /** + * Return if is in Newsletter Node. + * + * @deprecated Just here not to break themes. + * @return bool + */ + public function isRelatedToNewsletter(): bool + { + return false; + } + /** * Return if part of Node offspring. * @@ -541,7 +561,7 @@ public function getAllOffspringId(): array * * @return $this */ - public function makeHome(): self + public function makeHome() { $defaults = $this->getRepository() ->setDisplayingNotPublishedNodes(true) @@ -563,7 +583,7 @@ public function makeHome(): self * @return Node * @deprecated Use NodeDuplicator::duplicate() instead. */ - public function duplicate(): Node + public function duplicate() { $duplicator = new NodeDuplicator( $this->getNode(), @@ -576,17 +596,15 @@ public function duplicate(): Node /** * Get previous node from hierarchy. * - * @param array|null $criteria - * @param array|null $order + * @param array|null $criteria + * @param array|null $order * * @return Node|null - * @throws NonUniqueResultException - * @deprecated Use NodeRepository::findPreviousNode() instead. */ public function getPrevious( ?array $criteria = null, ?array $order = null - ): ?Node { + ) { if ($this->getNode()->getPosition() <= 1) { return null; } @@ -619,17 +637,15 @@ public function getPrevious( /** * Get next node from hierarchy. * - * @param array|null $criteria - * @param array|null $order + * @param array|null $criteria + * @param array|null $order * * @return Node|null - * @throws NonUniqueResultException - * @deprecated Use NodeRepository::findNextNode() instead. */ public function getNext( ?array $criteria = null, ?array $order = null - ): ?Node { + ) { if (null === $criteria) { $criteria = []; } @@ -659,7 +675,7 @@ public function getNext( /** * @return NodeRepository */ - protected function getRepository(): NodeRepository + public function getRepository(): NodeRepository { return $this->objectManager->getRepository(Node::class); } diff --git a/src/EntityHandler/NodeTypeFieldHandler.php b/src/EntityHandler/NodeTypeFieldHandler.php index 2a17f9d5..a1542bb8 100644 --- a/src/EntityHandler/NodeTypeFieldHandler.php +++ b/src/EntityHandler/NodeTypeFieldHandler.php @@ -12,8 +12,9 @@ /** * Handle operations with node-type fields entities. */ -final class NodeTypeFieldHandler extends AbstractHandler +class NodeTypeFieldHandler extends AbstractHandler { + private HandlerFactory $handlerFactory; private ?NodeTypeField $nodeTypeField = null; public function getNodeTypeField(): NodeTypeField @@ -34,9 +35,16 @@ public function setNodeTypeField(NodeTypeField $nodeTypeField): self return $this; } - public function __construct(ObjectManager $objectManager, private readonly HandlerFactory $handlerFactory) + /** + * Create a new node-type-field handler with node-type-field to handle. + * + * @param ObjectManager $objectManager + * @param HandlerFactory $handlerFactory + */ + public function __construct(ObjectManager $objectManager, HandlerFactory $handlerFactory) { parent::__construct($objectManager); + $this->handlerFactory = $handlerFactory; } /** diff --git a/src/EntityHandler/NodeTypeHandler.php b/src/EntityHandler/NodeTypeHandler.php index e391a276..c1ee99c3 100644 --- a/src/EntityHandler/NodeTypeHandler.php +++ b/src/EntityHandler/NodeTypeHandler.php @@ -24,9 +24,18 @@ /** * Handle operations with node-type entities. */ -final class NodeTypeHandler extends AbstractHandler +class NodeTypeHandler extends AbstractHandler { private ?NodeType $nodeType = null; + private EntityGeneratorFactory $entityGeneratorFactory; + private ApiResourceGenerator $apiResourceGenerator; + private HandlerFactory $handlerFactory; + private string $generatedEntitiesDir; + private SerializerInterface $serializer; + private string $serializedNodeTypesDir; + private string $importFilesConfigPath; + private string $kernelProjectDir; + private LoggerInterface $logger; /** * @return NodeType @@ -43,7 +52,7 @@ public function getNodeType(): NodeType * @param NodeType $nodeType * @return $this */ - public function setNodeType(NodeType $nodeType): self + public function setNodeType(NodeType $nodeType) { $this->nodeType = $nodeType; return $this; @@ -51,17 +60,26 @@ public function setNodeType(NodeType $nodeType): self public function __construct( ObjectManager $objectManager, - private readonly EntityGeneratorFactory $entityGeneratorFactory, - private readonly HandlerFactory $handlerFactory, - private readonly SerializerInterface $serializer, - private readonly ApiResourceGenerator $apiResourceGenerator, - private readonly LoggerInterface $logger, - private readonly string $generatedEntitiesDir, - private readonly string $serializedNodeTypesDir, - private readonly string $importFilesConfigPath, - private readonly string $kernelProjectDir + EntityGeneratorFactory $entityGeneratorFactory, + HandlerFactory $handlerFactory, + SerializerInterface $serializer, + ApiResourceGenerator $apiResourceGenerator, + LoggerInterface $logger, + string $generatedEntitiesDir, + string $serializedNodeTypesDir, + string $importFilesConfigPath, + string $kernelProjectDir ) { parent::__construct($objectManager); + $this->entityGeneratorFactory = $entityGeneratorFactory; + $this->handlerFactory = $handlerFactory; + $this->generatedEntitiesDir = $generatedEntitiesDir; + $this->serializer = $serializer; + $this->serializedNodeTypesDir = $serializedNodeTypesDir; + $this->importFilesConfigPath = $importFilesConfigPath; + $this->kernelProjectDir = $kernelProjectDir; + $this->apiResourceGenerator = $apiResourceGenerator; + $this->logger = $logger; } public function getGeneratedEntitiesFolder(): string diff --git a/src/EntityHandler/NodesSourcesHandler.php b/src/EntityHandler/NodesSourcesHandler.php index 55b2eb90..860fa692 100644 --- a/src/EntityHandler/NodesSourcesHandler.php +++ b/src/EntityHandler/NodesSourcesHandler.php @@ -6,8 +6,6 @@ use Doctrine\ORM\EntityRepository; use Doctrine\Persistence\ObjectManager; -use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; -use RZ\Roadiz\Core\Handlers\AbstractHandler; use RZ\Roadiz\CoreBundle\Bag\Settings; use RZ\Roadiz\CoreBundle\Entity\Document; use RZ\Roadiz\CoreBundle\Entity\Node; @@ -15,21 +13,32 @@ use RZ\Roadiz\CoreBundle\Entity\NodesSourcesDocuments; use RZ\Roadiz\CoreBundle\Entity\NodeTypeField; use RZ\Roadiz\CoreBundle\Entity\Tag; +use RZ\Roadiz\CoreBundle\Repository\NodesSourcesRepository; +use RZ\Roadiz\Core\Handlers\AbstractHandler; /** * Handle operations with node-sources entities. */ -final class NodesSourcesHandler extends AbstractHandler +class NodesSourcesHandler extends AbstractHandler { - private ?NodesSources $nodeSource = null; + protected ?NodesSources $nodeSource = null; /** * @var array|null */ - private ?array $parentsNodeSources = null; + protected ?array $parentsNodeSources = null; + protected Settings $settingsBag; - public function __construct(ObjectManager $objectManager, private readonly Settings $settingsBag) + /** + * Create a new node-source handler with node-source to handle. + * + * @param ObjectManager $objectManager + * @param Settings $settingsBag + */ + public function __construct(ObjectManager $objectManager, Settings $settingsBag) { parent::__construct($objectManager); + + $this->settingsBag = $settingsBag; } /** @@ -53,9 +62,9 @@ public function getNodeSource(): NodesSources /** * @param NodesSources $nodeSource - * @return $this + * @return NodesSourcesHandler */ - public function setNodeSource(NodesSources $nodeSource): self + public function setNodeSource(NodesSources $nodeSource) { $this->nodeSource = $nodeSource; return $this; @@ -68,7 +77,7 @@ public function setNodeSource(NodesSources $nodeSource): self * @param bool $flush * @return $this */ - public function cleanDocumentsFromField(NodeTypeFieldInterface $field, bool $flush = true): self + public function cleanDocumentsFromField(NodeTypeField $field, bool $flush = true) { $this->nodeSource->clearDocumentsByFields($field); @@ -90,17 +99,17 @@ public function cleanDocumentsFromField(NodeTypeFieldInterface $field, bool $flu */ public function addDocumentForField( Document $document, - NodeTypeFieldInterface $field, + NodeTypeField $field, bool $flush = true, ?float $position = null - ): self { + ) { $nsDoc = new NodesSourcesDocuments($this->nodeSource, $document, $field); if (!$this->nodeSource->hasNodesSourcesDocuments($nsDoc)) { if (null === $position) { $latestPosition = $this->objectManager ->getRepository(NodesSourcesDocuments::class) - ->getLatestPositionForFieldName($this->nodeSource, $field->getName()); + ->getLatestPosition($this->nodeSource, $field); $nsDoc->setPosition($latestPosition + 1); } else { @@ -121,16 +130,19 @@ public function addDocumentForField( * * @param string $fieldName Name of the node-type field * @return array - * @deprecated Use directly NodesSources::getDocumentsByFieldsWithName */ public function getDocumentsFromFieldName(string $fieldName): array { - return $this->objectManager - ->getRepository(Document::class) - ->findByNodeSourceAndFieldName( - $this->nodeSource, - $fieldName - ); + $field = $this->nodeSource->getNode()->getNodeType()->getFieldByName($fieldName); + if (null !== $field) { + return $this->objectManager + ->getRepository(Document::class) + ->findByNodeSourceAndField( + $this->nodeSource, + $field + ); + } + return []; } /** @@ -166,7 +178,7 @@ public function getParent(): ?NodesSources * * @param array|null $criteria * @return array - * @deprecated Use NodesSourcesRepository::findParents + * @throws \Doctrine\ORM\NonUniqueResultException */ public function getParents( array $criteria = null @@ -211,7 +223,6 @@ public function getParents( * @param array|null $order Non default ordering * * @return array - * @deprecated Use TreeWalker or NodesSourcesRepository::findChildren */ public function getChildren( array $criteria = null, @@ -247,7 +258,6 @@ public function getChildren( * @param array|null $order * * @return NodesSources|null - * @deprecated Use NodesSourcesRepository::findFirstChild */ public function getFirstChild( array $criteria = null, @@ -282,7 +292,6 @@ public function getFirstChild( * @param array|null $order * * @return NodesSources|null - * @deprecated Use NodesSourcesRepository::findLastChild */ public function getLastChild( array $criteria = null, @@ -318,7 +327,6 @@ public function getLastChild( * @param array|null $order * * @return NodesSources|null - * @deprecated Use NodesSourcesRepository::findFirstSibling */ public function getFirstSibling( array $criteria = null, @@ -341,7 +349,6 @@ public function getFirstSibling( * @param array|null $order * * @return NodesSources|null - * @deprecated Use NodesSourcesRepository::findLastSibling */ public function getLastSibling( array $criteria = null, @@ -364,7 +371,6 @@ public function getLastSibling( * @param array|null $order * * @return NodesSources|null - * @deprecated Use NodesSourcesRepository::findPrevious */ public function getPrevious( array $criteria = null, @@ -411,7 +417,6 @@ public function getPrevious( * @param array|null $order * * @return NodesSources|null - * @deprecated Use NodesSourcesRepository::findNext */ public function getNext( array $criteria = null, @@ -450,10 +455,9 @@ public function getNext( /** * Get node tags with current source translation. * - * @return iterable - * @deprecated Use TagRepository::findByNodesSources + * @return array */ - public function getTags(): iterable + public function getTags() { /** * @phpstan-ignore-next-line @@ -486,6 +490,7 @@ public function getSEO(): array 'description' => ($this->nodeSource->getMetaDescription() != "") ? $this->nodeSource->getMetaDescription() : $this->nodeSource->getTitle() . ', ' . $this->settingsBag->get('seo_description'), + 'keywords' => $this->nodeSource->getMetaKeywords(), ]; } @@ -496,7 +501,7 @@ public function getSEO(): array * * @return array Collection of nodes */ - public function getNodesFromFieldName(string $fieldName): array + public function getNodesFromFieldName(string $fieldName) { $field = $this->nodeSource->getNode()->getNodeType()->getFieldByName($fieldName); if (null !== $field) { @@ -518,7 +523,7 @@ public function getNodesFromFieldName(string $fieldName): array * * @return array Collection of nodes */ - public function getReverseNodesFromFieldName(string $fieldName): array + public function getReverseNodesFromFieldName(string $fieldName) { $field = $this->nodeSource->getNode()->getNodeType()->getFieldByName($fieldName); if (null !== $field) { diff --git a/src/EntityHandler/TagHandler.php b/src/EntityHandler/TagHandler.php index b995f460..b99e28c2 100644 --- a/src/EntityHandler/TagHandler.php +++ b/src/EntityHandler/TagHandler.php @@ -13,15 +13,26 @@ /** * Handle operations with tags entities. */ -final class TagHandler extends AbstractHandler +class TagHandler extends AbstractHandler { private ?Tag $tag = null; + /** + * @return Tag + */ + public function getTag(): Tag + { + if (null === $this->tag) { + throw new \BadMethodCallException('Tag is null'); + } + return $this->tag; + } + /** * @param Tag $tag * @return $this */ - public function setTag(Tag $tag): self + public function setTag(Tag $tag) { $this->tag = $tag; return $this; @@ -32,7 +43,7 @@ public function setTag(Tag $tag): self * * @return $this */ - private function removeChildren(): self + private function removeChildren() { /** @var Tag $tag */ foreach ($this->tag->getChildren() as $tag) { @@ -48,7 +59,7 @@ private function removeChildren(): self * * @return $this */ - public function removeAssociations(): self + public function removeAssociations() { foreach ($this->tag->getTranslatedTags() as $tt) { $this->objectManager->remove($tt); @@ -62,7 +73,7 @@ public function removeAssociations(): self * * @return $this */ - public function removeWithChildrenAndAssociations(): self + public function removeWithChildrenAndAssociations() { $this->removeChildren(); $this->removeAssociations(); @@ -77,6 +88,120 @@ public function removeWithChildrenAndAssociations(): self return $this; } + /** + * @return array Array of Translation + * @deprecated Do not query DB here + */ + public function getAvailableTranslations() + { + $query = $this->objectManager + ->createQuery(' + SELECT tr + FROM RZ\Roadiz\CoreBundle\Entity\Translation tr + INNER JOIN tr.tagTranslations tt + INNER JOIN tt.tag t + WHERE t.id = :tag_id') + ->setParameter('tag_id', $this->tag->getId()); + + try { + return $query->getResult(); + } catch (NoResultException $e) { + return []; + } + } + /** + * @return array Array of Translation id + * @deprecated Do not query DB here + */ + public function getAvailableTranslationsId() + { + $query = $this->objectManager + ->createQuery(' + SELECT tr.id FROM RZ\Roadiz\CoreBundle\Entity\Tag t + INNER JOIN t.translatedTags tt + INNER JOIN tt.translation tr + WHERE t.id = :tag_id') + ->setParameter('tag_id', $this->tag->getId()); + + try { + $simpleArray = []; + $complexArray = $query->getScalarResult(); + foreach ($complexArray as $subArray) { + $simpleArray[] = $subArray['id']; + } + + return $simpleArray; + } catch (NoResultException $e) { + return []; + } + } + + /** + * @return array Array of Translation + * @deprecated Do not query DB here + */ + public function getUnavailableTranslations() + { + $query = $this->objectManager + ->createQuery(' + SELECT tr FROM RZ\Roadiz\CoreBundle\Entity\Translation tr + WHERE tr.id NOT IN (:translations_id)') + ->setParameter('translations_id', $this->getAvailableTranslationsId()); + + try { + return $query->getResult(); + } catch (NoResultException $e) { + return []; + } + } + + /** + * @return array Array of Translation id + * @deprecated Do not query DB here + */ + public function getUnavailableTranslationsId() + { + /** @var Query $query */ + $query = $this->objectManager + ->createQuery(' + SELECT t.id FROM RZ\Roadiz\CoreBundle\Entity\Translation t + WHERE t.id NOT IN (:translations_id)') + ->setParameter('translations_id', $this->getAvailableTranslationsId()); + + try { + $simpleArray = []; + $complexArray = $query->getScalarResult(); + foreach ($complexArray as $subArray) { + $simpleArray[] = $subArray['id']; + } + + return $simpleArray; + } catch (NoResultException $e) { + return []; + } + } + + /** + * Return every tag’s parents. + * @deprecated Use directly Tag::getParents + * @return array + */ + public function getParents() + { + return $this->tag->getParents(); + } + + /** + * Get tag full path using tag names. + * + * @deprecated Use directly Tag::getFullPath + * @return string + */ + public function getFullPath(): string + { + return $this->tag->getFullPath(); + } + /** * Clean position for current tag siblings. * diff --git a/src/EntityHandler/TranslationHandler.php b/src/EntityHandler/TranslationHandler.php index 6f3f30ea..7a9d9673 100644 --- a/src/EntityHandler/TranslationHandler.php +++ b/src/EntityHandler/TranslationHandler.php @@ -14,16 +14,27 @@ /** * Handle operations with translations entities. */ -final class TranslationHandler extends AbstractHandler +class TranslationHandler extends AbstractHandler { private ?TranslationInterface $translation = null; + /** + * @return TranslationInterface + */ + public function getTranslation(): TranslationInterface + { + if (null === $this->translation) { + throw new \BadMethodCallException('Translation is null'); + } + return $this->translation; + } + /** * @param TranslationInterface $translation * * @return $this */ - public function setTranslation(TranslationInterface $translation): self + public function setTranslation(TranslationInterface $translation) { $this->translation = $translation; return $this; @@ -34,7 +45,7 @@ public function setTranslation(TranslationInterface $translation): self * * @return $this */ - public function makeDefault(): self + public function makeDefault() { $defaults = $this->objectManager ->getRepository(Translation::class) diff --git a/src/Event/NodesSources/NodesSourcesPathGeneratingEvent.php b/src/Event/NodesSources/NodesSourcesPathGeneratingEvent.php index 2efc28d3..b2cd984b 100644 --- a/src/Event/NodesSources/NodesSourcesPathGeneratingEvent.php +++ b/src/Event/NodesSources/NodesSourcesPathGeneratingEvent.php @@ -11,14 +11,46 @@ final class NodesSourcesPathGeneratingEvent extends Event { - private ?string $path; + /** + * @var bool + */ + protected $forceLocaleWithUrlAlias; + /** + * @var Theme|null + */ + private $theme; + /** + * @var NodesSources|null + */ + private $nodeSource; + /** + * @var array|null + */ + private $parameters; + /** + * @var RequestContext|null + */ + private $requestContext; + /** + * @var bool + */ + private $forceLocale = false; + /** + * @var string|null + */ + private $path; /** * @var bool Tells Node Router to prepend request context information to path or not. */ - private bool $isComplete = false; - protected bool $containsScheme = false; + private $isComplete = false; + /** + * @var bool + */ + protected $containsScheme = false; /** + * NodesSourcesPathGeneratingEvent constructor. + * * @param Theme|null $theme * @param NodesSources|null $nodeSource * @param RequestContext|null $requestContext @@ -27,13 +59,19 @@ final class NodesSourcesPathGeneratingEvent extends Event * @param bool $forceLocaleWithUrlAlias */ public function __construct( - private readonly ?Theme $theme, - private ?NodesSources $nodeSource, - private readonly ?RequestContext $requestContext, - private array $parameters = [], - private readonly bool $forceLocale = false, - private bool $forceLocaleWithUrlAlias = false + ?Theme $theme, + ?NodesSources $nodeSource, + ?RequestContext $requestContext, + array $parameters = [], + bool $forceLocale = false, + bool $forceLocaleWithUrlAlias = false ) { + $this->theme = $theme; + $this->nodeSource = $nodeSource; + $this->requestContext = $requestContext; + $this->forceLocale = $forceLocale; + $this->parameters = $parameters; + $this->forceLocaleWithUrlAlias = $forceLocaleWithUrlAlias; } /** diff --git a/src/EventSubscriber/AssetsCacheEventSubscriber.php b/src/EventSubscriber/AssetsCacheEventSubscriber.php index 664344d0..1e729545 100644 --- a/src/EventSubscriber/AssetsCacheEventSubscriber.php +++ b/src/EventSubscriber/AssetsCacheEventSubscriber.php @@ -11,16 +11,23 @@ final class AssetsCacheEventSubscriber implements EventSubscriberInterface { - public function __construct( - private readonly AssetsFileClearer $assetsClearer, - private readonly LoggerInterface $logger - ) { + private AssetsFileClearer $assetsClearer; + private LoggerInterface $logger; + + public function __construct(AssetsFileClearer $assetsClearer, LoggerInterface $logger) + { + $this->assetsClearer = $assetsClearer; + $this->logger = $logger; } + /** + * @inheritDoc + */ public static function getSubscribedEvents(): array { return [ - CachePurgeAssetsRequestEvent::class => ['onPurgeAssetsRequest', 0] + CachePurgeAssetsRequestEvent::class => ['onPurgeAssetsRequest', 0], + '\RZ\Roadiz\Core\Events\Cache\CachePurgeAssetsRequestEvent' => ['onPurgeAssetsRequest', 0], ]; } diff --git a/src/EventSubscriber/AutomaticWebhookSubscriber.php b/src/EventSubscriber/AutomaticWebhookSubscriber.php index fb29b2fc..1e6ba43f 100644 --- a/src/EventSubscriber/AutomaticWebhookSubscriber.php +++ b/src/EventSubscriber/AutomaticWebhookSubscriber.php @@ -25,25 +25,45 @@ final class AutomaticWebhookSubscriber implements EventSubscriberInterface { + private WebhookDispatcher $webhookDispatcher; + private HandlerFactoryInterface $handlerFactory; + private ManagerRegistry $managerRegistry; + + /** + * @param WebhookDispatcher $webhookDispatcher + * @param ManagerRegistry $managerRegistry + * @param HandlerFactoryInterface $handlerFactory + */ public function __construct( - private readonly WebhookDispatcher $webhookDispatcher, - private readonly ManagerRegistry $managerRegistry, - private readonly HandlerFactoryInterface $handlerFactory + WebhookDispatcher $webhookDispatcher, + ManagerRegistry $managerRegistry, + HandlerFactoryInterface $handlerFactory ) { + $this->webhookDispatcher = $webhookDispatcher; + $this->handlerFactory = $handlerFactory; + $this->managerRegistry = $managerRegistry; } public static function getSubscribedEvents(): array { return [ - 'workflow.node.completed' => 'onAutomaticWebhook', + 'workflow.node.completed' => ['onAutomaticWebhook'], NodeVisibilityChangedEvent::class => 'onAutomaticWebhook', + '\RZ\Roadiz\Core\Events\Node\NodeVisibilityChangedEvent' => 'onAutomaticWebhook', NodesSourcesPreUpdatedEvent::class => 'onAutomaticWebhook', + '\RZ\Roadiz\Core\Events\NodesSources\NodesSourcesPreUpdatedEvent' => 'onAutomaticWebhook', NodesSourcesDeletedEvent::class => 'onAutomaticWebhook', + '\RZ\Roadiz\Core\Events\NodesSources\NodesSourcesDeletedEvent' => 'onAutomaticWebhook', NodeUpdatedEvent::class => 'onAutomaticWebhook', + '\RZ\Roadiz\Core\Events\Node\NodeUpdatedEvent' => 'onAutomaticWebhook', NodeDeletedEvent::class => 'onAutomaticWebhook', + '\RZ\Roadiz\Core\Events\Node\NodeDeletedEvent' => 'onAutomaticWebhook', NodeTaggedEvent::class => 'onAutomaticWebhook', + '\RZ\Roadiz\Core\Events\Node\NodeTaggedEvent' => 'onAutomaticWebhook', TagUpdatedEvent::class => 'onAutomaticWebhook', + '\RZ\Roadiz\Core\Events\Tag\TagUpdatedEvent' => 'onAutomaticWebhook', DocumentTranslationUpdatedEvent::class => 'onAutomaticWebhook', + '\RZ\Roadiz\Core\Events\DocumentTranslationUpdatedEvent' => 'onAutomaticWebhook', DocumentUpdatedEvent::class => 'onAutomaticWebhook', ]; } @@ -95,6 +115,8 @@ private function isEventSubjectInRootNode(mixed $event, ?Node $rootNode): bool */ return true; } + /** @var Node|null $subject */ + $subject = null; switch (true) { case $event instanceof Event: diff --git a/src/EventSubscriber/CloudflareCacheEventSubscriber.php b/src/EventSubscriber/CloudflareCacheEventSubscriber.php index 48fd5f2e..caa710e9 100644 --- a/src/EventSubscriber/CloudflareCacheEventSubscriber.php +++ b/src/EventSubscriber/CloudflareCacheEventSubscriber.php @@ -24,12 +24,27 @@ final class CloudflareCacheEventSubscriber implements EventSubscriberInterface { + private LoggerInterface $logger; + private MessageBusInterface $bus; + private UrlGeneratorInterface $urlGenerator; + private ReverseProxyCacheLocator $reverseProxyCacheLocator; + + /** + * @param MessageBusInterface $bus + * @param ReverseProxyCacheLocator $reverseProxyCacheLocator + * @param UrlGeneratorInterface $urlGenerator + * @param LoggerInterface $logger + */ public function __construct( - private readonly MessageBusInterface $bus, - private readonly ReverseProxyCacheLocator $reverseProxyCacheLocator, - private readonly UrlGeneratorInterface $urlGenerator, - private readonly LoggerInterface $logger + MessageBusInterface $bus, + ReverseProxyCacheLocator $reverseProxyCacheLocator, + UrlGeneratorInterface $urlGenerator, + LoggerInterface $logger ) { + $this->logger = $logger; + $this->bus = $bus; + $this->reverseProxyCacheLocator = $reverseProxyCacheLocator; + $this->urlGenerator = $urlGenerator; } /** * @inheritDoc @@ -38,15 +53,25 @@ public static function getSubscribedEvents(): array { return [ CachePurgeRequestEvent::class => ['onBanRequest', 3], + '\RZ\Roadiz\Core\Events\Cache\CachePurgeRequestEvent' => ['onBanRequest', 3], NodesSourcesUpdatedEvent::class => ['onPurgeRequest', 3], + '\RZ\Roadiz\Core\Events\NodesSources\NodesSourcesUpdatedEvent' => ['onPurgeRequest', 3], ]; } + /** + * @return bool + */ protected function supportConfig(): bool { return null !== $this->reverseProxyCacheLocator->getCloudflareProxyCache(); } + /** + * @param CachePurgeRequestEvent $event + * @throws \GuzzleHttp\Exception\GuzzleException + * @return void + */ public function onBanRequest(CachePurgeRequestEvent $event): void { if (!$this->supportConfig()) { @@ -84,6 +109,11 @@ public function onBanRequest(CachePurgeRequestEvent $event): void } } + /** + * @param NodesSourcesUpdatedEvent $event + * + * @throws \GuzzleHttp\Exception\GuzzleException + */ public function onPurgeRequest(NodesSourcesUpdatedEvent $event): void { if (!$this->supportConfig()) { @@ -124,7 +154,6 @@ private function getCloudflareCacheProxy(): CloudflareProxyCache /** * @param array $body * @return Request - * @throws \JsonException */ protected function createRequest(array $body): Request { @@ -140,7 +169,10 @@ protected function createRequest(array $body): Request $this->getCloudflareCacheProxy()->getVersion(), $this->getCloudflareCacheProxy()->getZone() ); - $body = \json_encode($body, JSON_THROW_ON_ERROR); + $body = \json_encode($body); + if (false === $body) { + throw new \RuntimeException('Unable to json_encode body'); + } return new Request( 'POST', $uri, @@ -151,7 +183,6 @@ protected function createRequest(array $body): Request /** * @return Request - * @throws \JsonException */ protected function createBanRequest(): Request { @@ -162,8 +193,8 @@ protected function createBanRequest(): Request /** * @param string[] $uris + * * @return Request - * @throws \JsonException */ protected function createPurgeRequest(array $uris = []): Request { diff --git a/src/EventSubscriber/LocaleSubscriber.php b/src/EventSubscriber/LocaleSubscriber.php index 8583550f..cfdd022b 100644 --- a/src/EventSubscriber/LocaleSubscriber.php +++ b/src/EventSubscriber/LocaleSubscriber.php @@ -7,20 +7,19 @@ use Doctrine\Persistence\ManagerRegistry; use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; use RZ\Roadiz\CoreBundle\Entity\Translation; -use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; -use RZ\Roadiz\CoreBundle\Repository\TranslationRepository; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Routing\RequestContextAwareInterface; final class LocaleSubscriber implements EventSubscriberInterface { - public function __construct( - private readonly PreviewResolverInterface $previewResolver, - private readonly ManagerRegistry $managerRegistry, - private readonly RequestContextAwareInterface $router - ) { + private ManagerRegistry $managerRegistry; + private RequestContextAwareInterface $router; + + public function __construct(ManagerRegistry $managerRegistry, RequestContextAwareInterface $router) + { + $this->managerRegistry = $managerRegistry; + $this->router = $router; } /** @@ -30,44 +29,13 @@ public static function getSubscribedEvents(): array { return [ // must be registered just after Symfony\Component\HttpKernel\EventListener\LocaleListener - RequestEvent::class => ['onKernelRequest', 16], + RequestEvent::class => [['onKernelRequest', 16]], ]; } - private function getRepository(): TranslationRepository - { - return $this->managerRegistry->getRepository(Translation::class); - } - private function getDefaultTranslation(): ?TranslationInterface { - return $this->getRepository()->findDefault(); - } - - private function supportsLocale(?string $locale): bool - { - if (null === $locale || $locale === '') { - return false; - } - - if ($this->previewResolver->isPreview()) { - $locales = $this->getRepository()->getAllLocales(); - } else { - $locales = $this->getRepository()->getAvailableLocales(); - } - return \in_array( - $locale, - $locales, - true - ); - } - - private function getTranslationByLocale(string $locale): ?TranslationInterface - { - if ($this->previewResolver->isPreview()) { - return $this->getRepository()->findOneByLocaleOrOverrideLocale($locale); - } - return $this->getRepository()->findOneAvailableByLocaleOrOverrideLocale($locale); + return $this->managerRegistry->getRepository(Translation::class)->findDefault(); } public function onKernelRequest(RequestEvent $event): void @@ -75,53 +43,27 @@ public function onKernelRequest(RequestEvent $event): void $request = $event->getRequest(); $locale = $request->query->get('_locale') ?? $request->attributes->get('_locale'); - /* - * Set default locale - */ - if ($this->supportsLocale($locale)) { - $this->setTranslation($request, $this->getTranslationByLocale($locale)); - return; - } - - $statelessRoutes = [ - 'api_genid', - 'api_doc', - 'api_entrypoint', - 'api_graphql_entrypoint', - 'api_jsonld_context', - 'healthCheckAction', - 'interventionRequestProcess', - ]; - if ( - !\in_array($request->attributes->getString('_route'), $statelessRoutes, true) && - !$request->attributes->getBoolean('_stateless') && - $request->hasPreviousSession() - ) { - $sessionLocale = $request->getSession()->get('_locale', null); - if ($this->supportsLocale($sessionLocale)) { - $this->setTranslation($request, $this->getTranslationByLocale($sessionLocale)); - return; + if ($request->hasPreviousSession()) { + $locale = $request->getSession()->get('_locale', null); + if (null !== $locale) { + $this->setLocale($event, $locale); } } - if (null !== $translation = $this->getDefaultTranslation()) { - $this->setTranslation($request, $translation); - return; + /* + * Set default locale + */ + if (null !== $locale && $locale !== '') { + $this->setLocale($event, $locale); + } elseif (null !== $translation = $this->getDefaultTranslation()) { + $shortLocale = $translation->getLocale(); + $this->setLocale($event, $shortLocale); } } - private function setTranslation(Request $request, ?TranslationInterface $translation): void + private function setLocale(RequestEvent $event, string $locale): void { - if (null === $translation) { - return; - } - $locale = $translation->getPreferredLocale(); - /* - * Set current translation globally for controllers, utils, etc - */ - $request->attributes->set('_translation', $translation); - $request->attributes->set('_locale', $locale); - $request->setLocale($locale); + $event->getRequest()->setLocale($locale); \Locale::setDefault($locale); $this->router->getContext()->setParameter('_locale', $locale); } diff --git a/src/EventSubscriber/LoggableSubscriber.php b/src/EventSubscriber/LoggableSubscriber.php index 8bec9f86..9dc9a7ed 100644 --- a/src/EventSubscriber/LoggableSubscriber.php +++ b/src/EventSubscriber/LoggableSubscriber.php @@ -5,18 +5,27 @@ namespace RZ\Roadiz\CoreBundle\EventSubscriber; use Gedmo\Loggable\LoggableListener; -use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; final class LoggableSubscriber implements EventSubscriberInterface { + private LoggableListener $loggableListener; + private ?TokenStorageInterface $tokenStorage; + private ?AuthorizationCheckerInterface $authorizationChecker; + public function __construct( - private readonly LoggableListener $loggableListener, - private readonly Security $security, + LoggableListener $loggableListener, + TokenStorageInterface $tokenStorage = null, + AuthorizationCheckerInterface $authorizationChecker = null ) { + $this->loggableListener = $loggableListener; + $this->tokenStorage = $tokenStorage; + $this->authorizationChecker = $authorizationChecker; } public function onKernelRequest(RequestEvent $event): void @@ -25,12 +34,14 @@ public function onKernelRequest(RequestEvent $event): void return; } - if (null === $user = $this->security->getUser()) { + if (null === $this->tokenStorage || null === $this->authorizationChecker) { return; } - if ($this->security->isGranted('IS_AUTHENTICATED_REMEMBERED')) { - $this->loggableListener->setUsername($user); + $token = $this->tokenStorage->getToken(); + + if (null !== $token && $this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + $this->loggableListener->setUsername($token); } } diff --git a/src/EventSubscriber/NodeDuplicationSubscriber.php b/src/EventSubscriber/NodeDuplicationSubscriber.php index 6990f179..7370ee3d 100644 --- a/src/EventSubscriber/NodeDuplicationSubscriber.php +++ b/src/EventSubscriber/NodeDuplicationSubscriber.php @@ -10,18 +10,26 @@ use RZ\Roadiz\CoreBundle\EntityHandler\NodeHandler; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -final class NodeDuplicationSubscriber implements EventSubscriberInterface +class NodeDuplicationSubscriber implements EventSubscriberInterface { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly HandlerFactoryInterface $handlerFactory - ) { + protected HandlerFactoryInterface $handlerFactory; + private ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + * @param HandlerFactoryInterface $handlerFactory + */ + public function __construct(ManagerRegistry $managerRegistry, HandlerFactoryInterface $handlerFactory) + { + $this->handlerFactory = $handlerFactory; + $this->managerRegistry = $managerRegistry; } public static function getSubscribedEvents(): array { return [ NodeDuplicatedEvent::class => 'cleanPosition', + '\RZ\Roadiz\Core\Events\Node\NodeDuplicatedEvent' => 'cleanPosition', ]; } diff --git a/src/EventSubscriber/NodeNameSubscriber.php b/src/EventSubscriber/NodeNameSubscriber.php index 91659409..17517016 100644 --- a/src/EventSubscriber/NodeNameSubscriber.php +++ b/src/EventSubscriber/NodeNameSubscriber.php @@ -43,6 +43,7 @@ public static function getSubscribedEvents(): array { return [ NodesSourcesPreUpdatedEvent::class => ['onBeforeUpdate', 0], + '\RZ\Roadiz\Core\Events\NodesSources\NodesSourcesPreUpdatedEvent' => ['onBeforeUpdate', 0], ]; } diff --git a/src/EventSubscriber/NodeRedirectionSubscriber.php b/src/EventSubscriber/NodeRedirectionSubscriber.php index 2b3e00e8..31158fbb 100644 --- a/src/EventSubscriber/NodeRedirectionSubscriber.php +++ b/src/EventSubscriber/NodeRedirectionSubscriber.php @@ -10,23 +10,37 @@ use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelInterface; /** * Subscribe to Node, NodesSources and UrlAlias event to clear ns url cache. */ class NodeRedirectionSubscriber implements EventSubscriberInterface { + protected NodeMover $nodeMover; + protected KernelInterface $kernel; + protected PreviewResolverInterface $previewResolver; + + /** + * @param NodeMover $nodeMover + * @param KernelInterface $kernel + * @param PreviewResolverInterface $previewResolver + */ public function __construct( - protected readonly NodeMover $nodeMover, - protected readonly string $kernelEnvironment, - protected readonly PreviewResolverInterface $previewResolver + NodeMover $nodeMover, + KernelInterface $kernel, + PreviewResolverInterface $previewResolver ) { + $this->nodeMover = $nodeMover; + $this->kernel = $kernel; + $this->previewResolver = $previewResolver; } public static function getSubscribedEvents(): array { return [ NodePathChangedEvent::class => 'redirectOldPaths', + '\RZ\Roadiz\Core\Events\Node\NodePathChangedEvent' => 'redirectOldPaths' ]; } @@ -43,7 +57,7 @@ public function redirectOldPaths( EventDispatcherInterface $dispatcher ): void { if ( - $this->kernelEnvironment === 'prod' && + $this->kernel->getEnvironment() === 'prod' && !$this->previewResolver->isPreview() && null !== $event->getNode() && $event->getNode()->isPublished() && diff --git a/src/EventSubscriber/NodeSourcePathSubscriber.php b/src/EventSubscriber/NodeSourcePathSubscriber.php index 0165266b..82dbaccf 100644 --- a/src/EventSubscriber/NodeSourcePathSubscriber.php +++ b/src/EventSubscriber/NodeSourcePathSubscriber.php @@ -9,11 +9,16 @@ use RZ\Roadiz\CoreBundle\Routing\NodesSourcesUrlGenerator; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -final class NodeSourcePathSubscriber implements EventSubscriberInterface +class NodeSourcePathSubscriber implements EventSubscriberInterface { - public function __construct( - private readonly NodesSourcesPathAggregator $pathAggregator - ) { + protected NodesSourcesPathAggregator $pathAggregator; + + /** + * @param NodesSourcesPathAggregator $pathAggregator + */ + public function __construct(NodesSourcesPathAggregator $pathAggregator) + { + $this->pathAggregator = $pathAggregator; } /** @@ -22,7 +27,8 @@ public function __construct( public static function getSubscribedEvents(): array { return [ - NodesSourcesPathGeneratingEvent::class => ['onNodesSourcesPath', -100], + NodesSourcesPathGeneratingEvent::class => [['onNodesSourcesPath', -100]], + '\RZ\Roadiz\Core\Events\NodesSources\NodesSourcesPathGeneratingEvent' => [['onNodesSourcesPath', -100]], ]; } @@ -33,6 +39,7 @@ public function onNodesSourcesPath(NodesSourcesPathGeneratingEvent $event): void { $urlGenerator = new NodesSourcesUrlGenerator( $this->pathAggregator, + null, $event->getNodeSource(), $event->isForceLocale(), $event->isForceLocaleWithUrlAlias() diff --git a/src/EventSubscriber/NodesSourcesAddHeadersSubscriber.php b/src/EventSubscriber/NodesSourcesAddHeadersSubscriber.php deleted file mode 100644 index 63808c9f..00000000 --- a/src/EventSubscriber/NodesSourcesAddHeadersSubscriber.php +++ /dev/null @@ -1,86 +0,0 @@ - ['onKernelResponse', 0] - ]; - } - - public function onKernelResponse(ResponseEvent $event): void - { - $request = $event->getRequest(); - $response = $event->getResponse(); - - if (!$request->isMethodCacheable()) { - return; - } - if (!$response->getContent() || !$response->isSuccessful()) { - return; - } - - $attributes = RequestAttributesExtractor::extractAttributes($request); - if (\count($attributes) < 1) { - return; - } - - if ($this->previewResolver->isPreview()) { - return; - } - - if ($this->security->isGranted('IS_AUTHENTICATED')) { - return; - } - - $resourceCacheHeaders = $attributes['cache_headers'] ?? []; - $data = $request->attributes->get('data'); - - // Work with WebResponse item instead of WebResponse itself - if ($data instanceof WebResponseInterface) { - $data = $data->getItem(); - } - - // if the public-property is defined and not yet set; apply it to the response - $public = $resourceCacheHeaders['public'] ?? null; - if (null !== $public && !$response->headers->hasCacheControlDirective('public')) { - $public ? $response->setPublic() : $response->setPrivate(); - } - - if (!$data instanceof NodesSources) { - return; - } - - if ($data->getNode()->getTtl() <= 0) { - return; - } - - if (null !== ($maxAge = $resourceCacheHeaders['max_age'] ?? $data->getNode()->getTtl()) && !$response->headers->hasCacheControlDirective('max-age')) { - $response->setMaxAge($maxAge * 60); - } - // Cache-Control "s-maxage" is only relevant is resource is not marked as "private" - if (false !== $public && null !== ($sharedMaxAge = $resourceCacheHeaders['shared_max_age'] ?? $data->getNode()->getTtl()) && !$response->headers->hasCacheControlDirective('s-maxage')) { - $response->setSharedMaxAge($sharedMaxAge * 60); - } - } -} diff --git a/src/EventSubscriber/NodesSourcesLinkHeaderEventSubscriber.php b/src/EventSubscriber/NodesSourcesLinkHeaderEventSubscriber.php index 3c0b5c91..a852c6eb 100644 --- a/src/EventSubscriber/NodesSourcesLinkHeaderEventSubscriber.php +++ b/src/EventSubscriber/NodesSourcesLinkHeaderEventSubscriber.php @@ -6,7 +6,6 @@ use Doctrine\Persistence\ManagerRegistry; use Psr\Link\EvolvableLinkProviderInterface; -use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -15,12 +14,17 @@ use Symfony\Component\WebLink\GenericLinkProvider; use Symfony\Component\WebLink\Link; -final class NodesSourcesLinkHeaderEventSubscriber implements EventSubscriberInterface +class NodesSourcesLinkHeaderEventSubscriber implements EventSubscriberInterface { + private ManagerRegistry $managerRegistry; + private UrlGeneratorInterface $urlGenerator; + public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly UrlGeneratorInterface $urlGenerator + ManagerRegistry $managerRegistry, + UrlGeneratorInterface $urlGenerator ) { + $this->managerRegistry = $managerRegistry; + $this->urlGenerator = $urlGenerator; } /** @@ -36,40 +40,33 @@ public static function getSubscribedEvents(): array public function onKernelView(ViewEvent $event): void { $request = $event->getRequest(); - $resources = $request->attributes->get('data'); + $resources = $request->attributes->get('data', null); $linkProvider = $request->attributes->get('_links', new GenericLinkProvider()); - // Work with WebResponse item instead of WebResponse itself - if ($resources instanceof WebResponseInterface) { - $resources = $resources->getItem(); - } - - if (!$resources instanceof NodesSources || !$linkProvider instanceof EvolvableLinkProviderInterface) { - return; - } - - /* - * Preview and authentication is handled at repository level. - */ - /** @var NodesSources[] $allSources */ - $allSources = $this->managerRegistry - ->getRepository(get_class($resources)) - ->findByNode($resources->getNode()); + if ($resources instanceof NodesSources && $linkProvider instanceof EvolvableLinkProviderInterface) { + /* + * Preview and authentication is handled at repository level. + */ + /** @var NodesSources[] $allSources */ + $allSources = $this->managerRegistry + ->getRepository(get_class($resources)) + ->findByNode($resources->getNode()); - foreach ($allSources as $singleSource) { - $linkProvider = $linkProvider->withLink( - (new Link( - 'alternate', - $this->urlGenerator->generate(RouteObjectInterface::OBJECT_BASED_ROUTE_NAME, [ - RouteObjectInterface::ROUTE_OBJECT => $singleSource - ]) - )) - ->withAttribute('hreflang', $singleSource->getTranslation()->getLocale()) - // Must encode translation name in base64 because headers are ASCII only - ->withAttribute('title', \base64_encode($singleSource->getTranslation()->getName())) - ->withAttribute('type', 'text/html') - ); + foreach ($allSources as $singleSource) { + $linkProvider = $linkProvider->withLink( + (new Link( + 'alternate', + $this->urlGenerator->generate(RouteObjectInterface::OBJECT_BASED_ROUTE_NAME, [ + RouteObjectInterface::ROUTE_OBJECT => $singleSource + ]) + )) + ->withAttribute('hreflang', $singleSource->getTranslation()->getLocale()) + // Must encode translation name in base64 because headers are ASCII only + ->withAttribute('title', \base64_encode($singleSource->getTranslation()->getName())) + ->withAttribute('type', 'text/html') + ); + } + $request->attributes->set('_links', $linkProvider); } - $request->attributes->set('_links', $linkProvider); } } diff --git a/src/EventSubscriber/NodesSourcesUniversalSubscriber.php b/src/EventSubscriber/NodesSourcesUniversalSubscriber.php index b16cb180..d13e80d6 100644 --- a/src/EventSubscriber/NodesSourcesUniversalSubscriber.php +++ b/src/EventSubscriber/NodesSourcesUniversalSubscriber.php @@ -33,6 +33,7 @@ public static function getSubscribedEvents(): array { return [ NodesSourcesUpdatedEvent::class => 'duplicateUniversalContents', + '\RZ\Roadiz\Core\Events\NodesSources\NodesSourcesUpdatedEvent' => 'duplicateUniversalContents', ]; } diff --git a/src/EventSubscriber/NodesSourcesUrlsCacheEventSubscriber.php b/src/EventSubscriber/NodesSourcesUrlsCacheEventSubscriber.php index 19a51cbd..771eea51 100644 --- a/src/EventSubscriber/NodesSourcesUrlsCacheEventSubscriber.php +++ b/src/EventSubscriber/NodesSourcesUrlsCacheEventSubscriber.php @@ -37,17 +37,28 @@ public static function getSubscribedEvents(): array { return [ NodesSourcesCreatedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\NodesSources\NodesSourcesCreatedEvent' => 'onPurgeRequest', NodesSourcesDeletedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\NodesSources\NodesSourcesDeletedEvent' => 'onPurgeRequest', TranslationUpdatedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\Translation\TranslationUpdatedEvent' => 'onPurgeRequest', TranslationDeletedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\Translation\TranslationDeletedEvent' => 'onPurgeRequest', NodeDeletedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\Node\NodeDeletedEvent' => 'onPurgeRequest', NodeUndeletedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\Node\NodeUndeletedEvent' => 'onPurgeRequest', NodeUpdatedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\Node\NodeUpdatedEvent' => 'onPurgeRequest', UrlAliasCreatedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\UrlAlias\UrlAliasCreatedEvent' => 'onPurgeRequest', UrlAliasUpdatedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\UrlAlias\UrlAliasUpdatedEvent' => 'onPurgeRequest', UrlAliasDeletedEvent::class => 'onPurgeRequest', + '\RZ\Roadiz\Core\Events\UrlAlias\UrlAliasDeletedEvent' => 'onPurgeRequest', 'workflow.node.completed' => 'onPurgeRequest', CachePurgeRequestEvent::class => ['onPurgeRequest', 3], + '\RZ\Roadiz\Core\Events\Cache\CachePurgeRequestEvent' => ['onPurgeRequest', 3], ]; } diff --git a/src/EventSubscriber/OPCacheEventSubscriber.php b/src/EventSubscriber/OPCacheEventSubscriber.php index fb080645..6ab73e53 100644 --- a/src/EventSubscriber/OPCacheEventSubscriber.php +++ b/src/EventSubscriber/OPCacheEventSubscriber.php @@ -17,6 +17,7 @@ public static function getSubscribedEvents(): array { return [ CachePurgeRequestEvent::class => ['onPurgeRequest', 3], + '\RZ\Roadiz\Core\Events\Cache\CachePurgeRequestEvent' => ['onPurgeRequest', 3], ]; } diff --git a/src/EventSubscriber/RealmNodeInheritanceSubscriber.php b/src/EventSubscriber/RealmNodeInheritanceSubscriber.php index 22ffbd6f..b18da0fa 100644 --- a/src/EventSubscriber/RealmNodeInheritanceSubscriber.php +++ b/src/EventSubscriber/RealmNodeInheritanceSubscriber.php @@ -59,7 +59,7 @@ public function onNodeJoinedRealm(AbstractRealmNodeEvent $event): void */ $this->bus->dispatch(new Envelope(new ApplyRealmNodeInheritanceMessage( $event->getRealmNode()->getNode()->getId(), - $event->getRealmNode()->getRealm()->getId() + $event->getRealmNode()->getRealm()?->getId() ))); } @@ -70,7 +70,7 @@ public function onNodeLeftRealm(AbstractRealmNodeEvent $event): void */ $this->bus->dispatch(new Envelope(new CleanRealmNodeInheritanceMessage( $event->getRealmNode()->getNode()->getId(), - $event->getRealmNode()->getRealm()->getId() + $event->getRealmNode()->getRealm()?->getId() ))); } } diff --git a/src/EventSubscriber/ReverseProxyCacheEventSubscriber.php b/src/EventSubscriber/ReverseProxyCacheEventSubscriber.php index d59055fc..b21cb81e 100644 --- a/src/EventSubscriber/ReverseProxyCacheEventSubscriber.php +++ b/src/EventSubscriber/ReverseProxyCacheEventSubscriber.php @@ -46,7 +46,9 @@ public static function getSubscribedEvents(): array { return [ CachePurgeRequestEvent::class => ['onBanRequest', 3], + '\RZ\Roadiz\Core\Events\Cache\CachePurgeRequestEvent' => ['onBanRequest', 3], NodesSourcesUpdatedEvent::class => ['onPurgeRequest', 3], + '\RZ\Roadiz\Core\Events\NodesSources\NodesSourcesUpdatedEvent' => ['onPurgeRequest', 3], 'workflow.node.completed' => ['onNodeWorkflowCompleted', 3], ]; } diff --git a/src/EventSubscriber/RoleSubscriber.php b/src/EventSubscriber/RoleSubscriber.php index b3547449..d04eb2ab 100644 --- a/src/EventSubscriber/RoleSubscriber.php +++ b/src/EventSubscriber/RoleSubscriber.php @@ -37,8 +37,11 @@ public static function getSubscribedEvents(): array { return [ PreCreatedRoleEvent::class => 'onRoleChanged', + '\RZ\Roadiz\Core\Events\Role\PreCreatedRoleEvent' => 'onRoleChanged', PreUpdatedRoleEvent::class => 'onRoleChanged', + '\RZ\Roadiz\Core\Events\Role\PreUpdatedRoleEvent' => 'onRoleChanged', PreDeletedRoleEvent::class => 'onRoleChanged', + '\RZ\Roadiz\Core\Events\Role\PreDeletedRoleEvent' => 'onRoleChanged', ]; } diff --git a/src/EventSubscriber/SignatureSubscriber.php b/src/EventSubscriber/SignatureSubscriber.php index 68bcb97c..518e73b1 100644 --- a/src/EventSubscriber/SignatureSubscriber.php +++ b/src/EventSubscriber/SignatureSubscriber.php @@ -10,11 +10,15 @@ final class SignatureSubscriber implements EventSubscriberInterface { - public function __construct( - private readonly string $cmsVersion, - private readonly bool $hideRoadizVersion, - private readonly bool $debug = false - ) { + private string $version; + private bool $debug; + private bool $hideRoadizVersion; + + public function __construct(string $cmsVersion, bool $hideRoadizVersion, bool $debug = false) + { + $this->version = $cmsVersion; + $this->debug = $debug; + $this->hideRoadizVersion = $hideRoadizVersion; } /** * Filters the Response. @@ -30,8 +34,8 @@ public function onKernelResponse(ResponseEvent $event): void $response = $event->getResponse(); $response->headers->add(['X-Powered-By' => 'Roadiz CMS']); - if ($this->debug && $this->cmsVersion) { - $response->headers->add(['X-Version' => $this->cmsVersion]); + if ($this->debug && $this->version) { + $response->headers->add(['X-Version' => $this->version]); } } diff --git a/src/EventSubscriber/TagTimestampSubscriber.php b/src/EventSubscriber/TagTimestampSubscriber.php index 86b30b43..ca59a717 100644 --- a/src/EventSubscriber/TagTimestampSubscriber.php +++ b/src/EventSubscriber/TagTimestampSubscriber.php @@ -22,6 +22,9 @@ public static function getSubscribedEvents(): array public function onTagUpdatedEvent(TagUpdatedEvent $event): void { - $event->getTag()->setUpdatedAt(new \DateTime()); + $tag = $event->getTag(); + if ($tag instanceof AbstractDateTimed) { + $tag->setUpdatedAt(new \DateTime()); + } } } diff --git a/src/EventSubscriber/TranslationSubscriber.php b/src/EventSubscriber/TranslationSubscriber.php index d9b85f68..e7f61509 100644 --- a/src/EventSubscriber/TranslationSubscriber.php +++ b/src/EventSubscriber/TranslationSubscriber.php @@ -18,18 +18,24 @@ /** * Subscribe to Translation event to clear result cache. */ -final class TranslationSubscriber implements EventSubscriberInterface +class TranslationSubscriber implements EventSubscriberInterface { - public function __construct(private readonly ManagerRegistry $managerRegistry) + protected ManagerRegistry $managerRegistry; + + public function __construct(ManagerRegistry $managerRegistry) { + $this->managerRegistry = $managerRegistry; } public static function getSubscribedEvents(): array { return [ TranslationCreatedEvent::class => 'purgeCache', + '\RZ\Roadiz\Core\Events\Translation\TranslationCreatedEvent' => 'purgeCache', TranslationUpdatedEvent::class => 'purgeCache', + '\RZ\Roadiz\Core\Events\Translation\TranslationUpdatedEvent' => 'purgeCache', TranslationDeletedEvent::class => 'purgeCache', + '\RZ\Roadiz\Core\Events\Translation\TranslationDeletedEvent' => 'purgeCache', ]; } diff --git a/src/EventSubscriber/UserLocaleSubscriber.php b/src/EventSubscriber/UserLocaleSubscriber.php index 5b865a5c..fc75e89f 100644 --- a/src/EventSubscriber/UserLocaleSubscriber.php +++ b/src/EventSubscriber/UserLocaleSubscriber.php @@ -15,10 +15,15 @@ final class UserLocaleSubscriber implements EventSubscriberInterface { + private RequestStack $requestStack; + private TokenStorageInterface $tokenStorage; + public function __construct( - private readonly RequestStack $requestStack, - private readonly TokenStorageInterface $tokenStorage + RequestStack $requestStack, + TokenStorageInterface $tokenStorage ) { + $this->requestStack = $requestStack; + $this->tokenStorage = $tokenStorage; } /** @@ -29,7 +34,8 @@ public static function getSubscribedEvents(): array // must be registered after the default Locale listener return [ SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin', - UserUpdatedEvent::class => 'onUserUpdated', + UserUpdatedEvent::class => [['onUserUpdated']], + '\RZ\Roadiz\Core\Events\User\UserUpdatedEvent' => [['onUserUpdated']], ]; } @@ -38,10 +44,6 @@ public static function getSubscribedEvents(): array */ public function onInteractiveLogin(InteractiveLoginEvent $event): void { - if ($this->requestStack->getMainRequest()?->attributes->getBoolean('_stateless')) { - return; - } - $user = $event->getAuthenticationToken()->getUser(); if ( @@ -57,15 +59,12 @@ public function onInteractiveLogin(InteractiveLoginEvent $event): void */ public function onUserUpdated(FilterUserEvent $event): void { - if ($this->requestStack->getMainRequest()?->attributes->getBoolean('_stateless')) { - return; - } $user = $event->getUser(); if ( null !== $this->tokenStorage->getToken() && $this->tokenStorage->getToken()->getUser() instanceof User && - $this->tokenStorage->getToken()->getUserIdentifier() === $user->getUserIdentifier() + $this->tokenStorage->getToken()->getUsername() === $user->getUsername() ) { if (null === $user->getLocale()) { $this->requestStack->getSession()->remove('_locale'); diff --git a/src/Form/DataListTextType.php b/src/Form/DataListTextType.php deleted file mode 100644 index 4cf6b095..00000000 --- a/src/Form/DataListTextType.php +++ /dev/null @@ -1,43 +0,0 @@ -setRequired('listName'); - $resolver->setAllowedTypes('listName', 'string'); - $resolver->setRequired('list'); - $resolver->setAllowedTypes('list', 'array'); - } - - public function buildView(FormView $view, FormInterface $form, array $options): void - { - parent::buildView($view, $form, $options); - - $view->vars['listName'] = $options['listName']; - $view->vars['list'] = $options['list']; - } - - - public function getBlockPrefix(): string - { - return 'data_list_text'; - } - - public function getParent(): string - { - return TextType::class; - } -} diff --git a/src/Form/RoleEntityType.php b/src/Form/RoleEntityType.php index 44d53e57..6e0a4cf9 100644 --- a/src/Form/RoleEntityType.php +++ b/src/Form/RoleEntityType.php @@ -10,7 +10,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; final class RoleEntityType extends AbstractType { diff --git a/src/Form/SettingType.php b/src/Form/SettingType.php index 56392a67..cd5b2613 100644 --- a/src/Form/SettingType.php +++ b/src/Form/SettingType.php @@ -48,6 +48,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => 'visible', 'required' => false, ]) + ->add('encrypted', CheckboxType::class, [ + 'label' => 'encrypted', + 'required' => false, + ]) ->add('type', ChoiceType::class, [ 'label' => 'type', 'required' => true, diff --git a/src/ListManager/SessionListFilters.php b/src/ListManager/SessionListFilters.php deleted file mode 100644 index 4e8dc5f7..00000000 --- a/src/ListManager/SessionListFilters.php +++ /dev/null @@ -1,53 +0,0 @@ -hasSession() && - $request->getSession()->has($this->sessionIdentifier) && - $request->getSession()->get($this->sessionIdentifier) > 0 && - (!$request->query->has('item_per_page') || - $request->query->get('item_per_page') < 1) - ) { - /* - * Item count is in session - */ - $request->query->set('item_per_page', intval($request->getSession()->get($this->sessionIdentifier))); - $listManager->setItemPerPage(intval($request->getSession()->get($this->sessionIdentifier))); - } elseif ( - $request->query->has('item_per_page') && - $request->query->get('item_per_page') > 0 - ) { - /* - * Item count is in query, save it in session - */ - $request->getSession()->set($this->sessionIdentifier, intval($request->query->get('item_per_page'))); - $listManager->setItemPerPage(intval($request->query->get('item_per_page'))); - } else { - $listManager->setItemPerPage($this->defaultItemsParPage); - } - } -} diff --git a/src/Logger/DoctrineHandler.php b/src/Logger/DoctrineHandler.php index bcc9e42c..dbb55899 100644 --- a/src/Logger/DoctrineHandler.php +++ b/src/Logger/DoctrineHandler.php @@ -215,10 +215,10 @@ public function write(array $record): void ] ); } else { - $log->setUsername($user->getUserIdentifier()); + $log->setUsername($user->getUsername()); } } else { - $log->setUsername($token->getUserIdentifier()); + $log->setUsername($token->getUsername()); } } diff --git a/src/Mailer/ContactFormManager.php b/src/Mailer/ContactFormManager.php index 8fa3df41..1c340f40 100644 --- a/src/Mailer/ContactFormManager.php +++ b/src/Mailer/ContactFormManager.php @@ -56,6 +56,10 @@ class ContactFormManager extends EmailManager 'image/gif', ]; protected int $maxFileSize = 5242880; // 5MB + protected FormFactoryInterface $formFactory; + protected FormErrorSerializerInterface $formErrorSerializer; + protected ?string $recaptchaPrivateKey; + protected ?string $recaptchaPublicKey; /* * DO NOT DIRECTLY USE THIS CONSTRUCTOR @@ -63,18 +67,20 @@ class ContactFormManager extends EmailManager */ public function __construct( RequestStack $requestStack, + FormFactoryInterface $formFactory, TranslatorInterface $translator, Environment $templating, MailerInterface $mailer, Settings $settingsBag, DocumentUrlGeneratorInterface $documentUrlGenerator, - protected readonly FormFactoryInterface $formFactory, - protected readonly FormErrorSerializerInterface $formErrorSerializer, - protected readonly ?string $recaptchaPrivateKey, - protected readonly ?string $recaptchaPublicKey + FormErrorSerializerInterface $formErrorSerializer, + ?string $recaptchaPrivateKey, + ?string $recaptchaPublicKey ) { parent::__construct($requestStack, $translator, $templating, $mailer, $settingsBag, $documentUrlGenerator); + $this->formFactory = $formFactory; + $this->formErrorSerializer = $formErrorSerializer; $this->options = [ 'attr' => [ 'id' => 'contactForm', @@ -95,6 +101,8 @@ public function __construct( 'new.contact.form.%site%', ['%site%' => $this->settingsBag->get('site_name')] )); + $this->recaptchaPrivateKey = $recaptchaPrivateKey; + $this->recaptchaPublicKey = $recaptchaPublicKey; } /** @@ -122,7 +130,7 @@ public function setFormName(string $formName): ContactFormManager * * @return $this */ - public function disableCsrfProtection(): self + public function disableCsrfProtection() { $this->options['csrf_protection'] = false; return $this; @@ -145,7 +153,7 @@ public function getForm(): FormInterface * @see https://symfony.com/doc/4.4/reference/constraints/Email.html#strict * @return $this */ - public function setEmailStrictMode(bool $emailStrictMode = true): self + public function setEmailStrictMode(bool $emailStrictMode = true) { $this->emailStrictMode = $emailStrictMode; @@ -163,9 +171,9 @@ public function isEmailStrictMode(): bool * Adds email, name and message fields with their constraints. * * @param bool $useHoneypot - * @return $this + * @return ContactFormManager $this */ - public function withDefaultFields(bool $useHoneypot = true): self + public function withDefaultFields(bool $useHoneypot = true) { $this->getFormBuilder()->add('email', EmailType::class, [ 'label' => 'your.email', @@ -176,7 +184,7 @@ public function withDefaultFields(bool $useHoneypot = true): self 'message' => 'email.not.valid', 'mode' => $this->isEmailStrictMode() ? Email::VALIDATION_MODE_STRICT : - Email::VALIDATION_MODE_HTML5 + Email::VALIDATION_MODE_LOOSE ]), ], ]) @@ -209,7 +217,7 @@ public function withDefaultFields(bool $useHoneypot = true): self * @param string $honeypotName * @return $this */ - public function withHoneypot(string $honeypotName = 'eml'): self + public function withHoneypot(string $honeypotName = 'eml') { $this->getFormBuilder()->add($honeypotName, HoneypotType::class); return $this; @@ -221,7 +229,7 @@ public function withHoneypot(string $honeypotName = 'eml'): self * @param string $consentDescription * @return $this */ - public function withUserConsent(string $consentDescription = 'contact_form.user_consent'): self + public function withUserConsent(string $consentDescription = 'contact_form.user_consent') { $this->getFormBuilder()->add('consent', CheckboxType::class, [ 'label' => $consentDescription, @@ -268,7 +276,7 @@ public function getFormBuilder(): FormBuilderInterface public function withGoogleRecaptcha( string $name = 'recaptcha', string $validatorFieldName = Recaptcha::FORM_NAME - ): self { + ) { if ( !empty($this->recaptchaPublicKey) && !empty($this->recaptchaPrivateKey) @@ -626,7 +634,7 @@ public function getRedirectUrl(): ?string * * @return self */ - public function setRedirectUrl(?string $redirectUrl): self + public function setRedirectUrl(?string $redirectUrl) { $this->redirectUrl = $redirectUrl; @@ -638,7 +646,7 @@ public function setRedirectUrl(?string $redirectUrl): self * * @return int */ - public function getMaxFileSize(): int + public function getMaxFileSize() { return $this->maxFileSize; } @@ -650,7 +658,7 @@ public function getMaxFileSize(): int * * @return self */ - public function setMaxFileSize($maxFileSize): self + public function setMaxFileSize($maxFileSize) { $this->maxFileSize = (int) $maxFileSize; @@ -662,7 +670,7 @@ public function setMaxFileSize($maxFileSize): self * * @return array */ - public function getAllowedMimeTypes(): array + public function getAllowedMimeTypes() { return $this->allowedMimeTypes; } @@ -674,7 +682,7 @@ public function getAllowedMimeTypes(): array * * @return self */ - public function setAllowedMimeTypes(array $allowedMimeTypes): self + public function setAllowedMimeTypes(array $allowedMimeTypes) { $this->allowedMimeTypes = $allowedMimeTypes; @@ -684,7 +692,7 @@ public function setAllowedMimeTypes(array $allowedMimeTypes): self /** * @return array */ - public function getOptions(): array + public function getOptions() { return $this->options; } @@ -692,9 +700,9 @@ public function getOptions(): array /** * @param array $options * - * @return $this + * @return ContactFormManager */ - public function setOptions(array $options): self + public function setOptions($options) { $this->options = $options; diff --git a/src/Mailer/EmailManager.php b/src/Mailer/EmailManager.php index 73890767..eb84898f 100644 --- a/src/Mailer/EmailManager.php +++ b/src/Mailer/EmailManager.php @@ -33,30 +33,43 @@ class EmailManager protected ?Address $origin = null; protected string $successMessage = 'email.successfully.sent'; protected string $failMessage = 'email.has.errors'; + protected TranslatorInterface $translator; + protected Environment $templating; + protected MailerInterface $mailer; protected ?string $emailTemplate = null; protected ?string $emailPlainTextTemplate = null; protected string $emailStylesheet; + protected RequestStack $requestStack; protected array $assignation; protected ?Email $message; + protected ?Settings $settingsBag; + protected ?DocumentUrlGeneratorInterface $documentUrlGenerator; /** @var File[] */ protected array $files = []; /** @var array */ protected array $resources = []; public function __construct( - protected readonly RequestStack $requestStack, - protected readonly TranslatorInterface $translator, - protected readonly Environment $templating, - protected readonly MailerInterface $mailer, - protected readonly Settings $settingsBag, - protected readonly DocumentUrlGeneratorInterface $documentUrlGenerator + RequestStack $requestStack, + TranslatorInterface $translator, + Environment $templating, + MailerInterface $mailer, + ?Settings $settingsBag = null, + ?DocumentUrlGeneratorInterface $documentUrlGenerator = null ) { + $this->requestStack = $requestStack; + $this->translator = $translator; + $this->mailer = $mailer; + $this->templating = $templating; $this->assignation = []; $this->message = null; + /* * Sets a default CSS for emails. */ $this->emailStylesheet = dirname(__DIR__) . '/../css/transactionalStyles.css'; + $this->settingsBag = $settingsBag; + $this->documentUrlGenerator = $documentUrlGenerator; } /** @@ -265,7 +278,7 @@ public function getReceiverEmail(): ?string * @return $this * @throws \Exception */ - public function setReceiver(mixed $receiver): static + public function setReceiver($receiver): static { if ($receiver instanceof Address) { $this->receiver = [$receiver]; @@ -320,7 +333,7 @@ public function getSenderEmail(): ?string * @return $this * @throws \Exception */ - public function setSender(mixed $sender): static + public function setSender($sender): static { if ($sender instanceof Address) { $this->sender = [$sender]; @@ -388,6 +401,16 @@ public function getTranslator(): TranslatorInterface return $this->translator; } + /** + * @param TranslatorInterface $translator + * @return $this + */ + public function setTranslator(TranslatorInterface $translator): static + { + $this->translator = $translator; + return $this; + } + /** * @return Environment */ @@ -396,6 +419,16 @@ public function getTemplating(): Environment return $this->templating; } + /** + * @param Environment $templating + * @return $this + */ + public function setTemplating(Environment $templating): static + { + $this->templating = $templating; + return $this; + } + /** * @return MailerInterface */ @@ -404,6 +437,16 @@ public function getMailer(): MailerInterface return $this->mailer; } + /** + * @param MailerInterface $mailer + * @return $this + */ + public function setMailer(MailerInterface $mailer): static + { + $this->mailer = $mailer; + return $this; + } + /** * @return string|null */ diff --git a/src/Message/Handler/ApplyRealmNodeInheritanceMessageHandler.php b/src/Message/Handler/ApplyRealmNodeInheritanceMessageHandler.php index bae2c618..8f4199f0 100644 --- a/src/Message/Handler/ApplyRealmNodeInheritanceMessageHandler.php +++ b/src/Message/Handler/ApplyRealmNodeInheritanceMessageHandler.php @@ -54,7 +54,9 @@ public function __invoke(ApplyRealmNodeInheritanceMessage $message): void foreach ($childrenIds as $childId) { /** @var Node|null $child */ - $child = $nodeRepository->find($childId); + $child = $this->managerRegistry + ->getRepository(Node::class) + ->find($childId); if (null === $child) { continue; } diff --git a/src/Message/Handler/DeleteNodeTypeMessageHandler.php b/src/Message/Handler/DeleteNodeTypeMessageHandler.php index 9a9c4723..6159b918 100644 --- a/src/Message/Handler/DeleteNodeTypeMessageHandler.php +++ b/src/Message/Handler/DeleteNodeTypeMessageHandler.php @@ -19,11 +19,15 @@ final class DeleteNodeTypeMessageHandler implements MessageHandlerInterface { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly HandlerFactoryInterface $handlerFactory, - private readonly MessageBusInterface $messageBus - ) { + private ManagerRegistry $managerRegistry; + private HandlerFactoryInterface $handlerFactory; + private MessageBusInterface $messageBus; + + public function __construct(ManagerRegistry $managerRegistry, HandlerFactoryInterface $handlerFactory, MessageBusInterface $messageBus) + { + $this->managerRegistry = $managerRegistry; + $this->handlerFactory = $handlerFactory; + $this->messageBus = $messageBus; } /** diff --git a/src/Message/Handler/HttpRequestMessageHandler.php b/src/Message/Handler/HttpRequestMessageHandler.php index 35f275e5..b7848ed5 100644 --- a/src/Message/Handler/HttpRequestMessageHandler.php +++ b/src/Message/Handler/HttpRequestMessageHandler.php @@ -7,15 +7,24 @@ use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use RZ\Roadiz\CoreBundle\Message\HttpRequestMessage; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; final class HttpRequestMessageHandler implements MessageHandlerInterface { - public function __construct( - private readonly LoggerInterface $logger - ) { + private LoggerInterface $logger; + private ?Client $client; + + /** + * @param Client|null $client + * @param LoggerInterface|null $logger + */ + public function __construct(Client $client = null, ?LoggerInterface $logger = null) + { + $this->logger = $logger ?? new NullLogger(); + $this->client = $client ?? new Client(); } public function __invoke(HttpRequestMessage $message): void @@ -26,8 +35,7 @@ public function __invoke(HttpRequestMessage $message): void $message->getRequest()->getMethod(), $message->getRequest()->getUri() )); - $client = new Client(); - $client->send($message->getRequest(), $message->getOptions()); + $this->client->send($message->getRequest(), $message->getOptions()); } catch (GuzzleException $exception) { throw new UnrecoverableMessageHandlingException($exception->getMessage(), 0, $exception); } diff --git a/src/Message/Handler/PurgeReverseProxyCacheMessageHandler.php b/src/Message/Handler/PurgeReverseProxyCacheMessageHandler.php index 13da36f7..6082040d 100644 --- a/src/Message/Handler/PurgeReverseProxyCacheMessageHandler.php +++ b/src/Message/Handler/PurgeReverseProxyCacheMessageHandler.php @@ -20,12 +20,27 @@ final class PurgeReverseProxyCacheMessageHandler implements MessageHandlerInterface { + private UrlGeneratorInterface $urlGenerator; + private ReverseProxyCacheLocator $reverseProxyCacheLocator; + private MessageBusInterface $bus; + private ManagerRegistry $managerRegistry; + + /** + * @param MessageBusInterface $bus + * @param UrlGeneratorInterface $urlGenerator + * @param ReverseProxyCacheLocator $reverseProxyCacheLocator + * @param ManagerRegistry $managerRegistry + */ public function __construct( - private readonly MessageBusInterface $bus, - private readonly UrlGeneratorInterface $urlGenerator, - private readonly ReverseProxyCacheLocator $reverseProxyCacheLocator, - private readonly ManagerRegistry $managerRegistry + MessageBusInterface $bus, + UrlGeneratorInterface $urlGenerator, + ReverseProxyCacheLocator $reverseProxyCacheLocator, + ManagerRegistry $managerRegistry ) { + $this->urlGenerator = $urlGenerator; + $this->reverseProxyCacheLocator = $reverseProxyCacheLocator; + $this->managerRegistry = $managerRegistry; + $this->bus = $bus; } public function __invoke(PurgeReverseProxyCacheMessage $message): void diff --git a/src/Message/Handler/SearchRealmNodeInheritanceMessageHandler.php b/src/Message/Handler/SearchRealmNodeInheritanceMessageHandler.php index 277bf6a5..145bc5e8 100644 --- a/src/Message/Handler/SearchRealmNodeInheritanceMessageHandler.php +++ b/src/Message/Handler/SearchRealmNodeInheritanceMessageHandler.php @@ -21,12 +21,21 @@ final class SearchRealmNodeInheritanceMessageHandler implements MessageHandlerInterface { + private ManagerRegistry $managerRegistry; + private HandlerFactoryInterface $handlerFactory; + private MessageBusInterface $bus; + private LoggerInterface $logger; + public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly HandlerFactoryInterface $handlerFactory, - private readonly MessageBusInterface $bus, - private readonly LoggerInterface $logger + ManagerRegistry $managerRegistry, + HandlerFactoryInterface $handlerFactory, + MessageBusInterface $bus, + LoggerInterface $logger ) { + $this->managerRegistry = $managerRegistry; + $this->handlerFactory = $handlerFactory; + $this->bus = $bus; + $this->logger = $logger; } public function __invoke(SearchRealmNodeInheritanceMessage $message): void @@ -56,7 +65,7 @@ private function clearAnyExistingRealmNodes(Node $node): void $this->logger->info('Clean existing RealmNode information'); $this->bus->dispatch(new Envelope(new CleanRealmNodeInheritanceMessage( $autoRealmNode->getNode()->getId(), - $autoRealmNode->getRealm()->getId() + null !== $autoRealmNode->getRealm() ? $autoRealmNode->getRealm()->getId() : null ))); } } @@ -81,7 +90,7 @@ private function applyRootRealmNodes(Node $node): void $this->logger->info('Apply new root RealmNode information'); $this->bus->dispatch(new Envelope(new ApplyRealmNodeInheritanceMessage( $rootRealmNode->getNode()->getId(), - $rootRealmNode->getRealm()->getId() + null !== $rootRealmNode->getRealm() ? $rootRealmNode->getRealm()->getId() : null ))); } } diff --git a/src/Message/Handler/UpdateDoctrineSchemaMessageHandler.php b/src/Message/Handler/UpdateDoctrineSchemaMessageHandler.php index e9fc413d..e9c384f5 100644 --- a/src/Message/Handler/UpdateDoctrineSchemaMessageHandler.php +++ b/src/Message/Handler/UpdateDoctrineSchemaMessageHandler.php @@ -10,8 +10,11 @@ final class UpdateDoctrineSchemaMessageHandler implements MessageHandlerInterface { - public function __construct(private readonly SchemaUpdater $schemaUpdater) + private SchemaUpdater $schemaUpdater; + + public function __construct(SchemaUpdater $schemaUpdater) { + $this->schemaUpdater = $schemaUpdater; } /** diff --git a/src/Message/Handler/UpdateNodeTypeSchemaMessageHandler.php b/src/Message/Handler/UpdateNodeTypeSchemaMessageHandler.php index a14b07d1..6a0185ec 100644 --- a/src/Message/Handler/UpdateNodeTypeSchemaMessageHandler.php +++ b/src/Message/Handler/UpdateNodeTypeSchemaMessageHandler.php @@ -17,11 +17,15 @@ final class UpdateNodeTypeSchemaMessageHandler implements MessageHandlerInterface { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly HandlerFactoryInterface $handlerFactory, - private readonly MessageBusInterface $messageBus - ) { + private ManagerRegistry $managerRegistry; + private HandlerFactoryInterface $handlerFactory; + private MessageBusInterface $messageBus; + + public function __construct(ManagerRegistry $managerRegistry, HandlerFactoryInterface $handlerFactory, MessageBusInterface $messageBus) + { + $this->managerRegistry = $managerRegistry; + $this->handlerFactory = $handlerFactory; + $this->messageBus = $messageBus; } public function __invoke(UpdateNodeTypeSchemaMessage $message): void diff --git a/src/Model/AttributeGroupTrait.php b/src/Model/AttributeGroupTrait.php index 398d03d5..9bd498c7 100644 --- a/src/Model/AttributeGroupTrait.php +++ b/src/Model/AttributeGroupTrait.php @@ -32,7 +32,7 @@ trait AttributeGroupTrait #[ ORM\OneToMany(mappedBy: "group", targetEntity: AttributeInterface::class), Serializer\Groups(["attribute_group"]), - Serializer\Type("ArrayCollection") + Serializer\Type("ArrayCollection") ] protected Collection $attributes; @@ -40,14 +40,9 @@ trait AttributeGroupTrait * @var Collection */ #[ - ORM\OneToMany( - mappedBy: "attributeGroup", - targetEntity: AttributeGroupTranslationInterface::class, - cascade: ["all"], - orphanRemoval: true - ), + ORM\OneToMany(mappedBy: "attributeGroup", targetEntity: AttributeGroupTranslationInterface::class, cascade: ["all"]), Serializer\Groups(["attribute_group", "attribute", "node", "nodes_sources"]), - Serializer\Type("ArrayCollection"), + Serializer\Type("ArrayCollection"), Serializer\Accessor(getter: "getAttributeGroupTranslations", setter: "setAttributeGroupTranslations") ] protected Collection $attributeGroupTranslations; diff --git a/src/Model/AttributeGroupTranslationTrait.php b/src/Model/AttributeGroupTranslationTrait.php index 34ec8324..f89961f2 100644 --- a/src/Model/AttributeGroupTranslationTrait.php +++ b/src/Model/AttributeGroupTranslationTrait.php @@ -13,12 +13,12 @@ trait AttributeGroupTranslationTrait { #[ ORM\ManyToOne(targetEntity: "RZ\Roadiz\Core\AbstractEntities\TranslationInterface"), - ORM\JoinColumn(name: "translation_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(name: "translation_id", onDelete: "CASCADE"), Serializer\Groups(["attribute_group", "attribute", "node", "nodes_sources"]), Serializer\Type("RZ\Roadiz\Core\AbstractEntities\TranslationInterface"), Serializer\Accessor(getter: "getTranslation", setter: "setTranslation") ] - protected TranslationInterface $translation; + protected ?TranslationInterface $translation = null; #[ ORM\Column(type: "string", length: 255, unique: false, nullable: false), @@ -30,10 +30,10 @@ trait AttributeGroupTranslationTrait #[ ORM\ManyToOne(targetEntity: AttributeGroupInterface::class, cascade: ["persist"], inversedBy: "attributeGroupTranslations"), - ORM\JoinColumn(name: "attribute_group_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(name: "attribute_group_id", referencedColumnName: "id", nullable: true, onDelete: "CASCADE"), Serializer\Exclude ] - protected AttributeGroupInterface $attributeGroup; + protected ?AttributeGroupInterface $attributeGroup = null; /** * @return string @@ -45,6 +45,7 @@ public function getName(): string /** * @param string $value + * * @return self */ public function setName(string $value) @@ -55,7 +56,8 @@ public function setName(string $value) /** * @param TranslationInterface $translation - * @return self + * + * @return mixed */ public function setTranslation(TranslationInterface $translation) { @@ -63,7 +65,10 @@ public function setTranslation(TranslationInterface $translation) return $this; } - public function getTranslation(): TranslationInterface + /** + * @return TranslationInterface|null + */ + public function getTranslation(): ?TranslationInterface { return $this->translation; } @@ -78,7 +83,8 @@ public function getAttributeGroup(): AttributeGroupInterface /** * @param AttributeGroupInterface $attributeGroup - * @return self + * + * @return mixed */ public function setAttributeGroup(AttributeGroupInterface $attributeGroup) { diff --git a/src/Model/AttributeTrait.php b/src/Model/AttributeTrait.php index bb52d5e3..2bdde936 100644 --- a/src/Model/AttributeTrait.php +++ b/src/Model/AttributeTrait.php @@ -6,8 +6,6 @@ use Doctrine\Common\Collections\Collection; use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; -use RZ\Roadiz\CoreBundle\Entity\AttributeGroup; -use RZ\Roadiz\CoreBundle\Entity\AttributeTranslation; use RZ\Roadiz\Utils\StringHandler; use Doctrine\ORM\Mapping as ORM; use JMS\Serializer\Annotation as Serializer; @@ -62,12 +60,12 @@ trait AttributeTrait ORM\JoinColumn(name: "group_id", onDelete: "SET NULL"), Serializer\Groups(["attribute", "node", "nodes_sources"]), SymfonySerializer\Groups(["attribute", "node", "nodes_sources"]), - Serializer\Type(AttributeGroup::class) + Serializer\Type("RZ\Roadiz\CoreBundle\Model\AttributeGroupInterface") ] protected ?AttributeGroupInterface $group = null; /** - * @var Collection + * @var Collection */ #[ ORM\OneToMany( @@ -79,7 +77,7 @@ trait AttributeTrait ), Serializer\Groups(["attribute", "node", "nodes_sources"]), SymfonySerializer\Groups(["attribute", "node", "nodes_sources"]), - Serializer\Type("ArrayCollection<" . AttributeTranslation::class . ">"), + Serializer\Type("ArrayCollection"), Serializer\Accessor(getter: "getAttributeTranslations", setter: "setAttributeTranslations") ] protected Collection $attributeTranslations; diff --git a/src/Model/AttributeTranslationTrait.php b/src/Model/AttributeTranslationTrait.php index 9926436f..b415ee86 100644 --- a/src/Model/AttributeTranslationTrait.php +++ b/src/Model/AttributeTranslationTrait.php @@ -13,12 +13,12 @@ trait AttributeTranslationTrait { #[ ORM\ManyToOne(targetEntity: TranslationInterface::class), - ORM\JoinColumn(name: "translation_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(onDelete: "CASCADE"), Serializer\Groups(["attribute", "node", "nodes_sources"]), Serializer\Type("RZ\Roadiz\Core\AbstractEntities\TranslationInterface"), Serializer\Accessor(getter: "getTranslation", setter: "setTranslation") ] - protected TranslationInterface $translation; + protected ?TranslationInterface $translation = null; #[ ORM\Column(type: "string", length: 250, unique: false, nullable: false), @@ -40,10 +40,10 @@ trait AttributeTranslationTrait #[ ORM\ManyToOne(targetEntity: AttributeInterface::class, cascade: ["persist"], inversedBy: "attributeTranslations"), - ORM\JoinColumn(name: "attribute_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(referencedColumnName: "id", onDelete: "CASCADE"), Serializer\Exclude ] - protected AttributeInterface $attribute; + protected ?AttributeInterface $attribute = null; /** * @return string|null @@ -66,6 +66,7 @@ public function setLabel(?string $label) /** * @param TranslationInterface $translation + * * @return $this */ public function setTranslation(TranslationInterface $translation) @@ -74,7 +75,10 @@ public function setTranslation(TranslationInterface $translation) return $this; } - public function getTranslation(): TranslationInterface + /** + * @return TranslationInterface|null + */ + public function getTranslation(): ?TranslationInterface { return $this->translation; } @@ -89,6 +93,7 @@ public function getAttribute(): AttributeInterface /** * @param AttributeInterface $attribute + * * @return $this */ public function setAttribute(AttributeInterface $attribute) diff --git a/src/Model/AttributeValueTrait.php b/src/Model/AttributeValueTrait.php index ef154cfc..91edbd7c 100644 --- a/src/Model/AttributeValueTrait.php +++ b/src/Model/AttributeValueTrait.php @@ -15,7 +15,7 @@ trait AttributeValueTrait { #[ ORM\ManyToOne(targetEntity: AttributeInterface::class, fetch: "EAGER", inversedBy: "attributeValues"), - ORM\JoinColumn(name: "attribute_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(name: "attribute_id", referencedColumnName: "id", onDelete: "CASCADE"), Serializer\Groups(["attribute", "node", "nodes_sources"]), Serializer\Type("RZ\Roadiz\CoreBundle\Entity\Attribute"), ApiFilter(BaseFilter\SearchFilter::class, properties: [ @@ -38,7 +38,7 @@ trait AttributeValueTrait "attribute.weight" => "DESC", ]) ] - protected AttributeInterface $attribute; + protected ?AttributeInterface $attribute = null; /** * @var Collection @@ -76,7 +76,8 @@ public function getAttribute(): ?AttributeInterface /** * @param AttributeInterface $attribute - * @return self + * + * @return static */ public function setAttribute(AttributeInterface $attribute) { diff --git a/src/Model/AttributeValueTranslationTrait.php b/src/Model/AttributeValueTranslationTrait.php index 57cb8ae2..abb93cb0 100644 --- a/src/Model/AttributeValueTranslationTrait.php +++ b/src/Model/AttributeValueTranslationTrait.php @@ -13,12 +13,12 @@ trait AttributeValueTranslationTrait { #[ ORM\ManyToOne(targetEntity: TranslationInterface::class), - ORM\JoinColumn(name: "translation_id", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(name: "translation_id", referencedColumnName: "id", onDelete: "CASCADE"), Serializer\Groups(["attribute", "node", "nodes_sources"]), Serializer\Type("RZ\Roadiz\Core\AbstractEntities\TranslationInterface"), Serializer\Accessor(getter: "getTranslation", setter: "setTranslation") ] - protected TranslationInterface $translation; + protected ?TranslationInterface $translation = null; #[ ORM\Column(type: "string", length: 255, unique: false, nullable: true), @@ -30,16 +30,16 @@ trait AttributeValueTranslationTrait #[ ORM\ManyToOne(targetEntity: AttributeValueInterface::class, cascade: ["persist"], inversedBy: "attributeValueTranslations"), - ORM\JoinColumn(name: "attribute_value", referencedColumnName: "id", nullable: false, onDelete: "CASCADE"), + ORM\JoinColumn(name: "attribute_value", referencedColumnName: "id", onDelete: "CASCADE"), Serializer\Exclude ] - protected AttributeValueInterface $attributeValue; + protected ?AttributeValueInterface $attributeValue = null; /** * @return bool|\DateTime|float|int|string|null * @throws \Exception */ - public function getValue(): bool|\DateTime|float|int|string|null + public function getValue() { if (null === $this->value) { return null; @@ -56,9 +56,9 @@ public function getValue(): bool|\DateTime|float|int|string|null /** * @param mixed|null $value * - * @return self + * @return static */ - public function setValue(mixed $value) + public function setValue($value) { if (null === $value) { $this->value = null; @@ -86,7 +86,8 @@ public function setValue(mixed $value) /** * @param TranslationInterface $translation - * @return self + * + * @return static */ public function setTranslation(TranslationInterface $translation) { @@ -94,7 +95,10 @@ public function setTranslation(TranslationInterface $translation) return $this; } - public function getTranslation(): TranslationInterface + /** + * @return TranslationInterface|null + */ + public function getTranslation(): ?TranslationInterface { return $this->translation; } @@ -109,7 +113,8 @@ public function getAttributeValue(): AttributeValueInterface /** * @param AttributeValueInterface $attributeValue - * @return self + * + * @return static */ public function setAttributeValue(AttributeValueInterface $attributeValue) { @@ -117,7 +122,10 @@ public function setAttributeValue(AttributeValueInterface $attributeValue) return $this; } - public function getAttribute(): AttributeInterface + /** + * @return AttributeInterface|null + */ + public function getAttribute(): ?AttributeInterface { return $this->getAttributeValue()->getAttribute(); } diff --git a/src/Node/NodeDuplicator.php b/src/Node/NodeDuplicator.php index da995e69..0c1d6131 100644 --- a/src/Node/NodeDuplicator.php +++ b/src/Node/NodeDuplicator.php @@ -102,7 +102,8 @@ private function doDuplicate(Node &$node): Node $nsDoc->setNodeSource($nodeSource); $doc = $nsDoc->getDocument(); $nsDoc->setDocument($doc); - $nsDoc->setFieldName($nsDoc->getFieldName()); + $f = $nsDoc->getField(); + $nsDoc->setField($f); $this->objectManager->persist($nsDoc); } } @@ -140,11 +141,9 @@ private function doDuplicate(Node &$node): Node */ private function doDuplicateNodeRelations(Node $node): Node { - /** @var NodesToNodes[] $nodeRelations */ - $nodeRelations = $node->getBNodes()->toArray(); + $nodeRelations = new ArrayCollection($node->getBNodes()->toArray()); foreach ($nodeRelations as $position => $nodeRelation) { - $ntn = new NodesToNodes($node, $nodeRelation->getNodeB()); - $ntn->setFieldName($nodeRelation->getFieldName()); + $ntn = new NodesToNodes($node, $nodeRelation->getNodeB(), $nodeRelation->getField()); $ntn->setPosition($position); $this->objectManager->persist($ntn); } diff --git a/src/Node/NodeFactory.php b/src/Node/NodeFactory.php index 1cbc852b..49aeff5c 100644 --- a/src/Node/NodeFactory.php +++ b/src/Node/NodeFactory.php @@ -17,10 +17,19 @@ final class NodeFactory { + private ManagerRegistry $managerRegistry; + private NodeNamePolicyInterface $nodeNamePolicy; + + /** + * @param ManagerRegistry $managerRegistry + * @param NodeNamePolicyInterface $nodeNamePolicy + */ public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly NodeNamePolicyInterface $nodeNamePolicy + ManagerRegistry $managerRegistry, + NodeNamePolicyInterface $nodeNamePolicy ) { + $this->nodeNamePolicy = $nodeNamePolicy; + $this->managerRegistry = $managerRegistry; } public function create( @@ -43,14 +52,12 @@ public function create( } if ($node === null) { - $node = new Node(); - $node->setNodeType($type); + $node = new Node($type); } - if ($node->getNodeType() instanceof NodeType) { - $node->setTtl($node->getNodeType()->getDefaultTtl()); + if ($type instanceof NodeType) { + $node->setTtl($type->getDefaultTtl()); } - if (null !== $parent) { $node->setParent($parent); } @@ -93,12 +100,10 @@ public function createWithUrlAlias( ?Node $parent = null ): Node { $node = $this->create($title, $type, $translation, $node, $parent); - $nodeSource = $node->getNodeSources()->first(); /** @var UrlAliasRepository $repository */ $repository = $this->managerRegistry->getRepository(UrlAlias::class); - if (false !== $nodeSource && false === $repository->exists($urlAlias)) { - $alias = new UrlAlias(); - $alias->setNodeSource($nodeSource); + if (false === $repository->exists($urlAlias)) { + $alias = new UrlAlias($node->getNodeSources()->first() ?: null); $alias->setAlias($urlAlias); $this->managerRegistry->getManagerForClass(UrlAlias::class)->persist($alias); } diff --git a/src/Node/NodeTranstyper.php b/src/Node/NodeTranstyper.php index 2109757e..f5a150e1 100644 --- a/src/Node/NodeTranstyper.php +++ b/src/Node/NodeTranstyper.php @@ -97,7 +97,6 @@ public function transtype(Node $node, NodeTypeInterface $destinationNodeType, bo } $this->logger->debug('Get matching fields'); - /** @var class-string $sourceClass */ $sourceClass = $destinationNodeType->getSourceEntityFullQualifiedClassName(); /* @@ -165,7 +164,7 @@ protected function removeOldSources(Node $node, array &$sources): void * @param Node $node * @param NodesSources $existingSource * @param TranslationInterface $translation - * @param class-string $sourceClass + * @param string $sourceClass * @param array $fieldAssociations * @param array $existingRedirections * @return NodesSources @@ -202,8 +201,7 @@ protected function doTranstypeSingleSource( */ $documents = $existingSource->getDocumentsByFieldsWithName($oldField->getName()); foreach ($documents as $document) { - $nsDoc = new NodesSourcesDocuments($source, $document); - $nsDoc->setFieldName($matchingField->getName()); + $nsDoc = new NodesSourcesDocuments($source, $document, $matchingField); $this->getManager()->persist($nsDoc); $source->getDocumentsByFields()->add($nsDoc); } @@ -216,8 +214,7 @@ protected function doTranstypeSingleSource( */ /** @var UrlAlias $urlAlias */ foreach ($existingSource->getUrlAliases() as $urlAlias) { - $newUrlAlias = new UrlAlias(); - $newUrlAlias->setNodeSource($source); + $newUrlAlias = new UrlAlias($source); $this->getManager()->persist($newUrlAlias); $newUrlAlias->setAlias($urlAlias->getAlias()); $source->addUrlAlias($newUrlAlias); @@ -259,7 +256,6 @@ protected function mockTranstype(NodeTypeInterface $nodeType): void * transtype, not to get an orphan node. */ $node = new Node(); - $node->setNodeType($nodeType); $node->setNodeName('testing_before_transtype' . $uniqueId); $this->getManager()->persist($node); diff --git a/src/Node/UniqueNodeGenerator.php b/src/Node/UniqueNodeGenerator.php index 718cea75..cdbedc64 100644 --- a/src/Node/UniqueNodeGenerator.php +++ b/src/Node/UniqueNodeGenerator.php @@ -18,7 +18,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; class UniqueNodeGenerator { @@ -49,8 +49,7 @@ public function generate( bool $pushToTop = false ): NodesSources { $name = $nodeType->getDisplayName() . " " . uniqid(); - $node = new Node(); - $node->setNodeType($nodeType); + $node = new Node($nodeType); $node->setTtl($nodeType->getDefaultTtl()); if (null !== $tag) { diff --git a/src/Node/UniversalDataDuplicator.php b/src/Node/UniversalDataDuplicator.php index c806f3ae..41b0ae45 100644 --- a/src/Node/UniversalDataDuplicator.php +++ b/src/Node/UniversalDataDuplicator.php @@ -5,7 +5,6 @@ namespace RZ\Roadiz\CoreBundle\Node; use Doctrine\Persistence\ManagerRegistry; -use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\Core\AbstractEntities\AbstractField; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\NodesSourcesDocuments; @@ -111,10 +110,15 @@ private function hasDefaultTranslation(NodesSources $source): bool return $sourceCount === 1; } + /** + * @param NodesSources $universalSource + * @param NodesSources $destSource + * @param NodeTypeField $field + */ protected function duplicateNonVirtualField( NodesSources $universalSource, NodesSources $destSource, - NodeTypeFieldInterface $field + NodeTypeField $field ): void { $getter = $field->getGetterName(); $setter = $field->getSetterName(); @@ -122,18 +126,23 @@ protected function duplicateNonVirtualField( $destSource->$setter($universalSource->$getter()); } + /** + * @param NodesSources $universalSource + * @param NodesSources $destSource + * @param NodeTypeField $field + */ protected function duplicateDocumentsField( NodesSources $universalSource, NodesSources $destSource, - NodeTypeFieldInterface $field + NodeTypeField $field ): void { $newDocuments = $this->managerRegistry ->getRepository(NodesSourcesDocuments::class) - ->findBy(['nodeSource' => $universalSource, 'fieldName' => $field->getName()]); + ->findBy(['nodeSource' => $universalSource, 'field' => $field]); $formerDocuments = $this->managerRegistry ->getRepository(NodesSourcesDocuments::class) - ->findBy(['nodeSource' => $destSource, 'fieldName' => $field->getName()]); + ->findBy(['nodeSource' => $destSource, 'field' => $field]); $manager = $this->managerRegistry->getManagerForClass(NodesSourcesDocuments::class); if (null === $manager) { diff --git a/src/NodeType/ApiResourceGenerator.php b/src/NodeType/ApiResourceGenerator.php index 6506c450..548ae63d 100644 --- a/src/NodeType/ApiResourceGenerator.php +++ b/src/NodeType/ApiResourceGenerator.php @@ -4,32 +4,23 @@ namespace RZ\Roadiz\CoreBundle\NodeType; -use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GetCollection; use Doctrine\Inflector\InflectorFactory; use LogicException; use Psr\Log\LoggerInterface; use RZ\Roadiz\Contracts\NodeType\NodeTypeInterface; -use RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController; -use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\String\UnicodeString; use Symfony\Component\Yaml\Yaml; final class ApiResourceGenerator { - /** - * @param ApiResourceOperationNameGenerator $apiResourceOperationNameGenerator - * @param string $apiResourcesDir - * @param LoggerInterface $logger - * @param class-string $webResponseClass - */ - public function __construct( - private readonly ApiResourceOperationNameGenerator $apiResourceOperationNameGenerator, - private readonly string $apiResourcesDir, - private readonly LoggerInterface $logger, - private readonly string $webResponseClass - ) { + private string $apiResourcesDir; + private LoggerInterface $logger; + + public function __construct(string $apiResourcesDir, LoggerInterface $logger) + { + $this->apiResourcesDir = $apiResourcesDir; + $this->logger = $logger; } /** @@ -45,33 +36,11 @@ public function generate(NodeTypeInterface $nodeType): ?string } $resourcePath = $this->getResourcePath($nodeType); - $webResponseResourcePath = $this->getWebResponseResourcePath(); - - if (!$filesystem->exists($webResponseResourcePath)) { - $filesystem->dumpFile( - $webResponseResourcePath, - Yaml::dump([ - 'resources' => [ - $this->webResponseClass => [ - 'operations' => [], - ] - ] - ], 7) - ); - } - $filesystem->dumpFile( - $webResponseResourcePath, - Yaml::dump($this->addWebResponseResourceOperation($nodeType, $webResponseResourcePath), 7) - ); - $this->logger->info('API WebResponse config file has been updated.', [ - 'file' => $webResponseResourcePath, - ]); - \clearstatcache(true, $webResponseResourcePath); if (!$filesystem->exists($resourcePath)) { $filesystem->dumpFile( $resourcePath, - Yaml::dump($this->getApiResourceDefinition($nodeType), 7) + Yaml::dump($this->getApiResourceDefinition($nodeType), 6) ); $this->logger->info('API resource config file has been generated.', [ 'nodeType' => $nodeType->getName(), @@ -93,18 +62,6 @@ public function remove(NodeTypeInterface $nodeType): void } $resourcePath = $this->getResourcePath($nodeType); - $webResponseResourcePath = $this->getWebResponseResourcePath(); - - if ($filesystem->exists($webResponseResourcePath)) { - $filesystem->dumpFile( - $webResponseResourcePath, - Yaml::dump($this->removeWebResponseResourceOperation($nodeType, $webResponseResourcePath), 7) - ); - $this->logger->info('API WebResponse config file has been updated.', [ - 'file' => $webResponseResourcePath, - ]); - \clearstatcache(true, $webResponseResourcePath); - } if ($filesystem->exists($resourcePath)) { $filesystem->remove($resourcePath); @@ -116,7 +73,7 @@ public function remove(NodeTypeInterface $nodeType): void } } - public function getResourcePath(NodeTypeInterface $nodeType): string + protected function getResourcePath(NodeTypeInterface $nodeType): string { return $this->apiResourcesDir . '/' . (new UnicodeString($nodeType->getName())) ->lower() @@ -125,11 +82,6 @@ public function getResourcePath(NodeTypeInterface $nodeType): string ->toString(); } - protected function getWebResponseResourcePath(): string - { - return $this->apiResourcesDir . '/web_response.yml'; - } - protected function getResourceName(string $nodeTypeName): string { return (new UnicodeString($nodeTypeName)) @@ -150,118 +102,13 @@ protected function getApiResourceDefinition(NodeTypeInterface $nodeType): array ->trimStart('\\') ->toString(); - return [ - 'resources' => [ - $fqcn => [ - 'shortName' => $nodeType->getName(), - 'types' => [$nodeType->getName()], - 'operations' => [ - ...$this->getCollectionOperations($nodeType), - ...$this->getItemOperations($nodeType) - ], - ] - ] - ]; - } - - protected function addWebResponseResourceOperation(NodeTypeInterface $nodeType, string $webResponseResourcePath): array - { - $getByPathOperationName = $this->apiResourceOperationNameGenerator->generateGetByPath( - $nodeType->getSourceEntityFullQualifiedClassName() - ); - $webResponseResource = Yaml::parseFile($webResponseResourcePath); - - if (!\array_key_exists($this->webResponseClass, $webResponseResource['resources'])) { - $webResponseResource = [ - 'resources' => [ - $this->webResponseClass => [ - 'operations' => [], - ] - ], - ]; - } - - if (\array_key_exists('operations', $webResponseResource['resources'][$this->webResponseClass])) { - $operations = $webResponseResource['resources'][$this->webResponseClass]['operations']; - } else { - $operations = []; - } - - if (!$nodeType->isReachable()) { - // Do not add operation if node-type is not reachable - return $webResponseResource; - } - if (\array_key_exists($getByPathOperationName, $operations)) { - // Do not add operation if already exists - return $webResponseResource; - } - - $groups = $this->getItemOperationSerializationGroups($nodeType); - $operations[$getByPathOperationName] = [ - 'method' => 'GET', - 'class' => Get::class, - 'uriTemplate' => '/web_response_by_path', - 'read' => false, - 'controller' => GetWebResponseByPathController::class, - 'normalizationContext' => [ - 'pagination_enabled' => false, - 'enable_max_depth' => true, - 'groups' => [ - $getByPathOperationName, - ...array_values(array_filter(array_unique($groups))), - ...[ - 'web_response', - 'walker', - 'children', - ] - ] + return [ $fqcn => [ + 'types' => [$nodeType->getName()], + 'operations' => [ + ...$this->getCollectionOperations($nodeType), + ...$this->getItemOperations($nodeType) ], - 'openapiContext' => [ - 'tags' => ['WebResponse'], - 'summary' => 'Get a ' . $nodeType->getName() . ' by its path wrapped in a WebResponse object', - 'description' => 'Get a ' . $nodeType->getName() . ' by its path wrapped in a WebResponse', - 'parameters' => [ - [ - 'type' => 'string', - 'name' => 'path', - 'in' => 'query', - 'required' => true, - 'description' => 'Resource path, or `/` for home page', - 'schema' => [ - 'type' => 'string', - ], - ] - ] - ] - ]; - - $webResponseResource['resources'][$this->webResponseClass]['operations'] = $operations; - return $webResponseResource; - } - - protected function removeWebResponseResourceOperation(NodeTypeInterface $nodeType, string $webResponseResourcePath): array - { - $getByPathOperationName = $this->apiResourceOperationNameGenerator->generateGetByPath( - $nodeType->getSourceEntityFullQualifiedClassName() - ); - $webResponseResource = Yaml::parseFile($webResponseResourcePath); - - if (!\array_key_exists($this->webResponseClass, $webResponseResource['resources'])) { - return $webResponseResource; - } - if (\array_key_exists('operations', $webResponseResource['resources'][$this->webResponseClass])) { - $operations = $webResponseResource['resources'][$this->webResponseClass]['operations']; - } else { - return $webResponseResource; - } - if (!\array_key_exists($getByPathOperationName, $operations)) { - // Do not remove operation if it does not exist - return $webResponseResource; - } - - unset($operations[$getByPathOperationName]); - $webResponseResource['resources'][$this->webResponseClass]['operations'] = array_filter($operations); - return $webResponseResource; + ]]; } protected function getCollectionOperations(NodeTypeInterface $nodeType): array @@ -279,17 +126,11 @@ protected function getCollectionOperations(NodeTypeInterface $nodeType): array "document_display_sources", ...$this->getGroupedFieldsSerializationGroups($nodeType) ]; - - $collectionOperationName = $this->apiResourceOperationNameGenerator->generate( - $nodeType->getSourceEntityFullQualifiedClassName(), - 'get_collection' - ); $operations = array_merge( $operations, [ - $collectionOperationName => [ + 'ApiPlatform\Metadata\GetCollection' => [ 'method' => 'GET', - 'class' => GetCollection::class, 'shortName' => $nodeType->getName(), 'normalizationContext' => [ 'enable_max_depth' => true, @@ -300,16 +141,13 @@ protected function getCollectionOperations(NodeTypeInterface $nodeType): array ); } if ($nodeType->isPublishable()) { - $archivesOperationName = $this->apiResourceOperationNameGenerator->generate( - $nodeType->getSourceEntityFullQualifiedClassName(), - 'archives_collection' - ); + $archivesRouteName = '_api_' . $this->getResourceName($nodeType->getName()) . '_archives'; $operations = array_merge( $operations, [ - $archivesOperationName => [ + $archivesRouteName => [ 'method' => 'GET', - 'class' => GetCollection::class, + 'class' => 'ApiPlatform\Metadata\GetCollection', 'shortName' => $nodeType->getName(), 'uriTemplate' => $this->getResourceUriPrefix($nodeType) . '/archives', 'extraProperties' => [ @@ -328,9 +166,9 @@ protected function getCollectionOperations(NodeTypeInterface $nodeType): array return $operations; } - protected function getItemOperationSerializationGroups(NodeTypeInterface $nodeType): array + protected function getItemOperations(NodeTypeInterface $nodeType): array { - return [ + $groups = [ "nodes_sources", "node_listing", "urls", @@ -341,25 +179,59 @@ protected function getItemOperationSerializationGroups(NodeTypeInterface $nodeTy "document_display_sources", ...$this->getGroupedFieldsSerializationGroups($nodeType) ]; - } - - protected function getItemOperations(NodeTypeInterface $nodeType): array - { - $groups = $this->getItemOperationSerializationGroups($nodeType); - $itemOperationName = $this->apiResourceOperationNameGenerator->generate( - $nodeType->getSourceEntityFullQualifiedClassName(), - 'get' - ); - return [ - $itemOperationName => [ + $operations = [ + 'ApiPlatform\Metadata\Get' => [ 'method' => 'GET', - 'class' => Get::class, 'shortName' => $nodeType->getName(), 'normalizationContext' => [ 'groups' => array_values(array_filter(array_unique($groups))) ], ] ]; + + /* + * Create itemOperation for WebResponseController action + */ + if ($nodeType->isReachable()) { + $operations['getByPath'] = [ + 'method' => 'GET', + 'class' => 'ApiPlatform\Metadata\Get', + 'uriTemplate' => '/web_response_by_path', + 'read' => false, + 'controller' => 'RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController', + 'normalizationContext' => [ + 'pagination_enabled' => false, + 'enable_max_depth' => true, + 'groups' => array_merge(array_values(array_filter(array_unique($groups))), [ + 'web_response', + 'walker', + 'walker_level', + 'walker_metadata', + 'meta', + 'children', + ]) + ], + 'openapiContext' => [ + 'tags' => ['WebResponse'], + 'summary' => 'Get a resource by its path wrapped in a WebResponse object', + 'description' => 'Get a resource by its path wrapped in a WebResponse', + 'parameters' => [ + [ + 'type' => 'string', + 'name' => 'path', + 'in' => 'query', + 'required' => true, + 'description' => 'Resource path, or `/` for home page', + 'schema' => [ + 'type' => 'string', + ], + ] + ] + ] + ]; + } + + return $operations; } protected function getGroupedFieldsSerializationGroups(NodeTypeInterface $nodeType): array diff --git a/src/NodeType/ApiResourceOperationNameGenerator.php b/src/NodeType/ApiResourceOperationNameGenerator.php deleted file mode 100644 index bd3da5fa..00000000 --- a/src/NodeType/ApiResourceOperationNameGenerator.php +++ /dev/null @@ -1,28 +0,0 @@ -afterLast('\\') - ->trimPrefix('NS') - ->lower() - ->toString(), - $operation - ); - } - - public function generateGetByPath(string $resourceClass): string - { - return self::generate($resourceClass, 'get_by_path'); - } -} diff --git a/src/Preview/EventSubscriber/PreviewBarSubscriber.php b/src/Preview/EventSubscriber/PreviewBarSubscriber.php index a3ab4354..f1e05262 100644 --- a/src/Preview/EventSubscriber/PreviewBarSubscriber.php +++ b/src/Preview/EventSubscriber/PreviewBarSubscriber.php @@ -10,10 +10,16 @@ use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; -final class PreviewBarSubscriber implements EventSubscriberInterface +class PreviewBarSubscriber implements EventSubscriberInterface { - public function __construct(private readonly PreviewResolverInterface $previewResolver) + protected PreviewResolverInterface $previewResolver; + + /** + * @param PreviewResolverInterface $previewResolver + */ + public function __construct(PreviewResolverInterface $previewResolver) { + $this->previewResolver = $previewResolver; } /** diff --git a/src/Preview/EventSubscriber/PreviewModeSubscriber.php b/src/Preview/EventSubscriber/PreviewModeSubscriber.php index 82540443..2850b83f 100644 --- a/src/Preview/EventSubscriber/PreviewModeSubscriber.php +++ b/src/Preview/EventSubscriber/PreviewModeSubscriber.php @@ -6,21 +6,31 @@ use RZ\Roadiz\CoreBundle\Preview\Exception\PreviewNotAllowedException; use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; -use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Security; -final class PreviewModeSubscriber implements EventSubscriberInterface +class PreviewModeSubscriber implements EventSubscriberInterface { public const QUERY_PARAM_NAME = '_preview'; + protected PreviewResolverInterface $previewResolver; + protected TokenStorageInterface $tokenStorage; + protected Security $security; + public function __construct( - private readonly PreviewResolverInterface $previewResolver, - private readonly Security $security + PreviewResolverInterface $previewResolver, + TokenStorageInterface $tokenStorage, + Security $security ) { + $this->previewResolver = $previewResolver; + $this->tokenStorage = $tokenStorage; + $this->security = $security; } /** @@ -29,10 +39,9 @@ public function __construct( public static function getSubscribedEvents(): array { return [ - KernelEvents::REQUEST => ['onKernelRequest', 2047], + KernelEvents::REQUEST => ['onKernelRequest', 9999], KernelEvents::CONTROLLER => ['onControllerMatched', 10], - // Must Triggered after API platform AddHeadersListener - KernelEvents::RESPONSE => ['onResponse', -255], + KernelEvents::RESPONSE => 'onResponse', ]; } @@ -52,9 +61,9 @@ public function onKernelRequest(RequestEvent $event): void $request = $event->getRequest(); if ( $event->isMainRequest() && - $request->query->has(self::QUERY_PARAM_NAME) && + $request->query->has(static::QUERY_PARAM_NAME) && \in_array( - $request->query->get(self::QUERY_PARAM_NAME, 0), + $request->query->get(static::QUERY_PARAM_NAME, 0), ['true', true, '1', 1, 'on', 'yes', 'y'], true ) @@ -74,6 +83,11 @@ public function onKernelRequest(RequestEvent $event): void public function onControllerMatched(ControllerEvent $event): void { if ($this->supports() && $event->isMainRequest()) { + /** @var TokenInterface|null $token */ + $token = $this->tokenStorage->getToken(); + if (null === $token || !$token->isAuthenticated()) { + throw new PreviewNotAllowedException('You are not authenticated to use preview mode.'); + } if (!$this->security->isGranted($this->previewResolver->getRequiredRole())) { throw new PreviewNotAllowedException('You are not granted to use preview mode.'); } @@ -89,11 +103,10 @@ public function onResponse(ResponseEvent $event): void { if ($this->supports()) { $response = $event->getResponse(); - $response->setMaxAge(0); - $response->setSharedMaxAge(0); + $response->expire(); $response->headers->addCacheControlDirective('no-store'); $response->headers->add(['X-Roadiz-Preview' => true]); - $response->setPrivate(); + $event->setResponse($response); } } } diff --git a/src/Preview/Exception/PreviewNotAllowedException.php b/src/Preview/Exception/PreviewNotAllowedException.php index 07da1c7b..2c376595 100644 --- a/src/Preview/Exception/PreviewNotAllowedException.php +++ b/src/Preview/Exception/PreviewNotAllowedException.php @@ -12,7 +12,7 @@ */ class PreviewNotAllowedException extends AccessDeniedHttpException { - public function __construct(string $message = "You are not allowed to use preview mode.") + public function __construct($message = "You are not allowed to use preview mode.") { parent::__construct($message); } diff --git a/src/Preview/RequestPreviewRevolver.php b/src/Preview/RequestPreviewRevolver.php index b11944fb..632f9889 100644 --- a/src/Preview/RequestPreviewRevolver.php +++ b/src/Preview/RequestPreviewRevolver.php @@ -33,7 +33,7 @@ public function isPreview(): bool if (null === $request) { return false; } - return $request->attributes->getBoolean('preview'); + return $request->attributes->get('preview', false); } public function getRequiredRole(): string diff --git a/src/Preview/User/PreviewUserProvider.php b/src/Preview/User/PreviewUserProvider.php index 70e07fb8..cea9ee39 100644 --- a/src/Preview/User/PreviewUserProvider.php +++ b/src/Preview/User/PreviewUserProvider.php @@ -6,7 +6,7 @@ use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; final class PreviewUserProvider implements PreviewUserProviderInterface diff --git a/src/Realm/RealmResolver.php b/src/Realm/RealmResolver.php index d900db6b..46570ecb 100644 --- a/src/Realm/RealmResolver.php +++ b/src/Realm/RealmResolver.php @@ -11,7 +11,7 @@ use RZ\Roadiz\CoreBundle\Model\RealmInterface; use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\RealmVoter; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; use Symfony\Component\String\Slugger\AsciiSlugger; final class RealmResolver implements RealmResolverInterface @@ -31,14 +31,6 @@ public function getRealms(?Node $node): array return $this->managerRegistry->getRepository(Realm::class)->findByNode($node); } - public function getRealmsWithSerializationGroup(?Node $node): array - { - if (null === $node) { - return []; - } - return $this->managerRegistry->getRepository(Realm::class)->findByNodeWithSerializationGroup($node); - } - public function isGranted(RealmInterface $realm): bool { return $this->security->isGranted(RealmVoter::READ, $realm); @@ -84,28 +76,4 @@ public function getDeniedRealms(): array } return $cacheItem->get(); } - - public function hasRealms(): bool - { - $cacheItem = $this->cache->getItem('app_has_realms'); - if (!$cacheItem->isHit()) { - $hasRealms = $this->managerRegistry->getRepository(Realm::class)->countBy([]) > 0; - $cacheItem->set($hasRealms); - $cacheItem->expiresAfter(new \DateInterval('PT2H')); - $this->cache->save($cacheItem); - } - return $cacheItem->get(); - } - - public function hasRealmsWithSerializationGroup(): bool - { - $cacheItem = $this->cache->getItem('app_has_realms_with_serialization_group'); - if (!$cacheItem->isHit()) { - $hasRealms = $this->managerRegistry->getRepository(Realm::class)->countWithSerializationGroup() > 0; - $cacheItem->set($hasRealms); - $cacheItem->expiresAfter(new \DateInterval('PT2H')); - $this->cache->save($cacheItem); - } - return $cacheItem->get(); - } } diff --git a/src/Realm/RealmResolverInterface.php b/src/Realm/RealmResolverInterface.php index 0124db98..fd0ca8cb 100644 --- a/src/Realm/RealmResolverInterface.php +++ b/src/Realm/RealmResolverInterface.php @@ -10,24 +10,11 @@ interface RealmResolverInterface { - /** - * @return bool Does current application has realms? - */ - public function hasRealms(): bool; - /** - * @return bool Does current application has realms with serialization groups? - */ - public function hasRealmsWithSerializationGroup(): bool; /** * @param Node|null $node * @return RealmInterface[] */ public function getRealms(?Node $node): array; - /** - * @param Node|null $node - * @return RealmInterface[] - */ - public function getRealmsWithSerializationGroup(?Node $node): array; public function isGranted(RealmInterface $realm): bool; /** diff --git a/src/Repository/CustomFormRepository.php b/src/Repository/CustomFormRepository.php index 62df6b92..027ea52e 100644 --- a/src/Repository/CustomFormRepository.php +++ b/src/Repository/CustomFormRepository.php @@ -33,38 +33,14 @@ public function findAllWithRetentionTime(): array ->getResult(); } - /** - * @param Node $node - * @param NodeTypeFieldInterface $field - * @return CustomForm[] - * @deprecated Use findByNodeAndFieldName instead - */ public function findByNodeAndField(Node $node, NodeTypeFieldInterface $field): array { $query = $this->_em->createQuery(' SELECT cf FROM RZ\Roadiz\CoreBundle\Entity\CustomForm cf INNER JOIN cf.nodes ncf - WHERE ncf.fieldName = :fieldName AND ncf.node = :node - ORDER BY ncf.position ASC') - ->setParameter('fieldName', $field->getName()) - ->setParameter('node', $node); - - return $query->getResult(); - } - - /** - * @param Node $node - * @param string $fieldName - * @return CustomForm[] - */ - public function findByNodeAndFieldName(Node $node, string $fieldName): array - { - $query = $this->_em->createQuery(' - SELECT cf FROM RZ\Roadiz\CoreBundle\Entity\CustomForm cf - INNER JOIN cf.nodes ncf - WHERE ncf.fieldName = :fieldName AND ncf.node = :node + WHERE ncf.field = :field AND ncf.node = :node ORDER BY ncf.position ASC') - ->setParameter('fieldName', $fieldName) + ->setParameter('field', $field) ->setParameter('node', $node); return $query->getResult(); diff --git a/src/Repository/DocumentRepository.php b/src/Repository/DocumentRepository.php index 163c8268..a135e1e6 100644 --- a/src/Repository/DocumentRepository.php +++ b/src/Repository/DocumentRepository.php @@ -528,8 +528,7 @@ public function countBy( /** * @param NodesSources $nodeSource * @param NodeTypeFieldInterface $field - * @return array - * @deprecated Use findByNodeSourceAndFieldName instead + * @return array */ public function findByNodeSourceAndField( NodesSources $nodeSource, @@ -539,35 +538,10 @@ public function findByNodeSourceAndField( $qb->addSelect('dt') ->leftJoin('d.documentTranslations', 'dt', 'WITH', 'dt.translation = :translation') ->innerJoin('d.nodesSourcesByFields', 'nsf', 'WITH', 'nsf.nodeSource = :nodeSource') - ->andWhere($qb->expr()->eq('nsf.fieldName', ':fieldName')) - ->andWhere($qb->expr()->eq('d.raw', ':raw')) - ->addOrderBy('nsf.position', 'ASC') - ->setParameter('fieldName', $field->getName()) - ->setParameter('nodeSource', $nodeSource) - ->setParameter('translation', $nodeSource->getTranslation()) - ->setParameter('raw', false) - ->setCacheable(true); - - return $qb->getQuery()->getResult(); - } - - /** - * @param NodesSources $nodeSource - * @param string $fieldName - * @return array - */ - public function findByNodeSourceAndFieldName( - NodesSources $nodeSource, - string $fieldName - ): array { - $qb = $this->createQueryBuilder('d'); - $qb->addSelect('dt') - ->leftJoin('d.documentTranslations', 'dt', 'WITH', 'dt.translation = :translation') - ->innerJoin('d.nodesSourcesByFields', 'nsf', 'WITH', 'nsf.nodeSource = :nodeSource') - ->andWhere($qb->expr()->eq('nsf.fieldName', ':fieldName')) + ->andWhere($qb->expr()->eq('nsf.field', ':field')) ->andWhere($qb->expr()->eq('d.raw', ':raw')) ->addOrderBy('nsf.position', 'ASC') - ->setParameter('fieldName', $fieldName) + ->setParameter('field', $field) ->setParameter('nodeSource', $nodeSource) ->setParameter('translation', $nodeSource->getTranslation()) ->setParameter('raw', false) diff --git a/src/Repository/NodeRepository.php b/src/Repository/NodeRepository.php index 441ade0f..8e4b2727 100644 --- a/src/Repository/NodeRepository.php +++ b/src/Repository/NodeRepository.php @@ -21,7 +21,7 @@ use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; use Symfony\Component\String\Slugger\AsciiSlugger; use Symfony\Contracts\EventDispatcher\Event; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -809,14 +809,14 @@ public function findByNodeAndField( $qb = $this->createQueryBuilder(self::NODE_ALIAS); $qb->select(self::NODE_ALIAS) ->innerJoin('n.aNodes', 'ntn') - ->andWhere($qb->expr()->eq('ntn.fieldName', ':fieldName')) + ->andWhere($qb->expr()->eq('ntn.field', ':field')) ->andWhere($qb->expr()->eq('ntn.nodeA', ':nodeA')) ->addOrderBy('ntn.position', 'ASC') ->setCacheable(true); $this->alterQueryBuilderWithAuthorizationChecker($qb); - $qb->setParameter('fieldName', $field->getName()) + $qb->setParameter('field', $field) ->setParameter('nodeA', $node); return $qb->getQuery()->getResult(); @@ -837,7 +837,7 @@ public function findByNodeAndFieldAndTranslation( $qb->select('n, ns') ->innerJoin('n.aNodes', 'ntn') ->innerJoin('n.nodeSources', self::NODESSOURCES_ALIAS) - ->andWhere($qb->expr()->eq('ntn.fieldName', ':fieldName')) + ->andWhere($qb->expr()->eq('ntn.field', ':field')) ->andWhere($qb->expr()->eq('ntn.nodeA', ':nodeA')) ->andWhere($qb->expr()->eq('ns.translation', ':translation')) ->addOrderBy('ntn.position', 'ASC') @@ -845,7 +845,7 @@ public function findByNodeAndFieldAndTranslation( $this->alterQueryBuilderWithAuthorizationChecker($qb); - $qb->setParameter('fieldName', $field->getName()) + $qb->setParameter('field', $field) ->setParameter('nodeA', $node) ->setParameter('translation', $translation); @@ -864,14 +864,14 @@ public function findByReverseNodeAndField( $qb = $this->createQueryBuilder(self::NODE_ALIAS); $qb->select(self::NODE_ALIAS) ->innerJoin('n.bNodes', 'ntn') - ->andWhere($qb->expr()->eq('ntn.fieldName', ':fieldName')) + ->andWhere($qb->expr()->eq('ntn.field', ':field')) ->andWhere($qb->expr()->eq('ntn.nodeB', ':nodeB')) ->addOrderBy('ntn.position', 'ASC') ->setCacheable(true); $this->alterQueryBuilderWithAuthorizationChecker($qb); - $qb->setParameter('fieldName', $field->getName()) + $qb->setParameter('field', $field) ->setParameter('nodeB', $node); return $qb->getQuery()->getResult(); @@ -892,7 +892,7 @@ public function findByReverseNodeAndFieldAndTranslation( $qb->select('n, ns') ->innerJoin('n.bNodes', 'ntn') ->innerJoin('n.nodeSources', self::NODESSOURCES_ALIAS) - ->andWhere($qb->expr()->eq('ntn.fieldName', ':fieldName')) + ->andWhere($qb->expr()->eq('ntn.field', ':field')) ->andWhere($qb->expr()->eq('ns.translation', ':translation')) ->andWhere($qb->expr()->eq('ntn.nodeB', ':nodeB')) ->addOrderBy('ntn.position', 'ASC') @@ -900,7 +900,7 @@ public function findByReverseNodeAndFieldAndTranslation( $this->alterQueryBuilderWithAuthorizationChecker($qb); - $qb->setParameter('fieldName', $field->getName()) + $qb->setParameter('field', $field) ->setParameter('translation', $translation) ->setParameter('nodeB', $node); @@ -1164,74 +1164,4 @@ protected function classicLikeComparison( $qb->orWhere($qb->expr()->like('LOWER(' . $alias . '.nodeName)', $qb->expr()->literal($value))); return $qb; } - - /** - * Get previous node from hierarchy - */ - public function findPreviousNode( - Node $node, - ?array $criteria = null, - ?array $order = null - ): ?Node { - if ($node->getPosition() <= 1) { - return null; - } - if (null === $order) { - $order = []; - } - - if (null === $criteria) { - $criteria = []; - } - - $criteria['parent'] = $node->getParent(); - /* - * Use < operator to get first previous nodeSource - * even if it’s not the previous position index - */ - $criteria['position'] = [ - '<', - $node->getPosition(), - ]; - - $order['position'] = 'DESC'; - - return $this->findOneBy( - $criteria, - $order - ); - } - - /** - * Get next node from hierarchy. - */ - public function findNextNode( - Node $node, - ?array $criteria = null, - ?array $order = null - ): ?Node { - if (null === $criteria) { - $criteria = []; - } - if (null === $order) { - $order = []; - } - - $criteria['parent'] = $node->getParent(); - - /* - * Use > operator to get first next nodeSource - * even if it’s not the next position index - */ - $criteria['position'] = [ - '>', - $node->getPosition(), - ]; - $order['position'] = 'ASC'; - - return $this->findOneBy( - $criteria, - $order - ); - } } diff --git a/src/Repository/NodesCustomFormsRepository.php b/src/Repository/NodesCustomFormsRepository.php index 112e0e90..dc41cec0 100644 --- a/src/Repository/NodesCustomFormsRepository.php +++ b/src/Repository/NodesCustomFormsRepository.php @@ -5,7 +5,6 @@ namespace RZ\Roadiz\CoreBundle\Repository; use Doctrine\Persistence\ManagerRegistry; -use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodesCustomForms; use RZ\Roadiz\CoreBundle\Entity\NodeTypeField; @@ -23,38 +22,20 @@ public function __construct( ) { parent::__construct($registry, NodesCustomForms::class, $dispatcher); } - /** - * @param Node $node - * @param NodeTypeFieldInterface $field - * @return int - * @throws \Doctrine\ORM\NoResultException - * @throws \Doctrine\ORM\NonUniqueResultException - * @deprecated Use getLatestPositionForFieldName instead + * @param Node $node + * @param NodeTypeField $field + * + * @return integer */ - public function getLatestPosition(Node $node, NodeTypeFieldInterface $field): int - { - $query = $this->_em->createQuery(' - SELECT MAX(ncf.position) FROM RZ\Roadiz\CoreBundle\Entity\NodesCustomForms ncf - WHERE ncf.node = :node AND ncf.fieldName = :fieldName') - ->setParameter('node', $node) - ->setParameter('fieldName', $field->getName()); - - $latestPosition = $query->getSingleScalarResult(); - - return is_numeric($latestPosition) ? (int) $latestPosition : 0; - } - - public function getLatestPositionForFieldName(Node $node, string $fieldName): int + public function getLatestPosition(Node $node, NodeTypeField $field) { $query = $this->_em->createQuery(' SELECT MAX(ncf.position) FROM RZ\Roadiz\CoreBundle\Entity\NodesCustomForms ncf - WHERE ncf.node = :node AND ncf.fieldName = :fieldName') + WHERE ncf.node = :node AND ncf.field = :field') ->setParameter('node', $node) - ->setParameter('fieldName', $fieldName); - - $latestPosition = $query->getSingleScalarResult(); + ->setParameter('field', $field); - return is_numeric($latestPosition) ? (int) $latestPosition : 0; + return (int) $query->getSingleScalarResult(); } } diff --git a/src/Repository/NodesSourcesDocumentsRepository.php b/src/Repository/NodesSourcesDocumentsRepository.php index 3cc0ea1c..0c24dc5d 100644 --- a/src/Repository/NodesSourcesDocumentsRepository.php +++ b/src/Repository/NodesSourcesDocumentsRepository.php @@ -7,7 +7,6 @@ use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; use Doctrine\Persistence\ManagerRegistry; -use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\NodesSourcesDocuments; use RZ\Roadiz\CoreBundle\Entity\NodeTypeField; @@ -27,35 +26,19 @@ public function __construct( /** * @param NodesSources $nodeSource - * @param NodeTypeFieldInterface $field + * @param NodeTypeField $field * @return int * @throws NoResultException * @throws NonUniqueResultException - * @deprecated Use getLatestPositionForFieldName instead */ - public function getLatestPosition(NodesSources $nodeSource, NodeTypeFieldInterface $field): int + public function getLatestPosition(NodesSources $nodeSource, NodeTypeField $field): int { $query = $this->_em->createQuery(' SELECT MAX(nsd.position) FROM RZ\Roadiz\CoreBundle\Entity\NodesSourcesDocuments nsd - WHERE nsd.nodeSource = :nodeSource AND nsd.fieldName = :fieldName') + WHERE nsd.nodeSource = :nodeSource AND nsd.field = :field') ->setParameter('nodeSource', $nodeSource) - ->setParameter('fieldName', $field->getName()); + ->setParameter('field', $field); - $latestPosition = $query->getSingleScalarResult(); - - return is_numeric($latestPosition) ? (int) $latestPosition : 0; - } - - public function getLatestPositionForFieldName(NodesSources $nodeSource, string $fieldName): int - { - $query = $this->_em->createQuery(' - SELECT MAX(nsd.position) FROM RZ\Roadiz\CoreBundle\Entity\NodesSourcesDocuments nsd - WHERE nsd.nodeSource = :nodeSource AND nsd.fieldName = :fieldName') - ->setParameter('nodeSource', $nodeSource) - ->setParameter('fieldName', $fieldName); - - $latestPosition = $query->getSingleScalarResult(); - - return is_numeric($latestPosition) ? (int) $latestPosition : 0; + return (int) $query->getSingleScalarResult(); } } diff --git a/src/Repository/NodesSourcesRepository.php b/src/Repository/NodesSourcesRepository.php index 088a52cd..a414a6f9 100644 --- a/src/Repository/NodesSourcesRepository.php +++ b/src/Repository/NodesSourcesRepository.php @@ -4,7 +4,6 @@ namespace RZ\Roadiz\CoreBundle\Repository; -use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Tools\Pagination\Paginator; @@ -22,9 +21,8 @@ use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; use RZ\Roadiz\CoreBundle\SearchEngine\NodeSourceSearchHandlerInterface; use RZ\Roadiz\CoreBundle\SearchEngine\SearchResultsInterface; -use RZ\Roadiz\CoreBundle\SearchEngine\SolrSearchResultItem; use RZ\Roadiz\CoreBundle\SearchEngine\SolrSearchResults; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; use Symfony\Contracts\EventDispatcher\Event; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -352,7 +350,7 @@ public function countBy(mixed $criteria): int * @param array|null $orderBy * @param int|null $limit * @param int|null $offset - * @return array|Paginator + * @return array|Paginator */ public function findBy( array $criteria, @@ -436,7 +434,7 @@ public function findOneBy( * * @param string $query Solr query string (for example: `text:Lorem Ipsum`) * @param int $limit Result number to fetch (default: all) - * @return array> + * @return array */ public function findBySearchQuery(string $query, int $limit = 25): array { @@ -516,6 +514,7 @@ public function findByTextQuery( ->andWhere($qb->expr()->orX( $qb->expr()->like(static::NODESSOURCES_ALIAS . '.title', ':query'), $qb->expr()->like(static::NODESSOURCES_ALIAS . '.metaTitle', ':query'), + $qb->expr()->like(static::NODESSOURCES_ALIAS . '.metaKeywords', ':query'), $qb->expr()->like(static::NODESSOURCES_ALIAS . '.metaDescription', ':query') )) ->orderBy(static::NODESSOURCES_ALIAS . '.title', 'ASC') @@ -700,7 +699,7 @@ public function searchBy( /** * @param NodesSources $nodesSources * @param NodeTypeFieldInterface $field - * @deprecated Use findByNodesSourcesAndFieldNameAndTranslation instead + * * @return array|null */ public function findByNodesSourcesAndFieldAndTranslation( @@ -712,37 +711,7 @@ public function findByNodesSourcesAndFieldAndTranslation( ->innerJoin('ns.node', static::NODE_ALIAS) ->leftJoin('ns.urlAliases', 'ua') ->innerJoin('n.aNodes', 'ntn') - ->andWhere($qb->expr()->eq('ntn.fieldName', ':fieldName')) - ->andWhere($qb->expr()->eq('ntn.nodeA', ':nodeA')) - ->andWhere($qb->expr()->eq('ns.translation', ':translation')) - ->addOrderBy('ntn.position', 'ASC') - ->setCacheable(true); - - $this->alterQueryBuilderWithAuthorizationChecker($qb); - - $qb->setParameter('fieldName', $field->getName()) - ->setParameter('nodeA', $nodesSources->getNode()) - ->setParameter('translation', $nodesSources->getTranslation()); - - return $qb->getQuery()->getResult(); - } - - /** - * @param NodesSources $nodesSources - * @param string $fieldName - * - * @return array|null - */ - public function findByNodesSourcesAndFieldNameAndTranslation( - NodesSources $nodesSources, - string $fieldName - ): ?array { - $qb = $this->createQueryBuilder(static::NODESSOURCES_ALIAS); - $qb->select('ns, n, ua') - ->innerJoin('ns.node', static::NODE_ALIAS) - ->leftJoin('ns.urlAliases', 'ua') - ->innerJoin('n.aNodes', 'ntn') - ->andWhere($qb->expr()->eq('ntn.fieldName', ':fieldName')) + ->andWhere($qb->expr()->eq('ntn.field', ':field')) ->andWhere($qb->expr()->eq('ntn.nodeA', ':nodeA')) ->andWhere($qb->expr()->eq('ns.translation', ':translation')) ->addOrderBy('ntn.position', 'ASC') @@ -750,7 +719,7 @@ public function findByNodesSourcesAndFieldNameAndTranslation( $this->alterQueryBuilderWithAuthorizationChecker($qb); - $qb->setParameter('fieldName', $fieldName) + $qb->setParameter('field', $field) ->setParameter('nodeA', $nodesSources->getNode()) ->setParameter('translation', $nodesSources->getTranslation()); @@ -795,302 +764,4 @@ protected function classicLikeComparison( $qb->orWhere($qb->expr()->like('LOWER(' . static::NODE_ALIAS . '.nodeName)', $qb->expr()->literal($value))); return $qb; } - - /** - * Get every nodeSources parents from direct parent to farthest ancestor. - * - * @param NodesSources $nodeSource - * @param array|null $criteria - * @return array - * @throws \Doctrine\ORM\NonUniqueResultException - */ - public function findParents( - NodesSources $nodeSource, - ?array $criteria = null - ): array { - $parentsNodeSources = []; - - if (null === $criteria) { - $criteria = []; - } - - $parent = $nodeSource; - - while (null !== $parent) { - $criteria = array_merge( - $criteria, - [ - 'node' => $parent->getNode()->getParent(), - 'translation' => $nodeSource->getTranslation(), - ] - ); - $currentParent = $this->findOneBy( - $criteria, - [] - ); - - if (null !== $currentParent) { - $parentsNodeSources[] = $currentParent; - } - - $parent = $currentParent; - } - - return $parentsNodeSources; - } - - /** - * Get children nodes sources to lock with current translation. - * - * @param NodesSources $nodeSource - * @param array|null $criteria Additional criteria - * @param array|null $order Non default ordering - * - * @return Paginator|array - */ - public function findChildren( - NodesSources $nodeSource, - array $criteria = null, - array $order = null - ): Paginator|array { - $defaultCriteria = [ - 'node.parent' => $nodeSource->getNode(), - 'translation' => $nodeSource->getTranslation(), - ]; - - if (null !== $order) { - $defaultOrder = $order; - } else { - $defaultOrder = [ - 'node.position' => 'ASC', - ]; - } - - if (null !== $criteria) { - $defaultCriteria = array_merge($defaultCriteria, $criteria); - } - - return $this->findBy( - $defaultCriteria, - $defaultOrder - ); - } - - /** - * Get first node-source among current node-source children. - * - * @param NodesSources|null $nodeSource - * @param array|null $criteria - * @param array|null $order - * - * @return NodesSources|null - * @throws NonUniqueResultException - */ - public function findFirstChild( - ?NodesSources $nodeSource, - array $criteria = null, - array $order = null - ): ?NodesSources { - $defaultCriteria = [ - 'node.parent' => $nodeSource?->getNode() ?? null, - ]; - - if (null !== $nodeSource) { - $defaultCriteria['translation'] = $nodeSource->getTranslation(); - } - - if (null !== $order) { - $defaultOrder = $order; - } else { - $defaultOrder = [ - 'node.position' => 'ASC', - ]; - } - - if (null !== $criteria) { - $defaultCriteria = array_merge($defaultCriteria, $criteria); - } - - return $this->findOneBy( - $defaultCriteria, - $defaultOrder - ); - } - - /** - * Get last node-source among current node-source children. - * - * @param NodesSources|null $nodeSource - * @param array|null $criteria - * @param array|null $order - * - * @return NodesSources|null - * @throws NonUniqueResultException - */ - public function findLastChild( - ?NodesSources $nodeSource, - array $criteria = null, - array $order = null - ): ?NodesSources { - $defaultCriteria = [ - 'node.parent' => $nodeSource?->getNode() ?? null, - ]; - - if (null !== $nodeSource) { - $defaultCriteria['translation'] = $nodeSource->getTranslation(); - } - - if (null !== $order) { - $defaultOrder = $order; - } else { - $defaultOrder = [ - 'node.position' => 'DESC', - ]; - } - - if (null !== $criteria) { - $defaultCriteria = array_merge($defaultCriteria, $criteria); - } - - return $this->findOneBy( - $defaultCriteria, - $defaultOrder - ); - } - - /** - * Get first node-source in the same parent as current node-source. - * - * @param NodesSources $nodeSource - * @param array|null $criteria - * @param array|null $order - * - * @return NodesSources|null - * @throws NonUniqueResultException - */ - public function findFirstSibling( - NodesSources $nodeSource, - array $criteria = null, - array $order = null - ): ?NodesSources { - if (null !== $nodeSource->getParent()) { - return $this->findFirstChild($nodeSource->getParent(), $criteria, $order); - } - return $this->findFirstChild(null, $criteria, $order); - } - - /** - * Get last node-source in the same parent as current node-source. - * - * @param NodesSources $nodeSource - * @param array|null $criteria - * @param array|null $order - * - * @return NodesSources|null - * @throws NonUniqueResultException - */ - public function findLastSibling( - NodesSources $nodeSource, - array $criteria = null, - array $order = null - ): ?NodesSources { - if (null !== $nodeSource->getParent()) { - return $this->findLastChild($nodeSource->getParent(), $criteria, $order); - } - return $this->findLastChild(null, $criteria, $order); - } - - /** - * Get previous node-source from hierarchy. - * - * @param NodesSources $nodeSource - * @param array|null $criteria - * @param array|null $order - * - * @return NodesSources|null - * @throws NonUniqueResultException - */ - public function findPrevious( - NodesSources $nodeSource, - array $criteria = null, - array $order = null - ): ?NodesSources { - if ($nodeSource->getNode()->getPosition() <= 1) { - return null; - } - - $defaultCriteria = [ - /* - * Use < operator to get first next nodeSource - * even if it’s not the next position index - */ - 'node.position' => [ - '<', - $nodeSource - ->getNode() - ->getPosition(), - ], - 'node.parent' => $nodeSource->getNode()->getParent(), - 'translation' => $nodeSource->getTranslation(), - ]; - if (null !== $criteria) { - $defaultCriteria = array_merge($defaultCriteria, $criteria); - } - - if (null === $order) { - $order = []; - } - - $order['node.position'] = 'DESC'; - - return $this->findOneBy( - $defaultCriteria, - $order - ); - } - - /** - * Get next node-source from hierarchy. - * - * @param NodesSources $nodeSource - * @param array|null $criteria - * @param array|null $order - * - * @return NodesSources|null - * @throws NonUniqueResultException - */ - public function findNext( - NodesSources $nodeSource, - array $criteria = null, - array $order = null - ): ?NodesSources { - $defaultCriteria = [ - /* - * Use > operator to get first next nodeSource - * even if it’s not the next position index - */ - 'node.position' => [ - '>', - $nodeSource - ->getNode() - ->getPosition(), - ], - 'node.parent' => $nodeSource->getNode()->getParent(), - 'translation' => $nodeSource->getTranslation(), - ]; - if (null !== $criteria) { - $defaultCriteria = array_merge($defaultCriteria, $criteria); - } - - if (null === $order) { - $order = []; - } - - $order['node.position'] = 'ASC'; - - return $this->findOneBy( - $defaultCriteria, - $order - ); - } } diff --git a/src/Repository/NodesToNodesRepository.php b/src/Repository/NodesToNodesRepository.php index d23079d9..a64042a3 100644 --- a/src/Repository/NodesToNodesRepository.php +++ b/src/Repository/NodesToNodesRepository.php @@ -7,7 +7,6 @@ use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; use Doctrine\Persistence\ManagerRegistry; -use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodesToNodes; use RZ\Roadiz\CoreBundle\Entity\NodeTypeField; @@ -28,35 +27,20 @@ public function __construct( /** * @param Node $node - * @param NodeTypeFieldInterface $field - * @return int + * @param NodeTypeField $field + * + * @return integer * @throws NoResultException * @throws NonUniqueResultException - * @deprecated Use getLatestPositionForFieldName instead */ - public function getLatestPosition(Node $node, NodeTypeFieldInterface $field): int + public function getLatestPosition(Node $node, NodeTypeField $field): int { $query = $this->_em->createQuery(' SELECT MAX(ntn.position) FROM RZ\Roadiz\CoreBundle\Entity\NodesToNodes ntn - WHERE ntn.nodeA = :nodeA AND ntn.fieldName = :fieldName') + WHERE ntn.nodeA = :nodeA AND ntn.field = :field') ->setParameter('nodeA', $node) - ->setParameter('fieldName', $field->getName()); + ->setParameter('field', $field); - $latestPosition = $query->getSingleScalarResult(); - - return is_numeric($latestPosition) ? (int) $latestPosition : 0; - } - - public function getLatestPositionForFieldName(Node $node, string $fieldName): int - { - $query = $this->_em->createQuery(' - SELECT MAX(ntn.position) FROM RZ\Roadiz\CoreBundle\Entity\NodesToNodes ntn - WHERE ntn.nodeA = :nodeA AND ntn.fieldName = :fieldName') - ->setParameter('nodeA', $node) - ->setParameter('fieldName', $fieldName); - - $latestPosition = $query->getSingleScalarResult(); - - return is_numeric($latestPosition) ? (int) $latestPosition : 0; + return (int) $query->getSingleScalarResult(); } } diff --git a/src/Repository/RealmRepository.php b/src/Repository/RealmRepository.php index 25382cba..21b4c507 100644 --- a/src/Repository/RealmRepository.php +++ b/src/Repository/RealmRepository.php @@ -41,13 +41,4 @@ public function findByNodeAndBehaviour(Node $node, string $realmBehaviour): arra return $qb->getQuery()->getResult(); } - - public function countWithSerializationGroup(): int - { - $qb = $this->createQueryBuilder('r'); - $qb->select($qb->expr()->count('r')) - ->andWhere($qb->expr()->isNotNull('r.serializationGroup')); - - return intval($qb->getQuery()->getSingleScalarResult()); - } } diff --git a/src/Repository/StatusAwareRepository.php b/src/Repository/StatusAwareRepository.php index 8aaf6ede..49f35899 100644 --- a/src/Repository/StatusAwareRepository.php +++ b/src/Repository/StatusAwareRepository.php @@ -8,7 +8,7 @@ use Doctrine\Persistence\ManagerRegistry; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** diff --git a/src/Repository/TagRepository.php b/src/Repository/TagRepository.php index 6d0eef31..3ec1db72 100644 --- a/src/Repository/TagRepository.php +++ b/src/Repository/TagRepository.php @@ -13,7 +13,6 @@ use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; use RZ\Roadiz\CoreBundle\Doctrine\ORM\SimpleQueryBuilder; use RZ\Roadiz\CoreBundle\Entity\Node; -use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\Tag; use RZ\Roadiz\CoreBundle\Entity\TagTranslation; use RZ\Roadiz\CoreBundle\Entity\Translation; @@ -234,7 +233,7 @@ protected function getCountContextualQueryWithTranslation( * @param integer|null $offset * @param TranslationInterface|null $translation * - * @return array|Paginator + * @return array|Paginator */ public function findBy( array $criteria, @@ -721,7 +720,7 @@ public function findOrCreateByPath(string $tagPath, ?TranslationInterface $trans * * @return Tag|null */ - public function findByPath(string $tagPath): ?Tag + public function findByPath(string $tagPath) { $tagPath = trim($tagPath); $tags = explode('/', $tagPath); @@ -746,12 +745,10 @@ public function findByPath(string $tagPath): ?Tag * * Parent can be null for tag root * - * @param Tag|null $parent + * @param Tag|null $parent * @return int - * @throws NoResultException - * @throws NonUniqueResultException */ - public function findLatestPositionInParent(Tag $parent = null): int + public function findLatestPositionInParent(Tag $parent = null) { $qb = $this->createQueryBuilder('t'); $qb->select($qb->expr()->max('t.position')); @@ -765,15 +762,4 @@ public function findLatestPositionInParent(Tag $parent = null): int return (int) $qb->getQuery()->getSingleScalarResult(); } - - public function findByNodesSources(NodesSources $nodesSources): array|Paginator - { - // @phpstan-ignore-next-line - return $this->findBy([ - "nodes" => $nodesSources->getNode(), - "translation" => $nodesSources->getTranslation(), - ], [ - 'position' => 'ASC', - ]); - } } diff --git a/src/Routing/DeferredRouteCollection.php b/src/Routing/DeferredRouteCollection.php new file mode 100644 index 00000000..4597717c --- /dev/null +++ b/src/Routing/DeferredRouteCollection.php @@ -0,0 +1,29 @@ +stopwatch = $stopwatch; + $this->logger = $logger ?? new NullLogger(); + $this->previewResolver = $previewResolver; } } diff --git a/src/Routing/NodeRouter.php b/src/Routing/NodeRouter.php index 4d839b48..bdea3b74 100644 --- a/src/Routing/NodeRouter.php +++ b/src/Routing/NodeRouter.php @@ -5,7 +5,6 @@ namespace RZ\Roadiz\CoreBundle\Routing; use Psr\Cache\CacheItemPoolInterface; -use Psr\Cache\InvalidArgumentException; use Psr\Log\LoggerInterface; use RZ\Roadiz\CoreBundle\Bag\Settings; use RZ\Roadiz\CoreBundle\Entity\NodesSources; @@ -16,6 +15,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Routing\Exception\InvalidParameterException; use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Matcher\UrlMatcherInterface; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RouteCollection; @@ -28,12 +28,24 @@ class NodeRouter extends Router implements VersatileGeneratorInterface */ public const NO_CACHE_PARAMETER = '_no_cache'; private ?Theme $theme = null; + private CacheItemPoolInterface $nodeSourceUrlCacheAdapter; + private Settings $settingsBag; + private EventDispatcherInterface $eventDispatcher; + /** + * @param NodeUrlMatcherInterface $matcher + * @param Settings $settingsBag + * @param EventDispatcherInterface $eventDispatcher + * @param CacheItemPoolInterface $nodeSourceUrlCacheAdapter + * @param array $options + * @param RequestContext $context + * @param LoggerInterface $logger + */ public function __construct( NodeUrlMatcherInterface $matcher, - protected readonly Settings $settingsBag, - protected readonly EventDispatcherInterface $eventDispatcher, - protected readonly CacheItemPoolInterface $nodeSourceUrlCacheAdapter, + Settings $settingsBag, + EventDispatcherInterface $eventDispatcher, + CacheItemPoolInterface $nodeSourceUrlCacheAdapter, RequestContext $context, LoggerInterface $logger, array $options = [] @@ -45,7 +57,10 @@ public function __construct( $context, $logger ); + $this->settingsBag = $settingsBag; + $this->eventDispatcher = $eventDispatcher; $this->matcher = $matcher; + $this->nodeSourceUrlCacheAdapter = $nodeSourceUrlCacheAdapter; } /** @@ -66,6 +81,22 @@ public function getMatcher(): UrlMatcherInterface return $this->matcher; } + /** + * No generator for a node router. + */ + public function getGenerator(): UrlGeneratorInterface + { + throw new \BadMethodCallException(get_class($this) . ' does not support path generation.'); + } + + /** + * @inheritDoc + */ + public function supports($name): bool + { + return ($name instanceof NodesSources || $name === RouteObjectInterface::OBJECT_BASED_ROUTE_NAME); + } + /** * @return Theme|null */ @@ -85,11 +116,24 @@ public function setTheme(?Theme $theme): NodeRouter } /** - * @inheritDoc + * Convert a route identifier (name, content object etc) into a string + * usable for logging and other debug/error messages + * + * @param mixed $name + * @param array $parameters which should contain a content field containing + * a RouteReferrersReadInterface object + * + * @return string */ - public function getRouteDebugMessage(string $name, array $parameters = []): string + public function getRouteDebugMessage($name, array $parameters = []): string { - if (RouteObjectInterface::OBJECT_BASED_ROUTE_NAME === $name) { + if ($name instanceof NodesSources) { + @trigger_error('Passing an object as route name is deprecated since version 1.5. Pass the `RouteObjectInterface::OBJECT_BASED_ROUTE_NAME` as route name and the object in the parameters with key `RouteObjectInterface::ROUTE_OBJECT` resp the content id with content_id.', E_USER_DEPRECATED); + return '[' . $name->getTranslation()->getLocale() . ']' . + $name->getTitle() . ' - ' . + $name->getNode()->getNodeName() . + '[' . $name->getNode()->getId() . ']'; + } elseif (RouteObjectInterface::OBJECT_BASED_ROUTE_NAME === $name) { if ( array_key_exists(RouteObjectInterface::ROUTE_OBJECT, $parameters) && $parameters[RouteObjectInterface::ROUTE_OBJECT] instanceof NodesSources @@ -106,20 +150,19 @@ public function getRouteDebugMessage(string $name, array $parameters = []): stri /** * {@inheritdoc} - * @throws InvalidArgumentException */ public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string { - if (RouteObjectInterface::OBJECT_BASED_ROUTE_NAME !== $name) { - throw new RouteNotFoundException(); - } - - if ( - array_key_exists(RouteObjectInterface::ROUTE_OBJECT, $parameters) && - $parameters[RouteObjectInterface::ROUTE_OBJECT] instanceof NodesSources - ) { - $route = $parameters[RouteObjectInterface::ROUTE_OBJECT]; - unset($parameters[RouteObjectInterface::ROUTE_OBJECT]); + if (RouteObjectInterface::OBJECT_BASED_ROUTE_NAME === $name) { + if ( + array_key_exists(RouteObjectInterface::ROUTE_OBJECT, $parameters) && + $parameters[RouteObjectInterface::ROUTE_OBJECT] instanceof NodesSources + ) { + $route = $parameters[RouteObjectInterface::ROUTE_OBJECT]; + unset($parameters[RouteObjectInterface::ROUTE_OBJECT]); + } else { + $route = null; + } } else { $route = null; } diff --git a/src/Routing/NodeUrlMatcher.php b/src/Routing/NodeUrlMatcher.php index 743e405f..1e1f7b0a 100644 --- a/src/Routing/NodeUrlMatcher.php +++ b/src/Routing/NodeUrlMatcher.php @@ -15,28 +15,16 @@ use Symfony\Component\Stopwatch\Stopwatch; /** - * UrlMatcher which tries to grab Node and Translation information for a route. + * UrlMatcher which tries to grab Node and Translation + * information for a route. */ final class NodeUrlMatcher extends DynamicUrlMatcher implements NodeUrlMatcherInterface { + protected PathResolverInterface $pathResolver; /** - * @param PathResolverInterface $pathResolver - * @param RequestContext $context - * @param PreviewResolverInterface $previewResolver - * @param Stopwatch $stopwatch - * @param LoggerInterface $logger - * @param class-string $defaultControllerClass + * @var class-string */ - public function __construct( - private readonly PathResolverInterface $pathResolver, - RequestContext $context, - PreviewResolverInterface $previewResolver, - Stopwatch $stopwatch, - LoggerInterface $logger, - private readonly string $defaultControllerClass - ) { - parent::__construct($context, $previewResolver, $stopwatch, $logger); - } + private string $defaultControllerClass; /** * @return array @@ -55,7 +43,28 @@ public function getDefaultSupportedFormatExtension(): string } /** - * @inheritDoc + * @param PathResolverInterface $pathResolver + * @param RequestContext $context + * @param PreviewResolverInterface $previewResolver + * @param Stopwatch $stopwatch + * @param LoggerInterface $logger + * @param class-string $defaultControllerClass + */ + public function __construct( + PathResolverInterface $pathResolver, + RequestContext $context, + PreviewResolverInterface $previewResolver, + Stopwatch $stopwatch, + LoggerInterface $logger, + string $defaultControllerClass + ) { + parent::__construct($context, $previewResolver, $stopwatch, $logger); + $this->pathResolver = $pathResolver; + $this->defaultControllerClass = $defaultControllerClass; + } + + /** + * {@inheritdoc} */ public function match(string $pathinfo): array { @@ -81,6 +90,7 @@ protected function getNodeRouteHelper(NodesSources $nodeSource, ?Theme $theme): * @param string $decodedUrl * @param Theme|null $theme * @return array + * @throws \ReflectionException */ public function matchNode(string $decodedUrl, ?Theme $theme): array { diff --git a/src/Routing/NodesSourcesPathResolver.php b/src/Routing/NodesSourcesPathResolver.php index 2e8167c1..f40cc067 100644 --- a/src/Routing/NodesSourcesPathResolver.php +++ b/src/Routing/NodesSourcesPathResolver.php @@ -20,16 +20,28 @@ final class NodesSourcesPathResolver implements PathResolverInterface { + private ManagerRegistry $managerRegistry; + private Stopwatch $stopwatch; private static string $nodeNamePattern = '[a-zA-Z0-9\-\_\.]+'; + private PreviewResolverInterface $previewResolver; + private Settings $settingsBag; + private RequestStack $requestStack; + private bool $useAcceptLanguageHeader; public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly PreviewResolverInterface $previewResolver, - private readonly Stopwatch $stopwatch, - private readonly Settings $settingsBag, - private readonly RequestStack $requestStack, - private readonly bool $useAcceptLanguageHeader + ManagerRegistry $managerRegistry, + PreviewResolverInterface $previewResolver, + Stopwatch $stopwatch, + Settings $settingsBag, + RequestStack $requestStack, + bool $useAcceptLanguageHeader ) { + $this->stopwatch = $stopwatch; + $this->previewResolver = $previewResolver; + $this->managerRegistry = $managerRegistry; + $this->settingsBag = $settingsBag; + $this->requestStack = $requestStack; + $this->useAcceptLanguageHeader = $useAcceptLanguageHeader; } /** diff --git a/src/Routing/NodesSourcesUrlGenerator.php b/src/Routing/NodesSourcesUrlGenerator.php index 943aa29e..293d5e5e 100644 --- a/src/Routing/NodesSourcesUrlGenerator.php +++ b/src/Routing/NodesSourcesUrlGenerator.php @@ -13,12 +13,31 @@ */ final class NodesSourcesUrlGenerator { + protected ?Request $request; + protected ?NodesSources $nodeSource; + protected bool $forceLocale; + protected bool $forceLocaleWithUrlAlias; + protected NodesSourcesPathAggregator $pathAggregator; + + /** + * @param NodesSourcesPathAggregator $pathAggregator + * @param Request|null $request + * @param NodesSources|null $nodeSource + * @param bool $forceLocale + * @param bool $forceLocaleWithUrlAlias + */ public function __construct( - private readonly NodesSourcesPathAggregator $pathAggregator, - private readonly ?NodesSources $nodeSource = null, - private readonly bool $forceLocale = false, - private readonly bool $forceLocaleWithUrlAlias = false + NodesSourcesPathAggregator $pathAggregator, + Request $request = null, + NodesSources $nodeSource = null, + bool $forceLocale = false, + bool $forceLocaleWithUrlAlias = false ) { + $this->pathAggregator = $pathAggregator; + $this->request = $request; + $this->nodeSource = $nodeSource; + $this->forceLocale = $forceLocale; + $this->forceLocaleWithUrlAlias = $forceLocaleWithUrlAlias; } /** diff --git a/src/Routing/NullLoader.php b/src/Routing/NullLoader.php index 673b621d..70eaacd3 100644 --- a/src/Routing/NullLoader.php +++ b/src/Routing/NullLoader.php @@ -10,23 +10,36 @@ final class NullLoader implements LoaderInterface { /** - * @inheritDoc + * Loads a resource. + * + * @param mixed $resource The resource + * @param string|null $type The resource type or null if unknown + * @return mixed + * + * @throws \Exception If something went wrong */ - public function load(mixed $resource, string $type = null): mixed + public function load($resource, $type = null): mixed { return null; } /** - * @inheritDoc + * Returns whether this class supports the given resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return bool True if this class supports the given resource, false otherwise */ - public function supports(mixed $resource, string $type = null): bool + public function supports($resource, $type = null): bool { return true; } /** - * @inheritDoc + * Gets the loader resolver. + * + * @return LoaderResolverInterface|null A LoaderResolverInterface instance */ public function getResolver(): ?LoaderResolverInterface { @@ -34,7 +47,10 @@ public function getResolver(): ?LoaderResolverInterface } /** - * @inheritDoc + * Sets the loader resolver. + * + * @param LoaderResolverInterface $resolver + * @return NullLoader */ public function setResolver(LoaderResolverInterface $resolver): self { diff --git a/src/Routing/OptimizedNodesSourcesGraphPathAggregator.php b/src/Routing/OptimizedNodesSourcesGraphPathAggregator.php index 0d171edf..94af76a0 100644 --- a/src/Routing/OptimizedNodesSourcesGraphPathAggregator.php +++ b/src/Routing/OptimizedNodesSourcesGraphPathAggregator.php @@ -7,16 +7,22 @@ use Doctrine\ORM\Query; use Doctrine\Persistence\ManagerRegistry; use Psr\Cache\CacheItemPoolInterface; -use Psr\Cache\InvalidArgumentException; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodesSources; final class OptimizedNodesSourcesGraphPathAggregator implements NodesSourcesPathAggregator { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly CacheItemPoolInterface $cacheAdapter - ) { + private ManagerRegistry $managerRegistry; + private CacheItemPoolInterface $cacheAdapter; + + /** + * @param ManagerRegistry $managerRegistry + * @param CacheItemPoolInterface $cacheAdapter + */ + public function __construct(ManagerRegistry $managerRegistry, CacheItemPoolInterface $cacheAdapter) + { + $this->managerRegistry = $managerRegistry; + $this->cacheAdapter = $cacheAdapter; } private function getCacheKey(NodesSources $nodesSources): string @@ -28,7 +34,6 @@ private function getCacheKey(NodesSources $nodesSources): string * @param NodesSources $nodesSources * @param array $parameters * @return string - * @throws InvalidArgumentException */ public function aggregatePath(NodesSources $nodesSources, array $parameters = []): string { @@ -54,7 +59,7 @@ public function aggregatePath(NodesSources $nodesSources, array $parameters = [] * * @return array */ - private function getParentsIds(Node $parent): array + protected function getParentsIds(Node $parent): array { $parentIds = []; while ($parent !== null && !$parent->isHome()) { @@ -67,13 +72,13 @@ private function getParentsIds(Node $parent): array /** * Get every nodeSource parents identifier from current to - * farthest ancestor. + * farest ancestor. * * @param NodesSources $source * * @return array */ - private function getIdentifiers(NodesSources $source): array + protected function getIdentifiers(NodesSources $source): array { $urlTokens = []; $parents = []; diff --git a/src/Routing/RedirectionMatcher.php b/src/Routing/RedirectionMatcher.php index 81001bee..7dcbfe2a 100644 --- a/src/Routing/RedirectionMatcher.php +++ b/src/Routing/RedirectionMatcher.php @@ -18,12 +18,17 @@ */ final class RedirectionMatcher extends UrlMatcher { + private LoggerInterface $logger; + private RedirectionPathResolver $pathResolver; + public function __construct( RequestContext $context, - private readonly RedirectionPathResolver $pathResolver, - private readonly LoggerInterface $logger + RedirectionPathResolver $pathResolver, + LoggerInterface $logger ) { parent::__construct(new RouteCollection(), $context); + $this->logger = $logger; + $this->pathResolver = $pathResolver; } /** diff --git a/src/Routing/RedirectionPathResolver.php b/src/Routing/RedirectionPathResolver.php index d6947849..7842a60d 100644 --- a/src/Routing/RedirectionPathResolver.php +++ b/src/Routing/RedirectionPathResolver.php @@ -12,13 +12,20 @@ final class RedirectionPathResolver implements PathResolverInterface { + private ManagerRegistry $managerRegistry; + private Stopwatch $stopwatch; + private CacheItemPoolInterface $cacheAdapter; + public const CACHE_KEY = 'redirection_path_resolver_cache'; public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly CacheItemPoolInterface $cacheAdapter, - private readonly Stopwatch $stopwatch, + ManagerRegistry $managerRegistry, + CacheItemPoolInterface $cacheAdapter, + Stopwatch $stopwatch, ) { + $this->managerRegistry = $managerRegistry; + $this->stopwatch = $stopwatch; + $this->cacheAdapter = $cacheAdapter; } public function resolvePath( diff --git a/src/Routing/RedirectionRouter.php b/src/Routing/RedirectionRouter.php index 1ec14a2e..8df68ff7 100644 --- a/src/Routing/RedirectionRouter.php +++ b/src/Routing/RedirectionRouter.php @@ -7,7 +7,6 @@ use Doctrine\Persistence\ManagerRegistry; use Psr\Log\LoggerInterface; use Symfony\Cmf\Component\Routing\VersatileGeneratorInterface; -use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RouteCollection; @@ -16,13 +15,24 @@ class RedirectionRouter extends Router implements VersatileGeneratorInterface { + protected ManagerRegistry $managerRegistry; + protected ?Stopwatch $stopwatch; + + /** + * @param RedirectionMatcher $matcher + * @param ManagerRegistry $managerRegistry + * @param array $options + * @param RequestContext|null $context + * @param LoggerInterface|null $logger + * @param Stopwatch|null $stopwatch + */ public function __construct( RedirectionMatcher $matcher, - protected readonly ManagerRegistry $managerRegistry, - protected readonly Stopwatch $stopwatch, + ManagerRegistry $managerRegistry, array $options = [], RequestContext $context = null, LoggerInterface $logger = null, + Stopwatch $stopwatch = null ) { parent::__construct( new NullLoader(), @@ -31,6 +41,8 @@ public function __construct( $context, $logger ); + $this->stopwatch = $stopwatch; + $this->managerRegistry = $managerRegistry; $this->matcher = $matcher; } @@ -47,7 +59,7 @@ public function getRouteCollection(): RouteCollection */ public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string { - throw new RouteNotFoundException(get_class($this) . ' does not support path generation.'); + return ''; } /** @@ -58,7 +70,12 @@ public function getGenerator(): UrlGeneratorInterface throw new \BadMethodCallException(get_class($this) . ' does not support path generation.'); } - public function getRouteDebugMessage(mixed $name, array $parameters = []): string + public function supports($name): bool + { + return false; + } + + public function getRouteDebugMessage($name, array $parameters = []): string { return 'RedirectionRouter does not support path generation.'; } diff --git a/src/Routing/StaticRouter.php b/src/Routing/StaticRouter.php new file mode 100644 index 00000000..4f9b917c --- /dev/null +++ b/src/Routing/StaticRouter.php @@ -0,0 +1,52 @@ +routeCollection = $routeCollection; + } + + /** + * @return RouteCollection + */ + public function getRouteCollection(): RouteCollection + { + if (null === $this->collection) { + $this->routeCollection->parseResources(); + $this->collection = $this->routeCollection; + } + return $this->collection; + } +} diff --git a/src/SearchEngine/AbstractSearchHandler.php b/src/SearchEngine/AbstractSearchHandler.php index 133f3691..9e12ac68 100644 --- a/src/SearchEngine/AbstractSearchHandler.php +++ b/src/SearchEngine/AbstractSearchHandler.php @@ -54,6 +54,7 @@ public function getSolr(): Client * @param array $args * @param int $rows * @param bool $searchTags Search in tags/folders too, even if a node don’t match + * @param int $proximity Proximity matching: Lucene supports finding words are a within a specific distance away. * @param int $page * * @return SearchResultsInterface Return a SearchResultsInterface iterable object. @@ -63,13 +64,14 @@ public function searchWithHighlight( array $args = [], int $rows = 20, bool $searchTags = false, + int $proximity = 1, int $page = 1 ): SearchResultsInterface { $args = $this->argFqProcess($args); $args["fq"][] = "document_type_s:" . $this->getDocumentType(); - $args["hl.q"] = $this->buildHighlightingQuery($q); + $args["hl.q"] = $this->escapeQuery(trim($q)); $args = array_merge($this->getHighlightingOptions($args), $args); - $response = $this->nativeSearch($q, $args, $rows, $searchTags, $page); + $response = $this->nativeSearch($q, $args, $rows, $searchTags, $proximity, $page); return $this->createSearchResultsFromResponse($response); } @@ -150,6 +152,7 @@ public function setHighlightingFragmentSize(int $highlightingFragmentSize): Abst * @param array $args * @param int $rows * @param bool $searchTags + * @param int $proximity Proximity matching: Lucene supports finding words are a within a specific distance away. * @param int $page * * @return array|null @@ -159,6 +162,7 @@ abstract protected function nativeSearch( array $args = [], int $rows = 20, bool $searchTags = false, + int $proximity = 1, int $page = 1 ): ?array; @@ -191,6 +195,7 @@ abstract protected function nativeSearch( * @param array $args * @param int $rows Results per page * @param bool $searchTags Search in tags/folders too, even if a node don’t match + * @param int $proximity Proximity matching: Lucene supports finding words are a within a specific distance away. Default 10000000 * @param int $page Retrieve a specific page * * @return SearchResultsInterface Return an array of doctrine Entities (Document, NodesSources) @@ -200,6 +205,7 @@ public function search( array $args = [], int $rows = 20, bool $searchTags = false, + int $proximity = 1, int $page = 1 ): SearchResultsInterface { $args = $this->argFqProcess($args); @@ -207,7 +213,7 @@ public function search( $tmp = []; $args = array_merge($tmp, $args); - $response = $this->nativeSearch($q, $args, $rows, $searchTags, $page); + $response = $this->nativeSearch($q, $args, $rows, $searchTags, $proximity, $page); return $this->createSearchResultsFromResponse($response); } @@ -229,9 +235,10 @@ public function escapeQuery(string $input): string /** * @param string $q + * @param int $proximity * @return array [$exactQuery, $fuzzyQuery, $wildcardQuery] */ - protected function getFormattedQuery(string $q): array + protected function getFormattedQuery(string $q, int $proximity = 1): array { $q = trim($q); /** @@ -242,13 +249,13 @@ protected function getFormattedQuery(string $q): array if (false === $words) { throw new \RuntimeException('Cannot split query string.'); } - $fuzzyiedQuery = implode(' ', array_map(function (string $word) { + $fuzzyiedQuery = implode(' ', array_map(function (string $word) use ($proximity) { /* * Do not fuzz short words: Solr crashes * Proximity is set to 1 by default for single-words */ if (\mb_strlen($word) > 3) { - return $this->escapeQuery($word) . '~2'; + return $this->escapeQuery($word) . '~' . $proximity; } return $this->escapeQuery($word); }, $words)); @@ -259,7 +266,7 @@ protected function getFormattedQuery(string $q): array /* * Wildcard search for allowing autocomplete */ - $wildcardQuery = $this->escapeQuery($q) . '*~2'; + $wildcardQuery = $this->escapeQuery($q) . '*~' . $proximity; return [$exactQuery, $fuzzyiedQuery, $wildcardQuery]; } @@ -272,14 +279,15 @@ protected function getFormattedQuery(string $q): array * @param string $q * @param array $args * @param bool $searchTags + * @param int $proximity * @return string */ - protected function buildQuery(string $q, array &$args, bool $searchTags = false): string + protected function buildQuery(string $q, array &$args, bool $searchTags = false, int $proximity = 1): string { $titleField = $this->getTitleField($args); $collectionField = $this->getCollectionField($args); $tagsField = $this->getTagsField($args); - [$exactQuery, $fuzzyiedQuery, $wildcardQuery] = $this->getFormattedQuery($q); + [$exactQuery, $fuzzyiedQuery, $wildcardQuery] = $this->getFormattedQuery($q, $proximity); /* * Search in node-sources tags name… @@ -308,35 +316,6 @@ protected function buildQuery(string $q, array &$args, bool $searchTags = false) } } - protected function buildHighlightingQuery(string $q): string - { - $q = trim($q); - $words = preg_split('#[\s,]+#', $q, -1, PREG_SPLIT_NO_EMPTY); - if (\is_array($words) && \count($words) > 1) { - return $this->escapeQuery($q); - } - - $q = $this->escapeQuery($q); - return sprintf('%s~2', $q); - } - - /** - * @param array $args - * @param bool $searchTags - * @return string - */ - protected function buildQueryFields(array &$args, bool $searchTags = true): string - { - $titleField = $this->getTitleField($args); - $collectionField = $this->getCollectionField($args); - $tagsField = $this->getTagsField($args); - - if ($searchTags) { - return $titleField . '^10 ' . $collectionField . '^2 ' . $tagsField . ' slug_s'; - } - return $titleField . ' ' . $collectionField . ' slug_s'; - } - /** * @param string $q * diff --git a/src/SearchEngine/DocumentSearchHandler.php b/src/SearchEngine/DocumentSearchHandler.php index eb83052a..58cb4084 100644 --- a/src/SearchEngine/DocumentSearchHandler.php +++ b/src/SearchEngine/DocumentSearchHandler.php @@ -18,6 +18,7 @@ class DocumentSearchHandler extends AbstractSearchHandler * @param array $args * @param integer $rows * @param bool $searchTags + * @param integer $proximity Proximity matching: Lucene supports finding words are a within a specific distance away. * @param integer $page * * @return array|null @@ -27,13 +28,14 @@ protected function nativeSearch( array $args = [], int $rows = 20, bool $searchTags = false, + int $proximity = 1, int $page = 1 ): ?array { if (empty($q)) { return null; } $query = $this->createSolrQuery($args, $rows, $page); - $queryTxt = $this->buildQuery($q, $args, $searchTags); + $queryTxt = $this->buildQuery($q, $args, $searchTags, $proximity); $query->setQuery($queryTxt); /* diff --git a/src/SearchEngine/GlobalNodeSourceSearchHandler.php b/src/SearchEngine/GlobalNodeSourceSearchHandler.php index 761a221d..f14e4dac 100644 --- a/src/SearchEngine/GlobalNodeSourceSearchHandler.php +++ b/src/SearchEngine/GlobalNodeSourceSearchHandler.php @@ -54,55 +54,50 @@ public function getNodeSourcesBySearchTerm( ): array { $safeSearchTerms = strip_tags($searchTerm); - /** - * First try with Solr. - * - * @var array> $nodesSources + /* + * First try with Solr */ + /** @var array $nodesSources */ $nodesSources = $this->getRepository()->findBySearchQuery( $safeSearchTerms, $resultCount ); - if (count($nodesSources) > 0) { - return array_map(function (SolrSearchResultItem $item) { - return $item->getItem(); - }, $nodesSources); - } - /* * Second try with sources fields */ - $nodesSources = $this->getRepository()->searchBy( - $safeSearchTerms, - [], - [], - $resultCount - ); - if (count($nodesSources) === 0) { - /* - * Then try with node name. - */ - $qb = $this->getRepository()->createQueryBuilder('ns'); + $nodesSources = $this->getRepository()->searchBy( + $safeSearchTerms, + [], + [], + $resultCount + ); - $qb->select('ns, n') - ->innerJoin('ns.node', 'n') - ->andWhere($qb->expr()->orX( - $qb->expr()->like('n.nodeName', ':nodeName'), - $qb->expr()->like('ns.title', ':nodeName') - )) - ->setMaxResults($resultCount) - ->setParameter('nodeName', '%' . $safeSearchTerms . '%'); + if (count($nodesSources) === 0) { + /* + * Then try with node name. + */ + $qb = $this->getRepository()->createQueryBuilder('ns'); - if (null !== $translation) { - $qb->andWhere($qb->expr()->eq('ns.translation', ':translation')) - ->setParameter('translation', $translation); - } - try { - return $qb->getQuery()->getResult(); - } catch (NoResultException $e) { - return []; + $qb->select('ns, n') + ->innerJoin('ns.node', 'n') + ->andWhere($qb->expr()->orX( + $qb->expr()->like('n.nodeName', ':nodeName'), + $qb->expr()->like('ns.title', ':nodeName') + )) + ->setMaxResults($resultCount) + ->setParameter('nodeName', '%' . $safeSearchTerms . '%'); + + if (null !== $translation) { + $qb->andWhere($qb->expr()->eq('ns.translation', ':translation')) + ->setParameter('translation', $translation); + } + try { + return $qb->getQuery()->getResult(); + } catch (NoResultException $e) { + return []; + } } } diff --git a/src/SearchEngine/Indexer/AbstractIndexer.php b/src/SearchEngine/Indexer/AbstractIndexer.php index 94e9716c..ee723f7f 100644 --- a/src/SearchEngine/Indexer/AbstractIndexer.php +++ b/src/SearchEngine/Indexer/AbstractIndexer.php @@ -14,16 +14,22 @@ abstract class AbstractIndexer implements CliAwareIndexer { + private ClientRegistry $clientRegistry; + protected SolariumFactoryInterface $solariumFactory; protected LoggerInterface $logger; protected ?SymfonyStyle $io = null; + protected ManagerRegistry $managerRegistry; public function __construct( - protected readonly ClientRegistry $clientRegistry, - protected readonly ManagerRegistry $managerRegistry, - protected readonly SolariumFactoryInterface $solariumFactory, - readonly LoggerInterface $searchEngineLogger + ClientRegistry $clientRegistry, + ManagerRegistry $managerRegistry, + SolariumFactoryInterface $solariumFactory, + LoggerInterface $searchEngineLogger ) { + $this->solariumFactory = $solariumFactory; + $this->clientRegistry = $clientRegistry; $this->logger = $searchEngineLogger; + $this->managerRegistry = $managerRegistry; } /** diff --git a/src/SearchEngine/Indexer/DocumentIndexer.php b/src/SearchEngine/Indexer/DocumentIndexer.php index 7118e785..20992dff 100644 --- a/src/SearchEngine/Indexer/DocumentIndexer.php +++ b/src/SearchEngine/Indexer/DocumentIndexer.php @@ -61,8 +61,9 @@ public function reindexAll(): void ->createQueryBuilder('d') ->getQuery(); - $this->io?->title(get_class($this)); - $this->io?->progressStart((int) $countQuery->getSingleScalarResult()); + if (null !== $this->io) { + $this->io->progressStart((int) $countQuery->getSingleScalarResult()); + } foreach ($q->toIterable() as $row) { $solarium = $this->solariumFactory->createWithDocument($row); @@ -71,7 +72,9 @@ public function reindexAll(): void foreach ($solarium->getDocuments() as $document) { $buffer->addDocument($document); } - $this->io?->progressAdvance(); + if (null !== $this->io) { + $this->io->progressAdvance(); + } // detach from Doctrine, so that it can be Garbage-Collected immediately $this->managerRegistry->getManager()->detach($row); } @@ -80,6 +83,8 @@ public function reindexAll(): void // optimize the index $this->optimizeSolr(); - $this->io?->progressFinish(); + if (null !== $this->io) { + $this->io->progressFinish(); + } } } diff --git a/src/SearchEngine/Indexer/NodesSourcesIndexer.php b/src/SearchEngine/Indexer/NodesSourcesIndexer.php index 65279dd0..a7780845 100644 --- a/src/SearchEngine/Indexer/NodesSourcesIndexer.php +++ b/src/SearchEngine/Indexer/NodesSourcesIndexer.php @@ -115,8 +115,9 @@ public function reindexAll(int $batchCount = 1, int $batchNumber = 0): void */ $paginator = new Paginator($baseQb->getQuery(), true); - $this->io?->title(get_class($this)); - $this->io?->progressStart($count); + if (null !== $this->io) { + $this->io->progressStart($count); + } foreach ($paginator as $row) { $solarium = $this->solariumFactory->createWithNodesSources($row); @@ -124,7 +125,9 @@ public function reindexAll(int $batchCount = 1, int $batchNumber = 0): void $solarium->index(); $buffer->addDocument($solarium->getDocument()); - $this->io?->progressAdvance(); + if (null !== $this->io) { + $this->io->progressAdvance(); + } // detach from Doctrine, so that it can be Garbage-Collected immediately $this->managerRegistry->getManager()->detach($row); } @@ -134,6 +137,8 @@ public function reindexAll(int $batchCount = 1, int $batchNumber = 0): void // optimize the index $this->optimizeSolr(); - $this->io?->progressFinish(); + if (null !== $this->io) { + $this->io->progressFinish(); + } } } diff --git a/src/SearchEngine/NodeSourceSearchHandler.php b/src/SearchEngine/NodeSourceSearchHandler.php index 56c6a166..bc4dc90d 100644 --- a/src/SearchEngine/NodeSourceSearchHandler.php +++ b/src/SearchEngine/NodeSourceSearchHandler.php @@ -25,6 +25,7 @@ class NodeSourceSearchHandler extends AbstractSearchHandler implements NodeSourc * @param array $args * @param integer $rows * @param bool $searchTags + * @param int $proximity Proximity matching: Lucene supports finding words are a within a specific distance away. * @param int $page * * @return array|null @@ -34,13 +35,14 @@ protected function nativeSearch( array $args = [], int $rows = 20, bool $searchTags = false, + int $proximity = 1, int $page = 1 ): ?array { if (empty($q)) { return null; } $query = $this->createSolrQuery($args, $rows, $page); - $queryTxt = $this->buildQuery($q, $args, $searchTags); + $queryTxt = $this->buildQuery($q, $args, $searchTags, $proximity); if ($this->boostByPublicationDate) { $boost = '{!boost b=recip(ms(NOW,published_at_dt),3.16e-11,1,1)}'; diff --git a/src/SearchEngine/SearchHandlerInterface.php b/src/SearchEngine/SearchHandlerInterface.php index 9612829c..373c3a66 100644 --- a/src/SearchEngine/SearchHandlerInterface.php +++ b/src/SearchEngine/SearchHandlerInterface.php @@ -11,6 +11,7 @@ interface SearchHandlerInterface * @param array $args * @param int $rows Results per page * @param bool $searchTags Search in tags/folders too, even if a node don’t match + * @param int $proximity Proximity matching: Lucene supports finding words are a within a specific distance away. Default 10000000 * @param int $page Retrieve a specific page * * @return SearchResultsInterface Return an array of doctrine Entities (Document, NodesSources) @@ -20,6 +21,7 @@ public function search( array $args = [], int $rows = 20, bool $searchTags = false, + int $proximity = 1, int $page = 1 ): SearchResultsInterface; @@ -30,6 +32,7 @@ public function search( * @param array $args * @param int $rows * @param boolean $searchTags Search in tags/folders too, even if a node don’t match + * @param int $proximity Proximity matching: Lucene supports finding words are a within a specific distance away. * @param int $page * * @return SearchResultsInterface Return a SearchResultsInterface iterable object. @@ -39,6 +42,7 @@ public function searchWithHighlight( array $args = [], int $rows = 20, bool $searchTags = false, + int $proximity = 1, int $page = 1 ): SearchResultsInterface; diff --git a/src/SearchEngine/SearchResultsInterface.php b/src/SearchEngine/SearchResultsInterface.php index 8705097a..a4ffb6c2 100644 --- a/src/SearchEngine/SearchResultsInterface.php +++ b/src/SearchEngine/SearchResultsInterface.php @@ -4,15 +4,9 @@ namespace RZ\Roadiz\CoreBundle\SearchEngine; -/** - * @extends \Iterator - */ interface SearchResultsInterface extends \Iterator { public function getResultCount(): int; - /** - * @return array - */ public function getResultItems(): array; public function map(callable $callable): array; } diff --git a/src/SearchEngine/SolrSearchResultItem.php b/src/SearchEngine/SolrSearchResultItem.php deleted file mode 100644 index 9f26b97c..00000000 --- a/src/SearchEngine/SolrSearchResultItem.php +++ /dev/null @@ -1,48 +0,0 @@ -> $highlighting - */ - public function __construct( - protected readonly object $item, - protected readonly array $highlighting = [] - ) { - } - - /** - * @return T - */ - #[ApiProperty] - #[Groups(['get'])] - public function getItem(): object - { - return $this->item; - } - - /** - * @return array> - */ - #[ApiProperty] - #[Groups(['get'])] - public function getHighlighting(): array - { - return $this->highlighting; - } -} diff --git a/src/SearchEngine/SolrSearchResults.php b/src/SearchEngine/SolrSearchResults.php index 50d7bc52..056653a6 100644 --- a/src/SearchEngine/SolrSearchResults.php +++ b/src/SearchEngine/SolrSearchResults.php @@ -8,41 +8,49 @@ use JMS\Serializer\Annotation as JMS; use RZ\Roadiz\CoreBundle\Entity\DocumentTranslation; use RZ\Roadiz\CoreBundle\Entity\NodesSources; -use Symfony\Component\Serializer\Attribute\Ignore; +use RZ\Roadiz\Documents\Models\DocumentInterface; /** * Wrapper over Solr search results and metas. + * + * @package RZ\Roadiz\CoreBundle\SearchEngine */ class SolrSearchResults implements SearchResultsInterface { - #[JMS\Exclude] - #[Ignore] + /** + * @JMS\Exclude() + */ + protected array $response; + /** + * @JMS\Exclude() + */ + protected ObjectManager $entityManager; + /** + * @JMS\Exclude() + */ protected int $position; - /** - * @var array|null + * @JMS\Exclude() */ - #[JMS\Exclude] - #[Ignore] protected ?array $resultItems; - public function __construct( - #[JMS\Exclude] - #[Ignore] - protected readonly array $response, - #[JMS\Exclude] - #[Ignore] - protected readonly ObjectManager $entityManager - ) { + /** + * @param array $response + * @param ObjectManager $entityManager + */ + public function __construct(array $response, ObjectManager $entityManager) + { + $this->response = $response; + $this->entityManager = $entityManager; $this->position = 0; $this->resultItems = null; } /** * @return int + * @JMS\Groups({"search_results"}) + * @JMS\VirtualProperty() */ - #[JMS\Groups(["search_results"])] - #[JMS\VirtualProperty()] public function getResultCount(): int { if ( @@ -54,10 +62,10 @@ public function getResultCount(): int } /** - * @return array + * @return array + * @JMS\Groups({"search_results"}) + * @JMS\VirtualProperty() */ - #[JMS\Groups(["search_results"])] - #[JMS\VirtualProperty()] public function getResultItems(): array { if (null === $this->resultItems) { @@ -66,16 +74,26 @@ public function getResultItems(): array isset($this->response['response']['docs']) ) { $this->resultItems = array_filter(array_map( - function (array $item) { + function ($item) { $object = $this->getHydratedItem($item); - if (!\is_object($object)) { - return null; + if (isset($this->response["highlighting"])) { + $key = 'object'; + if ($object instanceof NodesSources) { + $key = 'nodeSource'; + } + if ($object instanceof DocumentInterface) { + $key = 'document'; + } + if ($object instanceof DocumentTranslation) { + $key = 'document'; + $object = $object->getDocument(); + } + return [ + $key => $object, + 'highlighting' => $this->getHighlighting($item['id']), + ]; } - $highlighting = $this->getHighlighting($item['id']); - return new SolrSearchResultItem( - $object, - $highlighting - ); + return $object; }, $this->response['response']['docs'] )); @@ -87,7 +105,7 @@ function (array $item) { /** * Get highlighting for one field. - * This does not merge highlighting for all fields anymore. + * This do not merge highlighting for all fields anymore. * * @param string $id * @return array @@ -125,11 +143,10 @@ protected function getHydratedItem(array $item): mixed $item[SolariumNodeSource::IDENTIFIER_KEY] ); case SolariumDocumentTranslation::DOCUMENT_TYPE: - $documentTranslation = $this->entityManager->find( + return $this->entityManager->find( DocumentTranslation::class, $item[SolariumDocumentTranslation::IDENTIFIER_KEY] ); - return $documentTranslation?->getDocument(); } } @@ -140,11 +157,11 @@ protected function getHydratedItem(array $item): mixed * Return the current element * * @link https://php.net/manual/en/iterator.current.php - * @return SolrSearchResultItem + * @return mixed Can return any type. * @since 5.0 */ #[\ReturnTypeWillChange] - public function current(): SolrSearchResultItem + public function current(): mixed { return $this->getResultItems()[$this->position]; } diff --git a/src/SearchEngine/Subscriber/DefaultNodesSourcesIndexingSubscriber.php b/src/SearchEngine/Subscriber/DefaultNodesSourcesIndexingSubscriber.php index f86bb8e1..70a7ae00 100644 --- a/src/SearchEngine/Subscriber/DefaultNodesSourcesIndexingSubscriber.php +++ b/src/SearchEngine/Subscriber/DefaultNodesSourcesIndexingSubscriber.php @@ -235,6 +235,9 @@ protected function canIndexTitleInCollection(NodesSources $source): bool return ((bool) $source->getShowTitle()); } - return $source->getNode()->getNodeType()->isSearchable(); + if (null !== $source->getNode() && $source->getNode()->getNodeType()) { + return $source->getNode()->getNodeType()->isSearchable(); + } + return true; } } diff --git a/src/Security/Authentication/RoadizAuthenticator.php b/src/Security/Authentication/RoadizAuthenticator.php index 6c22dc08..c3708ca0 100644 --- a/src/Security/Authentication/RoadizAuthenticator.php +++ b/src/Security/Authentication/RoadizAuthenticator.php @@ -16,7 +16,7 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge; diff --git a/src/Security/Authorization/Voter/GroupVoter.php b/src/Security/Authorization/Voter/GroupVoter.php index a6d8a341..10bcfb52 100644 --- a/src/Security/Authorization/Voter/GroupVoter.php +++ b/src/Security/Authorization/Voter/GroupVoter.php @@ -13,8 +13,11 @@ class GroupVoter extends RoleVoter { - public function __construct(private readonly RoleHierarchyInterface $roleHierarchy, string $prefix = 'ROLE_') + private RoleHierarchyInterface $roleHierarchy; + + public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefix = 'ROLE_') { + $this->roleHierarchy = $roleHierarchy; parent::__construct($prefix); } @@ -93,6 +96,11 @@ protected function extractGroupRoles(Group $group): array */ protected function isRoleContained(string $role, array $roles): bool { - return \in_array($role, $roles, true); + foreach ($roles as $singleRole) { + if ($role === $singleRole) { + return true; + } + } + return false; } } diff --git a/src/Security/Authorization/Voter/NodeTypeFieldVoter.php b/src/Security/Authorization/Voter/NodeTypeFieldVoter.php index eb55732c..9789d022 100644 --- a/src/Security/Authorization/Voter/NodeTypeFieldVoter.php +++ b/src/Security/Authorization/Voter/NodeTypeFieldVoter.php @@ -5,9 +5,9 @@ namespace RZ\Roadiz\CoreBundle\Security\Authorization\Voter; use RZ\Roadiz\CoreBundle\Entity\NodeTypeField; -use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; final class NodeTypeFieldVoter extends Voter diff --git a/src/Security/Authorization/Voter/NodeVoter.php b/src/Security/Authorization/Voter/NodeVoter.php index 4cfbe0aa..b44ca91a 100644 --- a/src/Security/Authorization/Voter/NodeVoter.php +++ b/src/Security/Authorization/Voter/NodeVoter.php @@ -8,14 +8,11 @@ use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Node\NodeOffspringResolverInterface; use RZ\Roadiz\CoreBundle\Security\Authorization\Chroot\NodeChrootResolver; -use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; -/** - * @extends Voter<'CREATE'|'DUPLICATE'|'CREATE_AT_ROOT'|'SEARCH'|'READ'|'READ_AT_ROOT'|'EMPTY_TRASH'|'READ_LOGS'|'EDIT_CONTENT'|'EDIT_TAGS'|'EDIT_REALMS'|'EDIT_SETTING'|'EDIT_STATUS'|'EDIT_ATTRIBUTE'|'DELETE', Node> - */ final class NodeVoter extends Voter { public const CREATE = 'CREATE'; @@ -41,7 +38,7 @@ public function __construct( ) { } - protected function supports(string $attribute, mixed $subject): bool + protected function supports(string $attribute, $subject): bool { if ( \in_array($attribute, [ @@ -79,7 +76,7 @@ protected function supports(string $attribute, mixed $subject): bool return false; } - protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { $user = $token->getUser(); diff --git a/src/Security/Authorization/Voter/RealmVoter.php b/src/Security/Authorization/Voter/RealmVoter.php index 204508da..c7d31b0b 100644 --- a/src/Security/Authorization/Voter/RealmVoter.php +++ b/src/Security/Authorization/Voter/RealmVoter.php @@ -4,25 +4,26 @@ namespace RZ\Roadiz\CoreBundle\Security\Authorization\Voter; +use RZ\Roadiz\CoreBundle\Entity\Realm; use RZ\Roadiz\CoreBundle\Model\RealmInterface; -use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; -/** - * @extends Voter<'read'|'password', RealmInterface> - */ final class RealmVoter extends Voter { public const READ = 'read'; public const PASSWORD_QUERY_PARAMETER = 'password'; - public function __construct( - private readonly Security $security, - private readonly RequestStack $requestStack - ) { + private Security $security; + private RequestStack $requestStack; + + public function __construct(Security $security, RequestStack $requestStack) + { + $this->security = $security; + $this->requestStack = $requestStack; } public function supportsAttribute(string $attribute): bool @@ -30,18 +31,18 @@ public function supportsAttribute(string $attribute): bool return $attribute === self::READ; } - protected function supports(string $attribute, mixed $subject): bool + protected function supports(string $attribute, $subject): bool { return $this->supportsAttribute($attribute) && $subject instanceof RealmInterface; } /** * @param string $attribute - * @param RealmInterface $subject + * @param Realm $subject * @param TokenInterface $token * @return bool */ - public function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + public function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { return match ($subject->getType()) { RealmInterface::TYPE_PLAIN_PASSWORD => $this->voteForPassword($attribute, $subject, $token), diff --git a/src/Security/Authorization/Voter/RoleArrayVoter.php b/src/Security/Authorization/Voter/RoleArrayVoter.php index b373a3c2..21ae892f 100644 --- a/src/Security/Authorization/Voter/RoleArrayVoter.php +++ b/src/Security/Authorization/Voter/RoleArrayVoter.php @@ -40,8 +40,10 @@ public function vote(TokenInterface $token, $subject, array $attributes): int } $result = VoterInterface::ACCESS_DENIED; - if (\in_array($singleAttribute, $roles, true)) { - return VoterInterface::ACCESS_GRANTED; + foreach ($roles as $role) { + if ($singleAttribute === $role) { + return VoterInterface::ACCESS_GRANTED; + } } } } diff --git a/src/Security/Authorization/Voter/SuperAdminRoleHierarchyVoter.php b/src/Security/Authorization/Voter/SuperAdminRoleHierarchyVoter.php index 23ccd845..e893fcbb 100644 --- a/src/Security/Authorization/Voter/SuperAdminRoleHierarchyVoter.php +++ b/src/Security/Authorization/Voter/SuperAdminRoleHierarchyVoter.php @@ -10,9 +10,16 @@ final class SuperAdminRoleHierarchyVoter extends RoleArrayVoter { - public function __construct(private readonly ManagerRegistry $managerRegistry, string $prefix = 'ROLE_') + private ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + * @param string $prefix + */ + public function __construct(ManagerRegistry $managerRegistry, string $prefix = 'ROLE_') { parent::__construct($prefix); + $this->managerRegistry = $managerRegistry; } protected function extractRoles(TokenInterface $token): array @@ -30,10 +37,7 @@ protected function extractRoles(TokenInterface $token): array private function isSuperAdmin(TokenInterface $token): bool { $roleNames = parent::extractRoles($token); - if ( - \in_array('ROLE_SUPER_ADMIN', $roleNames) || - \in_array('ROLE_SUPERADMIN', $roleNames) - ) { + if (\in_array('ROLE_SUPER_ADMIN', $roleNames) || \in_array('ROLE_SUPERADMIN', $roleNames)) { return true; } return false; diff --git a/src/Serializer/CircularReferenceHandler.php b/src/Serializer/CircularReferenceHandler.php index 8b5055d7..d53c7666 100644 --- a/src/Serializer/CircularReferenceHandler.php +++ b/src/Serializer/CircularReferenceHandler.php @@ -6,11 +6,18 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\UrlGeneratorInterface; +use ApiPlatform\Metadata\Operation; final class CircularReferenceHandler { - public function __construct(private readonly IriConverterInterface $iriConverter) + private IriConverterInterface $iriConverter; + + /** + * @param IriConverterInterface $iriConverter + */ + public function __construct(IriConverterInterface $iriConverter) { + $this->iriConverter = $iriConverter; } public function __invoke(mixed $object, string $format, array $context): ?string diff --git a/src/Serializer/Normalizer/AbstractPathNormalizer.php b/src/Serializer/Normalizer/AbstractPathNormalizer.php index 729e3ae5..e66fb474 100644 --- a/src/Serializer/Normalizer/AbstractPathNormalizer.php +++ b/src/Serializer/Normalizer/AbstractPathNormalizer.php @@ -5,12 +5,13 @@ namespace RZ\Roadiz\CoreBundle\Serializer\Normalizer; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; -abstract class AbstractPathNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface +abstract class AbstractPathNormalizer implements ContextAwareNormalizerInterface, DenormalizerInterface, SerializerAwareInterface { protected UrlGeneratorInterface $urlGenerator; /** @@ -28,27 +29,27 @@ public function __construct(NormalizerInterface $decorated, UrlGeneratorInterfac $this->urlGenerator = $urlGenerator; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization($data, $format = null, array $context = []): bool { - return $this->decorated->supportsNormalization($data, $format/*, $context*/); + return $this->decorated->supportsNormalization($data, $format); } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization($data, $type, $format = null): bool { - return $this->decorated->supportsDenormalization($data, $type, $format/*, $context*/); + return $this->decorated->supportsDenormalization($data, $type, $format); } /** * @param mixed $data - * @param string $type + * @param string $class * @param string|null $format * @param array $context * @return mixed * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize($data, $class, $format = null, array $context = []) { - return $this->decorated->denormalize($data, $type, $format, $context); + return $this->decorated->denormalize($data, $class, $format, $context); } public function setSerializer(SerializerInterface $serializer): void @@ -57,11 +58,4 @@ public function setSerializer(SerializerInterface $serializer): void $this->decorated->setSerializer($serializer); } } - - public function getSupportedTypes(?string $format): array - { - return [ - '*' => false, - ]; - } } diff --git a/src/Serializer/Normalizer/AttributeValueNormalizer.php b/src/Serializer/Normalizer/AttributeValueNormalizer.php index b125a1a3..ed4d34c9 100644 --- a/src/Serializer/Normalizer/AttributeValueNormalizer.php +++ b/src/Serializer/Normalizer/AttributeValueNormalizer.php @@ -21,7 +21,7 @@ final class AttributeValueNormalizer extends AbstractPathNormalizer * @return array|\ArrayObject|bool|float|int|mixed|string|null * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ - public function normalize(mixed $object, ?string $format = null, array $context = []): mixed + public function normalize($object, $format = null, array $context = []) { $data = $this->decorated->normalize($object, $format, $context); if ($object instanceof AttributeValue && is_array($data)) { diff --git a/src/Serializer/Normalizer/CustomFormNormalizer.php b/src/Serializer/Normalizer/CustomFormNormalizer.php index 6faf3844..e818d442 100644 --- a/src/Serializer/Normalizer/CustomFormNormalizer.php +++ b/src/Serializer/Normalizer/CustomFormNormalizer.php @@ -19,7 +19,7 @@ final class CustomFormNormalizer extends AbstractPathNormalizer * @return array|\ArrayObject|bool|float|int|mixed|string|null * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ - public function normalize(mixed $object, ?string $format = null, array $context = []): mixed + public function normalize($object, $format = null, array $context = []) { $data = $this->decorated->normalize($object, $format, $context); if ($object instanceof CustomForm && is_array($data)) { diff --git a/src/Serializer/Normalizer/DocumentNormalizer.php b/src/Serializer/Normalizer/DocumentNormalizer.php index b11d4d95..2f783e7c 100644 --- a/src/Serializer/Normalizer/DocumentNormalizer.php +++ b/src/Serializer/Normalizer/DocumentNormalizer.php @@ -18,13 +18,18 @@ */ final class DocumentNormalizer extends AbstractPathNormalizer { + private FilesystemOperator $documentsStorage; + private EmbedFinderFactory $embedFinderFactory; + public function __construct( + FilesystemOperator $documentsStorage, NormalizerInterface $decorated, UrlGeneratorInterface $urlGenerator, - private readonly FilesystemOperator $documentsStorage, - private readonly EmbedFinderFactory $embedFinderFactory + EmbedFinderFactory $embedFinderFactory ) { parent::__construct($decorated, $urlGenerator); + $this->documentsStorage = $documentsStorage; + $this->embedFinderFactory = $embedFinderFactory; } /** @@ -34,7 +39,7 @@ public function __construct( * @return array|\ArrayObject|bool|float|int|string|null * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ - public function normalize(mixed $object, ?string $format = null, array $context = []): mixed + public function normalize($object, $format = null, array $context = []) { $data = $this->decorated->normalize($object, $format, $context); if ( diff --git a/src/Serializer/Normalizer/DocumentSourcesNormalizer.php b/src/Serializer/Normalizer/DocumentSourcesNormalizer.php index e52f4004..e61ef468 100644 --- a/src/Serializer/Normalizer/DocumentSourcesNormalizer.php +++ b/src/Serializer/Normalizer/DocumentSourcesNormalizer.php @@ -11,12 +11,15 @@ final class DocumentSourcesNormalizer extends AbstractPathNormalizer { + protected DocumentFinderInterface $documentFinder; + public function __construct( NormalizerInterface $decorated, UrlGeneratorInterface $urlGenerator, - private readonly DocumentFinderInterface $documentFinder + DocumentFinderInterface $documentFinder ) { parent::__construct($decorated, $urlGenerator); + $this->documentFinder = $documentFinder; } /** @@ -26,7 +29,7 @@ public function __construct( * @return array|\ArrayObject|bool|float|int|mixed|string|null * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ - public function normalize(mixed $object, ?string $format = null, array $context = []): mixed + public function normalize($object, $format = null, array $context = []) { $data = $this->decorated->normalize($object, $format, $context); if ($object instanceof Document && is_array($data)) { diff --git a/src/Serializer/Normalizer/FolderNormalizer.php b/src/Serializer/Normalizer/FolderNormalizer.php index a3f86112..b50a7642 100644 --- a/src/Serializer/Normalizer/FolderNormalizer.php +++ b/src/Serializer/Normalizer/FolderNormalizer.php @@ -20,7 +20,7 @@ final class FolderNormalizer extends AbstractPathNormalizer * @return array|\ArrayObject|bool|float|int|mixed|string|null * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ - public function normalize(mixed $object, ?string $format = null, array $context = []): mixed + public function normalize($object, $format = null, array $context = []) { $data = $this->decorated->normalize($object, $format, $context); if ($object instanceof Folder && is_array($data)) { diff --git a/src/Serializer/Normalizer/NodesSourcesPathNormalizer.php b/src/Serializer/Normalizer/NodesSourcesPathNormalizer.php index 650ff2e0..e6c63040 100644 --- a/src/Serializer/Normalizer/NodesSourcesPathNormalizer.php +++ b/src/Serializer/Normalizer/NodesSourcesPathNormalizer.php @@ -17,7 +17,7 @@ final class NodesSourcesPathNormalizer extends AbstractPathNormalizer * @return array|\ArrayObject|bool|float|int|mixed|string|null * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ - public function normalize(mixed $object, ?string $format = null, array $context = []): mixed + public function normalize($object, $format = null, array $context = []) { $data = $this->decorated->normalize($object, $format, $context); if ( diff --git a/src/Serializer/Normalizer/RealmSerializationGroupNormalizer.php b/src/Serializer/Normalizer/RealmSerializationGroupNormalizer.php index a470c7eb..f51583b7 100644 --- a/src/Serializer/Normalizer/RealmSerializationGroupNormalizer.php +++ b/src/Serializer/Normalizer/RealmSerializationGroupNormalizer.php @@ -4,59 +4,52 @@ namespace RZ\Roadiz\CoreBundle\Serializer\Normalizer; +use Doctrine\Persistence\ManagerRegistry; use RZ\Roadiz\CoreBundle\Entity\NodesSources; -use RZ\Roadiz\CoreBundle\Model\RealmInterface; -use RZ\Roadiz\CoreBundle\Realm\RealmResolver; +use RZ\Roadiz\CoreBundle\Entity\Realm; use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\RealmVoter; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Stopwatch\Stopwatch; -final class RealmSerializationGroupNormalizer implements NormalizerInterface, NormalizerAwareInterface +final class RealmSerializationGroupNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; private const ALREADY_CALLED = 'REALM_SERIALIZER_NORMALIZER_ALREADY_CALLED'; + private Security $security; + private ManagerRegistry $managerRegistry; - public function __construct( - private readonly Security $security, - private readonly RealmResolver $realmResolver, - private readonly Stopwatch $stopwatch - ) { + /** + * @param Security $security + * @param ManagerRegistry $managerRegistry + */ + public function __construct(Security $security, ManagerRegistry $managerRegistry) + { + $this->security = $security; + $this->managerRegistry = $managerRegistry; } /** * @inheritDoc */ - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization($data, string $format = null, array $context = []): bool { - if (!($data instanceof NodesSources)) { - return false; - } // Make sure we're not called twice if (isset($context[self::ALREADY_CALLED])) { return false; } - return $this->realmResolver->hasRealmsWithSerializationGroup(); - } - - public function getSupportedTypes(?string $format): array - { - return [ - '*' => false, - ]; + return $data instanceof NodesSources; } /** * @inheritDoc * @return array|string|int|float|bool|\ArrayObject|null */ - public function normalize(mixed $object, ?string $format = null, array $context = []): mixed + public function normalize($object, string $format = null, array $context = []) { - $this->stopwatch->start('realm-serialization-group-normalizer', 'serializer'); $realms = $this->getAuthorizedRealmsForObject($object); foreach ($realms as $realm) { @@ -66,19 +59,18 @@ public function normalize(mixed $object, ?string $format = null, array $context } $context[self::ALREADY_CALLED] = true; - $this->stopwatch->stop('realm-serialization-group-normalizer'); return $this->normalizer->normalize($object, $format, $context); } /** - * @return RealmInterface[] + * @return Realm[] */ private function getAuthorizedRealmsForObject(NodesSources $object): array { - $realms = $this->realmResolver->getRealmsWithSerializationGroup($object->getNode()); + $realms = $this->managerRegistry->getRepository(Realm::class)->findByNode($object->getNode()); - return array_filter($realms, function (RealmInterface $realm) { + return array_filter($realms, function (Realm $realm) { return $this->security->isGranted(RealmVoter::READ, $realm); }); } diff --git a/src/Serializer/Normalizer/TagNormalizer.php b/src/Serializer/Normalizer/TagNormalizer.php index da9a11c7..aa6aa71a 100644 --- a/src/Serializer/Normalizer/TagNormalizer.php +++ b/src/Serializer/Normalizer/TagNormalizer.php @@ -21,28 +21,27 @@ final class TagNormalizer extends AbstractPathNormalizer * @return array|\ArrayObject|bool|float|int|mixed|string|null * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ - public function normalize(mixed $object, ?string $format = null, array $context = []): mixed + public function normalize($object, $format = null, array $context = []) { $data = $this->decorated->normalize($object, $format, $context); - if ( - $object instanceof Tag && - is_array($data) && - isset($context['translation']) && - $context['translation'] instanceof TranslationInterface - ) { + if ($object instanceof Tag && is_array($data)) { + $data['slug'] = $object->getTagName(); /** @var array $serializationGroups */ $serializationGroups = isset($context['groups']) && is_array($context['groups']) ? $context['groups'] : []; - $translatedData = $object->getTranslatedTagsByTranslation($context['translation'])->first() ?: null; - if ($translatedData instanceof TagTranslation) { - $data['name'] = $translatedData->getName(); - $data['description'] = $translatedData->getDescription(); - if (\in_array('tag_documents', $serializationGroups, true)) { - $documentsContext = $context; - $documentsContext['groups'] = ['document_display']; - $data['documents'] = array_map(function (DocumentInterface $document) use ($format, $documentsContext) { - return $this->decorated->normalize($document, $format, $documentsContext); - }, $translatedData->getDocuments()); + if (isset($context['translation']) && $context['translation'] instanceof TranslationInterface) { + $documentsContext = $context; + $documentsContext['groups'] = ['document_display']; + $translatedData = $object->getTranslatedTagsByTranslation($context['translation'])->first() ?: null; + if ($translatedData instanceof TagTranslation) { + $data['name'] = $translatedData->getName(); + $data['description'] = $translatedData->getDescription(); + + if (\in_array('tag_documents', $serializationGroups, true)) { + $data['documents'] = array_map(function (DocumentInterface $document) use ($format, $documentsContext) { + return $this->decorated->normalize($document, $format, $documentsContext); + }, $translatedData->getDocuments()); + } } } } diff --git a/src/Serializer/Normalizer/TranslationAwareNormalizer.php b/src/Serializer/Normalizer/TranslationAwareNormalizer.php index eb1c3df2..583afe2f 100644 --- a/src/Serializer/Normalizer/TranslationAwareNormalizer.php +++ b/src/Serializer/Normalizer/TranslationAwareNormalizer.php @@ -12,21 +12,28 @@ use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; use RZ\Roadiz\CoreBundle\Repository\TranslationRepository; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; -final class TranslationAwareNormalizer implements NormalizerInterface, NormalizerAwareInterface +final class TranslationAwareNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; + private RequestStack $requestStack; + private ManagerRegistry $managerRegistry; + private PreviewResolverInterface $previewResolver; + private const ALREADY_CALLED = 'TRANSLATION_AWARE_NORMALIZER_ALREADY_CALLED'; public function __construct( - private readonly RequestStack $requestStack, - private readonly ManagerRegistry $managerRegistry, - private readonly PreviewResolverInterface $previewResolver + RequestStack $requestStack, + ManagerRegistry $managerRegistry, + PreviewResolverInterface $previewResolver ) { + $this->requestStack = $requestStack; + $this->managerRegistry = $managerRegistry; + $this->previewResolver = $previewResolver; } /** @@ -36,7 +43,7 @@ public function __construct( * @return array|\ArrayObject|bool|float|int|string|null * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface */ - public function normalize(mixed $object, ?string $format = null, array $context = []): mixed + public function normalize($object, $format = null, array $context = []) { if ($object instanceof WebResponseInterface) { $item = $object->getItem(); @@ -76,32 +83,22 @@ private function getTranslationFromRequest(): ?TranslationInterface { $request = $this->requestStack->getMainRequest(); - if (null === $request) { - return $this->managerRegistry - ->getRepository(Translation::class) - ->findDefault(); - } - - /* - * Try to get translation resolved from LocaleSubscriber before - */ - $requestTranslation = $request->attributes->get('_translation'); - if ($requestTranslation instanceof TranslationInterface) { - return $requestTranslation; - } - - $locale = $request->query->get('_locale', $request->getLocale()); - if ( - \is_string($locale) && - null !== $translation = $this->getTranslationFromLocale($locale) - ) { - return $translation; + if (null !== $request) { + $locale = $request->query->get('_locale', $request->getLocale()); + if ( + \is_string($locale) && + null !== $translation = $this->getTranslationFromLocale($locale) + ) { + return $translation; + } } - return null; + return $this->managerRegistry + ->getRepository(Translation::class) + ->findDefault(); } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization($data, $format = null, array $context = []): bool { // Make sure we're not called twice if (isset($context[self::ALREADY_CALLED])) { @@ -110,11 +107,4 @@ public function supportsNormalization(mixed $data, string $format = null, array return true; } - - public function getSupportedTypes(?string $format): array - { - return [ - '*' => false, - ]; - } } diff --git a/src/Serializer/ObjectConstructor/AbstractTypedObjectConstructor.php b/src/Serializer/ObjectConstructor/AbstractTypedObjectConstructor.php index 476a75dd..9fb1f1ea 100644 --- a/src/Serializer/ObjectConstructor/AbstractTypedObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/AbstractTypedObjectConstructor.php @@ -13,10 +13,17 @@ abstract class AbstractTypedObjectConstructor implements TypedObjectConstructorInterface { - public function __construct( - protected readonly ObjectManager $entityManager, - protected readonly ObjectConstructorInterface $fallbackConstructor - ) { + protected ObjectManager $entityManager; + protected ObjectConstructorInterface $fallbackConstructor; + + /** + * @param ObjectManager $entityManager + * @param ObjectConstructorInterface $fallbackConstructor + */ + public function __construct(ObjectManager $entityManager, ObjectConstructorInterface $fallbackConstructor) + { + $this->entityManager = $entityManager; + $this->fallbackConstructor = $fallbackConstructor; } /** @@ -25,7 +32,7 @@ public function __construct( * * @return object|null */ - abstract protected function findObject(mixed $data, DeserializationContext $context): ?object; + abstract protected function findObject($data, DeserializationContext $context): ?object; /** * @param object $object diff --git a/src/Serializer/ObjectConstructor/AttributeObjectConstructor.php b/src/Serializer/ObjectConstructor/AttributeObjectConstructor.php deleted file mode 100644 index ba437452..00000000 --- a/src/Serializer/ObjectConstructor/AttributeObjectConstructor.php +++ /dev/null @@ -1,55 +0,0 @@ -entityManager - ->getRepository(AttributeInterface::class) - ->findOneByCode($data['code']); - - if ( - null !== $tag && - $context->hasAttribute(self::EXCEPTION_ON_EXISTING) && - true === $context->hasAttribute(self::EXCEPTION_ON_EXISTING) - ) { - throw new EntityAlreadyExistsException('Attribute already exists in database.'); - } - - return $tag; - } - - protected function fillIdentifier(object $object, array $data): void - { - if ($object instanceof AttributeInterface) { - $object->setCode($data['code']); - } - } -} diff --git a/src/Serializer/ObjectConstructor/ChainDoctrineObjectConstructor.php b/src/Serializer/ObjectConstructor/ChainDoctrineObjectConstructor.php index eeff57cc..d41adee3 100644 --- a/src/Serializer/ObjectConstructor/ChainDoctrineObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/ChainDoctrineObjectConstructor.php @@ -11,13 +11,28 @@ use JMS\Serializer\Visitor\DeserializationVisitorInterface; use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; -final class ChainDoctrineObjectConstructor implements ObjectConstructorInterface +class ChainDoctrineObjectConstructor implements ObjectConstructorInterface { + protected ?ObjectManager $entityManager; + /** + * @var array + */ + protected array $typedObjectConstructors; + protected ObjectConstructorInterface $fallbackConstructor; + + /** + * @param ObjectManager|null $entityManager + * @param ObjectConstructorInterface $fallbackConstructor + * @param array $typedObjectConstructors + */ public function __construct( - private readonly ?ObjectManager $entityManager, - private readonly ObjectConstructorInterface $fallbackConstructor, - private readonly array $typedObjectConstructors + ?ObjectManager $entityManager, + ObjectConstructorInterface $fallbackConstructor, + array $typedObjectConstructors ) { + $this->entityManager = $entityManager; + $this->typedObjectConstructors = $typedObjectConstructors; + $this->fallbackConstructor = $fallbackConstructor; } /** @@ -88,6 +103,7 @@ public function construct( foreach ($classMetadata->getIdentifierFieldNames() as $name) { if ( + isset($metadata->propertyMetadata[$name]) && isset($metadata->propertyMetadata[$name]->serializedName) ) { $dataName = $metadata->propertyMetadata[$name]->serializedName; diff --git a/src/Serializer/ObjectConstructor/GroupObjectConstructor.php b/src/Serializer/ObjectConstructor/GroupObjectConstructor.php index 9cd71848..8d0fbf1e 100644 --- a/src/Serializer/ObjectConstructor/GroupObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/GroupObjectConstructor.php @@ -8,7 +8,7 @@ use JMS\Serializer\Exception\ObjectConstructionException; use RZ\Roadiz\CoreBundle\Entity\Group; -final class GroupObjectConstructor extends AbstractTypedObjectConstructor +class GroupObjectConstructor extends AbstractTypedObjectConstructor { /** * @inheritDoc @@ -21,7 +21,7 @@ public function supports(string $className, array $data): bool /** * @inheritDoc */ - protected function findObject(mixed $data, DeserializationContext $context): ?object + protected function findObject($data, DeserializationContext $context): ?object { if (null === $data['name'] || $data['name'] === '') { throw new ObjectConstructionException('Group name can not be empty'); diff --git a/src/Serializer/ObjectConstructor/NodeObjectConstructor.php b/src/Serializer/ObjectConstructor/NodeObjectConstructor.php index ec0cc75c..4a9412dc 100644 --- a/src/Serializer/ObjectConstructor/NodeObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/NodeObjectConstructor.php @@ -9,7 +9,7 @@ use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Repository\NodeRepository; -final class NodeObjectConstructor extends AbstractTypedObjectConstructor +class NodeObjectConstructor extends AbstractTypedObjectConstructor { /** * @inheritDoc @@ -22,7 +22,7 @@ public function supports(string $className, array $data): bool /** * @inheritDoc */ - protected function findObject(mixed $data, DeserializationContext $context): ?object + protected function findObject($data, DeserializationContext $context): ?object { if (empty($data['nodeName']) && empty($data['node_name'])) { throw new ObjectConstructionException('Node name can not be empty'); diff --git a/src/Serializer/ObjectConstructor/NodeTypeFieldObjectConstructor.php b/src/Serializer/ObjectConstructor/NodeTypeFieldObjectConstructor.php new file mode 100644 index 00000000..71fddc76 --- /dev/null +++ b/src/Serializer/ObjectConstructor/NodeTypeFieldObjectConstructor.php @@ -0,0 +1,64 @@ +entityManager + ->getRepository(NodeType::class) + ->findOneByName($data['nodeTypeName'] ?? $data['node_type_name']); + + if (null === $nodeType) { + /* + * Do not look for existing fields if node-type does not exist either. + */ + return null; + } + return $this->entityManager + ->getRepository(NodeTypeField::class) + ->findOneBy([ + 'name' => $data['name'], + 'nodeType' => $nodeType, + ]); + } + + protected function fillIdentifier(object $object, array $data): void + { + trigger_error('Cannot call fillIdentifier on NodeTypeField', E_USER_WARNING); + } + + /** + * @return bool + */ + protected function canBeFlushed(): bool + { + return false; + } +} diff --git a/src/Serializer/ObjectConstructor/NodeTypeObjectConstructor.php b/src/Serializer/ObjectConstructor/NodeTypeObjectConstructor.php index 4d51fccd..a3a53084 100644 --- a/src/Serializer/ObjectConstructor/NodeTypeObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/NodeTypeObjectConstructor.php @@ -6,24 +6,56 @@ use JMS\Serializer\DeserializationContext; use JMS\Serializer\Exception\ObjectConstructionException; -use RZ\Roadiz\Contracts\NodeType\NodeTypeInterface; +use JMS\Serializer\Metadata\ClassMetadata; +use JMS\Serializer\Visitor\DeserializationVisitorInterface; use RZ\Roadiz\CoreBundle\Entity\NodeType; -final class NodeTypeObjectConstructor extends AbstractTypedObjectConstructor +class NodeTypeObjectConstructor extends AbstractTypedObjectConstructor { /** * @inheritDoc */ public function supports(string $className, array $data): bool { - return \is_subclass_of($className, NodeTypeInterface::class) && - array_key_exists('name', $data); + return $className === NodeType::class && array_key_exists('name', $data); + } + + public function construct( + DeserializationVisitorInterface $visitor, + ClassMetadata $metadata, + $data, + array $type, + DeserializationContext $context + ): ?object { + $nodeType = parent::construct($visitor, $metadata, $data, $type, $context); + + if ($nodeType instanceof NodeType && \is_array($data) && \array_key_exists('fields', $data)) { + $nodeType = $this->removeExtraFields($nodeType, $data); + } + + return $nodeType; + } + + protected function removeExtraFields(NodeType $nodeType, array $data): NodeType + { + $fieldsName = array_map(function ($field) { + return $field['name']; + }, $data['fields']); + + foreach ($nodeType->getFields() as $field) { + if (!\in_array($field->getName(), $fieldsName)) { + $nodeType->getFields()->removeElement($field); + $field->setNodeType(null); + } + } + + return $nodeType; } /** * @inheritDoc */ - protected function findObject(mixed $data, DeserializationContext $context): ?object + protected function findObject($data, DeserializationContext $context): ?object { if (null === $data['name'] || $data['name'] === '') { throw new ObjectConstructionException('NodeType name can not be empty'); diff --git a/src/Serializer/ObjectConstructor/RoleObjectConstructor.php b/src/Serializer/ObjectConstructor/RoleObjectConstructor.php index 00698c86..492348be 100644 --- a/src/Serializer/ObjectConstructor/RoleObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/RoleObjectConstructor.php @@ -8,7 +8,7 @@ use JMS\Serializer\Exception\ObjectConstructionException; use RZ\Roadiz\CoreBundle\Entity\Role; -final class RoleObjectConstructor extends AbstractTypedObjectConstructor +class RoleObjectConstructor extends AbstractTypedObjectConstructor { /** * @inheritDoc @@ -21,7 +21,7 @@ public function supports(string $className, array $data): bool /** * @inheritDoc */ - protected function findObject(mixed $data, DeserializationContext $context): ?object + protected function findObject($data, DeserializationContext $context): ?object { if (null === $data['name'] || $data['name'] === '') { throw new ObjectConstructionException('Role name can not be empty'); diff --git a/src/Serializer/ObjectConstructor/SettingGroupObjectConstructor.php b/src/Serializer/ObjectConstructor/SettingGroupObjectConstructor.php index 19834d39..0b6a8adb 100644 --- a/src/Serializer/ObjectConstructor/SettingGroupObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/SettingGroupObjectConstructor.php @@ -8,7 +8,7 @@ use JMS\Serializer\Exception\ObjectConstructionException; use RZ\Roadiz\CoreBundle\Entity\SettingGroup; -final class SettingGroupObjectConstructor extends AbstractTypedObjectConstructor +class SettingGroupObjectConstructor extends AbstractTypedObjectConstructor { /** * @inheritDoc @@ -21,7 +21,7 @@ public function supports(string $className, array $data): bool /** * @inheritDoc */ - protected function findObject(mixed $data, DeserializationContext $context): ?object + protected function findObject($data, DeserializationContext $context): ?object { if (null === $data['name'] || $data['name'] === '') { throw new ObjectConstructionException('SettingGroup name can not be empty'); diff --git a/src/Serializer/ObjectConstructor/SettingObjectConstructor.php b/src/Serializer/ObjectConstructor/SettingObjectConstructor.php index c43a7384..7db87eb5 100644 --- a/src/Serializer/ObjectConstructor/SettingObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/SettingObjectConstructor.php @@ -8,7 +8,7 @@ use JMS\Serializer\Exception\ObjectConstructionException; use RZ\Roadiz\CoreBundle\Entity\Setting; -final class SettingObjectConstructor extends AbstractTypedObjectConstructor +class SettingObjectConstructor extends AbstractTypedObjectConstructor { /** * @inheritDoc @@ -21,7 +21,7 @@ public function supports(string $className, array $data): bool /** * @inheritDoc */ - protected function findObject(mixed $data, DeserializationContext $context): ?object + protected function findObject($data, DeserializationContext $context): ?object { if (null === $data['name'] || $data['name'] === '') { throw new ObjectConstructionException('Setting name can not be empty'); diff --git a/src/Serializer/ObjectConstructor/TagObjectConstructor.php b/src/Serializer/ObjectConstructor/TagObjectConstructor.php index cc984c9a..126ed722 100644 --- a/src/Serializer/ObjectConstructor/TagObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/TagObjectConstructor.php @@ -9,7 +9,7 @@ use RZ\Roadiz\CoreBundle\Entity\Tag; use RZ\Roadiz\CoreBundle\Exception\EntityAlreadyExistsException; -final class TagObjectConstructor extends AbstractTypedObjectConstructor +class TagObjectConstructor extends AbstractTypedObjectConstructor { public const EXCEPTION_ON_EXISTING_TAG = 'exception_on_existing_tag'; @@ -27,7 +27,7 @@ public function supports(string $className, array $data): bool /** * @inheritDoc */ - protected function findObject(mixed $data, DeserializationContext $context): ?object + protected function findObject($data, DeserializationContext $context): ?object { if (empty($data['tagName']) && empty($data['tag_name'])) { throw new ObjectConstructionException('Tag name can not be empty'); diff --git a/src/Serializer/ObjectConstructor/TranslationObjectConstructor.php b/src/Serializer/ObjectConstructor/TranslationObjectConstructor.php index 4bc72788..40309d06 100644 --- a/src/Serializer/ObjectConstructor/TranslationObjectConstructor.php +++ b/src/Serializer/ObjectConstructor/TranslationObjectConstructor.php @@ -6,17 +6,16 @@ use JMS\Serializer\DeserializationContext; use JMS\Serializer\Exception\ObjectConstructionException; -use RZ\Roadiz\Core\AbstractEntities\TranslationInterface; +use RZ\Roadiz\CoreBundle\Entity\Translation; -final class TranslationObjectConstructor extends AbstractTypedObjectConstructor +class TranslationObjectConstructor extends AbstractTypedObjectConstructor { /** * @inheritDoc */ public function supports(string $className, array $data): bool { - return \is_subclass_of($className, TranslationInterface::class) && - array_key_exists('locale', $data); + return $className === Translation::class && array_key_exists('locale', $data); } /** @@ -29,13 +28,13 @@ protected function findObject($data, DeserializationContext $context): ?object } return $this->entityManager - ->getRepository(TranslationInterface::class) + ->getRepository(Translation::class) ->findOneByLocale($data['locale']); } protected function fillIdentifier(object $object, array $data): void { - if ($object instanceof TranslationInterface) { + if ($object instanceof Translation) { $object->setLocale($data['locale']); $object->setName($data['locale']); } diff --git a/src/Serializer/TranslationAwareContextBuilder.php b/src/Serializer/TranslationAwareContextBuilder.php index 66867a64..edf63081 100644 --- a/src/Serializer/TranslationAwareContextBuilder.php +++ b/src/Serializer/TranslationAwareContextBuilder.php @@ -13,11 +13,18 @@ final class TranslationAwareContextBuilder implements SerializerContextBuilderInterface { + private ManagerRegistry $managerRegistry; + private SerializerContextBuilderInterface $decorated; + private PreviewResolverInterface $previewResolver; + public function __construct( - private readonly SerializerContextBuilderInterface $decorated, - private readonly ManagerRegistry $managerRegistry, - private readonly PreviewResolverInterface $previewResolver + SerializerContextBuilderInterface $decorated, + ManagerRegistry $managerRegistry, + PreviewResolverInterface $previewResolver ) { + $this->decorated = $decorated; + $this->managerRegistry = $managerRegistry; + $this->previewResolver = $previewResolver; } /** * @inheritDoc @@ -26,38 +33,26 @@ public function createFromRequest(Request $request, bool $normalization, array $ { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); - if (isset($context['translation']) && $context['translation'] instanceof TranslationInterface) { - return $context; - } - - /* - * Try to get translation resolved from LocaleSubscriber before - */ - $requestTranslation = $request->attributes->get('_translation'); - if ($requestTranslation instanceof TranslationInterface) { - $context['translation'] = $requestTranslation; - return $context; - } - - /** @var TranslationRepository $repository */ - $repository = $this->managerRegistry - ->getRepository(TranslationInterface::class); - $locale = $request->query->get('_locale', $request->getLocale()); - - if (!\is_string($locale)) { - return $context; + if (!isset($context['translation']) || !($context['translation'] instanceof TranslationInterface)) { + /** @var TranslationRepository $repository */ + $repository = $this->managerRegistry + ->getRepository(TranslationInterface::class); + $locale = $request->query->get('_locale', $request->getLocale()); + + if (!\is_string($locale)) { + return $context; + } + + if ($this->previewResolver->isPreview()) { + $translation = $repository->findOneByLocaleOrOverrideLocale($locale); + } else { + $translation = $repository->findOneAvailableByLocaleOrOverrideLocale($locale); + } + + if ($translation instanceof TranslationInterface) { + $context['translation'] = $translation; + } } - - if ($this->previewResolver->isPreview()) { - $translation = $repository->findOneByLocaleOrOverrideLocale($locale); - } else { - $translation = $repository->findOneAvailableByLocaleOrOverrideLocale($locale); - } - - if ($translation instanceof TranslationInterface) { - $context['translation'] = $translation; - } - return $context; } } diff --git a/src/Test/NodeType/ApiResourceGeneratorTest.php b/src/Test/NodeType/ApiResourceGeneratorTest.php deleted file mode 100644 index 138a8efc..00000000 --- a/src/Test/NodeType/ApiResourceGeneratorTest.php +++ /dev/null @@ -1,152 +0,0 @@ -getContainer()->get(ApiResourceOperationNameGenerator::class); - - return new ApiResourceGenerator( - $apiResourceOperationNameGenerator, - static::getGeneratedPath(), - new NullLogger(), - WebResponse::class - ); - } - - public function testGenerate(): void - { - $apiResourceGenerator = $this->getApiResourceGenerator(); - - $nodeType = new NodeType(); - $nodeType->setName('Test'); - - $apiResourceGenerator->generate($nodeType); - $resourcePath = $apiResourceGenerator->getResourcePath($nodeType); - $this->assertFileExists($resourcePath); - $this->assertFileEquals( - dirname(__DIR__) . '/../../tests/expected_api_resources/nstest.yml', - $resourcePath - ); - } - - public function testReachableGenerate(): void - { - $apiResourceGenerator = $this->getApiResourceGenerator(); - - $nodeType = new NodeType(); - $nodeType->setName('Test'); - $nodeType->setReachable(true); - - $apiResourceGenerator->generate($nodeType); - $resourcePath = $apiResourceGenerator->getResourcePath($nodeType); - $this->assertFileExists($resourcePath); - $this->assertFileExists(dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml'); - $this->assertFileEquals( - dirname(__DIR__) . '/../../tests/expected_api_resources/nstest.yml', - $resourcePath - ); - $this->assertFileEquals( - dirname(__DIR__) . '/../../tests/expected_api_resources/web_response.yml', - dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml', - ); - } - - public function testMultipleGenerate(): void - { - $apiResourceGenerator = $this->getApiResourceGenerator(); - - $nodeType = new NodeType(); - $nodeType->setName('Test'); - $nodeType->setReachable(true); - - $nodeType2 = new NodeType(); - $nodeType2->setName('SecondTest'); - $nodeType2->setReachable(true); - - $apiResourceGenerator->generate($nodeType); - $resourcePath = $apiResourceGenerator->getResourcePath($nodeType); - $this->assertFileExists($resourcePath); - $this->assertFileEquals( - dirname(__DIR__) . '/../../tests/expected_api_resources/nstest.yml', - $resourcePath - ); - - $apiResourceGenerator->generate($nodeType2); - $resourcePath2 = $apiResourceGenerator->getResourcePath($nodeType2); - $this->assertFileExists($resourcePath2); - $this->assertFileEquals( - dirname(__DIR__) . '/../../tests/expected_api_resources/nssecondtest.yml', - $resourcePath2 - ); - - $this->assertFileExists(dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml'); - $this->assertFileEquals( - dirname(__DIR__) . '/../../tests/expected_api_resources/web_response_multiple.yml', - dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml', - ); - } - - public function testRemoveGenerate(): void - { - $apiResourceGenerator = $this->getApiResourceGenerator(); - - $nodeType = new NodeType(); - $nodeType->setName('Test'); - $nodeType->setReachable(true); - - $nodeType2 = new NodeType(); - $nodeType2->setName('SecondTest'); - $nodeType2->setReachable(true); - - $apiResourceGenerator->generate($nodeType); - $apiResourceGenerator->generate($nodeType2); - - // Remove second node-type to check if operation - // is removed from web_response - $apiResourceGenerator->remove($nodeType2); - $resourcePath2 = $apiResourceGenerator->getResourcePath($nodeType2); - $this->assertFileDoesNotExist($resourcePath2); - - $this->assertFileExists(dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml'); - $this->assertFileEquals( - dirname(__DIR__) . '/../../tests/expected_api_resources/web_response.yml', - dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml', - ); - } - - protected function setUp(): void - { - parent::setUp(); - - $filesystem = new Filesystem(); - $filesystem->mkdir(static::getGeneratedPath()); - } - - - protected function tearDown(): void - { - parent::tearDown(); - - $filesystem = new Filesystem(); - $filesystem->remove(static::getGeneratedPath()); - } -} diff --git a/src/TwigExtension/AttributesExtension.php b/src/TwigExtension/AttributesExtension.php index 92755704..890cfa49 100644 --- a/src/TwigExtension/AttributesExtension.php +++ b/src/TwigExtension/AttributesExtension.php @@ -19,10 +19,16 @@ use Twig\TwigFunction; use Twig\TwigTest; -final class AttributesExtension extends AbstractExtension +class AttributesExtension extends AbstractExtension { - public function __construct(private readonly EntityManagerInterface $entityManager) + protected EntityManagerInterface $entityManager; + + /** + * @param EntityManagerInterface $entityManager + */ + public function __construct(EntityManagerInterface $entityManager) { + $this->entityManager = $entityManager; } public function getFunctions(): array @@ -207,7 +213,7 @@ public function getNodeSourceGroupedAttributeValues(?NodesSources $nodesSources, * * @return string|null */ - public function getAttributeLabelOrCode(mixed $mixed, TranslationInterface $translation = null): ?string + public function getAttributeLabelOrCode($mixed, TranslationInterface $translation = null): ?string { if (null === $mixed) { return null; @@ -234,7 +240,7 @@ public function getAttributeLabelOrCode(mixed $mixed, TranslationInterface $tran * @param TranslationInterface|null $translation * @return string|null */ - public function getAttributeGroupLabelOrCode(mixed $mixed, TranslationInterface $translation = null): ?string + public function getAttributeGroupLabelOrCode($mixed, TranslationInterface $translation = null): ?string { if (null === $mixed) { return null; diff --git a/src/TwigExtension/BlockRenderExtension.php b/src/TwigExtension/BlockRenderExtension.php index 5e028743..e4944307 100644 --- a/src/TwigExtension/BlockRenderExtension.php +++ b/src/TwigExtension/BlockRenderExtension.php @@ -15,10 +15,16 @@ * Extension that allow render inner page part calling directly their * controller response instead of doing a simple include. */ -final class BlockRenderExtension extends AbstractExtension +class BlockRenderExtension extends AbstractExtension { - public function __construct(private readonly FragmentHandler $handler) + protected FragmentHandler $handler; + + /** + * @param FragmentHandler $handler + */ + public function __construct(FragmentHandler $handler) { + $this->handler = $handler; } public function getFilters(): array @@ -36,7 +42,7 @@ public function getFilters(): array * @return string * @throws RuntimeError */ - public function blockRender(NodesSources $nodeSource = null, string $themeName = "DefaultTheme", array $assignation = []): string + public function blockRender(NodesSources $nodeSource = null, string $themeName = "DefaultTheme", array $assignation = []) { if (null !== $nodeSource) { if (!empty($themeName)) { diff --git a/src/TwigExtension/CentralTruncateExtension.php b/src/TwigExtension/CentralTruncateExtension.php index 077e73e9..8c3c8851 100644 --- a/src/TwigExtension/CentralTruncateExtension.php +++ b/src/TwigExtension/CentralTruncateExtension.php @@ -9,7 +9,7 @@ use function Symfony\Component\String\u; -final class CentralTruncateExtension extends AbstractExtension +class CentralTruncateExtension extends AbstractExtension { public function getFilters(): array { diff --git a/src/TwigExtension/DocumentUrlExtension.php b/src/TwigExtension/DocumentUrlExtension.php index af68dd7d..945ffda6 100644 --- a/src/TwigExtension/DocumentUrlExtension.php +++ b/src/TwigExtension/DocumentUrlExtension.php @@ -15,12 +15,21 @@ /** * Extension that allow render documents Url */ -final class DocumentUrlExtension extends AbstractExtension +class DocumentUrlExtension extends AbstractExtension { + protected DocumentUrlGeneratorInterface $documentUrlGenerator; + protected bool $throwExceptions; + + /** + * @param DocumentUrlGeneratorInterface $documentUrlGenerator + * @param bool $throwExceptions Trigger exception if using filter on NULL values (default: false) + */ public function __construct( - private readonly DocumentUrlGeneratorInterface $documentUrlGenerator, - private readonly bool $throwExceptions = false + DocumentUrlGeneratorInterface $documentUrlGenerator, + bool $throwExceptions = false ) { + $this->throwExceptions = $throwExceptions; + $this->documentUrlGenerator = $documentUrlGenerator; } /** @@ -45,7 +54,7 @@ public function getFilters(): array * @return string * @throws RuntimeError */ - public function getUrl(PersistableInterface $mixed = null, array $criteria = []): string + public function getUrl(PersistableInterface $mixed = null, array $criteria = []) { if (null === $mixed) { if ($this->throwExceptions) { diff --git a/src/TwigExtension/HandlerExtension.php b/src/TwigExtension/HandlerExtension.php index 46bd6cd0..186355d6 100644 --- a/src/TwigExtension/HandlerExtension.php +++ b/src/TwigExtension/HandlerExtension.php @@ -4,8 +4,6 @@ namespace RZ\Roadiz\CoreBundle\TwigExtension; -use Psr\Container\ContainerExceptionInterface; -use Psr\Container\NotFoundExceptionInterface; use RZ\Roadiz\Core\AbstractEntities\AbstractEntity; use RZ\Roadiz\Core\Handlers\AbstractHandler; use RZ\Roadiz\CoreBundle\EntityHandler\HandlerFactory; @@ -15,8 +13,14 @@ final class HandlerExtension extends AbstractExtension { - public function __construct(private readonly HandlerFactory $handlerFactory) + private HandlerFactory $handlerFactory; + + /** + * @param HandlerFactory $handlerFactory + */ + public function __construct(HandlerFactory $handlerFactory) { + $this->handlerFactory = $handlerFactory; } public function getFilters(): array @@ -30,10 +34,8 @@ public function getFilters(): array * @param mixed $mixed * @return AbstractHandler|null * @throws RuntimeError - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface */ - public function getHandler(mixed $mixed): ?AbstractHandler + public function getHandler($mixed) { if (null === $mixed) { return null; diff --git a/src/TwigExtension/JwtExtension.php b/src/TwigExtension/JwtExtension.php index 246e20d8..c8d2452b 100644 --- a/src/TwigExtension/JwtExtension.php +++ b/src/TwigExtension/JwtExtension.php @@ -9,16 +9,24 @@ use Psr\Log\LoggerInterface; use RZ\Roadiz\CoreBundle\Preview\User\PreviewUserProviderInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\User\UserInterface; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; final class JwtExtension extends AbstractExtension { + private PreviewUserProviderInterface $previewUserProvider; + private JWTTokenManagerInterface $tokenManager; + private LoggerInterface $logger; + public function __construct( - private readonly JWTTokenManagerInterface $tokenManager, - private readonly LoggerInterface $logger, - private readonly PreviewUserProviderInterface $previewUserProvider + JWTTokenManagerInterface $tokenManager, + LoggerInterface $logger, + PreviewUserProviderInterface $previewUserProvider ) { + $this->tokenManager = $tokenManager; + $this->logger = $logger; + $this->previewUserProvider = $previewUserProvider; } public function getFunctions(): array diff --git a/src/TwigExtension/LogExtension.php b/src/TwigExtension/LogExtension.php index cfaa612e..8d39a699 100644 --- a/src/TwigExtension/LogExtension.php +++ b/src/TwigExtension/LogExtension.php @@ -14,16 +14,19 @@ use RZ\Roadiz\CoreBundle\Entity\User; use RZ\Roadiz\CoreBundle\Logger\Entity\Log; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; final class LogExtension extends AbstractExtension { - public function __construct( - private readonly Security $security, - private readonly UrlGeneratorInterface $urlGenerator - ) { + private Security $security; + private UrlGeneratorInterface $urlGenerator; + + public function __construct(Security $security, UrlGeneratorInterface $urlGenerator) + { + $this->security = $security; + $this->urlGenerator = $urlGenerator; } public function getFunctions(): array diff --git a/src/TwigExtension/NodesSourcesExtension.php b/src/TwigExtension/NodesSourcesExtension.php index 55d8fcc8..61726ab0 100644 --- a/src/TwigExtension/NodesSourcesExtension.php +++ b/src/TwigExtension/NodesSourcesExtension.php @@ -4,13 +4,11 @@ namespace RZ\Roadiz\CoreBundle\TwigExtension; -use Doctrine\ORM\NonUniqueResultException; -use Doctrine\Persistence\ManagerRegistry; -use Psr\Container\ContainerExceptionInterface; -use Psr\Container\NotFoundExceptionInterface; use RZ\Roadiz\CoreBundle\Bag\NodeTypes; use RZ\Roadiz\CoreBundle\Entity\NodesSources; -use RZ\Roadiz\CoreBundle\Entity\Tag; +use RZ\Roadiz\CoreBundle\EntityApi\NodeSourceApi; +use RZ\Roadiz\CoreBundle\EntityHandler\HandlerFactory; +use RZ\Roadiz\CoreBundle\EntityHandler\NodesSourcesHandler; use Twig\Error\RuntimeError; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -21,11 +19,27 @@ */ final class NodesSourcesExtension extends AbstractExtension { + protected NodeSourceApi $nodeSourceApi; + protected HandlerFactory $handlerFactory; + private bool $throwExceptions; + private NodeTypes $nodeTypesBag; + + /** + * @param NodeSourceApi $nodeSourceApi + * @param HandlerFactory $handlerFactory + * @param NodeTypes $nodeTypesBag + * @param bool $throwExceptions + */ public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly NodeTypes $nodeTypesBag, - private readonly bool $throwExceptions = false + NodeSourceApi $nodeSourceApi, + HandlerFactory $handlerFactory, + NodeTypes $nodeTypesBag, + bool $throwExceptions = false ) { + $this->throwExceptions = $throwExceptions; + $this->handlerFactory = $handlerFactory; + $this->nodeTypesBag = $nodeTypesBag; + $this->nodeSourceApi = $nodeSourceApi; } public function getFilters(): array @@ -74,10 +88,24 @@ public function getChildren(NodesSources $ns = null, array $criteria = null, arr return []; } } + $defaultCrit = [ + 'node.parent' => $ns->getNode(), + 'translation' => $ns->getTranslation(), + ]; + + if (null !== $order) { + $defaultOrder = $order; + } else { + $defaultOrder = [ + 'node.position' => 'ASC', + ]; + } + + if (null !== $criteria) { + $defaultCrit = array_merge($defaultCrit, $criteria); + } - return $this->managerRegistry - ->getRepository(NodesSources::class) - ->findChildren($ns, $criteria, $order); + return $this->nodeSourceApi->getBy($defaultCrit, $defaultOrder); } /** @@ -87,7 +115,7 @@ public function getChildren(NodesSources $ns = null, array $criteria = null, arr * @return NodesSources|null * @throws RuntimeError */ - public function getNext(NodesSources $ns = null, array $criteria = null, array $order = null): ?NodesSources + public function getNext(NodesSources $ns = null, array $criteria = null, array $order = null) { if (null === $ns) { if ($this->throwExceptions) { @@ -96,10 +124,9 @@ public function getNext(NodesSources $ns = null, array $criteria = null, array $ return null; } } - - return $this->managerRegistry - ->getRepository(NodesSources::class) - ->findNext($ns, $criteria, $order); + /** @var NodesSourcesHandler $nodeSourceHandler */ + $nodeSourceHandler = $this->handlerFactory->getHandler($ns); + return $nodeSourceHandler->getNext($criteria, $order); } /** @@ -109,7 +136,7 @@ public function getNext(NodesSources $ns = null, array $criteria = null, array $ * @return NodesSources|null * @throws RuntimeError */ - public function getPrevious(NodesSources $ns = null, array $criteria = null, array $order = null): ?NodesSources + public function getPrevious(NodesSources $ns = null, array $criteria = null, array $order = null) { if (null === $ns) { if ($this->throwExceptions) { @@ -118,10 +145,9 @@ public function getPrevious(NodesSources $ns = null, array $criteria = null, arr return null; } } - - return $this->managerRegistry - ->getRepository(NodesSources::class) - ->findPrevious($ns, $criteria, $order); + /** @var NodesSourcesHandler $nodeSourceHandler */ + $nodeSourceHandler = $this->handlerFactory->getHandler($ns); + return $nodeSourceHandler->getPrevious($criteria, $order); } /** @@ -131,7 +157,7 @@ public function getPrevious(NodesSources $ns = null, array $criteria = null, arr * @return NodesSources|null * @throws RuntimeError */ - public function getLastSibling(NodesSources $ns = null, array $criteria = null, array $order = null): ?NodesSources + public function getLastSibling(NodesSources $ns = null, array $criteria = null, array $order = null) { if (null === $ns) { if ($this->throwExceptions) { @@ -140,10 +166,9 @@ public function getLastSibling(NodesSources $ns = null, array $criteria = null, return null; } } - - return $this->managerRegistry - ->getRepository(NodesSources::class) - ->findLastSibling($ns, $criteria, $order); + /** @var NodesSourcesHandler $nodeSourceHandler */ + $nodeSourceHandler = $this->handlerFactory->getHandler($ns); + return $nodeSourceHandler->getLastSibling($criteria, $order); } /** @@ -153,7 +178,7 @@ public function getLastSibling(NodesSources $ns = null, array $criteria = null, * @return NodesSources|null * @throws RuntimeError */ - public function getFirstSibling(NodesSources $ns = null, array $criteria = null, array $order = null): ?NodesSources + public function getFirstSibling(NodesSources $ns = null, array $criteria = null, array $order = null) { if (null === $ns) { if ($this->throwExceptions) { @@ -162,10 +187,9 @@ public function getFirstSibling(NodesSources $ns = null, array $criteria = null, return null; } } - - return $this->managerRegistry - ->getRepository(NodesSources::class) - ->findFirstSibling($ns, $criteria, $order); + /** @var NodesSourcesHandler $nodeSourceHandler */ + $nodeSourceHandler = $this->handlerFactory->getHandler($ns); + return $nodeSourceHandler->getFirstSibling($criteria, $order); } /** @@ -173,7 +197,7 @@ public function getFirstSibling(NodesSources $ns = null, array $criteria = null, * @return NodesSources|null * @throws RuntimeError */ - public function getParent(NodesSources $ns = null): ?NodesSources + public function getParent(NodesSources $ns = null) { if (null === $ns) { if ($this->throwExceptions) { @@ -190,12 +214,9 @@ public function getParent(NodesSources $ns = null): ?NodesSources * @param NodesSources|null $ns * @param array|null $criteria * @return array - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface * @throws RuntimeError - * @throws NonUniqueResultException */ - public function getParents(NodesSources $ns = null, array $criteria = null): array + public function getParents(NodesSources $ns = null, array $criteria = null) { if (null === $ns) { if ($this->throwExceptions) { @@ -204,20 +225,17 @@ public function getParents(NodesSources $ns = null, array $criteria = null): arr return []; } } - - return $this->managerRegistry - ->getRepository(NodesSources::class) - ->findParents($ns, $criteria); + /** @var NodesSourcesHandler $nodeSourceHandler */ + $nodeSourceHandler = $this->handlerFactory->getHandler($ns); + return $nodeSourceHandler->getParents($criteria); } /** * @param NodesSources|null $ns - * @return iterable + * @return array * @throws RuntimeError - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface */ - public function getTags(NodesSources $ns = null): iterable + public function getTags(NodesSources $ns = null) { if (null === $ns) { if ($this->throwExceptions) { @@ -226,9 +244,8 @@ public function getTags(NodesSources $ns = null): iterable return []; } } - - return $this->managerRegistry - ->getRepository(Tag::class) - ->findByNodesSources($ns); + /** @var NodesSourcesHandler $nodeSourceHandler */ + $nodeSourceHandler = $this->handlerFactory->getHandler($ns); + return $nodeSourceHandler->getTags(); } } diff --git a/src/TwigExtension/RoadizExtension.php b/src/TwigExtension/RoadizExtension.php index c5d4d333..62b7fd10 100644 --- a/src/TwigExtension/RoadizExtension.php +++ b/src/TwigExtension/RoadizExtension.php @@ -11,18 +11,35 @@ use Twig\Extension\AbstractExtension; use Twig\Extension\GlobalsInterface; -final class RoadizExtension extends AbstractExtension implements GlobalsInterface +class RoadizExtension extends AbstractExtension implements GlobalsInterface { + protected Settings $settingsBag; + protected NodeTypes $nodeTypesBag; + protected PreviewResolverInterface $previewResolver; + protected NodeChrootResolver $chrootResolver; + protected string $cmsVersion; + protected string $cmsVersionPrefix; + protected bool $hideRoadizVersion; + protected int $maxVersionsShowed; + public function __construct( - private readonly Settings $settingsBag, - private readonly NodeTypes $nodeTypesBag, - private readonly PreviewResolverInterface $previewResolver, - private readonly NodeChrootResolver $chrootResolver, - private readonly string $cmsVersion, - private readonly string $cmsVersionPrefix, - private readonly bool $hideRoadizVersion, - private readonly int $maxVersionsShowed + Settings $settingsBag, + NodeTypes $nodeTypesBag, + PreviewResolverInterface $previewResolver, + NodeChrootResolver $chrootResolver, + string $cmsVersion, + string $cmsVersionPrefix, + bool $hideRoadizVersion, + int $maxVersionsShowed ) { + $this->settingsBag = $settingsBag; + $this->nodeTypesBag = $nodeTypesBag; + $this->previewResolver = $previewResolver; + $this->chrootResolver = $chrootResolver; + $this->cmsVersion = $cmsVersion; + $this->cmsVersionPrefix = $cmsVersionPrefix; + $this->hideRoadizVersion = $hideRoadizVersion; + $this->maxVersionsShowed = $maxVersionsShowed; } /** diff --git a/src/TwigExtension/RoutingExtension.php b/src/TwigExtension/RoutingExtension.php index f78d3dd3..bfdd578d 100644 --- a/src/TwigExtension/RoutingExtension.php +++ b/src/TwigExtension/RoutingExtension.php @@ -14,12 +14,21 @@ /** * Override Symfony RoutingExtension to support object url generation. */ -final class RoutingExtension extends AbstractExtension +class RoutingExtension extends AbstractExtension { + private UrlGeneratorInterface $generator; + private \Symfony\Bridge\Twig\Extension\RoutingExtension $decorated; + + /** + * @param UrlGeneratorInterface $generator + * @param \Symfony\Bridge\Twig\Extension\RoutingExtension $decorated + */ public function __construct( - private readonly \Symfony\Bridge\Twig\Extension\RoutingExtension $decorated, - private readonly UrlGeneratorInterface $generator + \Symfony\Bridge\Twig\Extension\RoutingExtension $decorated, + UrlGeneratorInterface $generator ) { + $this->generator = $generator; + $this->decorated = $decorated; } /** @@ -40,7 +49,7 @@ public function getFunctions(): array * @return string * @throws RuntimeError */ - public function getPath(string|object|null $name, array $parameters = [], bool $relative = false): string + public function getPath($name, array $parameters = [], bool $relative = false): string { if (is_string($name)) { return $this->decorated->getPath( @@ -66,7 +75,7 @@ public function getPath(string|object|null $name, array $parameters = [], bool $ * @return string * @throws RuntimeError */ - public function getUrl(string|object|null $name, array $parameters = [], bool $schemeRelative = false): string + public function getUrl($name, array $parameters = [], bool $schemeRelative = false): string { if (is_string($name)) { return $this->decorated->getUrl( diff --git a/src/TwigExtension/TransChoiceExtension.php b/src/TwigExtension/TransChoiceExtension.php index 96c19b15..949b0243 100644 --- a/src/TwigExtension/TransChoiceExtension.php +++ b/src/TwigExtension/TransChoiceExtension.php @@ -15,8 +15,14 @@ */ final class TransChoiceExtension extends AbstractExtension { - public function __construct(private readonly TranslatorInterface $translator) + private TranslatorInterface $translator; + + /** + * @param TranslatorInterface $translator + */ + public function __construct(TranslatorInterface $translator) { + $this->translator = $translator; } public function getFilters(): array diff --git a/src/TwigExtension/TranslationExtension.php b/src/TwigExtension/TranslationExtension.php index cd248352..807a2fc7 100644 --- a/src/TwigExtension/TranslationExtension.php +++ b/src/TwigExtension/TranslationExtension.php @@ -34,7 +34,7 @@ public function getTests(): array * * @return bool */ - public function isLocaleRtl(mixed $mixed): bool + public function isLocaleRtl($mixed) { if ($mixed instanceof TranslationInterface) { return $mixed->isRtl(); diff --git a/src/TwigExtension/TranslationMenuExtension.php b/src/TwigExtension/TranslationMenuExtension.php index 2c0bccd8..b1fdb2fd 100644 --- a/src/TwigExtension/TranslationMenuExtension.php +++ b/src/TwigExtension/TranslationMenuExtension.php @@ -13,10 +13,17 @@ final class TranslationMenuExtension extends AbstractExtension { - public function __construct( - private readonly RequestStack $requestStack, - private readonly TranslationViewer $translationViewer - ) { + private RequestStack $requestStack; + private TranslationViewer $translationViewer; + + /** + * @param RequestStack $requestStack + * @param TranslationViewer $translationViewer + */ + public function __construct(RequestStack $requestStack, TranslationViewer $translationViewer) + { + $this->requestStack = $requestStack; + $this->translationViewer = $translationViewer; } public function getFilters(): array @@ -33,7 +40,7 @@ public function getFilters(): array * @return array * @throws ORMException */ - public function getMenuAssignation(TranslationInterface $translation = null, bool $absolute = false): array + public function getMenuAssignation(TranslationInterface $translation = null, bool $absolute = false) { if (null !== $translation) { $this->translationViewer->setTranslation($translation); diff --git a/src/Workflow/Event/NodeStatusGuardListener.php b/src/Workflow/Event/NodeStatusGuardListener.php index edc6b66e..92de5153 100644 --- a/src/Workflow/Event/NodeStatusGuardListener.php +++ b/src/Workflow/Event/NodeStatusGuardListener.php @@ -7,7 +7,7 @@ use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Workflow\Event\GuardEvent; use Symfony\Component\Workflow\TransitionBlocker; diff --git a/src/Xlsx/NodeSourceXlsxSerializer.php b/src/Xlsx/NodeSourceXlsxSerializer.php index 56e19597..b2579415 100644 --- a/src/Xlsx/NodeSourceXlsxSerializer.php +++ b/src/Xlsx/NodeSourceXlsxSerializer.php @@ -68,6 +68,7 @@ public function toArray($nodeSource): array $data['title'] = $nodeSource->getTitle(); $data['published_at'] = $nodeSource->getPublishedAt(); $data['meta_title'] = $nodeSource->getMetaTitle(); + $data['meta_keywords'] = $nodeSource->getMetaKeywords(); $data['meta_description'] = $nodeSource->getMetaDescription(); $data = array_merge($data, $this->getSourceFields($nodeSource)); diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 469dccee..00000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,11 +0,0 @@ -bootEnv(dirname(__DIR__).'/.env'); -} diff --git a/tests/expected_api_resources/nssecondtest.yml b/tests/expected_api_resources/nssecondtest.yml deleted file mode 100644 index 50af807a..00000000 --- a/tests/expected_api_resources/nssecondtest.yml +++ /dev/null @@ -1,35 +0,0 @@ -resources: - App\GeneratedEntity\NSSecondTest: - shortName: SecondTest - types: - - SecondTest - operations: - secondtest_get_collection: - method: GET - class: ApiPlatform\Metadata\GetCollection - shortName: SecondTest - normalizationContext: - enable_max_depth: true - groups: - - nodes_sources_base - - nodes_sources_default - - urls - - tag_base - - translation_base - - document_display - - document_thumbnails - - document_display_sources - secondtest_get: - method: GET - class: ApiPlatform\Metadata\Get - shortName: SecondTest - normalizationContext: - groups: - - nodes_sources - - node_listing - - urls - - tag_base - - translation_base - - document_display - - document_thumbnails - - document_display_sources diff --git a/tests/expected_api_resources/nstest.yml b/tests/expected_api_resources/nstest.yml deleted file mode 100644 index cc8c952f..00000000 --- a/tests/expected_api_resources/nstest.yml +++ /dev/null @@ -1,35 +0,0 @@ -resources: - App\GeneratedEntity\NSTest: - shortName: Test - types: - - Test - operations: - test_get_collection: - method: GET - class: ApiPlatform\Metadata\GetCollection - shortName: Test - normalizationContext: - enable_max_depth: true - groups: - - nodes_sources_base - - nodes_sources_default - - urls - - tag_base - - translation_base - - document_display - - document_thumbnails - - document_display_sources - test_get: - method: GET - class: ApiPlatform\Metadata\Get - shortName: Test - normalizationContext: - groups: - - nodes_sources - - node_listing - - urls - - tag_base - - translation_base - - document_display - - document_thumbnails - - document_display_sources diff --git a/tests/expected_api_resources/web_response.yml b/tests/expected_api_resources/web_response.yml deleted file mode 100644 index 176c0a5d..00000000 --- a/tests/expected_api_resources/web_response.yml +++ /dev/null @@ -1,32 +0,0 @@ -resources: - RZ\Roadiz\CoreBundle\Api\Model\WebResponse: - operations: - test_get_by_path: - method: GET - class: ApiPlatform\Metadata\Get - uriTemplate: /web_response_by_path - read: false - controller: RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController - normalizationContext: - pagination_enabled: false - enable_max_depth: true - groups: - - test_get_by_path - - nodes_sources - - node_listing - - urls - - tag_base - - translation_base - - document_display - - document_thumbnails - - document_display_sources - - web_response - - walker - - children - openapiContext: - tags: - - WebResponse - summary: 'Get a Test by its path wrapped in a WebResponse object' - description: 'Get a Test by its path wrapped in a WebResponse' - parameters: - - { type: string, name: path, in: query, required: true, description: 'Resource path, or `/` for home page', schema: { type: string } } diff --git a/tests/expected_api_resources/web_response_multiple.yml b/tests/expected_api_resources/web_response_multiple.yml deleted file mode 100644 index eff72beb..00000000 --- a/tests/expected_api_resources/web_response_multiple.yml +++ /dev/null @@ -1,61 +0,0 @@ -resources: - RZ\Roadiz\CoreBundle\Api\Model\WebResponse: - operations: - test_get_by_path: - method: GET - class: ApiPlatform\Metadata\Get - uriTemplate: /web_response_by_path - read: false - controller: RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController - normalizationContext: - pagination_enabled: false - enable_max_depth: true - groups: - - test_get_by_path - - nodes_sources - - node_listing - - urls - - tag_base - - translation_base - - document_display - - document_thumbnails - - document_display_sources - - web_response - - walker - - children - openapiContext: - tags: - - WebResponse - summary: 'Get a Test by its path wrapped in a WebResponse object' - description: 'Get a Test by its path wrapped in a WebResponse' - parameters: - - { type: string, name: path, in: query, required: true, description: 'Resource path, or `/` for home page', schema: { type: string } } - secondtest_get_by_path: - method: GET - class: ApiPlatform\Metadata\Get - uriTemplate: /web_response_by_path - read: false - controller: RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController - normalizationContext: - pagination_enabled: false - enable_max_depth: true - groups: - - secondtest_get_by_path - - nodes_sources - - node_listing - - urls - - tag_base - - translation_base - - document_display - - document_thumbnails - - document_display_sources - - web_response - - walker - - children - openapiContext: - tags: - - WebResponse - summary: 'Get a SecondTest by its path wrapped in a WebResponse object' - description: 'Get a SecondTest by its path wrapped in a WebResponse' - parameters: - - { type: string, name: path, in: query, required: true, description: 'Resource path, or `/` for home page', schema: { type: string } } diff --git a/tests/object-manager.php b/tests/object-manager.php deleted file mode 100644 index c15dbe95..00000000 --- a/tests/object-manager.php +++ /dev/null @@ -1,11 +0,0 @@ -bootEnv(__DIR__ . '/../.env'); - -$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); -$kernel->boot(); -return $kernel->getContainer()->get('doctrine')->getManager(); diff --git a/translations/validators.ar.xlf b/translations/validators.ar.xlf index f4fb0597..7fcf2644 100644 --- a/translations/validators.ar.xlf +++ b/translations/validators.ar.xlf @@ -4,7 +4,6 @@ - diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index 1abb71e8..c10b9005 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -10,10 +10,12 @@ tagName.%name%.alreadyExists Tag %name% already exists. - - field_with_same_name_already_exists_but_with_different_doctrine_type - This field already exists with the same name in another node type, but with a different Doctrine data type. - + + + + field_with_same_name_already_exists_but_with_different_doctrine_type + There already is a "%name%" field inside "%nodeTypeName%" but with a different data-type: %type%. + diff --git a/translations/validators.es.xlf b/translations/validators.es.xlf index bf1185dd..b98ab19c 100644 --- a/translations/validators.es.xlf +++ b/translations/validators.es.xlf @@ -4,7 +4,6 @@ - diff --git a/translations/validators.fr.xlf b/translations/validators.fr.xlf index 6de39b55..9e538c0e 100644 --- a/translations/validators.fr.xlf +++ b/translations/validators.fr.xlf @@ -10,10 +10,11 @@ tagName.%name%.alreadyExists L'étiquette «%name%» existe déjà. - - field_with_same_name_already_exists_but_with_different_doctrine_type - Ce champ existe déjà avec le même nom dans un autre type de nœud, mais avec un type de donnée Doctrine différent. - + + + field_with_same_name_already_exists_but_with_different_doctrine_type + Un champ « %name% » existe déjà pour « %nodeTypeName% » mais avec un type de données différent : %type%. + diff --git a/translations/validators.id.xlf b/translations/validators.id.xlf index 707d35fe..8d22e146 100644 --- a/translations/validators.id.xlf +++ b/translations/validators.id.xlf @@ -4,7 +4,6 @@ - diff --git a/translations/validators.it.xlf b/translations/validators.it.xlf index 0e106d9c..aa25ca67 100644 --- a/translations/validators.it.xlf +++ b/translations/validators.it.xlf @@ -4,7 +4,6 @@ - diff --git a/translations/validators.ru.xlf b/translations/validators.ru.xlf index 12dceea9..2871bce1 100644 --- a/translations/validators.ru.xlf +++ b/translations/validators.ru.xlf @@ -4,7 +4,6 @@ - diff --git a/translations/validators.sr.xlf b/translations/validators.sr.xlf index 11661084..d39ab964 100644 --- a/translations/validators.sr.xlf +++ b/translations/validators.sr.xlf @@ -4,7 +4,6 @@ - diff --git a/translations/validators.tr.xlf b/translations/validators.tr.xlf index 1241fabb..7d3ddf7b 100644 --- a/translations/validators.tr.xlf +++ b/translations/validators.tr.xlf @@ -4,7 +4,6 @@ -