diff --git a/modules/product/commerce_product.module b/modules/product/commerce_product.module index 914e638b15410b04c669678cda09643430d07d24..083a0df60e354609beba57a873b4a814cff20684 100644 --- a/modules/product/commerce_product.module +++ b/modules/product/commerce_product.module @@ -407,3 +407,27 @@ function commerce_product_field_group_content_element_keys_alter(&$keys) { $keys['commerce_product'] = 'product'; $keys['commerce_product_variation'] = 'product_variation'; } + +/** + * Implements hook_entity_operation_alter(). + */ +function commerce_product_entity_operation_alter(array &$operations, EntityInterface $entity): void { + // For the 'commerce_product_attribute' entity type when the 'translate' + // operation does not exist we need to check if the user has access to manage + // translations. + if ($entity->getEntityTypeId() !== 'commerce_product_attribute' + || !$entity->hasLinkTemplate('config-translation-overview') + || isset($operations['translate']) + ) { + return; + } + + $url = $entity->toUrl('config-translation-overview'); + if ($url->access()) { + $operations['translate'] = [ + 'title' => t('Translate'), + 'weight' => 50, + 'url' => $url, + ]; + } +} diff --git a/modules/product/commerce_product.permissions.yml b/modules/product/commerce_product.permissions.yml index 2f56ba5e89a2275852e6ca4da102def5c03f177f..a147db9a785aa7d794bb74d0d796697cdc68f022 100644 --- a/modules/product/commerce_product.permissions.yml +++ b/modules/product/commerce_product.permissions.yml @@ -2,3 +2,8 @@ title: 'Administer product types' description: 'Maintain the types of products available and the fields that are associated with those types.' restrict access: true + +'translate commerce_product_attribute': + title: 'Translate product attribute' + description: 'Translate any product attribute.' + restrict access: true diff --git a/modules/product/commerce_product.services.yml b/modules/product/commerce_product.services.yml index ef2f0c9053425785715da0ee8f09c649e4bce6f1..ecdabc732ddb759cf491e830c8f3028047dce5db 100644 --- a/modules/product/commerce_product.services.yml +++ b/modules/product/commerce_product.services.yml @@ -11,6 +11,7 @@ services: tags: - { name: access_check, applies_to: _product_variation_create_access } + commerce_product.attribute_field_manager: class: Drupal\commerce_product\ProductAttributeFieldManager arguments: ['@entity_field.manager', '@entity_type.bundle.info', '@entity_type.manager', '@cache.data'] diff --git a/modules/product/src/Access/ProductAttributeTranslationAccessCheck.php b/modules/product/src/Access/ProductAttributeTranslationAccessCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..779feaab7d93772af4be0554918651f88a337baa --- /dev/null +++ b/modules/product/src/Access/ProductAttributeTranslationAccessCheck.php @@ -0,0 +1,31 @@ +<?php + +namespace Drupal\commerce_product\Access; + +use Drupal\config_translation\Access\ConfigTranslationOverviewAccess; +use Drupal\config_translation\ConfigMapperInterface; +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Session\AccountInterface; + +/** + * Checks access for displaying the product attribute translation overview. + */ +class ProductAttributeTranslationAccessCheck extends ConfigTranslationOverviewAccess { + + /** + * {@inheritdoc} + */ + protected function doCheckAccess(AccountInterface $account, ConfigMapperInterface $mapper, $source_language = NULL) { + $permission_access = $account->hasPermission('translate commerce_product_attribute') || + $account->hasPermission('translate configuration'); + + $access = + $permission_access && + $mapper->hasSchema() && + $mapper->hasTranslatable() && + (!$source_language || !$source_language->isLocked()); + + return AccessResult::allowedIf($access)->cachePerPermissions(); + } + +} diff --git a/modules/product/src/Access/ProductAttributeTranslationFormAccessCheck.php b/modules/product/src/Access/ProductAttributeTranslationFormAccessCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..f29a449c2d39cbfa73d32437c093c0ee92a42e09 --- /dev/null +++ b/modules/product/src/Access/ProductAttributeTranslationFormAccessCheck.php @@ -0,0 +1,45 @@ +<?php + +namespace Drupal\commerce_product\Access; + +use Drupal\config_translation\Access\ConfigTranslationFormAccess; +use Drupal\config_translation\ConfigMapperInterface; +use Drupal\config_translation\ConfigMapperManagerInterface; +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Session\AccountInterface; + +/** + * Checks access for displaying the product attribute add, edit, delete forms. + */ +class ProductAttributeTranslationFormAccessCheck extends ConfigTranslationFormAccess { + + /** + * Constructs a new ProductAttributeTranslationFormAccessCheck object. + * + * @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager + * The mapper plugin discovery service. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager service. + * @param \Drupal\commerce_product\Access\ProductAttributeTranslationAccessCheck $translationAccessCheck + * The main access check service. + */ + public function __construct(ConfigMapperManagerInterface $config_mapper_manager, LanguageManagerInterface $language_manager, protected ProductAttributeTranslationAccessCheck $translationAccessCheck) { + parent::__construct($config_mapper_manager, $language_manager); + } + + /** + * {@inheritdoc} + */ + protected function doCheckAccess(AccountInterface $account, ConfigMapperInterface $mapper, $source_language = NULL, $target_language = NULL) { + $base_access_result = $this->translationAccessCheck->doCheckAccess($account, $mapper, $source_language); + + $access = + $target_language && + !$target_language->isLocked() && + (!$source_language || ($target_language->getId() !== $source_language->getId())); + + return $base_access_result->andIf(AccessResult::allowedIf($access)); + } + +} diff --git a/modules/product/src/CommerceProductServiceProvider.php b/modules/product/src/CommerceProductServiceProvider.php index 6f5619fd659c037fdf9fae5e743675a133494c80..ba09562e404a94db9b86ef47bf3bdb8cc3ebc9a7 100644 --- a/modules/product/src/CommerceProductServiceProvider.php +++ b/modules/product/src/CommerceProductServiceProvider.php @@ -2,6 +2,8 @@ namespace Drupal\commerce_product; +use Drupal\commerce_product\Access\ProductAttributeTranslationAccessCheck; +use Drupal\commerce_product\Access\ProductAttributeTranslationFormAccessCheck; use Drupal\commerce_product\EventSubscriber\VariationFieldComponentSubscriber; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderBase; @@ -38,6 +40,19 @@ class CommerceProductServiceProvider extends ServiceProviderBase { ->setClass(VariationFieldComponentSubscriber::class) ->addTag('event_subscriber'); } + + if (isset($modules['config_translation'])) { + $parent = $container->getDefinition('config_translation.access.overview'); + $container->register('access_check.product_attribute_translation', ProductAttributeTranslationAccessCheck::class) + ->setArguments($parent->getArguments()) + ->addTag('access_check', ['applies_to' => '_product_attribute_translation_access']); + + $parent = $container->getDefinition('config_translation.access.form'); + $container->register('access_check.product_attribute_translation.form', ProductAttributeTranslationFormAccessCheck::class) + ->setArguments($parent->getArguments()) + ->addArgument(new Reference('access_check.product_attribute_translation')) + ->addTag('access_check', ['applies_to' => '_product_attribute_translation_form_access']); + } } } diff --git a/modules/product/src/ConfigTranslation/ProductAttributeMapper.php b/modules/product/src/ConfigTranslation/ProductAttributeMapper.php index 04ba3b39d066c6a1178cbb028f9650109e0e3a87..88a5fc8a586cf3800807371d8fabcad65ad21d8a 100644 --- a/modules/product/src/ConfigTranslation/ProductAttributeMapper.php +++ b/modules/product/src/ConfigTranslation/ProductAttributeMapper.php @@ -3,6 +3,7 @@ namespace Drupal\commerce_product\ConfigTranslation; use Drupal\config_translation\ConfigEntityMapper; +use Symfony\Component\Routing\Route; /** * Provides a configuration mapper for product attributes. @@ -15,6 +16,7 @@ class ProductAttributeMapper extends ConfigEntityMapper { public function getAddRoute() { $route = parent::getAddRoute(); $route->setDefault('_form', '\Drupal\commerce_product\Form\ProductAttributeTranslationAddForm'); + $this->addTranslationFormAccessCheck($route); return $route; } @@ -24,7 +26,42 @@ class ProductAttributeMapper extends ConfigEntityMapper { public function getEditRoute() { $route = parent::getEditRoute(); $route->setDefault('_form', '\Drupal\commerce_product\Form\ProductAttributeTranslationEditForm'); + $this->addTranslationFormAccessCheck($route); return $route; } + /** + * {@inheritdoc} + */ + public function getDeleteRoute() { + $route = parent::getDeleteRoute(); + $this->addTranslationFormAccessCheck($route); + return $route; + } + + /** + * {@inheritdoc} + */ + public function getOverviewRoute() { + $route = parent::getOverviewRoute(); + $route_requirements = $route->getRequirements(); + unset($route_requirements['_config_translation_overview_access']); + $route_requirements['_product_attribute_translation_access'] = 'TRUE'; + $route->setRequirements($route_requirements); + return $route; + } + + /** + * Modifies route to use custom access check. + * + * @param \Symfony\Component\Routing\Route $route + * The route object. + */ + protected function addTranslationFormAccessCheck(Route &$route): void { + $route_requirements = $route->getRequirements(); + unset($route_requirements['_config_translation_form_access']); + $route_requirements['_product_attribute_translation_form_access'] = 'TRUE'; + $route->setRequirements($route_requirements); + } + }