Newer
Older
Bojan Živanović
committed
* @file
* Allows operations to be performed on items selected in a view.
*/
// Access operations.
define('VBO_ACCESS_OP_VIEW', 0x01);
define('VBO_ACCESS_OP_UPDATE', 0x02);
define('VBO_ACCESS_OP_CREATE', 0x04);
define('VBO_ACCESS_OP_DELETE', 0x08);
Bojan Živanović
committed
/**
* Implementation of hook_cron_queue_info().
*/
function views_bulk_operations_cron_queue_info() {
return array(
'views_bulk_operations' => array(
'worker callback' => '_views_bulk_operations_queue_process',
'time' => 30,
),
);
}
* Implements hook_views_api().
function views_bulk_operations_views_api() {
return array(
Bojan Živanović
committed
'api' => 3,
'path' => drupal_get_path('module', 'views_bulk_operations') . '/views',
}
Karim Ratib
committed
$themes = array(
Bojan Živanović
committed
'views_bulk_operations_select_all' => array(
'variables' => array('view' => NULL),
),
Karim Ratib
committed
'views_bulk_operations_confirmation' => array(
'variables' => array('rows' => NULL, 'vbo' => NULL),
Karim Ratib
committed
),
$files = file_scan_directory(drupal_get_path('module', 'views_bulk_operations') . '/actions', '/\.action\.inc$/');
Karim Ratib
committed
if ($files) foreach ($files as $file) {
$action_theme_fn = 'views_bulk_operations_'. str_replace('.', '_', basename($file->filename, '.inc')).'_theme';
if (function_exists($action_theme_fn)) {
$themes += call_user_func($action_theme_fn);
}
}
return $themes;
Bojan Živanović
committed
function views_bulk_operations_init() {
// Reset selection if we're not in the view anymore.
if (isset($_SESSION) && !isset($_SESSION['vbo_values'][$_GET['q']])) {
unset($_SESSION['vbo_values']);
Karim Ratib
committed
}
Bojan Živanović
committed
// Automatically include the action files.
$files = file_scan_directory(drupal_get_path('module', 'views_bulk_operations') . '/actions', '/\.action\.inc$/');
Bojan Živanović
committed
if ($files) {
foreach ($files as $file) {
require_once($file->uri);
Bojan Živanović
committed
}
}
/**
* Gets the VBO field if it exists on the passed-in view.
*
* @return
* The field object if found. Otherwise, FALSE.
*/
function _views_bulk_operations_get_field($view) {
foreach ($view->field as $field_name => $field) {
if ($field instanceof views_bulk_operations_handler_field_operations) {
Bojan Živanović
committed
// Add in the view object for convenience.
$field->view = $view;
return $field;
Bojan Živanović
committed
return FALSE;
Bojan Živanović
committed
/**
* Implements hook_views_form_substitutions().
*/
function views_bulk_operations_views_form_substitutions() {
// Views check_plains the column label, so VBO needs to do the same
// in order for the replace operation to succeed.
$select_all_placeholder = check_plain('<!--views-bulk-operations-select-all-->');
$select_all = array(
'#type' => 'checkbox',
'#default_value' => FALSE,
'#attributes' => array('class' => array('vbo-select-all')),
);
return array(
$select_all_placeholder => drupal_render($select_all),
);
}
Bojan Živanović
committed
* Implements hook_form_alter().
Bojan Živanović
committed
function views_bulk_operations_form_alter(&$form, &$form_state, $form_id) {
if (strpos($form_id, 'views_form_') === 0) {
Bojan Živanović
committed
$vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);
Bojan Živanović
committed
}
// Not a VBO-enabled views form.
if (empty($vbo)) {
return;
// Add VBO's custom callbacks.
$form['#validate'][] = 'views_bulk_operations_form_validate';
$form['#submit'][] = 'views_bulk_operations_form_submit';
Bojan Živanović
committed
// Quickfix for VBO & exposed filters using ajax. See http://drupal.org/node/1191928.
$query = drupal_get_query_parameters($_GET, array('q'));
$form['#action'] = url($vbo->view->get_url(), array('query' => $query));
Bojan Živanović
committed
// Add basic VBO functionality.
if ($form_state['step'] == 'views_form_views_form') {
$form = views_bulk_operations_form($form, $form_state, $vbo);
}
}
Bojan Živanović
committed
/**
* Returns the 'select all' div that gets inserted below the table header row
* or above the view results (depending on the style plugin), providing a choice
* between selecting items on the current page, and on all pages.
*
* The actual insertion is done by JS, matching the degradation behavior
* of Drupal core (no JS - no select all).
*/
function theme_views_bulk_operations_select_all($variables) {
$view = $variables['view'];
$form = array();
if ($view->style_plugin instanceof views_plugin_style_table) {
Bojan Živanović
committed
// Table displays only get the "select all" markup if they are paginated.
if (count($view->result) == $view->total_rows) {
return '';
}
$this_page_count = format_plural(count($view->result), '1 row', '@count rows');
Bojan Živanović
committed
$this_page = t('Selected <strong>!row_count</strong> in this page.', array('!row_count' => $this_page_count));
$all_pages_count = format_plural($view->total_rows, '1 row', '@count rows');
Bojan Živanović
committed
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
$all_pages = t('Selected <strong>!row_count</strong> in this view.', array('!row_count' => $all_pages_count));
$form['select_all_pages'] = array(
'#type' => 'button',
'#attributes' => array('class' => array('vbo-select-all-pages')),
'#value' => t('Select all !row_count in this view.', array('!row_count' => $all_pages_count)),
'#prefix' => '<span class="vbo-this-page">' . $this_page . ' ',
'#suffix' => '</span>',
);
$form['select_this_page'] = array(
'#type' => 'button',
'#attributes' => array('class' => array('vbo-select-this-page')),
'#value' => t('Select only !row_count in this page.', array('!row_count' => $this_page_count)),
'#prefix' => '<span class="vbo-all-pages" style="display: none">' . $all_pages . ' ',
'#suffix' => '</span>',
);
}
else {
$form['select_all'] = array(
'#type' => 'fieldset',
'#attributes' => array('class' => array('vbo-fieldset-select-all')),
);
$form['select_all']['this_page'] = array(
'#type' => 'checkbox',
'#title' => t('Select all items on this page'),
'#default_value' => '',
'#attributes' => array('class' => array('vbo-select-this-page')),
);
// If the view is paginated, offer an option to select rows on all pages.
if (count($view->result) != $view->total_rows) {
$form['select_all']['or'] = array(
'#type' => 'markup',
'#markup' => '<em>OR</em>',
);
$form['select_all']['all_pages'] = array(
'#type' => 'checkbox',
'#title' => t('Select all items on all pages'),
'#default_value' => '',
'#attributes' => array('class' => array('vbo-select-all-pages')),
);
}
}
Bojan Živanović
committed
$output = '<div class="vbo-select-all-markup">';
$output .= drupal_render($form);
$output .= '</div>';
Bojan Živanović
committed
return $output;
}
Bojan Živanović
committed
* Extend the views_form multistep form with elements for executing an operation.
Bojan Živanović
committed
function views_bulk_operations_form($form, &$form_state, $vbo) {
$form['#attached']['js'][] = drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.js';
$form['#attached']['css'][] = drupal_get_path('module', 'views_bulk_operations') . '/css/views_bulk_operations.css';
$form['#prefix'] = '<div class="vbo-views-form">';
$form['#suffix'] = '</div>';
Bojan Živanović
committed
// Force browser to reload the page if Back is hit.
if (preg_match('/msie/i', $_SERVER['HTTP_USER_AGENT'])) {
drupal_add_http_header('Cache-Control', 'no-cache'); // works for IE6+
drupal_add_http_header('Cache-Control', 'no-store'); // works for Firefox and other browsers
Bojan Živanović
committed
// If there's a session variable on this view, pre-load the old operation value.
if (isset($_SESSION['vbo_values'][$_GET['q']])) {
$default_operation = $_SESSION['vbo_values'][$_GET['q']]['operation'];
$select_all = $_SESSION['vbo_values'][$_GET['q']]['rows']['select_all'];
Karim Ratib
committed
}
Bojan Živanović
committed
$select_all = FALSE;
Karim Ratib
committed
// Set by JS to indicate that all rows on all pages are selected.
Bojan Živanović
committed
$form['select_all'] = array(
'#type' => 'hidden',
'#attributes' => array('class' => 'select-all-rows'),
Bojan Živanović
committed
'#default_value' => $select_all,
Bojan Živanović
committed
);
if (count($vbo->get_selected_operations()) == 1 && $vbo->options['vbo']['merge_single_action']) {
Bojan Živanović
committed
$form['single_operation'] = array(
Bojan Živanović
committed
'#value' => TRUE,
Bojan Živanović
committed
$ops = array_keys($vbo->get_selected_operations());
$operation = $vbo->get_operation_info($ops[0]);
$form['operation'] = array('#type' => 'value', '#value' => $ops[0]);
if ($operation['configurable']) {
$dummy_selection = array();
foreach ($vbo->view->result as $result) {
$dummy_selection[$result->{$vbo->view->base_field}] = $result;
Bojan Živanović
committed
$form += _views_bulk_operations_action_form($form, $form_state, $operation, $vbo->view, $dummy_selection, $vbo->get_operation_settings($operation));
Bojan Živanović
committed
}
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $operation['label'],
);
Bojan Živanović
committed
else {
$form['single_operation'] = array(
'#type' => 'value',
'#value' => FALSE,
);
unset($form['actions']['submit']);
$form['select'] = array(
'#type' => 'fieldset',
'#title' => t('Operations'),
'#collapsible' => TRUE,
'#attributes' => array('class' => array('container-inline')),
Bojan Živanović
committed
);
if ($vbo->options['vbo']['display_type'] == 0) {
// Create dropdown and submit button.
$form['select']['operation'] = array(
'#type' => 'select',
'#options' => array(0 => t('- Choose an operation -')) + $vbo->get_selected_operations(),
'#default_value' => $default_operation,
Bojan Živanović
committed
$form['select']['submit'] = array(
'#type' => 'submit',
'#value' => t('Execute'),
Bojan Živanović
committed
}
else {
// Create buttons for actions.
foreach ($vbo->get_selected_operations() as $md5 => $description) {
$form['select'][$md5] = array(
Bojan Živanović
committed
'#value' => $description,
'#hash' => $md5,
Bojan Živanović
committed
}
Bojan Živanović
committed
Bojan Živanović
committed
// Adds the "select all" functionality if the view has results.
// If the view is using a table style plugin, the markup gets moved to
// a table row below the header.
Bojan Živanović
committed
// If we are using radio buttons, we don't use select all at all.
Bojan Živanović
committed
if (!empty($vbo->view->result) && !$vbo->options['vbo']['force_single']) {
Bojan Živanović
committed
$form['select_all_markup'] = array(
'#type' => 'markup',
'#markup' => theme('views_bulk_operations_select_all', array('view' => $vbo->view)),
);
}
Bojan Živanović
committed
Bojan Živanović
committed
* Multistep form callback for the "configure" step.
Bojan Živanović
committed
function views_bulk_operations_config_form($form, &$form_state, $view, $output) {
Bojan Živanović
committed
$vbo = _views_bulk_operations_get_field($view);
$operation = $vbo->get_operation_info($form_state['views_bulk_operations']['views_form_views_form']['operation']);
Bojan Živanović
committed
$query = drupal_get_query_parameters($_GET, array('q'));
Bojan Živanović
committed
$form['operation'] = array('#type' => 'value', '#value' => $operation);
Bojan Živanović
committed
$form['actions'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('form-actions')),
'#weight' => 100,
);
$form['actions']['submit'] = array(
Bojan Živanović
committed
'#type' => 'submit',
'#value' => t('Next'),
Bojan Živanović
committed
'#suffix' => l(t('Cancel'), $vbo->view->get_url(), array('query' => $query)),
Bojan Živanović
committed
);
drupal_set_title(t('Set parameters for %action', array('%action' => $operation['label'])), PASS_THROUGH);
Bojan Živanović
committed
$form += _views_bulk_operations_action_form(
Bojan Živanović
committed
$form,
$form_state,
$operation,
$vbo->view,
$form_state['views_bulk_operations']['views_form_views_form']['selection'],
$vbo->get_operation_settings($operation)
);
Bojan Živanović
committed
return $form;
}
Bojan Živanović
committed
* Multistep form callback for the "confirm" step.
Bojan Živanović
committed
function views_bulk_operations_confirm_form($form, &$form_state, $view, $output) {
Bojan Živanović
committed
$vbo = _views_bulk_operations_get_field($view);
$operation = $vbo->get_operation_info($form_state['views_bulk_operations']['views_form_views_form']['operation']);
$rows = $form_state['views_bulk_operations']['views_form_views_form']['selection'];
Bojan Živanović
committed
$query = drupal_get_query_parameters($_GET, array('q'));
Bojan Živanović
committed
$form = confirm_form($form,
t('Are you sure you want to perform %action on selected rows?', array('%action' => $operation['label'])),
Bojan Živanović
committed
array('path' => $view->get_url(), 'query' => $query),
theme('views_bulk_operations_confirmation', array('rows' => $rows, 'vbo' => $vbo))
Bojan Živanović
committed
);
return $form;
}
/**
* Goes through the submitted values, and returns
* an array of selected rows, in the form of
* $entity_id => $row_index.
Bojan Živanović
committed
*/
function _views_bulk_operations_get_selection($vbo, $form_state) {
Bojan Živanović
committed
$selection = array();
Bojan Živanović
committed
$field_name = $vbo->options['id'];
Bojan Živanović
committed
if (!empty($form_state['values'][$field_name])) {
Bojan Živanović
committed
// If using "force single", the selection needs to be converted to an array.
if (is_array($form_state['values'][$field_name])) {
$selected_rows = array_filter($form_state['values'][$field_name]);
}
else {
$selected_rows = array($form_state['values'][$field_name]);
}
Bojan Živanović
committed
// At this point, $selected_rows is an array of $row_number => $entity_id.
// We need the $entity_id to be the key, so the array gets flipped.
$selection = array_flip($selected_rows);
}
return $selection;
Bojan Živanović
committed
}
/**
* Validate all steps of the VBO multistep form.
*/
function views_bulk_operations_form_validate($form, &$form_state) {
Bojan Živanović
committed
$vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);
Bojan Živanović
committed
// This is not a VBO-enabled views form.
if (!$vbo) {
return;
}
switch ($form_state['step']) {
case 'views_form_views_form':
if ($form_state['values']['single_operation']) {
$operation = $vbo->get_operation_info($form_state['values']['operation']);
if ($operation['configurable']) {
_views_bulk_operations_action_validate($operation, $form, $form_state);
}
Bojan Živanović
committed
else {
if (!empty($form_state['clicked_button']['#hash'])) {
$form_state['values']['operation'] = $form_state['clicked_button']['#hash'];
}
if (!$form_state['values']['operation']) { // No action selected
form_set_error('operation', t('No operation selected. Please select an operation to perform.'));
}
Bojan Živanović
committed
$field_name = $vbo->options['id'];
$selection = _views_bulk_operations_get_selection($vbo, $form_state);
if (!$selection) {
Bojan Živanović
committed
form_set_error($field_name, t('Please select at least one item.'));
}
// if using force single, make sure only one value was submitted.
Bojan Živanović
committed
if ($vbo->options['vbo']['force_single'] && count($selection) > 1) {
Bojan Živanović
committed
form_set_error($field_name, t('You may only select one item.'));
Bojan Živanović
committed
}
$_SESSION['vbo_values'][$_GET['q']] = array(
'operation' => $form_state['values']['operation'],
'rows' => array(
'selection' => $selection,
'select_all' => $form_state['values']['select_all'],
),
);
Bojan Živanović
committed
case 'views_bulk_operations_config_form':
$operation = $vbo->get_operation_info($form_state['views_bulk_operations']['views_form_views_form']['operation']);
_views_bulk_operations_action_validate($operation, $form, $form_state);
Karim Ratib
committed
/**
* Helper function to adjust the selected set of rows.
Bojan Živanović
committed
*/
function _views_bulk_operations_adjust_selection(&$selection, $select_all, $vbo) {
if ($select_all) {
// Adjust selection to select all rows across pages.
Bojan Živanović
committed
$view = views_get_view($vbo->view->name);
$view->set_exposed_input($vbo->view->get_exposed_input());
$view->set_arguments($vbo->view->args);
$view->set_display($vbo->view->current_display);
$view->display_handler->set_option('pager', array('type' => 'none'));
$view->build();
Bojan Živanović
committed
// Unset every field except the VBO one (which holds the entity id).
// That way the performance hit becomes much smaller, because there is no
// chance of views_handler_field_field::post_execute() firing entity_load().
foreach ($view->field as $field_name => $field) {
if ($field_name != $vbo->options['id']) {
unset($view->field[$field_name]);
}
}
Bojan Živanović
committed
$view->execute($vbo->view->current_display);
$results = array();
Bojan Živanović
committed
foreach ($view->result as $row_index => $result) {
$results[$result->{$vbo->field_alias}] = $row_index;
$selection = $results;
}
Bojan Živanović
committed
// Adjust sticky selection accordingly.
$_SESSION['vbo_values'][$_GET['q']]['rows'] = array('selection' => $selection, 'select_all' => $select_all);
}
Bojan Živanović
committed
* Submit handler for all steps of the VBO multistep form.
function views_bulk_operations_form_submit($form, &$form_state) {
Bojan Živanović
committed
$vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);
Bojan Živanović
committed
// This is not a VBO-enabled views form.
if (!$vbo) {
return;
}
Bojan Živanović
committed
switch ($form_state['step']) {
case 'views_form_views_form':
$form_state['views_bulk_operations']['views_form_views_form'] = $form_state['values'];
$form_state['views_bulk_operations']['views_form_views_form']['selection'] = _views_bulk_operations_get_selection($vbo, $form_state);
_views_bulk_operations_adjust_selection(
Bojan Živanović
committed
$form_state['views_bulk_operations']['views_form_views_form']['selection'],
$form_state['views_bulk_operations']['views_form_views_form']['select_all'],
$vbo
Bojan Živanović
committed
$operation = $vbo->get_operation_info($form_state['values']['operation']);
if ($form_state['values']['single_operation']) {
if ($operation['configurable']) {
$form_state['views_bulk_operations']['operation_arguments'] = _views_bulk_operations_action_submit($operation, $form, $form_state);
}
Bojan Živanović
committed
if (!empty($operation['options']['skip_confirmation'])) {
Bojan Živanović
committed
break; // Go directly to execution
}
$form_state['step'] = 'views_bulk_operations_confirm_form';
Bojan Živanović
committed
else {
Bojan Živanović
committed
if (!$operation['configurable'] && !empty($operation['options']['skip_confirmation'])) {
Bojan Živanović
committed
break; // Go directly to execution
}
$form_state['step'] = $operation['configurable'] ? 'views_bulk_operations_config_form' : 'views_bulk_operations_confirm_form';
$form_state['single_operation'] = $form_state['values']['single_operation'];
Bojan Živanović
committed
case 'views_bulk_operations_config_form':
$form_state['step'] = 'views_bulk_operations_confirm_form';
$form_state['views_bulk_operations']['views_bulk_operations_config_form'] = $form_state['values'];
$operation = $vbo->get_operation_info($form_state['views_bulk_operations']['views_form_views_form']['operation']);
$form_state['views_bulk_operations']['operation_arguments'] = _views_bulk_operations_action_submit($operation, $form, $form_state);
Bojan Živanović
committed
if (!empty($operation['options']['skip_confirmation'])) {
Bojan Živanović
committed
return;
Bojan Živanović
committed
case 'views_bulk_operations_confirm_form':
Karim Ratib
committed
// Clean up unneeded SESSION variables.
unset($_SESSION['vbo_values'][$_GET['q']]);
Karim Ratib
committed
// Execute the VBO.
Bojan Živanović
committed
$operation = $vbo->get_operation_info($form_state['views_bulk_operations']['views_form_views_form']['operation']);
$operation_arguments = array();
if ($operation['configurable']) {
if ($form_state['single_operation']) {
$form_state['values'] += $form_state['views_bulk_operations']['views_form_views_form'];
}
else {
$form_state['values'] += $form_state['views_bulk_operations']['views_bulk_operations_config_form'];
}
Bojan Živanović
committed
$operation_arguments = $form_state['views_bulk_operations']['operation_arguments'];
}
_views_bulk_operations_execute(
Bojan Živanović
committed
$form_state['views_bulk_operations']['views_form_views_form']['selection'],
$operation,
$operation_arguments,
Bojan Živanović
committed
array('execution_type' => $vbo->options['vbo']['execution_type'], 'display_result' => $vbo->options['vbo']['display_result'])
);
// Clean up the form.
Bojan Živanović
committed
unset($form_state['step']);
unset($form_state['views_bulk_operations']);
Bojan Živanović
committed
$query = drupal_get_query_parameters($_GET, array('q'));
$form_state['redirect'] = array('path' => $vbo->view->get_url(), array('query' => $query));
Karim Ratib
committed
/**
* Execute the chosen action upon selected entities.
*
* There are three different ways of executing the action:
* - Executing the action directly.
* Simple, fast, fragile. Tells PHP to ignore the execution time limit,
* loads all selected entities, runs the action. Of course, it's still
* limited by available memory, so it's very easy to try and load too many
* entities, which kills the script. This is the only execution type
* available for aggregate actions, because they need all selected entities,
* and Batch API / Drupal Queue are all about segmenting the entity loading
* and action executing in order to get around memory limits and timeouts.
* - Using Batch API
* The most commonly used method. Spreads the execution across several
* background page requests, while showing a progress bar to the user.
* Each time _views_bulk_operations_batch_process() runs, it tries to load
* and execute a safe number of entities ($entity_load_capacity, defaults
* to 10). If the total selected number of entities is less than that, the
* code falls back to the direct execution method, since the execution can
* fit into a single page request, and there's no gain in using Batch API.
* - Using the Drupal Queue.
* Adds each entity separately to the queue, to be processed by a worker
* function, which usually happens on cron.
Karim Ratib
committed
*/
function _views_bulk_operations_execute($vbo, $rows, $operation, $operation_arguments, $options) {
Bojan Živanović
committed
global $user;
// Add action arguments, callback arguments, other settings needed by helper
// functions for specific execution types.
$params = array();
if ($operation['configurable'] && is_array($operation_arguments)) {
$params += $operation_arguments;
Karim Ratib
committed
if (isset($operation['callback arguments'])) {
$params += $operation['callback arguments'];
$params['revision'] = $vbo->revision;
$params['entity_load_capacity'] = $vbo->options['vbo']['entity_load_capacity'];
// If the selected rows need to be passed to the action, add them in.
if ($operation['pass rows']) {
foreach ($rows as $entity_id => $row_index) {
$rows[$entity_id] = $vbo->view->result[$row_index];
}
}
if (!$operation['aggregate'] && !empty($operation['options']['use_queue'])) {
Bojan Živanović
committed
$entity_type = $vbo->get_entity_type();
unset($params['view']);
foreach ($rows as $entity_id => $row) {
Bojan Živanović
committed
$job = array(
'description' => t('Perform %action on @type !entity_id.', array(
'%action' => $operation['label'],
Bojan Živanović
committed
'@type' => $entity_type,
'!entity_id' => $entity_id
Bojan Živanović
committed
)),
'arguments' => array($entity_id, $row, $operation, $params, $user->uid, $options['display_result']),
);
Bojan Živanović
committed
$queue = DrupalQueue::get('views_bulk_operations');
$queue->createItem($job);
if ($options['display_result']) {
Bojan Živanović
committed
drupal_set_message(t('Enqueued the selected operation (%action).', array(
Bojan Živanović
committed
'%action' => $operation['label'],
)));
elseif (!$operation['aggregate'] && $params['entity_load_capacity'] < count($rows)) {
// Save the options in the session because Batch API doesn't give a way to
// send a parameter to the finished callback.
$_SESSION['vbo_options']['display_result'] = $options['display_result'];
$_SESSION['vbo_options']['operation'] = $operation;
$_SESSION['vbo_options']['params'] = serialize($params);
$batch = array(
'operations' => array(
array('_views_bulk_operations_batch_process', array($rows)),
),
'finished' => '_views_bulk_operations_execute_finished',
'progress_message' => '',
'title' => t('Performing %action on selected rows...', array('%action' => $operation['label'])),
);
batch_set($batch);
}
else {
@set_time_limit(0);
Bojan Živanović
committed
$context['results']['rows'] = 0;
$context['results']['time'] = microtime(TRUE);
_views_bulk_operations_direct_process($operation, $rows, $params, $context);
Bojan Živanović
committed
_views_bulk_operations_execute_finished(TRUE, $context['results'], array(), 0, $options + array('operation' => $operation, 'params' => $params));
}
/**
* Worker function to handle actions coming from the Drupal Queue.
*/
Bojan Živanović
committed
function _views_bulk_operations_queue_process($data) {
list($entity_id, $row, $operation, $params, $uid, $display_result) = $data['arguments'];
Bojan Živanović
committed
$entity_type = $operation['type'];
$entities = _views_bulk_operations_entity_load($entity_type, array($entity_id), $params['revision']);
$entity = reset($entities);
// No entity found. It might have been deleted in the meantime. Abort.
if (!$entity) {
return;
}
$account = user_load($uid);
Bojan Živanović
committed
if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity, $account)) {
watchdog('views bulk operations', 'Skipped %action on @type %title due to insufficient permissions.', array(
Bojan Živanović
committed
'%action' => $operation['label'],
Bojan Živanović
committed
'@type' => $entity_type,
'%title' => _views_bulk_operations_entity_label($entity_type, $entity),
), WATCHDOG_ALERT);
return;
}
// Pass the selected row to the action if needed.
if ($operation['pass rows']) {
$params['rows'] = array($entity_id => $row);
}
Bojan Živanović
committed
_views_bulk_operations_action_do($operation, $entity_id, $entity, $params);
if ($display_result) {
watchdog('views bulk operations', 'Performed %action on @type %title.', array(
Bojan Živanović
committed
'%action' => $operation['label'],
Bojan Živanović
committed
'@type' => $entity_type,
'%title' => _views_bulk_operations_entity_label($entity_type, $entity),
), WATCHDOG_INFO);
Karim Ratib
committed
* Helper function to handle Batch API operations.
function _views_bulk_operations_batch_process($rows, &$context) {
if (!isset($context['sandbox']['progress'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['max'] = count($rows);
$context['results']['time'] = microtime(TRUE);
}
$operation = $_SESSION['vbo_options']['operation'];
$params = unserialize($_SESSION['vbo_options']['params']);
// Process rows in groups.
$remaining = $context['sandbox']['max'] - $context['sandbox']['progress'];
$count = min($params['entity_load_capacity'], $remaining);
$row_group = array_slice($rows, $context['sandbox']['progress'], $count, TRUE);
$entity_type = $operation['type'];
$entities = _views_bulk_operations_entity_load($entity_type, array_keys($row_group), $params['revision']);
foreach ($entities as $entity_id => $entity) {
if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity)) {
$context['results']['log'][] = t('Skipped %action on @type %title due to insufficient permissions.', array(
'%action' => $operation['label'],
'@type' => $entity_type,
'%title' => _views_bulk_operations_entity_label($entity_type, $entity),
));
$context['sandbox']['progress']++;
continue;
}
// Pass the selected row to the action if needed.
if ($operation['pass rows']) {
$params['rows'] = array($entity_id => $rows[$entity_id]);
}
_views_bulk_operations_action_do($operation, $entity_id, $entity, $params);
$context['results']['log'][] = t('Performed %action on @type %title.', array(
Bojan Živanović
committed
'%action' => $operation['label'],
'@type' => $entity_type,
'%title' => _views_bulk_operations_entity_label($entity_type, $entity),
$context['sandbox']['progress']++;
unset($row_group[$entity_id]);
}
// Looks like some entities couldn't be loaded. Adjust the count and move on.
if (count($row_group)) {
$context['sandbox']['progress'] += count($row_group);
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
// Provide an estimation of the completion level we've reached.
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
$context['message'] = t('Processed @current out of @total', array('@current' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']));
else {
// All done. Save the number of results processed, for the finish callback.
$context['results']['rows'] = $context['sandbox']['progress'];
}
Karim Ratib
committed
* Helper function for direct execution operations.
function _views_bulk_operations_direct_process($operation, $rows, $params, &$context) {
$entity_type = $operation['type'];
$entities = _views_bulk_operations_entity_load($entity_type, array_keys($rows), $params['revision']);
foreach ($entities as $id => $entity) {
if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity)) {
$context['results']['log'][] = t('Skipped %action on @type %title due to insufficient permissions.', array(
Bojan Živanović
committed
'%action' => $operation['label'],
'@type' => $entity_type,
'%title' => _views_bulk_operations_entity_label($entity_type, $entity),
}
if (empty($entities)) {
return;
}
Bojan Živanović
committed
if ($operation['aggregate']) {
// Pass the selected rows to the action if needed.
if ($operation['pass rows']) {
$params['rows'] = $rows;
}
_views_bulk_operations_action_aggregate_do($operation, $entities, $params);
$context['results']['log'][] = t('Performed aggregate %action on @types %ids.', array(
Bojan Živanović
committed
'%action' => $operation['label'],
'@types' => format_plural(count($entities), $entity_type, $entity_type . 's'),
'%ids' => implode(',', array_keys($entities)),
));
$context['results']['rows'] += count($entities);
}
else {
foreach ($entities as $entity_id => $entity) {
// Pass the selected row to the action if needed.
if ($operation['pass rows']) {
$params['rows'] = array($entity_id => $rows[$entity_id]);
}
_views_bulk_operations_action_do($operation, $entity_id, $entity, $params);
$context['results']['log'][] = t('Performed %action on @type %title.', array(
'%action' => $operation['label'],
'@type' => $entity_type,
'%title' => _views_bulk_operations_entity_label($entity_type, $entity),
));
$context['results']['rows'] += 1;
}
Karim Ratib
committed
/**
Bojan Živanović
committed
* Helper function to cleanup operations.
Karim Ratib
committed
*/
Bojan Živanović
committed
function _views_bulk_operations_execute_finished($success, $results, $operations, $elapsed, $options = NULL) {
if ($success) {
if (!empty($results['rows'])) {
Bojan Živanović
committed
$row_count = format_plural($results['rows'], '1 row', '@count rows');
Bojan Živanović
committed
$message = t('!row_count processed in about !time ms:', array('!row_count' => $row_count, '!time' => round((microtime(TRUE) - $results['time']) * 1000)));
}
else {
$message = t('No rows were processed:');
}
$message .= "\n". theme('item_list', array('items' => $results['log']));
}
else {
// An error occurred.
// $operations contains the operations that remained unprocessed.
$error_operation = reset($operations);
$message = t('An error occurred while processing @operation with arguments: @arguments',
array('@operation' => $error_operation[0], '@arguments' => print_r($error_operation[0], TRUE)));
}
if (empty($options)) {
$options = $_SESSION['vbo_options'];
}
// Inform other modules that VBO has finished executing.
module_invoke_all('views_bulk_operations_finish', $options['operation'], $options['params'], array('results' => $results));
if (!empty($options['display_result'])) {
drupal_set_message($message);
}
unset($_SESSION['vbo_options']); // unset the options which were used for just one invocation
}
Karim Ratib
committed
* Helper function to execute one operation.
function _views_bulk_operations_action_do($operation, $entity_id, $entity, $params, $account = NULL) {
_views_bulk_operations_action_permission($operation, $account);
Bojan Živanović
committed
$params['entity_type'] = $operation['type'];
if ($operation['source'] == 'action') {
actions_do($operation['callback'], $entity, $params);
Bojan Živanović
committed
// Actions that specify 'changes_property' need to be explicitly saved.
if (in_array('changes_property', $operation['behavior'])) {
entity_save($operation['type'], $entity);
}
Bojan Živanović
committed
elseif ($operation['source'] == 'rules_action') {
call_user_func_array($operation['callback'], array('entity' => $entity, 'context' => $params));
}
Karim Ratib
committed
* Helper function to execute an aggregate operation.
function _views_bulk_operations_action_aggregate_do($operation, $entities, $params) {
_views_bulk_operations_action_permission($operation);
Bojan Živanović
committed
$params['entity_type'] = $operation['type'];
actions_do($operation['callback'], $entities, $params);
Karim Ratib
committed
* Helper function to verify access permission to execute operation.
function _views_bulk_operations_action_permission($operation, $account = NULL) {
Bojan Živanović
committed
global $user;
if (!$account) {
$account = $user;
}
$user_label = _views_bulk_operations_entity_label('user', $account);
if (module_exists('actions_permissions')) {
Bojan Živanović
committed
$perm = actions_permissions_get_perm($operation['original label'], $operation['callback']);
if (!user_access($perm, $account)) {
Bojan Živanović
committed
watchdog('actions permissions', 'An attempt by user %user to !perm was blocked due to insufficient permissions.',
array('!perm' => $perm, '%user' => $user_label), WATCHDOG_ALERT);
Bojan Živanović
committed
drupal_access_denied();
// Check against additional permissions.
Bojan Živanović
committed
if (!empty($operation['permissions'])) {
foreach ($operation['permissions'] as $perm) {
if (!user_access($perm, $account)) {
watchdog('actions permissions', 'An attempt by user %user to !perm was blocked due to insufficient permissions.',
array('!perm' => $perm, '%user' => $user_label), WATCHDOG_ALERT);
Bojan Živanović
committed
drupal_access_denied();
drupal_exit();
}
}
}
// If this is a rules action, check its permissions.
if ($operation['source'] == 'rules_action') {
if (!rules_action('component_' . $operation['key'])->access()) {
watchdog('actions permissions', 'An attempt by user %user to rules action !key was blocked due to insufficient permissions.',
array('!key' => $operation['key'], '%user' => $user_label), WATCHDOG_ALERT);
Bojan Živanović
committed
drupal_access_denied();
* Helper function to verify access permission to operate on an entity.
function _views_bulk_operations_entity_access($operation, $entity_type, $entity, $account = NULL) {
if (!entity_type_supports($entity_type, 'access')) {
return TRUE;
}
$access_ops = array(
VBO_ACCESS_OP_VIEW => 'view',
VBO_ACCESS_OP_UPDATE => 'update',
VBO_ACCESS_OP_CREATE => 'create',
VBO_ACCESS_OP_DELETE => 'delete',
);
foreach ($access_ops as $bit => $op) {
if ($operation['access operation'] & $bit) {
if (!entity_access($op, $entity_type, $entity, $account)) {
return FALSE;
}
}
}
return TRUE;
Karim Ratib
committed
* Helper function to let the configurable action provide its configuration form.
Bojan Živanović
committed
function _views_bulk_operations_action_form($form, &$form_state, $action, $view, $selection, $settings) {
Bojan Živanović
committed
$context = array(
'view' => $view,
'selection' => $selection,
'settings' => $settings,
'action' => $action,
);
if ($action['source'] == 'rules_action') {
$form = $action_form($form, $form_state, $context);
}
else {
$form = $action_form($context);
}
return $form;
Karim Ratib
committed
* Helper function to let the configurable action validate the form if it provides a validator.
Bojan Živanović
committed
function _views_bulk_operations_action_validate($action, $form, $form_state) {
$action_validate = $action['callback'].'_validate';
Bojan Živanović
committed
$action_validate($form, $form_state);
Karim Ratib
committed
* Helper function to let the configurable action process the configuration form.
Bojan Živanović
committed
function _views_bulk_operations_action_submit($action, $form, $form_state) {
$action_submit = $action['callback'].'_submit';
Bojan Živanović
committed
return $action_submit($form, $form_state);
Karim Ratib
committed
/**
* Theme function to show the confirmation page before executing the action.
*/
function theme_views_bulk_operations_confirmation($variables) {
$vbo = $variables['vbo'];
$entity_type = $vbo->get_entity_type();
Bojan Živanović
committed
$rows = $variables['rows'];
$result_count = count($vbo->view->result);
$row_count = count($rows);
$items = array();
Bojan Živanović
committed
// All rows on all pages have been selected.
if ($result_count < $row_count) {
$entity_ids = array_slice(array_keys($rows), 0, $result_count);
$entities = _views_bulk_operations_entity_load($entity_type, $entity_ids, $vbo->revision);
Bojan Živanović
committed
foreach ($entities as $entity) {
$items[] = check_plain(_views_bulk_operations_entity_label($entity_type, $entity));
}
$items[] = t('...and <strong>!count</strong> more.', array('!count' => $row_count - $result_count));
Bojan Živanović
committed
else {
$entities = _views_bulk_operations_entity_load($entity_type, array_keys($rows), $vbo->revision);
Bojan Živanović
committed
foreach ($entities as $entity) {
$items[] = check_plain(_views_bulk_operations_entity_label($entity_type, $entity));