View source  
  <?php
namespace Drupal\views_bulk_operations\Plugin\views\field;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RedirectDestinationTrait;
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;
use Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface;
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
use Drupal\user\PrivateTempStoreFactory;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDependencyInterface, ContainerFactoryPluginInterface {
  use RedirectDestinationTrait;
  use UncacheableFieldHandlerTrait;
  
  protected $viewData;
  
  protected $actionManager;
  
  protected $actionProcessor;
  
  protected $tempStoreFactory;
  
  protected $currentUser;
  
  protected $actions = [];
  
  protected $bulkOptions;
  
  protected $userTempStore;
  
  protected $tempStoreData = [];
  
  public function __construct(array $configuration, $plugin_id, $plugin_definition, ViewsbulkOperationsViewDataInterface $viewData, ViewsBulkOperationsActionManager $actionManager, ViewsBulkOperationsActionProcessorInterface $actionProcessor, PrivateTempStoreFactory $tempStoreFactory, AccountInterface $currentUser) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->viewData = $viewData;
    $this->actionManager = $actionManager;
    $this->actionProcessor = $actionProcessor;
    $this->tempStoreFactory = $tempStoreFactory;
    $this->currentUser = $currentUser;
  }
  
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('views_bulk_operations.data'), $container
      ->get('plugin.manager.views_bulk_operations_action'), $container
      ->get('views_bulk_operations.processor'), $container
      ->get('user.private_tempstore'), $container
      ->get('current_user'));
  }
  
  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
    parent::init($view, $display, $options);
    
    $this->viewData
      ->init($view, $display, $this->options['relationship']);
    
    $this->actions = [];
    $entity_types = $this->viewData
      ->getEntityTypeIds();
    
    if (!empty($entity_types)) {
      foreach ($this->actionManager
        ->getDefinitions() as $id => $definition) {
        if (empty($definition['type']) || in_array($definition['type'], $entity_types, TRUE)) {
          $this->actions[$id] = $definition;
        }
      }
    }
    
    $tempstore_name = 'views_bulk_operations_' . $view
      ->id() . '_' . $view->current_display;
    $this->userTempStore = $this->tempStoreFactory
      ->get($tempstore_name);
    
    $this->options['form_step'] = TRUE;
  }
  
  public function getCacheMaxAge() {
    
    return 0;
  }
  
  public function getCacheContexts() {
    return [];
  }
  
  public function getCacheTags() {
    return [];
  }
  
  public function getEntity(ResultRow $row) {
    return $this->viewData
      ->getEntity($row);
  }
  
  public function query() {
  }
  
  protected function defineOptions() {
    $options = parent::defineOptions();
    $options['batch'] = [
      'default' => TRUE,
    ];
    $options['batch_size'] = [
      'default' => 10,
    ];
    $options['form_step'] = [
      'default' => TRUE,
    ];
    $options['buttons'] = [
      'default' => FALSE,
    ];
    $options['action_title'] = [
      'default' => $this
        ->t('Action'),
    ];
    $options['selected_actions'] = [
      'default' => [],
    ];
    $options['preconfiguration'] = [
      'default' => [],
    ];
    return $options;
  }
  
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    
    if (empty($this->actions)) {
      $form = [
        '#type' => 'item',
        '#title' => $this
          ->t('NOTE'),
        '#markup' => $this
          ->t('Views Bulk Operations will work only with normal entity views and contrib module views that are integrated. See /Drupal\\views_bulk_operations\\EventSubscriber\\ViewsBulkOperationsEventSubscriber class for integration best practice.'),
        '#prefix' => '<div class="scroll">',
        '#suffix' => '</div>',
      ];
      return;
    }
    $form['#attributes']['class'][] = 'views-bulk-operations-ui';
    $form['#attached']['library'][] = 'views_bulk_operations/adminUi';
    $form['batch'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Process in a batch operation'),
      '#default_value' => $this->options['batch'],
    ];
    $form['batch_size'] = [
      '#title' => $this
        ->t('Batch size'),
      '#type' => 'number',
      '#min' => 1,
      '#step' => 1,
      '#description' => $this
        ->t('Only applicable if results are processed in a batch operation.'),
      '#default_value' => $this->options['batch_size'],
    ];
    $form['form_step'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Configuration form on new page (configurable actions)'),
      '#default_value' => $this->options['form_step'],
      
      '#access' => FALSE,
    ];
    $form['buttons'] = [
      '#type' => 'checkbox',
      '#title' => $this
        ->t('Display selectable actions as buttons.'),
      '#default_value' => $this->options['buttons'],
    ];
    $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['selected_actions'] = [
      '#tree' => TRUE,
      '#type' => 'details',
      '#open' => TRUE,
      '#title' => $this
        ->t('Selected actions'),
      '#attributes' => [
        'class' => [
          'vbo-actions-widget',
        ],
      ],
    ];
    
    $form_values = $form_state
      ->getValue([
      'options',
      'selected_actions',
    ]);
    if (is_null($form_values)) {
      $selected_actions = $this->options['selected_actions'];
      $preconfiguration = $this->options['preconfiguration'];
    }
    else {
      $selected_actions = [];
      $preconfiguration = [];
      foreach ($form_values as $id => $value) {
        $selected_actions[$id] = $value['state'] ? $id : 0;
        $preconfiguration[$id] = isset($value['preconfiguration']) ? $value['preconfiguration'] : [];
      }
    }
    foreach ($this->actions as $id => $action) {
      $form['selected_actions'][$id]['state'] = [
        '#type' => 'checkbox',
        '#title' => $action['label'],
        '#default_value' => empty($selected_actions[$id]) ? 0 : 1,
        '#attributes' => [
          'class' => [
            'vbo-action-state',
          ],
        ],
      ];
      
      $form['selected_actions'][$id]['preconfiguration'] = [
        '#type' => 'fieldset',
        '#title' => $this
          ->t('Preconfiguration for "@action"', [
          '@action' => $action['label'],
        ]),
        '#attributes' => [
          'data-for' => $id,
          'style' => empty($selected_actions[$id]) ? 'display: none' : NULL,
        ],
      ];
      
      $form['selected_actions'][$id]['preconfiguration']['label_override'] = [
        '#type' => 'textfield',
        '#title' => $this
          ->t('Override label'),
        '#description' => $this
          ->t('Leave empty for the default label.'),
        '#default_value' => isset($preconfiguration[$id]['label_override']) ? $preconfiguration[$id]['label_override'] : '',
      ];
      
      if (method_exists($action['class'], 'buildPreConfigurationForm')) {
        if (!isset($preconfiguration[$id])) {
          $preconfiguration[$id] = [];
        }
        $actionObject = $this->actionManager
          ->createInstance($id);
        $form['selected_actions'][$id]['preconfiguration'] = $actionObject
          ->buildPreConfigurationForm($form['selected_actions'][$id]['preconfiguration'], $preconfiguration[$id], $form_state);
      }
    }
    parent::buildOptionsForm($form, $form_state);
  }
  
  public function submitOptionsForm(&$form, FormStateInterface $form_state) {
    $options =& $form_state
      ->getValue('options');
    foreach ($options['selected_actions'] as $id => $action) {
      if (!empty($action['state'])) {
        if (isset($action['preconfiguration'])) {
          $options['preconfiguration'][$id] = $action['preconfiguration'];
          unset($options['selected_actions'][$id]['preconfiguration']);
        }
        $options['selected_actions'][$id] = $id;
      }
      else {
        unset($options['preconfiguration'][$id]);
        $options['selected_actions'][$id] = 0;
      }
    }
    parent::submitOptionsForm($form, $form_state);
  }
  
  public function preRender(&$values) {
    parent::preRender($values);
    
    if (empty($this
      ->getBulkOptions())) {
      $this->options['element_label_class'] .= 'empty';
      $this->options['element_class'] .= 'empty';
      $this->options['element_wrapper_class'] .= 'empty';
      $this->options['label'] = '';
    }
    elseif (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) {
      
      $this->options['element_label_class'] .= 'select-all';
      
      $this->options['label'] = '';
    }
  }
  
  public function getValue(ResultRow $row, $field = NULL) {
    return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
  }
  
  public function viewsForm(array &$form, FormStateInterface $form_state) {
    
    $form['#cache']['max-age'] = 0;
    $use_revision = array_key_exists('revision', $this->view
      ->getQuery()
      ->getEntityTableInfo());
    
    if ($this->view->style_plugin instanceof Table) {
      $form['#attached']['library'][] = 'core/drupal.tableselect';
      $form['#attached']['library'][] = 'views_bulk_operations/selectAll';
    }
    
    $action_options = $this
      ->getBulkOptions();
    if (!empty($this->view->result) && !empty($action_options)) {
      
      $this->tempStoreData['entity_labels'] = [];
      
      $form[$this->options['id']]['#tree'] = TRUE;
      foreach ($this->view->result as $row_index => $row) {
        $entity = $this
          ->getEntity($row);
        $this->tempStoreData['entity_labels'][$row_index] = $entity
          ->label();
        $form[$this->options['id']][$row_index] = [
          '#type' => 'checkbox',
          
          '#title' => $this
            ->t('Update this item'),
          '#title_display' => 'invisible',
          '#default_value' => !empty($form_state
            ->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
          '#return_value' => self::calculateEntityBulkFormKey($entity, $use_revision, $row_index),
        ];
      }
      
      $form['header'] = [
        '#type' => 'container',
        '#weight' => -100,
      ];
      
      $form['header'][$this->options['id']] = [
        '#type' => 'container',
        '#attributes' => [
          'id' => 'vbo-action-form-wrapper',
        ],
      ];
      
      if ($this->options['buttons']) {
        unset($form['actions']['submit']);
        foreach ($action_options as $id => $label) {
          $form['actions'][$id] = [
            '#type' => 'submit',
            '#value' => $label,
          ];
        }
      }
      else {
        
        $form['actions']['submit']['#value'] = $this
          ->t('Apply to selected items');
        $form['header'][$this->options['id']]['action'] = [
          '#type' => 'select',
          '#title' => $this->options['action_title'],
          '#options' => [
            '' => $this
              ->t('-- Select action --'),
          ] + $action_options,
        ];
      }
      
      if (empty($this->options['form_step'])) {
        $form['header'][$this->options['id']]['action']['#ajax'] = [
          'callback' => [
            __CLASS__,
            'viewsFormAjax',
          ],
          'wrapper' => 'vbo-action-configuration-wrapper',
        ];
        $form['header'][$this->options['id']]['configuration'] = [
          '#type' => 'container',
          '#attributes' => [
            'id' => 'vbo-action-configuration-wrapper',
          ],
        ];
        $action_id = $form_state
          ->getValue('action');
        if (!empty($action_id)) {
          $action = $this->actions[$action_id];
          if ($this
            ->isConfigurable($action)) {
            $actionObject = $this->actionManager
              ->createInstance($action_id);
            $form['header'][$this->options['id']]['configuration'] += $actionObject
              ->buildConfigurationForm($form['header'][$this->options['id']]['configuration'], $form_state);
            $form['header'][$this->options['id']]['configuration']['#config_included'] = TRUE;
          }
        }
      }
      
      $show_all_selector = FALSE;
      if (!empty($this->view->pager) && method_exists($this->view->pager, 'hasMoreRecords')) {
        $show_all_selector = $this->view->pager
          ->getCurrentPage() > 0 || $this->view->pager
          ->hasMoreRecords();
      }
      $this->tempStoreData['total_results'] = $this->viewData
        ->getTotalResults();
      if ($show_all_selector) {
        $form['header'][$this->options['id']]['select_all'] = [
          '#type' => 'checkbox',
          '#title' => $this
            ->t('Select all@count results in this view', [
            '@count' => $this->tempStoreData['total_results'] ? ' ' . $this->tempStoreData['total_results'] : '',
          ]),
          '#attributes' => [
            'class' => [
              'vbo-select-all',
            ],
          ],
        ];
      }
      
      $form['header'][$this->options['id']]['actions'] = $form['actions'];
    }
    else {
      
      unset($form['actions']);
    }
  }
  
  public static function viewsFormAjax(array $form, FormStateInterface $form_state) {
    $trigger = $form_state
      ->getTriggeringElement();
    $plugin_id = $trigger['#array_parents'][1];
    return $form['header'][$plugin_id]['configuration'];
  }
  
  protected function getBulkOptions() {
    if (!isset($this->bulkOptions)) {
      $this->bulkOptions = [];
      foreach ($this->actions as $id => $definition) {
        
        if (!in_array($id, $this->options['selected_actions'], TRUE)) {
          continue;
        }
        
        if (!empty($definition['requirements']['_permission']) && !$this->currentUser
          ->hasPermission($definition['requirements']['_permission'])) {
          continue;
        }
        
        if (!empty($this->options['preconfiguration'][$id]['label_override'])) {
          $this->bulkOptions[$id] = $this->options['preconfiguration'][$id]['label_override'];
        }
        else {
          $this->bulkOptions[$id] = $definition['label'];
        }
      }
    }
    return $this->bulkOptions;
  }
  
  public function viewsFormSubmit(array &$form, FormStateInterface $form_state) {
    if ($form_state
      ->get('step') == 'views_form_views_form') {
      $action_id = $form_state
        ->getValue('action');
      $action = $this->actions[$action_id];
      $this->tempStoreData += [
        'action_id' => $action_id,
        'action_label' => empty($this->options['preconfiguration'][$action_id]['label_override']) ? (string) $action['label'] : $this->options['preconfiguration'][$action_id]['label_override'],
        'relationship_id' => $this->options['relationship'],
        'preconfiguration' => isset($this->options['preconfiguration'][$action_id]) ? $this->options['preconfiguration'][$action_id] : [],
        'list' => [],
        'view_id' => $this->view
          ->id(),
        'display_id' => $this->view->current_display,
        'batch' => $this->options['batch'],
        'arguments' => $this->view->args,
        'exposed_input' => $this->view
          ->getExposedInput(),
      ];
      
      if (!empty($action['pass_view'])) {
        $this->tempStoreData['current_page'] = 0;
        if (!empty($this->view->pager) && method_exists($this->view->pager, 'getCurrentPage')) {
          $this->tempStoreData['current_page'] = $this->view->pager
            ->getCurrentPage();
        }
      }
      if (!$form_state
        ->getValue('select_all')) {
        $selected = array_filter($form_state
          ->getValue($this->options['id']));
        $selected_indexes = [];
        foreach ($selected as $bulk_form_key) {
          $item = json_decode(base64_decode($bulk_form_key));
          $this->tempStoreData['list'][] = $item;
          $selected_indexes[] = $item[0];
        }
        
        $this->tempStoreData['entity_labels'] = array_filter($this->tempStoreData['entity_labels'], function ($key) use ($selected_indexes) {
          return in_array($key, $selected_indexes, TRUE);
        }, ARRAY_FILTER_USE_KEY);
      }
      else {
        $this->tempStoreData['entity_labels'] = [];
      }
      $configurable = $this
        ->isConfigurable($action);
      
      if ($configurable && empty($this->options['form_step'])) {
        $actionObject = $this->actionManager
          ->createInstance($action_id);
        if (method_exists($actionObject, 'submitConfigurationForm')) {
          $actionObject
            ->submitConfigurationForm($form, $form_state);
          $this->tempStoreData['configuration'] = $actionObject
            ->getConfiguration();
        }
        else {
          $form_state
            ->cleanValues();
          $this->tempStoreData['configuration'] = $form_state
            ->getValues();
        }
      }
      
      if ($this->options['form_step'] && $configurable) {
        $redirect_route = 'views_bulk_operations.execute_configurable';
      }
      elseif ($this->options['batch']) {
        if (!empty($action['confirm_form_route_name'])) {
          $redirect_route = $action['confirm_form_route_name'];
        }
        else {
          $redirect_route = 'views_bulk_operations.execute_batch';
        }
      }
      elseif (!empty($action['confirm_form_route_name'])) {
        $redirect_route = $action['confirm_form_route_name'];
      }
      
      if (!empty($redirect_route)) {
        $this->tempStoreData['batch_size'] = $this->options['batch_size'];
        $this->tempStoreData['redirect_url'] = Url::createFromRequest(\Drupal::request());
        $this->userTempStore
          ->set($this->currentUser
          ->id(), $this->tempStoreData);
        $form_state
          ->setRedirect($redirect_route, [
          'view_id' => $this->view
            ->id(),
          'display_id' => $this->view->current_display,
        ]);
      }
      else {
        $this->actionProcessor
          ->executeProcessing($this->tempStoreData, $this->view);
      }
    }
  }
  
  public function viewsFormValidate(&$form, FormStateInterface $form_state) {
    if ($this->options['buttons']) {
      $trigger = $form_state
        ->getTriggeringElement();
      $action_id = end($trigger['#parents']);
      $form_state
        ->setValue('action', $action_id);
    }
    if (empty($form_state
      ->getValue('action'))) {
      $form_state
        ->setErrorByName('action', $this
        ->t('Please select an action to perform.'));
    }
    
    if (!isset($this->actions[$form_state
      ->getValue('action')])) {
      $form_state
        ->setErrorByName('action', $this
        ->t('Form error occurred, please try again.'));
    }
    if (!$form_state
      ->getValue('select_all')) {
      $selected = array_filter($form_state
        ->getValue($this->options['id']));
      if (empty($selected)) {
        $form_state
          ->setErrorByName('', $this
          ->t('No items selected.'));
      }
    }
    
    if (empty($this->options['form_step']) && !empty($form['header'][$this->options['id']]['configuration']['#config_included'])) {
      $action_id = $form_state
        ->getValue('action');
      $action = $this->actions[$action_id];
      if (method_exists($action['class'], 'validateConfigurationForm')) {
        $actionObject = $this->actionManager
          ->createInstance($action_id);
        $actionObject
          ->validateConfigurationForm($form['header'][$this->options['id']]['configuration'], $form_state);
      }
    }
  }
  
  public function clickSortable() {
    return FALSE;
  }
  
  protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
    drupal_set_message($message, $type, $repeat);
  }
  
  public static function calculateEntityBulkFormKey(EntityInterface $entity, $use_revision, $row_index) {
    $key_parts = [
      $row_index,
      $entity
        ->language()
        ->getId(),
      $entity
        ->getEntityTypeId(),
      $entity
        ->id(),
    ];
    if ($entity instanceof RevisionableInterface && $use_revision) {
      $key_parts[] = $entity
        ->getRevisionId();
    }
    
    $key = json_encode($key_parts);
    return base64_encode($key);
  }
  
  protected function isConfigurable($action) {
    return in_array('Drupal\\Core\\Plugin\\PluginFormInterface', class_implements($action['class']), TRUE) || method_exists($action['class'], 'buildConfigurationForm');
  }
}