From 1c523071f56ae7cc49c01aa04d5060225ca62935 Mon Sep 17 00:00:00 2001 From: Mikolaj Adamczyk Date: Thu, 18 Apr 2019 18:56:39 +0200 Subject: [PATCH] EZP-30344: Improved handling Language Limitation (#934) * Improved handling Language Limitation * Disabled HTTP cache for ContentController::checkEditPermissionAction * Enabled right sidebar edit button for translated Content Editing should be possible if Content is already translated. * Added permission check for edit button in Sub-items Co-authored-by: Jakub --- src/bundle/Controller/ContentController.php | 71 +++++++++++++- .../Controller/ContentViewController.php | 26 ++++- .../config/bazinga_js_translation.yml | 1 + src/bundle/Resources/config/routing.yml | 8 ++ .../Resources/config/services/permissions.yml | 2 + .../public/js/scripts/admin.location.view.js | 43 +++++--- .../js/scripts/admin.version.edit.conflict.js | 42 ++++++-- .../public/js/scripts/button.content.edit.js | 38 ++++--- .../Resources/translations/content.en.xliff | 5 + .../views/admin/content_draft/list.html.twig | 2 + .../content/tab/versions/table.html.twig | 2 + .../views/dashboard/macros.html.twig | 11 +++ .../views/dashboard/tab/all_content.html.twig | 12 +-- .../views/dashboard/tab/all_media.html.twig | 12 +-- .../views/dashboard/tab/my_content.html.twig | 12 +-- .../views/dashboard/tab/my_drafts.html.twig | 4 +- .../views/dashboard/tab/my_media.html.twig | 12 +-- src/lib/Form/Factory/FormFactory.php | 11 ++- .../ContentEditTranslationChoiceLoader.php | 98 +++++++++++++++++++ .../Type/Content/Draft/ContentEditType.php | 20 +++- .../Translation/TranslationAddType.php | 73 ++++++++++---- .../Menu/ContentEditRightSidebarBuilder.php | 6 +- src/lib/Menu/ContentRightSidebarBuilder.php | 10 +- .../LookupLimitationsTransformer.php | 37 +++++++ src/lib/Tab/LocationView/TranslationsTab.php | 26 ++++- 25 files changed, 478 insertions(+), 106 deletions(-) create mode 100644 src/bundle/Resources/views/dashboard/macros.html.twig create mode 100644 src/lib/Form/Type/ChoiceList/Loader/ContentEditTranslationChoiceLoader.php create mode 100644 src/lib/Permission/LookupLimitationsTransformer.php diff --git a/src/bundle/Controller/ContentController.php b/src/bundle/Controller/ContentController.php index 92b4b7b421..8c68bd49f9 100644 --- a/src/bundle/Controller/ContentController.php +++ b/src/bundle/Controller/ContentController.php @@ -10,11 +10,14 @@ use eZ\Publish\API\Repository\Exceptions as ApiException; use eZ\Publish\API\Repository\Exceptions\UnauthorizedException; use eZ\Publish\API\Repository\LocationService; +use eZ\Publish\API\Repository\PermissionResolver; use eZ\Publish\API\Repository\UserService; use eZ\Publish\API\Repository\Values\Content\Content; use eZ\Publish\API\Repository\Values\Content\Language; use eZ\Publish\API\Repository\Values\Content\Location; +use eZ\Publish\API\Repository\Values\User\Limitation; use eZ\Publish\Core\Base\Exceptions\BadStateException; +use eZ\Publish\SPI\Limitation\Target; use EzSystems\EzPlatformAdminUi\Exception\InvalidArgumentException as AdminInvalidArgumentException; use EzSystems\EzPlatformAdminUi\Form\Data\Content\ContentVisibilityUpdateData; use EzSystems\EzPlatformAdminUi\Form\Data\Content\Draft\ContentCreateData; @@ -28,9 +31,11 @@ use EzSystems\EzPlatformAdminUi\Form\Type\Content\ContentVisibilityUpdateType; use EzSystems\EzPlatformAdminUi\Form\Type\Content\Translation\MainTranslationUpdateType; use EzSystems\EzPlatformAdminUi\Notification\NotificationHandlerInterface; +use EzSystems\EzPlatformAdminUi\Permission\LookupLimitationsTransformer; use EzSystems\EzPlatformAdminUi\Siteaccess\SiteaccessResolverInterface; use EzSystems\EzPlatformAdminUi\Specification\ContentIsUser; use EzSystems\EzPlatformAdminUi\Specification\ContentType\ContentTypeIsUser; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -69,6 +74,12 @@ class ContentController extends Controller /** @var \eZ\Publish\API\Repository\UserService */ private $userService; + /** @var \eZ\Publish\API\Repository\PermissionResolver */ + private $permissionResolver; + + /** @var \EzSystems\EzPlatformAdminUi\Permission\LookupLimitationsTransformer */ + private $lookupLimitationsTransformer; + /** @var array */ private $userContentTypeIdentifier; @@ -82,6 +93,8 @@ class ContentController extends Controller * @param \EzSystems\EzPlatformAdminUi\Siteaccess\SiteaccessResolverInterface $siteaccessResolver * @param \eZ\Publish\API\Repository\LocationService $locationService * @param \eZ\Publish\API\Repository\UserService $userService + * @param \eZ\Publish\API\Repository\PermissionResolver $permissionResolver + * @param \EzSystems\EzPlatformAdminUi\Permission\LookupLimitationsTransformer $lookupLimitationsTransformer * @param array $userContentTypeIdentifier */ public function __construct( @@ -94,6 +107,8 @@ public function __construct( SiteaccessResolverInterface $siteaccessResolver, LocationService $locationService, UserService $userService, + PermissionResolver $permissionResolver, + LookupLimitationsTransformer $lookupLimitationsTransformer, array $userContentTypeIdentifier ) { $this->notificationHandler = $notificationHandler; @@ -106,6 +121,8 @@ public function __construct( $this->locationService = $locationService; $this->userService = $userService; $this->userContentTypeIdentifier = $userContentTypeIdentifier; + $this->permissionResolver = $permissionResolver; + $this->lookupLimitationsTransformer = $lookupLimitationsTransformer; } /** @@ -340,7 +357,7 @@ public function previewAction( } /** - * @param Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Request $request * * @return \Symfony\Component\HttpFoundation\Response */ @@ -388,7 +405,7 @@ public function updateMainTranslationAction(Request $request): Response } /** - * @param Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Request $request * * @return \Symfony\Component\HttpFoundation\Response */ @@ -459,6 +476,56 @@ public function updateVisibilityAction(Request $request): Response return $result instanceof Response ? $result : $this->redirectToRoute('ezplatform.dashboard'); } + /** + * @param \eZ\Publish\API\Repository\Values\Content\Content $content + * @param string|null $languageCode + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + * + * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException + * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException + */ + public function checkEditPermissionAction(Content $content, ?string $languageCode): JsonResponse + { + $targets = []; + + if (null !== $languageCode) { + $targets[] = (new Target\Builder\VersionBuilder())->translateToAnyLanguageOf([$languageCode])->build(); + } + + $canEdit = $this->permissionResolver->canUser( + 'content', + 'edit', + $content, + $targets + ); + + $lookupLimitations = $this->permissionResolver->lookupLimitations( + 'content', + 'edit', + $content, + $targets, + [Limitation::LANGUAGE] + ); + + $editLanguagesLimitationValues = $this->lookupLimitationsTransformer->getFlattenedLimitationsValues($lookupLimitations); + + $response = new JsonResponse(); + $response->setData([ + 'canEdit' => $canEdit, + 'editLanguagesLimitationValues' => $canEdit ? $editLanguagesLimitationValues : [], + ]); + + // Disable HTTP cache + $response->setPrivate(); + $response->setMaxAge(0); + $response->setSharedMaxAge(0); + $response->headers->addCacheControlDirective('must-revalidate', true); + $response->headers->addCacheControlDirective('no-store', true); + + return $response; + } + /** * @param \eZ\Publish\API\Repository\Values\Content\Content $contentDraft * @param \eZ\Publish\API\Repository\Values\Content\Language $language diff --git a/src/bundle/Controller/ContentViewController.php b/src/bundle/Controller/ContentViewController.php index bf02f4c5a8..411474aab9 100644 --- a/src/bundle/Controller/ContentViewController.php +++ b/src/bundle/Controller/ContentViewController.php @@ -35,7 +35,9 @@ use EzSystems\EzPlatformAdminUi\Form\Data\User\UserDeleteData; use EzSystems\EzPlatformAdminUi\Form\Data\User\UserEditData; use EzSystems\EzPlatformAdminUi\Form\Factory\FormFactory; +use EzSystems\EzPlatformAdminUi\Form\Type\ChoiceList\Loader\ContentEditTranslationChoiceLoader; use EzSystems\EzPlatformAdminUi\Form\Type\Content\ContentVisibilityUpdateType; +use EzSystems\EzPlatformAdminUi\Permission\LookupLimitationsTransformer; use EzSystems\EzPlatformAdminUi\Specification\Content\ContentHaveAssetRelation; use EzSystems\EzPlatformAdminUi\Specification\Content\ContentHaveUniqueRelation; use EzSystems\EzPlatformAdminUi\Specification\ContentIsUser; @@ -91,6 +93,9 @@ class ContentViewController extends Controller /** @var \eZ\Publish\API\Repository\PermissionResolver */ private $permissionResolver; + /** @var \EzSystems\EzPlatformAdminUi\Permission\LookupLimitationsTransformer */ + private $lookupLimitationsTransformer; + /** * @param \eZ\Publish\API\Repository\ContentTypeService $contentTypeService * @param \eZ\Publish\API\Repository\LanguageService $languageService @@ -106,6 +111,7 @@ class ContentViewController extends Controller * @param \eZ\Publish\Core\MVC\ConfigResolverInterface $configResolver * @param \eZ\Publish\API\Repository\Repository $repository * @param \eZ\Publish\API\Repository\PermissionResolver $permissionResolver + * @param \EzSystems\EzPlatformAdminUi\Permission\LookupLimitationsTransformer $lookupLimitationsTransformer */ public function __construct( ContentTypeService $contentTypeService, @@ -121,7 +127,8 @@ public function __construct( UserLanguagePreferenceProviderInterface $userLanguagePreferenceProvider, ConfigResolverInterface $configResolver, Repository $repository, - PermissionResolver $permissionResolver + PermissionResolver $permissionResolver, + LookupLimitationsTransformer $lookupLimitationsTransformer ) { $this->contentTypeService = $contentTypeService; $this->languageService = $languageService; @@ -135,8 +142,9 @@ public function __construct( $this->locationService = $locationService; $this->userLanguagePreferenceProvider = $userLanguagePreferenceProvider; $this->configResolver = $configResolver; - $this->repository = $repository; $this->permissionResolver = $permissionResolver; + $this->lookupLimitationsTransformer = $lookupLimitationsTransformer; + $this->repository = $repository; } /** @@ -347,8 +355,20 @@ private function createContentEditForm( ?Language $language = null, ?Location $location = null ): FormInterface { + $languageCodes = $versionInfo->languageCodes ?? []; + return $this->formFactory->contentEdit( - new ContentEditData($contentInfo, $versionInfo, $language, $location) + new ContentEditData($contentInfo, $versionInfo, $language, $location), + null, + [ + 'choice_loader' => new ContentEditTranslationChoiceLoader( + $this->languageService, + $this->permissionResolver, + $contentInfo, + $this->lookupLimitationsTransformer, + $languageCodes + ), + ] ); } diff --git a/src/bundle/Resources/config/bazinga_js_translation.yml b/src/bundle/Resources/config/bazinga_js_translation.yml index a6b13da545..b048a6ee48 100644 --- a/src/bundle/Resources/config/bazinga_js_translation.yml +++ b/src/bundle/Resources/config/bazinga_js_translation.yml @@ -4,3 +4,4 @@ active_domains: - 'fieldtypes_edit' - 'notifications' - 'search' + - 'content' diff --git a/src/bundle/Resources/config/routing.yml b/src/bundle/Resources/config/routing.yml index eda4983859..ea9aeac73a 100644 --- a/src/bundle/Resources/config/routing.yml +++ b/src/bundle/Resources/config/routing.yml @@ -561,6 +561,14 @@ ezplatform.content.translate: _controller: 'EzPlatformAdminUiBundle:ContentEdit:translate' fromLanguageCode: ~ +ezplatform.content.check_edit_permission: + path: /content/{contentId}/check-edit-permission/{languageCode} + options: + expose: true + defaults: + _controller: 'EzPlatformAdminUiBundle:Content:checkEditPermission' + languageCode: ~ + # # Search # diff --git a/src/bundle/Resources/config/services/permissions.yml b/src/bundle/Resources/config/services/permissions.yml index 8f18779f19..922cf18117 100644 --- a/src/bundle/Resources/config/services/permissions.yml +++ b/src/bundle/Resources/config/services/permissions.yml @@ -8,3 +8,5 @@ services: EzSystems\EzPlatformAdminUi\Permission\PermissionCheckerInterface: alias: EzSystems\EzPlatformAdminUi\Permission\PermissionChecker + + EzSystems\EzPlatformAdminUi\Permission\LookupLimitationsTransformer: ~ diff --git a/src/bundle/Resources/public/js/scripts/admin.location.view.js b/src/bundle/Resources/public/js/scripts/admin.location.view.js index 65cefa678d..eae15d9521 100644 --- a/src/bundle/Resources/public/js/scripts/admin.location.view.js +++ b/src/bundle/Resources/public/js/scripts/admin.location.view.js @@ -1,4 +1,4 @@ -(function(global, doc, $, React, ReactDOM, eZ, Routing) { +(function(global, doc, $, React, ReactDOM, eZ, Routing, Translator) { const SELECTOR_MODAL_BULK_ACTION_FAIL = '#bulk-action-failed-modal'; const listContainers = doc.querySelectorAll('.ez-sil'); const mfuContainer = doc.querySelector('#ez-mfu'); @@ -49,17 +49,36 @@ ); $('#version-draft-conflict-modal').modal('show'); }; - fetch(checkVersionDraftLink, { - credentials: 'same-origin', - }).then((response) => { - // Status 409 means that a draft conflict has occurred and the modal must be displayed. - // Otherwise we can go to Content Item edit page. - if (response.status === 409) { - response.text().then(showModal); - } else if (response.status === 200) { - submitVersionEditForm(); - } + const checkEditPermissionLink = Routing.generate('ezplatform.content.check_edit_permission', { + contentId, + languageCode: content.mainLanguageCode, }); + const errorMessage = Translator.trans( + /*@Desc("You don't have permission to edit the content")*/ 'content.edit.permission.error', + {}, + 'content' + ); + const handleCanEditCheck = (response) => { + if (response.canEdit) { + return fetch(checkVersionDraftLink, { mode: 'same-origin', credentials: 'same-origin' }); + } + + throw new Error(errorMessage); + }; + + fetch(checkEditPermissionLink, { mode: 'same-origin', credentials: 'same-origin' }) + .then(eZ.helpers.request.getJsonFromResponse) + .then(handleCanEditCheck) + .then((response) => { + // Status 409 means that a draft conflict has occurred and the modal must be displayed. + // Otherwise we can go to Content Item edit page. + if (response.status === 409) { + response.text().then(showModal); + } else if (response.status === 200) { + submitVersionEditForm(); + } + }) + .catch(eZ.helpers.notification.showErrorNotification); }; const generateLink = (locationId) => Routing.generate('_ezpublishLocation', { locationId }); const setModalTableTitle = (title) => { @@ -149,4 +168,4 @@ container ); }); -})(window, window.document, window.jQuery, window.React, window.ReactDOM, window.eZ, window.Routing); +})(window, window.document, window.jQuery, window.React, window.ReactDOM, window.eZ, window.Routing, window.Translator); diff --git a/src/bundle/Resources/public/js/scripts/admin.version.edit.conflict.js b/src/bundle/Resources/public/js/scripts/admin.version.edit.conflict.js index eaa85dd06f..5acf8a8480 100644 --- a/src/bundle/Resources/public/js/scripts/admin.version.edit.conflict.js +++ b/src/bundle/Resources/public/js/scripts/admin.version.edit.conflict.js @@ -1,22 +1,46 @@ -(function (global, doc, $) { +(function (global, doc, $, eZ, Translator) { const editVersion = (event) => { + const showErrorNotification = eZ.helpers.notification.showErrorNotification; const contentDraftEditUrl = event.currentTarget.dataset.contentDraftEditUrl; const versionHasConflictUrl = event.currentTarget.dataset.versionHasConflictUrl; + const contentId = event.currentTarget.dataset.contentId; + const languageCode = event.currentTarget.dataset.languageCode; + const checkEditPermissionLink = global.Routing.generate('ezplatform.content.check_edit_permission', { contentId, languageCode }); + const errorMessage = Translator.trans( + /*@Desc("You don't have permission to edit the content")*/ 'content.edit.permission.error', + {}, + 'content' + ); + const handleCanEditCheck = (response) => { + if (response.canEdit) { + return fetch(versionHasConflictUrl, { mode: 'same-origin', credentials: 'same-origin' }); + } - event.preventDefault(); - - fetch(versionHasConflictUrl, { - credentials: 'same-origin' - }).then(function (response) { + throw new Error(errorMessage); + }; + const handleVersionDraftConflict = (response) => { + // Status 409 means that a draft conflict has occurred and the modal must be displayed. + // Otherwise we can go to Content Item edit page. if (response.status === 409) { doc.querySelector('#edit-conflicted-draft').href = contentDraftEditUrl; $('#version-conflict-modal').modal('show'); } + if (response.status === 403) { + response.text().then(showErrorNotification); + } if (response.status === 200) { global.location.href = contentDraftEditUrl; } - }) + }; + + event.preventDefault(); + + fetch(checkEditPermissionLink, { mode: 'same-origin', credentials: 'same-origin' }) + .then(eZ.helpers.request.getJsonFromResponse) + .then(handleCanEditCheck) + .then(handleVersionDraftConflict) + .catch(showErrorNotification); }; - [...doc.querySelectorAll('.ez-btn--content-draft-edit')].forEach(link => link.addEventListener('click', editVersion, false)); -})(window, document, window.jQuery); + doc.querySelectorAll('.ez-btn--content-draft-edit').forEach((button) => button.addEventListener('click', editVersion, false)); +})(window, document, window.jQuery, window.eZ, window.Translator); diff --git a/src/bundle/Resources/public/js/scripts/button.content.edit.js b/src/bundle/Resources/public/js/scripts/button.content.edit.js index fc1b9100c5..7b75d46193 100644 --- a/src/bundle/Resources/public/js/scripts/button.content.edit.js +++ b/src/bundle/Resources/public/js/scripts/button.content.edit.js @@ -7,15 +7,19 @@ const contentId = event.currentTarget.dataset.contentId; const versionNo = event.currentTarget.dataset.versionNo; const languageCode = event.currentTarget.dataset.languageCode; - const contentInfoInput = versionEditForm.querySelector('input[name="' + versionEditFormName + '[content_info]"]'); + const contentInfoInput = versionEditForm.querySelector(`input[name="${versionEditFormName}[content_info]"]`); const versionInfoContentInfoInput = versionEditForm.querySelector( - 'input[name="' + versionEditFormName + '[version_info][content_info]"]' + `input[name="${versionEditFormName}[version_info][content_info]"]` ); - const versionInfoVersionNoInput = versionEditForm.querySelector( - 'input[name="' + versionEditFormName + '[version_info][version_no]"]' - ); - const languageInput = versionEditForm.querySelector('#' + versionEditFormName + '_language_' + languageCode); + const versionInfoVersionNoInput = versionEditForm.querySelector(`input[name="${versionEditFormName}[version_info][version_no]"]`); + const languageInput = versionEditForm.querySelector(`#${versionEditFormName}_language_${languageCode}`); const checkVersionDraftLink = global.Routing.generate('ezplatform.version_draft.has_no_conflict', { contentId, languageCode }); + const checkEditPermissionLink = global.Routing.generate('ezplatform.content.check_edit_permission', { contentId, languageCode }); + const errorMessage = Translator.trans( + /*@Desc("You don't have permission to edit the content")*/ 'content.edit.permission.error', + {}, + 'content' + ); const submitVersionEditForm = () => { contentInfoInput.value = contentId; versionInfoContentInfoInput.value = contentId; @@ -40,12 +44,14 @@ ); $('#version-draft-conflict-modal').modal('show'); }; + const handleCanEditCheck = (response) => { + if (response.canEdit) { + return fetch(checkVersionDraftLink, { mode: 'same-origin', credentials: 'same-origin' }); + } - event.preventDefault(); - - fetch(checkVersionDraftLink, { - credentials: 'same-origin', - }).then(function(response) { + throw new Error(errorMessage); + }; + const handleDraftConflict = (response) => { // Status 409 means that a draft conflict has occurred and the modal must be displayed. // Otherwise we can go to Content Item edit page. if (response.status === 409) { @@ -55,7 +61,15 @@ } else if (response.status === 200) { submitVersionEditForm(); } - }); + }; + + event.preventDefault(); + + fetch(checkEditPermissionLink, { mode: 'same-origin', credentials: 'same-origin' }) + .then(eZ.helpers.request.getJsonFromResponse) + .then(handleCanEditCheck) + .then(handleDraftConflict) + .catch(showErrorNotification); }; [...doc.querySelectorAll('.ez-btn--content-edit')].forEach((button) => button.addEventListener('click', editVersion, false)); diff --git a/src/bundle/Resources/translations/content.en.xliff b/src/bundle/Resources/translations/content.en.xliff index 96d5138b58..480208678a 100644 --- a/src/bundle/Resources/translations/content.en.xliff +++ b/src/bundle/Resources/translations/content.en.xliff @@ -16,6 +16,11 @@ Cannot check if the draft has no conflict with other drafts. %error%. key: content.draft.conflict.error + + You don't have permission to edit the content + You don't have permission to edit the content + key: content.edit.permission.error + Content '%name%' was already hidden. Content '%name%' has been hidden. diff --git a/src/bundle/Resources/views/admin/content_draft/list.html.twig b/src/bundle/Resources/views/admin/content_draft/list.html.twig index a7ddb42ad4..ad010f966a 100644 --- a/src/bundle/Resources/views/admin/content_draft/list.html.twig +++ b/src/bundle/Resources/views/admin/content_draft/list.html.twig @@ -80,6 +80,8 @@