Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
15.38% |
4 / 26 |
CRAP | |
10.71% |
15 / 140 |
| BulkForm | |
0.00% |
0 / 1 |
|
15.38% |
4 / 26 |
1829.45 | |
10.71% |
15 / 140 |
| __construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
| create | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| init | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| anonymous function | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| getCacheMaxAge | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| getCacheContexts | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 1 |
|||
| getCacheTags | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| getEntityTypeId | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| getEntityManager | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| getLanguageManager | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| getView | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| defineOptions | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
| buildOptionsForm | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 18 |
|||
| validateOptionsForm | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 4 |
|||
| preRender | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 5 |
|||
| getValue | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| viewsForm | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 23 |
|||
| getBulkOptions | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 10 |
|||
| viewsFormSubmit | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 26 |
|||
| emptySelectedMessage | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| viewsFormValidate | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
| query | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
| clickSortable | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| drupalSetMessage | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| calculateEntityBulkFormKey | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 5 |
|||
| loadEntityFromBulkFormKey | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 12 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\system\Plugin\views\field\BulkForm. | |
| */ | |
| namespace Drupal\system\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\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. | |
| * | |
| * @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 = array(); | |
| /** | |
| * 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'] = array('default' => $this->t('With selection')); | |
| $options['include_exclude'] = array( | |
| 'default' => 'exclude', | |
| ); | |
| $options['selected_actions'] = array( | |
| 'default' => array(), | |
| ); | |
| return $options; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function buildOptionsForm(&$form, FormStateInterface $form_state) { | |
| $form['action_title'] = array( | |
| '#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'] = array( | |
| '#type' => 'radios', | |
| '#title' => $this->t('Available actions'), | |
| '#options' => array( | |
| 'exclude' => $this->t('All actions, except selected'), | |
| 'include' => $this->t('Only selected actions'), | |
| ), | |
| '#default_value' => $this->options['include_exclude'], | |
| ); | |
| $form['selected_actions'] = array( | |
| '#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(array('options', 'selected_actions')); | |
| $form_state->setValue(array('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] = array( | |
| '#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'); | |
| // Ensure a consistent container for filters/operations in the view header. | |
| $form['header'] = array( | |
| '#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']] = array( | |
| '#type' => 'container', | |
| ); | |
| $form['header'][$this->options['id']]['action'] = array( | |
| '#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 = array(); | |
| // 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. | |
| $selected = array_filter($form_state->getValue($this->options['id'])); | |
| $entities = array(); | |
| $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 = array( | |
| 'query' => $this->getDestinationArray(), | |
| ); | |
| $form_state->setRedirect($operation_definition['confirm_form_route_name'], array(), $options); | |
| } | |
| else { | |
| // Don't display the message unless there are some elements affected and | |
| // there is no confirmation form. | |
| $count = count(array_filter($form_state->getValue($this->options['id']))); | |
| if ($count) { | |
| drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', array( | |
| '%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; | |
| } | |
| } |