diff --git a/commerce_shipping.module b/commerce_shipping.module index 42fc959..d04ecae 100644 --- a/commerce_shipping.module +++ b/commerce_shipping.module @@ -10,12 +10,16 @@ use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_order\Entity\OrderType; use Drupal\commerce_shipping\Entity\ShipmentType; use Drupal\entity\BundleFieldDefinition; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; /** @@ -343,3 +347,15 @@ function template_preprocess_commerce_shipment(array &$variables) { $variables['shipment'][$key] = $variables['elements'][$key]; } } + +/** + * Implements hook_entity_field_access(). + */ +function commerce_shipping_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) { + if (!\Drupal::moduleHandler()->moduleExists('jsonapi')) { + return AccessResult::neutral(); + } + /** @var \Drupal\commerce_shipping\FieldAccessInterface $field_access */ + $field_access = \Drupal::getContainer()->get('commerce_shipping.field_access'); + return $field_access->handle($operation, $field_definition, $account, $items); +} diff --git a/commerce_shipping.services.yml b/commerce_shipping.services.yml index 8d4cd7c..f259ec0 100644 --- a/commerce_shipping.services.yml +++ b/commerce_shipping.services.yml @@ -94,3 +94,7 @@ services: commerce_shipping.shipment_confirmation_mail: class: Drupal\commerce_shipping\Mail\ShipmentConfirmationMail arguments: ['@entity_type.manager', '@commerce.mail_handler'] + + commerce_shipping.field_access: + class: Drupal\commerce_shipping\FieldAccess + arguments: ['@current_route_match'] diff --git a/src/FieldAccess.php b/src/FieldAccess.php new file mode 100644 index 0000000..bf63973 --- /dev/null +++ b/src/FieldAccess.php @@ -0,0 +1,79 @@ +routeMatch = $route_match; + } + + /** + * {@inheritdoc} + */ + public function handle($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL): AccessResultInterface { + $route = $this->routeMatch->getRouteObject(); + // Only check access if this is running on JSON API routes. + if (!$route || !$route->hasRequirement(Routes::JSON_API_ROUTE_FLAG_KEY)) { + return AccessResult::neutral(); + } + $entity_type_id = $field_definition->getTargetEntityTypeId(); + // We currently only support shipments. + if ($entity_type_id !== 'commerce_shipment') { + return AccessResult::neutral(); + } + + if ($operation === 'edit') { + $disallowed = $this->getProtectedEditFieldNames($entity_type_id); + return AccessResult::forbiddenIf(in_array($field_definition->getName(), $disallowed, TRUE)); + } + + return AccessResult::neutral(); + } + + /** + * Gets protected fields that cannot be edited for an entity type. + * + * @param string $entity_type_id + * The entity type ID. + * + * @return array + * The array of field names. + */ + protected function getProtectedEditFieldNames(string $entity_type_id): array { + $field_names = [ + 'commerce_shipment' => [ + 'original_amount', + 'amount', + 'adjustments', + // When commerce_shipping is used in combination with commerce_api, + // the rate is applied using the order resources. + 'shipping_service', + 'shipping_method', + ], + ]; + return $field_names[$entity_type_id] ?? []; + } + +} diff --git a/src/FieldAccessInterface.php b/src/FieldAccessInterface.php new file mode 100644 index 0000000..8d68fbf --- /dev/null +++ b/src/FieldAccessInterface.php @@ -0,0 +1,36 @@ +addCacheableDependency($entity); } - if ($operation === 'view') { - $result = $order->access('view', $account, TRUE); - } - else { - $bundle = $entity->bundle(); - $result = AccessResult::allowedIfHasPermission($account, "manage $bundle commerce_shipment"); + $bundle = $entity->bundle(); + $result = AccessResult::allowedIfHasPermission($account, "manage $bundle commerce_shipment"); + if ($result->isNeutral() && ($operation === 'view' || $operation === 'update')) { + $result = $order->access($operation, $account, TRUE); } return $result; diff --git a/tests/src/Kernel/ShipmentAccessControlHandlerTest.php b/tests/src/Kernel/ShipmentAccessControlHandlerTest.php index a88d4bd..cc011fd 100644 --- a/tests/src/Kernel/ShipmentAccessControlHandlerTest.php +++ b/tests/src/Kernel/ShipmentAccessControlHandlerTest.php @@ -53,13 +53,18 @@ class ShipmentAccessControlHandlerTest extends ShippingKernelTestBase { $account = $this->createUser([], ['update default commerce_order']); $this->assertFalse($shipment->access('view', $account)); - $this->assertFalse($shipment->access('update', $account)); + $this->assertTrue($shipment->access('update', $account)); + $this->assertFalse($shipment->access('delete', $account)); + + $account = $this->createUser([], ['view commerce_order', 'update default commerce_order']); + $this->assertTrue($shipment->access('view', $account)); + $this->assertTrue($shipment->access('update', $account)); $this->assertFalse($shipment->access('delete', $account)); $account = $this->createUser([], [ 'manage default commerce_shipment', ]); - $this->assertFalse($shipment->access('view', $account)); + $this->assertTrue($shipment->access('view', $account)); $this->assertTrue($shipment->access('update', $account)); $this->assertTrue($shipment->access('delete', $account));