From e8cf74d719ebf3f70c7bcc2e4f26b6ea57dc6de8 Mon Sep 17 00:00:00 2001 From: roadiz-ci Date: Fri, 11 Oct 2024 08:40:35 +0000 Subject: [PATCH] chore: Bumped --- .env | 1 - .env.test | 6 - .github/workflows/run-test.yml | 2 - LICENSE.md | 2 +- Makefile | 3 + composer.json | 100 +++-- 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 | 36 +- 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 | 49 --- migrations/Version20240702205419.php | 31 -- phpstan.neon | 11 +- phpunit.xml.dist | 69 ---- src/Api/Breadcrumbs/Breadcrumbs.php | 17 +- .../NodesSourcesBreadcrumbsFactory.php | 6 +- .../GetWebResponseByPathController.php | 114 +++--- .../TranslationAwareControllerTrait.php | 5 - .../WebResponseDataTransformerInterface.php | 18 +- .../WebResponseOutputDataTransformer.php | 90 +++-- src/Api/Extension/ArchiveExtension.php | 9 +- .../AttributeValueQueryExtension.php | 5 +- .../AttributeValueRealmExtension.php | 6 +- src/Api/Extension/NodeQueryExtension.php | 5 +- .../Extension/NodesSourcesQueryExtension.php | 9 +- src/Api/Extension/NodesTagsQueryExtension.php | 5 +- src/Api/Filter/LocaleFilter.php | 5 +- src/Api/ListManager/SolrPaginator.php | 10 +- src/Api/ListManager/SolrSearchListManager.php | 20 +- .../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/JwtDecorator.php | 5 +- src/Api/OpenApi/PreviewDecorator.php | 5 +- src/Api/OpenApi/WebResponseDecorator.php | 9 +- .../DefinitionFactoryConfiguration.php | 17 +- .../MultiTypeChildrenDefinition.php | 54 +-- .../NonReachableNodeSourceBlockDefinition.php | 59 ++- .../ReachableNodeSourceDefinition.php | 59 ++- .../TreeWalker/NodeSourceWalkerContext.php | 35 +- .../NodeSourceWalkerContextFactory.php | 33 +- src/Api/TreeWalker/TreeWalkerGenerator.php | 17 +- 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 | 129 +++++-- src/Controller/HealthCheckController.php | 17 +- src/Controller/RedirectionController.php | 10 +- src/Crypto/UniqueKeyEncoderFactory.php | 55 +++ src/CustomForm/CustomFormAnswerSerializer.php | 25 +- src/CustomForm/CustomFormHelper.php | 167 +++++---- src/CustomForm/CustomFormHelperFactory.php | 28 +- .../Message/CustomFormAnswerNotifyMessage.php | 23 +- .../CustomFormAnswerNotifyMessageHandler.php | 60 ++- src/DataCollector/RequestDataCollector.php | 28 +- src/DependencyInjection/Configuration.php | 20 +- .../RoadizCoreExtension.php | 36 +- src/Doctrine/Event/QueryNodesSourcesEvent.php | 8 +- .../CustomFormFieldLifeCycleSubscriber.php | 8 +- .../NodesSourcesInheritanceSubscriber.php | 27 +- .../SettingLifeCycleSubscriber.php | 125 +++++++ .../EventSubscriber/TablePrefixSubscriber.php | 61 ++++ .../UserLifeCycleSubscriber.php | 32 +- src/Doctrine/ORM/Filter/ANodesFilter.php | 50 ++- src/Doctrine/ORM/Filter/BNodesFilter.php | 8 + .../Filter/NodesSourcesReachableFilter.php | 8 +- src/Doctrine/ORM/SimpleQueryBuilder.php | 16 +- src/Doctrine/SchemaUpdater.php | 17 +- src/Document/DocumentFactory.php | 5 +- src/Document/DocumentFinder.php | 8 +- .../DocumentMessageDispatchSubscriber.php | 8 +- .../AbstractDocumentMessageHandler.php | 18 +- .../DocumentAudioVideoMessageHandler.php | 13 +- .../DocumentAverageColorMessageHandler.php | 7 +- .../DocumentExifMessageHandler.php | 2 +- .../DocumentFilesizeMessageHandler.php | 2 +- .../DocumentPdfMessageHandler.php | 14 +- .../DocumentRawMessageHandler.php | 5 +- .../DocumentSizeMessageHandler.php | 7 +- .../DocumentSvgMessageHandler.php | 4 +- src/Entity/AbstractDateTimedPositioned.php | 42 --- src/Entity/AttributeDocuments.php | 44 ++- src/Entity/AttributeValue.php | 24 +- src/Entity/CustomForm.php | 13 +- src/Entity/CustomFormAnswer.php | 8 +- src/Entity/CustomFormField.php | 75 +--- src/Entity/CustomFormFieldAttribute.php | 36 +- 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/NodesTags.php | 3 +- 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 | 37 +- 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 | 139 ++++++- src/EntityHandler/TranslationHandler.php | 17 +- .../DocumentTranslationIndexingEvent.php | 33 +- .../DocumentTranslationUpdatedEvent.php | 9 +- .../NodesSourcesIndexingEvent.php | 41 ++- .../NodesSourcesPathGeneratingEvent.php | 56 ++- src/Event/User/UserJoinedGroupEvent.php | 5 +- src/Event/User/UserLeavedGroupEvent.php | 5 +- .../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 | 21 +- .../NodeRedirectionSubscriber.php | 22 +- .../NodeSourcePathSubscriber.php | 17 +- .../NodesSourcesAddHeadersSubscriber.php | 86 ----- .../NodesSourcesLinkHeaderEventSubscriber.php | 67 ++-- .../NodesSourcesUniversalSubscriber.php | 1 + .../NodesSourcesUrlsCacheEventSubscriber.php | 19 +- .../OPCacheEventSubscriber.php | 1 + .../RealmNodeInheritanceSubscriber.php | 12 +- .../ReverseProxyCacheEventSubscriber.php | 20 +- src/EventSubscriber/RoleSubscriber.php | 3 + src/EventSubscriber/SignatureSubscriber.php | 18 +- .../TagTimestampSubscriber.php | 5 +- src/EventSubscriber/TranslationSubscriber.php | 10 +- src/EventSubscriber/UserLocaleSubscriber.php | 21 +- src/Filesystem/RoadizFileDirectories.php | 8 +- src/Form/AttributeChoiceType.php | 2 +- src/Form/CustomFormsType.php | 12 +- src/Form/DataListTextType.php | 43 --- .../AttributeDocumentsTransformer.php | 11 +- src/Form/Error/FormErrorSerializer.php | 8 +- src/Form/RealmChoiceType.php | 5 +- src/Form/RoleEntityType.php | 13 +- src/Form/SettingType.php | 4 + src/Form/UserCollectionType.php | 5 +- src/Importer/AttributeImporter.php | 8 +- src/ListManager/SessionListFilters.php | 53 --- src/Logger/DoctrineHandler.php | 21 +- src/Mailer/ContactFormManager.php | 55 +-- src/Mailer/ContactFormManagerFactory.php | 4 +- src/Mailer/EmailManager.php | 66 +++- .../ApplyRealmNodeInheritanceMessage.php | 11 +- .../CleanRealmNodeInheritanceMessage.php | 11 +- src/Message/DeleteNodeTypeMessage.php | 8 +- src/Message/GuzzleRequestMessage.php | 8 +- ...pplyRealmNodeInheritanceMessageHandler.php | 4 +- .../Handler/DeleteNodeTypeMessageHandler.php | 14 +- .../Handler/HttpRequestMessageHandler.php | 18 +- .../PurgeReverseProxyCacheMessageHandler.php | 23 +- ...archRealmNodeInheritanceMessageHandler.php | 21 +- .../UpdateDoctrineSchemaMessageHandler.php | 5 +- .../UpdateNodeTypeSchemaMessageHandler.php | 14 +- src/Message/PurgeReverseProxyCacheMessage.php | 8 +- .../SearchRealmNodeInheritanceMessage.php | 5 +- src/Message/UpdateNodeTypeSchemaMessage.php | 8 +- 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 | 35 +- src/Node/NodeFactory.php | 27 +- src/Node/NodeNamePolicyFactory.php | 15 +- src/Node/NodeTranslator.php | 15 +- src/Node/NodeTranstyper.php | 10 +- src/Node/UniqueNodeGenerator.php | 5 +- src/Node/UniversalDataDuplicator.php | 27 +- src/NodeType/ApiResourceGenerator.php | 262 ++++--------- .../ApiResourceOperationNameGenerator.php | 28 -- src/NodeType/DefaultValuesResolver.php | 9 +- src/NodeType/NodeTypeResolver.php | 5 +- .../EventSubscriber/PreviewBarSubscriber.php | 10 +- .../EventSubscriber/PreviewModeSubscriber.php | 37 +- .../Exception/PreviewNotAllowedException.php | 2 +- src/Preview/RequestPreviewRevolver.php | 11 +- src/Preview/User/PreviewUserProvider.php | 17 +- src/Realm/RealmResolver.php | 34 +- src/Realm/RealmResolverInterface.php | 13 - src/Repository/AttributeGroupRepository.php | 6 +- src/Repository/AttributeRepository.php | 6 +- .../AttributeTranslationRepository.php | 6 +- .../AttributeValueTranslationRepository.php | 6 +- src/Repository/CustomFormAnswerRepository.php | 6 +- .../CustomFormFieldAttributeRepository.php | 6 +- src/Repository/CustomFormFieldRepository.php | 18 +- src/Repository/CustomFormRepository.php | 28 +- src/Repository/DocumentRepository.php | 76 +--- .../FolderTranslationRepository.php | 6 +- src/Repository/GroupRepository.php | 6 +- src/Repository/NodeRepository.php | 88 +---- src/Repository/NodesCustomFormsRepository.php | 36 +- .../NodesSourcesDocumentsRepository.php | 28 +- src/Repository/NodesSourcesRepository.php | 343 +----------------- src/Repository/NodesTagsRepository.php | 22 -- src/Repository/NodesToNodesRepository.php | 31 +- src/Repository/RealmRepository.php | 21 -- src/Repository/RedirectionRepository.php | 6 +- src/Repository/StatusAwareRepository.php | 2 +- src/Repository/TagRepository.php | 22 +- src/Repository/TagTranslationRepository.php | 6 +- src/Repository/WebhookRepository.php | 6 +- src/Routing/DeferredRouteCollection.php | 29 ++ src/Routing/DocumentUrlGenerator.php | 1 + src/Routing/DynamicUrlMatcher.php | 20 +- src/Routing/NodeRouteHelper.php | 27 +- src/Routing/NodeRouter.php | 79 +++- src/Routing/NodeUrlMatcher.php | 46 ++- src/Routing/NodesSourcesPathResolver.php | 24 +- src/Routing/NodesSourcesUrlGenerator.php | 28 +- 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/ClientRegistry.php | 8 +- src/SearchEngine/DocumentSearchHandler.php | 4 +- .../GlobalNodeSourceSearchHandler.php | 69 ++-- src/SearchEngine/Indexer/AbstractIndexer.php | 14 +- src/SearchEngine/Indexer/DocumentIndexer.php | 13 +- .../Indexer/NodesSourcesIndexer.php | 13 +- .../Handler/SolrDeleteMessageHandler.php | 13 +- .../Handler/SolrReindexMessageHandler.php | 13 +- src/SearchEngine/NodeSourceSearchHandler.php | 4 +- src/SearchEngine/SearchHandlerInterface.php | 4 + src/SearchEngine/SearchResultsInterface.php | 6 - src/SearchEngine/SolariumLogger.php | 13 +- src/SearchEngine/SolrSearchResultItem.php | 48 --- src/SearchEngine/SolrSearchResults.php | 83 +++-- .../DefaultNodesSourcesIndexingSubscriber.php | 5 +- .../Subscriber/SolariumSubscriber.php | 5 +- .../TreeWalkerIndexingEventSubscriber.php | 17 +- .../JwtAuthenticationSuccessHandler.php | 9 +- .../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 | 41 +-- .../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 ++-- src/Tag/TagFactory.php | 8 +- src/Translation/TranslationViewer.php | 22 +- 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 | 20 +- src/TwigExtension/TransChoiceExtension.php | 8 +- src/TwigExtension/TranslationExtension.php | 2 +- .../TranslationMenuExtension.php | 17 +- .../TooManyWebhookTriggeredException.php | 5 +- .../Message/GenericJsonPostMessage.php | 17 +- .../Message/GitlabPipelineTriggerMessage.php | 25 +- .../Message/NetlifyBuildHookMessage.php | 17 +- src/Webhook/ThrottledWebhookDispatcher.php | 13 +- .../Event/NodeStatusGuardListener.php | 10 +- src/Xlsx/AbstractXlsxSerializer.php | 9 +- src/Xlsx/NodeSourceXlsxSerializer.php | 32 +- src/Xlsx/SerializerInterface.php | 1 - src/Xlsx/XlsxExporter.php | 11 +- .../customForm/base_custom_form.html.twig | 56 ++- templates/customForm/customForm.html.twig | 2 +- templates/customForm/customFormSent.html.twig | 2 +- templates/email/forms/answerForm.html.twig | 70 ++-- tests/NodeType/ApiResourceGeneratorTest.php | 152 -------- tests/bootstrap.php | 16 - 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 - 414 files changed, 5993 insertions(+), 5440 deletions(-) delete mode 100644 .env delete mode 100644 .env.test create mode 100644 Makefile 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 migrations/Version20240702205419.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 create mode 100644 src/Doctrine/EventSubscriber/TablePrefixSubscriber.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 delete mode 100644 src/Repository/NodesTagsRepository.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 tests/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/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 2fe1003b..189e3a55 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -39,5 +39,3 @@ jobs: run: vendor/bin/phpcs -p ./src - name: Run PHPStan run: vendor/bin/phpstan analyse --no-progress -c phpstan.neon - - name: Run PHP Unit - run: vendor/bin/phpunit -v 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/Makefile b/Makefile new file mode 100644 index 00000000..16ef8148 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +test: + php -d "memory_limit=-1" vendor/bin/phpcbf --report=full --report-file=./report.txt -p ./src + php -d "memory_limit=-1" vendor/bin/phpstan analyse -c phpstan.neon diff --git a/composer.json b/composer.json index d4ca8d28..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.5.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.*", + "rezozero/tree-walker": "^1.3.0", + "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, @@ -123,11 +124,6 @@ "RZ\\Roadiz\\CoreBundle\\": "src/" } }, - "autoload-dev": { - "psr-4": { - "RZ\\Roadiz\\CoreBundle\\Tests\\": "tests/" - } - }, "scripts": { "auto-scripts": { "cache:clear": "symfony-cmd", @@ -136,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 ee7c4147..06f2a1e3 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,6 +1,6 @@ --- parameters: - roadiz_core.cms_version: '2.3.29' + roadiz_core.cms_version: '2.2.30' 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/' @@ -61,7 +60,6 @@ services: - '../src/Traits/' - '../src/Kernel.php' - '../src/Tests/' - - '../src/DataCollector/' - '../src/Event/' RZ\Roadiz\CoreBundle\EntityHandler\: @@ -70,13 +68,6 @@ services: shared: false public: true - RZ\Roadiz\CoreBundle\Xlsx\: - resource: '../src/Xlsx/' - deprecated: - message: 'The "%service_id%" service is deprecated and will be removed in Roadiz 2.4. Use CSV serialization instead.' - package: roadiz/core-bundle - version: '2.3.19' - RZ\Roadiz\CoreBundle\Document\MediaFinder\YoutubeEmbedFinder: tags: [ { name: 'roadiz_core.media_finder', platform: 'youtube' } ] RZ\Roadiz\CoreBundle\Document\MediaFinder\VimeoEmbedFinder: @@ -356,17 +347,17 @@ services: # Recreate manager for each usage shared: false deprecated: - message: 'The "%service_id%" service is deprecated and will be removed in Roadiz 2.4. Use RZ\Roadiz\CoreBundle\Mailer\EmailManagerFactory instead.' + message: 'The "%service_id%" service is deprecated and will be removed in Roadiz 3.0. Use RZ\Roadiz\CoreBundle\Mailer\EmailManagerFactory instead.' package: roadiz/core-bundle - version: '2.3.18' + version: '2.2.28' RZ\Roadiz\CoreBundle\Mailer\ContactFormManager: # Recreate manager for each usage shared: false deprecated: - message: 'The "%service_id%" service is deprecated and will be removed in Roadiz 2.4. Use RZ\Roadiz\CoreBundle\Mailer\ContactFormManagerFactory instead.' + message: 'The "%service_id%" service is deprecated and will be removed in Roadiz 3.0. Use RZ\Roadiz\CoreBundle\Mailer\ContactFormManagerFactory instead.' package: roadiz/core-bundle - version: '2.3.18' + version: '2.2.28' RZ\Roadiz\CoreBundle\Doctrine\EventSubscriber\: resource: '../src/Doctrine/EventSubscriber' @@ -457,6 +448,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 @@ -467,10 +464,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', @@ -548,9 +545,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: @@ -566,6 +563,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 1ac4f2f4..00000000 --- a/migrations/Version20240318204224.php +++ /dev/null @@ -1,49 +0,0 @@ -connection->executeQuery('SELECT count(id) FROM `node_type_fields`'); - $count = $result->fetchOne(); - - if ($count > 0) { - $result = $this->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/migrations/Version20240702205419.php b/migrations/Version20240702205419.php deleted file mode 100644 index 23e5cfeb..00000000 --- a/migrations/Version20240702205419.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE custom_form_fields ADD autocomplete VARCHAR(18) DEFAULT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE custom_form_fields DROP autocomplete'); - } -} 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 9cb64081..00000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - tests - - - - - - src - - - - - - - - - - - - diff --git a/src/Api/Breadcrumbs/Breadcrumbs.php b/src/Api/Breadcrumbs/Breadcrumbs.php index 6d0fb068..1105ff4a 100644 --- a/src/Api/Breadcrumbs/Breadcrumbs.php +++ b/src/Api/Breadcrumbs/Breadcrumbs.php @@ -10,13 +10,18 @@ final class Breadcrumbs implements BreadcrumbsInterface { /** - * @param PersistableInterface[] $items + * @var array */ - public function __construct( - #[Serializer\Groups(["breadcrumbs", "web_response"])] - #[Serializer\MaxDepth(1)] - private readonly array $items - ) { + #[Serializer\Groups(["breadcrumbs", "web_response"])] + #[Serializer\MaxDepth(1)] + private array $items; + + /** + * @param array $items + */ + public function __construct(array $items) + { + $this->items = $items; } /** diff --git a/src/Api/Breadcrumbs/NodesSourcesBreadcrumbsFactory.php b/src/Api/Breadcrumbs/NodesSourcesBreadcrumbsFactory.php index 93cb2ee1..07fcf193 100644 --- a/src/Api/Breadcrumbs/NodesSourcesBreadcrumbsFactory.php +++ b/src/Api/Breadcrumbs/NodesSourcesBreadcrumbsFactory.php @@ -19,10 +19,12 @@ public function create(?PersistableInterface $entity): ?BreadcrumbsInterface return null; } - if (!$entity->isReachable()) { + if ( + null === $entity->getNode()->getNodeType() || + !$entity->isReachable() + ) { return null; } - $parents = []; while (null !== $entity = $entity->getParent()) { diff --git a/src/Api/Controller/GetWebResponseByPathController.php b/src/Api/Controller/GetWebResponseByPathController.php index b6434889..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,20 +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); - - // Set translation and locale to be used in Request context - if (null !== $resourceInfo->getTranslation()) { - $request->attributes->set('_translation', $resourceInfo->getTranslation()); - } - - if (null !== $resourceInfo->getLocale()) { - $request->attributes->set('_locale', $resourceInfo->getLocale()); - } + $this->addResourceToCacheTags($resource); /* * Or plain entity @@ -149,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/ArchiveExtension.php b/src/Api/Extension/ArchiveExtension.php index 14c615c6..e61a3305 100644 --- a/src/Api/Extension/ArchiveExtension.php +++ b/src/Api/Extension/ArchiveExtension.php @@ -43,10 +43,15 @@ */ final class ArchiveExtension implements QueryResultCollectionExtensionInterface { + private RequestStack $requestStack; + private string $defaultPublicationFieldName; + public function __construct( - private readonly RequestStack $requestStack, - private readonly string $defaultPublicationFieldName = 'publishedAt' + RequestStack $requestStack, + string $defaultPublicationFieldName = 'publishedAt' ) { + $this->requestStack = $requestStack; + $this->defaultPublicationFieldName = $defaultPublicationFieldName; } public function applyToCollection( diff --git a/src/Api/Extension/AttributeValueQueryExtension.php b/src/Api/Extension/AttributeValueQueryExtension.php index 889d8ef4..3a095af7 100644 --- a/src/Api/Extension/AttributeValueQueryExtension.php +++ b/src/Api/Extension/AttributeValueQueryExtension.php @@ -16,9 +16,12 @@ final class AttributeValueQueryExtension implements QueryItemExtensionInterface, QueryCollectionExtensionInterface { + private PreviewResolverInterface $previewResolver; + public function __construct( - private readonly PreviewResolverInterface $previewResolver + PreviewResolverInterface $previewResolver ) { + $this->previewResolver = $previewResolver; } public function applyToItem( diff --git a/src/Api/Extension/AttributeValueRealmExtension.php b/src/Api/Extension/AttributeValueRealmExtension.php index d14b3437..a43c17fd 100644 --- a/src/Api/Extension/AttributeValueRealmExtension.php +++ b/src/Api/Extension/AttributeValueRealmExtension.php @@ -12,13 +12,13 @@ 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 { public function __construct( - private readonly Security $security, - private readonly RealmResolverInterface $realmResolver + private Security $security, + private RealmResolverInterface $realmResolver ) { } diff --git a/src/Api/Extension/NodeQueryExtension.php b/src/Api/Extension/NodeQueryExtension.php index 9a4ee6c6..0b15784e 100644 --- a/src/Api/Extension/NodeQueryExtension.php +++ b/src/Api/Extension/NodeQueryExtension.php @@ -16,9 +16,12 @@ final class NodeQueryExtension implements QueryItemExtensionInterface, QueryCollectionExtensionInterface { + private PreviewResolverInterface $previewResolver; + public function __construct( - private readonly PreviewResolverInterface $previewResolver + PreviewResolverInterface $previewResolver ) { + $this->previewResolver = $previewResolver; } public function applyToItem( diff --git a/src/Api/Extension/NodesSourcesQueryExtension.php b/src/Api/Extension/NodesSourcesQueryExtension.php index aca8ba43..2e5b3960 100644 --- a/src/Api/Extension/NodesSourcesQueryExtension.php +++ b/src/Api/Extension/NodesSourcesQueryExtension.php @@ -17,10 +17,15 @@ final class NodesSourcesQueryExtension implements QueryItemExtensionInterface, QueryCollectionExtensionInterface { + private PreviewResolverInterface $previewResolver; + private string $generatedEntityNamespacePattern; + public function __construct( - private readonly PreviewResolverInterface $previewResolver, - private readonly string $generatedEntityNamespacePattern = '#^App\\\GeneratedEntity\\\NS(?:[a-zA-Z]+)$#' + PreviewResolverInterface $previewResolver, + string $generatedEntityNamespacePattern = '#^App\\\GeneratedEntity\\\NS(?:[a-zA-Z]+)$#' ) { + $this->previewResolver = $previewResolver; + $this->generatedEntityNamespacePattern = $generatedEntityNamespacePattern; } public function applyToItem( diff --git a/src/Api/Extension/NodesTagsQueryExtension.php b/src/Api/Extension/NodesTagsQueryExtension.php index 37afa629..a5ea3cb3 100644 --- a/src/Api/Extension/NodesTagsQueryExtension.php +++ b/src/Api/Extension/NodesTagsQueryExtension.php @@ -16,9 +16,12 @@ final class NodesTagsQueryExtension implements QueryItemExtensionInterface, QueryCollectionExtensionInterface { + private PreviewResolverInterface $previewResolver; + public function __construct( - private readonly PreviewResolverInterface $previewResolver + PreviewResolverInterface $previewResolver ) { + $this->previewResolver = $previewResolver; } public function applyToItem( diff --git a/src/Api/Filter/LocaleFilter.php b/src/Api/Filter/LocaleFilter.php index aaefd4a5..a4579ec5 100644 --- a/src/Api/Filter/LocaleFilter.php +++ b/src/Api/Filter/LocaleFilter.php @@ -19,8 +19,10 @@ final class LocaleFilter extends GeneratedEntityFilter { public const PROPERTY = '_locale'; + private PreviewResolverInterface $previewResolver; + public function __construct( - private readonly PreviewResolverInterface $previewResolver, + PreviewResolverInterface $previewResolver, ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null, @@ -28,6 +30,7 @@ public function __construct( string $generatedEntityNamespacePattern = '#^App\\\GeneratedEntity\\\NS(?:[a-zA-Z]+)$#' ) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter, $generatedEntityNamespacePattern); + $this->previewResolver = $previewResolver; } protected function filterProperty( diff --git a/src/Api/ListManager/SolrPaginator.php b/src/Api/ListManager/SolrPaginator.php index b5e3f448..8eedf602 100644 --- a/src/Api/ListManager/SolrPaginator.php +++ b/src/Api/ListManager/SolrPaginator.php @@ -5,15 +5,19 @@ namespace RZ\Roadiz\CoreBundle\Api\ListManager; use ApiPlatform\State\Pagination\PaginatorInterface; -use Symfony\Component\DependencyInjection\Attribute\Exclude; +use Doctrine\Common\Collections\ArrayCollection; -#[Exclude] final class SolrPaginator implements PaginatorInterface, \IteratorAggregate { private bool $handled = false; + private SolrSearchListManager $listManager; - public function __construct(private readonly SolrSearchListManager $listManager) + /** + * @param SolrSearchListManager $listManager + */ + public function __construct(SolrSearchListManager $listManager) { + $this->listManager = $listManager; } protected function handleOnce(): void diff --git a/src/Api/ListManager/SolrSearchListManager.php b/src/Api/ListManager/SolrSearchListManager.php index e0dca7e7..a79c575a 100644 --- a/src/Api/ListManager/SolrSearchListManager.php +++ b/src/Api/ListManager/SolrSearchListManager.php @@ -7,22 +7,26 @@ use RZ\Roadiz\CoreBundle\ListManager\AbstractEntityListManager; use RZ\Roadiz\CoreBundle\SearchEngine\SearchHandlerInterface; use RZ\Roadiz\CoreBundle\SearchEngine\SearchResultsInterface; -use Symfony\Component\DependencyInjection\Attribute\Exclude; use Symfony\Component\HttpFoundation\Request; -#[Exclude] final class SolrSearchListManager extends AbstractEntityListManager { - private ?SearchResultsInterface $searchResults; + protected SearchHandlerInterface $searchHandler; + protected ?SearchResultsInterface $searchResults; + private array $criteria; + private bool $searchInTags; private ?string $query = null; public function __construct( ?Request $request, - private readonly SearchHandlerInterface $searchHandler, - private readonly array $criteria = [], - private readonly bool $searchInTags = true + SearchHandlerInterface $searchHandler, + array $criteria = [], + bool $searchInTags = true ) { parent::__construct($request); + $this->searchHandler = $searchHandler; + $this->criteria = $criteria; + $this->searchInTags = $searchInTags; } public function handle(bool $disabled = false) @@ -46,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 { @@ -54,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() ); } @@ -79,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/JwtDecorator.php b/src/Api/OpenApi/JwtDecorator.php index cb41dee7..2a449446 100644 --- a/src/Api/OpenApi/JwtDecorator.php +++ b/src/Api/OpenApi/JwtDecorator.php @@ -10,9 +10,12 @@ final class JwtDecorator implements OpenApiFactoryInterface { + private OpenApiFactoryInterface $decorated; + public function __construct( - private readonly OpenApiFactoryInterface $decorated + OpenApiFactoryInterface $decorated ) { + $this->decorated = $decorated; } public function __invoke(array $context = []): OpenApi diff --git a/src/Api/OpenApi/PreviewDecorator.php b/src/Api/OpenApi/PreviewDecorator.php index 7dcecaae..f63c706e 100644 --- a/src/Api/OpenApi/PreviewDecorator.php +++ b/src/Api/OpenApi/PreviewDecorator.php @@ -11,9 +11,12 @@ final class PreviewDecorator implements OpenApiFactoryInterface { + private OpenApiFactoryInterface $decorated; + public function __construct( - private readonly OpenApiFactoryInterface $decorated + OpenApiFactoryInterface $decorated ) { + $this->decorated = $decorated; } public function __invoke(array $context = []): OpenApi diff --git a/src/Api/OpenApi/WebResponseDecorator.php b/src/Api/OpenApi/WebResponseDecorator.php index e76a1947..da54e849 100644 --- a/src/Api/OpenApi/WebResponseDecorator.php +++ b/src/Api/OpenApi/WebResponseDecorator.php @@ -10,19 +10,18 @@ final class WebResponseDecorator implements OpenApiFactoryInterface { + private OpenApiFactoryInterface $decorated; + public function __construct( - private readonly OpenApiFactoryInterface $decorated + OpenApiFactoryInterface $decorated ) { + $this->decorated = $decorated; } 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/Api/TreeWalker/Definition/DefinitionFactoryConfiguration.php b/src/Api/TreeWalker/Definition/DefinitionFactoryConfiguration.php index a4326a3e..a63ca9c1 100644 --- a/src/Api/TreeWalker/Definition/DefinitionFactoryConfiguration.php +++ b/src/Api/TreeWalker/Definition/DefinitionFactoryConfiguration.php @@ -6,15 +6,22 @@ final class DefinitionFactoryConfiguration { + /** + * @var class-string + */ + public string $classname; + public bool $onlyVisible; + public DefinitionFactoryInterface $definitionFactory; + /** * @param class-string $classname * @param DefinitionFactoryInterface $definitionFactory * @param bool $onlyVisible */ - public function __construct( - public readonly string $classname, - public readonly DefinitionFactoryInterface $definitionFactory, - public readonly bool $onlyVisible - ) { + public function __construct(string $classname, DefinitionFactoryInterface $definitionFactory, bool $onlyVisible) + { + $this->classname = $classname; + $this->onlyVisible = $onlyVisible; + $this->definitionFactory = $definitionFactory; } } diff --git a/src/Api/TreeWalker/Definition/MultiTypeChildrenDefinition.php b/src/Api/TreeWalker/Definition/MultiTypeChildrenDefinition.php index 9e6999ea..030e7f45 100644 --- a/src/Api/TreeWalker/Definition/MultiTypeChildrenDefinition.php +++ b/src/Api/TreeWalker/Definition/MultiTypeChildrenDefinition.php @@ -14,16 +14,19 @@ final class MultiTypeChildrenDefinition { use ContextualDefinitionTrait; + private array $types; + private bool $onlyVisible; + /** * @param WalkerContextInterface $context * @param array $types * @param bool $onlyVisible */ - public function __construct( - private readonly WalkerContextInterface $context, - private readonly array $types, - private readonly bool $onlyVisible = true - ) { + public function __construct(WalkerContextInterface $context, array $types, bool $onlyVisible = true) + { + $this->context = $context; + $this->types = $types; + $this->onlyVisible = $onlyVisible; } /** @@ -32,27 +35,26 @@ public function __construct( */ public function __invoke(NodesSources $source) { - if (!($this->context instanceof NodeSourceWalkerContext)) { - throw new \InvalidArgumentException('Context should be instance of ' . NodeSourceWalkerContext::class); + if ($this->context instanceof NodeSourceWalkerContext) { + $this->context->getStopwatch()->start(self::class); + $bag = $this->context->getNodeTypesBag(); + $criteria = [ + 'node.parent' => $source->getNode(), + 'translation' => $source->getTranslation(), + 'node.nodeType' => array_map(function (string $singleType) use ($bag) { + return $bag->get($singleType); + }, $this->types) + ]; + if ($this->onlyVisible) { + $criteria['node.visible'] = true; + } + $children = $this->context->getNodeSourceApi()->getBy($criteria, [ + 'node.position' => 'ASC', + ]); + $this->context->getStopwatch()->stop(self::class); + + return $children; } - - $this->context->getStopwatch()->start(self::class); - $bag = $this->context->getNodeTypesBag(); - $criteria = [ - 'node.parent' => $source->getNode(), - 'translation' => $source->getTranslation(), - 'node.nodeType' => array_map(function (string $singleType) use ($bag) { - return $bag->get($singleType); - }, $this->types) - ]; - if ($this->onlyVisible) { - $criteria['node.visible'] = true; - } - $children = $this->context->getNodeSourceApi()->getBy($criteria, [ - 'node.position' => 'ASC', - ]); - $this->context->getStopwatch()->stop(self::class); - - return $children; + throw new \InvalidArgumentException('Context should be instance of ' . NodeSourceWalkerContext::class); } } diff --git a/src/Api/TreeWalker/Definition/NonReachableNodeSourceBlockDefinition.php b/src/Api/TreeWalker/Definition/NonReachableNodeSourceBlockDefinition.php index 64df42ec..968e4b0d 100644 --- a/src/Api/TreeWalker/Definition/NonReachableNodeSourceBlockDefinition.php +++ b/src/Api/TreeWalker/Definition/NonReachableNodeSourceBlockDefinition.php @@ -10,16 +10,16 @@ use RZ\Roadiz\CoreBundle\Api\TreeWalker\NodeSourceWalkerContext; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\TreeWalker\Definition\ContextualDefinitionTrait; -use RZ\TreeWalker\WalkerContextInterface; final class NonReachableNodeSourceBlockDefinition { use ContextualDefinitionTrait; - public function __construct( - private readonly WalkerContextInterface $context, - private readonly bool $onlyVisible = true - ) { + private bool $onlyVisible; + + public function __construct(bool $onlyVisible = true) + { + $this->onlyVisible = $onlyVisible; } /** @@ -27,32 +27,31 @@ public function __construct( */ public function __invoke(NodesSources $source): array { - if (!($this->context instanceof NodeSourceWalkerContext)) { - throw new \InvalidArgumentException('Context should be instance of ' . NodeSourceWalkerContext::class); - } - - $this->context->getStopwatch()->start(self::class); - $criteria = [ - 'node.parent' => $source->getNode(), - 'translation' => $source->getTranslation(), - 'node.nodeType.reachable' => false, - ]; - if ($this->onlyVisible) { - $criteria['node.visible'] = true; - } - $children = $this->context->getNodeSourceApi()->getBy($criteria, [ - 'node.position' => 'ASC', - ]); - $this->context->getStopwatch()->stop(self::class); - - if ($children instanceof Paginator) { - $iterator = $children->getIterator(); - if ($iterator instanceof ArrayIterator) { - return $iterator->getArrayCopy(); + if ($this->context instanceof NodeSourceWalkerContext) { + $this->context->getStopwatch()->start(self::class); + $criteria = [ + 'node.parent' => $source->getNode(), + 'translation' => $source->getTranslation(), + 'node.nodeType.reachable' => false, + ]; + if ($this->onlyVisible) { + $criteria['node.visible'] = true; + } + $children = $this->context->getNodeSourceApi()->getBy($criteria, [ + 'node.position' => 'ASC', + ]); + $this->context->getStopwatch()->stop(self::class); + + if ($children instanceof Paginator) { + $iterator = $children->getIterator(); + if ($iterator instanceof ArrayIterator) { + return $iterator->getArrayCopy(); + } + // @phpstan-ignore-next-line + return iterator_to_array($iterator); } - // @phpstan-ignore-next-line - return iterator_to_array($iterator); + return $children; } - return $children; + throw new \InvalidArgumentException('Context should be instance of ' . NodeSourceWalkerContext::class); } } diff --git a/src/Api/TreeWalker/Definition/ReachableNodeSourceDefinition.php b/src/Api/TreeWalker/Definition/ReachableNodeSourceDefinition.php index 5eecf6b3..22fbc6ac 100644 --- a/src/Api/TreeWalker/Definition/ReachableNodeSourceDefinition.php +++ b/src/Api/TreeWalker/Definition/ReachableNodeSourceDefinition.php @@ -10,16 +10,16 @@ use RZ\Roadiz\CoreBundle\Api\TreeWalker\NodeSourceWalkerContext; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\TreeWalker\Definition\ContextualDefinitionTrait; -use RZ\TreeWalker\WalkerContextInterface; final class ReachableNodeSourceDefinition { use ContextualDefinitionTrait; - public function __construct( - private readonly WalkerContextInterface $context, - private readonly bool $onlyVisible = true - ) { + private bool $onlyVisible; + + public function __construct(bool $onlyVisible = true) + { + $this->onlyVisible = $onlyVisible; } /** @@ -27,32 +27,31 @@ public function __construct( */ public function __invoke(NodesSources $source): array { - if (!($this->context instanceof NodeSourceWalkerContext)) { - throw new \InvalidArgumentException('Context should be instance of ' . NodeSourceWalkerContext::class); - } - - $this->context->getStopwatch()->start(self::class); - $criteria = [ - 'node.parent' => $source->getNode(), - 'translation' => $source->getTranslation(), - 'node.nodeType.reachable' => true, - ]; - if ($this->onlyVisible) { - $criteria['node.visible'] = true; - } - $children = $this->context->getNodeSourceApi()->getBy($criteria, [ - 'node.position' => 'ASC', - ]); - $this->context->getStopwatch()->stop(self::class); - - if ($children instanceof Paginator) { - $iterator = $children->getIterator(); - if ($iterator instanceof ArrayIterator) { - return $iterator->getArrayCopy(); + if ($this->context instanceof NodeSourceWalkerContext) { + $this->context->getStopwatch()->start(self::class); + $criteria = [ + 'node.parent' => $source->getNode(), + 'translation' => $source->getTranslation(), + 'node.nodeType.reachable' => true, + ]; + if ($this->onlyVisible) { + $criteria['node.visible'] = true; + } + $children = $this->context->getNodeSourceApi()->getBy($criteria, [ + 'node.position' => 'ASC', + ]); + $this->context->getStopwatch()->stop(self::class); + + if ($children instanceof Paginator) { + $iterator = $children->getIterator(); + if ($iterator instanceof ArrayIterator) { + return $iterator->getArrayCopy(); + } + // @phpstan-ignore-next-line + return iterator_to_array($iterator); } - // @phpstan-ignore-next-line - return iterator_to_array($iterator); + return $children; } - return $children; + throw new \InvalidArgumentException('Context should be instance of ' . NodeSourceWalkerContext::class); } } diff --git a/src/Api/TreeWalker/NodeSourceWalkerContext.php b/src/Api/TreeWalker/NodeSourceWalkerContext.php index 164154ac..42a20eb9 100644 --- a/src/Api/TreeWalker/NodeSourceWalkerContext.php +++ b/src/Api/TreeWalker/NodeSourceWalkerContext.php @@ -12,24 +12,39 @@ use RZ\Roadiz\CoreBundle\NodeType\NodeTypeResolver; use RZ\Roadiz\CoreBundle\Preview\PreviewResolverInterface; use RZ\TreeWalker\WalkerContextInterface; -use Symfony\Component\DependencyInjection\Attribute\Exclude; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Stopwatch\Stopwatch; -#[Exclude] class NodeSourceWalkerContext implements WalkerContextInterface { + private Stopwatch $stopwatch; + private NodeTypes $nodeTypesBag; + private NodeSourceApi $nodeSourceApi; + private RequestStack $requestStack; + private ManagerRegistry $managerRegistry; + private CacheItemPoolInterface $cacheAdapter; + private NodeTypeResolver $nodeTypeResolver; + private PreviewResolverInterface $previewResolver; + public function __construct( - private readonly Stopwatch $stopwatch, - private readonly NodeTypes $nodeTypesBag, - private readonly NodeSourceApi $nodeSourceApi, - private readonly RequestStack $requestStack, - private readonly ManagerRegistry $managerRegistry, - private readonly CacheItemPoolInterface $cacheAdapter, - private readonly NodeTypeResolver $nodeTypeResolver, - private readonly PreviewResolverInterface $previewResolver + Stopwatch $stopwatch, + NodeTypes $nodeTypesBag, + NodeSourceApi $nodeSourceApi, + RequestStack $requestStack, + ManagerRegistry $managerRegistry, + CacheItemPoolInterface $cacheAdapter, + NodeTypeResolver $nodeTypeResolver, + PreviewResolverInterface $previewResolver ) { + $this->stopwatch = $stopwatch; + $this->nodeTypesBag = $nodeTypesBag; + $this->nodeSourceApi = $nodeSourceApi; + $this->requestStack = $requestStack; + $this->managerRegistry = $managerRegistry; + $this->cacheAdapter = $cacheAdapter; + $this->nodeTypeResolver = $nodeTypeResolver; + $this->previewResolver = $previewResolver; } /** diff --git a/src/Api/TreeWalker/NodeSourceWalkerContextFactory.php b/src/Api/TreeWalker/NodeSourceWalkerContextFactory.php index 2c5099c6..9dea52fb 100644 --- a/src/Api/TreeWalker/NodeSourceWalkerContextFactory.php +++ b/src/Api/TreeWalker/NodeSourceWalkerContextFactory.php @@ -16,16 +16,33 @@ final class NodeSourceWalkerContextFactory implements WalkerContextFactoryInterface { + private Stopwatch $stopwatch; + private NodeTypes $nodeTypesBag; + private NodeSourceApi $nodeSourceApi; + private RequestStack $requestStack; + private ManagerRegistry $managerRegistry; + private CacheItemPoolInterface $cacheAdapter; + private NodeTypeResolver $nodeTypeResolver; + private PreviewResolverInterface $previewResolver; + public function __construct( - private readonly Stopwatch $stopwatch, - private readonly NodeTypes $nodeTypesBag, - private readonly NodeSourceApi $nodeSourceApi, - private readonly RequestStack $requestStack, - private readonly ManagerRegistry $managerRegistry, - private readonly CacheItemPoolInterface $cacheAdapter, - private readonly NodeTypeResolver $nodeTypeResolver, - private readonly PreviewResolverInterface $previewResolver + Stopwatch $stopwatch, + NodeTypes $nodeTypesBag, + NodeSourceApi $nodeSourceApi, + RequestStack $requestStack, + ManagerRegistry $managerRegistry, + CacheItemPoolInterface $cacheAdapter, + NodeTypeResolver $nodeTypeResolver, + PreviewResolverInterface $previewResolver ) { + $this->stopwatch = $stopwatch; + $this->nodeTypesBag = $nodeTypesBag; + $this->nodeSourceApi = $nodeSourceApi; + $this->requestStack = $requestStack; + $this->managerRegistry = $managerRegistry; + $this->cacheAdapter = $cacheAdapter; + $this->nodeTypeResolver = $nodeTypeResolver; + $this->previewResolver = $previewResolver; } public function createWalkerContext(): WalkerContextInterface diff --git a/src/Api/TreeWalker/TreeWalkerGenerator.php b/src/Api/TreeWalker/TreeWalkerGenerator.php index b456f2db..7a8bff01 100644 --- a/src/Api/TreeWalker/TreeWalkerGenerator.php +++ b/src/Api/TreeWalker/TreeWalkerGenerator.php @@ -18,17 +18,26 @@ final class TreeWalkerGenerator { + private NodeSourceApi $nodeSourceApi; + private NodeTypes $nodeTypesBag; + private WalkerContextInterface $walkerContext; + private CacheItemPoolInterface $cacheItemPool; + /** * @var array */ private array $walkerDefinitionFactories = []; public function __construct( - private readonly NodeSourceApi $nodeSourceApi, - private readonly NodeTypes $nodeTypesBag, - private readonly WalkerContextInterface $walkerContext, - private readonly CacheItemPoolInterface $cacheItemPool + NodeSourceApi $nodeSourceApi, + NodeTypes $nodeTypesBag, + WalkerContextInterface $walkerContext, + CacheItemPoolInterface $cacheItemPool ) { + $this->nodeSourceApi = $nodeSourceApi; + $this->nodeTypesBag = $nodeTypesBag; + $this->walkerContext = $walkerContext; + $this->cacheItemPool = $cacheItemPool; } /** 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 5a5d4eec..da8710c4 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 EmailManagerFactory $emailManagerFactory, - ?string $name = null - ) { - parent::__construct($name); + protected EmailManagerFactory $emailManagerFactory; + + /** + * @param EmailManagerFactory $emailManagerFactory + */ + public function __construct(EmailManagerFactory $emailManagerFactory) + { + parent::__construct(); + $this->emailManagerFactory = $emailManagerFactory; } + 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 179d96cf..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, @@ -160,7 +231,8 @@ public function addAction(Request $request, int $customFormId): Response ); if ($mixed instanceof Response) { - return $mixed; + $mixed->prepare($request); + return $mixed->send(); } else { return $this->render('@RoadizCore/customForm/customForm.html.twig', $mixed); } @@ -208,14 +280,9 @@ public function prepareAndHandleCustomFormAssignation( ?string $emailSender = null, bool $prefix = true ) { - $assignation = [ - 'customForm' => $customFormsEntity, - 'fields' => $customFormsEntity->getFields(), - 'head' => [ - 'siteTitle' => $this->settingsBag->get('site_name'), - 'mainColor' => $this->settingsBag->get('main_color'), - ] - ]; + $assignation = []; + $assignation['customForm'] = $customFormsEntity; + $assignation['fields'] = $customFormsEntity->getFields(); $helper = $this->customFormHelperFactory->createHelper($customFormsEntity); $form = $helper->getForm( $request, @@ -236,9 +303,6 @@ public function prepareAndHandleCustomFormAssignation( throw new \RuntimeException('Answer ID is null'); } - if (null === $emailSender || false === filter_var($answer->getEmail(), FILTER_VALIDATE_EMAIL)) { - $emailSender = $answer->getEmail(); - } if (null === $emailSender || false === filter_var($emailSender, FILTER_VALIDATE_EMAIL)) { $emailSender = $this->settingsBag->get('email_sender'); } @@ -258,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; @@ -277,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/Controller/HealthCheckController.php b/src/Controller/HealthCheckController.php index 952f17f1..4bff540b 100644 --- a/src/Controller/HealthCheckController.php +++ b/src/Controller/HealthCheckController.php @@ -11,12 +11,21 @@ final class HealthCheckController { + private ?string $healthCheckToken; + private ?string $appVersion; + private ?string $cmsVersion; + private ?string $cmsVersionPrefix; + public function __construct( - private readonly ?string $healthCheckToken, - private readonly ?string $appVersion, - private readonly ?string $cmsVersion, - private readonly ?string $cmsVersionPrefix + ?string $healthCheckToken, + ?string $appVersion, + ?string $cmsVersion, + ?string $cmsVersionPrefix ) { + $this->healthCheckToken = $healthCheckToken; + $this->appVersion = $appVersion; + $this->cmsVersion = $cmsVersion; + $this->cmsVersionPrefix = $cmsVersionPrefix; } public function __invoke(Request $request): JsonResponse diff --git a/src/Controller/RedirectionController.php b/src/Controller/RedirectionController.php index 6ede96a6..bffc5dc4 100644 --- a/src/Controller/RedirectionController.php +++ b/src/Controller/RedirectionController.php @@ -14,10 +14,18 @@ final class RedirectionController { - public function __construct(private readonly UrlGeneratorInterface $urlGenerator) + private UrlGeneratorInterface $urlGenerator; + + public function __construct(UrlGeneratorInterface $urlGenerator) { + $this->urlGenerator = $urlGenerator; } + /** + * @param Request $request + * @param Redirection $redirection + * @return RedirectResponse + */ public function redirectAction(Request $request, Redirection $redirection): RedirectResponse { if (null !== $redirection->getRedirectNodeSource()) { 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/CustomForm/CustomFormAnswerSerializer.php b/src/CustomForm/CustomFormAnswerSerializer.php index cdf08b8e..28545aad 100644 --- a/src/CustomForm/CustomFormAnswerSerializer.php +++ b/src/CustomForm/CustomFormAnswerSerializer.php @@ -11,27 +11,38 @@ class CustomFormAnswerSerializer { - public function __construct(protected readonly UrlGeneratorInterface $urlGenerator) + /** + * @var UrlGeneratorInterface + */ + protected UrlGeneratorInterface $urlGenerator; + + /** + * @param UrlGeneratorInterface $urlGenerator + */ + public function __construct(UrlGeneratorInterface $urlGenerator) { + $this->urlGenerator = $urlGenerator; } + /** + * @param CustomFormAnswer $answer + * + * @return array + */ public function toSimpleArray(CustomFormAnswer $answer): array { - $answers = [ - 'ip' => $answer->getIp(), - 'submitted.date' => $answer->getSubmittedAt() - ]; + $answers = []; /** @var CustomFormFieldAttribute $answerAttr */ foreach ($answer->getAnswerFields() as $answerAttr) { $field = $answerAttr->getCustomFormField(); if ($field->isDocuments()) { - $answers[$field->getLabel()] = implode(PHP_EOL, $answerAttr->getDocuments()->map(function (Document $document) { + $answers[$field->getName()] = implode(PHP_EOL, $answerAttr->getDocuments()->map(function (Document $document) { return $this->urlGenerator->generate('documentsDownloadPage', [ 'documentId' => $document->getId() ], UrlGeneratorInterface::ABSOLUTE_URL); })->toArray()); } else { - $answers[$field->getLabel()] = $answerAttr->getValue(); + $answers[$field->getName()] = $answerAttr->getValue(); } } return $answers; diff --git a/src/CustomForm/CustomFormHelper.php b/src/CustomForm/CustomFormHelper.php index 1a2dcb27..ce8a28de 100644 --- a/src/CustomForm/CustomFormHelper.php +++ b/src/CustomForm/CustomFormHelper.php @@ -17,7 +17,6 @@ use RZ\Roadiz\Documents\Events\DocumentCreatedEvent; use RZ\Roadiz\Documents\Models\DocumentInterface; use RZ\Roadiz\Utils\StringHandler; -use Symfony\Component\DependencyInjection\Attribute\Exclude; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -25,19 +24,39 @@ use Symfony\Component\String\Slugger\AsciiSlugger; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; -#[Exclude] class CustomFormHelper { public const ARRAY_SEPARATOR = ', '; + protected AbstractDocumentFactory $documentFactory; + protected ObjectManager $em; + protected CustomForm $customForm; + protected FormFactoryInterface $formFactory; + protected Settings $settingsBag; + protected EventDispatcherInterface $eventDispatcher; + + /** + * @param ObjectManager $em + * @param CustomForm $customForm + * @param AbstractDocumentFactory $documentFactory + * @param FormFactoryInterface $formFactory + * @param Settings $settingsBag + * @param EventDispatcherInterface $eventDispatcher + */ public function __construct( - protected readonly ObjectManager $em, - protected readonly CustomForm $customForm, - protected readonly AbstractDocumentFactory $documentFactory, - protected readonly FormFactoryInterface $formFactory, - protected readonly Settings $settingsBag, - protected readonly EventDispatcherInterface $eventDispatcher + ObjectManager $em, + CustomForm $customForm, + AbstractDocumentFactory $documentFactory, + FormFactoryInterface $formFactory, + Settings $settingsBag, + EventDispatcherInterface $eventDispatcher ) { + $this->em = $em; + $this->customForm = $customForm; + $this->documentFactory = $documentFactory; + $this->formFactory = $formFactory; + $this->settingsBag = $settingsBag; + $this->eventDispatcher = $eventDispatcher; } /** @@ -78,84 +97,81 @@ public function parseAnswerFormData( CustomFormAnswer $answer = null, string $ipAddress = "" ): CustomFormAnswer { - if (!$form->isSubmitted()) { - throw new \InvalidArgumentException('Form must be submitted before begin parsing.'); - } - if (!$form->isValid()) { - throw new \InvalidArgumentException('Form must be validated before begin parsing.'); - } - - /* - * Create answer if null. - */ - if (null === $answer) { - $answer = new CustomFormAnswer(); - $answer->setCustomForm($this->customForm); - $this->em->persist($answer); - } - $answer->setSubmittedAt(new \DateTime()); - $answer->setIp($ipAddress); - $documentsUploaded = []; - - /** @var CustomFormField $customFormField */ - foreach ($this->customForm->getFields() as $customFormField) { - $formField = null; - $fieldAttr = null; - + if ($form->isSubmitted() && $form->isValid()) { /* - * Get data in form groups + * Create answer if null. */ - if ($customFormField->getGroupName() != '') { - $groupCanonical = StringHandler::slugify($customFormField->getGroupName()); - $formGroup = $form->get($groupCanonical); - if ($formGroup->has($customFormField->getName())) { - $formField = $formGroup->get($customFormField->getName()); - $fieldAttr = $this->getAttribute($answer, $customFormField); - } - } else { - if ($form->has($customFormField->getName())) { - $formField = $form->get($customFormField->getName()); - $fieldAttr = $this->getAttribute($answer, $customFormField); - } + if (null === $answer) { + $answer = new CustomFormAnswer(); + $answer->setCustomForm($this->customForm); + $this->em->persist($answer); } + $answer->setSubmittedAt(new \DateTime()); + $answer->setIp($ipAddress); + $documentsUploaded = []; + + /** @var CustomFormField $customFormField */ + foreach ($this->customForm->getFields() as $customFormField) { + $formField = null; + $fieldAttr = null; - if (null !== $formField) { - $data = $formField->getData(); /* - * Create attribute if null. - */ - if (null === $fieldAttr) { - $fieldAttr = new CustomFormFieldAttribute(); - $fieldAttr->setCustomFormAnswer($answer); - $fieldAttr->setCustomFormField($customFormField); - $this->em->persist($fieldAttr); + * Get data in form groups + */ + if ($customFormField->getGroupName() != '') { + $groupCanonical = StringHandler::slugify($customFormField->getGroupName()); + $formGroup = $form->get($groupCanonical); + if ($formGroup->has($customFormField->getName())) { + $formField = $formGroup->get($customFormField->getName()); + $fieldAttr = $this->getAttribute($answer, $customFormField); + } + } else { + if ($form->has($customFormField->getName())) { + $formField = $form->get($customFormField->getName()); + $fieldAttr = $this->getAttribute($answer, $customFormField); + } } - if (is_array($data) && isset($data[0]) && $data[0] instanceof UploadedFile) { - /** @var UploadedFile $file */ - foreach ($data as $file) { - $documentsUploaded[] = $this->handleUploadedFile($file, $fieldAttr); + if (null !== $formField) { + $data = $formField->getData(); + /* + * Create attribute if null. + */ + if (null === $fieldAttr) { + $fieldAttr = new CustomFormFieldAttribute(); + $fieldAttr->setCustomFormAnswer($answer); + $fieldAttr->setCustomFormField($customFormField); + $this->em->persist($fieldAttr); + } + + if (is_array($data) && isset($data[0]) && $data[0] instanceof UploadedFile) { + /** @var UploadedFile $file */ + foreach ($data as $file) { + $documentsUploaded[] = $this->handleUploadedFile($file, $fieldAttr); + } + } elseif ($data instanceof UploadedFile) { + $documentsUploaded[] = $this->handleUploadedFile($data, $fieldAttr); + } else { + $fieldAttr->setValue($this->formValueToString($data)); } - } elseif ($data instanceof UploadedFile) { - $documentsUploaded[] = $this->handleUploadedFile($data, $fieldAttr); - } else { - $fieldAttr->setValue($this->formValueToString($data)); } } - } - $this->em->flush(); + $this->em->flush(); - // Dispatch event on document uploaded - foreach ($documentsUploaded as $documentUploaded) { - if ($documentUploaded instanceof DocumentInterface) { - $this->eventDispatcher->dispatch(new DocumentCreatedEvent($documentUploaded)); + // Dispatch event on document uploaded + foreach ($documentsUploaded as $documentUploaded) { + if ($documentUploaded instanceof DocumentInterface) { + $this->eventDispatcher->dispatch(new DocumentCreatedEvent($documentUploaded)); + } } - } - $this->em->refresh($answer); + $this->em->refresh($answer); - return $answer; + return $answer; + } + + throw new \InvalidArgumentException('Form must be submitted and validated before begin parsing.'); } /** @@ -178,6 +194,9 @@ protected function handleUploadedFile( return $document; } + /** + * @return Folder|null + */ protected function getDocumentFolderForCustomForm(): ?Folder { return $this->em->getRepository(Folder::class) @@ -188,7 +207,11 @@ protected function getDocumentFolderForCustomForm(): ?Folder ); } - private function formValueToString(mixed $rawValue): string + /** + * @param mixed $rawValue + * @return string + */ + private function formValueToString($rawValue): string { if ($rawValue instanceof \DateTimeInterface) { return $rawValue->format('Y-m-d H:i:s'); diff --git a/src/CustomForm/CustomFormHelperFactory.php b/src/CustomForm/CustomFormHelperFactory.php index a04deb5e..602c7133 100644 --- a/src/CustomForm/CustomFormHelperFactory.php +++ b/src/CustomForm/CustomFormHelperFactory.php @@ -13,13 +13,31 @@ final class CustomFormHelperFactory { + protected PrivateDocumentFactory $privateDocumentFactory; + protected ObjectManager $em; + protected FormFactoryInterface $formFactory; + protected Settings $settingsBag; + protected EventDispatcherInterface $eventDispatcher; + + /** + * @param PrivateDocumentFactory $privateDocumentFactory + * @param ObjectManager $em + * @param FormFactoryInterface $formFactory + * @param Settings $settingsBag + * @param EventDispatcherInterface $eventDispatcher + */ public function __construct( - private readonly PrivateDocumentFactory $privateDocumentFactory, - private readonly ObjectManager $em, - private readonly FormFactoryInterface $formFactory, - private readonly Settings $settingsBag, - private readonly EventDispatcherInterface $eventDispatcher + PrivateDocumentFactory $privateDocumentFactory, + ObjectManager $em, + FormFactoryInterface $formFactory, + Settings $settingsBag, + EventDispatcherInterface $eventDispatcher ) { + $this->privateDocumentFactory = $privateDocumentFactory; + $this->em = $em; + $this->formFactory = $formFactory; + $this->settingsBag = $settingsBag; + $this->eventDispatcher = $eventDispatcher; } public function createHelper(CustomForm $customForm): CustomFormHelper diff --git a/src/CustomForm/Message/CustomFormAnswerNotifyMessage.php b/src/CustomForm/Message/CustomFormAnswerNotifyMessage.php index ce56e283..8f092728 100644 --- a/src/CustomForm/Message/CustomFormAnswerNotifyMessage.php +++ b/src/CustomForm/Message/CustomFormAnswerNotifyMessage.php @@ -8,12 +8,23 @@ final class CustomFormAnswerNotifyMessage implements AsyncMessage { - public function __construct( - private readonly int $customFormAnswerId, - private readonly string $title, - private readonly string $senderAddress, - private readonly string $locale - ) { + private int $customFormAnswerId; + private string $title; + private string $senderAddress; + private string $locale; + + /** + * @param int $customFormAnswerId + * @param string $title + * @param string $senderAddress + * @param string $locale + */ + public function __construct(int $customFormAnswerId, string $title, string $senderAddress, string $locale) + { + $this->customFormAnswerId = $customFormAnswerId; + $this->title = $title; + $this->senderAddress = $senderAddress; + $this->locale = $locale; } public function getCustomFormAnswerId(): int diff --git a/src/CustomForm/Message/Handler/CustomFormAnswerNotifyMessageHandler.php b/src/CustomForm/Message/Handler/CustomFormAnswerNotifyMessageHandler.php index 5e8d9a1c..300f977f 100644 --- a/src/CustomForm/Message/Handler/CustomFormAnswerNotifyMessageHandler.php +++ b/src/CustomForm/Message/Handler/CustomFormAnswerNotifyMessageHandler.php @@ -24,11 +24,11 @@ final class CustomFormAnswerNotifyMessageHandler implements MessageHandlerInterface { public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly EmailManagerFactory $emailManagerFactory, - private readonly Settings $settingsBag, - private readonly FilesystemOperator $documentsStorage, - private readonly LoggerInterface $messengerLogger, + private ManagerRegistry $managerRegistry, + private EmailManagerFactory $emailManagerFactory, + private Settings $settingsBag, + private FilesystemOperator $documentsStorage, + private LoggerInterface $logger, ) { } @@ -51,6 +51,12 @@ public function __invoke(CustomFormAnswerNotifyMessage $message): void $answer->toArray(false) ); + $receiver = array_filter( + array_map('trim', explode(',', $answer->getCustomForm()->getEmail() ?? '')) + ); + $receiver = array_map(function (string $email) { + return new Address($email); + }, $receiver); $this->sendAnswer( $answer, [ @@ -59,21 +65,9 @@ public function __invoke(CustomFormAnswerNotifyMessage $message): void 'customForm' => $answer->getCustomForm(), 'title' => $message->getTitle(), 'requestLocale' => $message->getLocale(), - ] - ); - } - - /** - * @return Address[] - */ - private function getCustomFormReceivers(CustomFormAnswer $answer): array - { - $receiver = array_filter( - array_map('trim', explode(',', $answer->getCustomForm()->getEmail() ?? '')) + ], + $receiver ); - return array_map(function (string $email) { - return new Address($email); - }, $receiver); } /** @@ -81,6 +75,7 @@ private function getCustomFormReceivers(CustomFormAnswer $answer): array * * @param CustomFormAnswer $answer * @param array $assignation + * @param string|array|null $receiver * @throws TransportExceptionInterface * @throws LoaderError * @throws RuntimeError @@ -88,20 +83,18 @@ private function getCustomFormReceivers(CustomFormAnswer $answer): array */ private function sendAnswer( CustomFormAnswer $answer, - array $assignation + array $assignation, + $receiver ): void { - $defaultSender = $this->settingsBag->get('email_sender'); - $defaultSender = filter_var($defaultSender, FILTER_VALIDATE_EMAIL) ? $defaultSender : 'sender@roadiz.io'; - $receivers = $this->getCustomFormReceivers($answer); - - $realSender = filter_var($answer->getEmail(), FILTER_VALIDATE_EMAIL) ? $answer->getEmail() : $defaultSender; $emailManager = $this->emailManagerFactory->create(); + $defaultSender = $this->settingsBag->get('email_sender'); + $defaultSender = !empty($defaultSender) ? $defaultSender : 'sender@roadiz.io'; $emailManager->setAssignation($assignation); $emailManager->setEmailTemplate('@RoadizCore/email/forms/answerForm.html.twig'); $emailManager->setEmailPlainTextTemplate('@RoadizCore/email/forms/answerForm.txt.twig'); $emailManager->setSubject($assignation['title']); $emailManager->setEmailTitle($assignation['title']); - $emailManager->setSender($realSender); + $emailManager->setSender($defaultSender); try { foreach ($answer->getAnswerFields() as $customFormAnswerAttr) { @@ -112,30 +105,21 @@ private function sendAnswer( $document->getFilename(), $this->documentsStorage->mimeType($document->getMountPath()) ); - $this->messengerLogger->debug(sprintf( - 'Joining document %s to email.', - $document->getFilename() - )); } } } catch (FilesystemException $exception) { - $this->messengerLogger->error($exception->getMessage(), [ + $this->logger->error($exception->getMessage(), [ 'entity' => $answer ]); } - if (empty($receivers)) { + if (empty($receiver)) { $emailManager->setReceiver($defaultSender); } else { - $emailManager->setReceiver($receivers); + $emailManager->setReceiver($receiver); } // Send the message $emailManager->send(); - $this->messengerLogger->debug(sprintf( - 'CustomForm (%s) answer sent to %s', - $answer->getCustomForm()->getName(), - $realSender - )); } } diff --git a/src/DataCollector/RequestDataCollector.php b/src/DataCollector/RequestDataCollector.php index 38ed74f9..8c2c84bd 100644 --- a/src/DataCollector/RequestDataCollector.php +++ b/src/DataCollector/RequestDataCollector.php @@ -4,16 +4,20 @@ namespace RZ\Roadiz\CoreBundle\DataCollector; +use PackageVersions\Versions; use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; final class RequestDataCollector extends AbstractDataCollector { - public function __construct( - private readonly string $cmsVersion, - private readonly string $cmsVersionPrefix - ) { + private ?string $cmsVersion = null; + private ?string $cmsVersionPrefix = null; + + public function __construct(string $cmsVersion, string $cmsVersionPrefix) + { + $this->cmsVersion = $cmsVersion; + $this->cmsVersionPrefix = $cmsVersionPrefix; } /** @@ -21,14 +25,20 @@ public function __construct( */ public function collect(Request $request, Response $response, \Throwable $exception = null): void { - $this->data = [ - 'version' => implode(' - ', array_filter([$this->cmsVersionPrefix, $this->cmsVersion])), - ]; + $this->data = []; } - public function getVersion(): string + public function getVersion(): ?string { - return $this->data['version']; + $fallback = implode(' - ', array_filter([$this->cmsVersionPrefix, $this->cmsVersion])); + if (!class_exists(Versions::class)) { + return $fallback; + } + + $version = Versions::getVersion('roadiz/core-bundle'); + preg_match('/^v(.*?)@/', $version, $output); + + return $output[1] ?? strtok($version, '@') ?: $fallback; } public static function getTemplate(): ?string 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/Event/QueryNodesSourcesEvent.php b/src/Doctrine/Event/QueryNodesSourcesEvent.php index 07999c22..03d2c385 100644 --- a/src/Doctrine/Event/QueryNodesSourcesEvent.php +++ b/src/Doctrine/Event/QueryNodesSourcesEvent.php @@ -9,13 +9,19 @@ final class QueryNodesSourcesEvent extends QueryEvent { + /** + * @var class-string + */ + protected string $actualEntityName; + /** * @param Query $query * @param class-string $actualEntityName */ - public function __construct(Query $query, private readonly string $actualEntityName) + public function __construct(Query $query, string $actualEntityName) { parent::__construct($query, NodesSources::class); + $this->actualEntityName = $actualEntityName; } /** diff --git a/src/Doctrine/EventSubscriber/CustomFormFieldLifeCycleSubscriber.php b/src/Doctrine/EventSubscriber/CustomFormFieldLifeCycleSubscriber.php index 37b42b22..bb8122e1 100644 --- a/src/Doctrine/EventSubscriber/CustomFormFieldLifeCycleSubscriber.php +++ b/src/Doctrine/EventSubscriber/CustomFormFieldLifeCycleSubscriber.php @@ -12,8 +12,14 @@ final class CustomFormFieldLifeCycleSubscriber implements EventSubscriber { - public function __construct(private readonly CustomFormFieldHandler $customFormFieldHandler) + private CustomFormFieldHandler $customFormFieldHandler; + + /** + * @param CustomFormFieldHandler $customFormFieldHandler + */ + public function __construct(CustomFormFieldHandler $customFormFieldHandler) { + $this->customFormFieldHandler = $customFormFieldHandler; } /** diff --git a/src/Doctrine/EventSubscriber/NodesSourcesInheritanceSubscriber.php b/src/Doctrine/EventSubscriber/NodesSourcesInheritanceSubscriber.php index 0f1b7c4b..b0ee94d4 100644 --- a/src/Doctrine/EventSubscriber/NodesSourcesInheritanceSubscriber.php +++ b/src/Doctrine/EventSubscriber/NodesSourcesInheritanceSubscriber.php @@ -9,7 +9,6 @@ use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\ORM\Events; use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Psr\Log\LoggerInterface; use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\CoreBundle\Bag\NodeTypes; use RZ\Roadiz\CoreBundle\DependencyInjection\Configuration; @@ -18,11 +17,17 @@ final class NodesSourcesInheritanceSubscriber implements EventSubscriber { - public function __construct( - private readonly NodeTypes $nodeTypes, - private readonly string $inheritanceType, - private readonly LoggerInterface $logger - ) { + private NodeTypes $nodeTypes; + private string $inheritanceType; + + /** + * @param NodeTypes $nodeTypes + * @param string $inheritanceType + */ + public function __construct(NodeTypes $nodeTypes, string $inheritanceType) + { + $this->nodeTypes = $nodeTypes; + $this->inheritanceType = $inheritanceType; } /** @@ -61,15 +66,7 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void $nodeTypes = $this->nodeTypes->all(); $map = []; foreach ($nodeTypes as $type) { - if (\class_exists($type->getSourceEntityFullQualifiedClassName())) { - $map[\mb_strtolower($type->getName())] = $type->getSourceEntityFullQualifiedClassName(); - } else { - $this->logger->critical(sprintf( - '"%s" node-type is registered in database but source entity class "%s" does not exist.', - $type->getName(), - $type->getSourceEntityFullQualifiedClassName() - )); - } + $map[\mb_strtolower($type->getName())] = $type->getSourceEntityFullQualifiedClassName(); } $metadata->setDiscriminatorMap($map); 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/TablePrefixSubscriber.php b/src/Doctrine/EventSubscriber/TablePrefixSubscriber.php new file mode 100644 index 00000000..e01c4c69 --- /dev/null +++ b/src/Doctrine/EventSubscriber/TablePrefixSubscriber.php @@ -0,0 +1,61 @@ +tablesPrefix = $tablesPrefix; + } + + + /** + * @inheritDoc + */ + public function getSubscribedEvents(): array + { + return [ + Events::loadClassMetadata, + ]; + } + + /** + * @param LoadClassMetadataEventArgs $eventArgs + */ + public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void + { + /* + * Prefix tables + */ + if (!empty($this->tablesPrefix) && $this->tablesPrefix !== '') { + // the $metadata is all the mapping info for this class + $metadata = $eventArgs->getClassMetadata(); + $metadata->table['name'] = $this->tablesPrefix . '_' . $metadata->table['name']; + + /* + * Prefix join tables + */ + foreach ($metadata->associationMappings as $key => $association) { + if (!empty($association['joinTable']['name'])) { + $metadata->associationMappings[$key]['joinTable']['name'] = + $this->tablesPrefix . '_' . $association['joinTable']['name']; + } + } + } + } +} 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/Filter/NodesSourcesReachableFilter.php b/src/Doctrine/ORM/Filter/NodesSourcesReachableFilter.php index 6f524f5c..03528da7 100644 --- a/src/Doctrine/ORM/Filter/NodesSourcesReachableFilter.php +++ b/src/Doctrine/ORM/Filter/NodesSourcesReachableFilter.php @@ -18,13 +18,19 @@ */ final class NodesSourcesReachableFilter implements EventSubscriberInterface { + private NodeTypes $nodeTypesBag; + public const PARAMETER = [ 'node.nodeType.reachable', 'reachable' ]; - public function __construct(private readonly NodeTypes $nodeTypesBag) + /** + * @param NodeTypes $nodeTypesBag + */ + public function __construct(NodeTypes $nodeTypesBag) { + $this->nodeTypesBag = $nodeTypesBag; } public static function getSubscribedEvents(): array 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/Doctrine/SchemaUpdater.php b/src/Doctrine/SchemaUpdater.php index bd1ccdeb..a176b299 100644 --- a/src/Doctrine/SchemaUpdater.php +++ b/src/Doctrine/SchemaUpdater.php @@ -11,12 +11,21 @@ final class SchemaUpdater { + private LoggerInterface $logger; + private OPCacheClearer $opCacheClearer; + private string $projectDir; + private CacheClearerInterface $cacheClearer; + public function __construct( - private readonly CacheClearerInterface $cacheClearer, - private readonly OPCacheClearer $opCacheClearer, - private readonly LoggerInterface $logger, - private readonly string $projectDir + CacheClearerInterface $cacheClearer, + OPCacheClearer $opCacheClearer, + LoggerInterface $logger, + string $projectDir ) { + $this->logger = $logger; + $this->opCacheClearer = $opCacheClearer; + $this->projectDir = $projectDir; + $this->cacheClearer = $cacheClearer; } public function clearMetadata(): void diff --git a/src/Document/DocumentFactory.php b/src/Document/DocumentFactory.php index 91f6f612..5f088b40 100644 --- a/src/Document/DocumentFactory.php +++ b/src/Document/DocumentFactory.php @@ -19,13 +19,16 @@ */ final class DocumentFactory extends AbstractDocumentFactory { + private ManagerRegistry $managerRegistry; + public function __construct( - private readonly ManagerRegistry $managerRegistry, + ManagerRegistry $managerRegistry, FilesystemOperator $documentsStorage, DocumentFinderInterface $documentFinder, ?LoggerInterface $logger = null ) { parent::__construct($documentsStorage, $documentFinder, $logger); + $this->managerRegistry = $managerRegistry; } protected function persistDocument(DocumentInterface $document): void diff --git a/src/Document/DocumentFinder.php b/src/Document/DocumentFinder.php index 7de00596..1978621d 100644 --- a/src/Document/DocumentFinder.php +++ b/src/Document/DocumentFinder.php @@ -12,8 +12,14 @@ final class DocumentFinder extends AbstractDocumentFinder { - public function __construct(private readonly ManagerRegistry $managerRegistry) + private ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) { + $this->managerRegistry = $managerRegistry; } /** diff --git a/src/Document/EventSubscriber/DocumentMessageDispatchSubscriber.php b/src/Document/EventSubscriber/DocumentMessageDispatchSubscriber.php index cf6a6844..8dfb1350 100644 --- a/src/Document/EventSubscriber/DocumentMessageDispatchSubscriber.php +++ b/src/Document/EventSubscriber/DocumentMessageDispatchSubscriber.php @@ -22,8 +22,14 @@ final class DocumentMessageDispatchSubscriber implements EventSubscriberInterface { - public function __construct(private readonly MessageBusInterface $bus) + private MessageBusInterface $bus; + + /** + * @param MessageBusInterface $bus + */ + public function __construct(MessageBusInterface $bus) { + $this->bus = $bus; } /** diff --git a/src/Document/MessageHandler/AbstractDocumentMessageHandler.php b/src/Document/MessageHandler/AbstractDocumentMessageHandler.php index e9747f8b..92f3d2ec 100644 --- a/src/Document/MessageHandler/AbstractDocumentMessageHandler.php +++ b/src/Document/MessageHandler/AbstractDocumentMessageHandler.php @@ -15,11 +15,23 @@ abstract class AbstractDocumentMessageHandler implements MessageHandlerInterface { + protected ManagerRegistry $managerRegistry; + protected LoggerInterface $logger; + protected FilesystemOperator $documentsStorage; + + /** + * @param ManagerRegistry $managerRegistry + * @param LoggerInterface $messengerLogger + * @param FilesystemOperator $documentsStorage + */ public function __construct( - protected readonly ManagerRegistry $managerRegistry, - protected readonly LoggerInterface $messengerLogger, - protected readonly FilesystemOperator $documentsStorage + ManagerRegistry $managerRegistry, + LoggerInterface $messengerLogger, + FilesystemOperator $documentsStorage ) { + $this->managerRegistry = $managerRegistry; + $this->logger = $messengerLogger; + $this->documentsStorage = $documentsStorage; } abstract protected function supports(DocumentInterface $document): bool; diff --git a/src/Document/MessageHandler/DocumentAudioVideoMessageHandler.php b/src/Document/MessageHandler/DocumentAudioVideoMessageHandler.php index 056f2f8b..92830140 100644 --- a/src/Document/MessageHandler/DocumentAudioVideoMessageHandler.php +++ b/src/Document/MessageHandler/DocumentAudioVideoMessageHandler.php @@ -29,15 +29,22 @@ */ final class DocumentAudioVideoMessageHandler extends AbstractLockingDocumentMessageHandler { + private DocumentFactory $documentFactory; + private EventDispatcherInterface $eventDispatcher; + private ?string $ffmpegPath; + public function __construct( - private readonly DocumentFactory $documentFactory, - private readonly EventDispatcherInterface $eventDispatcher, - private readonly ?string $ffmpegPath, + DocumentFactory $documentFactory, + EventDispatcherInterface $eventDispatcher, + ?string $ffmpegPath, ManagerRegistry $managerRegistry, LoggerInterface $messengerLogger, FilesystemOperator $documentsStorage ) { parent::__construct($managerRegistry, $messengerLogger, $documentsStorage); + $this->ffmpegPath = $ffmpegPath; + $this->documentFactory = $documentFactory; + $this->eventDispatcher = $eventDispatcher; } /** diff --git a/src/Document/MessageHandler/DocumentAverageColorMessageHandler.php b/src/Document/MessageHandler/DocumentAverageColorMessageHandler.php index 6c251078..87183926 100644 --- a/src/Document/MessageHandler/DocumentAverageColorMessageHandler.php +++ b/src/Document/MessageHandler/DocumentAverageColorMessageHandler.php @@ -16,13 +16,16 @@ final class DocumentAverageColorMessageHandler extends AbstractLockingDocumentMessageHandler { + private ImageManager $imageManager; + public function __construct( ManagerRegistry $managerRegistry, LoggerInterface $messengerLogger, FilesystemOperator $documentsStorage, - private readonly ImageManager $imageManager + ImageManager $imageManager ) { parent::__construct($managerRegistry, $messengerLogger, $documentsStorage); + $this->imageManager = $imageManager; } /** @@ -50,7 +53,7 @@ protected function processMessage(AbstractDocumentMessage $message, DocumentInte $mediumColor = (new AverageColorResolver())->getAverageColor($this->imageManager->make($documentStream)); $document->setImageAverageColor($mediumColor); } catch (NotReadableException $exception) { - $this->messengerLogger->warning( + $this->logger->warning( 'Document file is not a readable image.', [ 'path' => $document->getMountPath(), diff --git a/src/Document/MessageHandler/DocumentExifMessageHandler.php b/src/Document/MessageHandler/DocumentExifMessageHandler.php index 5983a7b3..6aed4af5 100644 --- a/src/Document/MessageHandler/DocumentExifMessageHandler.php +++ b/src/Document/MessageHandler/DocumentExifMessageHandler.php @@ -50,7 +50,7 @@ function_exists('exif_read_data') && $description = $this->getDescription($exif); if (null !== $copyright || null !== $description) { - $this->messengerLogger->debug( + $this->logger->debug( 'EXIF information available for document.', [ 'document' => (string)$document diff --git a/src/Document/MessageHandler/DocumentFilesizeMessageHandler.php b/src/Document/MessageHandler/DocumentFilesizeMessageHandler.php index 14600d04..eda96d3f 100644 --- a/src/Document/MessageHandler/DocumentFilesizeMessageHandler.php +++ b/src/Document/MessageHandler/DocumentFilesizeMessageHandler.php @@ -28,7 +28,7 @@ protected function processMessage(AbstractDocumentMessage $message, DocumentInte try { $document->setFilesize($this->documentsStorage->fileSize($document->getMountPath())); } catch (FilesystemException $exception) { - $this->messengerLogger->warning($exception->getMessage()); + $this->logger->warning($exception->getMessage()); } } } diff --git a/src/Document/MessageHandler/DocumentPdfMessageHandler.php b/src/Document/MessageHandler/DocumentPdfMessageHandler.php index 2a6453ff..292de562 100644 --- a/src/Document/MessageHandler/DocumentPdfMessageHandler.php +++ b/src/Document/MessageHandler/DocumentPdfMessageHandler.php @@ -18,14 +18,19 @@ final class DocumentPdfMessageHandler extends AbstractLockingDocumentMessageHandler { + private DocumentFactory $documentFactory; + private EventDispatcherInterface $eventDispatcher; + public function __construct( - private readonly DocumentFactory $documentFactory, - private readonly EventDispatcherInterface $eventDispatcher, + DocumentFactory $documentFactory, + EventDispatcherInterface $eventDispatcher, ManagerRegistry $managerRegistry, LoggerInterface $messengerLogger, FilesystemOperator $documentsStorage ) { parent::__construct($managerRegistry, $messengerLogger, $documentsStorage); + $this->documentFactory = $documentFactory; + $this->eventDispatcher = $eventDispatcher; } /** @@ -100,13 +105,12 @@ protected function extractPdfThumbnail(DocumentInterface $document, string $loca } } } catch (\ImagickException $exception) { - // Silent fail to avoid issue with message handling - $this->messengerLogger->warning( + throw new UnrecoverableMessageHandlingException( sprintf( 'Cannot extract thumbnail from %s PDF file : %s', $localPdfPath, $exception->getMessage() - ) + ), ); } } diff --git a/src/Document/MessageHandler/DocumentRawMessageHandler.php b/src/Document/MessageHandler/DocumentRawMessageHandler.php index dc2c1588..8d241f4f 100644 --- a/src/Document/MessageHandler/DocumentRawMessageHandler.php +++ b/src/Document/MessageHandler/DocumentRawMessageHandler.php @@ -13,13 +13,16 @@ final class DocumentRawMessageHandler extends AbstractLockingDocumentMessageHandler { + private DownscaleImageManager $downscaleImageManager; + public function __construct( - private readonly DownscaleImageManager $downscaleImageManager, + DownscaleImageManager $downscaleImageManager, ManagerRegistry $managerRegistry, LoggerInterface $logger, FilesystemOperator $documentsStorage ) { parent::__construct($managerRegistry, $logger, $documentsStorage); + $this->downscaleImageManager = $downscaleImageManager; } /** diff --git a/src/Document/MessageHandler/DocumentSizeMessageHandler.php b/src/Document/MessageHandler/DocumentSizeMessageHandler.php index b337463f..a64659c0 100644 --- a/src/Document/MessageHandler/DocumentSizeMessageHandler.php +++ b/src/Document/MessageHandler/DocumentSizeMessageHandler.php @@ -15,13 +15,16 @@ final class DocumentSizeMessageHandler extends AbstractLockingDocumentMessageHandler { + private ImageManager $imageManager; + public function __construct( ManagerRegistry $managerRegistry, LoggerInterface $messengerLogger, FilesystemOperator $documentsStorage, - private readonly ImageManager $imageManager + ImageManager $imageManager ) { parent::__construct($managerRegistry, $messengerLogger, $documentsStorage); + $this->imageManager = $imageManager; } /** @@ -43,7 +46,7 @@ protected function processMessage(AbstractDocumentMessage $message, DocumentInte $document->setImageWidth($imageProcess->width()); $document->setImageHeight($imageProcess->height()); } catch (NotReadableException $exception) { - $this->messengerLogger->warning( + $this->logger->warning( 'Document file is not a readable image.', [ 'path' => $document->getMountPath(), diff --git a/src/Document/MessageHandler/DocumentSvgMessageHandler.php b/src/Document/MessageHandler/DocumentSvgMessageHandler.php index b833a3d3..f49d9376 100644 --- a/src/Document/MessageHandler/DocumentSvgMessageHandler.php +++ b/src/Document/MessageHandler/DocumentSvgMessageHandler.php @@ -38,7 +38,7 @@ protected function processMessage(AbstractDocumentMessage $message, DocumentInte // Load the dirty svg $dirtySVG = $this->documentsStorage->read($document->getMountPath()); $this->documentsStorage->write($document->getMountPath(), $sanitizer->sanitize($dirtySVG)); - $this->messengerLogger->info('Svg document sanitized.'); + $this->logger->info('Svg document sanitized.'); /* * Resolve SVG size @@ -48,7 +48,7 @@ protected function processMessage(AbstractDocumentMessage $message, DocumentInte $document->setImageWidth($svgSizeResolver->getWidth()); $document->setImageHeight($svgSizeResolver->getHeight()); } catch (\RuntimeException $exception) { - $this->messengerLogger->error($exception->getMessage()); + $this->logger->error($exception->getMessage()); } } } 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 b4f9be4e..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,13 +339,14 @@ public function removeField(CustomFormField $field): CustomForm { if ($this->getFields()->contains($field)) { $this->getFields()->removeElement($field); + $field->setCustomForm(null); } return $this; } /** - * @return Collection + * @return Collection */ public function getCustomFormAnswers(): Collection { diff --git a/src/Entity/CustomFormAnswer.php b/src/Entity/CustomFormAnswer.php index 3199d4d1..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() { @@ -170,6 +169,7 @@ public function setSubmittedAt(\DateTime $submittedAt): CustomFormAnswer /** * @return string|null + * @throws \Exception */ public function getEmail(): ?string { diff --git a/src/Entity/CustomFormField.php b/src/Entity/CustomFormField.php index b58a6f29..f7b90f28 100644 --- a/src/Entity/CustomFormField.php +++ b/src/Entity/CustomFormField.php @@ -8,18 +8,16 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use JMS\Serializer\Annotation as Serializer; -use RZ\Roadiz\CoreBundle\Repository\CustomFormFieldRepository; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation as SymfonySerializer; use RZ\Roadiz\Core\AbstractEntities\AbstractField; -use Symfony\Component\Validator\Constraints\Choice; /** * CustomFormField entities are used to create CustomForms with * custom data structure. */ #[ - ORM\Entity(repositoryClass: CustomFormFieldRepository::class), + ORM\Entity(repositoryClass: "RZ\Roadiz\CoreBundle\Repository\CustomFormFieldRepository"), ORM\Table(name: "custom_form_fields"), ORM\UniqueConstraint(columns: ["name", "custom_form_id"]), ORM\Index(columns: ["position"]), @@ -52,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 @@ -75,43 +73,6 @@ class CustomFormField extends AbstractField ] private bool $required = false; - /** - * @var string|null https://developer.mozilla.org/fr/docs/Web/HTML/Attributes/autocomplete - */ - #[ - ORM\Column(name: "autocomplete", type: 'string', length:18, nullable: true), - Serializer\Groups(["custom_form"]), - SymfonySerializer\Groups(["custom_form"]), - Choice([ - 'off', - 'name', - 'honorific-prefix', - 'honorific-suffix', - 'given-name', - 'additional-name', - 'family-name', - 'nickname', - 'email', - 'username', - 'organization-title', - 'organization', - 'street-address', - 'country', - 'country-name', - 'postal-code', - 'bday', - 'bday-day', - 'bday-month', - 'bday-year', - 'sex', - 'tel', - 'tel-national', - 'url', - 'photo', - ]) - ] - private ?string $autocomplete = null; - public function __construct() { parent::__construct(); @@ -123,7 +84,7 @@ public function __construct() * * @return $this */ - public function setLabel($label): CustomFormField + public function setLabel($label) { parent::setLabel($label); $this->setName($label); @@ -131,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; } @@ -171,17 +142,6 @@ public function setRequired(bool $required): CustomFormField return $this; } - public function getAutocomplete(): ?string - { - return $this->autocomplete; - } - - public function setAutocomplete(?string $autocomplete): CustomFormField - { - $this->autocomplete = $autocomplete; - return $this; - } - /** * @return string */ @@ -202,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 b8452226..63c441d1 100644 --- a/src/Entity/CustomFormFieldAttribute.php +++ b/src/Entity/CustomFormFieldAttribute.php @@ -24,21 +24,21 @@ 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 */ #[ - ORM\ManyToMany(targetEntity: Document::class, inversedBy: "customFormFieldAttributes"), + ORM\ManyToMany(targetEntity: "RZ\Roadiz\CoreBundle\Entity\Document", inversedBy: "customFormFieldAttributes"), ORM\JoinTable(name: "custom_form_answers_documents"), ORM\JoinColumn(name: "customformfieldattribute_id", onDelete: "CASCADE"), ORM\InverseJoinColumn(name: "document_id", onDelete: "CASCADE") @@ -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; @@ -132,6 +153,7 @@ public function getDocuments(): Collection public function setDocuments(Collection $documents): CustomFormFieldAttribute { $this->documents = $documents; + return $this; } } diff --git a/src/Entity/Document.php b/src/Entity/Document.php index e81e5633..ae026cd1 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. @@ -662,10 +627,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 c583b4e9..197b95b6 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/NodesTags.php b/src/Entity/NodesTags.php index e2e82b67..c2de4697 100644 --- a/src/Entity/NodesTags.php +++ b/src/Entity/NodesTags.php @@ -9,11 +9,10 @@ use JMS\Serializer\Annotation as Serializer; use RZ\Roadiz\Core\AbstractEntities\PositionedInterface; use RZ\Roadiz\Core\AbstractEntities\PositionedTrait; -use RZ\Roadiz\CoreBundle\Repository\NodesTagsRepository; use Symfony\Component\Serializer\Annotation as SymfonySerializer; #[ - ORM\Entity(repositoryClass: NodesTagsRepository::class), + ORM\Entity, ORM\Table(name: "nodes_tags"), ORM\Index(columns: ['node_id', 'position'], name: 'nodes_tags_node_id_position'), ORM\Index(columns: ['tag_id', 'position'], name: 'nodes_tags_tag_id_position'), 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 e8503dea..c4e34ec8 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; /** @@ -510,17 +476,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 dfa24595..368bb8fd 100644 --- a/src/EntityHandler/CustomFormFieldHandler.php +++ b/src/EntityHandler/CustomFormFieldHandler.php @@ -11,31 +11,40 @@ /** * Handle operations with customForms fields entities. */ -final class CustomFormFieldHandler extends AbstractHandler +class CustomFormFieldHandler extends AbstractHandler { private ?CustomFormField $customFormField = null; + private CustomFormHandler $customFormHandler; /** - * @param ObjectManager $objectManager - * @param CustomFormHandler $customFormHandler + * @return CustomFormField */ - public function __construct( - ObjectManager $objectManager, - private readonly CustomFormHandler $customFormHandler - ) { - parent::__construct($objectManager); + 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, CustomFormHandler $customFormHandler) + { + parent::__construct($objectManager); + $this->customFormHandler = $customFormHandler; + } + /** * Clean position for current customForm siblings. * @@ -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 355d583a..b99e28c2 100644 --- a/src/EntityHandler/TagHandler.php +++ b/src/EntityHandler/TagHandler.php @@ -5,21 +5,34 @@ namespace RZ\Roadiz\CoreBundle\EntityHandler; use Doctrine\Common\Collections\Criteria; -use RZ\Roadiz\Core\Handlers\AbstractHandler; +use Doctrine\ORM\NoResultException; +use Doctrine\ORM\Query; use RZ\Roadiz\CoreBundle\Entity\Tag; +use RZ\Roadiz\Core\Handlers\AbstractHandler; /** * 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; @@ -30,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) { @@ -46,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); @@ -60,7 +73,7 @@ public function removeAssociations(): self * * @return $this */ - public function removeWithChildrenAndAssociations(): self + public function removeWithChildrenAndAssociations() { $this->removeChildren(); $this->removeAssociations(); @@ -75,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/Document/DocumentTranslationIndexingEvent.php b/src/Event/Document/DocumentTranslationIndexingEvent.php index 1130fb58..6417e77c 100644 --- a/src/Event/Document/DocumentTranslationIndexingEvent.php +++ b/src/Event/Document/DocumentTranslationIndexingEvent.php @@ -10,12 +10,27 @@ final class DocumentTranslationIndexingEvent extends Event { + protected DocumentTranslation $documentTranslation; + protected array $associations; + protected AbstractSolarium $solariumDocument; + protected bool $subResource; + + /** + * @param DocumentTranslation $documentTranslation + * @param array $associations + * @param AbstractSolarium $solariumDocument + * @param bool $subResource + */ public function __construct( - private readonly DocumentTranslation $documentTranslation, - private array $associations, - private readonly AbstractSolarium $solariumDocument, - private readonly bool $subResource = false + DocumentTranslation $documentTranslation, + array $associations, + AbstractSolarium $solariumDocument, + bool $subResource = false ) { + $this->documentTranslation = $documentTranslation; + $this->associations = $associations; + $this->solariumDocument = $solariumDocument; + $this->subResource = $subResource; } /** @@ -59,4 +74,14 @@ public function setAssociations(array $associations): DocumentTranslationIndexin $this->associations = $associations; return $this; } + + /** + * @param AbstractSolarium $solariumDocument + * @return DocumentTranslationIndexingEvent + */ + public function setSolariumDocument(AbstractSolarium $solariumDocument): DocumentTranslationIndexingEvent + { + $this->solariumDocument = $solariumDocument; + return $this; + } } diff --git a/src/Event/Document/DocumentTranslationUpdatedEvent.php b/src/Event/Document/DocumentTranslationUpdatedEvent.php index bf889883..8abf8b0e 100644 --- a/src/Event/Document/DocumentTranslationUpdatedEvent.php +++ b/src/Event/Document/DocumentTranslationUpdatedEvent.php @@ -10,11 +10,12 @@ final class DocumentTranslationUpdatedEvent extends FilterDocumentEvent { - public function __construct( - DocumentInterface $document, - private readonly ?DocumentTranslation $documentTranslation = null - ) { + protected ?DocumentTranslation $documentTranslation; + + public function __construct(DocumentInterface $document, ?DocumentTranslation $documentTranslation = null) + { parent::__construct($document); + $this->documentTranslation = $documentTranslation; } /** diff --git a/src/Event/NodesSources/NodesSourcesIndexingEvent.php b/src/Event/NodesSources/NodesSourcesIndexingEvent.php index 9e8381e0..aea95c47 100644 --- a/src/Event/NodesSources/NodesSourcesIndexingEvent.php +++ b/src/Event/NodesSources/NodesSourcesIndexingEvent.php @@ -10,12 +10,27 @@ final class NodesSourcesIndexingEvent extends Event { + protected NodesSources $nodeSource; + protected array $associations; + protected AbstractSolarium $solariumDocument; + protected bool $subResource; + + /** + * @param NodesSources $nodeSource + * @param array $associations + * @param AbstractSolarium $solariumDocument + * @param bool $subResource + */ public function __construct( - private readonly NodesSources $nodeSource, - private array $associations, - private readonly AbstractSolarium $solariumDocument, - private readonly bool $subResource = false + NodesSources $nodeSource, + array $associations, + AbstractSolarium $solariumDocument, + bool $subResource = false ) { + $this->nodeSource = $nodeSource; + $this->associations = $associations; + $this->solariumDocument = $solariumDocument; + $this->subResource = $subResource; } public function getNodeSource(): NodesSources @@ -45,11 +60,29 @@ public function setAssociations(array $associations): NodesSourcesIndexingEvent return $this; } + /** + * @return AbstractSolarium + */ public function getSolariumDocument(): AbstractSolarium { return $this->solariumDocument; } + /** + * @param AbstractSolarium $solariumDocument + * + * @return NodesSourcesIndexingEvent + */ + public function setSolariumDocument(AbstractSolarium $solariumDocument): NodesSourcesIndexingEvent + { + $this->solariumDocument = $solariumDocument; + + return $this; + } + + /** + * @return bool + */ public function isSubResource(): bool { return $this->subResource; 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/Event/User/UserJoinedGroupEvent.php b/src/Event/User/UserJoinedGroupEvent.php index 77c1ef3b..9f6393ff 100644 --- a/src/Event/User/UserJoinedGroupEvent.php +++ b/src/Event/User/UserJoinedGroupEvent.php @@ -10,9 +10,12 @@ final class UserJoinedGroupEvent extends FilterUserEvent { - public function __construct(User $user, private readonly Group $group) + private Group $group; + + public function __construct(User $user, Group $group) { parent::__construct($user); + $this->group = $group; } /** diff --git a/src/Event/User/UserLeavedGroupEvent.php b/src/Event/User/UserLeavedGroupEvent.php index e776a374..aa250160 100644 --- a/src/Event/User/UserLeavedGroupEvent.php +++ b/src/Event/User/UserLeavedGroupEvent.php @@ -10,9 +10,12 @@ final class UserLeavedGroupEvent extends FilterUserEvent { - public function __construct(User $user, private readonly Group $group) + private Group $group; + + public function __construct(User $user, Group $group) { parent::__construct($user); + $this->group = $group; } /** 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 6f9288a9..17517016 100644 --- a/src/EventSubscriber/NodeNameSubscriber.php +++ b/src/EventSubscriber/NodeNameSubscriber.php @@ -5,6 +5,7 @@ namespace RZ\Roadiz\CoreBundle\EventSubscriber; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use RZ\Roadiz\CoreBundle\Event\Node\NodePathChangedEvent; use RZ\Roadiz\CoreBundle\Event\Node\NodeUpdatedEvent; use RZ\Roadiz\CoreBundle\Event\NodesSources\NodesSourcesPreUpdatedEvent; @@ -19,11 +20,20 @@ */ final class NodeNameSubscriber implements EventSubscriberInterface { - public function __construct( - private readonly LoggerInterface $logger, - private readonly NodeNamePolicyInterface $nodeNamePolicy, - private readonly NodeMover $nodeMover - ) { + private NodeMover $nodeMover; + private LoggerInterface $logger; + private NodeNamePolicyInterface $nodeNamePolicy; + + /** + * @param LoggerInterface|null $logger + * @param NodeNamePolicyInterface $nodeNamePolicy + * @param NodeMover $nodeMover + */ + public function __construct(?LoggerInterface $logger, NodeNamePolicyInterface $nodeNamePolicy, NodeMover $nodeMover) + { + $this->logger = $logger ?? new NullLogger(); + $this->nodeNamePolicy = $nodeNamePolicy; + $this->nodeMover = $nodeMover; } /** @@ -33,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 d6d9ccae..771eea51 100644 --- a/src/EventSubscriber/NodesSourcesUrlsCacheEventSubscriber.php +++ b/src/EventSubscriber/NodesSourcesUrlsCacheEventSubscriber.php @@ -20,8 +20,14 @@ final class NodesSourcesUrlsCacheEventSubscriber implements EventSubscriberInterface { - public function __construct(private readonly NodesSourcesUrlsCacheClearer $cacheClearer) + private NodesSourcesUrlsCacheClearer $cacheClearer; + + /** + * @param NodesSourcesUrlsCacheClearer $cacheClearer + */ + public function __construct(NodesSourcesUrlsCacheClearer $cacheClearer) { + $this->cacheClearer = $cacheClearer; } /** @@ -31,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 d41b9d7e..b18da0fa 100644 --- a/src/EventSubscriber/RealmNodeInheritanceSubscriber.php +++ b/src/EventSubscriber/RealmNodeInheritanceSubscriber.php @@ -19,8 +19,14 @@ final class RealmNodeInheritanceSubscriber implements EventSubscriberInterface { - public function __construct(private readonly MessageBusInterface $bus) + private MessageBusInterface $bus; + + /** + * @param MessageBusInterface $bus + */ + public function __construct(MessageBusInterface $bus) { + $this->bus = $bus; } /** @@ -53,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() ))); } @@ -64,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 4285e7f4..b21cb81e 100644 --- a/src/EventSubscriber/ReverseProxyCacheEventSubscriber.php +++ b/src/EventSubscriber/ReverseProxyCacheEventSubscriber.php @@ -21,11 +21,23 @@ final class ReverseProxyCacheEventSubscriber implements EventSubscriberInterface { + private ReverseProxyCacheLocator $reverseProxyCacheLocator; + private LoggerInterface $logger; + private MessageBusInterface $bus; + + /** + * @param ReverseProxyCacheLocator $reverseProxyCacheLocator + * @param MessageBusInterface $bus + * @param LoggerInterface $logger + */ public function __construct( - private readonly ReverseProxyCacheLocator $reverseProxyCacheLocator, - private readonly MessageBusInterface $bus, - private readonly LoggerInterface $logger + ReverseProxyCacheLocator $reverseProxyCacheLocator, + MessageBusInterface $bus, + LoggerInterface $logger ) { + $this->logger = $logger; + $this->bus = $bus; + $this->reverseProxyCacheLocator = $reverseProxyCacheLocator; } /** * @inheritDoc @@ -34,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/Filesystem/RoadizFileDirectories.php b/src/Filesystem/RoadizFileDirectories.php index 8a475cb9..691e2f35 100644 --- a/src/Filesystem/RoadizFileDirectories.php +++ b/src/Filesystem/RoadizFileDirectories.php @@ -8,8 +8,14 @@ final class RoadizFileDirectories implements FileAwareInterface { - public function __construct(private readonly string $projectDir) + private string $projectDir; + + /** + * @param string $projectDir + */ + public function __construct(string $projectDir) { + $this->projectDir = $projectDir; } public function getPublicFilesPath(): string diff --git a/src/Form/AttributeChoiceType.php b/src/Form/AttributeChoiceType.php index 1a385c3e..5c421243 100644 --- a/src/Form/AttributeChoiceType.php +++ b/src/Form/AttributeChoiceType.php @@ -16,7 +16,7 @@ final class AttributeChoiceType extends AbstractType { - public function __construct(private readonly ManagerRegistry $managerRegistry) + public function __construct(private ManagerRegistry $managerRegistry) { } diff --git a/src/Form/CustomFormsType.php b/src/Form/CustomFormsType.php index 059f5e28..5cd0711c 100644 --- a/src/Form/CustomFormsType.php +++ b/src/Form/CustomFormsType.php @@ -173,14 +173,10 @@ protected function getOptionsForField(CustomFormField $field, array $formOptions ], ]; - if (!empty($field->getPlaceholder())) { + if ($field->getPlaceholder() !== '') { $option['attr']['placeholder'] = $field->getPlaceholder(); } - if ($field->getAutocomplete() !== null) { - $option['attr']['autocomplete'] = $field->getAutocomplete(); - } - if ($field->isRequired()) { $option['required'] = true; $option['constraints'] = [ @@ -202,7 +198,7 @@ protected function getOptionsForField(CustomFormField $field, array $formOptions $option["format"] = DateType::HTML5_FORMAT; break; case AbstractField::ENUM_T: - if (!empty($field->getPlaceholder())) { + if ($field->getPlaceholder() !== '') { $option['placeholder'] = $field->getPlaceholder(); } $option["choices"] = $this->getChoices($field); @@ -216,7 +212,7 @@ protected function getOptionsForField(CustomFormField $field, array $formOptions } break; case AbstractField::MULTIPLE_T: - if (!empty($field->getPlaceholder())) { + if ($field->getPlaceholder() !== '') { $option['placeholder'] = $field->getPlaceholder(); } $option["choices"] = $this->getChoices($field); @@ -259,7 +255,7 @@ protected function getOptionsForField(CustomFormField $field, array $formOptions break; case AbstractField::COUNTRY_T: $option["expanded"] = $field->isExpanded(); - if (!empty($field->getPlaceholder())) { + if ($field->getPlaceholder() !== '') { $option['placeholder'] = $field->getPlaceholder(); } if (!empty($field->getDefaultValues())) { 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/DataTransformer/AttributeDocumentsTransformer.php b/src/Form/DataTransformer/AttributeDocumentsTransformer.php index da372aef..61343456 100644 --- a/src/Form/DataTransformer/AttributeDocumentsTransformer.php +++ b/src/Form/DataTransformer/AttributeDocumentsTransformer.php @@ -14,8 +14,17 @@ final class AttributeDocumentsTransformer implements DataTransformerInterface { - public function __construct(private readonly ObjectManager $manager, private readonly Attribute $attribute) + private ObjectManager $manager; + private Attribute $attribute; + + /** + * @param ObjectManager $manager + * @param Attribute $attribute + */ + public function __construct(ObjectManager $manager, Attribute $attribute) { + $this->manager = $manager; + $this->attribute = $attribute; } /** diff --git a/src/Form/Error/FormErrorSerializer.php b/src/Form/Error/FormErrorSerializer.php index 4837bdc5..6c7b062e 100644 --- a/src/Form/Error/FormErrorSerializer.php +++ b/src/Form/Error/FormErrorSerializer.php @@ -11,8 +11,14 @@ final class FormErrorSerializer implements FormErrorSerializerInterface { - public function __construct(private readonly TranslatorInterface $translator) + private TranslatorInterface $translator; + + /** + * @param TranslatorInterface $translator + */ + public function __construct(TranslatorInterface $translator) { + $this->translator = $translator; } public function getErrorsAsArray(FormInterface $form): array diff --git a/src/Form/RealmChoiceType.php b/src/Form/RealmChoiceType.php index a92877b4..a6f466fe 100644 --- a/src/Form/RealmChoiceType.php +++ b/src/Form/RealmChoiceType.php @@ -12,8 +12,11 @@ final class RealmChoiceType extends AbstractType { - public function __construct(private readonly ManagerRegistry $managerRegistry) + private ManagerRegistry $managerRegistry; + + public function __construct(ManagerRegistry $managerRegistry) { + $this->managerRegistry = $managerRegistry; } /** diff --git a/src/Form/RoleEntityType.php b/src/Form/RoleEntityType.php index 0ac24b40..6e0a4cf9 100644 --- a/src/Form/RoleEntityType.php +++ b/src/Form/RoleEntityType.php @@ -10,14 +10,17 @@ 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 { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly Security $security - ) { + private ManagerRegistry $managerRegistry; + private Security $security; + + public function __construct(ManagerRegistry $managerRegistry, Security $security) + { + $this->managerRegistry = $managerRegistry; + $this->security = $security; } /** 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/Form/UserCollectionType.php b/src/Form/UserCollectionType.php index e372ff50..ecef8dc5 100644 --- a/src/Form/UserCollectionType.php +++ b/src/Form/UserCollectionType.php @@ -14,8 +14,11 @@ final class UserCollectionType extends AbstractType { - public function __construct(private readonly ManagerRegistry $managerRegistry) + private ManagerRegistry $managerRegistry; + + public function __construct(ManagerRegistry $managerRegistry) { + $this->managerRegistry = $managerRegistry; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Importer/AttributeImporter.php b/src/Importer/AttributeImporter.php index a3c96fcb..a47127db 100644 --- a/src/Importer/AttributeImporter.php +++ b/src/Importer/AttributeImporter.php @@ -11,8 +11,14 @@ final class AttributeImporter implements EntityImporterInterface { - public function __construct(private readonly SerializerInterface $serializer) + protected SerializerInterface $serializer; + + /** + * @param SerializerInterface $serializer + */ + public function __construct(SerializerInterface $serializer) { + $this->serializer = $serializer; } /** 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 99c540fd..dbb55899 100644 --- a/src/Logger/DoctrineHandler.php +++ b/src/Logger/DoctrineHandler.php @@ -24,15 +24,24 @@ */ final class DoctrineHandler extends AbstractProcessingHandler { + private ManagerRegistry $managerRegistry; + private TokenStorageInterface $tokenStorage; + private RequestStack $requestStack; + private DocumentUrlGeneratorInterface $documentUrlGenerator; + public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly TokenStorageInterface $tokenStorage, - private readonly RequestStack $requestStack, - private readonly DocumentUrlGeneratorInterface $documentUrlGenerator, + ManagerRegistry $managerRegistry, + TokenStorageInterface $tokenStorage, + RequestStack $requestStack, + DocumentUrlGeneratorInterface $documentUrlGenerator, $level = Logger::INFO, $bubble = true ) { parent::__construct($level, $bubble); + $this->tokenStorage = $tokenStorage; + $this->requestStack = $requestStack; + $this->managerRegistry = $managerRegistry; + $this->documentUrlGenerator = $documentUrlGenerator; } protected function getThumbnailSourcePath(?DocumentInterface $thumbnail): ?string @@ -206,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 2476885d..1c340f40 100644 --- a/src/Mailer/ContactFormManager.php +++ b/src/Mailer/ContactFormManager.php @@ -37,10 +37,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; -/** - * @internal Use ContactFormManagerFactory to create a new instance. - */ -final class ContactFormManager extends EmailManager +class ContactFormManager extends EmailManager { protected string $formName = 'contact_form'; protected ?array $uploadedFiles = null; @@ -59,25 +56,31 @@ final 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 - * USE 'ContactFormManagerFactory' Factory Service + * USE 'contactFormManager' Factory Service */ public function __construct( RequestStack $requestStack, + FormFactoryInterface $formFactory, TranslatorInterface $translator, Environment $templating, MailerInterface $mailer, Settings $settingsBag, DocumentUrlGeneratorInterface $documentUrlGenerator, - private readonly FormFactoryInterface $formFactory, - private readonly FormErrorSerializerInterface $formErrorSerializer, - private readonly ?string $recaptchaPrivateKey, - private 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', @@ -98,6 +101,8 @@ public function __construct( 'new.contact.form.%site%', ['%site%' => $this->settingsBag->get('site_name')] )); + $this->recaptchaPrivateKey = $recaptchaPrivateKey; + $this->recaptchaPublicKey = $recaptchaPublicKey; } /** @@ -125,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; @@ -148,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; @@ -166,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', @@ -179,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 ]), ], ]) @@ -212,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; @@ -224,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, @@ -271,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) @@ -629,7 +634,7 @@ public function getRedirectUrl(): ?string * * @return self */ - public function setRedirectUrl(?string $redirectUrl): self + public function setRedirectUrl(?string $redirectUrl) { $this->redirectUrl = $redirectUrl; @@ -641,7 +646,7 @@ public function setRedirectUrl(?string $redirectUrl): self * * @return int */ - public function getMaxFileSize(): int + public function getMaxFileSize() { return $this->maxFileSize; } @@ -653,7 +658,7 @@ public function getMaxFileSize(): int * * @return self */ - public function setMaxFileSize($maxFileSize): self + public function setMaxFileSize($maxFileSize) { $this->maxFileSize = (int) $maxFileSize; @@ -665,7 +670,7 @@ public function setMaxFileSize($maxFileSize): self * * @return array */ - public function getAllowedMimeTypes(): array + public function getAllowedMimeTypes() { return $this->allowedMimeTypes; } @@ -677,7 +682,7 @@ public function getAllowedMimeTypes(): array * * @return self */ - public function setAllowedMimeTypes(array $allowedMimeTypes): self + public function setAllowedMimeTypes(array $allowedMimeTypes) { $this->allowedMimeTypes = $allowedMimeTypes; @@ -687,7 +692,7 @@ public function setAllowedMimeTypes(array $allowedMimeTypes): self /** * @return array */ - public function getOptions(): array + public function getOptions() { return $this->options; } @@ -695,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/ContactFormManagerFactory.php b/src/Mailer/ContactFormManagerFactory.php index 53a146a0..a109e75b 100644 --- a/src/Mailer/ContactFormManagerFactory.php +++ b/src/Mailer/ContactFormManagerFactory.php @@ -17,12 +17,12 @@ final class ContactFormManagerFactory { public function __construct( private readonly RequestStack $requestStack, + private readonly FormFactoryInterface $formFactory, private readonly TranslatorInterface $translator, private readonly Environment $templating, private readonly MailerInterface $mailer, private readonly Settings $settingsBag, private readonly DocumentUrlGeneratorInterface $documentUrlGenerator, - private readonly FormFactoryInterface $formFactory, private readonly FormErrorSerializerInterface $formErrorSerializer, private readonly ?string $recaptchaPrivateKey, private readonly ?string $recaptchaPublicKey @@ -33,12 +33,12 @@ public function create(): ContactFormManager { return new ContactFormManager( $this->requestStack, + $this->formFactory, $this->translator, $this->templating, $this->mailer, $this->settingsBag, $this->documentUrlGenerator, - $this->formFactory, $this->formErrorSerializer, $this->recaptchaPrivateKey, $this->recaptchaPublicKey diff --git a/src/Mailer/EmailManager.php b/src/Mailer/EmailManager.php index 67e9daee..eb84898f 100644 --- a/src/Mailer/EmailManager.php +++ b/src/Mailer/EmailManager.php @@ -21,9 +21,6 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; -/** - * @internal Use EmailManagerFactory to create a new instance. - */ class EmailManager { protected ?string $subject = null; @@ -36,34 +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 = []; - /* - * DO NOT DIRECTLY USE THIS CONSTRUCTOR - * USE 'EmailManagerFactory' Factory Service - */ 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; } /** @@ -272,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]; @@ -327,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]; @@ -395,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 */ @@ -403,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 */ @@ -411,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/ApplyRealmNodeInheritanceMessage.php b/src/Message/ApplyRealmNodeInheritanceMessage.php index f00d994c..7943f8a8 100644 --- a/src/Message/ApplyRealmNodeInheritanceMessage.php +++ b/src/Message/ApplyRealmNodeInheritanceMessage.php @@ -6,10 +6,13 @@ final class ApplyRealmNodeInheritanceMessage implements AsyncMessage { - public function __construct( - private readonly int|string|null $nodeId, - private readonly int|string|null $realmId - ) { + private int|string|null $nodeId; + private int|string|null $realmId; + + public function __construct(int|string|null $nodeId, int|string|null $realmId) + { + $this->nodeId = $nodeId; + $this->realmId = $realmId; } /** diff --git a/src/Message/CleanRealmNodeInheritanceMessage.php b/src/Message/CleanRealmNodeInheritanceMessage.php index 107e5b76..cf1d1201 100644 --- a/src/Message/CleanRealmNodeInheritanceMessage.php +++ b/src/Message/CleanRealmNodeInheritanceMessage.php @@ -6,10 +6,13 @@ final class CleanRealmNodeInheritanceMessage implements AsyncMessage { - public function __construct( - private readonly int|string|null $nodeId, - private readonly int|string|null $realmId - ) { + private int|string|null $nodeId; + private int|string|null $realmId; + + public function __construct(int|string|null $nodeId, int|string|null $realmId) + { + $this->nodeId = $nodeId; + $this->realmId = $realmId; } /** diff --git a/src/Message/DeleteNodeTypeMessage.php b/src/Message/DeleteNodeTypeMessage.php index ca7566d8..0f3caca1 100644 --- a/src/Message/DeleteNodeTypeMessage.php +++ b/src/Message/DeleteNodeTypeMessage.php @@ -6,8 +6,14 @@ final class DeleteNodeTypeMessage implements AsyncMessage { - public function __construct(private readonly int|string|null $nodeTypeId) + private int|string|null $nodeTypeId; + + /** + * @param int|string|null $nodeTypeId + */ + public function __construct(int|string|null $nodeTypeId) { + $this->nodeTypeId = $nodeTypeId; } /** diff --git a/src/Message/GuzzleRequestMessage.php b/src/Message/GuzzleRequestMessage.php index 65bb93f7..458e8c8e 100644 --- a/src/Message/GuzzleRequestMessage.php +++ b/src/Message/GuzzleRequestMessage.php @@ -8,16 +8,16 @@ final class GuzzleRequestMessage implements AsyncMessage, HttpRequestMessage { + private RequestInterface $request; private array $options; /** * @param RequestInterface $request * @param array $options */ - public function __construct( - private readonly RequestInterface $request, - array $options = [] - ) { + public function __construct(RequestInterface $request, array $options = []) + { + $this->request = $request; $this->options = array_merge([ 'debug' => false, 'timeout' => 3 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/Message/PurgeReverseProxyCacheMessage.php b/src/Message/PurgeReverseProxyCacheMessage.php index d8a47f51..be0c54c0 100644 --- a/src/Message/PurgeReverseProxyCacheMessage.php +++ b/src/Message/PurgeReverseProxyCacheMessage.php @@ -6,8 +6,14 @@ final class PurgeReverseProxyCacheMessage implements AsyncMessage { - public function __construct(private readonly int|string|null $nodeSourceId) + private int|string|null $nodeSourceId; + + /** + * @param int|string|null $nodeSourceId + */ + public function __construct(int|string|null $nodeSourceId) { + $this->nodeSourceId = $nodeSourceId; } /** diff --git a/src/Message/SearchRealmNodeInheritanceMessage.php b/src/Message/SearchRealmNodeInheritanceMessage.php index a1a634f2..c9ec45ff 100644 --- a/src/Message/SearchRealmNodeInheritanceMessage.php +++ b/src/Message/SearchRealmNodeInheritanceMessage.php @@ -6,8 +6,11 @@ final class SearchRealmNodeInheritanceMessage implements AsyncMessage { - public function __construct(private readonly int|string|null $nodeId) + private int|string|null $nodeId; + + public function __construct(int|string|null $nodeId) { + $this->nodeId = $nodeId; } /** diff --git a/src/Message/UpdateNodeTypeSchemaMessage.php b/src/Message/UpdateNodeTypeSchemaMessage.php index 33b36324..f4a309a5 100644 --- a/src/Message/UpdateNodeTypeSchemaMessage.php +++ b/src/Message/UpdateNodeTypeSchemaMessage.php @@ -9,8 +9,14 @@ */ final class UpdateNodeTypeSchemaMessage { - public function __construct(private readonly int|string|null $nodeTypeId) + private int|string|null $nodeTypeId; + + /** + * @param int|string|null $nodeTypeId + */ + public function __construct(int|string|null $nodeTypeId) { + $this->nodeTypeId = $nodeTypeId; } /** 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 f1b6c5a6..0c1d6131 100644 --- a/src/Node/NodeDuplicator.php +++ b/src/Node/NodeDuplicator.php @@ -4,25 +4,36 @@ namespace RZ\Roadiz\CoreBundle\Node; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Persistence\ObjectManager; use RZ\Roadiz\CoreBundle\Entity\AttributeValue; use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\NodesSourcesDocuments; use RZ\Roadiz\CoreBundle\Entity\NodesToNodes; -use Symfony\Component\DependencyInjection\Attribute\Exclude; /** * Handles node duplication. */ -#[Exclude] final class NodeDuplicator { + private Node $originalNode; + private ObjectManager $objectManager; + private NodeNamePolicyInterface $nodeNamePolicy; + + /** + * @param Node $originalNode + * @param ObjectManager $objectManager + * @param NodeNamePolicyInterface $nodeNamePolicy + */ public function __construct( - private readonly Node $originalNode, - private readonly ObjectManager $objectManager, - private readonly NodeNamePolicyInterface $nodeNamePolicy + Node $originalNode, + ObjectManager $objectManager, + NodeNamePolicyInterface $nodeNamePolicy ) { + $this->objectManager = $objectManager; + $this->originalNode = $originalNode; + $this->nodeNamePolicy = $nodeNamePolicy; } /** @@ -91,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); } } @@ -125,16 +137,17 @@ private function doDuplicate(Node &$node): Node * Warning, do not do any FLUSH here to preserve transactional integrity. * * @param Node $node + * @return Node */ - private function doDuplicateNodeRelations(Node $node): void + 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); } + + return $node; } } 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/NodeNamePolicyFactory.php b/src/Node/NodeNamePolicyFactory.php index 2eefaa2a..20c78008 100644 --- a/src/Node/NodeNamePolicyFactory.php +++ b/src/Node/NodeNamePolicyFactory.php @@ -8,10 +8,17 @@ final class NodeNamePolicyFactory { - public function __construct( - private readonly ManagerRegistry $registry, - private readonly bool $useTypedNodeNames - ) { + private ManagerRegistry $registry; + private bool $useTypedNodeNames; + + /** + * @param ManagerRegistry $registry + * @param bool $useTypedNodeNames + */ + public function __construct(ManagerRegistry $registry, bool $useTypedNodeNames) + { + $this->registry = $registry; + $this->useTypedNodeNames = $useTypedNodeNames; } public function create(): NodeNamePolicyInterface diff --git a/src/Node/NodeTranslator.php b/src/Node/NodeTranslator.php index be1d63ca..c4dbbb1c 100644 --- a/src/Node/NodeTranslator.php +++ b/src/Node/NodeTranslator.php @@ -13,10 +13,17 @@ final class NodeTranslator { - public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly EventDispatcherInterface $dispatcher - ) { + private ManagerRegistry $managerRegistry; + private EventDispatcherInterface $dispatcher; + + /** + * @param ManagerRegistry $managerRegistry + * @param EventDispatcherInterface $dispatcher + */ + public function __construct(ManagerRegistry $managerRegistry, EventDispatcherInterface $dispatcher) + { + $this->managerRegistry = $managerRegistry; + $this->dispatcher = $dispatcher; } public function translateNode( 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 0f8c9803..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; @@ -16,8 +15,14 @@ final class UniversalDataDuplicator { - public function __construct(private readonly ManagerRegistry $managerRegistry) + private ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) { + $this->managerRegistry = $managerRegistry; } /** @@ -105,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(); @@ -116,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/NodeType/DefaultValuesResolver.php b/src/NodeType/DefaultValuesResolver.php index 28739dd7..e2180403 100644 --- a/src/NodeType/DefaultValuesResolver.php +++ b/src/NodeType/DefaultValuesResolver.php @@ -12,10 +12,15 @@ final class DefaultValuesResolver implements DefaultValuesResolverInterface { + private ManagerRegistry $managerRegistry; + private string $inheritanceType; + public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly string $inheritanceType + ManagerRegistry $managerRegistry, + string $inheritanceType ) { + $this->managerRegistry = $managerRegistry; + $this->inheritanceType = $inheritanceType; } public function getDefaultValuesAmongAllFields(NodeTypeFieldInterface $field): array diff --git a/src/NodeType/NodeTypeResolver.php b/src/NodeType/NodeTypeResolver.php index 687fa7b9..d8dfdd67 100644 --- a/src/NodeType/NodeTypeResolver.php +++ b/src/NodeType/NodeTypeResolver.php @@ -11,8 +11,11 @@ final class NodeTypeResolver { - public function __construct(private readonly CacheItemPoolInterface $cacheAdapter) + private CacheItemPoolInterface $cacheAdapter; + + public function __construct(CacheItemPoolInterface $cacheAdapter) { + $this->cacheAdapter = $cacheAdapter; } /** 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 46ff3646..632f9889 100644 --- a/src/Preview/RequestPreviewRevolver.php +++ b/src/Preview/RequestPreviewRevolver.php @@ -13,10 +13,15 @@ */ final class RequestPreviewRevolver implements PreviewResolverInterface { + private RequestStack $requestStack; + private string $requiredRole; + public function __construct( - private readonly RequestStack $requestStack, - private readonly string $requiredRole + RequestStack $requestStack, + string $requiredRole ) { + $this->requestStack = $requestStack; + $this->requiredRole = $requiredRole; } /** @@ -28,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 9184f8ef..cea9ee39 100644 --- a/src/Preview/User/PreviewUserProvider.php +++ b/src/Preview/User/PreviewUserProvider.php @@ -6,15 +6,22 @@ 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 { - public function __construct( - private readonly PreviewResolverInterface $previewResolver, - private readonly Security $security - ) { + private PreviewResolverInterface $previewResolver; + private Security $security; + + /** + * @param PreviewResolverInterface $previewResolver + * @param Security $security + */ + public function __construct(PreviewResolverInterface $previewResolver, Security $security) + { + $this->previewResolver = $previewResolver; + $this->security = $security; } public function createFromSecurity(): UserInterface 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/AttributeGroupRepository.php b/src/Repository/AttributeGroupRepository.php index 45dd1b0c..b913a8da 100644 --- a/src/Repository/AttributeGroupRepository.php +++ b/src/Repository/AttributeGroupRepository.php @@ -13,10 +13,8 @@ */ final class AttributeGroupRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, AttributeGroup::class, $dispatcher); } } diff --git a/src/Repository/AttributeRepository.php b/src/Repository/AttributeRepository.php index 111e992f..a7d7da19 100644 --- a/src/Repository/AttributeRepository.php +++ b/src/Repository/AttributeRepository.php @@ -13,10 +13,8 @@ */ final class AttributeRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, Attribute::class, $dispatcher); } } diff --git a/src/Repository/AttributeTranslationRepository.php b/src/Repository/AttributeTranslationRepository.php index 0fcb735a..785d0faf 100644 --- a/src/Repository/AttributeTranslationRepository.php +++ b/src/Repository/AttributeTranslationRepository.php @@ -13,10 +13,8 @@ */ final class AttributeTranslationRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, AttributeTranslation::class, $dispatcher); } } diff --git a/src/Repository/AttributeValueTranslationRepository.php b/src/Repository/AttributeValueTranslationRepository.php index 553db03c..eb3cb6e5 100644 --- a/src/Repository/AttributeValueTranslationRepository.php +++ b/src/Repository/AttributeValueTranslationRepository.php @@ -13,10 +13,8 @@ */ final class AttributeValueTranslationRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, AttributeValueTranslation::class, $dispatcher); } } diff --git a/src/Repository/CustomFormAnswerRepository.php b/src/Repository/CustomFormAnswerRepository.php index 36870df2..d7774a73 100644 --- a/src/Repository/CustomFormAnswerRepository.php +++ b/src/Repository/CustomFormAnswerRepository.php @@ -15,10 +15,8 @@ final class CustomFormAnswerRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, CustomFormAnswer::class, $dispatcher); } diff --git a/src/Repository/CustomFormFieldAttributeRepository.php b/src/Repository/CustomFormFieldAttributeRepository.php index f89b32bd..fe4f039d 100644 --- a/src/Repository/CustomFormFieldAttributeRepository.php +++ b/src/Repository/CustomFormFieldAttributeRepository.php @@ -13,10 +13,8 @@ */ final class CustomFormFieldAttributeRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, CustomFormFieldAttribute::class, $dispatcher); } } diff --git a/src/Repository/CustomFormFieldRepository.php b/src/Repository/CustomFormFieldRepository.php index 117fe365..355adc44 100644 --- a/src/Repository/CustomFormFieldRepository.php +++ b/src/Repository/CustomFormFieldRepository.php @@ -5,7 +5,6 @@ namespace RZ\Roadiz\CoreBundle\Repository; use Doctrine\Persistence\ManagerRegistry; -use RZ\Roadiz\CoreBundle\Entity\CustomForm; use RZ\Roadiz\CoreBundle\Entity\CustomFormField; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -14,21 +13,8 @@ */ final class CustomFormFieldRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { - parent::__construct($registry, CustomFormField::class, $dispatcher); - } - - public function findDistinctGroupNamesInCustomForm(CustomForm $customForm): array + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) { - $qb = $this->createQueryBuilder('o'); - $qb->select('DISTINCT o.groupName') - ->andWhere($qb->expr()->eq('o.customForm', ':customForm')) - ->setParameter('customForm', $customForm); - - $result = $qb->getQuery()->getResult(); - return array_map(fn (array $row) => $row['groupName'], $result); + parent::__construct($registry, CustomFormField::class, $dispatcher); } } 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 dccd2688..a135e1e6 100644 --- a/src/Repository/DocumentRepository.php +++ b/src/Repository/DocumentRepository.php @@ -11,7 +11,6 @@ use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Tools\Pagination\Paginator; use Doctrine\Persistence\ManagerRegistry; -use RZ\Roadiz\CoreBundle\Entity\CustomFormFieldAttribute; use RZ\Roadiz\Documents\Repository\DocumentRepositoryInterface; use RZ\Roadiz\Contracts\NodeType\NodeTypeFieldInterface; use RZ\Roadiz\Core\AbstractEntities\AbstractField; @@ -529,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, @@ -540,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('nsf.field', ':field')) ->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('d.raw', ':raw')) - ->addOrderBy('nsf.position', 'ASC') - ->setParameter('fieldName', $fieldName) + ->setParameter('field', $field) ->setParameter('nodeSource', $nodeSource) ->setParameter('translation', $nodeSource->getTranslation()) ->setParameter('raw', false) @@ -606,8 +579,12 @@ public function findAllUnused(): array return $this->getAllUnusedQueryBuilder()->getQuery()->getResult(); } - protected function getAllDocumentsIdUsedInSettings(): array + /** + * @return QueryBuilder + */ + public function getAllUnusedQueryBuilder(): QueryBuilder { + $qb1 = $this->createQueryBuilder('d1'); $qb2 = $this->_em->createQueryBuilder(); /* @@ -627,43 +604,6 @@ protected function getAllDocumentsIdUsedInSettings(): array $idArray[] = (int) $value['value']; } - return $idArray; - } - - protected function getAllDocumentsIdUsedInCustomFormAnswers(): array - { - $qb2 = $this->_em->createQueryBuilder(); - - /* - * Get documents used by settings - */ - $qb2->select('d.id') - ->from(CustomFormFieldAttribute::class, 'cffa') - ->innerJoin('cffa.documents', 'd') - ; - - $subQuery = $qb2->getQuery(); - $array = $subQuery->getScalarResult(); - $idArray = []; - - foreach ($array as $value) { - $idArray[] = (int) $value['id']; - } - - return $idArray; - } - - /** - * @return QueryBuilder - */ - public function getAllUnusedQueryBuilder(): QueryBuilder - { - $qb1 = $this->createQueryBuilder('d1'); - - $idArray = []; - $idArray = array_merge($idArray, $this->getAllDocumentsIdUsedInSettings()); - $idArray = array_merge($idArray, $this->getAllDocumentsIdUsedInCustomFormAnswers()); - /* * Get unused documents */ diff --git a/src/Repository/FolderTranslationRepository.php b/src/Repository/FolderTranslationRepository.php index 3b78c003..3224fcfa 100644 --- a/src/Repository/FolderTranslationRepository.php +++ b/src/Repository/FolderTranslationRepository.php @@ -13,10 +13,8 @@ */ final class FolderTranslationRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, FolderTranslation::class, $dispatcher); } } diff --git a/src/Repository/GroupRepository.php b/src/Repository/GroupRepository.php index 557e0166..d3660fd2 100644 --- a/src/Repository/GroupRepository.php +++ b/src/Repository/GroupRepository.php @@ -18,10 +18,8 @@ */ final class GroupRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, Group::class, $dispatcher); } } 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 0164634b..dc41cec0 100644 --- a/src/Repository/NodesCustomFormsRepository.php +++ b/src/Repository/NodesCustomFormsRepository.php @@ -5,9 +5,9 @@ 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; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -22,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 81ba862a..0c24dc5d 100644 --- a/src/Repository/NodesSourcesDocumentsRepository.php +++ b/src/Repository/NodesSourcesDocumentsRepository.php @@ -7,9 +7,9 @@ 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; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -26,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/NodesTagsRepository.php b/src/Repository/NodesTagsRepository.php deleted file mode 100644 index 540478e0..00000000 --- a/src/Repository/NodesTagsRepository.php +++ /dev/null @@ -1,22 +0,0 @@ - - */ -class NodesTagsRepository extends EntityRepository -{ - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { - parent::__construct($registry, NodesTags::class, $dispatcher); - } -} diff --git a/src/Repository/NodesToNodesRepository.php b/src/Repository/NodesToNodesRepository.php index edb68b93..a64042a3 100644 --- a/src/Repository/NodesToNodesRepository.php +++ b/src/Repository/NodesToNodesRepository.php @@ -7,9 +7,9 @@ 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; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -27,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 1222df4b..21b4c507 100644 --- a/src/Repository/RealmRepository.php +++ b/src/Repository/RealmRepository.php @@ -41,25 +41,4 @@ public function findByNodeAndBehaviour(Node $node, string $realmBehaviour): arra return $qb->getQuery()->getResult(); } - - public function findByNodeWithSerializationGroup(Node $node): array - { - $qb = $this->createQueryBuilder('r'); - $qb->innerJoin('r.realmNodes', 'rn') - ->andWhere($qb->expr()->in('rn.node', ':node')) - ->andWhere($qb->expr()->isNotNull('rn.realm')) - ->andWhere($qb->expr()->isNotNull('r.serializationGroup')) - ->setParameter('node', $node); - - return $qb->getQuery()->setCacheable(true)->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/RedirectionRepository.php b/src/Repository/RedirectionRepository.php index 24cf7709..507b4989 100644 --- a/src/Repository/RedirectionRepository.php +++ b/src/Repository/RedirectionRepository.php @@ -18,10 +18,8 @@ */ final class RedirectionRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, Redirection::class, $dispatcher); } } 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/Repository/TagTranslationRepository.php b/src/Repository/TagTranslationRepository.php index 1683b98f..8c74f408 100644 --- a/src/Repository/TagTranslationRepository.php +++ b/src/Repository/TagTranslationRepository.php @@ -18,10 +18,8 @@ */ final class TagTranslationRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, TagTranslation::class, $dispatcher); } } diff --git a/src/Repository/WebhookRepository.php b/src/Repository/WebhookRepository.php index cecbbc79..7592cea6 100644 --- a/src/Repository/WebhookRepository.php +++ b/src/Repository/WebhookRepository.php @@ -18,10 +18,8 @@ */ final class WebhookRepository extends EntityRepository { - public function __construct( - ManagerRegistry $registry, - EventDispatcherInterface $dispatcher - ) { + public function __construct(ManagerRegistry $registry, EventDispatcherInterface $dispatcher) + { parent::__construct($registry, Webhook::class, $dispatcher); } } 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/NodeRouteHelper.php b/src/Routing/NodeRouteHelper.php index d9bb2723..dfeec92a 100644 --- a/src/Routing/NodeRouteHelper.php +++ b/src/Routing/NodeRouteHelper.php @@ -13,6 +13,15 @@ final class NodeRouteHelper { + private Node $node; + private ?Theme $theme; + private PreviewResolverInterface $previewResolver; + private LoggerInterface $logger; + private string $defaultControllerNamespace; + /** + * @var class-string + */ + private string $defaultControllerClass; /** * @var class-string|null */ @@ -27,13 +36,19 @@ final class NodeRouteHelper * @param string $defaultControllerNamespace */ public function __construct( - private readonly Node $node, - private readonly ?Theme $theme, - private readonly PreviewResolverInterface $previewResolver, - private readonly LoggerInterface $logger, - private readonly string $defaultControllerClass, - private readonly string $defaultControllerNamespace = '\\App\\Controller' + Node $node, + ?Theme $theme, + PreviewResolverInterface $previewResolver, + LoggerInterface $logger, + string $defaultControllerClass, + string $defaultControllerNamespace = '\\App\\Controller' ) { + $this->node = $node; + $this->theme = $theme; + $this->previewResolver = $previewResolver; + $this->defaultControllerClass = $defaultControllerClass; + $this->logger = $logger; + $this->defaultControllerNamespace = $defaultControllerNamespace; } /** 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 1b3b8fc2..293d5e5e 100644 --- a/src/Routing/NodesSourcesUrlGenerator.php +++ b/src/Routing/NodesSourcesUrlGenerator.php @@ -6,18 +6,38 @@ use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\Theme; +use Symfony\Component\HttpFoundation\Request; /** * Do not extend this class, use NodesSourcesPathGeneratingEvent::class event. */ 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/ClientRegistry.php b/src/SearchEngine/ClientRegistry.php index 85ef24f8..0e9cae6c 100644 --- a/src/SearchEngine/ClientRegistry.php +++ b/src/SearchEngine/ClientRegistry.php @@ -9,8 +9,14 @@ final class ClientRegistry { - public function __construct(private readonly ContainerInterface $container) + protected ContainerInterface $container; + + /** + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container) { + $this->container = $container; } public function getClient(): ?Client 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/Message/Handler/SolrDeleteMessageHandler.php b/src/SearchEngine/Message/Handler/SolrDeleteMessageHandler.php index 3ee62b78..587b9680 100644 --- a/src/SearchEngine/Message/Handler/SolrDeleteMessageHandler.php +++ b/src/SearchEngine/Message/Handler/SolrDeleteMessageHandler.php @@ -12,10 +12,13 @@ final class SolrDeleteMessageHandler implements MessageHandlerInterface { - public function __construct( - private readonly IndexerFactoryInterface $indexerFactory, - private readonly LoggerInterface $searchEngineLogger - ) { + private LoggerInterface $logger; + private IndexerFactoryInterface $indexerFactory; + + public function __construct(IndexerFactoryInterface $indexerFactory, LoggerInterface $searchEngineLogger) + { + $this->logger = $searchEngineLogger; + $this->indexerFactory = $indexerFactory; } public function __invoke(SolrDeleteMessage $message): void @@ -29,7 +32,7 @@ public function __invoke(SolrDeleteMessage $message): void } catch (SolrServerNotAvailableException $exception) { return; } catch (\LogicException $exception) { - $this->searchEngineLogger->error($exception->getMessage()); + $this->logger->error($exception->getMessage()); } } } diff --git a/src/SearchEngine/Message/Handler/SolrReindexMessageHandler.php b/src/SearchEngine/Message/Handler/SolrReindexMessageHandler.php index 6087ce7a..af3e006c 100644 --- a/src/SearchEngine/Message/Handler/SolrReindexMessageHandler.php +++ b/src/SearchEngine/Message/Handler/SolrReindexMessageHandler.php @@ -12,10 +12,13 @@ final class SolrReindexMessageHandler implements MessageHandlerInterface { - public function __construct( - private readonly IndexerFactoryInterface $indexerFactory, - private readonly LoggerInterface $searchEngineLogger - ) { + private LoggerInterface $logger; + private IndexerFactoryInterface $indexerFactory; + + public function __construct(IndexerFactoryInterface $indexerFactory, LoggerInterface $searchEngineLogger) + { + $this->logger = $searchEngineLogger; + $this->indexerFactory = $indexerFactory; } public function __invoke(SolrReindexMessage $message): void @@ -29,7 +32,7 @@ public function __invoke(SolrReindexMessage $message): void } catch (SolrServerNotAvailableException $exception) { return; } catch (\LogicException $exception) { - $this->searchEngineLogger->error($exception->getMessage()); + $this->logger->error($exception->getMessage()); } } } 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/SolariumLogger.php b/src/SearchEngine/SolariumLogger.php index fb0f7e19..120b0a67 100644 --- a/src/SearchEngine/SolariumLogger.php +++ b/src/SearchEngine/SolariumLogger.php @@ -29,12 +29,14 @@ final class SolariumLogger extends SolariumPlugin implements DataCollectorInterf private ?SolariumRequest $currentRequest = null; private ?float $currentStartTime = null; private ?SolariumEndpoint $currentEndpoint = null; + private LoggerInterface $logger; + private Stopwatch $stopwatch; - public function __construct( - private readonly LoggerInterface $searchEngineLogger, - private readonly Stopwatch $stopwatch - ) { + public function __construct(LoggerInterface $searchEngineLogger, Stopwatch $stopwatch) + { parent::__construct(); + $this->logger = $searchEngineLogger; + $this->stopwatch = $stopwatch; } public static function getSubscribedEvents(): array @@ -105,7 +107,8 @@ public function preExecuteRequest(SolariumPreExecuteRequestEvent $event): void $this->currentRequest = $event->getRequest(); $this->currentEndpoint = $event->getEndpoint(); - $this->searchEngineLogger->debug($this->getEndpointBaseUrl($this->currentEndpoint) . $this->currentRequest->getUri()); + + $this->logger->debug($this->getEndpointBaseUrl($this->currentEndpoint) . $this->currentRequest->getUri()); $this->currentStartTime = microtime(true); } diff --git a/src/SearchEngine/SolrSearchResultItem.php b/src/SearchEngine/SolrSearchResultItem.php deleted file mode 100644 index ac474955..00000000 --- a/src/SearchEngine/SolrSearchResultItem.php +++ /dev/null @@ -1,48 +0,0 @@ -> $highlighting - */ - public function __construct( - private readonly object $item, - private 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/SearchEngine/Subscriber/SolariumSubscriber.php b/src/SearchEngine/Subscriber/SolariumSubscriber.php index 397b1455..05868eca 100644 --- a/src/SearchEngine/Subscriber/SolariumSubscriber.php +++ b/src/SearchEngine/Subscriber/SolariumSubscriber.php @@ -36,8 +36,11 @@ final class SolariumSubscriber implements EventSubscriberInterface { - public function __construct(private readonly MessageBusInterface $messageBus) + protected MessageBusInterface $messageBus; + + public function __construct(MessageBusInterface $messageBus) { + $this->messageBus = $messageBus; } public static function getSubscribedEvents(): array diff --git a/src/SearchEngine/Subscriber/TreeWalkerIndexingEventSubscriber.php b/src/SearchEngine/Subscriber/TreeWalkerIndexingEventSubscriber.php index cc104e46..76ccc9ef 100644 --- a/src/SearchEngine/Subscriber/TreeWalkerIndexingEventSubscriber.php +++ b/src/SearchEngine/Subscriber/TreeWalkerIndexingEventSubscriber.php @@ -16,12 +16,21 @@ */ final class TreeWalkerIndexingEventSubscriber extends AbstractIndexingSubscriber { + private WalkerContextInterface $walkerContext; + private SolariumFactoryInterface $solariumFactory; + private int $maxLevel; + private string $defaultLocale; + public function __construct( - private readonly WalkerContextInterface $walkerContext, - private readonly SolariumFactoryInterface $solariumFactory, - private readonly int $maxLevel = 5, - private readonly string $defaultLocale = 'en' + WalkerContextInterface $walkerContext, + SolariumFactoryInterface $solariumFactory, + int $maxLevel = 5, + string $defaultLocale = 'en' ) { + $this->walkerContext = $walkerContext; + $this->solariumFactory = $solariumFactory; + $this->maxLevel = $maxLevel; + $this->defaultLocale = $defaultLocale; } /** diff --git a/src/Security/Authentication/JwtAuthenticationSuccessHandler.php b/src/Security/Authentication/JwtAuthenticationSuccessHandler.php index d21a2a3d..3230aab3 100644 --- a/src/Security/Authentication/JwtAuthenticationSuccessHandler.php +++ b/src/Security/Authentication/JwtAuthenticationSuccessHandler.php @@ -14,10 +14,15 @@ final class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface { + private ManagerRegistry $managerRegistry; + private AuthenticationSuccessHandler $decorated; + public function __construct( - private readonly AuthenticationSuccessHandler $decorated, - private readonly ManagerRegistry $managerRegistry + AuthenticationSuccessHandler $decorated, + ManagerRegistry $managerRegistry ) { + $this->decorated = $decorated; + $this->managerRegistry = $managerRegistry; } public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response 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 7ec0ce91..16bcbb9b 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 c9573a21..576e4b63 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 0970525f..2736a7fb 100644 --- a/src/Serializer/Normalizer/TagNormalizer.php +++ b/src/Serializer/Normalizer/TagNormalizer.php @@ -21,32 +21,31 @@ 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'] : []; - /* - * Always falls back on default translation if no translation is found for Tags entities - */ - $translatedData = $object->getTranslatedTagsByTranslation($context['translation'])->first() ?: - $object->getTranslatedTagsByDefaultTranslation(); - 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']; + /* + * Always falls back on default translation if no translation is found for Tags entities + */ + $translatedData = $object->getTranslatedTagsByTranslation($context['translation'])->first() ?: + $object->getTranslatedTagsByDefaultTranslation(); + 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/Tag/TagFactory.php b/src/Tag/TagFactory.php index 9a7c79fa..4a3f7828 100644 --- a/src/Tag/TagFactory.php +++ b/src/Tag/TagFactory.php @@ -13,8 +13,14 @@ final class TagFactory { - public function __construct(private readonly ManagerRegistry $managerRegistry) + private ManagerRegistry $managerRegistry; + + /** + * @param ManagerRegistry $managerRegistry + */ + public function __construct(ManagerRegistry $managerRegistry) { + $this->managerRegistry = $managerRegistry; } /** diff --git a/src/Translation/TranslationViewer.php b/src/Translation/TranslationViewer.php index 450d746b..af42958e 100644 --- a/src/Translation/TranslationViewer.php +++ b/src/Translation/TranslationViewer.php @@ -21,14 +21,28 @@ final class TranslationViewer { + private Settings $settingsBag; + private ManagerRegistry $managerRegistry; + private RouterInterface $router; + private PreviewResolverInterface $previewResolver; private ?TranslationInterface $translation = null; + /** + * @param ManagerRegistry $managerRegistry + * @param Settings $settingsBag + * @param RouterInterface $router + * @param PreviewResolverInterface $previewResolver + */ public function __construct( - private readonly ManagerRegistry $managerRegistry, - private readonly Settings $settingsBag, - private readonly RouterInterface $router, - private readonly PreviewResolverInterface $previewResolver + ManagerRegistry $managerRegistry, + Settings $settingsBag, + RouterInterface $router, + PreviewResolverInterface $previewResolver ) { + $this->settingsBag = $settingsBag; + $this->router = $router; + $this->previewResolver = $previewResolver; + $this->managerRegistry = $managerRegistry; } /** 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 adb4d9ac..bfdd578d 100644 --- a/src/TwigExtension/RoutingExtension.php +++ b/src/TwigExtension/RoutingExtension.php @@ -4,7 +4,6 @@ namespace RZ\Roadiz\CoreBundle\TwigExtension; -use Symfony\Bridge\Twig\Extension\RoutingExtension as BaseRoutingExtension; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Twig\Error\RuntimeError; @@ -15,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 BaseRoutingExtension $decorated, - private readonly UrlGeneratorInterface $generator + \Symfony\Bridge\Twig\Extension\RoutingExtension $decorated, + UrlGeneratorInterface $generator ) { + $this->generator = $generator; + $this->decorated = $decorated; } /** @@ -41,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( @@ -67,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/Webhook/Exception/TooManyWebhookTriggeredException.php b/src/Webhook/Exception/TooManyWebhookTriggeredException.php index c53c4b58..67163dae 100644 --- a/src/Webhook/Exception/TooManyWebhookTriggeredException.php +++ b/src/Webhook/Exception/TooManyWebhookTriggeredException.php @@ -8,13 +8,16 @@ final class TooManyWebhookTriggeredException extends \RuntimeException { + private ?\DateTimeImmutable $doNotTriggerBefore; + public function __construct( - private readonly ?\DateTimeImmutable $doNotTriggerBefore = null, + ?\DateTimeImmutable $doNotTriggerBefore = null, string $message = "", int $code = 0, Throwable $previous = null ) { parent::__construct($message, $code, $previous); + $this->doNotTriggerBefore = $doNotTriggerBefore; } /** diff --git a/src/Webhook/Message/GenericJsonPostMessage.php b/src/Webhook/Message/GenericJsonPostMessage.php index cd8d9c4b..8d9f739f 100644 --- a/src/Webhook/Message/GenericJsonPostMessage.php +++ b/src/Webhook/Message/GenericJsonPostMessage.php @@ -13,10 +13,17 @@ final class GenericJsonPostMessage implements AsyncMessage, HttpRequestMessage, WebhookMessage { - public function __construct( - private readonly string $uri, - private readonly ?array $payload = null - ) { + private string $uri; + private ?array $payload; + + /** + * @param string $uri + * @param array|null $payload + */ + public function __construct(string $uri, ?array $payload = null) + { + $this->uri = $uri; + $this->payload = $payload; } public function getRequest(): RequestInterface @@ -47,7 +54,7 @@ public function getOptions(): array * @param Webhook $webhook * @return static */ - public static function fromWebhook(WebhookInterface $webhook): self + public static function fromWebhook(WebhookInterface $webhook) { return new self($webhook->getUri(), $webhook->getPayload()); } diff --git a/src/Webhook/Message/GitlabPipelineTriggerMessage.php b/src/Webhook/Message/GitlabPipelineTriggerMessage.php index 8783c286..875d856d 100644 --- a/src/Webhook/Message/GitlabPipelineTriggerMessage.php +++ b/src/Webhook/Message/GitlabPipelineTriggerMessage.php @@ -12,12 +12,23 @@ final class GitlabPipelineTriggerMessage implements AsyncMessage, HttpRequestMessage, WebhookMessage { - public function __construct( - private readonly string $uri, - private readonly string $token, - private readonly string $ref = 'main', - private readonly ?array $variables = null - ) { + private string $uri; + private string $token; + private string $ref; + private ?array $variables; + + /** + * @param string $uri + * @param string $token + * @param string $ref + * @param array|null $variables + */ + public function __construct(string $uri, string $token, string $ref = 'main', ?array $variables = null) + { + $this->uri = $uri; + $this->token = $token; + $this->ref = $ref; + $this->variables = $variables; } public function getRequest(): RequestInterface @@ -56,7 +67,7 @@ public function getOptions(): array * @param WebhookInterface $webhook * @return static */ - public static function fromWebhook(WebhookInterface $webhook): self + public static function fromWebhook(WebhookInterface $webhook) { $payload = $webhook->getPayload(); return new self( diff --git a/src/Webhook/Message/NetlifyBuildHookMessage.php b/src/Webhook/Message/NetlifyBuildHookMessage.php index dc0ee797..76cdf7e1 100644 --- a/src/Webhook/Message/NetlifyBuildHookMessage.php +++ b/src/Webhook/Message/NetlifyBuildHookMessage.php @@ -12,10 +12,17 @@ final class NetlifyBuildHookMessage implements AsyncMessage, HttpRequestMessage, WebhookMessage { - public function __construct( - private readonly string $uri, - private readonly ?array $payload = null - ) { + private string $uri; + private ?array $payload; + + /** + * @param string $uri + * @param array|null $payload + */ + public function __construct(string $uri, ?array $payload = null) + { + $this->uri = $uri; + $this->payload = $payload; } public function getRequest(): RequestInterface @@ -49,7 +56,7 @@ public function getOptions(): array * @param WebhookInterface $webhook * @return static */ - public static function fromWebhook(WebhookInterface $webhook): self + public static function fromWebhook(WebhookInterface $webhook) { return new self($webhook->getUri(), $webhook->getPayload()); } diff --git a/src/Webhook/ThrottledWebhookDispatcher.php b/src/Webhook/ThrottledWebhookDispatcher.php index 82e08d8d..48810428 100644 --- a/src/Webhook/ThrottledWebhookDispatcher.php +++ b/src/Webhook/ThrottledWebhookDispatcher.php @@ -12,11 +12,18 @@ final class ThrottledWebhookDispatcher implements WebhookDispatcher { + private WebhookMessageFactoryInterface $messageFactory; + private MessageBusInterface $messageBus; + private RateLimiterFactory $throttledWebhooksLimiter; + public function __construct( - private readonly WebhookMessageFactoryInterface $messageFactory, - private readonly MessageBusInterface $messageBus, - private readonly RateLimiterFactory $throttledWebhooksLimiter + WebhookMessageFactoryInterface $messageFactory, + MessageBusInterface $messageBus, + RateLimiterFactory $throttledWebhooksLimiter ) { + $this->messageFactory = $messageFactory; + $this->messageBus = $messageBus; + $this->throttledWebhooksLimiter = $throttledWebhooksLimiter; } /** diff --git a/src/Workflow/Event/NodeStatusGuardListener.php b/src/Workflow/Event/NodeStatusGuardListener.php index 742fa04c..92de5153 100644 --- a/src/Workflow/Event/NodeStatusGuardListener.php +++ b/src/Workflow/Event/NodeStatusGuardListener.php @@ -7,14 +7,20 @@ 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; final class NodeStatusGuardListener implements EventSubscriberInterface { - public function __construct(private readonly Security $security) + private Security $security; + + /** + * @param Security $security + */ + public function __construct(Security $security) { + $this->security = $security; } /** diff --git a/src/Xlsx/AbstractXlsxSerializer.php b/src/Xlsx/AbstractXlsxSerializer.php index 664457b4..4e6340d4 100644 --- a/src/Xlsx/AbstractXlsxSerializer.php +++ b/src/Xlsx/AbstractXlsxSerializer.php @@ -7,13 +7,18 @@ use Symfony\Contracts\Translation\TranslatorInterface; /** - * @deprecated XLSX serialization is deprecated and will be removed in next major version. * Define basic serialize operations for XLSX data type. */ abstract class AbstractXlsxSerializer implements SerializerInterface { - public function __construct(protected readonly TranslatorInterface $translator) + protected TranslatorInterface $translator; + + /** + * @param TranslatorInterface $translator + */ + public function __construct(TranslatorInterface $translator) { + $this->translator = $translator; } /** diff --git a/src/Xlsx/NodeSourceXlsxSerializer.php b/src/Xlsx/NodeSourceXlsxSerializer.php index 40747f99..b2579415 100644 --- a/src/Xlsx/NodeSourceXlsxSerializer.php +++ b/src/Xlsx/NodeSourceXlsxSerializer.php @@ -4,36 +4,49 @@ namespace RZ\Roadiz\CoreBundle\Xlsx; +use Doctrine\Common\Collections\Collection; use Doctrine\Persistence\ObjectManager; use RZ\Roadiz\Contracts\NodeType\NodeTypeInterface; use RZ\Roadiz\Core\AbstractEntities\AbstractField; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\NodeTypeField; use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** - * @deprecated XLSX serialization is deprecated and will be removed in next major version. * XLSX Serialization handler for NodeSource. */ -final class NodeSourceXlsxSerializer extends AbstractXlsxSerializer +class NodeSourceXlsxSerializer extends AbstractXlsxSerializer { + protected ObjectManager $objectManager; + protected Request $request; + protected UrlGeneratorInterface $urlGenerator; + protected bool $forceLocale = false; protected bool $addUrls = false; protected bool $onlyTexts = false; + /** + * + * @param ObjectManager $objectManager + * @param TranslatorInterface $translator + * @param UrlGeneratorInterface $urlGenerator + */ public function __construct( + ObjectManager $objectManager, TranslatorInterface $translator, - private readonly ObjectManager $objectManager, - private readonly UrlGeneratorInterface $urlGenerator + UrlGeneratorInterface $urlGenerator ) { parent::__construct($translator); + $this->objectManager = $objectManager; + $this->urlGenerator = $urlGenerator; } /** * Create a simple associative array with a NodeSource. * - * @param NodesSources|iterable|null $nodeSource + * @param NodesSources|Collection|array|null $nodeSource * @return array */ public function toArray($nodeSource): array @@ -55,10 +68,11 @@ 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)); - } elseif (is_iterable($nodeSource)) { + } elseif ($nodeSource instanceof Collection || is_array($nodeSource)) { /* * If asked to serialize a nodeSource collection */ @@ -152,11 +166,15 @@ public function setOnlyTexts(bool $onlyTexts = true): self } /** + * @param Request $request + * @param bool $forceLocale * @return NodeSourceXlsxSerializer */ - public function addUrls(): self + public function addUrls(Request $request, bool $forceLocale = false): self { $this->addUrls = true; + $this->request = $request; + $this->forceLocale = $forceLocale; return $this; } } diff --git a/src/Xlsx/SerializerInterface.php b/src/Xlsx/SerializerInterface.php index 7cfde0e3..07229172 100644 --- a/src/Xlsx/SerializerInterface.php +++ b/src/Xlsx/SerializerInterface.php @@ -5,7 +5,6 @@ namespace RZ\Roadiz\CoreBundle\Xlsx; /** - * @deprecated XLSX serialization is deprecated and will be removed in next major version. * EntitySerializer that implements simple serialization/deserialization methods. */ interface SerializerInterface diff --git a/src/Xlsx/XlsxExporter.php b/src/Xlsx/XlsxExporter.php index e82e931f..633d7017 100644 --- a/src/Xlsx/XlsxExporter.php +++ b/src/Xlsx/XlsxExporter.php @@ -11,13 +11,16 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use Symfony\Contracts\Translation\TranslatorInterface; -/** - * @deprecated XLSX serialization is deprecated and will be removed in next major version. - */ class XlsxExporter { - public function __construct(protected readonly TranslatorInterface $translator) + protected TranslatorInterface $translator; + + /** + * @param TranslatorInterface $translator + */ + public function __construct(TranslatorInterface $translator) { + $this->translator = $translator; } /** diff --git a/templates/customForm/base_custom_form.html.twig b/templates/customForm/base_custom_form.html.twig index e1b14ada..0111cce7 100644 --- a/templates/customForm/base_custom_form.html.twig +++ b/templates/customForm/base_custom_form.html.twig @@ -1,34 +1,26 @@ -{% set formattedLocale = app.request.locale|replace({'_': '-'})|lower %} +{% set formattedLocale = request.locale|replace({'_': '-'})|lower %} - - - - - {% block title %}{{ head.siteTitle }}{% endblock %} - - - {% include '@RoadizRozier/admin/meta-icon.html.twig' %} - {# CSS scripts inclusions / Using webpack #} - {% include '@RoadizRozier/partials/css-inject.html.twig' %} - {% if head.mainColor %} - - {% endif %} - - - -{% include '@RoadizRozier/includes/messages.html.twig' %} -
- {% block content %} -

{% trans %}Welcome{% endtrans %}

- {% endblock %} -
-{% block customScripts %}{% endblock %} - + + + + + {% block title %}{{ head.siteTitle }}{% endblock %} + + + + {% include '@RoadizRozier/partials/css-inject.html.twig' %} + + + + + + +
+ {% block content %} +

{% trans %}Welcome{% endtrans %}

+ {% endblock %} +
+ + + diff --git a/templates/customForm/customForm.html.twig b/templates/customForm/customForm.html.twig index 893ceff0..86d9b857 100644 --- a/templates/customForm/customForm.html.twig +++ b/templates/customForm/customForm.html.twig @@ -1,6 +1,6 @@ {% extends '@RoadizCore/customForm/base_custom_form.html.twig' %} -{% block title %}{{ customForm.displayName }} | {{ parent() }}{% endblock %} +{% block title %}{{ customForm.displayName }}{% endblock %} {% block content %}
diff --git a/templates/customForm/customFormSent.html.twig b/templates/customForm/customFormSent.html.twig index 2127bfd2..e9c4a655 100644 --- a/templates/customForm/customFormSent.html.twig +++ b/templates/customForm/customFormSent.html.twig @@ -1,6 +1,6 @@ {% extends '@RoadizCore/customForm/base_custom_form.html.twig' %} -{% block title %}{{ customForm.displayName }} | {{ parent() }}{% endblock %} +{% block title %}{{ customForm.displayName }}{% endblock %} {% block content %}
diff --git a/templates/email/forms/answerForm.html.twig b/templates/email/forms/answerForm.html.twig index 2c35a8b9..a6cf6428 100644 --- a/templates/email/forms/answerForm.html.twig +++ b/templates/email/forms/answerForm.html.twig @@ -7,41 +7,39 @@ {% set requestLocale = app.request.locale %} {% endif %} - - - - - - - - - - - + + + + + + + + +
-

{% trans %}answer.form{% endtrans %}

-
-

{{ title }}

-
- - - - -
- - {% for field in fields %} - - - - - {% endfor %} -
{{ field.label|default(field.name|trans(locale=requestLocale)) }} - {% if field.name == "submittedAt" %} - {{ field.value|format_datetime("medium", "short", locale=requestLocale) }} - {% else %} - {{ field.value }} - {% endif %} -
-
-
+

{% trans %}answer.form{% endtrans %}

+
+

{{ title }}

+
+ + + + +
+ + {% for field in fields %} + + + + + {% endfor %} +
{{ field.label|default(field.name|trans(locale=requestLocale)) }} + {% if field.name == "submittedAt" %} + {{ field.value|format_datetime("medium", "short", locale=requestLocale) }} + {% else %} + {{ field.value }} + {% endif %} +
+
+
{% endblock %} diff --git a/tests/NodeType/ApiResourceGeneratorTest.php b/tests/NodeType/ApiResourceGeneratorTest.php deleted file mode 100644 index 6af763db..00000000 --- a/tests/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__) . '/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__) . '/generated_api_resources/web_response.yml'); - $this->assertFileEquals( - dirname(__DIR__) . '/expected_api_resources/nstest.yml', - $resourcePath - ); - $this->assertFileEquals( - dirname(__DIR__) . '/expected_api_resources/web_response.yml', - dirname(__DIR__) . '/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__) . '/expected_api_resources/nstest.yml', - $resourcePath - ); - - $apiResourceGenerator->generate($nodeType2); - $resourcePath2 = $apiResourceGenerator->getResourcePath($nodeType2); - $this->assertFileExists($resourcePath2); - $this->assertFileEquals( - dirname(__DIR__) . '/expected_api_resources/nssecondtest.yml', - $resourcePath2 - ); - - $this->assertFileExists(dirname(__DIR__) . '/generated_api_resources/web_response.yml'); - $this->assertFileEquals( - dirname(__DIR__) . '/expected_api_resources/web_response_multiple.yml', - dirname(__DIR__) . '/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__) . '/generated_api_resources/web_response.yml'); - $this->assertFileEquals( - dirname(__DIR__) . '/expected_api_resources/web_response.yml', - dirname(__DIR__) . '/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/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 7cfee6d9..00000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,16 +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 @@ -