Commit a69bb373 authored by bojanz's avatar bojanz Committed by bojanz

Issue #2924736 by bojanz: Allow bundle config entities to be locked

parent 0e5a3fbf
......@@ -13,6 +13,9 @@ commerce_config_entity_bundle:
orderby: value
sequence:
type: string
locked:
type: boolean
label: 'Locked'
commerce_condition:
type: mapping
......
......@@ -3,8 +3,9 @@ status: true
label: Default
id: default
workflow: order_default
traits: { }
refresh_mode: customer
refresh_frequency: 300
sendReceipt: true
receiptBcc: ''
traits: { }
locked: false
......@@ -18,6 +18,7 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* plural = "@count order item types",
* ),
* handlers = {
* "access" = "Drupal\commerce\CommerceBundleAccessControlHandler",
* "form" = {
* "add" = "Drupal\commerce_order\Form\OrderItemTypeForm",
* "edit" = "Drupal\commerce_order\Form\OrderItemTypeForm",
......@@ -42,6 +43,7 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* "purchasableEntityType",
* "orderType",
* "traits",
* "locked",
* },
* links = {
* "add-form" = "/admin/commerce/config/order-item-types/add",
......
......@@ -18,6 +18,7 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* plural = "@count order types",
* ),
* handlers = {
* "access" = "Drupal\commerce\CommerceBundleAccessControlHandler",
* "form" = {
* "add" = "Drupal\commerce_order\Form\OrderTypeForm",
* "edit" = "Drupal\commerce_order\Form\OrderTypeForm",
......@@ -40,11 +41,12 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* "label",
* "id",
* "workflow",
* "traits",
* "refresh_mode",
* "refresh_frequency",
* "sendReceipt",
* "receiptBcc",
* "traits",
* "locked",
* },
* links = {
* "add-form" = "/admin/commerce/config/order-types/add",
......
......@@ -67,19 +67,28 @@ class OrderItemTypeTest extends OrderBrowserTestBase {
* Tests deleting an order item type programmatically and through the form.
*/
public function testOrderItemTypeDeletion() {
$values = [
/** @var \Drupal\commerce_order\Entity\OrderItemTypeInterface $type */
$type = $this->createEntity('commerce_order_item_type', [
'id' => strtolower($this->randomMachineName(8)),
'label' => $this->randomMachineName(16),
'purchasableEntityType' => 'commerce_product_variation',
'orderType' => 'default',
];
$order_item_type = $this->createEntity('commerce_order_item_type', $values);
]);
// Confirm that the delete page is not available when the type is locked.
$type->lock();
$type->save();
$this->drupalGet($type->toUrl('delete-form'));
$this->assertSession()->statusCodeEquals('403');
$this->drupalGet($order_item_type->toUrl('delete-form'));
// Unlock the type, confirm that deletion works.
$type->unlock();
$type->save();
$this->drupalGet($type->toUrl('delete-form'));
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains(t('This action cannot be undone.'));
$this->submitForm([], t('Delete'));
$order_item_type_exists = (bool) OrderItemType::load($order_item_type->id());
$order_item_type_exists = (bool) OrderItemType::load($type->id());
$this->assertEmpty($order_item_type_exists, 'The order item type has been deleted form the database.');
}
......
......@@ -80,34 +80,40 @@ class OrderTypeTest extends OrderBrowserTestBase {
* Tests deleting an order Type through the form.
*/
public function testDeleteOrderType() {
// Create an order type programmaticaly.
/** @var \Drupal\commerce_order\Entity\OrderTypeInterface $type */
$type = $this->createEntity('commerce_order_type', [
'id' => 'foo',
'label' => 'Label for foo',
'workflow' => 'order_default',
]);
commerce_order_add_order_items_field($type);
// Create an order.
$order = $this->createEntity('commerce_order', [
'type' => $type->id(),
'mail' => $this->loggedInUser->getEmail(),
'store_id' => $this->store,
]);
// Try to delete the order type.
$this->drupalGet('admin/commerce/config/order-types/' . $type->id() . '/delete');
// Confirm that the type can't be deleted while there's an order.
$this->drupalGet($type->toUrl('delete-form'));
$this->assertSession()->pageTextContains(t('@type is used by 1 order on your site. You cannot remove this order type until you have removed all of the @type orders.', ['@type' => $type->label()]));
$this->assertSession()->pageTextNotContains(t('This action cannot be undone.'));
// Deleting the order type when its not being referenced by an order.
// Confirm that the delete page is not available when the type is locked.
$type->lock();
$type->save();
$this->drupalGet($type->toUrl('delete-form'));
$this->assertSession()->statusCodeEquals('403');
// Delete the order, unlock the type, confirm that deletion works.
$order->delete();
$this->drupalGet('admin/commerce/config/order-types/' . $type->id() . '/delete');
$type->unlock();
$type->save();
$this->drupalGet($type->toUrl('delete-form'));
$this->assertSession()->pageTextContains(t('Are you sure you want to delete the order type @label?', ['@label' => $type->label()]));
$this->assertSession()->pageTextContains(t('This action cannot be undone.'));
$this->submitForm([], t('Delete'));
$type_exists = (bool) OrderType::load($type->id());
$this->assertEmpty($type_exists, 'The order type has been deleted from the database.');
$this->assertEmpty($type_exists);
}
}
......@@ -7,3 +7,4 @@ description: ''
variationType: default
injectVariationFields: true
traits: { }
locked: false
......@@ -6,3 +6,4 @@ label: Default
orderItemType: default
generateTitle: true
traits: { }
locked: false
......@@ -9,3 +9,4 @@ id: default
purchasableEntityType: commerce_product_variation
orderType: default
traits: { }
locked: false
......@@ -18,6 +18,7 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* plural = "@count product types",
* ),
* handlers = {
* "access" = "Drupal\commerce\CommerceBundleAccessControlHandler",
* "list_builder" = "Drupal\commerce_product\ProductTypeListBuilder",
* "form" = {
* "add" = "Drupal\commerce_product\Form\ProductTypeForm",
......@@ -43,6 +44,7 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* "variationType",
* "injectVariationFields",
* "traits",
* "locked",
* },
* links = {
* "add-form" = "/admin/commerce/config/product-types/add",
......
......@@ -18,6 +18,7 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* plural = "@count product variation types",
* ),
* handlers = {
* "access" = "Drupal\commerce\CommerceBundleAccessControlHandler",
* "list_builder" = "Drupal\commerce_product\ProductVariationTypeListBuilder",
* "form" = {
* "add" = "Drupal\commerce_product\Form\ProductVariationTypeForm",
......@@ -42,6 +43,7 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* "orderItemType",
* "generateTitle",
* "traits",
* "locked",
* },
* links = {
* "add-form" = "/admin/commerce/config/product-variation-types/add",
......
......@@ -83,6 +83,7 @@ class ProductTypeTest extends ProductBrowserTestBase {
'id' => 'foo',
'label' => 'foo',
]);
/** @var \Drupal\commerce_product\Entity\ProductTypeInterface $product_type */
$product_type = $this->createEntity('commerce_product_type', [
'id' => 'foo',
'label' => 'foo',
......@@ -90,23 +91,32 @@ class ProductTypeTest extends ProductBrowserTestBase {
]);
commerce_product_add_stores_field($product_type);
commerce_product_add_variations_field($product_type);
$product = $this->createEntity('commerce_product', [
'type' => $product_type->id(),
'title' => $this->randomMachineName(),
]);
// Confirm that the type can't be deleted while there's a product.
// @todo Make sure $product_type->delete() also does nothing if there's
// a product of that type. Right now the check is done on the form level.
$this->drupalGet('admin/commerce/config/product-types/' . $product_type->id() . '/delete');
$this->drupalGet($product_type->toUrl('delete-form'));
$this->assertSession()->pageTextContains(
t('@type is used by 1 product on your site. You cannot remove this product type until you have removed all of the @type products.', ['@type' => $product_type->label()]),
'The product type will not be deleted until all products of that type are deleted.'
);
$this->assertSession()->pageTextNotContains(t('This action cannot be undone.'));
// Confirm that the delete page is not available when the type is locked.
$product_type->lock();
$product_type->save();
$this->drupalGet($product_type->toUrl('delete-form'));
$this->assertSession()->statusCodeEquals('403');
// Delete the product, unlock the type, confirm that deletion works.
$product->delete();
$this->drupalGet('admin/commerce/config/product-types/' . $product_type->id() . '/delete');
$product_type->unlock();
$product_type->save();
$this->drupalGet($product_type->toUrl('delete-form'));
$this->assertSession()->pageTextContains(
t('Are you sure you want to delete the product type @type?', ['@type' => $product_type->label()]),
'The product type is available for deletion'
......@@ -114,7 +124,7 @@ class ProductTypeTest extends ProductBrowserTestBase {
$this->assertSession()->pageTextContains(t('This action cannot be undone.'));
$this->submitForm([], 'Delete');
$exists = (bool) ProductType::load($product_type->id());
$this->assertEmpty($exists, 'The new product type has been deleted from the database.');
$this->assertEmpty($exists);
}
}
......@@ -79,6 +79,7 @@ class ProductVariationTypeTest extends ProductBrowserTestBase {
* Tests deleting a product variation type via a form.
*/
public function testProductVariationTypeDeletion() {
/** @var \Drupal\commerce_product\Entity\ProductTypeInterface $variation_type */
$variation_type = $this->createEntity('commerce_product_variation_type', [
'id' => 'foo',
'label' => 'foo',
......@@ -89,17 +90,25 @@ class ProductVariationTypeTest extends ProductBrowserTestBase {
'title' => $this->randomMachineName(),
]);
// @todo Make sure $variation_type->delete() also does nothing if there's
// a variation of that type. Right now the check is done on the form level.
$this->drupalGet('admin/commerce/config/product-variation-types/' . $variation_type->id() . '/delete');
// Confirm that the type can't be deleted while there's a variation.
$this->drupalGet($variation_type->toUrl('delete-form'));
$this->assertSession()->pageTextContains(
t('@type is used by 1 product variation on your site. You cannot remove this product variation type until you have removed all of the @type product variations.', ['@type' => $variation_type->label()]),
'The product variation type will not be deleted until all variations of that type are deleted.'
);
$this->assertSession()->pageTextNotContains(t('This action cannot be undone.'), 'The product variation type deletion confirmation form is not available');
// Confirm that the delete page is not available when the type is locked.
$variation_type->lock();
$variation_type->save();
$this->drupalGet($variation_type->toUrl('delete-form'));
$this->assertSession()->statusCodeEquals('403');
// Delete the variation, unlock the type, confirm that deletion works.
$variation->delete();
$this->drupalGet('admin/commerce/config/product-variation-types/' . $variation_type->id() . '/delete');
$variation_type->unlock();
$variation_type->save();
$this->drupalGet($variation_type->toUrl('delete-form'));
$this->assertSession()->pageTextContains(
t('Are you sure you want to delete the product variation type @type?', ['@type' => $variation_type->label()]),
'The product variation type is available for deletion'
......@@ -107,7 +116,7 @@ class ProductVariationTypeTest extends ProductBrowserTestBase {
$this->assertSession()->pageTextContains(t('This action cannot be undone.'));
$this->getSession()->getPage()->pressButton('Delete');
$exists = (bool) ProductVariationType::load($variation_type->id());
$this->assertEmpty($exists, 'The new product variation type has been deleted from the database.');
$this->assertEmpty($exists);
}
/**
......
......@@ -5,3 +5,4 @@ id: online
label: Online
description: ''
traits: { }
locked: false
......@@ -18,6 +18,7 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* plural = "@count store types",
* ),
* handlers = {
* "access" = "Drupal\commerce\CommerceBundleAccessControlHandler",
* "list_builder" = "Drupal\commerce_store\StoreTypeListBuilder",
* "form" = {
* "add" = "Drupal\commerce_store\Form\StoreTypeForm",
......@@ -42,6 +43,7 @@ use Drupal\commerce\Entity\CommerceBundleEntityBase;
* "uuid",
* "description",
* "traits",
* "locked",
* },
* links = {
* "add-form" = "/admin/commerce/config/store-types/add",
......
......@@ -97,30 +97,36 @@ class StoreTypeTest extends CommerceBrowserTestBase {
* Tests deleting a Store Type through the form.
*/
public function testDeleteStoreType() {
// Create a store type programmaticaly.
/** @var \Drupal\commerce_store\Entity\StoreTypeInterface $type */
$type = $this->createEntity('commerce_store_type', [
'id' => 'foo',
'label' => 'Label for foo',
]);
// Create a store.
$store = $this->createStore(NULL, NULL, $type->id());
// Try to delete the store type.
$this->drupalGet('admin/commerce/config/store-types/' . $type->id() . '/delete');
// Confirm that the type can't be deleted while there's a store.
$this->drupalGet($type->toUrl('delete-form'));
$this->assertSession()->pageTextContains(t('@type is used by 1 store on your site. You cannot remove this store type until you have removed all of the @type stores.', ['@type' => $type->label()]));
$this->assertSession()->pageTextNotContains('This action cannot be undone.');
$this->assertSession()->pageTextNotContains('The store type deletion confirmation form is not available');
// Deleting the store type when its not being referenced by a store.
// Confirm that the delete page is not available when the type is locked.
$type->lock();
$type->save();
$this->drupalGet($type->toUrl('delete-form'));
$this->assertSession()->statusCodeEquals('403');
// Delete the store, unlock the type, confirm that deletion works.
$store->delete();
$this->drupalGet('admin/commerce/config/store-types/' . $type->id() . '/delete');
$type->unlock();
$type->save();
$this->drupalGet($type->toUrl('delete-form'));
$this->assertSession()->pageTextContains(t('Are you sure you want to delete the store type @type?', ['@type' => $type->label()]));
$this->saveHtmlOutput();
$this->assertSession()->pageTextContains('This action cannot be undone.');
$this->submitForm([], 'Delete');
$type_exists = (bool) StoreType::load($type->id());
$this->assertEmpty($type_exists, 'The new store type has been deleted from the database.');
$this->assertEmpty($type_exists);
}
}
<?php
namespace Drupal\commerce;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the access control handler for bundles.
*/
class CommerceBundleAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\commerce\Entity\CommerceBundleEntityInterface $entity */
$admin_permission = $entity->getEntityType()->getAdminPermission();
if ($operation === 'delete') {
if ($entity->isLocked()) {
return AccessResult::forbidden()->addCacheableDependency($entity);
}
else {
return AccessResult::allowedIfHasPermission($account, $admin_permission)->addCacheableDependency($entity);
}
}
return AccessResult::allowedIfHasPermission($account, $admin_permission);
}
}
......@@ -30,6 +30,13 @@ class CommerceBundleEntityBase extends ConfigEntityBundleBase implements Commerc
*/
protected $traits = [];
/**
* Whether the bundle is locked, indicating that it cannot be deleted.
*
* @var bool
*/
protected $locked = FALSE;
/**
* {@inheritdoc}
*/
......@@ -52,4 +59,27 @@ class CommerceBundleEntityBase extends ConfigEntityBundleBase implements Commerc
return in_array($trait, $this->traits);
}
/**
* {@inheritdoc}
*/
public function isLocked() {
return (bool) $this->locked;
}
/**
* {@inheritdoc}
*/
public function lock() {
$this->locked = TRUE;
return $this;
}
/**
* {@inheritdoc}
*/
public function unlock() {
$this->locked = FALSE;
return $this;
}
}
......@@ -39,4 +39,28 @@ interface CommerceBundleEntityInterface extends ConfigEntityInterface {
*/
public function hasTrait($trait);
/**
* Gets whether the bundle is locked.
*
* Locked bundles cannot be deleted.
*
* @return bool
* TRUE if the bundle is locked, FALSE otherwise.
*/
public function isLocked();
/**
* Locks the bundle.
*
* @return $this
*/
public function lock();
/**
* Unlocks the bundle.
*
* @return $this
*/
public function unlock();
}
......@@ -5,3 +5,4 @@ id: testing
label: Testing
description: ''
traits: { }
locked: false
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment