From 578226c4ebe6bfd67286c2654f5e7cffb686806c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Parafi=C5=84ski?= Date: Fri, 28 Sep 2018 15:55:35 +0200 Subject: [PATCH] EZP-29408: As an editor I want to delete a content item with an image asset field (#591) * EZP-29408: As an editor I want to delete a content item with an image asset field * EZP-29408: As an editor I want to delete a content item with an image asset field * EZP-29408: Updated modals design * EZP-29408: changed when form options are build + CR fixes * fixup! EZP-29408: changed when form options are build + CR fixes * fixup! EZP-29408: changed when form options are build + CR fixes --- .../Controller/ContentViewController.php | 32 ++++- .../TrashLocationWithAssetController.php | 97 +++++++++++++ src/bundle/Resources/config/routing.yml | 6 + src/bundle/Resources/config/services.yml | 1 - .../js/scripts/button.state.radio.toggle.js | 17 +++ .../Resources/public/scss/_buttons.scss | 19 ++- src/bundle/Resources/public/scss/_forms.scss | 6 + src/bundle/Resources/public/scss/_modals.scss | 47 ++++++ .../Resources/translations/forms.en.xliff | 10 ++ .../Resources/translations/messages.en.xliff | 25 ++++ .../views/content/locationview.html.twig | 12 ++ .../modal_location_trash_with_asset.html.twig | 32 +++++ ...location_trash_with_single_asset.html.twig | 52 +++++++ .../Location/LocationTrashWithAssetData.php | 72 ++++++++++ src/lib/Form/Factory/FormFactory.php | 47 +++++- .../Location/LocationTrashWithAssetType.php | 135 ++++++++++++++++++ src/lib/Menu/ContentRightSidebarBuilder.php | 27 +++- .../Content/ContentHaveAssetRelation.php | 54 +++++++ .../Content/ContentHaveUniqueRelation.php | 58 ++++++++ .../LocationHaveUniqueAssetRelation.php | 29 ++++ ...cationHaveUniqueAssetRelationValidator.php | 56 ++++++++ 21 files changed, 816 insertions(+), 18 deletions(-) create mode 100644 src/bundle/Controller/Location/TrashLocationWithAssetController.php create mode 100644 src/bundle/Resources/public/js/scripts/button.state.radio.toggle.js create mode 100644 src/bundle/Resources/views/content/modal_location_trash_with_asset.html.twig create mode 100644 src/bundle/Resources/views/content/modal_location_trash_with_single_asset.html.twig create mode 100644 src/lib/Form/Data/Location/LocationTrashWithAssetData.php create mode 100644 src/lib/Form/Type/Location/LocationTrashWithAssetType.php create mode 100644 src/lib/Specification/Content/ContentHaveAssetRelation.php create mode 100644 src/lib/Specification/Content/ContentHaveUniqueRelation.php create mode 100644 src/lib/Validator/Constraints/LocationHaveUniqueAssetRelation.php create mode 100644 src/lib/Validator/Constraints/LocationHaveUniqueAssetRelationValidator.php diff --git a/src/bundle/Controller/ContentViewController.php b/src/bundle/Controller/ContentViewController.php index 63d51efab2..f0c75230a0 100644 --- a/src/bundle/Controller/ContentViewController.php +++ b/src/bundle/Controller/ContentViewController.php @@ -9,6 +9,7 @@ namespace EzSystems\EzPlatformAdminUiBundle\Controller; use eZ\Publish\API\Repository\BookmarkService; +use eZ\Publish\API\Repository\ContentService; use eZ\Publish\API\Repository\ContentTypeService; use eZ\Publish\API\Repository\LanguageService; use eZ\Publish\API\Repository\Values\Content\Location; @@ -20,9 +21,12 @@ use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationCopySubtreeData; use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationMoveData; use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationTrashData; +use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationTrashWithAssetData; use EzSystems\EzPlatformAdminUi\Form\Data\User\UserDeleteData; use EzSystems\EzPlatformAdminUi\Form\Data\User\UserEditData; use EzSystems\EzPlatformAdminUi\Form\Factory\FormFactory; +use EzSystems\EzPlatformAdminUi\Specification\Content\ContentHaveAssetRelation; +use EzSystems\EzPlatformAdminUi\Specification\Content\ContentHaveUniqueRelation; use EzSystems\EzPlatformAdminUi\Specification\ContentIsUser; use EzSystems\EzPlatformAdminUi\UI\Module\Subitems\ContentViewParameterSupplier as SubitemsContentViewParameterSupplier; use EzSystems\EzPlatformAdminUi\UI\Service\PathService; @@ -69,6 +73,9 @@ class ContentViewController extends Controller /** @var int */ private $defaultCustomUrlPaginationLimit; + /** @var \eZ\Publish\API\Repository\ContentService */ + private $contentService; + /** * @param \eZ\Publish\API\Repository\ContentTypeService $contentTypeService * @param \eZ\Publish\API\Repository\LanguageService $languageService @@ -77,6 +84,7 @@ class ContentViewController extends Controller * @param \EzSystems\EzPlatformAdminUi\UI\Module\Subitems\ContentViewParameterSupplier $subitemsContentViewParameterSupplier * @param \eZ\Publish\API\Repository\UserService $userService * @param \eZ\Publish\API\Repository\BookmarkService $bookmarkService + * @param \eZ\Publish\API\Repository\ContentService $contentService * @param int $defaultDraftPaginationLimit * @param array $siteAccessLanguages * @param int $defaultRolePaginationLimit @@ -92,6 +100,7 @@ public function __construct( SubitemsContentViewParameterSupplier $subitemsContentViewParameterSupplier, UserService $userService, BookmarkService $bookmarkService, + ContentService $contentService, int $defaultDraftPaginationLimit, array $siteAccessLanguages, int $defaultRolePaginationLimit, @@ -112,6 +121,7 @@ public function __construct( $this->defaultPolicyPaginationLimit = $defaultPolicyPaginationLimit; $this->defaultSystemUrlPaginationLimit = $defaultSystemUrlPaginationLimit; $this->defaultCustomUrlPaginationLimit = $defaultCustomUrlPaginationLimit; + $this->contentService = $contentService; } /** @@ -230,7 +240,27 @@ private function supplyContentActionForms(ContentView $view): void 'form_location_copy_subtree' => $locationCopySubtreeType->createView(), ]); - if ((new ContentIsUser($this->userService))->isSatisfiedBy($content)) { + $contentHaveAssetRelation = new ContentHaveAssetRelation($this->contentService); + if ($contentHaveAssetRelation + ->and(new ContentHaveUniqueRelation($this->contentService)) + ->isSatisfiedBy($content) + ) { + $trashWithAssetType = $this->formFactory->trashLocationWithAsset( + new LocationTrashWithAssetData($location) + ); + + $view->addParameters([ + 'form_location_trash_with_single_asset' => $trashWithAssetType->createView(), + ]); + } elseif ($contentHaveAssetRelation->isSatisfiedBy($content)) { + $locationTrashType = $this->formFactory->trashLocation( + new LocationTrashData($location) + ); + + $view->addParameters([ + 'form_location_trash_with_asset' => $locationTrashType->createView(), + ]); + } elseif ((new ContentIsUser($this->userService))->isSatisfiedBy($content)) { $userDeleteType = $this->formFactory->deleteUser( new UserDeleteData($content->contentInfo) ); diff --git a/src/bundle/Controller/Location/TrashLocationWithAssetController.php b/src/bundle/Controller/Location/TrashLocationWithAssetController.php new file mode 100644 index 0000000000..679c049c16 --- /dev/null +++ b/src/bundle/Controller/Location/TrashLocationWithAssetController.php @@ -0,0 +1,97 @@ +formFactory = $formFactory; + $this->submitHandler = $submitHandler; + $this->trashService = $trashService; + $this->locationService = $locationService; + $this->contentService = $contentService; + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException + */ + public function trashAction(Request $request): Response + { + $form = $this->formFactory->trashLocationWithAsset( + new LocationTrashWithAssetData() + ); + $form->handleRequest($request); + + if ($form->isSubmitted()) { + $result = $this->submitHandler->handle($form, function (LocationTrashWithAssetData $data) { + $location = $data->getLocation(); + $parentLocation = $this->locationService->loadLocation($location->parentLocationId); + if ($data->getTrashAssets() === LocationTrashWithAssetType::RADIO_SELECT_TRASH_WITH_ASSETS) { + $content = $this->contentService->loadContentByContentInfo($location->contentInfo); + $relations = $this->contentService->loadRelations($content->versionInfo); + $imageLocation = $this->locationService->loadLocation($relations[0]->destinationContentInfo->mainLocationId); + $this->trashService->trash($imageLocation); + } + + $this->trashService->trash($location); + + return $this->redirectToLocation($parentLocation); + }); + + if ($result instanceof Response) { + return $result; + } + } + + return $this->redirect($this->generateUrl('ezplatform.trash.list')); + } +} diff --git a/src/bundle/Resources/config/routing.yml b/src/bundle/Resources/config/routing.yml index dc9bd8b8c5..db328248a2 100644 --- a/src/bundle/Resources/config/routing.yml +++ b/src/bundle/Resources/config/routing.yml @@ -418,6 +418,12 @@ ezplatform.location.copy_subtree: defaults: _controller: 'EzPlatformAdminUiBundle:Location:copySubtree' +ezplatform.location.trash_with_asset: + path: /location/trash-with-asset + methods: ['POST'] + defaults: + _controller: 'EzPlatformAdminUiBundle:Location\TrashLocationWithAsset:trash' + # LocationView / Translation tab ezplatform.translation.add: diff --git a/src/bundle/Resources/config/services.yml b/src/bundle/Resources/config/services.yml index 3f64271d18..283485d58c 100644 --- a/src/bundle/Resources/config/services.yml +++ b/src/bundle/Resources/config/services.yml @@ -106,7 +106,6 @@ services: EzSystems\EzPlatformAdminUi\Form\Factory\FormFactory: ~ - EzSystems\EzPlatformAdminUi\Notification\FlashBagNotificationHandler: ~ EzSystems\EzPlatformAdminUi\Notification\NotificationHandlerInterface: '@EzSystems\EzPlatformAdminUi\Notification\FlashBagNotificationHandler' diff --git a/src/bundle/Resources/public/js/scripts/button.state.radio.toggle.js b/src/bundle/Resources/public/js/scripts/button.state.radio.toggle.js new file mode 100644 index 0000000000..1c3187b7e4 --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/button.state.radio.toggle.js @@ -0,0 +1,17 @@ +(function (global, doc) { + const toggleForms = [...doc.querySelectorAll('.ez-toggle-btn-state-radio')]; + + toggleForms.forEach((toggleForm) => { + const radioInputs = [...toggleForm.querySelectorAll('input[type="radio"]')]; + const toggleButtonState = () => { + const button = doc.querySelector(toggleForm.dataset.toggleButtonId); + const oneIsSelected = radioInputs.some(el => el.checked); + + if (oneIsSelected) { + button['removeAttribute']('disabled', true); + } + }; + + radioInputs.forEach(radioInput => radioInput.addEventListener('change', toggleButtonState, false)); + }); +})(window, document); diff --git a/src/bundle/Resources/public/scss/_buttons.scss b/src/bundle/Resources/public/scss/_buttons.scss index 68180b6afb..18ccc0e3a3 100644 --- a/src/bundle/Resources/public/scss/_buttons.scss +++ b/src/bundle/Resources/public/scss/_buttons.scss @@ -109,6 +109,20 @@ } } +.ez-modal--trash-with-asset { + .modal-footer { + .btn-secondary { + background-color: $ez-color-base-medium; + border-color: $ez-color-base-medium; + + &:hover { + background-color: $ez-color-base-medium-hover; + border-color: $ez-color-base-medium-hover; + } + } + } +} + .ez-content-view, .ez-trash-list-view { .ez-modal--send-to-trash { @@ -116,11 +130,6 @@ .form-check-inline { margin-right: 0; - .btn { - font-size: 1rem; - padding-bottom: .5rem; - } - .btn-danger { margin-right: 0; } diff --git a/src/bundle/Resources/public/scss/_forms.scss b/src/bundle/Resources/public/scss/_forms.scss index d424a09237..51122e4564 100644 --- a/src/bundle/Resources/public/scss/_forms.scss +++ b/src/bundle/Resources/public/scss/_forms.scss @@ -112,3 +112,9 @@ form:not(.form-inline) { color: $ez-color-danger; } } + +.ez-trash-with-asset-checkbox-list { + .form-check-input { + position: absolute; + } +} diff --git a/src/bundle/Resources/public/scss/_modals.scss b/src/bundle/Resources/public/scss/_modals.scss index 1afd84da66..236e843414 100644 --- a/src/bundle/Resources/public/scss/_modals.scss +++ b/src/bundle/Resources/public/scss/_modals.scss @@ -149,3 +149,50 @@ } } } + +.ez-modal--trash-with-asset { + .modal-dialog { + max-width: calculateRem(800px); + } + + .modal-header { + background: $ez-color-base-pale; + border-bottom: none; + + .modal-title { + font-size: calculateRem(24px); + } + + .close { + margin-top: calculateRem(-9px); + } + } + + .modal-body { + padding: calculateRem(32px); + background-color: $ez-ground-base-medium; + border-radius: 0 0 calculateRem(4px) calculateRem(4px); + + .ez-modal-body__main { + margin-bottom: calculateRem(24px); + + .ez-modal-body__main-content { + margin-bottom: 0; + } + } + + .ez-table-header, + .ez-table__draft-conflict { + margin-bottom: 0; + } + + .form-check { + margin-bottom: 0; + margin-left: calculateRem(50px); + } + } + + .modal-footer { + justify-content: center; + } +} diff --git a/src/bundle/Resources/translations/forms.en.xliff b/src/bundle/Resources/translations/forms.en.xliff index 56788fdea7..fed774b532 100644 --- a/src/bundle/Resources/translations/forms.en.xliff +++ b/src/bundle/Resources/translations/forms.en.xliff @@ -146,11 +146,21 @@ Move key: location_move.move + + Delete only %content_name% (%content_type%) + Delete only %content_name% (%content_type%) + key: location_trash_form.default_trash + Send to Trash Send to Trash key: location_trash_form.trash + + Delete %content_name% (%content_type%) and its related image assets + Delete %content_name% (%content_type%) and its related image assets + key: location_trash_form.trash_with_asset + Delete policies Delete policies diff --git a/src/bundle/Resources/translations/messages.en.xliff b/src/bundle/Resources/translations/messages.en.xliff index f92ea162f8..c9ed82ad85 100644 --- a/src/bundle/Resources/translations/messages.en.xliff +++ b/src/bundle/Resources/translations/messages.en.xliff @@ -249,6 +249,31 @@ Are you sure you want to send this content item to trash? key: trash.modal.message + + If you wish to delete this(/these) asset(s) too, first make sure they are not used by other content. You can check these content going to the asset(s) and looking at their content relations in the Relation tab. + If you wish to delete this(/these) asset(s) too, first make sure they are not used by other content. You can check these content going to the asset(s) and looking at their content relations in the Relation tab. + key: trash_asset.modal.message_body + + + You are about to delete a content that has one or several asset(s) field(s) used by other content items. These assets will remain available in system. + You are about to delete a content that has one or several asset(s) field(s) used by other content items. These assets will remain available in system. + key: trash_asset.modal.message_header + + + Deleting content + Deleting content + key: trash_asset_single.modal.header + + + You have the option to delete %content_name% (%content_type%) only or to also delete the assets it is using. Make your choice: + You have the option to delete %content_name% (%content_type%) only or to also delete the assets it is using. Make your choice: + key: trash_asset_single.modal.message + + + You are about to delete a content with one or several asset(s) field(s). + You are about to delete a content with one or several asset(s) field(s). + key: trash_asset_single.modal.message_main + diff --git a/src/bundle/Resources/views/content/locationview.html.twig b/src/bundle/Resources/views/content/locationview.html.twig index 136297633f..8ad0b998ac 100644 --- a/src/bundle/Resources/views/content/locationview.html.twig +++ b/src/bundle/Resources/views/content/locationview.html.twig @@ -94,6 +94,18 @@ {% if form_user_delete is defined %} {% include '@ezdesign/content/modal_user_delete.html.twig' with {'form': form_user_delete} only %} {% endif %} + {% if form_location_trash_with_asset is defined %} + {% include '@ezdesign/content/modal_location_trash_with_asset.html.twig' with { + 'form': form_location_trash_with_asset + } only %} + {% endif %} + {% if form_location_trash_with_single_asset is defined %} + {% include '@ezdesign/content/modal_location_trash_with_single_asset.html.twig' with { + 'form': form_location_trash_with_single_asset, + 'content_name': content.name, + 'content_type': contentType.name + } only %} + {% endif %} {{ form(form_location_copy, {'action': path('ezplatform.location.copy')}) }} {{ form(form_location_move, {'action': path('ezplatform.location.move')}) }} {{ form(form_location_copy_subtree, {'action': path('ezplatform.location.copy_subtree')}) }} diff --git a/src/bundle/Resources/views/content/modal_location_trash_with_asset.html.twig b/src/bundle/Resources/views/content/modal_location_trash_with_asset.html.twig new file mode 100644 index 0000000000..47e841ad6c --- /dev/null +++ b/src/bundle/Resources/views/content/modal_location_trash_with_asset.html.twig @@ -0,0 +1,32 @@ +{% form_theme form '@ezdesign/form_fields.html.twig' %} + + diff --git a/src/bundle/Resources/views/content/modal_location_trash_with_single_asset.html.twig b/src/bundle/Resources/views/content/modal_location_trash_with_single_asset.html.twig new file mode 100644 index 0000000000..7f4da3bbd1 --- /dev/null +++ b/src/bundle/Resources/views/content/modal_location_trash_with_single_asset.html.twig @@ -0,0 +1,52 @@ +{% form_theme form '@ezdesign/form_fields.html.twig' %} + + + +{% block javascripts %} + {% javascripts + '@EzPlatformAdminUiBundle/Resources/public/js/scripts/button.state.radio.toggle.js' + %} + + {% endjavascripts %} +{% endblock %} diff --git a/src/lib/Form/Data/Location/LocationTrashWithAssetData.php b/src/lib/Form/Data/Location/LocationTrashWithAssetData.php new file mode 100644 index 0000000000..e22cd7c1b7 --- /dev/null +++ b/src/lib/Form/Data/Location/LocationTrashWithAssetData.php @@ -0,0 +1,72 @@ +location = $location; + $this->trashAssets = $trashAssets; + } + + /** + * @return Location|null + */ + public function getLocation(): ?Location + { + return $this->location; + } + + /** + * @param Location|null $location + */ + public function setLocation(?Location $location): void + { + $this->location = $location; + } + + /** + * @return string|null + */ + public function getTrashAssets(): ?string + { + return $this->trashAssets; + } + + /** + * @param string|null $trashAssets + */ + public function setTrashAssets(?string $trashAssets): void + { + $this->trashAssets = $trashAssets; + } +} diff --git a/src/lib/Form/Factory/FormFactory.php b/src/lib/Form/Factory/FormFactory.php index def1dd3144..6c14544590 100644 --- a/src/lib/Form/Factory/FormFactory.php +++ b/src/lib/Form/Factory/FormFactory.php @@ -8,6 +8,7 @@ namespace EzSystems\EzPlatformAdminUi\Form\Factory; +use eZ\Publish\API\Repository\ContentTypeService; use EzSystems\EzPlatformAdminUi\Form\Data\Bookmark\BookmarkRemoveData; use EzSystems\EzPlatformAdminUi\Form\Data\Content\CustomUrl\CustomUrlAddData; use EzSystems\EzPlatformAdminUi\Form\Data\Content\CustomUrl\CustomUrlRemoveData; @@ -31,6 +32,7 @@ use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationCopySubtreeData; use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationMoveData; use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationSwapData; +use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationTrashWithAssetData; use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationUpdateVisibilityData; use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationTrashData; use EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationUpdateData; @@ -94,6 +96,7 @@ use EzSystems\EzPlatformAdminUi\Form\Type\Location\LocationCopyType; use EzSystems\EzPlatformAdminUi\Form\Type\Location\LocationMoveType; use EzSystems\EzPlatformAdminUi\Form\Type\Location\LocationSwapType; +use EzSystems\EzPlatformAdminUi\Form\Type\Location\LocationTrashWithAssetType; use EzSystems\EzPlatformAdminUi\Form\Type\Location\LocationUpdateVisibilityType; use EzSystems\EzPlatformAdminUi\Form\Type\Location\LocationTrashType; use EzSystems\EzPlatformAdminUi\Form\Type\Location\LocationUpdateType; @@ -144,23 +147,38 @@ use Symfony\Component\Form\Util\StringUtil; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Translation\TranslatorInterface; class FormFactory { - /** @var FormFactoryInterface */ + /** @var \Symfony\Component\Form\FormFactoryInterface */ protected $formFactory; - /** @var UrlGeneratorInterface */ + /** @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface */ protected $urlGenerator; + /** @var \Symfony\Component\Translation\TranslatorInterface */ + private $translator; + + /** @var \eZ\Publish\API\Repository\ContentTypeService */ + private $contentTypeService; + /** - * @param FormFactoryInterface $formFactory - * @param UrlGeneratorInterface $urlGenerator + * @param \Symfony\Component\Form\FormFactoryInterface $formFactory + * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $urlGenerator + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @param \eZ\Publish\API\Repository\ContentTypeService $contentTypeService */ - public function __construct(FormFactoryInterface $formFactory, UrlGeneratorInterface $urlGenerator) - { + public function __construct( + FormFactoryInterface $formFactory, + UrlGeneratorInterface $urlGenerator, + TranslatorInterface $translator, + ContentTypeService $contentTypeService + ) { $this->formFactory = $formFactory; $this->urlGenerator = $urlGenerator; + $this->translator = $translator; + $this->contentTypeService = $contentTypeService; } /** @@ -1258,4 +1276,21 @@ public function editUser( return $this->formFactory->createNamed($name, UserEditType::class, $data, $options); } + + /** + * @param \EzSystems\EzPlatformAdminUi\Form\Data\Location\LocationTrashWithAssetData|null $data + * @param string|null $name + * + * @return \Symfony\Component\Form\FormInterface + * + * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException + */ + public function trashLocationWithAsset( + LocationTrashWithAssetData $data = null, + ?string $name = null + ): FormInterface { + $name = $name ?: StringUtil::fqcnToBlockPrefix(LocationTrashWithAssetType::class); + + return $this->formFactory->createNamed($name, LocationTrashWithAssetType::class, $data); + } } diff --git a/src/lib/Form/Type/Location/LocationTrashWithAssetType.php b/src/lib/Form/Type/Location/LocationTrashWithAssetType.php new file mode 100644 index 0000000000..0e9c3fd6be --- /dev/null +++ b/src/lib/Form/Type/Location/LocationTrashWithAssetType.php @@ -0,0 +1,135 @@ +translator = $translator; + $this->contentTypeService = $contentTypeService; + } + + /** + * @param \Symfony\Component\Form\FormBuilderInterface $builder + * @param array $options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add( + 'location', + LocationType::class, + ['label' => false] + ) + ->add( + 'trashAssets', + ChoiceType::class, + [ + 'expanded' => true, + 'multiple' => false, + ] + ) + ->add( + 'trash', + SubmitType::class, + ['label' => /** @Desc("Send to Trash") */ + 'location_trash_form.trash', ] + ); + + $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { + $this->addTrashAssetField( + $event->getForm(), + $event->getData()->getLocation() + ); + }); + + $builder->get('location')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { + $this->addTrashAssetField( + $event->getForm()->getParent(), + $event->getForm()->getData() + ); + }); + } + + /** + * {@inheritdoc} + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => LocationTrashWithAssetData::class, + 'translation_domain' => 'forms', + ]); + } + + /** + * @param \Symfony\Component\Form\FormInterface $form + * @param \eZ\Publish\API\Repository\Values\Content\Location|null $location + * + * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException + */ + private function addTrashAssetField(FormInterface $form, Location $location = null): void + { + if ($location === null) { + return; + } + + $contentType = $this->contentTypeService->loadContentType( + $location->getContentInfo()->contentTypeId + ); + + $translatorParameters = [ + '%content_name%' => $location->getContent()->getName(), + '%content_type%' => $contentType->getName( + $location->getContentInfo()->mainLanguageCode + ), + ]; + + $form->add('trashAssets', ChoiceType::class, [ + 'expanded' => true, + 'multiple' => false, + 'choices' => [ + /** @Desc("Delete only %content_name% (%content_type%)") */ + $this->translator->trans('location_trash_form.default_trash', $translatorParameters, 'forms') => LocationTrashWithAssetType::RADIO_SELECT_DEFAULT_TRASH, + /** @Desc("Delete %content_name% (%content_type%) and its related image assets") */ + $this->translator->trans('location_trash_form.trash_with_asset', $translatorParameters, 'forms') => LocationTrashWithAssetType::RADIO_SELECT_TRASH_WITH_ASSETS, + ], + ]); + } +} diff --git a/src/lib/Menu/ContentRightSidebarBuilder.php b/src/lib/Menu/ContentRightSidebarBuilder.php index 729ba56e72..d14433b42c 100644 --- a/src/lib/Menu/ContentRightSidebarBuilder.php +++ b/src/lib/Menu/ContentRightSidebarBuilder.php @@ -8,6 +8,7 @@ namespace EzSystems\EzPlatformAdminUi\Menu; +use eZ\Publish\API\Repository\ContentService; use eZ\Publish\API\Repository\ContentTypeService; use eZ\Publish\API\Repository\PermissionResolver; use eZ\Publish\API\Repository\SearchService; @@ -16,6 +17,7 @@ use eZ\Publish\API\Repository\Values\ContentType\ContentType; use eZ\Publish\Core\MVC\ConfigResolverInterface; use EzSystems\EzPlatformAdminUi\Menu\Event\ConfigureMenuEvent; +use EzSystems\EzPlatformAdminUi\Specification\Content\ContentHaveAssetRelation; use EzSystems\EzPlatformAdminUi\Specification\ContentIsUser; use EzSystems\EzPlatformAdminUi\Specification\Location\IsRoot; use EzSystems\EzPlatformAdminUiBundle\Templating\Twig\UniversalDiscoveryExtension; @@ -60,6 +62,9 @@ class ContentRightSidebarBuilder extends AbstractBuilder implements TranslationC /** @var \EzSystems\EzPlatformAdminUiBundle\Templating\Twig\UniversalDiscoveryExtension */ private $udwExtension; + /** @var \eZ\Publish\API\Repository\ContentService */ + private $contentService; + /** * @param \EzSystems\EzPlatformAdminUi\Menu\MenuItemFactory $factory * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher @@ -69,6 +74,7 @@ class ContentRightSidebarBuilder extends AbstractBuilder implements TranslationC * @param \eZ\Publish\API\Repository\ContentTypeService $contentTypeService * @param \eZ\Publish\API\Repository\SearchService $searchService * @param \EzSystems\EzPlatformAdminUiBundle\Templating\Twig\UniversalDiscoveryExtension $udwExtension + * @param \eZ\Publish\API\Repository\ContentService $contentService */ public function __construct( MenuItemFactory $factory, @@ -78,7 +84,8 @@ public function __construct( UserService $userService, ContentTypeService $contentTypeService, SearchService $searchService, - UniversalDiscoveryExtension $udwExtension + UniversalDiscoveryExtension $udwExtension, + ContentService $contentService ) { parent::__construct($factory, $eventDispatcher); @@ -88,6 +95,7 @@ public function __construct( $this->contentTypeService = $contentTypeService; $this->searchService = $searchService; $this->udwExtension = $udwExtension; + $this->contentService = $contentService; } /** @@ -140,6 +148,14 @@ public function createStructure(array $options): ItemInterface 'data-actions' => 'create', 'data-focus-element' => '.ez-instant-filter__input', ]; + $editAttributes = [ + 'class' => 'ez-btn--extra-actions ez-btn--edit', + 'data-actions' => 'edit', + ]; + $sendToTrashAttributes = [ + 'data-toggle' => 'modal', + 'data-target' => '#trash-location-modal', + ]; $copySubtreeAttributes = [ 'class' => 'ez-btn--udw-copy-subtree', 'data-root-location' => $this->configResolver->getParameter( @@ -155,6 +171,10 @@ public function createStructure(array $options): ItemInterface $this->searchService ))->and((new IsRoot())->not())->isSatisfiedBy($location); + if ((new ContentHaveAssetRelation($this->contentService))->isSatisfiedBy($content)) { + $sendToTrashAttributes['data-target'] = '#trash-with-asset-modal'; + } + $contentIsUser = (new ContentIsUser($this->userService))->isSatisfiedBy($content); $menu->setChildren([ @@ -236,10 +256,7 @@ public function createStructure(array $options): ItemInterface self::ITEM__SEND_TO_TRASH, [ 'extras' => ['icon' => 'trash-send'], - 'attributes' => [ - 'data-toggle' => 'modal', - 'data-target' => '#trash-location-modal', - ], + 'attributes' => $sendToTrashAttributes, ] ) ); diff --git a/src/lib/Specification/Content/ContentHaveAssetRelation.php b/src/lib/Specification/Content/ContentHaveAssetRelation.php new file mode 100644 index 0000000000..2a04afd57a --- /dev/null +++ b/src/lib/Specification/Content/ContentHaveAssetRelation.php @@ -0,0 +1,54 @@ +contentService = $contentService; + } + + /** + * @param $item + * + * @return bool + * + * @throws \EzSystems\EzPlatformAdminUi\Exception\InvalidArgumentException + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + */ + public function isSatisfiedBy($item): bool + { + if (!$item instanceof Content) { + throw new InvalidArgumentException($item, sprintf('Must be instance of %s', Content::class)); + } + + $relations = $this->contentService->loadRelations($item->versionInfo); + + foreach ($relations as $relation) { + if (Relation::ASSET === $relation->type) { + return true; + } + } + + return false; + } +} diff --git a/src/lib/Specification/Content/ContentHaveUniqueRelation.php b/src/lib/Specification/Content/ContentHaveUniqueRelation.php new file mode 100644 index 0000000000..5011a025f0 --- /dev/null +++ b/src/lib/Specification/Content/ContentHaveUniqueRelation.php @@ -0,0 +1,58 @@ +contentService = $contentService; + } + + /** + * @param $item + * + * @return bool + * + * @throws \EzSystems\EzPlatformAdminUi\Exception\InvalidArgumentException + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + */ + public function isSatisfiedBy($item): bool + { + if (!$item instanceof Content) { + throw new InvalidArgumentException($item, sprintf('Must be instance of %s', Content::class)); + } + + $relations = $this->contentService->loadRelations($item->versionInfo); + + foreach ($relations as $relation) { + if (Relation::ASSET === $relation->type) { + $relationsFromAssetSide = $this->contentService->loadReverseRelations($relation->destinationContentInfo); + + if (count($relationsFromAssetSide) > 1) { + return false; + } + } + } + + return true; + } +} diff --git a/src/lib/Validator/Constraints/LocationHaveUniqueAssetRelation.php b/src/lib/Validator/Constraints/LocationHaveUniqueAssetRelation.php new file mode 100644 index 0000000000..a4eae8e9d7 --- /dev/null +++ b/src/lib/Validator/Constraints/LocationHaveUniqueAssetRelation.php @@ -0,0 +1,29 @@ +setDesc('Selected Location have assets that can\'t be removed.'), + ]; + } +} diff --git a/src/lib/Validator/Constraints/LocationHaveUniqueAssetRelationValidator.php b/src/lib/Validator/Constraints/LocationHaveUniqueAssetRelationValidator.php new file mode 100644 index 0000000000..e7d581b507 --- /dev/null +++ b/src/lib/Validator/Constraints/LocationHaveUniqueAssetRelationValidator.php @@ -0,0 +1,56 @@ +contentService = $contentService; + } + + /** + * Checks if the passed value is valid. + * + * @param \eZ\Publish\API\Repository\Values\Content\Location $location The value that should be validated + * @param \Symfony\Component\Validator\Constraint $constraint The constraint for the validation + */ + public function validate($location, Constraint $constraint) + { + if (null === $location) { + $this->context->addViolation($constraint->message); + + return; + } + + $haveUniqueRelation = new ContentHaveUniqueRelation($this->contentService); + try { + if (!$haveUniqueRelation->isSatisfiedBy($location->getContent())) { + $this->context->addViolation($constraint->message); + } + } catch (InvalidArgumentException $e) { + $this->context->addViolation($e->getMessage()); + } catch (UnauthorizedException $e) { + $this->context->addViolation($e->getMessage()); + } + } +}