Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detailed permissions #1869

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ee57310
- Refactored product permission to have edit, create and delete permi…
pdaleramirez Oct 30, 2020
eb1e367
Modified hasPermission method to accept lowercae permission name.
pdaleramirez Oct 30, 2020
6a4c17b
Merge branch 'develop' into feature/edit-delete-product-permission
pdaleramirez Oct 30, 2020
583281c
Merge branch 'develop' into feature/edit-delete-product-permission
pdaleramirez Nov 4, 2020
b2494e0
Modified is admin condition
pdaleramirez Nov 5, 2020
648128e
Merge branch 'develop' into feature/edit-delete-product-permission
pdaleramirez Nov 7, 2020
1e74d1f
Changed manage products to edit product type permission on ProductTyp…
pdaleramirez Nov 9, 2020
5eefffc
- Added permission for create product index based on the product type…
pdaleramirez Nov 9, 2020
e05d848
Changed default view config permission to get permission from editabl…
pdaleramirez Nov 10, 2020
d683e1c
Changed manage products permission in widgets to product type editabl…
pdaleramirez Nov 10, 2020
7881ab7
Changed manage product type to edit product type permissions
pdaleramirez Nov 10, 2020
dc7015c
Created production permission migration for converting manage product…
pdaleramirez Nov 11, 2020
2d6aee6
Added user groups permission migration code.
pdaleramirez Nov 12, 2020
b7beb02
Merge branch 'develop' into feature/edit-delete-product-permission
pdaleramirez Nov 12, 2020
ba7fdf0
Added detailed user group permission project config migration.
pdaleramirez Nov 13, 2020
043a8d4
Merge branch 'develop' into feature/edit-delete-product-permission
pdaleramirez Nov 17, 2020
2e107fb
Dont use LIKE since we know what the string is exactly.
lukeholder Nov 17, 2020
1ac8208
Merge branch 'feature/edit-delete-product-permission' of github.com:c…
lukeholder Nov 17, 2020
20fbf26
Merge branch 'develop' into feature/edit-delete-product-permission
lukeholder Nov 17, 2020
8ec87e1
Cleanup
lukeholder Nov 30, 2020
3c04ce4
Context
lukeholder Nov 30, 2020
b1388e7
Included save as new product as create product permission.
pdaleramirez Dec 1, 2020
a612ee9
Merge branch 'develop' into feature/edit-delete-product-permission
lukeholder Dec 2, 2020
53cf2f2
Merge branch '4.0' into feature/edit-delete-product-permission
lukeholder Dec 2, 2020
6bbf19f
Chnagelog for new permissions migration
lukeholder Dec 2, 2020
682442b
Rename migration
lukeholder Dec 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
use craft\events\RegisterUserPermissionsEvent;
use craft\fixfks\controllers\RestoreController;
use craft\gql\ElementQueryConditionBuilder;
use craft\helpers\ArrayHelper;
use craft\helpers\FileHelper;
use craft\helpers\UrlHelper;
use craft\models\FieldLayout;
Expand Down Expand Up @@ -130,7 +131,7 @@ public static function t($message, $params = [], $language = null)
/**
* @inheritDoc
*/
public $schemaVersion = '3.2.9';
public $schemaVersion = '3.2.10';

/**
* @inheritdoc
Expand Down Expand Up @@ -236,7 +237,7 @@ public function getCpNavItem(): array
}

$hasEditableProductTypes = !empty($this->getProductTypes()->getEditableProductTypes());
if ($hasEditableProductTypes && Craft::$app->getUser()->checkPermission('commerce-manageProducts')) {
if ($hasEditableProductTypes) {
$ret['subnav']['products'] = [
'label' => Craft::t('commerce', 'Products'),
'url' => 'commerce/products'
Expand Down Expand Up @@ -359,16 +360,8 @@ private function _registerRedactorLinkOptions()
private function _registerPermissions()
{
Event::on(UserPermissions::class, UserPermissions::EVENT_REGISTER_PERMISSIONS, function(RegisterUserPermissionsEvent $event) {
$productTypes = Plugin::getInstance()->getProductTypes()->getAllProductTypes();

$productTypePermissions = [];
foreach ($productTypes as $productType) {
$suffix = ':' . $productType->uid;
$productTypePermissions['commerce-manageProductType' . $suffix] = ['label' => Craft::t('commerce', 'Manage “{type}” products', ['type' => $productType->name])];
}

$event->permissions[Craft::t('commerce', 'Craft Commerce')] = [
'commerce-manageProducts' => ['label' => Craft::t('commerce', 'Manage products'), 'nested' => $productTypePermissions],
$event->permissions[Craft::t('commerce', 'Craft Commerce')] = [
'commerce-manageOrders' => [
'label' => Craft::t('commerce', 'Manage orders'), 'nested' => [
'commerce-editOrders' => [
Expand All @@ -392,9 +385,43 @@ private function _registerPermissions()
'commerce-manageTaxes' => ['label' => Craft::t('commerce', 'Manage taxes (Pro edition Only)')],
'commerce-manageStoreSettings' => ['label' => Craft::t('commerce', 'Manage store settings')],
];

$productTypePermissions = $this->_registerProductTypePermission();

$event->permissions = ArrayHelper::merge($event->permissions, $productTypePermissions);
});
}

private function _registerProductTypePermission()
{
$productTypes = Plugin::getInstance()->getProductTypes()->getAllProductTypes();

$permissions = [];
foreach ($productTypes as $productType) {
$suffix = ':' . $productType->uid;

$productTypePermissions = [];
$productTypePermissions['commerce-editProductType' . $suffix] = [
'label' => Craft::t('commerce', 'Edit “{type}” products', ['type' => $productType->name]),
'nested' => [
"commerce-createProducts{$suffix}" => [
'label' => Craft::t('app', 'Create products'),
],
"commerce-deleteProducts{$suffix}" => [
'label' => Craft::t('app', 'Delete products'),
],
]
];

$label = Craft::t('commerce', 'Product Type - {type}',
['type' => Craft::t('commerce', $productType->name)]);

$permissions[$label] = $productTypePermissions;
}

return $permissions;
}

/**
* Register Commerce’s project config event listeners
*/
Expand Down
38 changes: 32 additions & 6 deletions src/controllers/ProductsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ class ProductsController extends BaseController
public function init()
{
parent::init();

$this->requirePermission('commerce-manageProducts');
}

/**
Expand All @@ -65,7 +63,7 @@ public function beforeAction($action): bool
if (!parent::beforeAction($action)) {
return false;
}

if (!in_array($action->id, $this->ignorePluginPermission)) {
$this->requirePermission('accessPlugin-commerce');
}
Expand Down Expand Up @@ -117,11 +115,23 @@ public function actionEditProduct(string $productTypeHandle, int $productId = nu
}

$this->_prepEditProductVariables($variables);

/** @var Product $product */
$product = $variables['product'];

$user = Craft::$app->getUser()->getIdentity();
if (Plugin::getInstance()->getProducts()->hasPermission($user, $product) === false) {
throw new ForbiddenHttpException('User not permitted to edit this product.');
}

$variables['canCreateProduct'] = Plugin::getInstance()->getProducts()->hasPermission($user, $product, 'commerce-createProducts');
$variables['canDeleteProduct'] = Plugin::getInstance()->getProducts()->hasPermission($user, $product, 'commerce-deleteProducts');

if ($product->id === null) {
if ($variables['canCreateProduct'] === false ) {
throw new ForbiddenHttpException('User not permitted to create a product for the product type.');
}

$variables['title'] = Craft::t('commerce', 'Create a new product');
} else {
$variables['title'] = $product->title;
Expand Down Expand Up @@ -189,6 +199,11 @@ public function actionDeleteProduct()
$productId = Craft::$app->getRequest()->getRequiredParam('productId');
$product = Plugin::getInstance()->getProducts()->getProductById($productId);

$user = Craft::$app->getUser()->getIdentity();
if (!Plugin::getInstance()->getProducts()->hasPermission($user, $product, 'commerce-deleteProducts')) {
throw new ForbiddenHttpException('User not permitted to delete this product.');
}

if (!$product) {
throw new Exception(Craft::t('commerce', 'No product exists with the ID “{id}”.',
['id' => $productId]));
Expand Down Expand Up @@ -235,7 +250,18 @@ public function actionSaveProduct(bool $duplicate = false)
$request = Craft::$app->getRequest();
$oldProduct = ProductHelper::productFromPost($request);
$variants = $request->getBodyParam('variants');
$this->enforceProductPermissions($oldProduct);

$user = Craft::$app->getUser()->getIdentity();
if ($oldProduct->id !== null && !Plugin::getInstance()->getProducts()->hasPermission($user, $oldProduct)) {
throw new ForbiddenHttpException('User not permitted to save this product.');
}

if ($request->getBodyParam('typeId') !== null && !Plugin::getInstance()->getProducts()->hasPermission($user, $oldProduct, 'commerce-createProducts')) {
if ($oldProduct->id === null || $duplicate === true) {
throw new ForbiddenHttpException('User not permitted to create a product for this type.');
}
}

$elementsService = Craft::$app->getElements();

$transaction = Craft::$app->getDb()->beginTransaction();
Expand Down Expand Up @@ -369,7 +395,7 @@ public function actionDuplicateProduct()
*/
protected function enforceProductPermissions(Product $product)
{
$this->requirePermission('commerce-manageProductType:' . $product->getType()->uid);
$this->requirePermission('commerce-editProductType:' . $product->getType()->uid);
}


Expand Down
2 changes: 1 addition & 1 deletion src/controllers/ProductsPreviewController.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public function actionSaveProduct()
*/
protected function enforceProductPermissions(Product $product)
{
$this->requirePermission('commerce-manageProductType:' . $product->getType()->uid);
$this->requirePermission('commerce-editProductType:' . $product->getType()->uid);
}

/**
Expand Down
41 changes: 24 additions & 17 deletions src/elements/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ public function getIsEditable(): bool
if ($this->getType()) {
$uid = $this->getType()->uid;

return Craft::$app->getUser()->checkPermission('commerce-manageProductType:' . $uid);
return Craft::$app->getUser()->checkPermission('commerce-editProductType:' . $uid);
}

return false;
Expand Down Expand Up @@ -1002,7 +1002,7 @@ protected static function defineSources(string $context = null): array

foreach ($productTypes as $productType) {
$key = 'productType:' . $productType->uid;
$canEditProducts = Craft::$app->getUser()->checkPermission('commerce-manageProductType:' . $productType->uid);
$canEditProducts = Craft::$app->getUser()->checkPermission('commerce-editProductType:' . $productType->uid);

$sources[$key] = [
'key' => $key,
Expand Down Expand Up @@ -1068,21 +1068,28 @@ protected static function defineActions(string $source = null): array
$canManage = false;

foreach ($productTypes as $productType) {
$canManage = $userSession->checkPermission('commerce-manageProductType:' . $productType->uid);
}

if ($canManage) {
// Duplicate
$actions[] = Duplicate::class;

// Allow deletion
$deleteAction = Craft::$app->getElements()->createAction([
'type' => Delete::class,
'confirmationMessage' => Craft::t('commerce', 'Are you sure you want to delete the selected product and its variants?'),
'successMessage' => Craft::t('commerce', 'Products and Variants deleted.'),
]);
$actions[] = $deleteAction;
$actions[] = SetStatus::class;
$canDelete = $userSession->checkPermission('commerce-deleteProducts:' . $productType->uid);
$canCreate = $userSession->checkPermission('commerce-createProducts:' . $productType->uid);
$canEdit = $userSession->checkPermission('commerce-editProducts:' . $productType->uid);

if ($canCreate) {
// Duplicate
$actions[] = Duplicate::class;
}

if ($canDelete) {
// Allow deletion
$deleteAction = Craft::$app->getElements()->createAction([
'type' => Delete::class,
'confirmationMessage' => Craft::t('commerce', 'Are you sure you want to delete the selected product and its variants?'),
'successMessage' => Craft::t('commerce', 'Products and Variants deleted.'),
]);
$actions[] = $deleteAction;
}

if ($canEdit) {
$actions[] = SetStatus::class;
}
}

if ($userSession->checkPermission('commerce-managePromotions')) {
Expand Down
114 changes: 114 additions & 0 deletions src/migrations/m201111_072959_product_permission_conversion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace craft\commerce\migrations;

use Craft;
use craft\db\Migration;
use craft\db\Query;
use craft\db\Table;
use yii\db\Expression;

/**
* m201111_072959_product_permission_conversion migration.
*/
class m201111_072959_product_permission_conversion extends Migration
{
/**
* @inheritdoc
*/
public function safeUp()
{
// Get existing manage product type permission
$permissions = (new Query())
->select(['id', 'name'])
->from([Table::USERPERMISSIONS])
->where(new Expression("LEFT([[name]], 26) = 'commerce-manageproducttype'"))
->all();

if (count($permissions) > 0) {

foreach ($permissions as $permission) {

$permissionName = explode(':', $permission['name']);
$productTypeUid = $permissionName[1];

// Rename manage product type to edit product type
$newName = str_replace('commerce-manageproducttype', 'commerce-editproducttype', $permission['name']);
$this->update(Table::USERPERMISSIONS, ['name' => $newName], ['id' => $permission['id']], [], false);

// Create new create product permission by product type
$this->insert(Table::USERPERMISSIONS, ['name' => 'commerce-createproducts:' . $productTypeUid]);
$createPermissionId = $this->db->getLastInsertID();

// Create new delete product permission by product type
$this->insert(Table::USERPERMISSIONS, ['name' => 'commerce-deleteproducts:' . $productTypeUid]);
$deletePermissionId = $this->db->getLastInsertID();

// Check if manage product type is ticked for user permissions
$manageProductTypes = (new Query())
->select(['id', 'permissionId', 'userId'])
->from([Table::USERPERMISSIONS_USERS])
->where(['permissionId' => $permission['id']])
->all();
// Add the new edit product child permissions for the same users
foreach ($manageProductTypes as $manageProductType) {
$this->insert(Table::USERPERMISSIONS_USERS, ['userId' => $manageProductType['userId'], 'permissionId' => $createPermissionId]);
$this->insert(Table::USERPERMISSIONS_USERS, ['userId' => $manageProductType['userId'], 'permissionId' => $deletePermissionId]);
}

// Check if manage product type is ticked for user group permissions
$manageProductTypesForGroups = (new Query())
->select(['id', 'permissionId', 'groupId'])
->from([Table::USERPERMISSIONS_USERGROUPS])
->where(['permissionId' => $permission['id']])
->all();
// Add the new edit product child permissions for the same groups
foreach ($manageProductTypesForGroups as $manageProductType) {
// Create new create and delete product permission relationship with a group.
$this->insert(Table::USERPERMISSIONS_USERGROUPS, ['groupId' => $manageProductType['groupId'], 'permissionId' => $createPermissionId]);
$this->insert(Table::USERPERMISSIONS_USERGROUPS, ['groupId' => $manageProductType['groupId'], 'permissionId' => $deletePermissionId]);
}
}

// No longer need this top level permission
$this->delete(Table::USERPERMISSIONS, ['name' => 'commerce-manageproducts']);

// Make project config updates
$projectConfig = Craft::$app->getProjectConfig();
$schemaVersion = $projectConfig->get('plugins.commerce.schemaVersion', true);
if (version_compare($schemaVersion, '3.2.10', '<')) {

$groups = (new Query())
->select(['id', 'name', 'uid'])
->from(['groups' => Table::USERGROUPS])
->all();

$setGroupPermissions = [];

foreach ($groups as $group) {
$groupPermissions = (new Query())
->select(['up.name'])
->from(['up_ug' => Table::USERPERMISSIONS_USERGROUPS])
->where(['up_ug.groupId' => $group['id']])
->innerJoin(['up' => Table::USERPERMISSIONS], '[[up.id]] = [[up_ug.permissionId]]')
->column();

$setGroupPermissions[$group['uid']] = $groupPermissions;
}

foreach ($setGroupPermissions as $uid => $setGroupPermission) {
$projectConfig->set('users.groups.' . $uid . '.permissions', $setGroupPermission);
}
}
}
}

/**
* @inheritdoc
*/
public function safeDown()
{
echo "m201111_072959_product_permission_conversion cannot be reverted.\n";
return false;
}
}
Loading