Issue #2916451 by Lendude, xjm: Move everything related to Bulk Form to Views module
parent
b912d77400
commit
c203575397
|
|
@ -213,23 +213,6 @@ theme_settings:
|
||||||
sequence:
|
sequence:
|
||||||
type: theme_settings.third_party.[%key]
|
type: theme_settings.third_party.[%key]
|
||||||
|
|
||||||
views_field_bulk_form:
|
|
||||||
type: views_field
|
|
||||||
label: 'Bulk operation'
|
|
||||||
mapping:
|
|
||||||
action_title:
|
|
||||||
type: label
|
|
||||||
label: 'Action title'
|
|
||||||
include_exclude:
|
|
||||||
type: string
|
|
||||||
label: 'Available actions'
|
|
||||||
selected_actions:
|
|
||||||
type: sequence
|
|
||||||
label: 'Available actions'
|
|
||||||
sequence:
|
|
||||||
type: string
|
|
||||||
label: 'Action'
|
|
||||||
|
|
||||||
# Array of routes with route_name and route_params keys.
|
# Array of routes with route_name and route_params keys.
|
||||||
route:
|
route:
|
||||||
type: mapping
|
type: mapping
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Drupal\comment\Plugin\views\field;
|
namespace Drupal\comment\Plugin\views\field;
|
||||||
|
|
||||||
use Drupal\system\Plugin\views\field\BulkForm;
|
use Drupal\views\Plugin\views\field\BulkForm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a comment operations bulk form element.
|
* Defines a comment operations bulk form element.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Drupal\node\Plugin\views\field;
|
namespace Drupal\node\Plugin\views\field;
|
||||||
|
|
||||||
use Drupal\system\Plugin\views\field\BulkForm;
|
use Drupal\views\Plugin\views\field\BulkForm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a node operations bulk form element.
|
* Defines a node operations bulk form element.
|
||||||
|
|
|
||||||
|
|
@ -2,506 +2,20 @@
|
||||||
|
|
||||||
namespace Drupal\system\Plugin\views\field;
|
namespace Drupal\system\Plugin\views\field;
|
||||||
|
|
||||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
@trigger_error(__NAMESPACE__ . '\BulkForm is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\views\Plugin\views\field\BulkForm instead. See https://www.drupal.org/node/2916716.', E_USER_DEPRECATED);
|
||||||
use Drupal\Core\Entity\EntityInterface;
|
|
||||||
use Drupal\Core\Entity\EntityManagerInterface;
|
use Drupal\views\Plugin\views\field\BulkForm as ViewsBulkForm;
|
||||||
use Drupal\Core\Entity\RevisionableInterface;
|
|
||||||
use Drupal\Core\Form\FormStateInterface;
|
|
||||||
use Drupal\Core\Language\LanguageManagerInterface;
|
|
||||||
use Drupal\Core\Routing\RedirectDestinationTrait;
|
|
||||||
use Drupal\Core\TypedData\TranslatableInterface;
|
|
||||||
use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
|
|
||||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
|
||||||
use Drupal\views\Plugin\views\field\FieldPluginBase;
|
|
||||||
use Drupal\views\Plugin\views\field\UncacheableFieldHandlerTrait;
|
|
||||||
use Drupal\views\Plugin\views\style\Table;
|
|
||||||
use Drupal\views\ResultRow;
|
|
||||||
use Drupal\views\ViewExecutable;
|
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a actions-based bulk operation form element.
|
* Defines a actions-based bulk operation form element.
|
||||||
*
|
*
|
||||||
* @ViewsField("bulk_form")
|
* @ViewsField("legacy_bulk_form")
|
||||||
|
*
|
||||||
|
* @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use
|
||||||
|
* \Drupal\views\Plugin\views\field\BulkForm instead.
|
||||||
|
*
|
||||||
|
* @see https://www.drupal.org/node/2916716
|
||||||
*/
|
*/
|
||||||
class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
|
class BulkForm extends ViewsBulkForm {
|
||||||
|
|
||||||
use RedirectDestinationTrait;
|
|
||||||
use UncacheableFieldHandlerTrait;
|
|
||||||
use EntityTranslationRenderTrait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The entity manager.
|
|
||||||
*
|
|
||||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
|
||||||
*/
|
|
||||||
protected $entityManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The action storage.
|
|
||||||
*
|
|
||||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
|
||||||
*/
|
|
||||||
protected $actionStorage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of actions that can be executed.
|
|
||||||
*
|
|
||||||
* @var \Drupal\system\ActionConfigEntityInterface[]
|
|
||||||
*/
|
|
||||||
protected $actions = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The language manager.
|
|
||||||
*
|
|
||||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
|
||||||
*/
|
|
||||||
protected $languageManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new BulkForm object.
|
|
||||||
*
|
|
||||||
* @param array $configuration
|
|
||||||
* A configuration array containing information about the plugin instance.
|
|
||||||
* @param string $plugin_id
|
|
||||||
* The plugin ID for the plugin instance.
|
|
||||||
* @param mixed $plugin_definition
|
|
||||||
* The plugin implementation definition.
|
|
||||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
|
||||||
* The entity manager.
|
|
||||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
|
||||||
* The language manager.
|
|
||||||
*/
|
|
||||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
|
|
||||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
|
||||||
|
|
||||||
$this->entityManager = $entity_manager;
|
|
||||||
$this->actionStorage = $entity_manager->getStorage('action');
|
|
||||||
$this->languageManager = $language_manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
|
||||||
return new static(
|
|
||||||
$configuration,
|
|
||||||
$plugin_id,
|
|
||||||
$plugin_definition,
|
|
||||||
$container->get('entity.manager'),
|
|
||||||
$container->get('language_manager')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
|
|
||||||
parent::init($view, $display, $options);
|
|
||||||
|
|
||||||
$entity_type = $this->getEntityType();
|
|
||||||
// Filter the actions to only include those for this entity type.
|
|
||||||
$this->actions = array_filter($this->actionStorage->loadMultiple(), function ($action) use ($entity_type) {
|
|
||||||
return $action->getType() == $entity_type;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getCacheMaxAge() {
|
|
||||||
// @todo Consider making the bulk operation form cacheable. See
|
|
||||||
// https://www.drupal.org/node/2503009.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getCacheContexts() {
|
|
||||||
return $this->languageManager->isMultilingual() ? $this->getEntityTranslationRenderer()->getCacheContexts() : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getCacheTags() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getEntityTypeId() {
|
|
||||||
return $this->getEntityType();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function getEntityManager() {
|
|
||||||
return $this->entityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function getLanguageManager() {
|
|
||||||
return $this->languageManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function getView() {
|
|
||||||
return $this->view;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function defineOptions() {
|
|
||||||
$options = parent::defineOptions();
|
|
||||||
$options['action_title'] = ['default' => $this->t('Action')];
|
|
||||||
$options['include_exclude'] = [
|
|
||||||
'default' => 'exclude',
|
|
||||||
];
|
|
||||||
$options['selected_actions'] = [
|
|
||||||
'default' => [],
|
|
||||||
];
|
|
||||||
return $options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
|
||||||
$form['action_title'] = [
|
|
||||||
'#type' => 'textfield',
|
|
||||||
'#title' => $this->t('Action title'),
|
|
||||||
'#default_value' => $this->options['action_title'],
|
|
||||||
'#description' => $this->t('The title shown above the actions dropdown.'),
|
|
||||||
];
|
|
||||||
|
|
||||||
$form['include_exclude'] = [
|
|
||||||
'#type' => 'radios',
|
|
||||||
'#title' => $this->t('Available actions'),
|
|
||||||
'#options' => [
|
|
||||||
'exclude' => $this->t('All actions, except selected'),
|
|
||||||
'include' => $this->t('Only selected actions'),
|
|
||||||
],
|
|
||||||
'#default_value' => $this->options['include_exclude'],
|
|
||||||
];
|
|
||||||
$form['selected_actions'] = [
|
|
||||||
'#type' => 'checkboxes',
|
|
||||||
'#title' => $this->t('Selected actions'),
|
|
||||||
'#options' => $this->getBulkOptions(FALSE),
|
|
||||||
'#default_value' => $this->options['selected_actions'],
|
|
||||||
];
|
|
||||||
|
|
||||||
parent::buildOptionsForm($form, $form_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
|
|
||||||
parent::validateOptionsForm($form, $form_state);
|
|
||||||
|
|
||||||
$selected_actions = $form_state->getValue(['options', 'selected_actions']);
|
|
||||||
$form_state->setValue(['options', 'selected_actions'], array_values(array_filter($selected_actions)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function preRender(&$values) {
|
|
||||||
parent::preRender($values);
|
|
||||||
|
|
||||||
// If the view is using a table style, provide a placeholder for a
|
|
||||||
// "select all" checkbox.
|
|
||||||
if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) {
|
|
||||||
// Add the tableselect css classes.
|
|
||||||
$this->options['element_label_class'] .= 'select-all';
|
|
||||||
// Hide the actual label of the field on the table header.
|
|
||||||
$this->options['label'] = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getValue(ResultRow $row, $field = NULL) {
|
|
||||||
return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Form constructor for the bulk form.
|
|
||||||
*
|
|
||||||
* @param array $form
|
|
||||||
* An associative array containing the structure of the form.
|
|
||||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
|
||||||
* The current state of the form.
|
|
||||||
*/
|
|
||||||
public function viewsForm(&$form, FormStateInterface $form_state) {
|
|
||||||
// Make sure we do not accidentally cache this form.
|
|
||||||
// @todo Evaluate this again in https://www.drupal.org/node/2503009.
|
|
||||||
$form['#cache']['max-age'] = 0;
|
|
||||||
|
|
||||||
// Add the tableselect javascript.
|
|
||||||
$form['#attached']['library'][] = 'core/drupal.tableselect';
|
|
||||||
$use_revision = array_key_exists('revision', $this->view->getQuery()->getEntityTableInfo());
|
|
||||||
|
|
||||||
// Only add the bulk form options and buttons if there are results.
|
|
||||||
if (!empty($this->view->result)) {
|
|
||||||
// Render checkboxes for all rows.
|
|
||||||
$form[$this->options['id']]['#tree'] = TRUE;
|
|
||||||
foreach ($this->view->result as $row_index => $row) {
|
|
||||||
$entity = $this->getEntityTranslation($this->getEntity($row), $row);
|
|
||||||
|
|
||||||
$form[$this->options['id']][$row_index] = [
|
|
||||||
'#type' => 'checkbox',
|
|
||||||
// We are not able to determine a main "title" for each row, so we can
|
|
||||||
// only output a generic label.
|
|
||||||
'#title' => $this->t('Update this item'),
|
|
||||||
'#title_display' => 'invisible',
|
|
||||||
'#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
|
|
||||||
'#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the form submit button label.
|
|
||||||
$form['actions']['submit']['#value'] = $this->t('Apply to selected items');
|
|
||||||
|
|
||||||
// Ensure a consistent container for filters/operations in the view header.
|
|
||||||
$form['header'] = [
|
|
||||||
'#type' => 'container',
|
|
||||||
'#weight' => -100,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Build the bulk operations action widget for the header.
|
|
||||||
// Allow themes to apply .container-inline on this separate container.
|
|
||||||
$form['header'][$this->options['id']] = [
|
|
||||||
'#type' => 'container',
|
|
||||||
];
|
|
||||||
$form['header'][$this->options['id']]['action'] = [
|
|
||||||
'#type' => 'select',
|
|
||||||
'#title' => $this->options['action_title'],
|
|
||||||
'#options' => $this->getBulkOptions(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Duplicate the form actions into the action container in the header.
|
|
||||||
$form['header'][$this->options['id']]['actions'] = $form['actions'];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Remove the default actions build array.
|
|
||||||
unset($form['actions']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the available operations for this form.
|
|
||||||
*
|
|
||||||
* @param bool $filtered
|
|
||||||
* (optional) Whether to filter actions to selected actions.
|
|
||||||
* @return array
|
|
||||||
* An associative array of operations, suitable for a select element.
|
|
||||||
*/
|
|
||||||
protected function getBulkOptions($filtered = TRUE) {
|
|
||||||
$options = [];
|
|
||||||
// Filter the action list.
|
|
||||||
foreach ($this->actions as $id => $action) {
|
|
||||||
if ($filtered) {
|
|
||||||
$in_selected = in_array($id, $this->options['selected_actions']);
|
|
||||||
// If the field is configured to include only the selected actions,
|
|
||||||
// skip actions that were not selected.
|
|
||||||
if (($this->options['include_exclude'] == 'include') && !$in_selected) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Otherwise, if the field is configured to exclude the selected
|
|
||||||
// actions, skip actions that were selected.
|
|
||||||
elseif (($this->options['include_exclude'] == 'exclude') && $in_selected) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$options[$id] = $action->label();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submit handler for the bulk form.
|
|
||||||
*
|
|
||||||
* @param array $form
|
|
||||||
* An associative array containing the structure of the form.
|
|
||||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
|
||||||
* The current state of the form.
|
|
||||||
*
|
|
||||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
|
||||||
* Thrown when the user tried to access an action without access to it.
|
|
||||||
*/
|
|
||||||
public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
|
|
||||||
if ($form_state->get('step') == 'views_form_views_form') {
|
|
||||||
// Filter only selected checkboxes. Use the actual user input rather than
|
|
||||||
// the raw form values array, since the site data may change before the
|
|
||||||
// bulk form is submitted, which can lead to data loss.
|
|
||||||
$user_input = $form_state->getUserInput();
|
|
||||||
$selected = array_filter($user_input[$this->options['id']]);
|
|
||||||
$entities = [];
|
|
||||||
$action = $this->actions[$form_state->getValue('action')];
|
|
||||||
$count = 0;
|
|
||||||
|
|
||||||
foreach ($selected as $bulk_form_key) {
|
|
||||||
$entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
|
|
||||||
|
|
||||||
// Skip execution if the user did not have access.
|
|
||||||
if (!$action->getPlugin()->access($entity, $this->view->getUser())) {
|
|
||||||
$this->drupalSetMessage($this->t('No access to execute %action on the @entity_type_label %entity_label.', [
|
|
||||||
'%action' => $action->label(),
|
|
||||||
'@entity_type_label' => $entity->getEntityType()->getLabel(),
|
|
||||||
'%entity_label' => $entity->label()
|
|
||||||
]), 'error');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$count++;
|
|
||||||
|
|
||||||
$entities[$bulk_form_key] = $entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
$action->execute($entities);
|
|
||||||
|
|
||||||
$operation_definition = $action->getPluginDefinition();
|
|
||||||
if (!empty($operation_definition['confirm_form_route_name'])) {
|
|
||||||
$options = [
|
|
||||||
'query' => $this->getDestinationArray(),
|
|
||||||
];
|
|
||||||
$form_state->setRedirect($operation_definition['confirm_form_route_name'], [], $options);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Don't display the message unless there are some elements affected and
|
|
||||||
// there is no confirmation form.
|
|
||||||
if ($count) {
|
|
||||||
drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', [
|
|
||||||
'%action' => $action->label(),
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the message to be displayed when there are no selected items.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* Message displayed when no items are selected.
|
|
||||||
*/
|
|
||||||
protected function emptySelectedMessage() {
|
|
||||||
return $this->t('No items selected.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function viewsFormValidate(&$form, FormStateInterface $form_state) {
|
|
||||||
$selected = array_filter($form_state->getValue($this->options['id']));
|
|
||||||
if (empty($selected)) {
|
|
||||||
$form_state->setErrorByName('', $this->emptySelectedMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function query() {
|
|
||||||
if ($this->languageManager->isMultilingual()) {
|
|
||||||
$this->getEntityTranslationRenderer()->query($this->query, $this->relationship);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function clickSortable() {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps drupal_set_message().
|
|
||||||
*/
|
|
||||||
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
|
|
||||||
drupal_set_message($message, $type, $repeat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates a bulk form key.
|
|
||||||
*
|
|
||||||
* This generates a key that is used as the checkbox return value when
|
|
||||||
* submitting a bulk form. This key allows the entity for the row to be loaded
|
|
||||||
* totally independently of the executed view row.
|
|
||||||
*
|
|
||||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
|
||||||
* The entity to calculate a bulk form key for.
|
|
||||||
* @param bool $use_revision
|
|
||||||
* Whether the revision id should be added to the bulk form key. This should
|
|
||||||
* be set to TRUE only if the view is listing entity revisions.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* The bulk form key representing the entity's id, language and revision (if
|
|
||||||
* applicable) as one string.
|
|
||||||
*
|
|
||||||
* @see self::loadEntityFromBulkFormKey()
|
|
||||||
*/
|
|
||||||
protected function calculateEntityBulkFormKey(EntityInterface $entity, $use_revision) {
|
|
||||||
$key_parts = [$entity->language()->getId(), $entity->id()];
|
|
||||||
|
|
||||||
if ($entity instanceof RevisionableInterface && $use_revision) {
|
|
||||||
$key_parts[] = $entity->getRevisionId();
|
|
||||||
}
|
|
||||||
|
|
||||||
// An entity ID could be an arbitrary string (although they are typically
|
|
||||||
// numeric). JSON then Base64 encoding ensures the bulk_form_key is
|
|
||||||
// safe to use in HTML, and that the key parts can be retrieved.
|
|
||||||
$key = json_encode($key_parts);
|
|
||||||
return base64_encode($key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads an entity based on a bulk form key.
|
|
||||||
*
|
|
||||||
* @param string $bulk_form_key
|
|
||||||
* The bulk form key representing the entity's id, language and revision (if
|
|
||||||
* applicable) as one string.
|
|
||||||
*
|
|
||||||
* @return \Drupal\Core\Entity\EntityInterface
|
|
||||||
* The entity loaded in the state (language, optionally revision) specified
|
|
||||||
* as part of the bulk form key.
|
|
||||||
*/
|
|
||||||
protected function loadEntityFromBulkFormKey($bulk_form_key) {
|
|
||||||
$key = base64_decode($bulk_form_key);
|
|
||||||
$key_parts = json_decode($key);
|
|
||||||
$revision_id = NULL;
|
|
||||||
|
|
||||||
// If there are 3 items, vid will be last.
|
|
||||||
if (count($key_parts) === 3) {
|
|
||||||
$revision_id = array_pop($key_parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The first two items will always be langcode and ID.
|
|
||||||
$id = array_pop($key_parts);
|
|
||||||
$langcode = array_pop($key_parts);
|
|
||||||
|
|
||||||
// Load the entity or a specific revision depending on the given key.
|
|
||||||
$storage = $this->entityManager->getStorage($this->getEntityType());
|
|
||||||
$entity = $revision_id ? $storage->loadRevision($revision_id) : $storage->load($id);
|
|
||||||
|
|
||||||
if ($entity instanceof TranslatableInterface) {
|
|
||||||
$entity = $entity->getTranslation($langcode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
namespace Drupal\user\Plugin\views\field;
|
namespace Drupal\user\Plugin\views\field;
|
||||||
|
|
||||||
use Drupal\Core\Form\FormStateInterface;
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
use Drupal\system\Plugin\views\field\BulkForm;
|
|
||||||
use Drupal\user\UserInterface;
|
use Drupal\user\UserInterface;
|
||||||
|
use Drupal\views\Plugin\views\field\BulkForm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a user operations bulk form element.
|
* Defines a user operations bulk form element.
|
||||||
|
|
|
||||||
|
|
@ -820,3 +820,20 @@ views_cache:
|
||||||
views_display_extender:
|
views_display_extender:
|
||||||
type: mapping
|
type: mapping
|
||||||
label: 'Display extender settings'
|
label: 'Display extender settings'
|
||||||
|
|
||||||
|
views_field_bulk_form:
|
||||||
|
type: views_field
|
||||||
|
label: 'Bulk operation'
|
||||||
|
mapping:
|
||||||
|
action_title:
|
||||||
|
type: label
|
||||||
|
label: 'Action title'
|
||||||
|
include_exclude:
|
||||||
|
type: string
|
||||||
|
label: 'Available actions'
|
||||||
|
selected_actions:
|
||||||
|
type: sequence
|
||||||
|
label: 'Available actions'
|
||||||
|
sequence:
|
||||||
|
type: string
|
||||||
|
label: 'Action'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,505 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\views\Plugin\views\field;
|
||||||
|
|
||||||
|
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||||
|
use Drupal\Core\Entity\EntityInterface;
|
||||||
|
use Drupal\Core\Entity\EntityManagerInterface;
|
||||||
|
use Drupal\Core\Entity\RevisionableInterface;
|
||||||
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
|
use Drupal\Core\Language\LanguageManagerInterface;
|
||||||
|
use Drupal\Core\Routing\RedirectDestinationTrait;
|
||||||
|
use Drupal\Core\TypedData\TranslatableInterface;
|
||||||
|
use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
|
||||||
|
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||||
|
use Drupal\views\Plugin\views\style\Table;
|
||||||
|
use Drupal\views\ResultRow;
|
||||||
|
use Drupal\views\ViewExecutable;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a actions-based bulk operation form element.
|
||||||
|
*
|
||||||
|
* @ViewsField("bulk_form")
|
||||||
|
*/
|
||||||
|
class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
|
||||||
|
|
||||||
|
use RedirectDestinationTrait;
|
||||||
|
use UncacheableFieldHandlerTrait;
|
||||||
|
use EntityTranslationRenderTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity manager.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||||
|
*/
|
||||||
|
protected $entityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The action storage.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||||
|
*/
|
||||||
|
protected $actionStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of actions that can be executed.
|
||||||
|
*
|
||||||
|
* @var \Drupal\system\ActionConfigEntityInterface[]
|
||||||
|
*/
|
||||||
|
protected $actions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The language manager.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||||
|
*/
|
||||||
|
protected $languageManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new BulkForm object.
|
||||||
|
*
|
||||||
|
* @param array $configuration
|
||||||
|
* A configuration array containing information about the plugin instance.
|
||||||
|
* @param string $plugin_id
|
||||||
|
* The plugin ID for the plugin instance.
|
||||||
|
* @param mixed $plugin_definition
|
||||||
|
* The plugin implementation definition.
|
||||||
|
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||||
|
* The entity manager.
|
||||||
|
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||||
|
* The language manager.
|
||||||
|
*/
|
||||||
|
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
|
||||||
|
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||||
|
|
||||||
|
$this->entityManager = $entity_manager;
|
||||||
|
$this->actionStorage = $entity_manager->getStorage('action');
|
||||||
|
$this->languageManager = $language_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||||
|
return new static(
|
||||||
|
$configuration,
|
||||||
|
$plugin_id,
|
||||||
|
$plugin_definition,
|
||||||
|
$container->get('entity.manager'),
|
||||||
|
$container->get('language_manager')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
|
||||||
|
parent::init($view, $display, $options);
|
||||||
|
|
||||||
|
$entity_type = $this->getEntityType();
|
||||||
|
// Filter the actions to only include those for this entity type.
|
||||||
|
$this->actions = array_filter($this->actionStorage->loadMultiple(), function ($action) use ($entity_type) {
|
||||||
|
return $action->getType() == $entity_type;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCacheMaxAge() {
|
||||||
|
// @todo Consider making the bulk operation form cacheable. See
|
||||||
|
// https://www.drupal.org/node/2503009.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCacheContexts() {
|
||||||
|
return $this->languageManager->isMultilingual() ? $this->getEntityTranslationRenderer()->getCacheContexts() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCacheTags() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getEntityTypeId() {
|
||||||
|
return $this->getEntityType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function getEntityManager() {
|
||||||
|
return $this->entityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function getLanguageManager() {
|
||||||
|
return $this->languageManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function getView() {
|
||||||
|
return $this->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function defineOptions() {
|
||||||
|
$options = parent::defineOptions();
|
||||||
|
$options['action_title'] = ['default' => $this->t('Action')];
|
||||||
|
$options['include_exclude'] = [
|
||||||
|
'default' => 'exclude',
|
||||||
|
];
|
||||||
|
$options['selected_actions'] = [
|
||||||
|
'default' => [],
|
||||||
|
];
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||||
|
$form['action_title'] = [
|
||||||
|
'#type' => 'textfield',
|
||||||
|
'#title' => $this->t('Action title'),
|
||||||
|
'#default_value' => $this->options['action_title'],
|
||||||
|
'#description' => $this->t('The title shown above the actions dropdown.'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$form['include_exclude'] = [
|
||||||
|
'#type' => 'radios',
|
||||||
|
'#title' => $this->t('Available actions'),
|
||||||
|
'#options' => [
|
||||||
|
'exclude' => $this->t('All actions, except selected'),
|
||||||
|
'include' => $this->t('Only selected actions'),
|
||||||
|
],
|
||||||
|
'#default_value' => $this->options['include_exclude'],
|
||||||
|
];
|
||||||
|
$form['selected_actions'] = [
|
||||||
|
'#type' => 'checkboxes',
|
||||||
|
'#title' => $this->t('Selected actions'),
|
||||||
|
'#options' => $this->getBulkOptions(FALSE),
|
||||||
|
'#default_value' => $this->options['selected_actions'],
|
||||||
|
];
|
||||||
|
|
||||||
|
parent::buildOptionsForm($form, $form_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
|
||||||
|
parent::validateOptionsForm($form, $form_state);
|
||||||
|
|
||||||
|
$selected_actions = $form_state->getValue(['options', 'selected_actions']);
|
||||||
|
$form_state->setValue(['options', 'selected_actions'], array_values(array_filter($selected_actions)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function preRender(&$values) {
|
||||||
|
parent::preRender($values);
|
||||||
|
|
||||||
|
// If the view is using a table style, provide a placeholder for a
|
||||||
|
// "select all" checkbox.
|
||||||
|
if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) {
|
||||||
|
// Add the tableselect css classes.
|
||||||
|
$this->options['element_label_class'] .= 'select-all';
|
||||||
|
// Hide the actual label of the field on the table header.
|
||||||
|
$this->options['label'] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getValue(ResultRow $row, $field = NULL) {
|
||||||
|
return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form constructor for the bulk form.
|
||||||
|
*
|
||||||
|
* @param array $form
|
||||||
|
* An associative array containing the structure of the form.
|
||||||
|
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||||
|
* The current state of the form.
|
||||||
|
*/
|
||||||
|
public function viewsForm(&$form, FormStateInterface $form_state) {
|
||||||
|
// Make sure we do not accidentally cache this form.
|
||||||
|
// @todo Evaluate this again in https://www.drupal.org/node/2503009.
|
||||||
|
$form['#cache']['max-age'] = 0;
|
||||||
|
|
||||||
|
// Add the tableselect javascript.
|
||||||
|
$form['#attached']['library'][] = 'core/drupal.tableselect';
|
||||||
|
$use_revision = array_key_exists('revision', $this->view->getQuery()->getEntityTableInfo());
|
||||||
|
|
||||||
|
// Only add the bulk form options and buttons if there are results.
|
||||||
|
if (!empty($this->view->result)) {
|
||||||
|
// Render checkboxes for all rows.
|
||||||
|
$form[$this->options['id']]['#tree'] = TRUE;
|
||||||
|
foreach ($this->view->result as $row_index => $row) {
|
||||||
|
$entity = $this->getEntityTranslation($this->getEntity($row), $row);
|
||||||
|
|
||||||
|
$form[$this->options['id']][$row_index] = [
|
||||||
|
'#type' => 'checkbox',
|
||||||
|
// We are not able to determine a main "title" for each row, so we can
|
||||||
|
// only output a generic label.
|
||||||
|
'#title' => $this->t('Update this item'),
|
||||||
|
'#title_display' => 'invisible',
|
||||||
|
'#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
|
||||||
|
'#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the form submit button label.
|
||||||
|
$form['actions']['submit']['#value'] = $this->t('Apply to selected items');
|
||||||
|
|
||||||
|
// Ensure a consistent container for filters/operations in the view header.
|
||||||
|
$form['header'] = [
|
||||||
|
'#type' => 'container',
|
||||||
|
'#weight' => -100,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Build the bulk operations action widget for the header.
|
||||||
|
// Allow themes to apply .container-inline on this separate container.
|
||||||
|
$form['header'][$this->options['id']] = [
|
||||||
|
'#type' => 'container',
|
||||||
|
];
|
||||||
|
$form['header'][$this->options['id']]['action'] = [
|
||||||
|
'#type' => 'select',
|
||||||
|
'#title' => $this->options['action_title'],
|
||||||
|
'#options' => $this->getBulkOptions(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Duplicate the form actions into the action container in the header.
|
||||||
|
$form['header'][$this->options['id']]['actions'] = $form['actions'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Remove the default actions build array.
|
||||||
|
unset($form['actions']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the available operations for this form.
|
||||||
|
*
|
||||||
|
* @param bool $filtered
|
||||||
|
* (optional) Whether to filter actions to selected actions.
|
||||||
|
* @return array
|
||||||
|
* An associative array of operations, suitable for a select element.
|
||||||
|
*/
|
||||||
|
protected function getBulkOptions($filtered = TRUE) {
|
||||||
|
$options = [];
|
||||||
|
// Filter the action list.
|
||||||
|
foreach ($this->actions as $id => $action) {
|
||||||
|
if ($filtered) {
|
||||||
|
$in_selected = in_array($id, $this->options['selected_actions']);
|
||||||
|
// If the field is configured to include only the selected actions,
|
||||||
|
// skip actions that were not selected.
|
||||||
|
if (($this->options['include_exclude'] == 'include') && !$in_selected) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Otherwise, if the field is configured to exclude the selected
|
||||||
|
// actions, skip actions that were selected.
|
||||||
|
elseif (($this->options['include_exclude'] == 'exclude') && $in_selected) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$options[$id] = $action->label();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit handler for the bulk form.
|
||||||
|
*
|
||||||
|
* @param array $form
|
||||||
|
* An associative array containing the structure of the form.
|
||||||
|
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||||
|
* The current state of the form.
|
||||||
|
*
|
||||||
|
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||||
|
* Thrown when the user tried to access an action without access to it.
|
||||||
|
*/
|
||||||
|
public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
|
||||||
|
if ($form_state->get('step') == 'views_form_views_form') {
|
||||||
|
// Filter only selected checkboxes. Use the actual user input rather than
|
||||||
|
// the raw form values array, since the site data may change before the
|
||||||
|
// bulk form is submitted, which can lead to data loss.
|
||||||
|
$user_input = $form_state->getUserInput();
|
||||||
|
$selected = array_filter($user_input[$this->options['id']]);
|
||||||
|
$entities = [];
|
||||||
|
$action = $this->actions[$form_state->getValue('action')];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
foreach ($selected as $bulk_form_key) {
|
||||||
|
$entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
|
||||||
|
|
||||||
|
// Skip execution if the user did not have access.
|
||||||
|
if (!$action->getPlugin()->access($entity, $this->view->getUser())) {
|
||||||
|
$this->drupalSetMessage($this->t('No access to execute %action on the @entity_type_label %entity_label.', [
|
||||||
|
'%action' => $action->label(),
|
||||||
|
'@entity_type_label' => $entity->getEntityType()->getLabel(),
|
||||||
|
'%entity_label' => $entity->label()
|
||||||
|
]), 'error');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$count++;
|
||||||
|
|
||||||
|
$entities[$bulk_form_key] = $entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action->execute($entities);
|
||||||
|
|
||||||
|
$operation_definition = $action->getPluginDefinition();
|
||||||
|
if (!empty($operation_definition['confirm_form_route_name'])) {
|
||||||
|
$options = [
|
||||||
|
'query' => $this->getDestinationArray(),
|
||||||
|
];
|
||||||
|
$form_state->setRedirect($operation_definition['confirm_form_route_name'], [], $options);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Don't display the message unless there are some elements affected and
|
||||||
|
// there is no confirmation form.
|
||||||
|
if ($count) {
|
||||||
|
drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', [
|
||||||
|
'%action' => $action->label(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the message to be displayed when there are no selected items.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* Message displayed when no items are selected.
|
||||||
|
*/
|
||||||
|
protected function emptySelectedMessage() {
|
||||||
|
return $this->t('No items selected.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function viewsFormValidate(&$form, FormStateInterface $form_state) {
|
||||||
|
$selected = array_filter($form_state->getValue($this->options['id']));
|
||||||
|
if (empty($selected)) {
|
||||||
|
$form_state->setErrorByName('', $this->emptySelectedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function query() {
|
||||||
|
if ($this->languageManager->isMultilingual()) {
|
||||||
|
$this->getEntityTranslationRenderer()->query($this->query, $this->relationship);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function clickSortable() {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps drupal_set_message().
|
||||||
|
*/
|
||||||
|
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
|
||||||
|
drupal_set_message($message, $type, $repeat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a bulk form key.
|
||||||
|
*
|
||||||
|
* This generates a key that is used as the checkbox return value when
|
||||||
|
* submitting a bulk form. This key allows the entity for the row to be loaded
|
||||||
|
* totally independently of the executed view row.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||||
|
* The entity to calculate a bulk form key for.
|
||||||
|
* @param bool $use_revision
|
||||||
|
* Whether the revision id should be added to the bulk form key. This should
|
||||||
|
* be set to TRUE only if the view is listing entity revisions.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The bulk form key representing the entity's id, language and revision (if
|
||||||
|
* applicable) as one string.
|
||||||
|
*
|
||||||
|
* @see self::loadEntityFromBulkFormKey()
|
||||||
|
*/
|
||||||
|
protected function calculateEntityBulkFormKey(EntityInterface $entity, $use_revision) {
|
||||||
|
$key_parts = [$entity->language()->getId(), $entity->id()];
|
||||||
|
|
||||||
|
if ($entity instanceof RevisionableInterface && $use_revision) {
|
||||||
|
$key_parts[] = $entity->getRevisionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// An entity ID could be an arbitrary string (although they are typically
|
||||||
|
// numeric). JSON then Base64 encoding ensures the bulk_form_key is
|
||||||
|
// safe to use in HTML, and that the key parts can be retrieved.
|
||||||
|
$key = json_encode($key_parts);
|
||||||
|
return base64_encode($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an entity based on a bulk form key.
|
||||||
|
*
|
||||||
|
* @param string $bulk_form_key
|
||||||
|
* The bulk form key representing the entity's id, language and revision (if
|
||||||
|
* applicable) as one string.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Entity\EntityInterface
|
||||||
|
* The entity loaded in the state (language, optionally revision) specified
|
||||||
|
* as part of the bulk form key.
|
||||||
|
*/
|
||||||
|
protected function loadEntityFromBulkFormKey($bulk_form_key) {
|
||||||
|
$key = base64_decode($bulk_form_key);
|
||||||
|
$key_parts = json_decode($key);
|
||||||
|
$revision_id = NULL;
|
||||||
|
|
||||||
|
// If there are 3 items, vid will be last.
|
||||||
|
if (count($key_parts) === 3) {
|
||||||
|
$revision_id = array_pop($key_parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first two items will always be langcode and ID.
|
||||||
|
$id = array_pop($key_parts);
|
||||||
|
$langcode = array_pop($key_parts);
|
||||||
|
|
||||||
|
// Load the entity or a specific revision depending on the given key.
|
||||||
|
$storage = $this->entityManager->getStorage($this->getEntityType());
|
||||||
|
$entity = $revision_id ? $storage->loadRevision($revision_id) : $storage->load($id);
|
||||||
|
|
||||||
|
if ($entity instanceof TranslatableInterface) {
|
||||||
|
$entity = $entity->getTranslation($langcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Test fixture.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Drupal\Core\Database\Database;
|
||||||
|
use Drupal\Core\Serialization\Yaml;
|
||||||
|
|
||||||
|
$connection = Database::getConnection();
|
||||||
|
|
||||||
|
$connection->insert('config')
|
||||||
|
->fields([
|
||||||
|
'collection' => '',
|
||||||
|
'name' => 'views.view.legacy_bulk_form',
|
||||||
|
'data' => serialize(Yaml::decode(file_get_contents(__DIR__ . '/views.view.legacy_bulk_form.yml'))),
|
||||||
|
])
|
||||||
|
->execute();
|
||||||
|
|
@ -0,0 +1,243 @@
|
||||||
|
uuid: 67e001ab-bf26-4317-98a0-9ef7c8e6773a
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- node
|
||||||
|
- system
|
||||||
|
- user
|
||||||
|
id: legacy_bulk_form
|
||||||
|
label: 'legacy bulk form'
|
||||||
|
module: views
|
||||||
|
description: ''
|
||||||
|
tag: ''
|
||||||
|
base_table: node_field_data
|
||||||
|
base_field: nid
|
||||||
|
core: 8.x
|
||||||
|
display:
|
||||||
|
default:
|
||||||
|
display_plugin: default
|
||||||
|
id: default
|
||||||
|
display_title: Master
|
||||||
|
position: 0
|
||||||
|
display_options:
|
||||||
|
access:
|
||||||
|
type: perm
|
||||||
|
options:
|
||||||
|
perm: 'access content'
|
||||||
|
cache:
|
||||||
|
type: tag
|
||||||
|
options: { }
|
||||||
|
query:
|
||||||
|
type: views_query
|
||||||
|
options:
|
||||||
|
disable_sql_rewrite: false
|
||||||
|
distinct: false
|
||||||
|
replica: false
|
||||||
|
query_comment: ''
|
||||||
|
query_tags: { }
|
||||||
|
exposed_form:
|
||||||
|
type: basic
|
||||||
|
options:
|
||||||
|
submit_button: Apply
|
||||||
|
reset_button: false
|
||||||
|
reset_button_label: Reset
|
||||||
|
exposed_sorts_label: 'Sort by'
|
||||||
|
expose_sort_order: true
|
||||||
|
sort_asc_label: Asc
|
||||||
|
sort_desc_label: Desc
|
||||||
|
pager:
|
||||||
|
type: mini
|
||||||
|
options:
|
||||||
|
items_per_page: 10
|
||||||
|
offset: 0
|
||||||
|
id: 0
|
||||||
|
total_pages: null
|
||||||
|
expose:
|
||||||
|
items_per_page: false
|
||||||
|
items_per_page_label: 'Items per page'
|
||||||
|
items_per_page_options: '5, 10, 25, 50'
|
||||||
|
items_per_page_options_all: false
|
||||||
|
items_per_page_options_all_label: '- All -'
|
||||||
|
offset: false
|
||||||
|
offset_label: Offset
|
||||||
|
tags:
|
||||||
|
previous: ‹‹
|
||||||
|
next: ››
|
||||||
|
style:
|
||||||
|
type: default
|
||||||
|
options:
|
||||||
|
grouping: { }
|
||||||
|
row_class: ''
|
||||||
|
default_row_class: true
|
||||||
|
uses_fields: false
|
||||||
|
row:
|
||||||
|
type: fields
|
||||||
|
options:
|
||||||
|
inline: { }
|
||||||
|
separator: ''
|
||||||
|
hide_empty: false
|
||||||
|
default_field_elements: true
|
||||||
|
fields:
|
||||||
|
nid:
|
||||||
|
id: nid
|
||||||
|
table: node_field_data
|
||||||
|
field: nid
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: ''
|
||||||
|
label: ''
|
||||||
|
exclude: false
|
||||||
|
alter:
|
||||||
|
alter_text: false
|
||||||
|
text: ''
|
||||||
|
make_link: false
|
||||||
|
path: ''
|
||||||
|
absolute: false
|
||||||
|
external: false
|
||||||
|
replace_spaces: false
|
||||||
|
path_case: none
|
||||||
|
trim_whitespace: false
|
||||||
|
alt: ''
|
||||||
|
rel: ''
|
||||||
|
link_class: ''
|
||||||
|
prefix: ''
|
||||||
|
suffix: ''
|
||||||
|
target: ''
|
||||||
|
nl2br: false
|
||||||
|
max_length: 0
|
||||||
|
word_boundary: true
|
||||||
|
ellipsis: true
|
||||||
|
more_link: false
|
||||||
|
more_link_text: ''
|
||||||
|
more_link_path: ''
|
||||||
|
strip_tags: false
|
||||||
|
trim: false
|
||||||
|
preserve_tags: ''
|
||||||
|
html: false
|
||||||
|
element_type: ''
|
||||||
|
element_class: ''
|
||||||
|
element_label_type: ''
|
||||||
|
element_label_class: ''
|
||||||
|
element_label_colon: false
|
||||||
|
element_wrapper_type: ''
|
||||||
|
element_wrapper_class: ''
|
||||||
|
element_default_classes: true
|
||||||
|
empty: ''
|
||||||
|
hide_empty: false
|
||||||
|
empty_zero: false
|
||||||
|
hide_alter_empty: true
|
||||||
|
click_sort_column: value
|
||||||
|
type: number_integer
|
||||||
|
settings:
|
||||||
|
thousand_separator: ''
|
||||||
|
prefix_suffix: true
|
||||||
|
group_column: value
|
||||||
|
group_columns: { }
|
||||||
|
group_rows: true
|
||||||
|
delta_limit: 0
|
||||||
|
delta_offset: 0
|
||||||
|
delta_reversed: false
|
||||||
|
delta_first_last: false
|
||||||
|
multi_type: separator
|
||||||
|
separator: ', '
|
||||||
|
field_api_classes: false
|
||||||
|
entity_type: node
|
||||||
|
entity_field: nid
|
||||||
|
plugin_id: field
|
||||||
|
node_bulk_form:
|
||||||
|
id: node_bulk_form
|
||||||
|
table: node
|
||||||
|
field: node_bulk_form
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: ''
|
||||||
|
label: ''
|
||||||
|
exclude: false
|
||||||
|
alter:
|
||||||
|
alter_text: false
|
||||||
|
text: ''
|
||||||
|
make_link: false
|
||||||
|
path: ''
|
||||||
|
absolute: false
|
||||||
|
external: false
|
||||||
|
replace_spaces: false
|
||||||
|
path_case: none
|
||||||
|
trim_whitespace: false
|
||||||
|
alt: ''
|
||||||
|
rel: ''
|
||||||
|
link_class: ''
|
||||||
|
prefix: ''
|
||||||
|
suffix: ''
|
||||||
|
target: ''
|
||||||
|
nl2br: false
|
||||||
|
max_length: 0
|
||||||
|
word_boundary: true
|
||||||
|
ellipsis: true
|
||||||
|
more_link: false
|
||||||
|
more_link_text: ''
|
||||||
|
more_link_path: ''
|
||||||
|
strip_tags: false
|
||||||
|
trim: false
|
||||||
|
preserve_tags: ''
|
||||||
|
html: false
|
||||||
|
element_type: ''
|
||||||
|
element_class: ''
|
||||||
|
element_label_type: ''
|
||||||
|
element_label_class: ''
|
||||||
|
element_label_colon: false
|
||||||
|
element_wrapper_type: ''
|
||||||
|
element_wrapper_class: ''
|
||||||
|
element_default_classes: true
|
||||||
|
empty: ''
|
||||||
|
hide_empty: false
|
||||||
|
empty_zero: false
|
||||||
|
hide_alter_empty: true
|
||||||
|
action_title: Action
|
||||||
|
include_exclude: exclude
|
||||||
|
selected_actions: { }
|
||||||
|
entity_type: node
|
||||||
|
plugin_id: bulk_form
|
||||||
|
filters:
|
||||||
|
status:
|
||||||
|
value: '1'
|
||||||
|
table: node_field_data
|
||||||
|
field: status
|
||||||
|
plugin_id: boolean
|
||||||
|
entity_type: node
|
||||||
|
entity_field: status
|
||||||
|
id: status
|
||||||
|
expose:
|
||||||
|
operator: ''
|
||||||
|
group: 1
|
||||||
|
sorts:
|
||||||
|
created:
|
||||||
|
id: created
|
||||||
|
table: node_field_data
|
||||||
|
field: created
|
||||||
|
order: DESC
|
||||||
|
entity_type: node
|
||||||
|
entity_field: created
|
||||||
|
plugin_id: date
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: ''
|
||||||
|
exposed: false
|
||||||
|
expose:
|
||||||
|
label: ''
|
||||||
|
granularity: second
|
||||||
|
header: { }
|
||||||
|
footer: { }
|
||||||
|
empty: { }
|
||||||
|
relationships: { }
|
||||||
|
arguments: { }
|
||||||
|
display_extenders: { }
|
||||||
|
cache_metadata:
|
||||||
|
max-age: 0
|
||||||
|
contexts:
|
||||||
|
- 'languages:language_content'
|
||||||
|
- 'languages:language_interface'
|
||||||
|
- url.query_args
|
||||||
|
- 'user.node_grants:view'
|
||||||
|
- user.permissions
|
||||||
|
tags: { }
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\views\Functional\Update;
|
||||||
|
|
||||||
|
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||||
|
use Drupal\views\Entity\View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests Views image style dependencies update.
|
||||||
|
*
|
||||||
|
* @group views
|
||||||
|
*/
|
||||||
|
class LegacyBulkFormUpdateTest extends UpdatePathTestBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function setDatabaseDumpFiles() {
|
||||||
|
$this->databaseDumpFiles = [
|
||||||
|
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
|
||||||
|
__DIR__ . '/../../../fixtures/update/legacy-bulk-form-update.php'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the updating of dependencies for Views using the bulk_form plugin.
|
||||||
|
*/
|
||||||
|
public function testBulkFormDependencies() {
|
||||||
|
$module_dependencies = View::load('legacy_bulk_form')->getDependencies()['module'];
|
||||||
|
|
||||||
|
$this->assertTrue(in_array('system', $module_dependencies));
|
||||||
|
|
||||||
|
$this->runUpdates();
|
||||||
|
|
||||||
|
$module_dependencies = View::load('legacy_bulk_form')->getDependencies()['module'];
|
||||||
|
|
||||||
|
$this->assertFalse(in_array('system', $module_dependencies));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -256,3 +256,17 @@ function views_post_update_entity_link_url() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update dependencies for moved bulk field plugin.
|
||||||
|
*/
|
||||||
|
function views_post_update_bulk_field_moved() {
|
||||||
|
$views = View::loadMultiple();
|
||||||
|
array_walk($views, function (View $view) {
|
||||||
|
$old_dependencies = $view->getDependencies();
|
||||||
|
$new_dependencies = $view->calculateDependencies()->getDependencies();
|
||||||
|
if ($old_dependencies !== $new_dependencies) {
|
||||||
|
$view->save();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue