From 92db3dac95201e857afe002fb48146fd5ddeb71d Mon Sep 17 00:00:00 2001 From: Mohammed Date: Tue, 11 Jul 2023 20:04:05 +0100 Subject: [PATCH 01/22] #106228 - Module traduction automatique [add menu Translation] --- bundle/DependencyInjection/Configuration.php | 2 +- ...zPlatformAutomatedTranslationExtension.php | 2 +- bundle/Event/FieldEncodeEvent.php | 2 +- bundle/EventListener/MenuSubscriber.php | 33 +++++++++++++++++++ bundle/Form/Extension/TranslationAddType.php | 2 +- bundle/Resources/config/services.yml | 12 ++++--- bundle/Resources/translations/menu.en.yml | 1 + bundle/Resources/translations/menu.fr.yml | 1 + lib/Encoder/Field/FieldEncoderInterface.php | 4 +-- lib/Encoder/Field/MetasFieldEncoder.php | 4 +-- 10 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 bundle/EventListener/MenuSubscriber.php create mode 100644 bundle/Resources/translations/menu.en.yml create mode 100644 bundle/Resources/translations/menu.fr.yml diff --git a/bundle/DependencyInjection/Configuration.php b/bundle/DependencyInjection/Configuration.php index 59b720a..56374e1 100644 --- a/bundle/DependencyInjection/Configuration.php +++ b/bundle/DependencyInjection/Configuration.php @@ -8,7 +8,7 @@ namespace EzSystems\EzPlatformAutomatedTranslationBundle\DependencyInjection; -use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware; +use Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware; use Symfony\Component\Config\Definition\Builder\TreeBuilder; class Configuration extends SiteAccessAware\Configuration diff --git a/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php b/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php index 12bdbd7..06ab372 100644 --- a/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php +++ b/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php @@ -8,7 +8,7 @@ namespace EzSystems\EzPlatformAutomatedTranslationBundle\DependencyInjection; -use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\ConfigurationProcessor; +use Ibexa\Bundle\Core\DependencyInjection\Configuration\SiteAccessAware\ConfigurationProcessor; use EzSystems\EzPlatformAutomatedTranslation\Encoder\BlockAttribute\BlockAttributeEncoderInterface; use EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\FieldEncoderInterface; use Symfony\Component\Config\FileLocator; diff --git a/bundle/Event/FieldEncodeEvent.php b/bundle/Event/FieldEncodeEvent.php index 6104c38..5732b11 100644 --- a/bundle/Event/FieldEncodeEvent.php +++ b/bundle/Event/FieldEncodeEvent.php @@ -12,7 +12,7 @@ final class FieldEncodeEvent { - /** @var \eZ\Publish\API\Repository\Values\Content\Field */ + /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Field */ private $field; /** @var string */ diff --git a/bundle/EventListener/MenuSubscriber.php b/bundle/EventListener/MenuSubscriber.php new file mode 100644 index 0000000..773fd85 --- /dev/null +++ b/bundle/EventListener/MenuSubscriber.php @@ -0,0 +1,33 @@ + ['onMainMenuConfigure', 0], + ]; + } + + public function onMainMenuConfigure(ConfigureMenuEvent $event): void + { + $menu = $event->getMenu(); + + $menu[MainMenuBuilder::ITEM_CONTENT]->addChild( + 'translation_manager_translations_list', + [ + 'label' => 'translation_manager', + 'route' => 'lexik_translation_overview', + ] + ); + } +} diff --git a/bundle/Form/Extension/TranslationAddType.php b/bundle/Form/Extension/TranslationAddType.php index 481b8a5..b3135e5 100644 --- a/bundle/Form/Extension/TranslationAddType.php +++ b/bundle/Form/Extension/TranslationAddType.php @@ -105,7 +105,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) $fillMap = function ($key, &$map) use ($form) { $languages = $form->get($key); $choices = $languages->getConfig()->getAttribute('choice_list')->getChoices(); - /** @var \eZ\Publish\API\Repository\Values\Content\Language $language */ + /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Language $language */ foreach ($choices as $language) { foreach ($this->clientProvider->getClients() as $client) { $posix = $this->localeConverter->convertToPOSIX($language->languageCode); diff --git a/bundle/Resources/config/services.yml b/bundle/Resources/config/services.yml index 264fd72..6582808 100644 --- a/bundle/Resources/config/services.yml +++ b/bundle/Resources/config/services.yml @@ -4,6 +4,13 @@ services: autoconfigure: true public: false + EzSystems\EzPlatformAutomatedTranslationBundle\: + resource: '../../../bundle/*' + exclude: + - '../../../bundle/DependencyInjection/' + - '../../../bundle/Entity/' + - '../../../bundle/IbexaAutomatedTranslationBundle.php' + _instanceof: EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\FieldEncoderInterface: tags: [ 'ezplatform.automated_translation.field_encoder' ] @@ -66,10 +73,6 @@ services: EzSystems\EzPlatformAutomatedTranslation\ClientProvider: arguments: [!tagged ezplatform.automated_translation.client] - EzSystems\EzPlatformAutomatedTranslationBundle\Command\: - resource: '../../Command/*' - tags: ['console.command'] - EzSystems\EzPlatformAutomatedTranslationBundle\Form\Extension\TranslationAddType: tags: - { name: form.type_extension, extended_type: Ibexa\AdminUi\Form\Type\Content\Translation\TranslationAddType } @@ -84,4 +87,3 @@ services: tags: - { name: form.type_extension, extended_type: Ibexa\AdminUi\Form\Type\Language\LanguageCreateType } - EzSystems\EzPlatformAutomatedTranslationBundle\EventListener\ContentProxyTranslateListener: ~ diff --git a/bundle/Resources/translations/menu.en.yml b/bundle/Resources/translations/menu.en.yml new file mode 100644 index 0000000..5580a1c --- /dev/null +++ b/bundle/Resources/translations/menu.en.yml @@ -0,0 +1 @@ +translation_manager: Translation diff --git a/bundle/Resources/translations/menu.fr.yml b/bundle/Resources/translations/menu.fr.yml new file mode 100644 index 0000000..5d1abea --- /dev/null +++ b/bundle/Resources/translations/menu.fr.yml @@ -0,0 +1 @@ +translation_manager: Traductions diff --git a/lib/Encoder/Field/FieldEncoderInterface.php b/lib/Encoder/Field/FieldEncoderInterface.php index 8024ff0..e410726 100644 --- a/lib/Encoder/Field/FieldEncoderInterface.php +++ b/lib/Encoder/Field/FieldEncoderInterface.php @@ -8,8 +8,8 @@ namespace EzSystems\EzPlatformAutomatedTranslation\Encoder\Field; -use eZ\Publish\API\Repository\Values\Content\Field; -use eZ\Publish\Core\FieldType\Value; +use Ibexa\Contracts\Core\Repository\Values\Content\Field; +use Ibexa\Core\FieldType\Value; interface FieldEncoderInterface { diff --git a/lib/Encoder/Field/MetasFieldEncoder.php b/lib/Encoder/Field/MetasFieldEncoder.php index 8fa56be..6db485c 100644 --- a/lib/Encoder/Field/MetasFieldEncoder.php +++ b/lib/Encoder/Field/MetasFieldEncoder.php @@ -2,8 +2,8 @@ namespace EzSystems\EzPlatformAutomatedTranslation\Encoder\Field; -use eZ\Publish\API\Repository\Values\Content\Field; -use eZ\Publish\Core\FieldType\Value; +use Ibexa\Contracts\Core\Repository\Values\Content\Field; +use Ibexa\Core\FieldType\Value; use Novactive\Bundle\eZSEOBundle\Core\FieldType\Metas\Value as MetasValue; use Symfony\Component\Serializer\Encoder\XmlEncoder; From 9172540498cae75b84192a47b4a53ea813bb2f07 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Wed, 12 Jul 2023 19:33:30 +0100 Subject: [PATCH 02/22] #106228 - Module traduction automatique [policy] --- bundle/Command/TranslateContentCommand.php | 6 +- bundle/Controller/BaseController.php | 87 +++++++++++++++++++ .../AutoTranslationPolicyProvider.php | 26 ++++++ bundle/Event/FieldEncodeEvent.php | 2 +- .../ContentProxyTranslateListener.php | 8 +- bundle/EventListener/MenuSubscriber.php | 29 +++++-- .../EzPlatformAutomatedTranslationBundle.php | 3 + bundle/Form/Extension/TranslationAddType.php | 3 +- bundle/Resources/config/policies.yaml | 4 + bundle/Resources/config/routing.yaml | 3 + bundle/Resources/translations/forms.en.xliff | 36 ++++++++ bundle/Resources/translations/forms.en.yml | 1 - bundle/Resources/translations/menu.en.yaml | 1 + bundle/Resources/translations/menu.en.yml | 1 - bundle/Resources/translations/menu.fr.yaml | 1 + bundle/Resources/translations/menu.fr.yml | 1 - .../Resources/translations/messages.en.yaml | 3 + bundle/Resources/translations/messages.en.yml | 2 - 18 files changed, 195 insertions(+), 22 deletions(-) create mode 100644 bundle/Controller/BaseController.php create mode 100644 bundle/DependencyInjection/Security/Provider/AutoTranslationPolicyProvider.php create mode 100644 bundle/Resources/config/policies.yaml create mode 100644 bundle/Resources/config/routing.yaml create mode 100644 bundle/Resources/translations/forms.en.xliff delete mode 100644 bundle/Resources/translations/forms.en.yml create mode 100644 bundle/Resources/translations/menu.en.yaml delete mode 100644 bundle/Resources/translations/menu.en.yml create mode 100644 bundle/Resources/translations/menu.fr.yaml delete mode 100644 bundle/Resources/translations/menu.fr.yml create mode 100644 bundle/Resources/translations/messages.en.yaml delete mode 100644 bundle/Resources/translations/messages.en.yml diff --git a/bundle/Command/TranslateContentCommand.php b/bundle/Command/TranslateContentCommand.php index 77d486c..7751632 100644 --- a/bundle/Command/TranslateContentCommand.php +++ b/bundle/Command/TranslateContentCommand.php @@ -29,13 +29,13 @@ final class TranslateContentCommand extends Command /** @var ClientProvider */ private $clientProvider; - /** @var \Ibexa\Contracts\Core\Repository\ContentService */ + /** @var ContentService */ private $contentService; - /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver */ + /** @var PermissionResolver */ private $permissionResolver; - /** @var \Ibexa\Contracts\Core\Repository\UserService */ + /** @var UserService */ private $userService; public function __construct( diff --git a/bundle/Controller/BaseController.php b/bundle/Controller/BaseController.php new file mode 100644 index 0000000..93cd1ba --- /dev/null +++ b/bundle/Controller/BaseController.php @@ -0,0 +1,87 @@ +permissionResolver = $permissionResolver; + } + + /** + * @required + */ + public function setTranslator(TranslatorInterface $translator): void + { + $this->translator = $translator; + } + + /** + * @required + */ + public function setNotificationHandler(NotificationHandlerInterface $notificationHandler): void + { + $this->notificationHandler = $notificationHandler; + } + + /** + * @throws InvalidArgumentException + */ + protected function permissionAccess(string $module, string $function) + { + if (!$this->permissionResolver->hasAccess($module, $function)) { + $exception = $this->createAccessDeniedException($this->translator->trans( + 'auto_translation.permission.failed' + )); + $exception->setAttributes(null); + $exception->setSubject(null); + + throw $exception; + } + + return null; + } + + /** + * @throws InvalidArgumentException + */ + protected function permissionManageAccess(string $module, array $functions): array + { + $access = []; + foreach ($functions as $function) { + $access[$function] = true; + if (!$this->permissionResolver->hasAccess($module, $function)) { + $access[$function] = false; + } + } + + return $access; + } +} diff --git a/bundle/DependencyInjection/Security/Provider/AutoTranslationPolicyProvider.php b/bundle/DependencyInjection/Security/Provider/AutoTranslationPolicyProvider.php new file mode 100644 index 0000000..d187a67 --- /dev/null +++ b/bundle/DependencyInjection/Security/Provider/AutoTranslationPolicyProvider.php @@ -0,0 +1,26 @@ +permissionResolver = $permissionResolver; + } + /** * @inheritDoc */ @@ -21,13 +33,14 @@ public static function getSubscribedEvents(): array public function onMainMenuConfigure(ConfigureMenuEvent $event): void { $menu = $event->getMenu(); - - $menu[MainMenuBuilder::ITEM_CONTENT]->addChild( - 'translation_manager_translations_list', - [ - 'label' => 'translation_manager', - 'route' => 'lexik_translation_overview', - ] - ); + if ($this->permissionResolver->hasAccess('auto_translation', 'view')) { + $menu[MainMenuBuilder::ITEM_CONTENT]->addChild( + 'content_translation_translations_list', + [ + 'label' => 'content_translation', + 'route' => 'automated_translation_index', + ] + ); + } } } diff --git a/bundle/EzPlatformAutomatedTranslationBundle.php b/bundle/EzPlatformAutomatedTranslationBundle.php index 5646e0f..11e2f53 100644 --- a/bundle/EzPlatformAutomatedTranslationBundle.php +++ b/bundle/EzPlatformAutomatedTranslationBundle.php @@ -8,6 +8,7 @@ namespace EzSystems\EzPlatformAutomatedTranslationBundle; +use EzSystems\EzPlatformAutomatedTranslationBundle\DependencyInjection\Security\Provider\AutoTranslationPolicyProvider; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -21,5 +22,7 @@ public function getParent(): ?string public function build(ContainerBuilder $container): void { parent::build($container); + $ibexaExtension = $container->getExtension('ibexa'); + $ibexaExtension->addPolicyProvider(new AutoTranslationPolicyProvider()); } } diff --git a/bundle/Form/Extension/TranslationAddType.php b/bundle/Form/Extension/TranslationAddType.php index b3135e5..0f2224c 100644 --- a/bundle/Form/Extension/TranslationAddType.php +++ b/bundle/Form/Extension/TranslationAddType.php @@ -8,6 +8,7 @@ namespace EzSystems\EzPlatformAutomatedTranslationBundle\Form\Extension; +use Ibexa\Contracts\Core\Repository\Values\Content\Language; use Ibexa\Core\MVC\Symfony\Locale\LocaleConverterInterface; use Ibexa\AdminUi\Form\Type\Content\Translation\TranslationAddType as BaseTranslationAddType; use EzSystems\EzPlatformAutomatedTranslation\Client\ClientInterface; @@ -105,7 +106,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) $fillMap = function ($key, &$map) use ($form) { $languages = $form->get($key); $choices = $languages->getConfig()->getAttribute('choice_list')->getChoices(); - /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Language $language */ + /** @var Language $language */ foreach ($choices as $language) { foreach ($this->clientProvider->getClients() as $client) { $posix = $this->localeConverter->convertToPOSIX($language->languageCode); diff --git a/bundle/Resources/config/policies.yaml b/bundle/Resources/config/policies.yaml new file mode 100644 index 0000000..3f890b9 --- /dev/null +++ b/bundle/Resources/config/policies.yaml @@ -0,0 +1,4 @@ +auto_translation: + view: ~ + create: ~ + delete: ~ diff --git a/bundle/Resources/config/routing.yaml b/bundle/Resources/config/routing.yaml new file mode 100644 index 0000000..f279320 --- /dev/null +++ b/bundle/Resources/config/routing.yaml @@ -0,0 +1,3 @@ +automated_translation: + resource: "@EzPlatformAutomatedTranslationBundle/Controller" + type: annotation \ No newline at end of file diff --git a/bundle/Resources/translations/forms.en.xliff b/bundle/Resources/translations/forms.en.xliff new file mode 100644 index 0000000..c78305b --- /dev/null +++ b/bundle/Resources/translations/forms.en.xliff @@ -0,0 +1,36 @@ + + + +
+ + The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. +
+ + + Automated Translation + Automated Translation + key: role.policy.auto_translation + + + Automated Translation/ All functions + Automated Translation/ All functions + key: role.policy.auto_translation.all_functions + + + Automated Translation/ View + Automated Translation/ View + key: role.policy.auto_translation.view + + + Automated Translation/ Create + Automated Translation/ Create + key: role.policy.auto_translation.create + + + Automated Translation/ Delete + Automated Translation/ Delete + key: role.policy.auto_translation.delete + + +
+
diff --git a/bundle/Resources/translations/forms.en.yml b/bundle/Resources/translations/forms.en.yml deleted file mode 100644 index e956eae..0000000 --- a/bundle/Resources/translations/forms.en.yml +++ /dev/null @@ -1 +0,0 @@ -no-service: "Select a translation service" diff --git a/bundle/Resources/translations/menu.en.yaml b/bundle/Resources/translations/menu.en.yaml new file mode 100644 index 0000000..f8da04e --- /dev/null +++ b/bundle/Resources/translations/menu.en.yaml @@ -0,0 +1 @@ +content_translation: Contents Translation diff --git a/bundle/Resources/translations/menu.en.yml b/bundle/Resources/translations/menu.en.yml deleted file mode 100644 index 5580a1c..0000000 --- a/bundle/Resources/translations/menu.en.yml +++ /dev/null @@ -1 +0,0 @@ -translation_manager: Translation diff --git a/bundle/Resources/translations/menu.fr.yaml b/bundle/Resources/translations/menu.fr.yaml new file mode 100644 index 0000000..4789a62 --- /dev/null +++ b/bundle/Resources/translations/menu.fr.yaml @@ -0,0 +1 @@ +content_translation: Traductions des Contenus diff --git a/bundle/Resources/translations/menu.fr.yml b/bundle/Resources/translations/menu.fr.yml deleted file mode 100644 index 5d1abea..0000000 --- a/bundle/Resources/translations/menu.fr.yml +++ /dev/null @@ -1 +0,0 @@ -translation_manager: Traductions diff --git a/bundle/Resources/translations/messages.en.yaml b/bundle/Resources/translations/messages.en.yaml new file mode 100644 index 0000000..0096023 --- /dev/null +++ b/bundle/Resources/translations/messages.en.yaml @@ -0,0 +1,3 @@ +tab.translations.remote.translation.service: 'Use automatic translation' +tab.translations.remote.translation.service.with: 'Use automatic translation (%alias%)' +auto_translation.permission.failed: 'You don''t have the permission to acces to the Content Translation interface' \ No newline at end of file diff --git a/bundle/Resources/translations/messages.en.yml b/bundle/Resources/translations/messages.en.yml deleted file mode 100644 index 8d12082..0000000 --- a/bundle/Resources/translations/messages.en.yml +++ /dev/null @@ -1,2 +0,0 @@ -tab.translations.remote.translation.service: "Use automatic translation" -tab.translations.remote.translation.service.with: "Use automatic translation (%alias%)" From 24d5d622c68902f3fae2cfe5d864e4d7cd9ca69f Mon Sep 17 00:00:00 2001 From: Mohammed Date: Mon, 24 Jul 2023 00:50:33 +0100 Subject: [PATCH 03/22] #106228 - Module traduction automatique [page liste] --- .../TranslationSubtreeController.php | 113 +++++++++++ ...zPlatformAutomatedTranslationExtension.php | 22 ++- bundle/Entity/AutoTranslationActions.php | 186 ++++++++++++++++++ .../Form/AutoTranslationActionsSearchType.php | 49 +++++ bundle/Form/AutoTranslationActionsType.php | 170 ++++++++++++++++ bundle/Form/SubtreeLocationType.php | 64 ++++++ .../Handler/AutoTranslationActionsHandler.php | 120 +++++++++++ .../AutoTranslationActionsRepository.php | 9 + bundle/Resources/config/ezadminui.yml | 7 + bundle/Resources/config/services.yml | 10 + .../config/universal_discovery_widget.yaml | 7 + bundle/Resources/doc/INSTALL.md | 5 +- bundle/Resources/encore/ez.config.js | 5 - bundle/Resources/encore/ibexa.config.js | 15 ++ .../public/admin/js/auto-translation.js | 92 +++++++++ .../components/selected.location.component.js | 34 ++++ .../selected.locations.component.js | 39 ++++ .../auto-translation-ezobjectrelationlist.js | 40 ++++ .../validator/auto-translation-ezselection.js | 49 +++++ .../public/admin/scss/auto-translation.scss | 30 +++ .../Resources/translations/messages.en.yaml | 25 ++- .../Resources/translations/messages.fr.yaml | 26 +++ .../views/ezadminui/stylesheet.html.twig | 1 + .../Form/auto_translation/form.html.twig | 6 + .../auto_translation/form_fields.html.twig | 53 +++++ .../auto_translation/form_search.html.twig | 6 + .../search_form_fields.html.twig | 5 + .../admin/auto_translation/view.html.twig | 124 ++++++++++++ .../remote_translate_form_fields.html.twig | 1 + 29 files changed, 1305 insertions(+), 8 deletions(-) create mode 100644 bundle/Controller/TranslationSubtreeController.php create mode 100644 bundle/Entity/AutoTranslationActions.php create mode 100644 bundle/Form/AutoTranslationActionsSearchType.php create mode 100755 bundle/Form/AutoTranslationActionsType.php create mode 100644 bundle/Form/SubtreeLocationType.php create mode 100644 bundle/Handler/AutoTranslationActionsHandler.php create mode 100644 bundle/Repository/AutoTranslationActionsRepository.php create mode 100644 bundle/Resources/config/universal_discovery_widget.yaml delete mode 100644 bundle/Resources/encore/ez.config.js create mode 100644 bundle/Resources/encore/ibexa.config.js create mode 100644 bundle/Resources/public/admin/js/auto-translation.js create mode 100644 bundle/Resources/public/admin/js/components/selected.location.component.js create mode 100644 bundle/Resources/public/admin/js/components/selected.locations.component.js create mode 100644 bundle/Resources/public/admin/js/validator/auto-translation-ezobjectrelationlist.js create mode 100644 bundle/Resources/public/admin/js/validator/auto-translation-ezselection.js create mode 100755 bundle/Resources/public/admin/scss/auto-translation.scss create mode 100644 bundle/Resources/translations/messages.fr.yaml create mode 100644 bundle/Resources/views/ezadminui/stylesheet.html.twig create mode 100644 bundle/Resources/views/themes/admin/Form/auto_translation/form.html.twig create mode 100644 bundle/Resources/views/themes/admin/Form/auto_translation/form_fields.html.twig create mode 100644 bundle/Resources/views/themes/admin/Form/auto_translation/form_search.html.twig create mode 100644 bundle/Resources/views/themes/admin/Form/auto_translation/search_form_fields.html.twig create mode 100644 bundle/Resources/views/themes/admin/auto_translation/view.html.twig diff --git a/bundle/Controller/TranslationSubtreeController.php b/bundle/Controller/TranslationSubtreeController.php new file mode 100644 index 0000000..ab2b8c7 --- /dev/null +++ b/bundle/Controller/TranslationSubtreeController.php @@ -0,0 +1,113 @@ +permissionAccess('auto_translation', 'view'); + + $formSearch = $this->createForm(AutoTranslationActionsSearchType::class , null, [ + 'action' => $this->generateUrl('automated_translation_index'), + 'method' => 'GET', + ]); + + $formSearch->handleRequest($request); + $page = $request->query->get('page' ,1); + + $adapter = new QueryAdapter( + $handler->getAllQuery($formSearch->getData()['sort'] ?? []), + function ($queryBuilder) use ($handler) { + return $handler->countAll($queryBuilder); + }); + $pagerfanta = new Pagerfanta( + $adapter + ); + + $pagerfanta->setMaxPerPage($this->defaultPaginationLimit); + $pagerfanta->setCurrentPage($page); + + $autoTranslationActions = new AutoTranslationActions(); + $form = $this->createForm(AutoTranslationActionsType::class, $autoTranslationActions, [ + 'action' => $this->generateUrl('automated_translation_add'), + 'method' => 'POST', + ]); + + return $this->render( '@ibexadesign/auto_translation/view.html.twig', [ + 'form' => $form->createView(), + 'form_search' => $formSearch->createView(), + 'pager' => $pagerfanta, + 'canCreate' => $this->permissionResolver->hasAccess('auto_translation', 'create'), + 'canDelete' => $this->permissionResolver->hasAccess('auto_translation', 'delete'), + ]); + } + /** + * @Route("/add", name="automated_translation_add") + */ + public function addAction( + Request $request, + EntityManagerInterface $em, + LocationService $locationService, + TranslatableNotificationHandlerInterface $notificationHandler + ): RedirectResponse { + $this->permissionAccess('auto_translation', 'create'); + + $autoTranslationActions = new AutoTranslationActions(); + $form = $this->createForm(AutoTranslationActionsType::class, $autoTranslationActions); + $form->handleRequest($request); + /** + * @var User $user + */ + $user = $this->getUser(); + + if ($form->isSubmitted() && $form->isValid()) { + $autoTranslationActions->setStatus(AutoTranslationActions::STATUS_PENDING); + $autoTranslationActions->setUserId($user->getAPIUser()->id); + $em->persist($autoTranslationActions); + $em->flush(); + try { + $location = $locationService->loadLocation($autoTranslationActions->getSubtreeId()); + $this->notificationHandler->success($this->translator->trans( + 'auto_translation.add.success' ,['%subtree_name%' => $location->contentInfo->name ] + )); + } catch (NotFoundException|UnauthorizedException $e) { + $this->notificationHandler->error($e->getMessage()); + } + } + + return new RedirectResponse($this->generateUrl( + 'automated_translation_index' + )); + } +} diff --git a/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php b/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php index 06ab372..6ed6867 100644 --- a/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php +++ b/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php @@ -12,11 +12,14 @@ use EzSystems\EzPlatformAutomatedTranslation\Encoder\BlockAttribute\BlockAttributeEncoderInterface; use EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\FieldEncoderInterface; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\Yaml\Yaml; -class EzPlatformAutomatedTranslationExtension extends Extension +class EzPlatformAutomatedTranslationExtension extends Extension implements PrependExtensionInterface { /** * {@inheritdoc} @@ -66,4 +69,21 @@ private function hasConfiguredClients(array $config, ContainerBuilder $container }); })); } + + /** + * Allow an extension to prepend the extension configurations. + */ + public function prepend(ContainerBuilder $container): void + { + $configs = [ + 'universal_discovery_widget.yaml' => 'ibexa', + ]; + + foreach ($configs as $fileName => $extensionName) { + $configFile = __DIR__.'/../Resources/config/'.$fileName; + $config = Yaml::parse(file_get_contents($configFile)); + $container->prependExtensionConfig($extensionName, $config); + $container->addResource(new FileResource($configFile)); + } + } } diff --git a/bundle/Entity/AutoTranslationActions.php b/bundle/Entity/AutoTranslationActions.php new file mode 100644 index 0000000..c07fe1b --- /dev/null +++ b/bundle/Entity/AutoTranslationActions.php @@ -0,0 +1,186 @@ +id; + } + + public function getSubtreeId(): int + { + return $this->subtreeId; + } + + public function setSubtreeId(int $subtreeId): self + { + $this->subtreeId = $subtreeId; + return $this; + } + + public function getUserId(): int + { + return $this->userId; + } + + public function setUserId(int $userId): self + { + $this->userId = $userId; + return $this; + } + + public function getTargetLanguage(): string + { + return $this->targetLanguage; + } + + public function setTargetLanguage(string $targetLanguage): self + { + $this->targetLanguage = $targetLanguage; + return $this; + } + + public function isOverwrite(): bool + { + return $this->overwrite; + } + + public function setOverwrite(bool $overwrite): self + { + $this->overwrite = $overwrite; + return $this; + } + + public function getStatus(): string + { + return $this->status; + } + + public function setStatus(string $status): self + { + $this->status = $status; + return $this; + } + + public function setCreatedAt(DateTime $createdAt): self + { + $this->createdAt = $createdAt; + return $this; + } + + public function getCreatedAt(): ?DateTime + { + return $this->createdAt; + } + + /** + * @return DateTime|null + */ + public function getFinishedAt(): ?DateTime + { + return $this->finishedAt; + } + + /** + * @param DateTime|null $finishedAt + */ + public function setFinishedAt(?DateTime $finishedAt = null): void + { + $this->finishedAt = $finishedAt; + } + + /** + * @ORM\PrePersist + */ + public function updatedTimestamps(): void + { + $dateTimeNow = new DateTime('now'); + if ($this->getCreatedAt() === null) { + $this->setCreatedAt($dateTimeNow); + } + } +} diff --git a/bundle/Form/AutoTranslationActionsSearchType.php b/bundle/Form/AutoTranslationActionsSearchType.php new file mode 100644 index 0000000..fde558d --- /dev/null +++ b/bundle/Form/AutoTranslationActionsSearchType.php @@ -0,0 +1,49 @@ +add('sort', SortType::class, [ + 'row_attr' => [ + 'hidden' => 'hidden' + ], + 'sort_fields' => ['user_name', 'content_name', 'target_language', 'created_at' ,'overwrite' ,'status'], + 'default' => ['field' => 'created_at', 'direction' => '1'], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults( + [ + 'attr' => ['__template' => self::TEMPLATE], + 'method' => Request::METHOD_GET, + 'csrf_protection' => false, + ] + ); + } +} diff --git a/bundle/Form/AutoTranslationActionsType.php b/bundle/Form/AutoTranslationActionsType.php new file mode 100755 index 0000000..9d91fd1 --- /dev/null +++ b/bundle/Form/AutoTranslationActionsType.php @@ -0,0 +1,170 @@ +em = $em; + $this->locationService = $locationService; + $this->choicesTransformer = $choicesTransformer; + $this->siteListService = $siteListService; + $this->languageService = $languageService; + $this->translator = $translator; + $this->notificationHandler = $notificationHandler; + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $languages = $this->languageService->loadLanguages(); + $defaultLanguage = new Language([ + 'languageCode' => '', + 'name' => 'auto_translation.form.target_language.default_language' + ]); + + $builder + ->add( + 'subtree_id', + SubtreeLocationType::class, + [ + 'compound' => true, + 'label' => 'auto_translation.form.subtree_id.label', + 'row_attr' => [ + 'class' => 'ibexa-field-edit auto-translation-field--ezobjectrelationlist' + ] , + 'attr' => [ + 'class' => 'btn-udw-trigger ibexa-button-tree pure-button + ibexa-font-icon ibexa-btn btn ibexa-btn--secondary + js-auto_translation-select-location-id' + ], + 'empty_data' => [], + ] + ) + ->add( + 'target_language', + ChoiceType::class, + [ + 'required' => true, + 'compound' => false, + 'choices' => [...[$defaultLanguage], ...$languages], + 'setter' => function (AutoTranslationActions $autoTranslationActions, ?Language $language, FormInterface $form): void { + $autoTranslationActions->setTargetLanguage($language->getLanguageCode()); + }, + 'choice_value' => function (?Language $language): string { + return $language ? $language->getLanguageCode() : ''; + }, + 'choice_label' => function (?Language $language): string { + return $language ? $this->translator->trans($language->getName()) : ''; + }, + 'label' => 'auto_translation.form.target_language.label', + 'row_attr' => [ + 'class' => 'ibexa-field-edit auto-translation-field--ezselection' + ], + 'attr' => [ + 'class' => 'ibexa-data-source__input ibexa-data-source__input--selection ibexa-input ibexa-input--select form-select' + ], + ] + ) + ->add( + 'overwrite', + CheckboxType::class, + + [ + 'required' => false, + 'label' => 'auto_translation.form.overwrite.label' + ] + ) + ->add( + 'submit', + SubmitType::class, + [ + 'label' => false + ] + ) + ->addEventListener(FormEvents::POST_SUBMIT, function (PostSubmitEvent $event) { + /** @var Form $form */ + $form = $event->getForm(); + /** @var AutoTranslationActions $autoTranslationActions */ + $autoTranslationActions = $form->getData(); + $count = $this->em->getRepository(AutoTranslationActions::class)->count([ + 'subtreeId' => $autoTranslationActions->getSubtreeId(), + 'targetLanguage' => $autoTranslationActions->getTargetLanguage(), + 'status' => [AutoTranslationActions::STATUS_PENDING, AutoTranslationActions::STATUS_IN_PROGRESS], + ]); + if ($count) { + try { + $location = $this->locationService->loadLocation($autoTranslationActions->getSubtreeId()); + $message = $this->translator->trans('auto_translation.add.failed', [ + '%subtree_name%' => $location->contentInfo->name + ]); + $event->getForm()->addError(new FormError($message)); + $this->notificationHandler->error($message); + } catch (NotFoundException|UnauthorizedException $e) { + $event->getForm()->addError(new FormError($e->getMessage())); + $this->notificationHandler->error($e->getMessage()); + } + } + }) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults( + [ + 'data_class' => AutoTranslationActions::class, + 'attr' => ['__template' => self::TEMPLATE] + ] + ); + } +} diff --git a/bundle/Form/SubtreeLocationType.php b/bundle/Form/SubtreeLocationType.php new file mode 100644 index 0000000..5a17e26 --- /dev/null +++ b/bundle/Form/SubtreeLocationType.php @@ -0,0 +1,64 @@ +add( + 'location', + IntegerType::class, + [ + 'required' => true, + 'attr' => ['hidden' => true], + 'label' => false, + 'empty_data' => [], + ] + ) + ->addModelTransformer($this->getDataTransformer()) + ; + } + + private function getDataTransformer(): DataTransformerInterface + { + return new CallbackTransformer( + function ($value) { + if (null === $value || 0 === $value) { + return $value; + } + + return ['location' => !empty($value) ? $value : null]; + }, + function ($value) { + if (\is_array($value) && array_key_exists('location', $value)) { + return $value['location'] ?? null; + } + + return $value; + } + ); + } + + public function getName(): string + { + return 'subtree_location'; + } +} diff --git a/bundle/Handler/AutoTranslationActionsHandler.php b/bundle/Handler/AutoTranslationActionsHandler.php new file mode 100644 index 0000000..aebd7f8 --- /dev/null +++ b/bundle/Handler/AutoTranslationActionsHandler.php @@ -0,0 +1,120 @@ +connection = $connection; + $this->criteriaConverter = $criteriaConverter; + $this->permissionCriterionResolver = $permissionCriterionResolver; + } + + public function getAllQuery(array $sort = []): QueryBuilder + { + $selectQuery = $this->getSelectQuery(); + + $selectQuery + ->addSelect('c.name as content_name') + ->addSelect('us.name as user_name') + ->addSelect('c.id as content_id') + ->innerJoin( + 'at', + LocationGateway::CONTENT_TREE_TABLE, + 't', + 't.node_id = at.subtree_id' + ) + ->innerJoin( + 't', + ContentGateway::CONTENT_ITEM_TABLE, + 'c', + 'c.id = t.contentobject_id' + ) + ->innerJoin( + 'at', + ContentGateway::CONTENT_ITEM_TABLE, + 'us', + 'us.id = at.user_id' + ) + ->andWhere( + $selectQuery->expr()->eq('c.status', ':content_status') + ) + ->setParameter(':content_status', ContentInfo::STATUS_PUBLISHED, ParameterType::INTEGER) + ; + if (isset($sort['field'])) { + $selectQuery->orderBy($sort['field'], ($sort['direction'] ?? 0) == 0 ? 'DESC' : 'ASC'); + } + // Check read access to whole source subtree + $permissionCriterion = $this->permissionCriterionResolver->getPermissionsCriterion( + 'content', + 'read' + ); + if ($permissionCriterion !== true && $permissionCriterion !== false) { + $query = new Query(); + $query->filter = new LogicalAnd( + [ + new Criterion\MatchAll(), + $permissionCriterion, + ] + ); + $selectQuery->andWhere($this->criteriaConverter->convertCriteria($selectQuery, $permissionCriterion, [])); + } + + return $selectQuery; + } + + + /** + * @return QueryBuilder + */ + private function getSelectQuery(): QueryBuilder + { + $selectQuery = $this->connection->createQueryBuilder(); + $selectQuery + ->select('at.*') + ->from(self::TABLE_NAME, 'at') + ; + + return $selectQuery; + } + + public function countAll(QueryBuilder $queryBuilder): int + { + $queryBuilder->select('COUNT(at.id)'); + $queryBuilder->orderBy('at.id'); + $queryBuilder->limit = 0; + + return (int) $queryBuilder->execute()->fetchOne(); + } +} + diff --git a/bundle/Repository/AutoTranslationActionsRepository.php b/bundle/Repository/AutoTranslationActionsRepository.php new file mode 100644 index 0000000..ba743fc --- /dev/null +++ b/bundle/Repository/AutoTranslationActionsRepository.php @@ -0,0 +1,9 @@ + { - Encore.addEntry('ezplatform-automated-translation-js', [path.resolve(__dirname, '../public/admin/js/ezplatformautomatedtranslation.js')]); -}; \ No newline at end of file diff --git a/bundle/Resources/encore/ibexa.config.js b/bundle/Resources/encore/ibexa.config.js new file mode 100644 index 0000000..301a27b --- /dev/null +++ b/bundle/Resources/encore/ibexa.config.js @@ -0,0 +1,15 @@ +const path = require('path'); + +module.exports = (Encore) => { + Encore.addEntry('ezplatform-automated-translation-js', [ + path.resolve('./public/bundles/ibexaadminui/js/scripts/admin.content.edit.js'), + path.resolve('./public/bundles/ibexaadminui/js/scripts/fieldType/base/base-field.js'), + path.resolve(__dirname, '../public/admin/js/validator/auto-translation-ezselection.js'), + path.resolve(__dirname, '../public/admin/js/validator/auto-translation-ezobjectrelationlist.js'), + path.resolve(__dirname, '../public/admin/js/ezplatformautomatedtranslation.js'), + path.resolve(__dirname, '../public/admin/js/auto-translation.js'), + ]); + Encore.addEntry('ezplatform-automated-translation-css', [ + path.resolve(__dirname, '../public/admin/scss/auto-translation.scss'), + ]); +}; \ No newline at end of file diff --git a/bundle/Resources/public/admin/js/auto-translation.js b/bundle/Resources/public/admin/js/auto-translation.js new file mode 100644 index 0000000..856ac35 --- /dev/null +++ b/bundle/Resources/public/admin/js/auto-translation.js @@ -0,0 +1,92 @@ +import SelectedLocationsComponent from './components/selected.locations.component.js'; +const { ibexa } = window; + +(function (global, doc) { + const udwContainer = doc.querySelector('#react-udw'); + const formSearch = doc.querySelector('form[name="auto_translation_actions_search"]'); + const sortableColumns = doc.querySelectorAll('.ibexa-table__sort-column'); + const sortedActiveField = doc.querySelector('#auto_translation_actions_search_sort_field').value; + const sortedActiveDirection = doc.querySelector('#auto_translation_actions_search_sort_direction').value; + const sortField = doc.querySelector('#auto_translation_actions_search_sort_field'); + const sortDirection = doc.querySelector('#auto_translation_actions_search_sort_direction'); + const CLASS_SORTED_ASC = 'ibexa-table__sort-column--asc'; + const CLASS_SORTED_DESC = 'ibexa-table__sort-column--desc'; + + const closeUDW = () => ReactDOM.unmountComponentAtNode(udwContainer); + const selectLocationBut = doc.querySelector('.js-auto_translation-select-location-id'); + function notify(message, type = 'info') { + if (!message) return; + const eventInfo = new CustomEvent('ibexa-notify', { + detail: { + label: type, + message: message + } + }); + document.body.dispatchEvent(eventInfo); + } + if (selectLocationBut) { + let udwRoot = null; + + selectLocationBut.addEventListener('click', function (e) { + e.preventDefault(); + const clickedButton = e.target; + const config = JSON.parse(e.currentTarget.dataset.udwConfig); + const selectedLocationList = doc.querySelector(clickedButton.dataset.selectedLocationListSelector); + ReactDOM.render(React.createElement(ibexa.modules.UniversalDiscovery, { + onConfirm: (data) => { + ReactDOM.render(React.createElement(SelectedLocationsComponent, { + items: data, + onDelete: (locations) => { + doc.querySelector(clickedButton.dataset.locationInputSelector).value = locations.map(location => location.id).join(); + } + }), selectedLocationList); + + doc.querySelector(clickedButton.dataset.locationInputSelector).value = data.map(location => location.id).join(); + closeUDW(); + const formError = clickedButton.closest('.auto-translation-field--ezobjectrelationlist') + .querySelector('.ibexa-form-error'); + if(formError) { + formError.innerHTML = ''; + } + }, + onCancel: () => { + closeUDW(); + }, + ...config + }), udwContainer); + }); + } + const sortItems = (event) => { + const { target } = event; + const { field, direction } = target.dataset; + + sortField.value = field; + target.dataset.direction = direction === 'ASC' ? 'DESC' : 'ASC'; + sortDirection.setAttribute('value', direction === 'DESC' ? 1 : 0); + formSearch.submit(); + }; + + const setSortedClass = () => { + doc.querySelectorAll('.ibexa-table__sort-column').forEach((node) => { + node.classList.remove(CLASS_SORTED_ASC, CLASS_SORTED_DESC); + }); + + if (sortedActiveField) { + const sortedFieldNode = doc.querySelector(`.ibexa-table__sort-column--${sortedActiveField}`); + + if (!sortedFieldNode) { + return; + } + + if (parseInt(sortedActiveDirection, 10) === 1) { + sortedFieldNode.classList.add(CLASS_SORTED_ASC); + } else { + sortedFieldNode.classList.add(CLASS_SORTED_DESC); + } + } + }; + + setSortedClass(); + sortableColumns.forEach((column) => column.addEventListener('click', sortItems, false)); + +})(window, document); diff --git a/bundle/Resources/public/admin/js/components/selected.location.component.js b/bundle/Resources/public/admin/js/components/selected.location.component.js new file mode 100644 index 0000000..7ce13c2 --- /dev/null +++ b/bundle/Resources/public/admin/js/components/selected.location.component.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Icon from '@ibexa-admin-ui/src/bundle/ui-dev/src/modules/common/icon/icon'; + +const SelectedLocationComponent = (props) => { + const { location, onDelete } = props; + const handleClick = function (event) { + onDelete(event.currentTarget.dataset.locationId); + }; + return ( +
  • +
    + + + + {location.ContentInfo.Content.Name} + + + +
    +
  • + ); +}; + +SelectedLocationComponent.propTypes = { + location: PropTypes.object, + onDelete: PropTypes.func, +}; + +SelectedLocationComponent.defaultProps = { + location: null +}; + +export default SelectedLocationComponent; diff --git a/bundle/Resources/public/admin/js/components/selected.locations.component.js b/bundle/Resources/public/admin/js/components/selected.locations.component.js new file mode 100644 index 0000000..d0867af --- /dev/null +++ b/bundle/Resources/public/admin/js/components/selected.locations.component.js @@ -0,0 +1,39 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; + +import SelectedLocationComponent from "./selected.location.component"; + +const SelectedLocationsComponent = (props) => { + const { items, onDelete } = props; + const [locations, setLocations] = useState([]); + const handleClick = function (locationId) { + setLocations(() => { + const results = locations.filter(location => parseInt(location.id) !== parseInt(locationId)); + onDelete(results); + return results; + }); + + }; + useEffect(() => { + setLocations((prevState) => [...prevState, ...items]); + }, [items]); + + return ( +
      + {locations.map((location) => ( + + ))} +
    + ); +}; + +SelectedLocationsComponent.propTypes = { + items: PropTypes.array, + onDelete: PropTypes.func, +}; + +SelectedLocationsComponent.defaultProps = { + items: [], +}; + +export default SelectedLocationsComponent; diff --git a/bundle/Resources/public/admin/js/validator/auto-translation-ezobjectrelationlist.js b/bundle/Resources/public/admin/js/validator/auto-translation-ezobjectrelationlist.js new file mode 100644 index 0000000..e050bb2 --- /dev/null +++ b/bundle/Resources/public/admin/js/validator/auto-translation-ezobjectrelationlist.js @@ -0,0 +1,40 @@ +(function (global, doc, ibexa) { + const SELECTOR_FIELD = '.auto-translation-field--ezobjectrelationlist'; + class AutoTranslationObjectRelationListValidator extends ibexa.BaseFieldValidator { + /** + * Validates the textarea field value + * + * @method validateInput + * @param {Event} event + * @returns {Object} + * @memberof EzTextValidator + */ + validateInput(event) { + const isError = event.target.required && !event.target.value.trim(); + const label = event.target.closest(SELECTOR_FIELD).querySelector('.ibexa-label').innerHTML; + const errorMessage = ibexa.errors.emptyField.replace('{fieldName}', label); + + return { + isError, + errorMessage, + }; + } + } + + const validator = new AutoTranslationObjectRelationListValidator({ + classInvalid: 'is-invalid', + fieldSelector: SELECTOR_FIELD, + eventsMap: [ + { + selector: `${SELECTOR_FIELD} input`, + eventName: 'blur', + callback: 'validateInput', + errorNodeSelectors: ['.ibexa-form-error'], + }, + ], + }); + + validator.init(); + + ibexa.addConfig('fieldTypeValidators', [validator], true); +})(window, window.document, window.ibexa); diff --git a/bundle/Resources/public/admin/js/validator/auto-translation-ezselection.js b/bundle/Resources/public/admin/js/validator/auto-translation-ezselection.js new file mode 100644 index 0000000..c6b0102 --- /dev/null +++ b/bundle/Resources/public/admin/js/validator/auto-translation-ezselection.js @@ -0,0 +1,49 @@ +(function (global, doc, ibexa) { + const SELECTOR_FIELD = '.auto-translation-field--ezselection'; + const SELECTOR_SELECTED = '.ibexa-dropdown__selection-info'; + const SELECTOR_ERROR_NODE = '.ibexa-form-error'; + const EVENT_VALUE_CHANGED = 'change'; + + class AutoTranslationSelectionValidator extends ibexa.BaseFieldValidator { + /** + * Validates the textarea field value + * + * @method validateInput + * @param {Event} event + * @returns {Object} + * @memberof EzSelectionValidator + */ + validateInput(event) { + const fieldContainer = event.currentTarget.closest(SELECTOR_FIELD); + const selection = fieldContainer.querySelector('.ibexa-data-source__input'); + const hasSelectedOptions = !!selection.value; + const isRequired = selection && selection.required; + const isError = isRequired && !hasSelectedOptions; + const label = fieldContainer.querySelector('.ibexa-label').innerHTML; + const errorMessage = ibexa.errors.emptyField.replace('{fieldName}', label); + + return { + isError, + errorMessage, + }; + } + } + + const validator = new AutoTranslationSelectionValidator({ + classInvalid: 'is-invalid', + fieldSelector: SELECTOR_FIELD, + eventsMap: [ + { + selector: `${SELECTOR_FIELD} .ibexa-data-source__input--selection`, + eventName: EVENT_VALUE_CHANGED, + callback: 'validateInput', + errorNodeSelectors: [SELECTOR_ERROR_NODE], + invalidStateSelectors: [SELECTOR_SELECTED], + }, + ], + }); + + validator.init(); + + ibexa.addConfig('fieldTypeValidators', [validator], true); +})(window, window.document, window.ibexa); diff --git a/bundle/Resources/public/admin/scss/auto-translation.scss b/bundle/Resources/public/admin/scss/auto-translation.scss new file mode 100755 index 0000000..1504c71 --- /dev/null +++ b/bundle/Resources/public/admin/scss/auto-translation.scss @@ -0,0 +1,30 @@ +.path-location { + padding: 5px 10px; + background-color: #fff; + height: 58px; + list-style-type: none; + i { + margin-top: 0.1rem; + width: 1.5rem; + height: 1.5rem; + font-size: 30px; + color: #fff; + } +} + +.location-tree { + padding: 0; + margin: 0; + div { + line-height: 50px; + font-weight: 700; + padding: 0 10px; + } +} + + +.pull-right { + display: flex; + flex-direction: row-reverse; +} + diff --git a/bundle/Resources/translations/messages.en.yaml b/bundle/Resources/translations/messages.en.yaml index 0096023..e5f43a7 100644 --- a/bundle/Resources/translations/messages.en.yaml +++ b/bundle/Resources/translations/messages.en.yaml @@ -1,3 +1,26 @@ tab.translations.remote.translation.service: 'Use automatic translation' tab.translations.remote.translation.service.with: 'Use automatic translation (%alias%)' -auto_translation.permission.failed: 'You don''t have the permission to acces to the Content Translation interface' \ No newline at end of file +auto_translation.title: 'Tree translation module' +auto_translation.permission.failed: 'You don''t have the permission to acces to the Content Translation interface' +auto_translation.form.subtree_id.label: 'Tree to translate' +auto_translation.form.location.label: 'Select content' +auto_translation.form.location.success: 'Tree "%location%" selected' +auto_translation.form.submit: 'Add' +auto_translation.form.target_language.label: 'Target language' +auto_translation.form.overwrite.label: 'Overwrite existing translations' +auto_translation.form.target_language.default_language: 'Select target language' +auto_translation.list.title: 'History of translations' +auto_translation.list.user_name: 'Requestor' +auto_translation.list.content_name: 'Tree' +auto_translation.list.target_language: 'Target Language' +auto_translation.list.overwrite: 'Overwrite' +auto_translation.list.created_at: 'Date of request' +auto_translation.list.status: 'Status' +auto_translation.viewing: 'Viewing %viewing% out of %total% items' +auto_translation.list.overwrite.value: '{0} No|{1} Yes|]1' +auto_translation.list.status.pending: 'Pending' +auto_translation.list.status.in_progress: 'In Progress' +auto_translation.list.status.failed: 'Failed' +auto_translation.list.status.finished: 'Done' +auto_translation.add.success: 'The translation request of "%subtree_name%" has been registered.' +auto_translation.add.failed: 'Another translation request for "%subtree_name%" already pending in the list.' \ No newline at end of file diff --git a/bundle/Resources/translations/messages.fr.yaml b/bundle/Resources/translations/messages.fr.yaml new file mode 100644 index 0000000..87de37a --- /dev/null +++ b/bundle/Resources/translations/messages.fr.yaml @@ -0,0 +1,26 @@ +tab.translations.remote.translation.service: 'Utiliser la traduction automatique' +tab.translations.remote.translation.service.with: 'Utiliser la traduction automatique (%alias%)' +auto_translation.title: Module de traduction d'une Arborescence +auto_translation.permission.failed: 'You don''t have the permission to acces to the Content Translation interface' +auto_translation.form.subtree_id.label: 'Arborescence à traduire' +auto_translation.form.location.label: 'Sélectionner un contenu' +auto_translation.form.location.success: 'Arborescence "%location%" séléctionné' +auto_translation.form.submit: 'Ajouter' +auto_translation.form.target_language.label: 'Langue Cible' +auto_translation.form.overwrite.label: 'Écraser les traductions existantes' +auto_translation.form.target_language.default_language: 'Sélectionner la langue Cible' +auto_translation.list.title: 'Historique des traductions' +auto_translation.list.user_name: 'Demandeur' +auto_translation.list.content_name: 'Arborescence' +auto_translation.list.target_language: 'Langue Cible' +auto_translation.list.overwrite: 'Écraser' +auto_translation.list.created_at: 'Date de la demande' +auto_translation.viewing: '%viewing% sur %total% éléments' +auto_translation.list.status: 'Statut' +auto_translation.list.overwrite.value: '{0} Non|{1} Oui|]1' +auto_translation.list.status.pending: 'En attente' +auto_translation.list.status.in_progress: 'En cours' +auto_translation.list.status.failed: 'Échoué' +auto_translation.list.status.finished: 'Fait' +auto_translation.add.success: 'La demande de traduction de "%subtree_name%" a été enregistré.' +auto_translation.add.failed: 'Un autre demande de traduction de "%subtree_name%" déjà en attente dans la liste.' \ No newline at end of file diff --git a/bundle/Resources/views/ezadminui/stylesheet.html.twig b/bundle/Resources/views/ezadminui/stylesheet.html.twig new file mode 100644 index 0000000..d085506 --- /dev/null +++ b/bundle/Resources/views/ezadminui/stylesheet.html.twig @@ -0,0 +1 @@ +{{ encore_entry_link_tags('ezplatform-automated-translation-css', null, 'ibexa') }} diff --git a/bundle/Resources/views/themes/admin/Form/auto_translation/form.html.twig b/bundle/Resources/views/themes/admin/Form/auto_translation/form.html.twig new file mode 100644 index 0000000..bd22761 --- /dev/null +++ b/bundle/Resources/views/themes/admin/Form/auto_translation/form.html.twig @@ -0,0 +1,6 @@ +{% form_theme form '@ibexadesign/Form/auto_translation/form_fields.html.twig' %} + +{{ form_start(form, { attr: {class: 'ibexa-form ibexa-form-validate'}}) }} + {{ form_errors(form) }} + {{ form_widget(form) }} +{{ form_end(form) }} \ No newline at end of file diff --git a/bundle/Resources/views/themes/admin/Form/auto_translation/form_fields.html.twig b/bundle/Resources/views/themes/admin/Form/auto_translation/form_fields.html.twig new file mode 100644 index 0000000..8335439 --- /dev/null +++ b/bundle/Resources/views/themes/admin/Form/auto_translation/form_fields.html.twig @@ -0,0 +1,53 @@ +{% extends '@IbexaAdminUi/themes/admin/ui/form_fields.html.twig' %} + +{% block form_errors -%} +
    {{ parent() }}
    +{%- endblock form_errors %} + +{% block submit_widget %} +
    + +{% endblock %} + +{% block subtree_location_widget %} +
    + +
    +
    + +
    +
    +
    +
    +
    +{% endblock %} + + +{% block _auto_translation_actions_overwrite_row -%} + {% use '@ibexadesign/ui/form_fields/toggle_widget.html.twig' %} +
    + {{ block('form_label') }} + {{ block('toggle_widget') }} +
    +{%- endblock %} diff --git a/bundle/Resources/views/themes/admin/Form/auto_translation/form_search.html.twig b/bundle/Resources/views/themes/admin/Form/auto_translation/form_search.html.twig new file mode 100644 index 0000000..5437ed4 --- /dev/null +++ b/bundle/Resources/views/themes/admin/Form/auto_translation/form_search.html.twig @@ -0,0 +1,6 @@ +{% form_theme form '@ibexadesign/Form/auto_translation/search_form_fields.html.twig' %} + +{{ form_start(form_search, { attr: {class: 'auto-translation-search-form'}}) }} + {{ form_errors(form_search) }} + {{ form_widget(form_search) }} +{{ form_end(form_search) }} \ No newline at end of file diff --git a/bundle/Resources/views/themes/admin/Form/auto_translation/search_form_fields.html.twig b/bundle/Resources/views/themes/admin/Form/auto_translation/search_form_fields.html.twig new file mode 100644 index 0000000..8281c39 --- /dev/null +++ b/bundle/Resources/views/themes/admin/Form/auto_translation/search_form_fields.html.twig @@ -0,0 +1,5 @@ +{% extends '@IbexaAdminUi/themes/admin/ui/form_fields.html.twig' %} + +{% block form_errors -%} +
    {{ parent() }}
    +{%- endblock form_errors %} \ No newline at end of file diff --git a/bundle/Resources/views/themes/admin/auto_translation/view.html.twig b/bundle/Resources/views/themes/admin/auto_translation/view.html.twig new file mode 100644 index 0000000..dcb6c75 --- /dev/null +++ b/bundle/Resources/views/themes/admin/auto_translation/view.html.twig @@ -0,0 +1,124 @@ +{% extends '@ibexadesign/ui/layout.html.twig' %} + +{% block content %} + {% if form_search.vars.data.sort is defined %} + {% set current_field = form_search.vars.data.sort['field'] %} + {% set current_direction = form_search.vars.data.sort['direction'] %} + {% else %} + {% set current_field = '' %} + {% set current_direction = '' %} + {% endif %} + + {% set sort_directions = { + 'user_name' : current_field == 'user_name' and current_direction == 0 ? 'DESC' : 'ASC', + 'content_name' : current_field == 'content_name' and current_direction == 0 ? 'DESC' : 'ASC', + 'target_language' : current_field == 'target_language' and current_direction == 0 ? 'DESC' : 'ASC', + 'created_at' : current_field == 'created_at' and current_direction == 0 ? 'DESC' : 'ASC', + 'overwrite' : current_field == 'overwrite' and current_direction == 0 ? 'DESC' : 'ASC', + 'status' : current_field == 'status' and current_direction == 0 ? 'DESC' : 'ASC' + } %} +
    +
    +
    +

    {{ 'auto_translation.title'|trans() }}

    +
    +
    + {% if canCreate is defined and canCreate %} + {% if form.vars.attr['__template'] is defined %} + {% include form.vars.attr['__template'] %} + {% else %} + {{ form_start(form, { attr: {class: 'ibexa-form'}}) }} + {{ form_errors(form) }} + {{ form_widget(form) }} + {{ form_end(form) }} + {% endif %} + {% endif %} + + {% if form_search.vars.attr['__template'] is defined %} + {% include form_search.vars.attr['__template'] %} + {% else %} + {{ form_start(form_search, { attr: {class: 'ibexa-adaptive-filters ibexa-adaptive-filters--inside-container auto-translation-search-form'}}) }} + {{ form_errors(form_search) }} + {{ form_widget(form_search) }} + {{ form_end(form_search) }} + {% endif %} + + +
    +
    {{ 'auto_translation.list.title'|trans() }}
    +
    +
    + + + + + + + + + + + + {% for item in pager.currentPageResults %} + + + + + + + + + {% endfor %} + +
    + + {{ 'auto_translation.list.user_name'|trans }} + + + + {{ 'auto_translation.list.content_name'|trans }} + + + + {{ 'auto_translation.list.target_language'|trans }} + + + + {{ 'auto_translation.list.overwrite'|trans }} + + + + {{ 'auto_translation.list.created_at'|trans }} + + + + {{ 'auto_translation.list.status'|trans }} + +
    + {{ item.user_name }} + + {{ item.content_name }} + {{ ibexa_admin_ui_config.languages.mappings[item.target_language].name }}{{ 'auto_translation.list.overwrite.value'|trans({'%count%': item.overwrite})|raw }}{{ item.created_at|date("d/m/Y H:i") }} + {{ ('auto_translation.list.status.' ~ item.status)|trans }} + {% if item.finished_at and item.status == 'finished' or item.status == 'failed' %} +
    ({{ item.finished_at|date("d/m/Y H:i") }}) + {% endif %} +
    + {% if pager.haveToPaginate %} +
    + + {{ 'auto_translation.viewing'|trans({ + '%viewing%': pager.currentPageResults|length, + '%total%': pager.nbResults})|desc('Viewing %viewing% out of %total% items')|raw }} + + +
    + {% endif %} +
    +
    +{% endblock %} diff --git a/bundle/Resources/views/themes/admin/content/remote_translate_form_fields.html.twig b/bundle/Resources/views/themes/admin/content/remote_translate_form_fields.html.twig index bf13cc9..3e4a081 100644 --- a/bundle/Resources/views/themes/admin/content/remote_translate_form_fields.html.twig +++ b/bundle/Resources/views/themes/admin/content/remote_translate_form_fields.html.twig @@ -1,3 +1,4 @@ + {% if form.vars.autotranslated_data is defined %}
    {% if form.translatorAlias.vars.choices is defined %} From 4d0f4a98dee3bcae76970a511168142721d96124 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Mon, 24 Jul 2023 18:06:19 +0100 Subject: [PATCH 04/22] #106228 - Module traduction automatique --- .../Provider/AutoTranslationPolicyProvider.php | 6 +++--- bundle/Entity/AutoTranslationActions.php | 7 +++---- bundle/Form/AutoTranslationActionsSearchType.php | 6 +++--- bundle/Form/AutoTranslationActionsType.php | 15 ++++----------- bundle/Form/SubtreeLocationType.php | 6 +++--- 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/bundle/DependencyInjection/Security/Provider/AutoTranslationPolicyProvider.php b/bundle/DependencyInjection/Security/Provider/AutoTranslationPolicyProvider.php index d187a67..66e807c 100644 --- a/bundle/DependencyInjection/Security/Provider/AutoTranslationPolicyProvider.php +++ b/bundle/DependencyInjection/Security/Provider/AutoTranslationPolicyProvider.php @@ -1,13 +1,13 @@ em = $em; $this->locationService = $locationService; - $this->choicesTransformer = $choicesTransformer; - $this->siteListService = $siteListService; $this->languageService = $languageService; $this->translator = $translator; $this->notificationHandler = $notificationHandler; diff --git a/bundle/Form/SubtreeLocationType.php b/bundle/Form/SubtreeLocationType.php index 5a17e26..40d1585 100644 --- a/bundle/Form/SubtreeLocationType.php +++ b/bundle/Form/SubtreeLocationType.php @@ -1,13 +1,13 @@ Date: Wed, 26 Jul 2023 05:42:33 +0100 Subject: [PATCH 05/22] #106228 - Module traduction automatique [Mise en place du script de traduction] --- bundle/Command/TranslateContentCommand.php | 350 ++++++++++++++++-- bundle/Entity/AutoTranslationActions.php | 23 ++ .../Form/AutoTranslationActionsSearchType.php | 4 +- bundle/Form/AutoTranslationActionsType.php | 3 +- bundle/Form/SubtreeLocationType.php | 2 +- .../Handler/AutoTranslationActionsHandler.php | 42 ++- bundle/Resources/config/services.yml | 4 + bundle/Resources/doc/FEATURES.md | 15 + bundle/Resources/doc/INSTALL.md | 1 - bundle/Resources/encore/ibexa.config.js | 1 + .../Resources/public/admin/js/show.history.js | 14 + .../public/admin/scss/auto-translation.scss | 25 ++ .../admin/auto_translation/view.html.twig | 57 ++- 13 files changed, 500 insertions(+), 41 deletions(-) create mode 100644 bundle/Resources/public/admin/js/show.history.js diff --git a/bundle/Command/TranslateContentCommand.php b/bundle/Command/TranslateContentCommand.php index 7751632..48983cb 100644 --- a/bundle/Command/TranslateContentCommand.php +++ b/bundle/Command/TranslateContentCommand.php @@ -8,48 +8,76 @@ namespace EzSystems\EzPlatformAutomatedTranslationBundle\Command; +use Doctrine\ORM\EntityManagerInterface; +use \Exception; +use EzSystems\EzPlatformAutomatedTranslationBundle\Entity\AutoTranslationActions; +use EzSystems\EzPlatformAutomatedTranslationBundle\Handler\AutoTranslationActionsHandler; use Ibexa\Contracts\Core\Repository\ContentService; +use Ibexa\Contracts\Core\Repository\LocationService; +use Ibexa\Contracts\Core\Repository\Exceptions\BadStateException; +use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException; +use Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException; use Ibexa\Contracts\Core\Repository\PermissionResolver; +use Ibexa\Contracts\Core\Repository\Repository; use Ibexa\Contracts\Core\Repository\UserService; use EzSystems\EzPlatformAutomatedTranslation\ClientProvider; use EzSystems\EzPlatformAutomatedTranslation\Translator; +use Ibexa\Contracts\Core\Repository\Values\Content\Content; +use Pagerfanta\Doctrine\DBAL\QueryAdapter; +use Pagerfanta\Pagerfanta; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Ibexa\Migration\Log\LoggerAwareTrait; +use RuntimeException; +use Ibexa\Core\Base\Exceptions\InvalidArgumentException; +use Symfony\Component\Console\Command\LockableTrait; +use Symfony\Component\Process\Process; final class TranslateContentCommand extends Command { - private const ADMINISTRATOR_USER_ID = 14; - - /** @var Translator */ - private $translator; - - /** @var ClientProvider */ - private $clientProvider; + use LockableTrait; + use LoggerAwareTrait; - /** @var ContentService */ - private $contentService; - - /** @var PermissionResolver */ - private $permissionResolver; + private const ADMINISTRATOR_USER_ID = 14; - /** @var UserService */ - private $userService; + protected EntityManagerInterface $em; + protected Translator $translator; + protected ClientProvider $clientProvider; + protected ContentService $contentService; + protected LocationService $locationService; + protected PermissionResolver $permissionResolver; + protected UserService $userService; + protected Repository $repository; + protected AutoTranslationActionsHandler $handler; public function __construct( + EntityManagerInterface $em, Translator $translator, ClientProvider $clientProvider, ContentService $contentService, + LocationService $locationService, PermissionResolver $permissionResolver, - UserService $userService + UserService $userService, + Repository $repository, + AutoTranslationActionsHandler $handler, + ?LoggerInterface $logger = null ) { + $this->em = $em; $this->clientProvider = $clientProvider; $this->translator = $translator; $this->contentService = $contentService; + $this->locationService = $locationService; $this->permissionResolver = $permissionResolver; $this->userService = $userService; + $this->repository = $repository; + $this->handler = $handler; + $this->logger = $logger ?? new NullLogger(); parent::__construct(); } @@ -60,38 +88,308 @@ protected function configure(): void ->setName('ezplatform:automated:translate') ->setAliases(['eztranslate']) ->setDescription('Translate a Content in a new Language') - ->addArgument('contentId', InputArgument::REQUIRED, 'ContentId') ->addArgument( 'service', InputArgument::REQUIRED, 'Remote Service for Translation. [' . implode(' ', array_keys($this->clientProvider->getClients())) . ']' ) - ->addOption('from', '--from', InputOption::VALUE_REQUIRED, 'Source Language') - ->addOption('to', '--to', InputOption::VALUE_REQUIRED, 'Target Language'); + ->addArgument('contentId', InputArgument::OPTIONAL, 'ContentId') + ->addOption('from', '--from', InputOption::VALUE_OPTIONAL, 'Source Language Code') + ->addOption('to', '--to', InputOption::VALUE_OPTIONAL, 'Target Language Code') + ->addOption('user', '--user', InputOption::VALUE_OPTIONAL, 'The user id to publish new version with.') + ->addOption('overwrite', '--overwrite', InputOption::VALUE_NONE, 'Overwrites existing translations') + ->addOption('sudo', '--sudo', InputOption::VALUE_NONE, 'Force publication with admin user.') + ; } - protected function execute(InputInterface $input, OutputInterface $output): int + { + $cmdStatus = Command::SUCCESS; + $logMessage = ''; + if ($input->hasArgument('contentId') && (int)$input->getArgument('contentId')) { + $this->subExecute($input, $output); + } else { + if (!$this->lock()) { + $output->writeln('The command ezplatform:automated:translate is already running in another process.'); + return 0; + } + $action = $this->handler->getFirstPendingAction(); + + $message = sprintf('Start the automated translate.'); + $this->getLogger()->info($message); + $output->writeln($message); + if(empty($action)){ + $message = sprintf('There is no pending action.'); + $this->getLogger()->info($message); + $output->writeln($message); + } + if (!empty($action)) { + unset( + $action['created_at'], + $action['finished_at'], + $action['content_name'], + $action['user_name'], + $action['user_name'], + $action['content_id'] + ); + [ + $actionId, + $subtreeId, + $userId, + $targetLanguage, + $overwrite + ] = array_values($action); + + $overwrite = (bool) $overwrite; + /** @var AutoTranslationActions $autoTranslationActions */ + $autoTranslationActions = $this->em->getRepository(AutoTranslationActions::class)->find($actionId); + $autoTranslationActions->setStatus(AutoTranslationActions::STATUS_IN_PROGRESS); + $this->em->flush($autoTranslationActions); + try { + $subtreeLocation = $this->locationService->loadLocation($subtreeId); + + $message = sprintf('Start Translation of subtree %s.', $subtreeLocation->pathString); + $this->getLogger()->info($message); + $output->writeln($message); + $logMessage .= $message .'
    '; + + $query = $this->handler->buildQueryForContentInSubtree($subtreeLocation->pathString); + $countQueryBuilderModifier = static function ($queryBuilder) { + $queryBuilder->select('COUNT(DISTINCT c.id) AS total_results') + ->setMaxResults(1); + }; + + $currentPage = 1; + $maxPerPage = 1; + $pager = new Pagerfanta( + new QueryAdapter($query, $countQueryBuilderModifier) + ); + $pager->setMaxPerPage($maxPerPage); + $pager->setCurrentPage($currentPage); + $i = 0; + $message = sprintf('Translate "%s" contents.', $pager->count()); + $this->getLogger()->info($message); + $output->writeln($message); + $logMessage .= $message .'
    '; + + $progressBar = new ProgressBar($output, $pager->count()); + + if ($pager->count() > 0) { + do { + $i++; + $pager = new Pagerfanta( + new QueryAdapter($query, $countQueryBuilderModifier) + ); + $pager->setMaxPerPage($maxPerPage); + $pager->setCurrentPage($currentPage); + $contentIds = []; + /** @var Content $content */ + foreach ($pager->getCurrentPageResults() as $result) { + $contentIds[] = $result['id']; + } + $processes = $this->getPhpProcess( + $contentIds[0], + $targetLanguage, + $userId, + $overwrite, + $input + ); + $processes->run(); + $processes->getErrorOutput(); + $logMessage .= $processes->getOutput() .'
    '; + if (!empty($processes->getErrorOutput())) { + $message = $processes->getErrorOutput(); + $this->getLogger()->info($message); + $logMessage .= $message .'
    '; + } else { + $logMessage .= $processes->getOutput() .'
    '; + } + $currentPage++; + $progressBar->advance($maxPerPage); + } while ($pager->hasNextPage() && $i < 2000); + $progressBar->finish(); + } + // clear leftover progress bar parts + $progressBar->clear(); + } catch (Exception $e) { + $logMessage = $e->getMessage(); + $this->logException($e); + $cmdStatus = Command::FAILURE; + } + + $autoTranslationActions->setStatus($cmdStatus === Command::FAILURE ? + AutoTranslationActions::STATUS_FAILED : AutoTranslationActions::STATUS_FINISHED + ); + $autoTranslationActions->setLogMessage($logMessage); + $autoTranslationActions->setFinishedAt(new \DateTime()); + $this->em->flush($autoTranslationActions); + } + $output->writeln(''); + $output->writeln('Finished'); + $this->getLogger()->info('Finished'); + $output->writeln(''); + } + + return $cmdStatus; + } + protected function subExecute(InputInterface $input, OutputInterface $output): int { $contentId = (int) $input->getArgument('contentId'); + $languageCodeFrom = $input->getOption('from'); + $languageCodeTo = $input->getOption('to'); + $serviceApi = $input->getArgument('service'); + $overwrite = $input->getOption('overwrite'); + + $status = Command::SUCCESS; + if ($input->getOption('sudo')) { + $status = $this->permissionResolver->sudo(function () use ( + $contentId, + $languageCodeFrom, + $languageCodeTo, + $serviceApi, + $overwrite, + $input, + $output + ) { + try { + $this->publishVersion($contentId, $languageCodeFrom, $languageCodeTo, $serviceApi, $overwrite); + $this->logDoneMessage($contentId, $languageCodeFrom, $languageCodeTo, $output); + return Command::SUCCESS; + } catch (Exception $e) { + $this->logFailedMessage($contentId, $e); + return Command::FAILURE; + } + }, $this->repository); + } else { + try { + $this->publishVersion($contentId, $languageCodeFrom, $languageCodeTo, $serviceApi, $overwrite); + $this->logDoneMessage($contentId, $languageCodeFrom, $languageCodeTo, $output); + } catch (\Throwable $e) { + $this->logFailedMessage($contentId, $e); + $status = Command::FAILURE; + } + } + + return $status; + } + + /** + * @throws BadStateException + * @throws NotFoundException + * @throws UnauthorizedException + * @throws InvalidArgumentException + */ + protected function publishVersion( + int $contentId, + ?string &$languageCodeFrom, + string $languageCodeTo, + string $serviceApi, + bool $overwrite = false + ): array { $content = $this->contentService->loadContent($contentId); + $languageCodeFrom = $languageCodeFrom ?? $content->contentInfo->mainLanguageCode; + + if($languageCodeFrom == $languageCodeTo) { + $message = sprintf('The target language from argument --to=%s must be different from the source language --from and the main language of the content %s .', + $languageCodeTo, + $languageCodeFrom, + ); + throw new InvalidArgumentException('--from', $message); + } + + if(!$overwrite && in_array($languageCodeTo,$content->getVersionInfo()->languageCodes)) { + $message = sprintf('The content %d already translated into language --to=%s . use the --overwrite option. ', + $contentId, + $languageCodeTo + ); + throw new InvalidArgumentException('--to', $message); + } + $draft = $this->translator->getTranslatedContent( - $input->getOption('from'), - $input->getOption('to'), - $input->getArgument('service'), + $languageCodeFrom, + $languageCodeTo, + $serviceApi, $content ); - $this->contentService->publishVersion($draft->versionInfo); - $output->writeln("Translation to {$contentId} Done."); + $newContentVersion = $this->contentService->publishVersion($draft->versionInfo); - return Command::SUCCESS; + return [$content, $newContentVersion]; } + protected function logDoneMessage( + int $contentId, + string $languageCodeFrom, + string $languageCodeTo, + OutputInterface $output + ): void + { + $message = sprintf( + 'Translation of content %d from %s to %s Done.', + $contentId, + $languageCodeFrom, + $languageCodeTo + ); + $this->getLogger()->info($message); + $output->writeln($message); + } + protected function logFailedMessage(int $contentId, Exception $e): void + { + $message = sprintf( + 'Translation to %d Failed', + $contentId + ); + $this->logException($e, $message); + } + protected function logException(Exception $e, string $message = ''): void + { + $message = sprintf( + '%s. %s', + $message, + $e->getMessage(), + ); + $exception = new RuntimeException($message, $e->getCode(), $e); + $this->getLogger()->error($message, [ + 'exception' => $exception, + ]); + } protected function initialize(InputInterface $input, OutputInterface $output): void { parent::initialize($input, $output); + $userId = (int) ($input->getOption('user') ?? self::ADMINISTRATOR_USER_ID); + $this->permissionResolver->setCurrentUserReference( - $this->userService->loadUser(self::ADMINISTRATOR_USER_ID) + $this->userService->loadUser($userId) ); } + + private function getPhpProcess(int $contentId, string $targetLanguage, int $userId, bool $overwrite, InputInterface $input): Process + { + $env = $input->getParameterOption(['--env', '-e'], getenv('APP_ENV') ?: 'dev', true); + $serviceApi = $input->getArgument('service'); + $sudo = $input->getOption('sudo'); + + $subProcessArgs = [ + 'php', + 'bin/console', + $this->getName(), + $serviceApi, + $contentId, + '--user=' . $userId, + '--to=' . $targetLanguage, + '--env=' . $env + ]; + + if ($overwrite) { + $subProcessArgs[] = '--overwrite'; + } + + if ($sudo) { + $subProcessArgs[] = '--sudo'; + } + + $process = new Process($subProcessArgs); + $process->setTimeout(null); + + return $process; + } } diff --git a/bundle/Entity/AutoTranslationActions.php b/bundle/Entity/AutoTranslationActions.php index 2099c27..6c7ee52 100644 --- a/bundle/Entity/AutoTranslationActions.php +++ b/bundle/Entity/AutoTranslationActions.php @@ -85,6 +85,13 @@ class AutoTranslationActions */ private $finishedAt; + /** + * @var String + * + * @ORM\Column(name="log_message", type="text", nullable=true) + */ + private $logMessage; + public function getId(): int { return $this->id; @@ -172,6 +179,22 @@ public function setFinishedAt(?DateTime $finishedAt = null): void $this->finishedAt = $finishedAt; } + /** + * @return string|null + */ + public function getLogMessage(): ?string + { + return $this->logMessage; + } + + /** + * @param string|null $logMessage + */ + public function setLogMessage(?string $logMessage): void + { + $this->logMessage = $logMessage; + } + /** * @ORM\PrePersist */ diff --git a/bundle/Form/AutoTranslationActionsSearchType.php b/bundle/Form/AutoTranslationActionsSearchType.php index a82be78..35ba88d 100644 --- a/bundle/Form/AutoTranslationActionsSearchType.php +++ b/bundle/Form/AutoTranslationActionsSearchType.php @@ -30,8 +30,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'row_attr' => [ 'hidden' => 'hidden' ], - 'sort_fields' => ['user_name', 'content_name', 'target_language', 'created_at' ,'overwrite' ,'status'], - 'default' => ['field' => 'created_at', 'direction' => '1'], + 'sort_fields' => ['created_at', 'user_name', 'content_name', 'target_language' ,'overwrite' ,'status'], + 'default' => ['field' => 'created_at', 'direction' => '0'], ]) ; } diff --git a/bundle/Form/AutoTranslationActionsType.php b/bundle/Form/AutoTranslationActionsType.php index 59e9be8..cd9c117 100755 --- a/bundle/Form/AutoTranslationActionsType.php +++ b/bundle/Form/AutoTranslationActionsType.php @@ -6,7 +6,7 @@ * @package EzPlatformAutomatedTranslationBundle * * @author Novactive - * @copyright 2018 Novactive + * @copyright 2023 Novactive * @license https://github.com/Novactive/ezplatform-automated-translation/blob/master/LICENSE */ @@ -14,7 +14,6 @@ use Doctrine\ORM\EntityManagerInterface; use EzSystems\EzPlatformAutomatedTranslationBundle\Entity\AutoTranslationActions; -use EzSystems\EzPlatformAutomatedTranslationBundle\Form\Type\SubtreeLocationType; use Ibexa\Contracts\AdminUi\Notification\TranslatableNotificationHandlerInterface; use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException; use Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException; diff --git a/bundle/Form/SubtreeLocationType.php b/bundle/Form/SubtreeLocationType.php index 40d1585..6f9b8ef 100644 --- a/bundle/Form/SubtreeLocationType.php +++ b/bundle/Form/SubtreeLocationType.php @@ -10,7 +10,7 @@ * @license https://github.com/Novactive/ezplatform-automated-translation/blob/master/LICENSE */ -namespace EzSystems\EzPlatformAutomatedTranslationBundle\Form\Type; +namespace EzSystems\EzPlatformAutomatedTranslationBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; diff --git a/bundle/Handler/AutoTranslationActionsHandler.php b/bundle/Handler/AutoTranslationActionsHandler.php index aebd7f8..5507da7 100644 --- a/bundle/Handler/AutoTranslationActionsHandler.php +++ b/bundle/Handler/AutoTranslationActionsHandler.php @@ -9,7 +9,9 @@ namespace EzSystems\EzPlatformAutomatedTranslationBundle\Handler; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\Query\QueryBuilder; +use EzSystems\EzPlatformAutomatedTranslationBundle\Entity\AutoTranslationActions; use Ibexa\Contracts\Core\Repository\PermissionCriterionResolver; use Ibexa\Contracts\Core\Repository\Values\Content\Query; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion; @@ -73,12 +75,11 @@ public function getAllQuery(array $sort = []): QueryBuilder ; if (isset($sort['field'])) { $selectQuery->orderBy($sort['field'], ($sort['direction'] ?? 0) == 0 ? 'DESC' : 'ASC'); + } else { + $selectQuery->orderBy('created_at', 'DESC'); } // Check read access to whole source subtree - $permissionCriterion = $this->permissionCriterionResolver->getPermissionsCriterion( - 'content', - 'read' - ); + $permissionCriterion = $this->permissionCriterionResolver->getPermissionsCriterion(); if ($permissionCriterion !== true && $permissionCriterion !== false) { $query = new Query(); $query->filter = new LogicalAnd( @@ -108,13 +109,44 @@ private function getSelectQuery(): QueryBuilder return $selectQuery; } + /** + * @throws Exception + * @throws \Doctrine\DBAL\Driver\Exception + */ + public function getFirstPendingAction() + { + $queryBuilder = $this->getAllQuery(['field' => 'created_at', 'direction' => 1]); + $queryBuilder + ->andWhere($queryBuilder->expr()->in('at.status', ':action_status')) + ->setParameter( + ':action_status', + [AutoTranslationActions::STATUS_PENDING, AutoTranslationActions::STATUS_IN_PROGRESS], + Connection::PARAM_STR_ARRAY + ); + $queryBuilder->setMaxResults(1); + + return $queryBuilder->execute()->fetchAllAssociative()[0] ?? null; + } + public function countAll(QueryBuilder $queryBuilder): int { $queryBuilder->select('COUNT(at.id)'); $queryBuilder->orderBy('at.id'); - $queryBuilder->limit = 0; + $queryBuilder->setMaxResults(1); return (int) $queryBuilder->execute()->fetchOne(); } + + public function buildQueryForContentInSubtree(string $locationPath): QueryBuilder + { + return $this->connection->createQueryBuilder() + ->select('DISTINCT c.id') + ->from(ContentGateway::CONTENT_ITEM_TABLE, 'c') + ->innerJoin('c', LocationGateway::CONTENT_TREE_TABLE, 't', 't.contentobject_id = c.id') + ->where('c.status = :status') + ->andWhere('t.path_string LIKE :path') + ->setParameter('status', ContentInfo::STATUS_PUBLISHED, ParameterType::INTEGER) + ->setParameter('path', $locationPath . '%', ParameterType::STRING); + } } diff --git a/bundle/Resources/config/services.yml b/bundle/Resources/config/services.yml index 186e1ff..e1afa89 100644 --- a/bundle/Resources/config/services.yml +++ b/bundle/Resources/config/services.yml @@ -22,6 +22,10 @@ services: EzSystems\EzPlatformAutomatedTranslation\Encoder\BlockAttribute\BlockAttributeEncoderInterface: tags: [ 'ezplatform.automated_translation.block_attribute_encoder' ] + Symfony\Component\Console\Command\Command: + tags: + - { name: console.command } + - { name: monolog.logger, channel: translate_content_cmd } # field encoder EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\TextLineFieldEncoder: ~ diff --git a/bundle/Resources/doc/FEATURES.md b/bundle/Resources/doc/FEATURES.md index f3a2575..23548db 100644 --- a/bundle/Resources/doc/FEATURES.md +++ b/bundle/Resources/doc/FEATURES.md @@ -16,3 +16,18 @@ To do so, you need to: - create a service that implements EzSystems\EzPlatformAutomatedTranslation\Client\ClientInterface - implements the method - tag this service: `ezplatform.automated_translation.client` + + +## Logging run command +If you want to log outputs of commands processed by run command you have to add the monolog channel `eztranslate_cmd` to your configuration. + +### Example +```yml + monolog: + channels: [...,'eztranslate_cmd'] + handlers: + eztranslate_cmd: + type: stream + path: '%kernel.logs_dir%/eztranslate_cmd.log' + channels: ['eztranslate_cmd'] +``` \ No newline at end of file diff --git a/bundle/Resources/doc/INSTALL.md b/bundle/Resources/doc/INSTALL.md index 9bf5b9f..8785eaa 100644 --- a/bundle/Resources/doc/INSTALL.md +++ b/bundle/Resources/doc/INSTALL.md @@ -27,4 +27,3 @@ Run ```cmd php bin/console doctrine:schema:update --force ``` - diff --git a/bundle/Resources/encore/ibexa.config.js b/bundle/Resources/encore/ibexa.config.js index 301a27b..8034383 100644 --- a/bundle/Resources/encore/ibexa.config.js +++ b/bundle/Resources/encore/ibexa.config.js @@ -8,6 +8,7 @@ module.exports = (Encore) => { path.resolve(__dirname, '../public/admin/js/validator/auto-translation-ezobjectrelationlist.js'), path.resolve(__dirname, '../public/admin/js/ezplatformautomatedtranslation.js'), path.resolve(__dirname, '../public/admin/js/auto-translation.js'), + path.resolve(__dirname, '../public/admin/js/show.history.js'), ]); Encore.addEntry('ezplatform-automated-translation-css', [ path.resolve(__dirname, '../public/admin/scss/auto-translation.scss'), diff --git a/bundle/Resources/public/admin/js/show.history.js b/bundle/Resources/public/admin/js/show.history.js new file mode 100644 index 0000000..b9350da --- /dev/null +++ b/bundle/Resources/public/admin/js/show.history.js @@ -0,0 +1,14 @@ +(function (global, doc, ibexa, bootstrap) { + const containers = doc.querySelectorAll('.ibexa-auto-translation-actions-table'); + const showPopup = ({ currentTarget: btn }) => { + const selector = `[data-action-logs-popup="${btn.dataset.uiComponent}-${btn.dataset.actionId}"]`; + const modal = doc.querySelector(selector); + bootstrap.Modal.getOrCreateInstance(modal).show(); + }; + + containers.forEach((container) => { + container.querySelectorAll('.ibexa-btn--translation-actions-chart').forEach((btn) => { + btn.addEventListener('click', showPopup, false); + }); + }); +})(window, window.document, window.ibexa, window.bootstrap); diff --git a/bundle/Resources/public/admin/scss/auto-translation.scss b/bundle/Resources/public/admin/scss/auto-translation.scss index 1504c71..5ac71f1 100755 --- a/bundle/Resources/public/admin/scss/auto-translation.scss +++ b/bundle/Resources/public/admin/scss/auto-translation.scss @@ -28,3 +28,28 @@ flex-direction: row-reverse; } +.ibexa-popup--auto-translation .modal-body { + overflow-y: scroll; + height: auto; + max-height: 600px; +} + +.ibexa-auto-translation-actions-table { + .ibexa-badge--secondary { + color: #FFF; + } + .status-pending { + background-color: #f15a10; + } + .status-in_progress { + background-color: #5a10f1; + } + .status-failed { + background-color: #db0032; + } + .status-finished { + background-color: #00621a; + } +} + + diff --git a/bundle/Resources/views/themes/admin/auto_translation/view.html.twig b/bundle/Resources/views/themes/admin/auto_translation/view.html.twig index dcb6c75..410511b 100644 --- a/bundle/Resources/views/themes/admin/auto_translation/view.html.twig +++ b/bundle/Resources/views/themes/admin/auto_translation/view.html.twig @@ -5,8 +5,8 @@ {% set current_field = form_search.vars.data.sort['field'] %} {% set current_direction = form_search.vars.data.sort['direction'] %} {% else %} - {% set current_field = '' %} - {% set current_direction = '' %} + {% set current_field = 'created_at' %} + {% set current_direction = 0 %} {% endif %} {% set sort_directions = { @@ -48,9 +48,14 @@
    {{ 'auto_translation.list.title'|trans() }}
    - +
    + + {% for item in pager.currentPageResults %} + @@ -98,11 +111,47 @@ + {% endfor %} From cb763d20100f19a2d982eab8c7330dfd58e0f3e1 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Tue, 8 Aug 2023 03:49:06 +0100 Subject: [PATCH 06/22] #106228 - Module traduction automatique --- bundle/Command/TranslateContentCommand.php | 29 +++++++++---- .../Handler/AutoTranslationActionsHandler.php | 43 ++++++++++++++++--- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/bundle/Command/TranslateContentCommand.php b/bundle/Command/TranslateContentCommand.php index 48983cb..d29d801 100644 --- a/bundle/Command/TranslateContentCommand.php +++ b/bundle/Command/TranslateContentCommand.php @@ -38,6 +38,7 @@ use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Symfony\Component\Console\Command\LockableTrait; use Symfony\Component\Process\Process; +use Pagerfanta\Adapter\CallbackAdapter; final class TranslateContentCommand extends Command { @@ -153,16 +154,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln($message); $logMessage .= $message .'
    '; - $query = $this->handler->buildQueryForContentInSubtree($subtreeLocation->pathString); - $countQueryBuilderModifier = static function ($queryBuilder) { - $queryBuilder->select('COUNT(DISTINCT c.id) AS total_results') - ->setMaxResults(1); - }; - + $adapter = new CallbackAdapter( + function () use ($subtreeLocation): int { + return $this->handler->countContentWithRelationsInSubtree($subtreeLocation->pathString); + }, + function (int $offset, int $limit) use ($subtreeLocation): iterable { + return $this->handler->getContentsWithRelationsInSubtree($subtreeLocation->pathString, $offset, $limit); + } + ); $currentPage = 1; $maxPerPage = 1; $pager = new Pagerfanta( - new QueryAdapter($query, $countQueryBuilderModifier) + $adapter ); $pager->setMaxPerPage($maxPerPage); $pager->setCurrentPage($currentPage); @@ -177,15 +180,23 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($pager->count() > 0) { do { $i++; + $adapter = new CallbackAdapter( + function () use ($subtreeLocation): int { + return $this->handler->countContentWithRelationsInSubtree($subtreeLocation->pathString); + }, + function (int $offset, int $limit) use ($subtreeLocation): iterable { + return $this->handler->getContentsWithRelationsInSubtree($subtreeLocation->pathString, $offset, $limit); + } + ); $pager = new Pagerfanta( - new QueryAdapter($query, $countQueryBuilderModifier) + $adapter ); $pager->setMaxPerPage($maxPerPage); $pager->setCurrentPage($currentPage); $contentIds = []; /** @var Content $content */ foreach ($pager->getCurrentPageResults() as $result) { - $contentIds[] = $result['id']; + $contentIds[] = $result['contentId']; } $processes = $this->getPhpProcess( $contentIds[0], diff --git a/bundle/Handler/AutoTranslationActionsHandler.php b/bundle/Handler/AutoTranslationActionsHandler.php index 5507da7..091eb72 100644 --- a/bundle/Handler/AutoTranslationActionsHandler.php +++ b/bundle/Handler/AutoTranslationActionsHandler.php @@ -137,16 +137,47 @@ public function countAll(QueryBuilder $queryBuilder): int return (int) $queryBuilder->execute()->fetchOne(); } - public function buildQueryForContentInSubtree(string $locationPath): QueryBuilder + public function getContentsWithRelationsInSubtree(string $locationPath, int $offset = 0 , int $limit = 10): array { - return $this->connection->createQueryBuilder() - ->select('DISTINCT c.id') + $sql = $this->getSqlContentsWithRelationsInSubtree(); + $query = $this->connection->prepare( + "SELECT * FROM ($sql) x LIMIT $offset,$limit"); + $query->bindValue( ':status', ContentInfo::STATUS_PUBLISHED, ParameterType::INTEGER); + $query->bindValue(':path', $locationPath . '%', ParameterType::STRING); + $result = $query->executeQuery(); + + return $result->fetchAllAssociative(); + } + public function countContentWithRelationsInSubtree(string $locationPath): int + { + $sql = $this->getSqlContentsWithRelationsInSubtree(); + $query = $this->connection->prepare( + "SELECT COUNT(*) FROM ($sql) x"); + $query->bindValue( ':status', ContentInfo::STATUS_PUBLISHED, ParameterType::INTEGER); + $query->bindValue(':path', $locationPath . '%', ParameterType::STRING); + $result = $query->executeQuery(); + + return $result->fetchOne(); + } + public function getSqlContentsWithRelationsInSubtree(): string + { + $query = $this->connection->createQueryBuilder() ->from(ContentGateway::CONTENT_ITEM_TABLE, 'c') ->innerJoin('c', LocationGateway::CONTENT_TREE_TABLE, 't', 't.contentobject_id = c.id') ->where('c.status = :status') - ->andWhere('t.path_string LIKE :path') - ->setParameter('status', ContentInfo::STATUS_PUBLISHED, ParameterType::INTEGER) - ->setParameter('path', $locationPath . '%', ParameterType::STRING); + ->andWhere('t.path_string LIKE :path'); + + $contentsSqlQuery = $query + ->select('DISTINCT c.id as contentId') + ->getSQL(); + + $relationContentsSqlQuery = $query + ->select('DISTINCT c_rel.to_contentobject_id as contentId') + ->innerJoin('c', ContentGateway::CONTENT_RELATION_TABLE, 'c_rel', 'c_rel.from_contentobject_id = c.id AND c_rel.from_contentobject_version = c.current_version') + ->getSQL(); + + return "$contentsSqlQuery UNION ALL $relationContentsSqlQuery"; } + } From c0acfe7cc3f96b2798a8335b4129a55e9366f658 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Thu, 31 Aug 2023 17:57:16 +0100 Subject: [PATCH 07/22] 106228 module traduction automatique --- .../EzPlatformAutomatedTranslationExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php b/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php index 6ed6867..9ac1bb9 100644 --- a/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php +++ b/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php @@ -30,8 +30,6 @@ public function load(array $configs, ContainerBuilder $container): void $config = $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); - // always needed because of Bundle extension. - $loader->load('services_override.yml'); $container->registerForAutoconfiguration(FieldEncoderInterface::class) ->addTag('ezplatform.automated_translation.field_encoder'); @@ -50,6 +48,8 @@ public function load(array $configs, ContainerBuilder $container): void $loader->load('ezadminui.yml'); $loader->load('default_settings.yml'); $loader->load('services.yml'); + // always needed because of Bundle extension. + $loader->load('services_override.yml'); $processor = new ConfigurationProcessor($container, $this->getAlias()); $processor->mapSetting('configurations', $config); From 742bcbf4dd9f68ad0916bed592f68e0a643b20fe Mon Sep 17 00:00:00 2001 From: Mohammed Date: Thu, 31 Aug 2023 18:39:45 +0100 Subject: [PATCH 08/22] 106228 module traduction automatique [alternative Text of image] --- bundle/Resources/config/services.yml | 2 + lib/Encoder/Field/ImageFieldEncoder.php | 50 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 lib/Encoder/Field/ImageFieldEncoder.php diff --git a/bundle/Resources/config/services.yml b/bundle/Resources/config/services.yml index e1afa89..fd14697 100644 --- a/bundle/Resources/config/services.yml +++ b/bundle/Resources/config/services.yml @@ -26,6 +26,8 @@ services: tags: - { name: console.command } - { name: monolog.logger, channel: translate_content_cmd } + + EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\ImageFieldEncoder: ~ # field encoder EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\TextLineFieldEncoder: ~ diff --git a/lib/Encoder/Field/ImageFieldEncoder.php b/lib/Encoder/Field/ImageFieldEncoder.php new file mode 100644 index 0000000..1f95c2f --- /dev/null +++ b/lib/Encoder/Field/ImageFieldEncoder.php @@ -0,0 +1,50 @@ +value instanceof ImageValue; + } + + public function canDecode(string $type): bool + { + return ImageValue::class === $type; + } + + public function encode(Field $field): string + { + return htmlentities((string) $field->value->alternativeText); + } + + public function decode(string $value, $previousFieldValue): Value + { + $value = str_replace( + Encoder::XML_MARKUP, + '', + $value + ); + $value = htmlspecialchars_decode(trim($value)); + if (strlen($value) === 0) { + throw new EmptyTranslatedFieldException(); + } + $imageValue = clone $previousFieldValue; + $imageValue->alternativeText = $value; + + return $imageValue; + } +} From a7c7590b3e80a676d57991881551f00d32cb5479 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Thu, 31 Aug 2023 19:59:31 +0100 Subject: [PATCH 09/22] 106228 module traduction automatique [alternative Text of image] --- lib/Encoder/BlockAttribute/TextBlockAttributeEncoder.php | 2 +- lib/Encoder/BlockAttribute/TextLineAttributeEncoder.php | 2 +- lib/Encoder/Field/ImageFieldEncoder.php | 2 +- lib/Encoder/Field/TextBlockFieldEncoder.php | 4 ++-- lib/Encoder/Field/TextLineFieldEncoder.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Encoder/BlockAttribute/TextBlockAttributeEncoder.php b/lib/Encoder/BlockAttribute/TextBlockAttributeEncoder.php index 682bb56..c7f89dc 100644 --- a/lib/Encoder/BlockAttribute/TextBlockAttributeEncoder.php +++ b/lib/Encoder/BlockAttribute/TextBlockAttributeEncoder.php @@ -37,6 +37,6 @@ public function decode(string $value): string $value ); - return htmlspecialchars_decode(trim($value)); + return html_entity_decode(htmlspecialchars_decode(trim($value))); } } diff --git a/lib/Encoder/BlockAttribute/TextLineAttributeEncoder.php b/lib/Encoder/BlockAttribute/TextLineAttributeEncoder.php index bc7fb85..3a60ccd 100644 --- a/lib/Encoder/BlockAttribute/TextLineAttributeEncoder.php +++ b/lib/Encoder/BlockAttribute/TextLineAttributeEncoder.php @@ -37,6 +37,6 @@ public function decode(string $value): string $value ); - return htmlspecialchars_decode(trim($value)); + return html_entity_decode(htmlspecialchars_decode(trim($value))); } } diff --git a/lib/Encoder/Field/ImageFieldEncoder.php b/lib/Encoder/Field/ImageFieldEncoder.php index 1f95c2f..581b6b3 100644 --- a/lib/Encoder/Field/ImageFieldEncoder.php +++ b/lib/Encoder/Field/ImageFieldEncoder.php @@ -38,7 +38,7 @@ public function decode(string $value, $previousFieldValue): Value '', $value ); - $value = htmlspecialchars_decode(trim($value)); + $value = html_entity_decode(htmlspecialchars_decode(trim($value))); if (strlen($value) === 0) { throw new EmptyTranslatedFieldException(); } diff --git a/lib/Encoder/Field/TextBlockFieldEncoder.php b/lib/Encoder/Field/TextBlockFieldEncoder.php index c87cb7d..772f6c6 100644 --- a/lib/Encoder/Field/TextBlockFieldEncoder.php +++ b/lib/Encoder/Field/TextBlockFieldEncoder.php @@ -28,7 +28,7 @@ public function canDecode(string $type): bool public function encode(Field $field): string { - return (string) $field->value; + return htmlentities((string) $field->value); } public function decode(string $value, $previousFieldValue): Value @@ -38,7 +38,7 @@ public function decode(string $value, $previousFieldValue): Value '', $value ); - $value = htmlspecialchars_decode(trim($value)); + $value = html_entity_decode(htmlspecialchars_decode(trim($value))); if (strlen($value) === 0) { throw new EmptyTranslatedFieldException(); diff --git a/lib/Encoder/Field/TextLineFieldEncoder.php b/lib/Encoder/Field/TextLineFieldEncoder.php index da10851..7c20f2a 100644 --- a/lib/Encoder/Field/TextLineFieldEncoder.php +++ b/lib/Encoder/Field/TextLineFieldEncoder.php @@ -38,7 +38,7 @@ public function decode(string $value, $previousFieldValue): Value '', $value ); - $value = htmlspecialchars_decode(trim($value)); + $value = html_entity_decode(htmlspecialchars_decode(trim($value))); if (strlen($value) === 0) { throw new EmptyTranslatedFieldException(); From 4945f85a08f2aa12344f7ebfb774d620a89092a6 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Mon, 4 Sep 2023 21:54:20 +0100 Subject: [PATCH 10/22] #110432 - Formulaire --- bundle/Resources/config/services.yml | 13 ++ lib/Encoder/Field/FormBuilderFieldEncoder.php | 155 ++++++++++++++++++ lib/Encoder/Field/PageBuilderFieldEncoder.php | 2 +- ...mBuilderFieldAttributeEncoderInterface.php | 23 +++ ...ormBuilderFieldAttributeEncoderManager.php | 64 ++++++++ .../TextBlockFieldEncoder.php | 42 +++++ .../TextLineFieldEncoder.php | 42 +++++ 7 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 lib/Encoder/Field/FormBuilderFieldEncoder.php create mode 100644 lib/Encoder/FormBuilderFieldAttribute/FormBuilderFieldAttributeEncoderInterface.php create mode 100644 lib/Encoder/FormBuilderFieldAttribute/FormBuilderFieldAttributeEncoderManager.php create mode 100644 lib/Encoder/FormBuilderFieldAttribute/TextBlockFieldEncoder.php create mode 100644 lib/Encoder/FormBuilderFieldAttribute/TextLineFieldEncoder.php diff --git a/bundle/Resources/config/services.yml b/bundle/Resources/config/services.yml index fd14697..611ceda 100644 --- a/bundle/Resources/config/services.yml +++ b/bundle/Resources/config/services.yml @@ -22,11 +22,16 @@ services: EzSystems\EzPlatformAutomatedTranslation\Encoder\BlockAttribute\BlockAttributeEncoderInterface: tags: [ 'ezplatform.automated_translation.block_attribute_encoder' ] + EzSystems\EzPlatformAutomatedTranslation\Encoder\FormBuilderFieldAttribute\FormBuilderFieldAttributeEncoderInterface: + tags: [ 'ezplatform.automated_translation.form_builder_field_encoder' ] + Symfony\Component\Console\Command\Command: tags: - { name: console.command } - { name: monolog.logger, channel: translate_content_cmd } + EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\FormBuilderFieldEncoder: ~ + EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\ImageFieldEncoder: ~ # field encoder EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\TextLineFieldEncoder: ~ @@ -70,6 +75,14 @@ services: arguments: $configResolver: '@ibexa.config.resolver' + # Form Builder Field attribute + EzSystems\EzPlatformAutomatedTranslation\Encoder\FormBuilderFieldAttribute\TextLineFieldEncoder: ~ + EzSystems\EzPlatformAutomatedTranslation\Encoder\FormBuilderFieldAttribute\TextBlockFieldEncoder: ~ + # Form Builder Field attribute encoder manager + EzSystems\EzPlatformAutomatedTranslation\Encoder\FormBuilderFieldAttribute\FormBuilderFieldAttributeEncoderManager: + arguments: + $formBuilderFieldAttributeEncoders: !tagged_iterator 'ezplatform.automated_translation.form_builder_field_encoder' + EzSystems\EzPlatformAutomatedTranslation\Encoder: ~ EzSystems\EzPlatformAutomatedTranslation\Translator: ~ diff --git a/lib/Encoder/Field/FormBuilderFieldEncoder.php b/lib/Encoder/Field/FormBuilderFieldEncoder.php new file mode 100644 index 0000000..28326b7 --- /dev/null +++ b/lib/Encoder/Field/FormBuilderFieldEncoder.php @@ -0,0 +1,155 @@ +formBuilderFieldAttributeEncoderManager = $formBuilderFieldAttributeEncoderManager; + $this->fieldDefinitionFactory = $fieldDefinitionFactory; + } + public function canEncode(Field $field): bool + { + + return $field->value instanceof FormValue; + } + + public function canDecode(string $type): bool + { + return FormValue::class === $type; + } + + public function encode(Field $field): string + { + /** @var FormValue $value */ + $value = $field->value; + $formFields = []; + $form = $value->getForm(); + $fieldDefinitionAttributesType = []; + /** @var \Ibexa\Contracts\FormBuilder\FieldType\Model\Field $formField */ + foreach ($value->getFormValue()?->getFields() as $formField) { + dump($formField); + $attrs = []; + $fieldDefinition = $this->fieldDefinitionFactory->getFieldDefinition($formField->getIdentifier()); + dump($fieldDefinition); + $fieldDefinitionAttributesType[$formField->getIdentifier()] = []; + foreach ($fieldDefinition->getAttributes() as $attribute) { + $fieldDefinitionAttributesType[$formField->getIdentifier()][$attribute->getIdentifier()] = $attribute->getType(); + } + + foreach ($formField->getAttributes() as $attribute) { + $attributeType = $fieldDefinitionAttributesType[$formField->getIdentifier()][$attribute->getIdentifier()]; + if (null === ($attributeValue = $this->encodeAttribute($attributeType, $attribute->getValue()))) { + continue; + } + + $attrs[$attribute->getIdentifier()] = [ + '@type' => $attributeType, + '#' => $attributeValue, + ]; + } + + $formFields[$formField->getId()] = [ + 'name' => $this->encodeAttribute('string', $formField->getName()), + 'attributes' => $attrs, + ]; + } + +dump($formFields); + + $encoder = new XmlEncoder(); + $payload = $encoder->encode($formFields, XmlEncoder::FORMAT); + + $payload = str_replace('' . "\n", '', $payload); + + $payload = str_replace( + [''], + ['<' . self::CDATA_FAKER_TAG . '>', ''], + $payload + ); + dump($payload); + + + return (string) $payload; + } + + public function decode(string $value, $previousFieldValue): FormValue + { + $encoder = new XmlEncoder(); + $data = str_replace( + ['<' . self::CDATA_FAKER_TAG . '>', ''], + [''], + $value + ); + + /** @var FormValue $formValue */ + $formValue = clone $previousFieldValue; + $decodeArray = $encoder->decode($data, XmlEncoder::FORMAT); + + if ($decodeArray) { + foreach ($decodeArray as $fieldId => $fieldValue) { + $field = $formValue->getFormValue()->getFieldById((string)$fieldId); + $field->setName($this->decodeAttribute('string', $fieldValue['name'])); + + if (is_array($fieldValue['attributes'])) { + foreach ($fieldValue['attributes'] as $attributeName => $attribute) { + if (null === ($attributeValue = $this->decodeAttribute($attribute['@type'], $attribute['#']))) { + continue; + } + + $field->getAttribute($attributeName)->setValue($attributeValue); + } + } + } + } + + return $formValue; + } + + /** + * @param mixed $value + */ + private function encodeAttribute(string $type, $value): ?string + { + try { + $value = $this->formBuilderFieldAttributeEncoderManager->encode($type, $value); + } catch (InvalidArgumentException $e) { + return null; + } + + return $value; + } + + private function decodeAttribute(string $type, string $value): ?string + { + try { + $value = $this->formBuilderFieldAttributeEncoderManager->decode($type, $value); + } catch (InvalidArgumentException | EmptyTranslatedAttributeException $e) { + return null; + } + + return $value; + } +} \ No newline at end of file diff --git a/lib/Encoder/Field/PageBuilderFieldEncoder.php b/lib/Encoder/Field/PageBuilderFieldEncoder.php index 6af6eb7..1aab302 100644 --- a/lib/Encoder/Field/PageBuilderFieldEncoder.php +++ b/lib/Encoder/Field/PageBuilderFieldEncoder.php @@ -122,7 +122,7 @@ public function decode(string $value, $previousFieldValue): APIValue } } } - + dump(new Value($page)); return new Value($page); } diff --git a/lib/Encoder/FormBuilderFieldAttribute/FormBuilderFieldAttributeEncoderInterface.php b/lib/Encoder/FormBuilderFieldAttribute/FormBuilderFieldAttributeEncoderInterface.php new file mode 100644 index 0000000..3407252 --- /dev/null +++ b/lib/Encoder/FormBuilderFieldAttribute/FormBuilderFieldAttributeEncoderInterface.php @@ -0,0 +1,23 @@ +formBuilderFieldAttributeEncoders = $formBuilderFieldAttributeEncoders; + } + + /** + * @param mixed $value + */ + public function encode(string $type, $value): string + { + foreach ($this->formBuilderFieldAttributeEncoders as $formBuilderFieldAttributeEncoder) { + if ($formBuilderFieldAttributeEncoder->canEncode($type)) { + return $formBuilderFieldAttributeEncoder->encode($value); + } + } + + throw new InvalidArgumentException( + sprintf( + 'Unable to encode form builder field attribute %s. Make sure form builder field attribute encoder service for it is properly registered.', + $type + ) + ); + } + + /** + * @throws EmptyTranslatedAttributeException + */ + public function decode(string $type, string $value): string + { + foreach ($this->formBuilderFieldAttributeEncoders as $formBuilderFieldAttributeEncoder) { + if ($formBuilderFieldAttributeEncoder->canDecode($type)) { + return $formBuilderFieldAttributeEncoder->decode($value); + } + } + + throw new InvalidArgumentException( + sprintf( + 'Unable to decode form builder field attribute %s. Make sure form builder field attribute encoder service for it is properly registered.', + $type + ) + ); + } +} diff --git a/lib/Encoder/FormBuilderFieldAttribute/TextBlockFieldEncoder.php b/lib/Encoder/FormBuilderFieldAttribute/TextBlockFieldEncoder.php new file mode 100644 index 0000000..3f4e30e --- /dev/null +++ b/lib/Encoder/FormBuilderFieldAttribute/TextBlockFieldEncoder.php @@ -0,0 +1,42 @@ + Date: Wed, 20 Sep 2023 23:43:07 +0100 Subject: [PATCH 11/22] #106228 - Module traduction automatique [bloc link attribute] --- bundle/Resources/config/services.yml | 12 +-- .../BlockAttribute/NestedAttributeEncoder.php | 87 +++++++++++++++++++ lib/Encoder/Field/FormBuilderFieldEncoder.php | 6 +- lib/Encoder/Field/PageBuilderFieldEncoder.php | 2 +- .../TextLineFieldEncoder.php | 4 +- 5 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 lib/Encoder/BlockAttribute/NestedAttributeEncoder.php diff --git a/bundle/Resources/config/services.yml b/bundle/Resources/config/services.yml index 611ceda..575c7dc 100644 --- a/bundle/Resources/config/services.yml +++ b/bundle/Resources/config/services.yml @@ -10,10 +10,10 @@ services: - '../../../bundle/DependencyInjection/' - '../../../bundle/Entity/' - '../../../bundle/IbexaAutomatedTranslationBundle.php' - - EzSystems\EzPlatformAutomatedTranslationBundle\Form\Type\SubtreeLocationType: - tags: - - { name: form.type, alias: field.subtree_location } + # Form type +# EzSystems\EzPlatformAutomatedTranslationBundle\Form\Type\SubtreeLocationType: +# tags: +# - { name: form.type, alias: field.subtree_location } _instanceof: EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\FieldEncoderInterface: @@ -65,6 +65,8 @@ services: arguments: $richTextEncoder: '@EzSystems\EzPlatformAutomatedTranslation\Encoder\RichText\RichTextEncoder' + EzSystems\EzPlatformAutomatedTranslation\Encoder\BlockAttribute\NestedAttributeEncoder: ~ + # block attribute encoder manager EzSystems\EzPlatformAutomatedTranslation\Encoder\BlockAttribute\BlockAttributeEncoderManager: arguments: @@ -95,7 +97,7 @@ services: EzSystems\EzPlatformAutomatedTranslation\ClientProvider: arguments: [!tagged ezplatform.automated_translation.client] - + # Form type EzSystems\EzPlatformAutomatedTranslationBundle\Form\Extension\TranslationAddType: tags: - { name: form.type_extension, extended_type: Ibexa\AdminUi\Form\Type\Content\Translation\TranslationAddType } diff --git a/lib/Encoder/BlockAttribute/NestedAttributeEncoder.php b/lib/Encoder/BlockAttribute/NestedAttributeEncoder.php new file mode 100644 index 0000000..296ffa3 --- /dev/null +++ b/lib/Encoder/BlockAttribute/NestedAttributeEncoder.php @@ -0,0 +1,87 @@ + []]; + try { + $value = json_decode($value); + foreach ($value->attributes as $index => $attributes) { + foreach ($attributes as $key => $attribute){ + if($key === 'url'){ + $encodeValue['attributes']['key_'.$index][$key] = (string) $attribute->value; + } else { + $encodeValue['attributes']['key_'.$index][$key] = htmlentities((string) $attribute->value); + } + } + } + } catch (\Exception $e){ + $encodeValue = ['attributes' => []]; + } + + $encoder = new XmlEncoder(); + $payload = $encoder->encode($encodeValue, XmlEncoder::FORMAT); + $payload = str_replace('' . "\n", '', $payload); + $payload = str_replace( + [''], + ['<' . self::CDATA_FAKER_TAG . '>', ''], + $payload + ); + + return (string) $payload; + } + + public function decode(string $value): string + { + $data = str_replace( + ['<' . self::CDATA_FAKER_TAG . '>', ''], + [''], + $value + ); + + $encoder = new XmlEncoder(); + $decodeArray = $encoder->decode($data, XmlEncoder::FORMAT); + $decodeValues = ['attributes' => []]; + + if (isset($decodeArray['attributes']) && !empty($decodeArray['attributes'])) { + foreach ($decodeArray['attributes'] as $index => $attributes){ + $index = str_replace('key_', '',$index); + foreach ($attributes as $key => $attributeValue){ + + if($key === 'url'){ + $decodeValues['attributes'][$index][$key]['value'] = $attributeValue; + } else { + $decodeValues['attributes'][$index][$key]['value'] = html_entity_decode( + htmlspecialchars_decode(trim($attributeValue)) + ); + } + } + } + } + + return json_encode($decodeValues); + } +} diff --git a/lib/Encoder/Field/FormBuilderFieldEncoder.php b/lib/Encoder/Field/FormBuilderFieldEncoder.php index 28326b7..2d594f5 100644 --- a/lib/Encoder/Field/FormBuilderFieldEncoder.php +++ b/lib/Encoder/Field/FormBuilderFieldEncoder.php @@ -50,10 +50,8 @@ public function encode(Field $field): string $fieldDefinitionAttributesType = []; /** @var \Ibexa\Contracts\FormBuilder\FieldType\Model\Field $formField */ foreach ($value->getFormValue()?->getFields() as $formField) { - dump($formField); $attrs = []; - $fieldDefinition = $this->fieldDefinitionFactory->getFieldDefinition($formField->getIdentifier()); - dump($fieldDefinition); + $fieldDefinition = $this->fieldDefinitionFactory->getFieldDefinition($formField->getIdentifier()); $fieldDefinitionAttributesType[$formField->getIdentifier()] = []; foreach ($fieldDefinition->getAttributes() as $attribute) { $fieldDefinitionAttributesType[$formField->getIdentifier()][$attribute->getIdentifier()] = $attribute->getType(); @@ -77,7 +75,6 @@ public function encode(Field $field): string ]; } -dump($formFields); $encoder = new XmlEncoder(); $payload = $encoder->encode($formFields, XmlEncoder::FORMAT); @@ -89,7 +86,6 @@ public function encode(Field $field): string ['<' . self::CDATA_FAKER_TAG . '>', ''], $payload ); - dump($payload); return (string) $payload; diff --git a/lib/Encoder/Field/PageBuilderFieldEncoder.php b/lib/Encoder/Field/PageBuilderFieldEncoder.php index 1aab302..6af6eb7 100644 --- a/lib/Encoder/Field/PageBuilderFieldEncoder.php +++ b/lib/Encoder/Field/PageBuilderFieldEncoder.php @@ -122,7 +122,7 @@ public function decode(string $value, $previousFieldValue): APIValue } } } - dump(new Value($page)); + return new Value($page); } diff --git a/lib/Encoder/FormBuilderFieldAttribute/TextLineFieldEncoder.php b/lib/Encoder/FormBuilderFieldAttribute/TextLineFieldEncoder.php index 44e6f0e..28a3342 100644 --- a/lib/Encoder/FormBuilderFieldAttribute/TextLineFieldEncoder.php +++ b/lib/Encoder/FormBuilderFieldAttribute/TextLineFieldEncoder.php @@ -26,7 +26,7 @@ public function canDecode(string $type): bool public function encode($value): string { - return 'TEST '.htmlentities((string) $value); + return htmlentities((string) $value); } public function decode(string $value): string @@ -37,6 +37,6 @@ public function decode(string $value): string $value ); - return 'TEST_decode '.html_entity_decode(htmlspecialchars_decode(trim($value))); + return html_entity_decode(htmlspecialchars_decode(trim($value))); } } From 2e424c1a6f7cc44d42245eaa88e1b81304c364c0 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Wed, 27 Sep 2023 19:32:45 +0100 Subject: [PATCH 12/22] #106228 - Module traduction automatique [bloc link attribute] --- bundle/Command/TranslateContentCommand.php | 10 +++++----- .../views/themes/admin/auto_translation/view.html.twig | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bundle/Command/TranslateContentCommand.php b/bundle/Command/TranslateContentCommand.php index d29d801..49ee5aa 100644 --- a/bundle/Command/TranslateContentCommand.php +++ b/bundle/Command/TranslateContentCommand.php @@ -152,7 +152,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $message = sprintf('Start Translation of subtree %s.', $subtreeLocation->pathString); $this->getLogger()->info($message); $output->writeln($message); - $logMessage .= $message .'
    '; + $logMessage .= $message .'
    '; $adapter = new CallbackAdapter( function () use ($subtreeLocation): int { @@ -173,7 +173,7 @@ function (int $offset, int $limit) use ($subtreeLocation): iterable { $message = sprintf('Translate "%s" contents.', $pager->count()); $this->getLogger()->info($message); $output->writeln($message); - $logMessage .= $message .'
    '; + $logMessage .= $message .'
    '; $progressBar = new ProgressBar($output, $pager->count()); @@ -207,13 +207,13 @@ function (int $offset, int $limit) use ($subtreeLocation): iterable { ); $processes->run(); $processes->getErrorOutput(); - $logMessage .= $processes->getOutput() .'
    '; + $logMessage .= $processes->getOutput() .'
    '; if (!empty($processes->getErrorOutput())) { $message = $processes->getErrorOutput(); $this->getLogger()->info($message); - $logMessage .= $message .'
    '; + $logMessage .= $message .'
    '; } else { - $logMessage .= $processes->getOutput() .'
    '; + $logMessage .= $processes->getOutput() .'
    '; } $currentPage++; $progressBar->advance($maxPerPage); diff --git a/bundle/Resources/views/themes/admin/auto_translation/view.html.twig b/bundle/Resources/views/themes/admin/auto_translation/view.html.twig index 410511b..3e630e2 100644 --- a/bundle/Resources/views/themes/admin/auto_translation/view.html.twig +++ b/bundle/Resources/views/themes/admin/auto_translation/view.html.twig @@ -145,7 +145,7 @@ From 1c1006add1a99d7e19ec86f9dbec164455563f49 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Wed, 27 Sep 2023 20:59:32 +0100 Subject: [PATCH 13/22] #106228 - Module traduction automatique [ne valider les champs] --- lib/Translator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Translator.php b/lib/Translator.php index fdab510..720c379 100644 --- a/lib/Translator.php +++ b/lib/Translator.php @@ -73,6 +73,7 @@ public function getTranslatedContent(string $from, string $to, string $remoteSer $translatedFields = $this->getTranslatedFields($from, $to, $remoteServiceKey, $content); $contentDraft = $this->contentService->createContentDraft($content->contentInfo); + $contentUpdateStruct = $this->contentService->newContentUpdateStruct(); $contentUpdateStruct->initialLanguageCode = $to; From 33ccab2a35114d5d9603e7104dac6ac2ae0a9f82 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Tue, 14 Nov 2023 03:56:36 +0100 Subject: [PATCH 14/22] #109934 - Traduction Deeple / bulles d'information --- bundle/Event/RichTextDecodeEvent.php | 24 ++++++++++++++++++++++++ bundle/Event/RichTextEncodeEvent.php | 24 ++++++++++++++++++++++++ bundle/Events.php | 10 ++++++++++ lib/Client/Deepl.php | 14 ++++++++++++-- lib/Encoder/RichText/RichTextEncoder.php | 22 +++++++++++++++++++--- 5 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 bundle/Event/RichTextDecodeEvent.php create mode 100644 bundle/Event/RichTextEncodeEvent.php diff --git a/bundle/Event/RichTextDecodeEvent.php b/bundle/Event/RichTextDecodeEvent.php new file mode 100644 index 0000000..f7b277f --- /dev/null +++ b/bundle/Event/RichTextDecodeEvent.php @@ -0,0 +1,24 @@ +xmlValue; + } + + public function setValue(string $value): void + { + $this->xmlValue = $value; + } +} diff --git a/bundle/Event/RichTextEncodeEvent.php b/bundle/Event/RichTextEncodeEvent.php new file mode 100644 index 0000000..53bc043 --- /dev/null +++ b/bundle/Event/RichTextEncodeEvent.php @@ -0,0 +1,24 @@ +xmlValue; + } + + public function setValue(string $value): void + { + $this->xmlValue = $value; + } +} diff --git a/bundle/Events.php b/bundle/Events.php index 1628efc..fab8f42 100644 --- a/bundle/Events.php +++ b/bundle/Events.php @@ -19,4 +19,14 @@ final class Events * @Event("\EzSystems\EzPlatformAutomatedTranslationBundle\Event\FieldDecodeEvent") */ const POST_FIELD_DECODE = 'ez_automated_translation.post_field_decode'; + + /** + * @Event("\EzSystems\EzPlatformAutomatedTranslationBundle\Event\RichTextEncodeEvent") + */ + const PRE_RICHTEXT_ENCODE = 'ez_automated_translation.pre_richtext_encode'; + + /** + * @Event("\EzSystems\EzPlatformAutomatedTranslationBundle\Event\RichTextDecodeEvent") + */ + const PRE_RICHTEXT_DECODE = 'ez_automated_translation.pre_richtext_decode'; } diff --git a/lib/Client/Deepl.php b/lib/Client/Deepl.php index 27f5470..27df68b 100644 --- a/lib/Client/Deepl.php +++ b/lib/Client/Deepl.php @@ -20,6 +20,9 @@ class Deepl implements ClientInterface /** @var string */ private $baseUri; + /** @var array */ + private $nonSplittingTags; + public function getServiceAlias(): string { return 'deepl'; @@ -36,7 +39,10 @@ public function setConfiguration(array $configuration): void throw new ClientNotConfiguredException('authKey is required'); } $this->authKey = $configuration['authKey']; - $this->baseUri = isset($configuration['baseUri']) ? $configuration['baseUri'] : 'https://api.deepl.com'; + $this->baseUri = $configuration['baseUri'] ?? 'https://api.deepl.com'; + if (isset($configuration['nonSplittingTags'])) { + $this->nonSplittingTags = array_filter(explode(',', $configuration['nonSplittingTags'])); + } } public function translate(string $payload, ?string $from, string $to): string @@ -45,9 +51,13 @@ public function translate(string $payload, ?string $from, string $to): string 'auth_key' => $this->authKey, 'target_lang' => $this->normalized($to), 'tag_handling' => 'xml', - 'text' => $payload, + 'text' => $payload ]; + if (!empty($this->nonSplittingTags)){ + $parameters['non_splitting_tags'] = implode(',', $this->nonSplittingTags); + } + if (null !== $from) { $parameters += [ 'source_lang' => $this->normalized($from), diff --git a/lib/Encoder/RichText/RichTextEncoder.php b/lib/Encoder/RichText/RichTextEncoder.php index a1d8c99..7c3d577 100644 --- a/lib/Encoder/RichText/RichTextEncoder.php +++ b/lib/Encoder/RichText/RichTextEncoder.php @@ -8,7 +8,11 @@ namespace EzSystems\EzPlatformAutomatedTranslation\Encoder\RichText; +use EzSystems\EzPlatformAutomatedTranslationBundle\Event\RichTextDecodeEvent; +use EzSystems\EzPlatformAutomatedTranslationBundle\Event\RichTextEncodeEvent; +use EzSystems\EzPlatformAutomatedTranslationBundle\Events; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; final class RichTextEncoder { @@ -47,9 +51,14 @@ final class RichTextEncoder /** @var array */ private $placeHolderMap; + /** @var EventDispatcherInterface */ + private $eventDispatcher; + public function __construct( - ConfigResolverInterface $configResolver + ConfigResolverInterface $configResolver, + EventDispatcherInterface $eventDispatcher, ) { + $this->eventDispatcher = $eventDispatcher; $tags = $configResolver->getParameter( 'non_translatable_tags', 'ez_platform_automated_translation' @@ -86,6 +95,10 @@ public function __construct( public function encode(string $xmlString): string { + $event = new RichTextEncodeEvent($xmlString); + $this->eventDispatcher->dispatch($event, Events::PRE_RICHTEXT_ENCODE); + $xmlString = $event->getValue(); + if (strpos($xmlString, self::XML_MARKUP . "\n") !== false) { $xmlString = substr($xmlString, strlen(self::XML_MARKUP . "\n")); } @@ -132,17 +145,20 @@ function ($matches) use ($tag) { $xmlString = str_replace("", "", $xmlString); } - $xmlString = str_replace( + return str_replace( [''], ['<' . self::CDATA_FAKER_TAG . '>', ''], $xmlString ); - return $xmlString; } public function decode(string $value): string { + $event = new RichTextDecodeEvent($value); + $this->eventDispatcher->dispatch($event, Events::PRE_RICHTEXT_DECODE); + $value = $event->getValue(); + $value = str_replace( ['<' . self::CDATA_FAKER_TAG . '>', ''], [''], From 80b98712f319cdfc773a1291863fa521ac41d56b Mon Sep 17 00:00:00 2001 From: Mohammed Date: Fri, 8 Dec 2023 10:57:35 +0100 Subject: [PATCH 15/22] =?UTF-8?q?#113907=20-=20(back=20office)=20Probl?= =?UTF-8?q?=C3=A8me=20d'enregistrement=20des=20contenus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EzPlatformAutomatedTranslationExtension.php | 1 - bundle/Resources/config/ezadminui.yml | 17 ----------------- .../views/ezadminui/javascripts.html.twig | 1 - .../views/ezadminui/stylesheet.html.twig | 1 - .../admin/auto_translation/view.html.twig | 10 ++++++++++ 5 files changed, 10 insertions(+), 20 deletions(-) delete mode 100644 bundle/Resources/config/ezadminui.yml delete mode 100644 bundle/Resources/views/ezadminui/javascripts.html.twig delete mode 100644 bundle/Resources/views/ezadminui/stylesheet.html.twig diff --git a/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php b/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php index 9ac1bb9..ca8cdc7 100644 --- a/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php +++ b/bundle/DependencyInjection/EzPlatformAutomatedTranslationExtension.php @@ -45,7 +45,6 @@ public function load(array $configs, ContainerBuilder $container): void return; } - $loader->load('ezadminui.yml'); $loader->load('default_settings.yml'); $loader->load('services.yml'); // always needed because of Bundle extension. diff --git a/bundle/Resources/config/ezadminui.yml b/bundle/Resources/config/ezadminui.yml deleted file mode 100644 index 6ba0785..0000000 --- a/bundle/Resources/config/ezadminui.yml +++ /dev/null @@ -1,17 +0,0 @@ -services: - - ezautotranslate.ezadminui.component.javascripts: - parent: Ibexa\AdminUi\Component\TwigComponent - public: false - arguments: - $template: '@@EzPlatformAutomatedTranslation/ezadminui/javascripts.html.twig' - tags: - - { name: ibexa.admin_ui.component, group: 'script-body' } - - ezautotranslate.ezadminui.component.stylesheet: - parent: Ibexa\AdminUi\Component\TwigComponent - public: false - arguments: - $template: '@@EzPlatformAutomatedTranslation/ezadminui/stylesheet.html.twig' - tags: - - { name: ibexa.admin_ui.component, group: 'stylesheet-body' } diff --git a/bundle/Resources/views/ezadminui/javascripts.html.twig b/bundle/Resources/views/ezadminui/javascripts.html.twig deleted file mode 100644 index 5f6a5cf..0000000 --- a/bundle/Resources/views/ezadminui/javascripts.html.twig +++ /dev/null @@ -1 +0,0 @@ -{{ encore_entry_script_tags('ezplatform-automated-translation-js', null, 'ibexa') }} diff --git a/bundle/Resources/views/ezadminui/stylesheet.html.twig b/bundle/Resources/views/ezadminui/stylesheet.html.twig deleted file mode 100644 index d085506..0000000 --- a/bundle/Resources/views/ezadminui/stylesheet.html.twig +++ /dev/null @@ -1 +0,0 @@ -{{ encore_entry_link_tags('ezplatform-automated-translation-css', null, 'ibexa') }} diff --git a/bundle/Resources/views/themes/admin/auto_translation/view.html.twig b/bundle/Resources/views/themes/admin/auto_translation/view.html.twig index 3e630e2..da6cdca 100644 --- a/bundle/Resources/views/themes/admin/auto_translation/view.html.twig +++ b/bundle/Resources/views/themes/admin/auto_translation/view.html.twig @@ -1,5 +1,15 @@ {% extends '@ibexadesign/ui/layout.html.twig' %} +{% block javascripts %} + {{ parent() }} + {{ encore_entry_script_tags('ezplatform-automated-translation-js', null, 'ibexa') }} +{% endblock %} + +{% block stylesheets %} + {{ parent() }} + {{ encore_entry_link_tags('ezplatform-automated-translation-css', null, 'ibexa') }} +{% endblock %} + {% block content %} {% if form_search.vars.data.sort is defined %} {% set current_field = form_search.vars.data.sort['field'] %} From 2e78f5dda43d52faa3f6b3f8d96ab9c2c7c77354 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Mon, 18 Dec 2023 20:45:33 +0100 Subject: [PATCH 16/22] =?UTF-8?q?#113888=20-=20Traduction=20-=20les=20trad?= =?UTF-8?q?uctions=20des=20langues=20Russes=20et=20Chinois=20simplifi?= =?UTF-8?q?=C3=A9=20tombent=20en=20erreur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bundle/Resources/config/default_settings.yml | 1 - bundle/Resources/doc/USAGE.md | 44 +++++++++++++++++++- lib/Client/Deepl.php | 24 ++++++++--- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/bundle/Resources/config/default_settings.yml b/bundle/Resources/config/default_settings.yml index db5ca3b..4ad0eef 100644 --- a/bundle/Resources/config/default_settings.yml +++ b/bundle/Resources/config/default_settings.yml @@ -4,4 +4,3 @@ parameters: ez_platform_automated_translation.default.non_translatable_tags: [] ez_platform_automated_translation.default.non_translatable_self_closed_tags: [] ez_platform_automated_translation.default.non_valid_attribute_tags: [] - diff --git a/bundle/Resources/doc/USAGE.md b/bundle/Resources/doc/USAGE.md index d25164e..1edba9b 100644 --- a/bundle/Resources/doc/USAGE.md +++ b/bundle/Resources/doc/USAGE.md @@ -16,6 +16,48 @@ ez_platform_automated_translation: deepl: authKey: 'deepl-pro-key' baseUri: 'api-url' #NEEDED ONLY FOR FREE API# - + nonSplittingTags: 'emphasis,subscript,superscript,strong' + supported_languages_mapping: + en_US: EN-US + en_GB: EN-GB + en_AU: EN + en_CA: EN + en_NZ: EN + es_ES: ES + bg_BG: BG + cs_CZ: CS + da_DK: DA + de_DE: DE + el_GR: EL + et_EE: ET + fi_FI: FI + fr_FR: FR + fr_BE: FR + fr_CA: FR + fr_CH: FR + hu_HU: HU + it_CH: IT + it_IT: IT + ja_JP: JP + ko_KR: KO + lt_LT: LT + lv_LV: LV + no_NO: NB + nl_BE: NL + nl_NL: NL + pl_PL: PL + pt_BR: PT-BR + pt_MZ: PT + pt_PT: PT-PT + ro_RO: RO + ru_RU: RU + sk_SK: SK + sl_SI: SL + sv_SE: SV + tr_TR: TR + uk_UA: UK + zh_CN: ZH + zh_HK: ZH + zh_TW: ZH ``` diff --git a/lib/Client/Deepl.php b/lib/Client/Deepl.php index 27df68b..d8fb656 100644 --- a/lib/Client/Deepl.php +++ b/lib/Client/Deepl.php @@ -9,19 +9,21 @@ namespace EzSystems\EzPlatformAutomatedTranslation\Client; use EzSystems\EzPlatformAutomatedTranslation\Exception\ClientNotConfiguredException; -use EzSystems\EzPlatformAutomatedTranslation\Exception\InvalidLanguageCodeException; use GuzzleHttp\Client; class Deepl implements ClientInterface { /** @var string */ - private $authKey; + private string $authKey; /** @var string */ - private $baseUri; + private string $baseUri; /** @var array */ - private $nonSplittingTags; + private array $nonSplittingTags; + + /** @var array */ + private array $supportedLanguagesMapping = []; public function getServiceAlias(): string { @@ -43,6 +45,10 @@ public function setConfiguration(array $configuration): void if (isset($configuration['nonSplittingTags'])) { $this->nonSplittingTags = array_filter(explode(',', $configuration['nonSplittingTags'])); } + if (isset($configuration['supported_languages_mapping'])) { + $this->supportedLanguagesMapping = array_unique((array) $configuration['supported_languages_mapping']); + } + } public function translate(string $payload, ?string $from, string $to): string @@ -87,6 +93,9 @@ private function normalized(string $languageCode): string|null if (\in_array($languageCode, self::LANGUAGE_CODES)) { return $languageCode; } + if (isset($this->supportedLanguagesMapping[$languageCode])) { + return $this->supportedLanguagesMapping[$languageCode]; + } $code = strtoupper(substr($languageCode, 0, 2)); if (\in_array($code, self::LANGUAGE_CODES)) { @@ -97,7 +106,10 @@ private function normalized(string $languageCode): string|null } /** - * List of available code https://www.deepl.com/api.html. + * List of available code https://www.deepl.com/docs-api/translate-text */ - private const LANGUAGE_CODES = ['EN', 'DE', 'FR', 'ES', 'IT', 'NL', 'PL', 'JA']; + private const LANGUAGE_CODES = ['BG', 'CS','DA', 'DE', 'EL', 'EN','EN-GB','EN-US', 'ES', 'ET', + 'FI','FR', 'HU', 'ID', 'IT', 'JA', 'KO', 'LT', 'LV', 'NB', 'NL', 'PL', 'PT', 'PT-BR', 'PT-PT', 'RO', + 'RU', 'SK', 'SL', 'SV', 'TR', 'UK', 'ZH' + ]; } From 479cd74d801998f59236fd13a0b8eecc8b7ddfed Mon Sep 17 00:00:00 2001 From: Mohammed Date: Sat, 30 Dec 2023 03:09:04 +0100 Subject: [PATCH 17/22] #114792 - Back-office - Traduction des blocs ne fonctionne plus --- lib/Encoder.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Encoder.php b/lib/Encoder.php index 0d6667d..c6503dd 100644 --- a/lib/Encoder.php +++ b/lib/Encoder.php @@ -21,6 +21,7 @@ use InvalidArgumentException; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Ibexa\FieldTypePage\FieldType\LandingPage\Value as LandingPageValue; /** * Class Encoder. @@ -76,10 +77,10 @@ class Encoder /** @var ContentTypeService */ private $contentTypeService; - /** @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface */ + /** @var EventDispatcherInterface */ private $eventDispatcher; - /** @var \EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\FieldEncoderManager */ + /** @var FieldEncoderManager */ private $fieldEncoderManager; public function __construct( @@ -104,6 +105,10 @@ public function encode(Content $content): string } $type = \get_class($field->value); + if ($field->value instanceof LandingPageValue){ + $type = LandingPageValue::class; + } + if (null === ($value = $this->encodeField($field))) { continue; } From d6600b75a8f0aadba32084fbb0e3afd424f1b86f Mon Sep 17 00:00:00 2001 From: Mohammed Date: Wed, 7 Feb 2024 00:19:45 +0100 Subject: [PATCH 18/22] #116150 - [ouvrage] le champ ISBN n'est pas traduisible --- bundle/DependencyInjection/Configuration.php | 4 ++-- bundle/Resources/config/default_settings.yml | 1 + lib/Encoder.php | 15 +++++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/bundle/DependencyInjection/Configuration.php b/bundle/DependencyInjection/Configuration.php index 56374e1..5392c0b 100644 --- a/bundle/DependencyInjection/Configuration.php +++ b/bundle/DependencyInjection/Configuration.php @@ -22,8 +22,8 @@ public function getConfigTreeBuilder(): TreeBuilder ->variableNode('configurations')->end() ->arrayNode('non_translatable_characters')->end() ->arrayNode('non_translatable_tags')->end() - ->arrayNode('non_translatable_self_closed_tags')->end(); - + ->arrayNode('non_translatable_self_closed_tags')->end() + ->arrayNode('exclude_attribute')->end(); return $treeBuilder; } } diff --git a/bundle/Resources/config/default_settings.yml b/bundle/Resources/config/default_settings.yml index 4ad0eef..c17faff 100644 --- a/bundle/Resources/config/default_settings.yml +++ b/bundle/Resources/config/default_settings.yml @@ -4,3 +4,4 @@ parameters: ez_platform_automated_translation.default.non_translatable_tags: [] ez_platform_automated_translation.default.non_translatable_self_closed_tags: [] ez_platform_automated_translation.default.non_valid_attribute_tags: [] + ez_platform_automated_translation.default.exclude_attributes: [] diff --git a/lib/Encoder.php b/lib/Encoder.php index c6503dd..b1830da 100644 --- a/lib/Encoder.php +++ b/lib/Encoder.php @@ -11,6 +11,7 @@ use Ibexa\Contracts\Core\Repository\ContentTypeService; use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\Content\Field; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\FieldType\Value; use Ibexa\Contracts\Core\FieldType\Value as SPIValue; use EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\FieldEncoderManager; @@ -83,14 +84,18 @@ class Encoder /** @var FieldEncoderManager */ private $fieldEncoderManager; + /** @var ConfigResolverInterface */ + private $configResolver; public function __construct( ContentTypeService $contentTypeService, EventDispatcherInterface $eventDispatcher, - FieldEncoderManager $fieldEncoderManager + FieldEncoderManager $fieldEncoderManager, + ConfigResolverInterface $configResolver, ) { $this->contentTypeService = $contentTypeService; $this->eventDispatcher = $eventDispatcher; $this->fieldEncoderManager = $fieldEncoderManager; + $this->configResolver = $configResolver; } public function encode(Content $content): string @@ -131,19 +136,25 @@ public function encode(Content $content): string public function decode(string $xml, Content $sourceContent): array { + $excludeAttributes = (array) $this->configResolver + ->getParameter('exclude_attributes', 'ez_platform_automated_translation'); $encoder = new XmlEncoder(); $data = str_replace( ['<' . self::CDATA_FAKER_TAG . '>', ''], [''], $xml ); - + $contentTypeIdentifier = $sourceContent->getContentType()->identifier; $decodeArray = $encoder->decode($data, XmlEncoder::FORMAT); $results = []; foreach ($decodeArray as $fieldIdentifier => $xmlValue) { $previousFieldValue = $sourceContent->getField($fieldIdentifier)->value; $type = $xmlValue['@type']; $stringValue = $xmlValue['#']; + $excludeAttribute = $contentTypeIdentifier.'/'.$fieldIdentifier; + if (in_array($excludeAttribute, $excludeAttributes, true)){ + continue; + } if (null === ($fieldValue = $this->decodeField($type, $stringValue, $previousFieldValue))) { continue; From f61c818b00cd0bb279632d2b67100c93434a72f9 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Fri, 16 Feb 2024 00:18:22 +0100 Subject: [PATCH 19/22] Exclude fields from overwrite translation --- bundle/Resources/config/default_settings.yml | 2 +- lib/Encoder.php | 16 +++---------- lib/Translator.php | 25 +++++++++++++++----- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/bundle/Resources/config/default_settings.yml b/bundle/Resources/config/default_settings.yml index c17faff..2a81ba2 100644 --- a/bundle/Resources/config/default_settings.yml +++ b/bundle/Resources/config/default_settings.yml @@ -4,4 +4,4 @@ parameters: ez_platform_automated_translation.default.non_translatable_tags: [] ez_platform_automated_translation.default.non_translatable_self_closed_tags: [] ez_platform_automated_translation.default.non_valid_attribute_tags: [] - ez_platform_automated_translation.default.exclude_attributes: [] + ez_platform_automated_translation.default.overwrite_exclude_attributes: [] diff --git a/lib/Encoder.php b/lib/Encoder.php index b1830da..f16a62b 100644 --- a/lib/Encoder.php +++ b/lib/Encoder.php @@ -11,7 +11,6 @@ use Ibexa\Contracts\Core\Repository\ContentTypeService; use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\Content\Field; -use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\FieldType\Value; use Ibexa\Contracts\Core\FieldType\Value as SPIValue; use EzSystems\EzPlatformAutomatedTranslation\Encoder\Field\FieldEncoderManager; @@ -84,18 +83,15 @@ class Encoder /** @var FieldEncoderManager */ private $fieldEncoderManager; - /** @var ConfigResolverInterface */ - private $configResolver; public function __construct( ContentTypeService $contentTypeService, EventDispatcherInterface $eventDispatcher, - FieldEncoderManager $fieldEncoderManager, - ConfigResolverInterface $configResolver, + FieldEncoderManager $fieldEncoderManager ) { $this->contentTypeService = $contentTypeService; $this->eventDispatcher = $eventDispatcher; $this->fieldEncoderManager = $fieldEncoderManager; - $this->configResolver = $configResolver; + } public function encode(Content $content): string @@ -136,25 +132,19 @@ public function encode(Content $content): string public function decode(string $xml, Content $sourceContent): array { - $excludeAttributes = (array) $this->configResolver - ->getParameter('exclude_attributes', 'ez_platform_automated_translation'); $encoder = new XmlEncoder(); $data = str_replace( ['<' . self::CDATA_FAKER_TAG . '>', ''], [''], $xml ); - $contentTypeIdentifier = $sourceContent->getContentType()->identifier; + $decodeArray = $encoder->decode($data, XmlEncoder::FORMAT); $results = []; foreach ($decodeArray as $fieldIdentifier => $xmlValue) { $previousFieldValue = $sourceContent->getField($fieldIdentifier)->value; $type = $xmlValue['@type']; $stringValue = $xmlValue['#']; - $excludeAttribute = $contentTypeIdentifier.'/'.$fieldIdentifier; - if (in_array($excludeAttribute, $excludeAttributes, true)){ - continue; - } if (null === ($fieldValue = $this->decodeField($type, $stringValue, $previousFieldValue))) { continue; diff --git a/lib/Translator.php b/lib/Translator.php index 720c379..653541b 100644 --- a/lib/Translator.php +++ b/lib/Translator.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; use Ibexa\Core\MVC\Symfony\Locale\LocaleConverterInterface; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; class Translator { @@ -28,19 +29,21 @@ class Translator /** @var Encoder */ private $encoder; - /** @var \Ibexa\Contracts\Core\Repository\ContentService */ + /** @var ContentService */ private $contentService; - /** @var \Ibexa\Contracts\Core\Repository\ContentTypeService */ + /** @var ContentTypeService */ private $contentTypeService; - + /** @var ConfigResolverInterface */ + private $configResolver; public function __construct( TranslatorGuard $guard, LocaleConverterInterface $localeConverter, ClientProvider $clientProvider, Encoder $encoder, ContentService $contentService, - ContentTypeService $contentTypeService + ContentTypeService $contentTypeService, + ConfigResolverInterface $configResolver ) { $this->guard = $guard; $this->localeConverter = $localeConverter; @@ -48,6 +51,7 @@ public function __construct( $this->encoder = $encoder; $this->contentService = $contentService; $this->contentTypeService = $contentTypeService; + $this->configResolver = $configResolver; } public function getTranslatedFields(?string $from, ?string $to, string $remoteServiceKey, Content $content): array @@ -80,15 +84,24 @@ public function getTranslatedContent(string $from, string $to, string $remoteSer $contentType = $this->contentTypeService->loadContentType( $content->contentInfo->contentTypeId ); + $excludeAttributes = (array) $this->configResolver + ->getParameter('overwrite_exclude_attributes', 'ez_platform_automated_translation'); foreach ($contentType->getFieldDefinitions() as $field) { if (!$field->isTranslatable) { continue; } - /** @var FieldDefinition $field */ $fieldName = $field->identifier; - $newValue = $translatedFields[$fieldName] ?? $content->getFieldValue($fieldName); + //Exclude fields from overwrite translation + $excludeAttribute = $contentType->identifier.'/'.$field->identifier; + if (in_array($excludeAttribute, $excludeAttributes, true) && + null !== $contentDraft->getFieldValue($fieldName, $to) + ) { + $newValue = $contentDraft->getFieldValue($fieldName, $to); + } else { + $newValue = $translatedFields[$fieldName] ?? $content->getFieldValue($fieldName); + } $contentUpdateStruct->setField($fieldName, $newValue, $to); } From cfededf02985d3157a4169a1226330fdd4190b2f Mon Sep 17 00:00:00 2001 From: mohamed-larbi-jebari <106680184+mohamed-larbi-jebari@users.noreply.github.com> Date: Tue, 4 Jun 2024 02:00:47 +0100 Subject: [PATCH 20/22] Update USAGE.md --- bundle/Resources/doc/USAGE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/Resources/doc/USAGE.md b/bundle/Resources/doc/USAGE.md index 1edba9b..87adca7 100644 --- a/bundle/Resources/doc/USAGE.md +++ b/bundle/Resources/doc/USAGE.md @@ -38,7 +38,7 @@ ez_platform_automated_translation: hu_HU: HU it_CH: IT it_IT: IT - ja_JP: JP + ja_JP: JA ko_KR: KO lt_LT: LT lv_LV: LV From 93d4022c24599d15a892c66019efbeca46a47e5a Mon Sep 17 00:00:00 2001 From: Mohammed Date: Mon, 3 Feb 2025 19:51:26 +0100 Subject: [PATCH 21/22] #125248 - March 2025: Deprecating GET requests to /translate and authenticating with auth_key --- lib/Client/Deepl.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/Client/Deepl.php b/lib/Client/Deepl.php index d8fb656..b5ecb67 100644 --- a/lib/Client/Deepl.php +++ b/lib/Client/Deepl.php @@ -54,14 +54,13 @@ public function setConfiguration(array $configuration): void public function translate(string $payload, ?string $from, string $to): string { $parameters = [ - 'auth_key' => $this->authKey, 'target_lang' => $this->normalized($to), 'tag_handling' => 'xml', - 'text' => $payload + 'text' => [$payload] ]; if (!empty($this->nonSplittingTags)){ - $parameters['non_splitting_tags'] = implode(',', $this->nonSplittingTags); + $parameters['non_splitting_tags'] = $this->nonSplittingTags; } if (null !== $from) { @@ -76,7 +75,12 @@ public function translate(string $payload, ?string $from, string $to): string 'timeout' => 5.0, ] ); - $response = $http->post('/v2/translate', ['form_params' => $parameters]); + $response = $http->post('/v2/translate', [ + 'headers' => [ + 'Authorization' => 'DeepL-Auth-Key ' . $this->authKey + ], + 'json' => $parameters + ]); // May use the native json method from guzzle $json = json_decode($response->getBody()->getContents()); From 78ccfaec43f242b6f6279f11c481cd3f572e888a Mon Sep 17 00:00:00 2001 From: Mohammed Date: Thu, 18 Sep 2025 22:29:00 +0100 Subject: [PATCH 22/22] #130636 - [DeepL] Modification Header Authentification HTT --- bundle/Resources/doc/USAGE.md | 1 - composer.json | 7 +++-- lib/Client/Deepl.php | 49 ++++++++++++++--------------------- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/bundle/Resources/doc/USAGE.md b/bundle/Resources/doc/USAGE.md index 87adca7..687e7cc 100644 --- a/bundle/Resources/doc/USAGE.md +++ b/bundle/Resources/doc/USAGE.md @@ -15,7 +15,6 @@ ez_platform_automated_translation: apiKey: 'google-api-key' deepl: authKey: 'deepl-pro-key' - baseUri: 'api-url' #NEEDED ONLY FOR FREE API# nonSplittingTags: 'emphasis,subscript,superscript,strong' supported_languages_mapping: en_US: EN-US diff --git a/composer.json b/composer.json index ce3da85..a1cf939 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,9 @@ "symfony/http-foundation": "^5.0", "symfony/event-dispatcher": "^5.0", "guzzlehttp/guzzle": "^6.3.0", - "ext-json": "*" + "ext-json": "*", + "deeplcom/deepl-php": "^1.12", + "http-interop/http-factory-guzzle": "^1.2" }, "require-dev": { "squizlabs/php_codesniffer": "^3.2", @@ -61,7 +63,8 @@ "config": { "allow-plugins": { "symfony/flex": true, - "ibexa/post-install": true + "ibexa/post-install": true, + "php-http/discovery": true } } } diff --git a/lib/Client/Deepl.php b/lib/Client/Deepl.php index b5ecb67..710a487 100644 --- a/lib/Client/Deepl.php +++ b/lib/Client/Deepl.php @@ -8,17 +8,16 @@ namespace EzSystems\EzPlatformAutomatedTranslation\Client; +use DeepL\DeepLClient; +use DeepL\DeepLException; +use DeepL\TranslateTextOptions; use EzSystems\EzPlatformAutomatedTranslation\Exception\ClientNotConfiguredException; -use GuzzleHttp\Client; class Deepl implements ClientInterface { /** @var string */ private string $authKey; - /** @var string */ - private string $baseUri; - /** @var array */ private array $nonSplittingTags; @@ -41,7 +40,7 @@ public function setConfiguration(array $configuration): void throw new ClientNotConfiguredException('authKey is required'); } $this->authKey = $configuration['authKey']; - $this->baseUri = $configuration['baseUri'] ?? 'https://api.deepl.com'; + if (isset($configuration['nonSplittingTags'])) { $this->nonSplittingTags = array_filter(explode(',', $configuration['nonSplittingTags'])); } @@ -51,40 +50,30 @@ public function setConfiguration(array $configuration): void } + /** + * @throws DeepLException + */ public function translate(string $payload, ?string $from, string $to): string { - $parameters = [ - 'target_lang' => $this->normalized($to), - 'tag_handling' => 'xml', - 'text' => [$payload] + $sourceLang = null; + $targetLang = $this->normalized($to); + $options = [ + TranslateTextOptions::TAG_HANDLING => 'xml', ]; - if (!empty($this->nonSplittingTags)){ - $parameters['non_splitting_tags'] = $this->nonSplittingTags; + if (!empty($this->nonSplittingTags)) { + $options[TranslateTextOptions::NON_SPLITTING_TAGS] = $this->nonSplittingTags; } if (null !== $from) { - $parameters += [ - 'source_lang' => $this->normalized($from), - ]; + $sourceLang = $this->normalized($from); } - $http = new Client( - [ - 'base_uri' => $this->baseUri, - 'timeout' => 5.0, - ] - ); - $response = $http->post('/v2/translate', [ - 'headers' => [ - 'Authorization' => 'DeepL-Auth-Key ' . $this->authKey - ], - 'json' => $parameters - ]); - // May use the native json method from guzzle - $json = json_decode($response->getBody()->getContents()); - - return $json->translations[0]->text; + $deeplClient = new DeepLClient($this->authKey); + + $result = $deeplClient->translateText($payload, $sourceLang, $targetLang, $options); + + return $result->text; } public function supportsLanguage(string $languageCode): bool
    + + {{ 'Num' }} + + {{ 'auto_translation.list.user_name'|trans }} @@ -80,11 +85,19 @@ {{ 'auto_translation.list.status'|trans }} + + {{ 'Log'|trans }} + +
    + {{ item.id }} + {{ item.user_name }} {{ 'auto_translation.list.overwrite.value'|trans({'%count%': item.overwrite})|raw }} {{ item.created_at|date("d/m/Y H:i") }} - {{ ('auto_translation.list.status.' ~ item.status)|trans }} + + {{ ('auto_translation.list.status.' ~ item.status)|trans }} + {% if item.finished_at and item.status == 'finished' or item.status == 'failed' %}
    ({{ item.finished_at|date("d/m/Y H:i") }}) {% endif %}
    + {% if item.finished_at and item.status == 'finished' or item.status == 'failed' %} + + + {% endif %} +