Skip to content

Commit

Permalink
Merge pull request #6 from venveo/develop
Browse files Browse the repository at this point in the history
Add permissions and queue improvements
  • Loading branch information
Mosnar authored Jul 21, 2019
2 parents cc563fe + 6c058e1 commit 08bcc5a
Show file tree
Hide file tree
Showing 24 changed files with 315 additions and 104 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@
- Added `EVENT_REGISTER_ELEMENT_PROCESSORS` to allow modules/plugins an opportunity to register an element processor
- Added `EVENT_REGISTER_SUPPORTED_FIELDS` to allow modules/plugins an opportunity to register a supported field
- Added a progress message to queue job
- Added permissions for each element type
- Edit contexts now ensure the element types match that of the original request

### Changed
- Refactored much of the modal form structure to accommodate strategies
- Code cleaning & abstraction
- The queue now runs automatically after saving a job
- The queue jobs now batch elements properly

### Removed
- Don't save revisions of entries anymore
Expand Down
40 changes: 34 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
# Bulk Edit plugin for Craft CMS 3.x
This plugin is built to offer extended editing functionality of a set of Craft entries.
# Bulk Edit plugin for Craft CMS 3.2

## Overview
The Bulk Edit plugin adds an action to supported element index pages that allows you to edit fields on a large number of
elements at once. Currently, the following element types are supported:
- Entries
- Categories
- Assets
- Users
- Craft Commerce Products

Additionally, some fields support different strategies for the edit process. At the moment, any field that works with
"relations" (such as Entries, Categories, Assets, etc) supports the following strategies:
- Replace: Replaces all content in the field
- Merge: Merges the selected elements into the relation field
- Subtract: Removes the selected elements from the relation field

## Instructions
1. Navigate to a supported element index page and select any number of elements
2. Click the gear at the top of the page and select "Bulk Edit"
3. Enable the light-switches next to the fields you're interested in editing and select a strategy
4. Click next and enter the content for the fields
5. Once you click Save, a task will be dispatched to the queue, at which point you will need to refresh the page for
Craft to pick it up. After the queue has finished, you may reload the page and see your changes.

## Limitation & Issues
* Custom fields and Matrix fields are not currently supported due to issues that arise when a field is rendered without
single entry selected.
* Currently, there isn't a way to edit properties on elements that are not custom fields (for example, title, slug,
post date, etc)
* Validation is not enforced when you're editing these fields, this means you can end up with elements with fields in
potentially erroneous states (for example, removing all content on a required field)
* After the queue finishes running, make sure you refresh the page to see the updates in the element index

## Steps to use:
![Screenshot](resources/img/screenshot1.png)
Expand All @@ -8,7 +39,7 @@ This plugin is built to offer extended editing functionality of a set of Craft e

## Requirements

This plugin requires Craft CMS 3.0.0 or later.
This plugin requires Craft CMS 3.2.0 or later.

## Installation

Expand All @@ -24,7 +55,4 @@ To install the plugin, follow these instructions.

3. In the Control Panel, go to Settings → Plugins and click the “Install” button for Bulk Edit.

## Known Issues
* I've had to disabled custom field support as well as Matrix support for the moment. Some refactoring will need to be done to support these types of fields.

Brought to you by [Venveo](https://venveo.com)
Binary file modified resources/img/screenshot1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/img/screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/img/screenshot3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 66 additions & 22 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use craft\elements\Entry;
use craft\elements\User;
use craft\events\RegisterElementActionsEvent;
use craft\events\RegisterUserPermissionsEvent;
use craft\services\UserPermissions;
use venveo\bulkedit\elements\actions\BulkEditElementAction;
use venveo\bulkedit\services\BulkEdit;
use yii\base\Event;
Expand All @@ -37,43 +39,85 @@ class Plugin extends BasePlugin
*/
public static $plugin;

public $schemaVersion = '1.0.2';
public $schemaVersion = '1.1.0';

public const PERMISSION_BULKEDIT_ENTRIES = 'PERMISSION_BULKEDIT_ENTRIES';
public const PERMISSION_BULKEDIT_PRODUCTS = 'PERMISSION_BULKEDIT_PRODUCTS';
public const PERMISSION_BULKEDIT_ASSETS = 'PERMISSION_BULKEDIT_ASSETS';
public const PERMISSION_BULKEDIT_CATEGORIES = 'PERMISSION_BULKEDIT_CATEGORIES';
public const PERMISSION_BULKEDIT_USERS = 'PERMISSION_BULKEDIT_USERS';

public function init()
{
parent::init();
self::$plugin = $this;

Event::on(Entry::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
Event::on(UserPermissions::class, UserPermissions::EVENT_REGISTER_PERMISSIONS, function (RegisterUserPermissionsEvent $event) {
$permissions = [];
$permissions[self::PERMISSION_BULKEDIT_ENTRIES] = [
'label' => \Craft::t('venveo-bulk-edit', 'Bulk Edit Entries')
];
$permissions[self::PERMISSION_BULKEDIT_ASSETS] = [
'label' => \Craft::t('venveo-bulk-edit', 'Bulk Edit Assets')
];
$permissions[self::PERMISSION_BULKEDIT_CATEGORIES] = [
'label' => \Craft::t('venveo-bulk-edit', 'Bulk Edit Categories')
];
$permissions[self::PERMISSION_BULKEDIT_USERS] = [
'label' => \Craft::t('venveo-bulk-edit', 'Bulk Edit Users')
];

if (\Craft::$app->plugins->isPluginInstalled('commerce')) {
$permissions[self::PERMISSION_BULKEDIT_PRODUCTS] = [
'label' => \Craft::t('venveo-bulk-edit', 'Bulk Edit Products')
];
}

$event->permissions[\Craft::t('venveo-bulk-edit', 'Bulk Edit')] = $permissions;
});

if (\Craft::$app->request->isCpRequest) {
if (\Craft::$app->user->checkPermission(self::PERMISSION_BULKEDIT_ENTRIES)) {
Event::on(Entry::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
}
);
}
);

Event::on(Category::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
if (\Craft::$app->user->checkPermission(self::PERMISSION_BULKEDIT_CATEGORIES)) {
Event::on(Category::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
}
);
}
);

Event::on(Asset::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
if (\Craft::$app->user->checkPermission(self::PERMISSION_BULKEDIT_ASSETS)) {
Event::on(Asset::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
}
);
}
);

Event::on(User::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
if (\Craft::$app->user->checkPermission(self::PERMISSION_BULKEDIT_USERS)) {
Event::on(User::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
}
);
}
);

if(\Craft::$app->plugins->isPluginInstalled('commerce') && class_exists(Product::class)) {
Event::on(Product::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
if (\Craft::$app->user->checkPermission(self::PERMISSION_BULKEDIT_PRODUCTS)) {
if (\Craft::$app->plugins->isPluginInstalled('commerce') && class_exists(Product::class)) {
Event::on(Product::class, Element::EVENT_REGISTER_ACTIONS,
function (RegisterElementActionsEvent $event) {
$event->actions[] = BulkEditElementAction::class;
}
);
}
);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ Craft.BulkEditModal = Garnish.Modal.extend(
const formValues = this.$container.find('#bulk-edit-values-modal').serializeArray();
Craft.postActionRequest('venveo-bulk-edit/bulk-edit/save-context', formValues, function(response) {
this.hide();
Craft.cp.trackJobProgress(false, true);
Craft.cp.runQueue();
}.bind(this));
},

Expand Down
10 changes: 10 additions & 0 deletions src/base/ElementTypeProcessorInterface.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
namespace venveo\bulkedit\base;

use craft\web\User;

interface ElementTypeProcessorInterface {

/**
Expand All @@ -10,6 +12,14 @@ interface ElementTypeProcessorInterface {
*/
public static function getLayoutsFromElementIds($elementIds): array;

/**
* Return whether a given user has permission to perform bulk edit actions on these elements
* @param $elementIds
* @param $user
* @return bool
*/
public static function hasPermission($elementIds, User $user): bool;

/**
* The fully qualified class name for the element this processor works on
* @return string
Expand Down
49 changes: 12 additions & 37 deletions src/controllers/BulkEditController.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public function actionGetFields(): Response
$view = \Craft::$app->getView();
$modalHtml = $view->renderTemplate('venveo-bulk-edit/elementactions/BulkEdit/_fields', [
'fieldWrappers' => $fields,
'elementType' => $elementType,
'bulkedit' => $service,
'elementIds' => $elementIds,
'site' => $site
Expand Down Expand Up @@ -90,6 +91,7 @@ public function actionGetEditScreen()

$elementIds = Craft::$app->getRequest()->getRequiredParam('elementIds');
$requestId = Craft::$app->getRequest()->getRequiredParam('requestId');
$elementType = Craft::$app->getRequest()->getRequiredParam('elementType');
$siteId = Craft::$app->getRequest()->getRequiredParam('siteId');
$fields = Craft::$app->getRequest()->getRequiredParam('fields');

Expand Down Expand Up @@ -144,6 +146,7 @@ public function actionGetEditScreen()

$modalHtml = $view->renderTemplate('venveo-bulk-edit/elementactions/BulkEdit/_edit', [
'fields' => $fieldModels,
'elementType' => $elementType,
'elementIds' => $elementIds,
'fieldData' => $enabledFields,
'site' => $site
Expand Down Expand Up @@ -171,8 +174,8 @@ public function actionSaveContext()
$this->requireAcceptsJson();

$elementIds = Craft::$app->getRequest()->getRequiredParam('elementIds');
$elementType = Craft::$app->getRequest()->getRequiredParam('elementType');
$siteId = Craft::$app->getRequest()->getRequiredParam('siteId');
// $fieldIds = array_values(Craft::$app->getRequest()->getRequiredParam('fieldIds'));
$fieldMeta = array_values(Craft::$app->getRequest()->getRequiredParam('fieldMeta'));

$fieldStrategies = [];
Expand All @@ -199,42 +202,14 @@ public function actionSaveContext()

$elementIds = explode(',', $elementIds);

$context = new EditContext();
$context->ownerId = \Craft::$app->getUser()->getIdentity()->id;
$context->siteId = $siteId;
$context->elementIds = \GuzzleHttp\json_encode($elementIds);
$context->fieldIds = \GuzzleHttp\json_encode($fieldIds);
$context->save();

$rows = [];
foreach ($elementIds as $elementId) {
foreach ($fieldIds as $fieldId) {
$strategy = $fieldStrategies[$fieldId] ?? 'replace';

$rows[] = [
'pending',
$context->id,
(int)$elementId,
(int)$fieldId,
(int)$siteId,
'[]',
\GuzzleHttp\json_encode($keyedFieldValues[$fieldId]),
$strategy
];
}
}

$cols = ['status', 'contextId', 'elementId', 'fieldId', 'siteId', 'originalValue', 'newValue', 'strategy'];
\Craft::$app->db->createCommand()->batchInsert(History::tableName(), $cols, $rows)->execute();


$job = new SaveBulkEditJob([
'context' => $context
]);
\Craft::$app->getQueue()->push($job);
try {
Plugin::$plugin->bulkEdit->saveContext($elementType, $siteId, $elementIds, $fieldIds, $keyedFieldValues);

return $this->asJson([
'success' => true
]);
return $this->asJson([
'success' => true
]);
} catch (\Exception $e) {
return $this->asErrorJson('Failed to save context');
}
}
}
15 changes: 13 additions & 2 deletions src/elements/processors/AssetProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
namespace venveo\bulkedit\elements\processors;

use craft\elements\Asset;
use craft\elements\User;
use craft\helpers\ArrayHelper;
use craft\records\FieldLayout;
use craft\services\Users;
use craft\web\User;
use venveo\bulkedit\base\AbstractElementTypeProcessor;
use venveo\bulkedit\Plugin;

class AssetProcessor extends AbstractElementTypeProcessor
{
Expand Down Expand Up @@ -41,4 +41,15 @@ public static function getType(): string
{
return get_class(new Asset);
}

/**
* Return whether a given user has permission to perform bulk edit actions on these elements
* @param $elementIds
* @param $user
* @return bool
*/
public static function hasPermission($elementIds, User $user): bool
{
return $user->checkPermission(Plugin::PERMISSION_BULKEDIT_ASSETS);
}
}
15 changes: 13 additions & 2 deletions src/elements/processors/CategoryProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
namespace venveo\bulkedit\elements\processors;

use craft\elements\Category;
use craft\elements\User;
use craft\helpers\ArrayHelper;
use craft\records\CategoryGroup;
use craft\records\FieldLayout;
use craft\services\Users;
use craft\web\User;
use venveo\bulkedit\base\AbstractElementTypeProcessor;
use venveo\bulkedit\Plugin;

class CategoryProcessor extends AbstractElementTypeProcessor
{
Expand Down Expand Up @@ -50,4 +50,15 @@ public static function getType(): string
{
return get_class(new Category);
}

/**
* Return whether a given user has permission to perform bulk edit actions on these elements
* @param $elementIds
* @param $user
* @return bool
*/
public static function hasPermission($elementIds, User $user): bool
{
return $user->checkPermission(Plugin::PERMISSION_BULKEDIT_CATEGORIES);
}
}
13 changes: 13 additions & 0 deletions src/elements/processors/EntryProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use craft\elements\Entry;
use craft\records\FieldLayout;
use craft\web\User;
use venveo\bulkedit\base\AbstractElementTypeProcessor;
use venveo\bulkedit\Plugin;

class EntryProcessor extends AbstractElementTypeProcessor
{
Expand Down Expand Up @@ -36,4 +38,15 @@ public static function getType(): string
{
return get_class(new Entry);
}

/**
* Return whether a given user has permission to perform bulk edit actions on these elements
* @param $elementIds
* @param $user
* @return bool
*/
public static function hasPermission($elementIds, User $user): bool
{
return $user->checkPermission(Plugin::PERMISSION_BULKEDIT_ENTRIES);
}
}
Loading

0 comments on commit 08bcc5a

Please sign in to comment.